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

Law*_*Dol 7 java ssl ibm-midrange

我正在使用旧的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服务器,我收到以下安全异常:

java.security.KeyStoreException: Cannot store non-PrivateKeys
Run Code Online (Sandbox Code Playgroud)

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

小智 36

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


Law*_*Dol 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);
    }
Run Code Online (Sandbox Code Playgroud)

请注意,它使用空密码从主密钥库中提取私钥和证书.这是我的问题 - 出于习惯使用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);
    }
Run Code Online (Sandbox Code Playgroud)


Bru*_*uno 3

您可以在单个SSLContext.

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

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