我工作较早的IBM i系列(IBM-I,i5OS,AS / 400,等),与Java 5 JVM(经典,不ITJ J9)上O / S版本V5R3M0。
简而言之,这是一个场景:
这一切都按预期工作。
然而,当我跑了JVM,正确配置为指向存储和提供它的密码(这是我在与Portecle测试创建自签名证书过去所做的那样),并尝试启动我的Web服务器在443上,我得到以下安全例外:
java.security.KeyStoreException: Cannot store non-PrivateKeys
有谁能告诉我哪里出错了,或者我接下来应该检查什么?
答案 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。