Search code examples
javabouncycastlekeystore

Java save keys to keystore KeyStoreException


I try to generate an RSA CA KeyPair and Certificate and save it to the keystore. My code is:

import java.io.FileOutputStream;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.security.Security;
import java.security.cert.X509Certificate;
import java.util.Date;

import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;

    private static final  String storeType = "PKCS12";
        private static final  String storePassword = "password";
        private static final  String storePath = "/usr/lib/java/keystore.ks";
        private static final Date startDate = new Date(System.currentTimeMillis());                                         // time from which certificate is valid
        private static final Date expiryDate = new Date(System.currentTimeMillis() + 2L * 365L * 24L * 60L * 60L * 1000L);   // time after which certificate is not valid (2 years)
        private static final BigInteger serialNumber = BigInteger.valueOf(System.currentTimeMillis());  
        private static X500Name issuer;
        private static X500Name subject; 
        private static KeyPair pair;

        public static void saveKeys() throws Exception{
            Security.addProvider(new BouncyCastleProvider());

            KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
            keyPairGen.initialize(2048, new SecureRandom());
            pair = keyPairGen.generateKeyPair();
            byte[] pub = pair.getPublic().getEncoded();
            byte[] priv = pair.getPrivate().getEncoded();
            SubjectPublicKeyInfo pubInfo = SubjectPublicKeyInfo.getInstance(pub);

            issuer = new X500Name("CN=CA");
            subject = issuer;

            X509v3CertificateBuilder certBuilder = new X509v3CertificateBuilder(
                  issuer,       //issuer (CA)
                  serialNumber,
                  startDate, expiryDate,
                  subject,      //subject
                  pubInfo);

            //signature for sig
            ContentSigner sigGen = new JcaContentSignerBuilder("SHA1WithRSA").build(pair.getPrivate());
            X509CertificateHolder certHolder = certBuilder.build(sigGen);
            X509Certificate caCert = new JcaX509CertificateConverter().getCertificate(certHolder);

            X509Certificate[] chain = new X509Certificate[3];
            chain[2] = caCert;

            KeyStore store;
            try {
                store = KeyStore.getInstance(storeType);
                store.load(null,null);
            store.setKeyEntry("CA-Key", priv, chain);  
            store.store(new FileOutputStream("public.p12"), null);

            } catch (Exception e) {
                e.printStackTrace();
            } 

Then I get the Error:

java.security.KeyStoreException: Private key is not stored as PKCS#8 EncryptedPrivateKeyInfo: java.io.IOException: overrun, bytes = 1194
    at sun.security.pkcs12.PKCS12KeyStore.engineSetKeyEntry(PKCS12KeyStore.java:687)
    at java.security.KeyStore.setKeyEntry(KeyStore.java:1174)
    at storeKeys.saveKeys(storeKeys.java:95)
    at storeKeys.main(storeKeys.java:146)
Caused by: java.io.IOException: overrun, bytes = 1194

How can I encrypt the private key to the right format? And where can I give the Keystore its path to save the keys to?


Solution

  • If you see that method's java doc, it says:

    Assigns the given key (that has already been protected) to the given alias.

    If the protected key is of type java.security.PrivateKey, it must be accompanied by a certificate chain certifying the corresponding public key. If the underlying keystore implementation is of type jks, key must be encoded as an EncryptedPrivateKeyInfo as defined in the PKCS #8 standard.

    It says JKS, but looks like it is expecting the encrypted private key format for PKCS12 also.

    So you cannot just pass in the private key byte array.

    To make things easier, you can do this:

    PrivateKeyEntry privateKeyEntry = new PrivateKeyEntry(pair.getPrivate(), chain);
    store.setEntry("CA-Key", privateKeyEntry, new KeyStore.PasswordProtection(storePassword.toCharArray()));
    

    And in your keystore.store(..) method, the first argument is the keystore path. And the second argument is the keystore's password.

    So you could do something like this:

    store.store(new FileOutputStream(new File(storePath)), storePassword.toCharArray());