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

我正在使用旧的IBM iSeries(IBM-i,i5OS,AS / 400等),在O / S版本V5R3M0上使用Java 5 JVM(Classic,而不是ITJ J9)。

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

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

这一切都按预期工作。

但是,当我运行JVM时,正确配置为指向商店并提供密码(我过去使用在Portecle中创建的自签名证书进行测试),并尝试在443上启动我的Web服务器,我得到以下安全性exception:

java.security.KeyStoreException: Cannot store non-PrivateKeys 

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

您可以在单个SSLContext处理所有内容,而不是使用临时密钥库。

您需要使用自定义X509KeyManager初始化SSLContext而不是使用默认KeyManagerFactory给出的SSLContext 。 在这个X509KeyManagerchooseServerAlias(String keyType, Principal[] issuers, Socket socket)应该返回一个不同的别名,具体取决于从套接字获得的本地地址。

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

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

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

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使用instanceof检查传递的Key并且(正确地)结束null不是instanceof PrivateKeyinstanceof 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); }