[SOLVED] TLS 1.2 – SSLHandshakeException: Remote host closed connection during handshake

SSLHandshakeException appear in logs when there is some error occurred while validating the certificate installed in the client machine with a certificate on the server machine. In this post, we will learn about fixing this if you are using the Apache HttpClient library to create HttpClient to connect to SSL/TLS-secured URLs.

1. Problem

The exception logs will look like this.

Caused by: javax.net.ssl.SSLHandshakeException: Remote host closed connection during handshake
  at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:980)
...
...
Caused by: java.io.EOFException: SSL peer shut down incorrectly
  at sun.security.ssl.InputRecord.read(InputRecord.java:505)
...
...

I have already posted a code fix to bypass SSL matching in an earlier post.

Unfortunately, that fix works in TLS and TLS 1.1 protocols. It doesn’t work in TLS 1.2 protocol. So ultimately, you need to fix the certificate issue anyway. There is a ‘no code only’ fix for this.

2. Solution

Now there are two ways, you can utilize the imported certificate from the server. Either add the certificate to the JDK cacerts store; or pass certificate information in JVM arguments.

2.1. Import certificate to JDK cacert Store

Import the certificate from the server.

Use the given command to add the certificate to JDK store. (Remove new line characters).

keytool -import
  -noprompt 
  -trustcacerts 
  -alias MAVEN-ROOT 
  -file C:/Users/Lokesh/keys/cert/maven.cer 
  -keystore "C:/Program Files (x86)/Java/jdk8/jre/lib/security/cacerts"
  -storepass changeit

Now create the HttpClient as given:

public HttpClient createTlsV2HttpClient() throws KeyManagementException, 
        UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException {
 
      SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
 
      SSLConnectionSocketFactory f = new SSLConnectionSocketFactory(sslContext, new String[] { "TLSv1.2" }, null,
                              SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
 
      Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
                      .register("http", PlainConnectionSocketFactory.getSocketFactory())
                      .register("https", f)
                      .build();
 
      PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
 
      CloseableHttpClient client = HttpClients
                .custom()
                .setSSLSocketFactory(f)
                      .setConnectionManager(cm)
                      .build();
      return client;
}

Notice the code : SSLContext.getInstance("TLSv1.2"). This code picks up the certificates added to JDK cacert store. So make a note of it.

2.2. Pass certificate information in JVM aruguments

Import the certificate from the server.

Add JVM arguments while starting the server. Change the parameter values as per your application.

-Djavax.net.ssl.keyStore="C:/Users/Lokesh\keys\maven.jks"
-Djavax.net.ssl.keyStorePassword="test"
-Djavax.net.ssl.trustStore="C:/Users/Lokesh\keys\maven.jks"
-Djavax.net.ssl.trustStorePassword="test"

Now create an HTTP client as given:

public HttpClient createTlsV2HttpClient() throws KeyManagementException, 
        UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException {
 
      SSLContext sslContext = SSLContexts.createSystemDefault();
 
      SSLConnectionSocketFactory f = new SSLConnectionSocketFactory(sslContext, new String[] { "TLSv1.2" }, null,
                              SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
 
      Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
                      .register("http", PlainConnectionSocketFactory.getSocketFactory())
                      .register("https", f)
                      .build();
 
      PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
 
      CloseableHttpClient client = HttpClients
                .custom()
                .setSSLSocketFactory(f)
                      .setConnectionManager(cm)
                      .build();
      return client;
}

Notice the code : SSLContext.createSystemDefault(). This code picks up the certificates passed as JVM arguments. Again, make a note of it.

3. Conclusion

  • Use SSLContext.getInstance("TLSv1.2") when the certificate is added to JDK cacert store.
  • Use SSLContext.createSystemDefault() when SSL info is passed as JVM argument.

Drop me your questions in the comments section.

Happy Learning !!

Comments

Subscribe
Notify of
guest
3 Comments
Most Voted
Newest Oldest
Inline Feedbacks
View all comments

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.