There is a Java-based web application in our company which is going to be extended to be able to communicate with a certain tax authority through an API. The tax authority issues a pfx file containing a key and a certificate and expects all API requests to contain the pfx (as base64 string) and its pin (the password for encrypting the pfx) in certain fields. The Java-based application uses a jks Java key store to store all kind of certificates and keys. The team has decided that the pfx should be stored in the key sore using Java Key Tool. The service responsible for communication with the tax authority then has to retrieve it and use it for the communication through the API. This is exactly what I am supposed to implement. Let’s start then.
Beside the Road: Java Keytool
The Keytool executable is distributed with the Java SDK. Here is a nice tutorial about it. I just add two commands for convenience below.
- How to use keytool command to see what is inside a pfx or key store file:
keytool -v -list -storetype pkcs12 -keystore ("path to the pfx file") keytool -v -list -storetype jks -keystore ("path to the jks file")
- How to use keytool command to add a pfx to a key store
keytool -importkeystore -srckeystore ("path to the pfx file") -srcstoretype pkcs12 -destkeystore ("path to the jks file") -deststoretype jks -deststorepass ("the password of the jks")
First off, I noticed the pfx has been decrypted and its contents are added to the key store. So I need to somehow recreate the pfx as base 64 string. I use the “KeyStore Explorer” on Ubuntu as a graphical tool to examine jks as well as pfx files:
Please note that this is just a sample jks file. the real key store contains a lot of other keys and certificates…
Notice the two tiny locks in the above screenshot: these indicate that the entries in the key store are also locked.
Getting the content from the key store is easy. You just need to know what you are looking for, or rather, its alias. (Use the KeyStore Explorer or keytool -v -list as above to find out what is inside with their corresponding aliases.) Then just load the key store:
KeyStore keyStore = KeyStore.getInstance("JKS"); keyStore.load(new FileInputStream("keystore.jks"), KEYPASS.toCharArray());
Here KEYPASS is the so called pin or password for decrypting the key store. Now we can retrieve the two fellows as follows:
java.security.cert.Certificate chain1 = keyStore.getCertificateChain("encryptionkey"); java.security.cert.Certificate chain2 = keyStore.getCertificateChain("signaturekey"); PrivateKey key1 = (PrivateKey)keyStore.getKey("encryptionkey", KEYPASS_ORG_PFX.toCharArray()); PrivateKey key2 = (PrivateKey)keyStore.getKey("signaturekey", KEYPASS_ORG_PFX.toCharArray());
Notice how I am reading each alias once as a certificate chain and once as a key. To recreate the pfx file, I need them both. Notice also, to retrieve the key1 and key2 I have to know “KEYPASS_ORG_PFX”, the corresponding protection key from the original pfx.
What I am going to do next is, to create a protection parameter and two entries as the ingredients of my pfx file as follows:
KeyStore.ProtectionParameter protParam = new KeyStore.PasswordProtection(KEYPASS_NEW_PFX.toCharArray()); KeyStore.PrivateKeyEntry pkEntry1 = new KeyStore.PrivateKeyEntry(key1, chain1); KeyStore.PrivateKeyEntry pkEntry2 = new KeyStore.PrivateKeyEntry(key2, chain2);
The KEYPASS_NEW_PFX is the key encrypting the entries of the newly created pfx file, which we can choose here (it has nothing to do with the key store password or the KEYPASS_ORG_PFX). I have to encrypt the entries, it is a requirement for the pfx to be accepted by the tax authorities. Having the ingredients, I just need to create a new Keystore object and add them to it:
KeyStore keyStorePfx = KeyStore.getInstance("PKCS12"); keyStorePfx.load(null, null); keyStorePfx.setEntry("encryptionkey", pkEntry1, protParam); keyStorePfx.setEntry("signaturekey", pkEntry2, protParam);
Finally, I need to write it to an OutputStream and convert it to base64 string:
ByteArrayOutputStream bs = new ByteArrayOutputStream(); keyStorePfx.store(bs, KEYPASS_NEW_PFX.toCharArray()); byte pfxBytes = Base64.getEncoder().encode(bs.toByteArray()); String pfxBase64 = new String(encoded);
I just used the same KEYPASS_NEW_PFX for encrypting the pfx. The resulting base64 string is written to the pfxBase64, ready to be used.