Remote host terminated the handshake ошибка

I’m running a local TCP server in C++ on Windows via OpenSSL and Windows sockets. I’m running the client in Java using SSL sockets. For most users this setup is working, however, some users run into the following Java exception upon attempting the SSL handshake:

javax.net.ssl.SSLHandshakeException: Remote host terminated the handshake
    at sun.security.ssl.SSLSocketImpl.handleEOF(Unknown Source) ~[?:1.8.0_321]
    at sun.security.ssl.SSLSocketImpl.decode(Unknown Source) ~[?:1.8.0_321]
    at sun.security.ssl.SSLSocketImpl.readHandshakeRecord(Unknown Source) ~[?:1.8.0_321]
    at sun.security.ssl.SSLSocketImpl.startHandshake(Unknown Source) ~[?:1.8.0_321]
    at sun.security.ssl.SSLSocketImpl.startHandshake(Unknown Source) ~[?:1.8.0_321]
    ...
Caused by: java.io.EOFException: SSL peer shut down incorrectly
    at sun.security.ssl.SSLSocketInputRecord.read(Unknown Source) ~[?:1.8.0_321]
    at sun.security.ssl.SSLSocketInputRecord.readHeader(Unknown Source) ~[?:1.8.0_321]
    at sun.security.ssl.SSLSocketInputRecord.decode(Unknown Source) ~[?:1.8.0_321]
    at sun.security.ssl.SSLTransport.decode(Unknown Source) ~[?:1.8.0_321]
    ... 10 more

My client socket code in Java looks like this:

public static Socket getClientSocket() throws Exception
{
    InetSocketAddress socketAddress = new InetSocketAddress("localhost", 54321);
    SSLSocketFactory socketFactory = getSSLSocketFactory();
    Socket clientSocket = socketFactory.createSocket();
    clientSocket.connect(socketAddress, 1_000);
    ((SSLSocket) clientSocket).startHandshake(); // <-- Exception here for some users
    return clientSocket;
}

private static SSLSocketFactory getSSLSocketFactory() throws Exception
{
    // Create a trust manager that does not validate certificate chains
    X509TrustManager trustManager = new X509TrustManager()
    {
        @Override
        public X509Certificate[] getAcceptedIssuers()
        {
            // TODO Returning null is not allowed but it still works
            return null;
        }

        @Override
        public void checkClientTrusted(final X509Certificate[] certificates, final String authType)
        {

        }

        @Override
        public void checkServerTrusted(final X509Certificate[] certificates, final String authType)
        {
            for (X509Certificate certificate : certificates)
            {
                String certificateString = certificate.toString();
                if (!certificateString.contains("blablabla"))
                {
                    throw new SSLException("Certificate not trusted");
                }
            }
        }
    };
    SSLContext sslContext = SSLContext.getInstance("SSL");
    sslContext.init(null, new TrustManager[]{trustManager}, new SecureRandom());
    return sslContext.getSocketFactory();
}

Attempted solutions:

  • Calling System.setProperty("https.protocols", "TLSv1,TLSv1.1,TLSv1.2") before connecting did not work (reference)
  • I do not want to tell users to import any SSL certificates into their truststore (unless I can automate this easily)

Where does the inconsistent behavior come from and how to fix it cleanly for everyone who gets the exception? I have access to the server source code and certificates. Also any critical questions or suggestions are welcome. I can provide more information if necessary.

javax.net.ssl.SSLHandshakeException: Remote host terminated the handshake
	at java.base/sun.security.ssl.SSLSocketImpl.handleEOF(SSLSocketImpl.java:1616)
	at java.base/sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1434)
	at java.base/sun.security.ssl.SSLSocketImpl.readHandshakeRecord(SSLSocketImpl.java:1336)
	at java.base/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:450)
	at java.base/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:421)
	at org.apache.http.conn.ssl.SSLConnectionSocketFactory.createLayeredSocket(SSLConnectionSocketFactory.java:436)
	at org.apache.http.conn.ssl.SSLConnectionSocketFactory.connectSocket(SSLConnectionSocketFactory.java:384)
	at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:142)
	at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect(PoolingHttpClientConnectionManager.java:376)
	at org.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:393)
	at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:236)
	at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:186)
	at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:89)
	at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110)
	at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:185)
	at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:83)
	at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:108)
	at com.tabnine.logging.TabnineLogDispatcher$dispatchLog$1.run(TabnineLogDispatcher.kt:40)
	at com.intellij.openapi.application.impl.ApplicationImpl$1.run(ApplicationImpl.java:263)
	at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
	at java.base/java.util.concurrent.Executors$PrivilegedThreadFactory$1$1.run(Executors.java:668)
	at java.base/java.util.concurrent.Executors$PrivilegedThreadFactory$1$1.run(Executors.java:665)
	at java.base/java.security.AccessController.doPrivileged(Native Method)
	at java.base/java.util.concurrent.Executors$PrivilegedThreadFactory$1.run(Executors.java:665)
	at java.base/java.lang.Thread.run(Thread.java:829)
Caused by: java.io.EOFException: SSL peer shut down incorrectly
	at java.base/sun.security.ssl.SSLSocketInputRecord.read(SSLSocketInputRecord.java:483)
	at java.base/sun.security.ssl.SSLSocketInputRecord.readHeader(SSLSocketInputRecord.java:472)
	at java.base/sun.security.ssl.SSLSocketInputRecord.decode(SSLSocketInputRecord.java:160)
	at java.base/sun.security.ssl.SSLTransport.decode(SSLTransport.java:111)
	at java.base/sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1426)
	... 26 more

When I perform a load testing to my website over HTTPS, I received multiples errors related with SSL Handshake when I simulate a high amount of concurrent users (>500 / sec).

Here are the errors that I received:

Error #1

javax.net.ssl.SSLHandshakeException: Remote host terminated the handshake

Error #2

javax.net.ssl.SSLException: Socket closed

Error #3

javax.net.ssl.SSLException: Read timed out

The thing is, everything is managed by the GCP, especially with the Load Balancer where my front-end is HTTPS associated with a valid certificate.

Also, the capacity of my back-end seems high enough to support everything. It is like the bottleneck is my Load Balancer.

The tests are made directly from my computer using JMeter.

So, what cause this problem and how can I fix it?

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/Lokeshkeysmaven.jks"
-Djavax.net.ssl.keyStorePassword="test"
-Djavax.net.ssl.trustStore="C:/Users/Lokeshkeysmaven.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 !!

Sonar Community

Loading

[WEBINAR] Clean Code Principles and Practices — JUNE 21ST
Register Now

  • Remote desktop произошла внутренняя ошибка
  • Remote control ошибка бмв
  • Remote access ошибка 20106
  • Remote access 20063 ошибка
  • Remnant from the ashes при проверке доступа к сетевым функциям возникла неизвестная ошибка egs