Apache HTTP Client – Client side SSL certificate

Hello! I’m sharing this snippet with you because it’s been a little tricky to make it work, so I hope to save you some time.
Note: I’m referring to Apache HTTP Client 4.3. You know its API is pretty “lively” so before you dive into this, make sure it applies to the version you’re using as well.

Background

Creating an HTTP connection using Apache HTTP Client is pretty straight forward, and this is why we love it. Fine tuning how the client works is essential in production environments, and it does require a little bit of tweaking, but the results are excellent.

It’s not a surprise that Client side SSL certificates (also known as 2-way SSL or mutual SSL authentication) is doable, but of course, being something that interferes with the intimate nature of the HTTP conversation, the process isn’t exactly straight forward.

If you’re unaware of this security practice, here’s the shortest possible summary:
to provide stricter security for HTTPS connections involving very selected parties, you can let them have a SSL client certificate that will identify them. The SSL dance will not work if server and client certificates are not a match. By doing so, no credential is sent over the Internet, just encrypted data that will be decrypted correctly only if the certificates match.
If you want to know more, here’s a useful article by Robin Howlett.

Practice

Certificates are pain already. Incomplete chains, root certificates to be updated etc. So you can assume this couldn’t be a piece of cake right?

First off, let’s be clear about the fact that you are not going to configure this for a specific connection. The Http Client instance will be built around the fact that it will support that certificate.

First, let’s create a keystore:

KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());

The factory will create a JKS keystore by default. You can alter which one you will get by changing the parameter. For example PKCS12 is an option. I’m sticking with JKS for this example. This implies you already created a keystore and added the certificate to it.

Second thing, we load the keystore:

ks.load(inputStream,"password")

Any inputStream will do. We need to provide a password to decode the keystore of course.

SSLContext sslContext = SSLContexts.custom()
                          .loadKeyMaterial(ks, "password".toCharArray())
                          .loadTrustMaterial(null, new TrustSelfSignedStrategy())
                          .build();

We basically create a custom SSL Context, load the keystore, load the trust store (the null parameter will default to the cacerts file).

SSLConnectionSocketFactory sslConnectionFactory =
                                new SSLConnectionSocketFactory(sslContext,
                                              SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);

We create a socket connection factory providing the SSL context. The hostname verifier is literally up to you, and it pretty much depends on the service you will be interactive with. The ALLOW_ALL_HOSTNAME_VERIFIER is loose, so you might want to consider moving it to “strict” if the server allows you to.

Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
				.register("https", sslConnectionFactory)
				.register("http", new PlainConnectionSocketFactory())
				.build();

This registry associates what socket factory to use based on the protocol.

BasicHttpClientConnectionManager connManager = BasicHttpClientConnectionManager(registry);

We instantiate a connection manager that will use the registry. We’re using the basic connection manager here because it’s simpler for the example. Connection managers are a vital part of tuning HTTP client, so make sure you’re using something that suits your use case.

HttpClient client = HttpClients.custom()
			.setConnectionManager(connManager)
			.setSSLSocketFactory(sslConnectionFactory)
			.setHostnameVerifier(SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER)
			.build();

We finally instantiate our Http client that will eventually be successful when talking to our highly secure peer. Same thing we previously said, the hostname verifier has to be edited based on your needs.
Note: this example contains only the strictly necessary commands to make it work. A lot of other configurations options may be needed in your context.

And we should be set to go.

Let me repeat it, before you dive into this practice, make sure it suits your HTTP Client version. Various refactoring and changes in the API may strongly impact the way to do it.

Hope this helps! Take care.