Apache HTTP Client: Client-Side SSL Certificate
Need help working with your Apache HTTP Client?
Join the DZone community and get the full member experience.Join For Free
I’m sharing this snippet with you because it’s been a little tricky to work with, so I hope that I can 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 it, make sure it applies to the version you’re using as well.
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 two-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 an SSL client certificate that will identify them. The SSL dance will not work if the 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.
Incomplete chains, root certificates that need to be updated, etc. — certificates are a pain already. So, you would assume that this is 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 needs to 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 that you already created a keystore and added the certificate to it.
Secondly, we load the keystore:
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, and 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. As we mentioned previously, 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 configuration options may be needed in your context.
And we should be set to go.
Let me repeat one important point: 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.
Published at DZone with permission of Simone Pezzano, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.