Thursday, June 11, 2009

How to use SSL with a client certificate in Java

I recently had to access a server (a web service to be specific but this doesn't matter) with a custom client certificate in Java. I never worked with SSL in Java so this was some challenge but I finally managed it. I also found most of the resources on the web rather unhelpful or at the least my specific use case was nowhere covered on the whole. So here is how I did it. Please note my instructions refer to Java 6 SE only.

Let me start at the end. What you need for SSL in Java is a keystore containing your private key information and a trust store containing the certificates you trust. When you access a secure site with a browser and the certificate authority (CA) that signed the site's certificate is unknown to the browser you get a prompt whether you trust this certificate or not. The trust store is the equivalent of you accepting a new certificate in a web browser. This is only needed in cases where you signed the certificate yourself.

The rest of this article assumes the file that represents the keystore is named keystore.jks and the file that represents trust store is named cacerts. The default format of the keystore is the proprietary Java KeyStore format, hence the .jks suffix. Java KeyStores are created with the keytool which is shipped with every Java 6 SDK.
Note: If you don't specify a file for the keystore, a file is created for you in your home directory (in Linux it's ~/.keystore). In this case you don't need to set up anything special in your Java program because this keystore is automatically searched for in case you try to establish a secure connection. I deliberately use my own file because it's a little bit more flexible—especially in development environments. There also exists a global trust store. You can find it under <jdk-install>/jre/lib/security/cacerts. I don't recommend to tamper with this file for development purposes. At the very least backup this file before you modify it.

Anyway, how do we create these two stores?

At first I assume you have a signed client certificate in e.g. PEM format. This means the content of the certificate will look something like this:

-------- BEGIN CERTIFICATE ----------
// lots of base64
-------- END CERTIFICATE ------------
-------- BEGIN RSA PRIVATE KEY ------
// lots of base64
-------- END RSA PRIVATE KEY --------

I'll refer to this file with the name client-cert.pem.
I think it doesn't really matter if the file has another format because you can convert many of these formats to PEM with one of the OpenSSL utilities. We're going to need these tools anyway.

The first approach I used does NOT work:

keytool -importcert -keystore keystore.jks -file client-cert.pem

The resulting file contains no information about the private key. Somehow only the certificate gets imported or something. You can easily verify this by issuing the following command:

keytool -list -v -keystore keystore.jks

This lists the entry type trustedCertEntry. What we need is PrivateKeyEntry.
To be honest I don't really understand this behaviour and it took me hours to realize that this step was the reason SSL didn't work for me.

At first we have to create a certificate in PKCS#12 format. With OpenSSL we can achieve this like that:

openssl pkcs12 -export -in client-cert.pem -out client-cert.p12

You get a prompt to enter a password. It is possible to leave this empty and theoretically you could do that because this file is only an intermediate file for your Java KeyStore. Don't leave the password empty! keytool doesn't like empty passwords so you definitely have to provide one. Just use password or something.

Now you get a new file client-cert.p12. Next we can create the keystore:

keytool -importkeystore -srckeystore client-cert.p12 -srcstoretype PKCS12 -destkeystore keystore.jks

You get prompted for the password for the PKCS#12 certificate (this is the point where an empty password is fatal) and another one for your new keystore. It is not possible to enter a password with less than six characters. If you check with the above used -list command you can see that this time the entry type is correct.

If you use a self signed certificate you need to create an appropriate trust store. I refer to this blog post. I used the program InstallCert with a few modifications (basically I changed some file paths because I wanted my very own trust store as I mentioned before). I hope this provides no difficulties.

Now we want to use these two files in our Java program. This is quite easy to accomplish. There are four properties we need: javax.net.ssl.keyStore and javax.net.ssl.trustStore for the file locations of our key and trust store respectively. The passwords to the files are provided with javax.net.ssl.keyStorePassword and javax.net.ssl.trustStorePassword.

You set these properties either before you start the JVM like this:

java -Djavax.net.ssl.keyStore=/path/to/keystore.jks -Djavax.net.ssl.keyStorePassword=password etc.

or in your code like this:

System.setProperty("javax.net.ssl.keyStore", "/path/to/keystore.jks");
System.setProperty("javax.net.ssl.keyStorePassword", "password");
// etc.


Well, I hope this helps someone. This took me quite some time to figure out.

6 comments:

  1. Very nice explaination. Thanks mate.

    ReplyDelete
  2. Very nice post. Really helpful. I am actually creating a crawler that crawls facebook. But when my crawler goes to facebook it generates ssl exceptions. I am unable to find certificate for facebook which I would include in java keystore. Please help.

    ReplyDelete
  3. We only can get the certificate which contains Public Key (NO Private Key). We tried to follow your instrucitons to import only the Public Key. Of course it doesn't work; we are still unable to access other site from ours. Do you have any suggestion?
    We run our site on Linux with Apache/Tomcat and JDK7. We believe the other site also runs on Apache/Tomcat but we are not sure about their JDK.

    ReplyDelete
  4. Thanks it helped me a lot. good job, keep it up.

    ReplyDelete