Sunday, November 10, 2013

Client Side SSL Certificates on the JVM

Recently I finished the course Functional Programming Principles in Scala and I've been wanting to try out some real world Scala code. I decided to rewrite a small screen scraper that I had written in Python to get a feel for how to do the same thing in Scala. The first task was to find an HTTP client library to use, and I chose Play framework's WS API since Play is well known and has documentation.

The site from which I am scraping requires a client side SSL certificate, which is very easy to deal with using the Requests library for Python, simply by passing the path of the certificate to the constructor of a session. But the documentation for Play gives a somewhat unclear explanation on how to achieve this, by saying that you first need direct access to the underlying AsyncHttpClient instance and customizing it:

WS does not support client certificates (aka mutual TLS / MTLS / client authentication). You should set the SSLContext directly in an instance of AsyncHttpClientConfig and set up the appropriate KeyStore and TrustStore.

The problem is, there does not seem to be a way to pass a customized AsyncHttpClientConfig to the client, since the passing of the config is hidden inside private methods (see here if you are interested).

After reading lots of confusing documentation on Java and SSL (practically none exists for Scala), it turns out the solution is not specific to either Scala or Play Framework. The JVM will handle your certificates for you, and the AsyncHttpClient used by Play just uses the ones that the JVM knows about. So your code does not need to specify the certificate, instead you can import it into a keystore and pass the path to the keystore to the JVM using command line arguments. Here is how I did it.

First, you need the client certificate, its private key, and the CA certificate of the authority that signed your certificate. I will call them example.crt, example.key, and cacert.crt. Mine were in PEM format, but they must be converted to PKCS12. The openssl command can do this for you.

openssl pkcs12 -export -in example.crt -inkey example.key -out certs.p12 -name client -CAfile cacert.crt -caname root

While the command is running, you will be asked to provide a password to protect the output file. The output file is called certs.p12, which you can use to create a JVM keystore using the keytool command that is included with the JDK.

keytool -importkeystore -deststorepass p@ssw0rd -destkeypass p@ssw0rd -destkeystore keystore.jks -srckeystore certs.p12 -srcstoretype PKCS12 -srcstorepass p@ssw0rd -alias client

The -srcstorepass argument needs to be the password that you provided when converting your certificate to PKCS12 format. Now you will have a keystore file called keystore.jks. When you fire up your JVM, pass to it the path of your new keystore and its password.

java [args ...] -Djavax.net.ssl.keyStore=/path/to/keystore.jks -Djavax.net.ssl.keyStorePassword=p@ssw0rd

Now your Play WS client will automagically use the client certificate from the keystore. For security purposes, you probably don't really want to pass a command line argument containing the password of your keystore, so you may set the properties in your code by reading the password from a properly protected file or some other source. Also, don't actually use 'p@ssw0rd' for your password! But this should be enough to illustrate what is required to get it working.

No comments:

Post a Comment