在Java中创建SSL套接字时,为什么会出现“无法存储非PrivateKeys”错误?

时间:2011-07-11 20:41:21

标签: java ssl ibm-midrange

我工作较早的IBM i系列(IBM-I,i5OS,AS / 400,等),与Java 5 JVM(经典,不ITJ J9)上O / S版本V5R3M0。

简而言之,这是一个场景:

  1. 我创建使用JKS类型的密钥存储Portecle 1.7(注:我曾尝试我的密钥存储转换为JCEKS但被拒绝,因为不支持的格式,所以看来JKS是唯一的选择iSeries机器(至少是我所使用的版本)。
  2. 然后,我创建了一个密钥对和CSR,并将CSR发送给Thawte进行签名。
  3. 我成功地使用PKCS#7的格式导入整个证书链,其中包括我的证书,Thawte中介和Thawte的服务器根导入的签名的证书从Thawte的。
  4. 这一切都按预期工作。

    然而,当我跑了JVM,正确配置为指向存储和提供它的密码(这是我在与Portecle测试创建自签名证书过去所做的那样),并尝试启动我的Web服务器在443上,我得到以下安全例外:

    java.security.KeyStoreException: Cannot store non-PrivateKeys
    

    有谁能告诉我哪里出错了,或者我接下来应该检查什么?

3 个答案:

答案 0 :(得分:31)

“无法存储非PrivateKeys”错误消息通常表示您正在尝试使用具有JKS密钥库类型的秘密对称密钥。 JKS密钥库类型仅支持非对称(公共/私有)密钥。您必须创建一个JCEKS类型的新密钥库来支持密钥。

答案 1 :(得分:5)

事实证明,这是一个微妙的问题,如果其他人有类似的东西,这里值得给出答案。

TLDR的答案是我没有检查我的密钥和证书是否为空,因此尝试将空密钥和证书添加到密钥库。更长的答案如下。

我们将Web服务器设置为使用SSL的方式,特别是支持我们用户的典型配置,其中IP地址用于配置网站侦听地址而不是DNS名称,它是将证书定位在使用别名主密钥存储区,并创建一个仅包含该网站证书的临时密钥存储区,使用该密钥存储区配置SSL上下文和SSL套接字工厂,如下所示:

// CREATE EPHEMERAL KEYSTORE FOR THIS SOCKET USING THE DESIRED CERTIFICATE
try {
    final char[]      BLANK_PWD=new char[0];
    SSLContext        ctx=SSLContext.getInstance("TLS");
    KeyManagerFactory kmf=KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
    Key               ctfkey=mstkst.getKey(svrctfals,BLANK_PWD);
    Certificate[]     ctfchn=mstkst.getCertificateChain(svrctfals);
    KeyStore          sktkst;

    sktkst=KeyStore.getInstance("jks");
    sktkst.load(null,BLANK_PWD);
    sktkst.setKeyEntry(svrctfals,ctfkey,BLANK_PWD,ctfchn);
    kmf.init(sktkst,BLANK_PWD);
    ctx.init(kmf.getKeyManagers(),null,null);
    ssf=ctx.getServerSocketFactory();
    }
catch(java.security.GeneralSecurityException thr) {
    throw new IOException("Cannot create server socket factory using ephemeral keystore ("+thr+")",thr);
    }

请注意,它使用空白密码从主密钥库中提取私钥和证书。这是我的问题 - 出于习惯使用keytool,我创建了带密码的私钥对(与密钥库相同的密码)。

因为我在证书上有密码,所以未提取密钥和证书,并且将null传递给sktkst.setKeyEntry(svrctfals,ctfkey,BLANK_PWD,ctfchn);但是,setKeyEntry使用Key检查传递的instanceof并且(正确地)结论null不是instanceof PrivateKey,导致我看到的误导性错误。

更正后的代码会检查是否找到了密钥和证书,并发送了相应的错误:

// CREATE EPHEMERAL KEYSTORE FOR THIS SOCKET USING THE DESIRED CERTIFICATE
try {
    final char[]      BLANK_PWD=new char[0];
    SSLContext        ctx=SSLContext.getInstance("TLS");
    KeyManagerFactory kmf=KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
    Key               ctfkey=mstkst.getKey(svrctfals,BLANK_PWD);
    Certificate[]     ctfchn=mstkst.getCertificateChain(svrctfals);
    KeyStore          sktkst;

    if(ctfkey==null) {
        throw new IOException("Cannot create server socket factory: No key found for alias '"+svrctfals+"'");
        }
    if(ctfchn==null || ctfchn.length==0) {
        throw new IOException("Cannot create server socket factory: No certificate found for alias '"+svrctfals+"'");
        }

    sktkst=KeyStore.getInstance("jks");
    sktkst.load(null,BLANK_PWD);
    sktkst.setKeyEntry(svrctfals,ctfkey,BLANK_PWD,ctfchn);
    kmf.init(sktkst,BLANK_PWD);
    ctx.init(kmf.getKeyManagers(),null,null);
    ssf=ctx.getServerSocketFactory();
    }
catch(java.security.GeneralSecurityException thr) {
    throw new IOException("Cannot create server socket factory using ephemeral keystore ("+thr+")",thr);
    }

答案 2 :(得分:3)

您可以在一个SSLContext内处理所有内容,而不是使用短暂的密钥库。

您需要使用自定义X509KeyManager初始化SSLContext,而不是使用默认KeyManagerFactory指定的X509KeyManager。在此chooseServerAlias(String keyType, Principal[] issuers, Socket socket)中,{{1}}应返回不同的别名,具体取决于从套接字获取的本地地址。

这样,您就不必担心将私钥从一个密钥库复制到另一个密钥库,这甚至可以用于您无法提取(并因此复制)但仅使用私钥的密钥库类型,例如PKCS#11。