Configuring SSL/TLS Connection Made Easy
By: Hakan Altındağ
Tags: Java, Kotlin, Scala, Ssl, Tls

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 when setting up a secure connection with https and certificates in vanilla 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:

import org.apache.http.HttpResponse;

import org.apache.http.client.HttpClient;

import org.apache.http.client.methods.HttpGet;

import org.apache.http.impl.client.HttpClients;

import java.io.IOException;



class App { public static void main(String[] args) throws IOException {

HttpClient httpClient = HttpClients.createMinimal();

HttpGet request = new HttpGet("http://localhost:8080/api/hello");

HttpResponse response = httpClient.execute(request);

}



}

Apache HttpClient requires a configured SSLContext instance to enable a secure https connection. The most minimal setup would look like the example below:

import org.apache.http.HttpResponse;

import org.apache.http.client.HttpClient;

import org.apache.http.client.methods.HttpGet;

import org.apache.http.conn.ssl.DefaultHostnameVerifier;

import org.apache.http.impl.client.HttpClients;

import javax.net.ssl.SSLContext;

import javax.net.ssl.TrustManagerFactory;

import java.io.IOException;

import java.security.KeyManagementException;

import java.security.KeyStore;

import java.security.KeyStoreException;

import java.security.NoSuchAlgorithmException;



class App {



public static void main(String[] args) throws IOException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException {

// in a real project this keystore instance will be most likely create from a java keystore file

// which contains a list of trusted certificates

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

TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());

trustManagerFactory.init(trustStore);



SSLContext sslContext = SSLContext.getInstance("TLSv1.3");

sslContext.init(null, trustManagerFactory.getTrustManagers(), null);



HttpClient httpClient = HttpClients.custom()

.setSSLContext(sslContext)

.setSSLHostnameVerifier(new DefaultHostnameVerifier())

.build();



HttpGet request = new HttpGet("https://localhost:8443/api/hello");

HttpResponse response = httpClient.execute(request);

}



}

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 SSLContext ?

? 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 SSLContext ?

? Why can I provide null as a parameter value when initializing an SSLContext , and what will the resulting SSLContext do 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 initialised 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 project is available at Github — SSLContext-Kickstart

Add the following dependency to your project to get started:

<dependency>

<groupId>io.github.hakky54</groupId>

<artifactId>sslcontext-kickstart</artifactId>

<version>6.3.0</version>

</dependency>

Advantages:

No need for low-level SSLContext configuration anymore.

configuration anymore. No knowledge needed about SSLContext , TrustManager , TrustManagerFactory , KeyManager , KeyManagerFactory , and how to create it.

, , , , , and how to create it. The above classes will all be created with just providing an identity and a trustStore .

. Create an sslcontext with multiple identities and trustStores .

with multiple identities and . Load multiple identities/trustStores/keyManagers/trustManagers

The vanilla java example of earlier could be replaced with the following code snippet:

import nl.altindag.ssl.SSLFactory;

import org.apache.http.HttpResponse;

import org.apache.http.client.HttpClient;

import org.apache.http.client.methods.HttpGet;

import org.apache.http.impl.client.HttpClients;

import java.io.IOException;

import java.security.KeyStore;

import java.security.KeyStoreException;



class App {



public static void main(String[] args) throws IOException, KeyStoreException {

SSLFactory sslFactory = SSLFactory.builder()

.withTrustStore("/path/to/trustStore.jks", "password".toCharArray())

.build();



HttpClient httpClient = HttpClients.custom()

.setSSLContext(sslFactory.getSslContext())

.setSSLHostnameVerifier(sslFactory.getHostnameVerifier())

.build();



HttpGet request = new HttpGet("https://localhost:8443/api/hello");

HttpResponse response = httpClient.execute(request);

}



}

The library also provides other utilities such as:

See the javadoc for all the options.

Let’s also have a quick look at the unit test:

import nl.altindag.ssl.SSLFactory;

import static org.assertj.core.api.Assertions.*;

public class SSLFactoryShould {



@Test

public void buildSSLFactoryWithTrustMaterial() throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException {

String trustStorePath = "/path/to/truststore.jks";

char[] trustStorepassword = "password".toCharArray();

SSLFactory sslFactory = SSLFactory.builder()

.withTrustStore(trustStorePath, trustStorepassword)

.build();



assertThat(sslFactory.getSslContext()).isNotNull();

assertThat(sslFactory.getTrustManager()).isNotNull();

assertThat(sslFactory.getTrustedCertificates()).isNotEmpty();

assertThat(sslFactory.getTrustedCertificates()).hasSize(3);

assertThat(sslFactory.getTrustStores()).isNotEmpty();

assertThat(sslFactory.getHostnameVerifier()).isNotNull();

}

}

Or, see SSLFactoryShould for all other possible unit tests cases.

Other possible configurations

Loading keystore and truststore from the classpath

SSLFactory.builder()

.withIdentityMaterial("identity.jks", "password".toCharArray())

.withTrustMaterial("truststore.jks", "password".toCharArray())

.build();

Loading keystore and trust store from anywhere on the filesystem

SSLFactory.builder()

.withIdentityMaterial(Paths.get("/path/to/your/identity.jks"), "password".toCharArray())

.withTrustMaterial(Paths.get("/path/to/your/truststore.jks"), "password".toCharArray())

.build();

Trusting all certificates without validation, not recommended to use at production!

SSLFactory.builder()

.withTrustingAllCertificatesWithoutValidation()

.build();

Loading JDK and OS trusted certificates

SSLFactory.builder()

.withDefaultTrustMaterial()

.withSystemTrustMaterial()

.build();

Using specific protocols, ciphers with custom secure random and hostname verifier. If you are using java 11 or newer, than you are also able to use TLSv1.3 as encryption protocol by default.

SSLFactory.builder()

.withDefaultTrustMaterial()

.withProtocols("TLSv1.3", "TLSv1.2")

.withCiphers("TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384")

.withHostnameVerifier(hostnameVerifier)

.withSecureRandom(secureRandom)

.build();

Support for using multiple identity materials and trust materials

SSLFactory.builder()

.withIdentityMaterial("identity-1.jks", password)

.withIdentityMaterial("identity-2.jks", password)

.withIdentityMaterial("identity-3.jks", password)

.withIdentityMaterial("identity-4.jks", password)

.withTrustMaterial("truststore-1.jks", password)

.withTrustMaterial("truststore-2.jks", password)

.withTrustMaterial("truststore-3.jks", password)

.withTrustMaterial("truststore-4.jks", password)

.build();

Support for using X509ExtendedKeyManager and X509ExtendedTrustManager

X509ExtendedKeyManager keyManager = ...

X509ExtendedTrustManager trustManager = ... SSLFactory.builder()

.withIdentityMaterial(keyManager)

.withTrustMaterial(trustManager)

.build();

Support for using PrivateKey and Certificates

PrivateKey privateKey = ...

char[] privateKeyPassword = ...

Certificate[] certificateChain = ... Certificate trustedCertificate = ... SSLFactory.builder()

.withIdentityMaterial(privateKey, privateKeyPassword, certificateChain)

.withTrustMaterial(trustedCertificate)

.build();

Using PEM Files

Support for using pem formatted private key and certificates from classpath, any directory or as an InputStream. See PemUtilsShould for detailed usages. Add the dependency below to use this feature, it also includes the core features from the library such as SSLFactory.

<dependency>

<groupId>io.github.hakky54</groupId>

<artifactId>sslcontext-kickstart-for-pem</artifactId>

<version>6.3.0</version>

</dependency> * EXAMPLE FILES

*

* [some-trusted-certificate.pem]

* -----BEGIN CERTIFICATE-----

* ...

* ...

* -----END CERTIFICATE-----

*

* [private-key.pem]

* -----BEGIN PRIVATE KEY-----

* ...

* ...

* -----END PRIVATE KEY-----

*

* [private-key.pem]

* -----BEGIN RSA PRIVATE KEY-----

* ...

* ...

* -----END RSA PRIVATE KEY-----

*

* [private-key.pem]

* -----BEGIN ENCRYPTED PRIVATE KEY-----

* ...

* ...

* -----END ENCRYPTED PRIVATE KEY-----

*/

Example usage:

X509ExtendedKeyManager keyManager = PemUtils.loadIdentityMaterial("certificate.pem", "private-key.pem");

X509ExtendedTrustManager trustManager = PemUtils.loadTrustMaterial("some-trusted-certificate.pem"); SSLFactory.builder()

.withIdentityMaterial(keyManager)

.withTrustMaterial(trustManager)

.build();

Or checkout the project to see all other available options: https://github.com/Hakky54/sslcontext-kickstart

Returnable values from the SSLFactory

The SSLFactory provides different kinds of returnable values, see below for all the options:

import nl.altindag.ssl.SSLFactory;

import nl.altindag.ssl.model.KeyStoreHolder;



import javax.net.ssl.HostnameVerifier;

import javax.net.ssl.SSLContext;

import javax.net.ssl.SSLEngine;

import javax.net.ssl.SSLParameters;

import javax.net.ssl.SSLServerSocketFactory;

import javax.net.ssl.SSLSocketFactory;

import javax.net.ssl.X509ExtendedKeyManager;

import javax.net.ssl.X509ExtendedTrustManager;

import java.security.cert.X509Certificate;

import java.util.List;

import java.util.Optional;



public class App {



public static void main(String[] args) {

SSLFactory sslFactory = SSLFactory.builder()

.withIdentityMaterial("keystore.p12", "secret".toCharArray(), "PKCS12")

.withTrustMaterial("truststore.p12", "secret".toCharArray(), "PKCS12")

.build();



SSLContext sslContext = sslFactory.getSslContext();

HostnameVerifier hostnameVerifier = sslFactory.getHostnameVerifier();

Optional<X509ExtendedKeyManager> keyManager = sslFactory.getKeyManager();

Optional<X509ExtendedTrustManager> trustManager = sslFactory.getTrustManager();

Optional<KeyManagerFactory> keyManagerFactory = sslFactory.getKeyManagerFactory();

Optional<TrustManagerFactory> trustManagerFactory = sslFactory.getTrustManagerFactory();

List<X509Certificate> trustedCertificates = sslFactory.getTrustedCertificates();

List<KeyStoreHolder> identities = sslFactory.getIdentities();

List<KeyStoreHolder> trustStores = sslFactory.getTrustStores();

SSLSocketFactory sslSocketFactory = sslFactory.getSslSocketFactory();

SSLServerSocketFactory sslServerSocketFactory = sslFactory.getSslServerSocketFactory();

SSLEngine sslEngine = sslFactory.getSslEngine(host, port);

SSLParameters sslParameters = sslFactory.getSslParameters();

List<String> ciphers = sslFactory.getCiphers();

List<String> protocols = sslFactory.getProtocols();

}



}

Tested HTTP Clients

Below is a list of clients which have already been tested with examples, see in the ClientConfig class and the service directory for detailed configuration. Visit the following page for the most recent version of the list: Example SSL / TLS Configuration for Http Clients

Java

Kotlin

Scala