-
Bug
-
Resolution: Won't Fix
-
P4
-
None
-
11, 17, 19, 20
-
generic
-
generic
ADDITIONAL SYSTEM INFORMATION :
Windows 10 x64 / Java 17.0.5.8.1 (corretto)
A DESCRIPTION OF THE PROBLEM :
Since we are using the new Java 11 HttpClient the cause (SSLHandshakeException) of an IOException is not allways set correctly. This only occurs with requests using a BodyPublisher, otherwise the cause is allways set to the SSLHandshakeException.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
run the provided code
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The SSLHandshakeException should allways be set as root cause of the IOException
ACTUAL -
Sometimes the root cause is: Caused by: java.io.IOException: Eine bestehende Verbindung wurde softwaregesteuert durch den Hostcomputer abgebrochen
This is german for the connection was canceled.
---------- BEGIN SOURCE ----------
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.security.KeyStore;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.time.Duration;
import java.util.Base64;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
public class Connection {
private static final Logger LOG = Logger.getLogger(Connection.class.getName());
public final static String[] TLS_PROTOCOLS = {"TLSv1.3"};
public final static String[] TLS_CIPHER_SUITES = {"TLS_AES_256_GCM_SHA384", "TLS_AES_128_GCM_SHA256"};
public static void main(final String[] args) throws Exception {
// to keep this dummy code simple
System.setProperty("jdk.internal.httpclient.disableHostnameVerification", "true");
// a trust manager to trust in all servers and reject all clients
TrustManager[] trusts = new TrustManager[]{
new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
throw new CertificateException("Not trusted.");
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
return; // trusted
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}
};
// start a SSL socket to show the issue
{
// a dummy SSL certificate
String cert = "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";
char[] password = "dummy".toCharArray();
{
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(new ByteArrayInputStream(Base64.getDecoder().decode(cert)), password);
KeyManagerFactory factory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
factory.init(keyStore, password);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(factory.getKeyManagers(), trusts, null);
SSLServerSocketFactory socketFactory = sslContext.getServerSocketFactory();
SSLServerSocket socket;
socket = (SSLServerSocket) socketFactory.createServerSocket(4567);
socket.setNeedClientAuth(true);
socket.setEnabledProtocols(TLS_PROTOCOLS);
socket.setEnabledCipherSuites(TLS_CIPHER_SUITES);
new Thread(() -> {
// try to read the incomming connections (this is doomed, we trust no client)
try ( var s = socket) {
while (true) {
Socket cnn = s.accept();
try {
cnn.getInputStream().read();
// should not happen, we do not trust the client
System.exit(666);
} catch (SSLException e) {
cnn.close();
}
}
} catch (IOException ex) {
Logger.getLogger(Connection.class.getName()).log(Level.SEVERE, null, ex);
}
}).start();
}
}
// start to send requests to the socket
{
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trusts, null);
HttpClient client = HttpClient
.newBuilder()
.sslContext(sslContext)
.connectTimeout(Duration.ofSeconds(2))
.build();
byte[] data = new byte[8 * 1024];
// the issue does not occur with out a BodyPublisher set
HttpRequest request = HttpRequest
.newBuilder(URI.create("https://localhost:4567"))
.method("POST", HttpRequest.BodyPublishers.ofInputStream(() -> new ByteArrayInputStream(data)))
.timeout(Duration.ofSeconds(4)).build();
// start to spin, to provoke the issue
for (int i = 0; i < 1000; i++) {
try {
// send the request
client.send(request, HttpResponse.BodyHandlers.ofByteArray());
} catch (IOException e) {
// we expect a failure here
Throwable cause = e.getCause();
// get the root cause of the exception
if (cause.getCause() != null) {
cause = cause.getCause();
}
// we expect a SSLException, if so continue to spin
if (cause instanceof SSLException) {
LOG.info(i + ": got the expected " + cause.toString());
continue;
}
// we are sending the same request and the dummy server is stateless
// why is there a different exception now?
throw new IllegalStateException( i + ": why is there no SSLException as root cause?", e);
}
}
}
}
}
---------- END SOURCE ----------
FREQUENCY : occasionally
Windows 10 x64 / Java 17.0.5.8.1 (corretto)
A DESCRIPTION OF THE PROBLEM :
Since we are using the new Java 11 HttpClient the cause (SSLHandshakeException) of an IOException is not allways set correctly. This only occurs with requests using a BodyPublisher, otherwise the cause is allways set to the SSLHandshakeException.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
run the provided code
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The SSLHandshakeException should allways be set as root cause of the IOException
ACTUAL -
Sometimes the root cause is: Caused by: java.io.IOException: Eine bestehende Verbindung wurde softwaregesteuert durch den Hostcomputer abgebrochen
This is german for the connection was canceled.
---------- BEGIN SOURCE ----------
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.security.KeyStore;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.time.Duration;
import java.util.Base64;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
public class Connection {
private static final Logger LOG = Logger.getLogger(Connection.class.getName());
public final static String[] TLS_PROTOCOLS = {"TLSv1.3"};
public final static String[] TLS_CIPHER_SUITES = {"TLS_AES_256_GCM_SHA384", "TLS_AES_128_GCM_SHA256"};
public static void main(final String[] args) throws Exception {
// to keep this dummy code simple
System.setProperty("jdk.internal.httpclient.disableHostnameVerification", "true");
// a trust manager to trust in all servers and reject all clients
TrustManager[] trusts = new TrustManager[]{
new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
throw new CertificateException("Not trusted.");
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
return; // trusted
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}
};
// start a SSL socket to show the issue
{
// a dummy SSL certificate
String cert = "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";
char[] password = "dummy".toCharArray();
{
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(new ByteArrayInputStream(Base64.getDecoder().decode(cert)), password);
KeyManagerFactory factory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
factory.init(keyStore, password);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(factory.getKeyManagers(), trusts, null);
SSLServerSocketFactory socketFactory = sslContext.getServerSocketFactory();
SSLServerSocket socket;
socket = (SSLServerSocket) socketFactory.createServerSocket(4567);
socket.setNeedClientAuth(true);
socket.setEnabledProtocols(TLS_PROTOCOLS);
socket.setEnabledCipherSuites(TLS_CIPHER_SUITES);
new Thread(() -> {
// try to read the incomming connections (this is doomed, we trust no client)
try ( var s = socket) {
while (true) {
Socket cnn = s.accept();
try {
cnn.getInputStream().read();
// should not happen, we do not trust the client
System.exit(666);
} catch (SSLException e) {
cnn.close();
}
}
} catch (IOException ex) {
Logger.getLogger(Connection.class.getName()).log(Level.SEVERE, null, ex);
}
}).start();
}
}
// start to send requests to the socket
{
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trusts, null);
HttpClient client = HttpClient
.newBuilder()
.sslContext(sslContext)
.connectTimeout(Duration.ofSeconds(2))
.build();
byte[] data = new byte[8 * 1024];
// the issue does not occur with out a BodyPublisher set
HttpRequest request = HttpRequest
.newBuilder(URI.create("https://localhost:4567"))
.method("POST", HttpRequest.BodyPublishers.ofInputStream(() -> new ByteArrayInputStream(data)))
.timeout(Duration.ofSeconds(4)).build();
// start to spin, to provoke the issue
for (int i = 0; i < 1000; i++) {
try {
// send the request
client.send(request, HttpResponse.BodyHandlers.ofByteArray());
} catch (IOException e) {
// we expect a failure here
Throwable cause = e.getCause();
// get the root cause of the exception
if (cause.getCause() != null) {
cause = cause.getCause();
}
// we expect a SSLException, if so continue to spin
if (cause instanceof SSLException) {
LOG.info(i + ": got the expected " + cause.toString());
continue;
}
// we are sending the same request and the dummy server is stateless
// why is there a different exception now?
throw new IllegalStateException( i + ": why is there no SSLException as root cause?", e);
}
}
}
}
}
---------- END SOURCE ----------
FREQUENCY : occasionally