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.