Creating Java KeyStore Programmatically
Challenge: A Java application at runtime that will receive a public X509 certificate and a RSA private key will create a Java KeyStore on the fly. Normally, I don't have to deal with the JSSE API in too much inner detail. The complex cases don't really come up in normal year to year application building and integrations. But this gave me a chance to play with that API more. I could find point examples for each specific point on the web, but I had a hard time finding an overall already together of what exactly I wanted to do. Thus, sharing my end result.
Preface: There is a command line keystore tool that is much easier to use and has tons of example on the web of how to use. Thus, this is only for the time that you really need to do this. For instance, this can be used if the private key is coming from some sort of secured source. But, be aware that throwing around a raw private key could be more of a risk than using a nice password protected Java KeyStore file. But, then we wouldn't have a challenge and an article, right?
Step 1 - Create a New Empty KeyStore
This is pretty simple - get an instance and load from null.
public KeyStore createEmptyKeyStore() throws IOException, GeneralSecurityException {
KeyStore keyStore = KeyStore.getInstance(“JKS”);
keyStore.load(null,null);
return keyStore;
}
Step 2 - Load Public Certificate
Loading the certificate is easy. We just need an InputStream to the source. That could be from a file, classpath, or what have you. (And if the certificate is in memory, you can always wrap a ByteArrayInputStream on it.)
public X509Certificate loadCertificate(InputStream publicCertIn) throws IOException, GeneralSecurityException {
CertificateFactory factory = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate)factory.generateCertificate(publicCertIn);
return cert;
}
Step 3 - Load Private Certificate
This is the hardest part. Compared to the public certificate, there's a surprising large amount of code.
One gotcha. The typical private key, such as generated by openssl, has a BEGIN and END lines with the actual certificate base64 encoded in the middle. You need to strip the BEGIN/END lines off and base64 decode it. That could be a string replace, parse, or I saw a really nifty example of using a regular expression. I liked the last one the best, so I show the code that way:
public PrivateKey loadPrivateKey(InputStream privateKeyIn) throws IOException, GeneralSecurityException {
//need the full file - org.apache.commons.io.IOUtils is handy
byte[] fullFileAsBytes = IOUtils.toByteArray( privateKeyIn );
//remember this is supposed to be a text source with the BEGIN/END and base64 in the middle of the file
String fullFileAsString = new String(fullFileAsBytes);
//nifty regular expression to extract out between BEGIN/END
Pattern parse = Pattern.compile("(?m)(?s)^---*BEGIN.*---*$(.*)^---*END.*---*$.*");
String encoded = parse.matcher(fullFileAsString).replaceFirst("$1");
//decode the Base64 string
byte[] keyDecoded = Base64.getMimeDecoder().decode(encoded);
//for my example, the source is in common PKCS#8 format
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyDecoded);
//from there we can use the KeyFactor to generate
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
return privateKey;
}
That is a lot of code to do something that doesn't seem like it should be so much work. I was digging into this more and there's another API - Bouncy House. It offers some useful utilities and support for other formats. Using it makes for smaller code, here's the same example. (It does take a Reader, but you could convert from InputStream. Besides, what is the best source object really depends on how exactly the private key is coming in.)
public X509Certificate loadPrivateKey(Reader privateKeyReader) throws IOException, GeneralSecurityException {
Security.addProvider(new BouncyCastleProvider());
PEMParser pemParser = new PEMParser(br);
PrivateKeyInfo pki = (PrivateKeyInfo)pemParser.readObject();
JcaPEMKeyConverter conv = new JcaPEMKeyConverter();
PrivateKey privateKey = conv.getPrivateKey(pki);
return privateKey;
}
//Related note, if your file is a key pair, it handles that too, instead:
PEMKeyPair pemKeyPair = (PEMKeyPair) pemParser.readObject(); //here
KeyPair kp = new JcaPEMKeyConverter().getKeyPair(pemKeyPair);
PrivateKey privateKey = kp.getPrivate();
Step 4 - Bring It Together
Now you have a public certificate, a private key, and an empty keystore. Time to bring them together.
public KeyStore createKeyStore(InputStream publicCertIn, InputStream privateKeyIn) throws IOException, GeneralSecurityException {
KeyStore keyStore = createEmptyKeyStore();
X509Certificate publicCert = loadCertificate(publicCertIn);
PrivateKey privateKey = loadPrivateKey(privateKeyIn);
keyStore.setCertificateEntry("aliasForCertHere", publicCert);
keyStore.setKeyEntry("aliasForPrivateKeyHere", privateKey, "PasswordForPrivateKeyHere", new Certificate[]{publicCert});
return keyStore;
}
From there, depends on what you need to do. In my case, I next needed the KeyStore converted into bytes. From there you could write to a file, give to an API that needs a KeyStore, or what have you. In any case, here's how you convert from KeyStore into a Byte Array.
public byte[] convertKeyStoreToBytes(KeyStore keyStore) throws IOException, GeneralSecurityException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
keyStore.store(out, "PasswordForPrivateKeyHere".toCharArray());
byte[] bytes = out.toByteArray();
return bytes;
}
byte[] keyStoreAsBytes = out.toByteArray();
Since this was an example, I abbreviated parts of the above. Code could be cleaned up, resources could be closed, exceptions could be narrowed down or not, embedded literals could be passed or constants, and so on. My main goal was to show using how to use the API to do these steps.
Great job. Tanks
exactly what I was looking for. Thanks