Mutual TLS communication using PKCS11 keystore in java
Java based application can make use of pkcs11 keystore to store the private keys. The SunPKCS11 provider is added to the list of security providers, to provide pkcs11 interface to applications. Using keytool, private keys can be provisioned into pkcs11 token and later loaded into keystore java object. Create CA store, that can be used as truststore during TLS authentication. The SSL context will use the keystore for private key and associated certificates. The truststore will be used for ca certificates, the context will trust. Finally enable client auth for mutual TLS.
Static and dynamic way of adding provider
You have the option of adding provider using static or dynamic method. In static method, you will be adding the provider to JRE, so that all application can use it using the getProvider method. You can choose to add dynamically to each application, using addProvider method and tool arguments. In either case, pkcs11 configuration file is required describing the token.
Add provider statically to JRE
Open java.security file and add the following line to the list of providers. Where n is the next provider number in the list.
security.provider.n=sun.security.pkcs11.SunPKCS11 /home/test/pkcs11.cfg
From application, now you can get the provider
Provider p = Security.getProvider("SunPKCS11-pkcs11Test");
where pkcs11Test is the name specified in the pkcs11.cfg file
Add provider dynamically to each application
String configName = "/home/test/pkcs11.cfg";
Provider p = new SunPKCS11(configName);
Security.addProvider(p);
Provision the keys on both server and client using the following steps
Create pkcs12 keystore with keys and certificates.
openssl pkcs12 -export -in <ca signed certificate> -inkey <private key> -out store.p12 -name keyimport -CAfile ca.cert -caname tlsca
Import keystore to pkcs11 token using static provider.
keytool -importkeystore -deststorepass <dest passwd> -destkeystore NONE -srckeystore store.p12 -deststoretype PKCS11 -srcstoretype PKCS12 -srcstorepass <src passwd> -alias keyimport
Import keystore to pkcs11 token using dynamic provider.
keytool -importkeystore -deststorepass <dest passwd> -destkeystore NONE -srckeystore store.p12 -providerClass sun.security.pkcs11.SunPKCS11 -providerArg /home/test/pkcs11.cfg -deststoretype PKCS11 -srcstoretype PKCS12 -srcstorepass <src passwd> -alias keyimport
Create CA store for truststore
keytool -keystore ca.jks -import -file ca.cert -alias cacert
Get the provider and load the keys
From java application get the provider instance.
Static provider.
Provider p = Security.getProvider("SunPKCS11-pkcs11Test");
where pkcs11Test is the name specified in the pkcs11.cfg file
Dynamic provider.
String configName = "/home/test/pkcs11.cfg";
Provider p = new SunPKCS11(configName);
Security.addProvider(p);
Load keys to keystore.
char[] pin = "<store pass>".toCharArray();
KeyStore keyStore = KeyStore.getInstance("PKCS11", p);
keyStore.load(null, pin);
Load ca certificates to truststore.
char[] tpin = "<store pass>".toCharArray();
FileInputStream tst = new FileInputStream("/ca.jks");
KeyStore trustStore = KeyStore.getInstance("JKS");
trustStore.load(tst, tpin);
TLS context initialization
Initialize key and trust Mangers
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, pin);
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(trustStore);
Initialize TLS context
SSLContext context = SSLContext.getInstance("TLS");
context.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
Create server socket and enable client auth
SSLServerSocketFactory ssf = context.getServerSocketFactory();
SSLServerSocket s = (SSLServerSocket) ssf.createServerSocket(<port>);
s.setNeedClientAuth(true);
SSLSocket c = (SSLSocket) s.accept();
Create client socket
SSLSocketFactory ssf = context.getSocketFactory();
SSLSocket s = (SSLSocket) ssf.createSocket("<hostname>", <port>);