We have a Java 17 Quarkus application compiled to a native executable (ubi-quarkus-native-image:22.2-java17), running in Kubernetes. It is connecting to an old integration server with SSL, making a HTTP request. Our application creates a SSLSocket using a socket factory and connects it; the request is successful. When it is done, we call SSLSocket#close. This works too, but on the other side they log this:
12:51:16 : ssl_debug(58): Shutting down SSL layer...
12:51:16 : ssl_debug(58): Sending alert: Alert Warning: close notify
12:51:16 : ssl_debug(58): Read 15328 bytes in 3 records, 15214 bytes net, 5071 average.
12:51:16 : ssl_debug(58): Wrote 175 bytes in 1 records, 152 bytes net, 152 average.
12:51:16 : ssl_debug(58): Closing transport...
12:51:16 : ssl_debug(58): Waiting on peer close notify...
12:51:16 : ssl_debug(58): Received alert message: Alert Warning: user canceled
12:51:16 : ssl_debug(58): Exception reading SSL message: iaik.security.ssl.SSLException: Peer sent alert: Alert Warning: user canceled
12:51:16 : ssl_debug(58): Closing transport...
12:51:16 : ssl_debug(58): Transport closed.
As far as I can understand they should get a close notify alert from us and we should get a close notify alert from them. Unfortunately our side sends a user canceled alert (code 90). It is only a warning, but they treat it as an error and throw away the entire request, even though they have answered.
I have found a way to circumvent this. If I call shutdownOutput on the socket before close, things work. However, according to properly close SSLSocket this is wrong and it really feels like a dirty workaround.
I can't get the other vendor to fix the error handling in their application. Is there a nicer way to avoid sending the user canceled alert? Is it a bug in the SSL implementation, or is it expected?
The socket has been flushed before close in both cases and reading from it (making sure there is no pending inbound data) makes no difference. Waiting for up to a minute before closing makes no difference either.
EDIT: it appears other servers fail with the workaround and require the final alerts to close cleanly, so it would be really nice to solve this.
EDIT: the code here is trivial, but I will add it as requested.
try (var socket = connect(url, httpChannel)) {
// Successfully make request here
// The workaround below fixes the problem
//socket.shutdownOutput();
} // close called on SSLSocket here
The code in connect is (simplified):
var plainSocket = new Socket();
plainSocket.connect(new InetSocketAddress(hostName, port), (int) connectionTimeout.toMillis());
plainSocket.setSoTimeout((int) connectionTimeout.toMillis());
var secureSocket = (SSLSocket) getSocketFactory(keyManagers, trustManagers, sslContextProtocol).createSocket(plainSocket, hostName, port, true);
var params = secureSocket.getSSLParameters();
params.setEndpointIdentificationAlgorithm("HTTPS");
secureSocket.setSSLParameters(params); // Optional but usually on
secureSocket.setUseClientMode(true);
secureSocket.setSoTimeout((int) readTimeout.toMillis());
secureSocket.startHandshake(); // Optional, but usually on
return secureSocket;
In the other end (where we act as server), we do a serverSocket#accept and call close on the request socket in a finally block, optionally with a similar socket#shutdownOutput first to work around the problem.