{{announcement.body}}
{{announcement.title}}

Configuring SSL/TLS Connection Made Easy

DZone 's Guide to

Configuring SSL/TLS Connection Made Easy

In this article, we discuss how to easily configure an SSL/TLS connection in Java to better secure your application.

· Security Zone ·
Free Resource

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:

Java
 




xxxxxxxxxx
1
17


 
1
import org.apache.http.HttpResponse;
2
import org.apache.http.client.HttpClient;
3
import org.apache.http.client.methods.HttpGet;
4
import org.apache.http.impl.client.HttpClients;
5
 
          
6
import java.io.IOException;
7
 
          
8
class App {
9
 
          
10
    public static void main(String[] args) throws IOException {
11
        HttpClient httpClient = HttpClients.createMinimal();
12
 
          
13
        HttpGet request = new HttpGet("http://localhost:8080/api/hello");
14
        HttpResponse response = httpClient.execute(request);
15
    }
16
 
          
17
}



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

Java
 




xxxxxxxxxx
1
37


 
1
import org.apache.http.HttpResponse;
2
import org.apache.http.client.HttpClient;
3
import org.apache.http.client.methods.HttpGet;
4
import org.apache.http.conn.ssl.DefaultHostnameVerifier;
5
import org.apache.http.impl.client.HttpClients;
6
 
          
7
import javax.net.ssl.SSLContext;
8
import javax.net.ssl.TrustManagerFactory;
9
import java.io.IOException;
10
import java.security.KeyManagementException;
11
import java.security.KeyStore;
12
import java.security.KeyStoreException;
13
import java.security.NoSuchAlgorithmException;
14
 
          
15
class App {
16
 
          
17
    public static void main(String[] args) throws IOException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
18
        // in a real project this keystore instance will be most likely create from a java keystore file
19
        // which contains a list of trusted certificates
20
        KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
21
 
          
22
        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
23
        trustManagerFactory.init(trustStore);
24
 
          
25
        SSLContext sslContext = SSLContext.getInstance("TLSv1.3");
26
        sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
27
 
          
28
        HttpClient httpClient = HttpClients.custom()
29
                .setSSLContext(sslContext)
30
                .setSSLHostnameVerifier(new DefaultHostnameVerifier())
31
                .build();
32
 
          
33
        HttpGet request = new HttpGet("https://localhost:8443/api/hello");
34
        HttpResponse response = httpClient.execute(request);
35
    }
36
 
          
37
}



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 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:

Java
 




xxxxxxxxxx
1
29


 
1
import nl.altindag.sslcontext.SSLFactory;
2
import org.apache.http.HttpResponse;
3
import org.apache.http.client.HttpClient;
4
import org.apache.http.client.methods.HttpGet;
5
import org.apache.http.impl.client.HttpClients;
6
 
          
7
import java.io.IOException;
8
import java.security.KeyStore;
9
import java.security.KeyStoreException;
10
 
          
11
class App {
12
 
          
13
    public static void main(String[] args) throws IOException, KeyStoreException {
14
        String trustStore = "path/to/trustStore.jks";
15
 
          
16
        SSLFactory sslFactory = SSLFactory.builder()
17
                .withTrustStore(trustStore, "password".toCharArray())
18
                .build();
19
 
          
20
        HttpClient httpClient = HttpClients.custom()
21
                .setSSLContext(sslFactory.getSslContext())
22
                .setSSLHostnameVerifier(sslFactory.getHostnameVerifier())
23
                .build();
24
 
          
25
        HttpGet request = new HttpGet("https://localhost:8443/api/hello");
26
        HttpResponse response = httpClient.execute(request);
27
    }
28
 
          
29
}



Other configurations are also possible:

One way authentication with the default JDK trustStore:

Java
 




xxxxxxxxxx
1


 
1
SSLFactory.builder()
2
    .withDefaultJdkTrustStore()
3
    .build();



One-way authentication while trusting all certificates without validation, not recommended to use at production!

Java
 




xxxxxxxxxx
1


 
1
SSLFactory.builder()
2
    .withTrustingAllCertificatesWithoutValidation()
3
    .build();



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.

Java


Two-way authentication with custom trustStore, hostname verified, and encryption protocol version:

Java


Support for using multiple identities and trustStores:

Java


Advantages:

  • No need for low-level SSLContext configuration anymore.
  • No knowledge needed about SSLContext, TrustManager, TrustManagerFactory, KeyManager, KeyManagerFactory, 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.

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

Java
 




xxxxxxxxxx
1
31


 
1
import nl.altindag.sslcontext.SSLFactory;
2
 
          
3
import static org.assertj.core.api.Assertions.*;
4
 
          
5
public class SSLFactoryShould {
6
 
          
7
    @Test
8
    public void buildSSLFactoryForOneWayAuthenticationWithKeyStore() throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException {
9
        String trustStorePath = "/path/to/truststore.jks";
10
        char[] trustStorepassword = "password".toCharArray();
11
      
12
        SSLFactory sslFactory = SSLFactory.builder()
13
            .withTrustStore(trustStorePath, trustStorepassword)
14
            .build();
15
 
          
16
        assertThat(sslFactory.isSecurityEnabled()).isTrue();
17
        assertThat(sslFactory.isOneWayAuthenticationEnabled()).isTrue();
18
        assertThat(sslFactory.isTwoWayAuthenticationEnabled()).isFalse();
19
        assertThat(sslFactory.getSslContext()).isNotNull();
20
 
          
21
        assertThat(sslFactory.getTrustManager()).isNotNull();
22
        assertThat(sslFactory.getTrustedCertificates()).hasSize(3);
23
        assertThat(sslFactory.getTrustStores()).isNotEmpty();
24
        assertThat(sslFactory.getTrustManagerFactory()).isNotNull();
25
        assertThat(sslFactory.getHostnameVerifier()).isNotNull();
26
 
          
27
        assertThat(sslFactory.getKeyManager()).isNull();
28
        assertThat(sslFactory.getKeyManagerFactory()).isNull();
29
        assertThat(sslFactory.getIdentities()).isEmpty();
30
    }
31
}



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:

XML


Good luck and enjoy enabling encryption on your project!

Topics:
client ,java ,scala ,security ,server ,spring-boot ,ssl ,tls ,tls 1.3

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}