综合编程

Configuring SSL/TLS Connection Made Easy

微信扫一扫,分享到朋友圈

Configuring SSL/TLS Connection Made Easy

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

xxxxxxxxxx

1

1

SSLFactory.builder()

2

    .withTrustStore(trustStore, trustStorePassword)

3

    .withHostnameVerifierEnabled(true)

4

    .withProtocol("TLSv1.2")

5

    .build();

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

Java

xxxxxxxxxx

1

1

SSLFactory.builder()

2

    .withIdentity(identity, identityPassword)

3

    .withTrustStore(trustStore, trustStorePassword)

4

    .withHostnameVerifierEnabled(true)

5

    .withProtocol("TLSv1.2")

6

    .build();

Support for using multiple identities and trustStores
:

Java

xxxxxxxxxx

1

11

1

SSLFactory.builder()

2

    .withIdentity(identityA, identityPasswordA)

3

    .withIdentity(identityB, identityPasswordB)

4

    .withIdentity(identityC, identityPasswordC)

5

    .withTrustStore(trustStoreA, trustStorePasswordA)

6

    .withTrustStore(trustStoreB, trustStorePasswordB)

7

    .withTrustStore(trustStoreC, trustStorePasswordC)

8

    .withTrustStore(trustStoreD, trustStorePasswordD)

9

    .withHostnameVerifierEnabled(true)

10

    .withProtocol("TLSv1.2")

11

    .build();

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

xxxxxxxxxx

1

1

    <dependency>

2

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

3

        <artifactId>sslcontext-kickstart</artifactId>

4

        <version>3.0.2</version>

5

    </dependency>

Good luck and enjoy enabling encryption on your project!

How can I place 2 iframes next to each other&quest;

上一篇

TypeScript 3.8 EcmaScript Private Fields by Example

下一篇

你也可能喜欢

评论已经被关闭。

插入图片

热门栏目

Configuring SSL/TLS Connection Made Easy

长按储存图像,分享给朋友