Setting up encryption for your application, how hard can it be? I thought it should be easy, as all communication with modern web applications should be encrypted, right? Well, my expectations were wrong… While setting it up, I encountered a couple of hidden difficulties. For example, the configuration is vague, verbose, not straight-forward to set it up, hard to debug, and not unit-test friendly.
For this article, I’ll assume you already have a basic understanding of certificates, keystores, encryption protocols, and ssl-handshake. If not, I would recommend going through this article: How to Easily Set Up Mutual TLS.
It will go through the following topics:
- No security.
- One-way authentication.
- Two-way authentication.
- Two-way authentication with trusting the Certificate Authority.
It will also explain how to create KeyStores, Certificates, Certificate Signing Requests, and how to implement it.
Let’s continue to the next part. I want to provide a couple of examples to explain the hidden difficulties to set up a secure connection with https and certificates in plain Java. For this example, I will use Apache HttpClient. If we want to use a client without encryption, the following setup will get the job done:
Apache HttpClient requires a configured
SSLContext instance to enable a secure https connection. The most minimal setup would look like the example below:
The above setup looks easy, but it brings a lot of additional logic in your codebase. It can be time-consuming to understand the following for someone who is not familiar with java security library:
- What is the best way to create a keystore?
- What is a default keystore type and what other options can I choose from?
- Why do I need a TrustManagerFactory? Can’t I just put my keystore instance with trusted certificates directly into the
- What is the default algorithm of a TrustManagerFactory?
- Is the default algorithm good enough, or do I need another one and what other options can I choose from?
- Which encryption protocol should I choose to create an
- Why can I provide null as a parameter value when initializing an
SSLContext, and what will the resulting
SSLContextdo for me?
Well, these are a lot of questions to be answered for just a basic setup. I won’t be providing the answers to the above questions, as these are already answered by our community. As a developer, you probably want to provide the best configuration for your projects and with the least amount of effort, which also should be maintainable.
The above configuration will get even harder if you also need to configure your client to communicate with mutual TLS, also known as mutual authentication. It is also hard to unit test an
SSLContext object because you can’t get any information from it that will tell if the
trustmanager is really initialized well and if it contains all the trusted certificates. The only way to really validate your code is by writing an integration test, where the client actually sends a request to a real server with HTTPS enabled.
I faced the same challenges as my colleagues. They also didn’t enjoy setting it up. It was just a configuration that needed to be set up once well enough to do the job, and after that, we feared to touch it again.
Some other HTTP clients even require a different setup (e.g., Netty HttpClient, AsyncHttpClient, and Dispatch Reboot). These clients only accept an
SSLContext from Netty’s library instead of the one from the JDK.
I wanted to help myself out from this difficult task and make my life easier. I thought why shouldn’t we have something similar to Lord Of The Rings — one ring to rule them all. So, in that way, sslcontext-kickstart was born. One library to configure and rule them all! It should be painless to use, easy to test and debug, and fun to set-it-up.
The above example could be replaced with the following code snippet:
Other configurations are also possible:
One way authentication with the default JDK
One-way authentication while trusting all certificates without validation, not recommended to use at production!
One-way authentication with a specific encryption protocol version and option to validate the hostname within the request against the SAN field of a certificate. If you are using Java 11 or newer, then you are also able to use TLSv1.3 as encryption protocol. Just provide “TLSv1.3” as a protocol argument, and it will work out-of-the-box.
Two-way authentication with custom
trustStore, hostname verified, and encryption protocol version:
Support for using multiple identities and
- No need for low-level
- No knowledge needed about
KeyManagerFactory, and how to create it.
- The above classes will all be created with just providing an identity and a
- Create an
sslcontextwith multiple identities and
Let’s also have a quick look at the unit test:
Or, see SSLFactoryShould for all other possible unit tests cases.
Below is a list of tested clients for Java and Scala with examples. See the ClientConfig class for a detailed configuration:
The source code is available here at Github: SSLContext-Kickstart.
Get the latest version from Maven Central or copy and paste the following snippet into your pom:
Good luck and enjoy enabling encryption on your project!