Why do I get the error “Cannot store non-PrivateKe

2019-01-23 20:39发布

I am working on an older IBM iSeries (IBM-i, i5OS, AS/400, etc), with a Java 5 JVM (Classic, not ITJ J9) on O/S version V5R3M0.

Here is the scenario in a nutshell:

  1. I created a key-store of type JKS using Portecle 1.7 (Note: I did try converting my key-store to JCEKS but that was rejected as an unsupported format, so it appears that JKS is the only option with the iSeries machine (at least the version I am on).
  2. I then created a key-pair and CSR and sent the CSR to Thawte to be signed.
  3. I imported the signed certificate from Thawte successfully using the PKCS#7 format to import the entire certificate chain, which included my certificate, the Thawte intermediary and the Thawte server root.

This all worked as expected.

However, when I ran up the JVM, configured properly to point to the store and supply it's password (which I have done in the past with self-signed certificates created in Portecle for testing), and try to start my web server on 443, I get the following security exception:

java.security.KeyStoreException: Cannot store non-PrivateKeys

Can anyone tell me where I went wrong, or what I should check next?

3条回答
不美不萌又怎样
2楼-- · 2019-01-23 20:48

As it turns out, this was a subtle problem, and it's worth giving the answer here in case someone else has something similar.

The TLDR answer is that I did not check that my key and certificate were not null and as a result attempted to add a null key and certificate to a key-store. The longer answer follows.

The way we have our web server set up to use SSL, specifically to support our user's typical configuration where the IP address is used to configure the web site listen address rather than a DNS name, is that it locates the certificate in the master key-store using the alias, and creates an ephemeral key-store containing just the certificate for that web site, using that key-store to configure an SSL context and an SSL socket factory, like so:

// 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);
    }

Notice that it uses a blank password for extracting the private key and certificates from the master key-store. That was my problem - I had, out of habit from using keytool, created the private key-pair with a password (the same password as the key-store).

Because I had a password on the certificate, the key and certificate were not extracted, and null was passed to sktkst.setKeyEntry(svrctfals,ctfkey,BLANK_PWD,ctfchn); However, setKeyEntry checks the passed Key using instanceof and concludes (correctly) that null is not an instanceof PrivateKey, resulting in the misleading error I was seeing.

The corrected code checks that a key and certificate are found and sends appropriate errors:

// 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);
    }
查看更多
SAY GOODBYE
3楼-- · 2019-01-23 20:56

The "Cannot store non-PrivateKeys" error message usually indicates you are trying to use secret symmetric keys with a JKS keystore type. The JKS keystore type only supports asymmetric (public/private) keys. You would have to create a new keystore of type JCEKS to support secret keys.

查看更多
霸刀☆藐视天下
4楼-- · 2019-01-23 21:09

Instead of using an ephemeral keystore, you could handle everything within a single SSLContext.

You would need to initialise your SSLContext using an custom X509KeyManager instead of using the one given by the default KeyManagerFactory. In this X509KeyManager,chooseServerAlias(String keyType, Principal[] issuers, Socket socket) should return a different alias depending on the local address obtained from the socket.

This way, you wouldn't have to worry about copying the private key from one keystore to another, and this would even work for keystore types from which you can't extract (and thus copy) but only use the private key, e.g. PKCS#11.

查看更多
登录 后发表回答