javajava-17apache-httpcomponentsapache-httpclient-5.x

CloseableHttpAsyncClient does not send fatal TLS alerts before closing connection


I'm trying to get Apache httpclient5 CloseableHttpAsyncClient to correctly send fatal TLS alerts in case of failures that close the connection. According to the TLS v1.2 protocol, "Whenever an implementation encounters a condition which is defined as a fatal alert, it MUST send the appropriate alert prior to closing the connection.", therefore a client that does not accept a server certificate for example because it has expired, must send a fatal alert before closing the connection.

I'm using httpclient5 5.2.3 with openjdk-17 with default JSSE security provider.

If I try to make a basic http request using CloseableHttpAsyncClient with TLS configured through system properties like this:

try (CloseableHttpAsyncClient client = HttpAsyncClients.createDefault()) {
    client.start();
    SimpleHttpRequest simpleHttpRequest = SimpleHttpRequest.create(Method.GET,
            URI.create("https://expired.badssl.com"));
    Future<SimpleHttpResponse> future = client.execute(simpleHttpRequest, null);
    SimpleHttpResponse simpleHttpResponse = future.get();
} catch (Exception e) {
    e.printStackTrace();
}

Then send this to a server with an expired certificate. As expected the client does not accept the certificate and closes the connection, but it does not inform the server by sending the fatal TLS alert.

Is there any configuration needed to make httpclient5 send the alert to the server?

Edit:

Logs when running the code example:

2024-07-05T14:08:47.183+02:00 DEBUG 38589 --- [io-10666-exec-1] j.e.security                             : X509Certificate: Alg:SHA512withRSA, Serial:7ef6d57d52073136912...
2024-07-05T14:08:47.319+02:00 DEBUG 38589 --- [io-10666-exec-1] .c.h.i.a.InternalAbstractHttpAsyncClient : ex-0000000001 preparing request execution
2024-07-05T14:08:47.331+02:00 DEBUG 38589 --- [io-10666-exec-1] o.a.h.c.h.i.a.AsyncProtocolExec          : ex-0000000001 target auth state: UNCHALLENGED
2024-07-05T14:08:47.332+02:00 DEBUG 38589 --- [io-10666-exec-1] o.a.h.c.h.i.a.AsyncProtocolExec          : ex-0000000001 proxy auth state: UNCHALLENGED
2024-07-05T14:08:47.333+02:00 DEBUG 38589 --- [io-10666-exec-1] o.a.h.c.h.i.a.AsyncConnectExec           : ex-0000000001 acquiring connection with route {s}->https://localhost:16270
2024-07-05T14:08:47.334+02:00 DEBUG 38589 --- [io-10666-exec-1] o.a.h.c.h.i.a.InternalHttpAsyncClient    : ex-0000000001 acquiring endpoint (3 MINUTES)
2024-07-05T14:08:47.335+02:00 DEBUG 38589 --- [io-10666-exec-1] .i.n.PoolingAsyncClientConnectionManager : ex-0000000001 endpoint lease request (3 MINUTES) [route: {s}->https://localhost:16270][total available: 0; route allocated: 0 of 5; total allocated: 0 of 25]
2024-07-05T14:08:47.339+02:00 DEBUG 38589 --- [io-10666-exec-1] .i.n.PoolingAsyncClientConnectionManager : ex-0000000001 endpoint leased [route: {s}->https://localhost:16270][total available: 0; route allocated: 1 of 5; total allocated: 1 of 25]
2024-07-05T14:08:47.339+02:00 DEBUG 38589 --- [io-10666-exec-1] .i.n.PoolingAsyncClientConnectionManager : ex-0000000001 acquired ep-0000000001
2024-07-05T14:08:47.339+02:00 DEBUG 38589 --- [io-10666-exec-1] o.a.h.c.h.i.a.InternalHttpAsyncClient    : ex-0000000001 acquired endpoint ep-0000000001
2024-07-05T14:08:47.339+02:00 DEBUG 38589 --- [io-10666-exec-1] o.a.h.c.h.i.a.InternalHttpAsyncClient    : ep-0000000001 connecting endpoint (null)
2024-07-05T14:08:47.340+02:00 DEBUG 38589 --- [io-10666-exec-1] .i.n.PoolingAsyncClientConnectionManager : ep-0000000001 connecting endpoint to https://localhost:16270 (3 MINUTES)
2024-07-05T14:08:47.341+02:00 DEBUG 38589 --- [io-10666-exec-1] .a.h.c.h.i.n.MultihomeIOSessionRequester : localhost resolving remote address
2024-07-05T14:08:47.341+02:00 DEBUG 38589 --- [io-10666-exec-1] .a.h.c.h.i.n.MultihomeIOSessionRequester : localhost resolved to [localhost/127.0.0.1]
2024-07-05T14:08:47.342+02:00 DEBUG 38589 --- [io-10666-exec-1] .a.h.c.h.i.n.MultihomeIOSessionRequester : localhost:16270 connecting null->localhost/127.0.0.1:16270 (3 MINUTES)
2024-07-05T14:08:47.353+02:00 DEBUG 38589 --- [ient-dispatch-1] o.a.h.c.r.IOSessionImpl                  : c-0000000000[ACTIVE][rc:c] protocol upgrade class org.apache.hc.core5.http2.impl.nio.HttpProtocolNegotiator
2024-07-05T14:08:47.354+02:00 DEBUG 38589 --- [ient-dispatch-1] .a.h.c.h.i.n.MultihomeIOSessionRequester : localhost:16270 connected null->localhost/127.0.0.1:16270 as c-0000000000
2024-07-05T14:08:47.358+02:00 DEBUG 38589 --- [ient-dispatch-1] .i.n.DefaultManagedAsyncClientConnection : c-0000000000 start TLS
2024-07-05T14:08:47.402+02:00 DEBUG 38589 --- [ient-dispatch-1] o.a.h.c.h.s.AbstractClientTlsStrategy    : Enabled protocols: [TLSv1.3, TLSv1.2]
2024-07-05T14:08:47.403+02:00 DEBUG 38589 --- [ient-dispatch-1] o.a.h.c.h.s.AbstractClientTlsStrategy    : Enabled cipher suites:[TLS_AES_256_GCM_SHA384, TLS_AES_128_GCM_SHA256, TLS_CHACHA20_POLY1305_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256, TLS_DHE_DSS_WITH_AES_256_GCM_SHA384, TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, TLS_DHE_DSS_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, TLS_DHE_DSS_WITH_AES_256_CBC_SHA256, TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, TLS_DHE_DSS_WITH_AES_128_CBC_SHA256, TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384, TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384, TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_RSA_WITH_AES_256_CBC_SHA, TLS_DHE_DSS_WITH_AES_256_CBC_SHA, TLS_DHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_DSS_WITH_AES_128_CBC_SHA, TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDH_RSA_WITH_AES_256_CBC_SHA, TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_256_GCM_SHA384, TLS_RSA_WITH_AES_128_GCM_SHA256, TLS_RSA_WITH_AES_256_CBC_SHA256, TLS_RSA_WITH_AES_128_CBC_SHA256, TLS_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_EMPTY_RENEGOTIATION_INFO_SCSV]
2024-07-05T14:08:47.403+02:00 DEBUG 38589 --- [ient-dispatch-1] o.a.h.c.h.s.AbstractClientTlsStrategy    : Starting handshake (3 MINUTES)
2024-07-05T14:08:47.449+02:00 DEBUG 38589 --- [ient-dispatch-1] o.a.h.c.r.s.SSLIOSession                 : c-0000000000[ACTIVE][rw:c][ACTIVE][r][NEED_UNWRAP][0][0][482] Event cleared [c]
2024-07-05T14:08:47.630+02:00 DEBUG 38589 --- [ient-dispatch-1] j.e.security                             : X509Certificate: Alg:SHA256withRSA, Serial:10... Valid from:5/13/24, 3:54 PM, Valid until:5/13/24, 3:54 PM
2024-07-05T14:08:47.651+02:00 DEBUG 38589 --- [ient-dispatch-1] .c.h.i.a.InternalAbstractHttpAsyncClient : ex-0000000001 request failed: PKIX path validation failed: java.security.cert.CertPathValidatorException: validity check failed
2024-07-05T14:08:47.652+02:00 DEBUG 38589 --- [ient-dispatch-1] .i.n.PoolingAsyncClientConnectionManager : ep-0000000001 close IMMEDIATE
2024-07-05T14:08:47.652+02:00 DEBUG 38589 --- [ient-dispatch-1] o.a.h.c.h.i.a.InternalHttpAsyncClient    : ep-0000000001 endpoint closed
2024-07-05T14:08:47.652+02:00 DEBUG 38589 --- [ient-dispatch-1] o.a.h.c.h.i.a.InternalHttpAsyncClient    : ep-0000000001 discarding endpoint
2024-07-05T14:08:47.652+02:00 DEBUG 38589 --- [ient-dispatch-1] .i.n.PoolingAsyncClientConnectionManager : ep-0000000001 releasing endpoint
2024-07-05T14:08:47.652+02:00 DEBUG 38589 --- [ient-dispatch-1] .i.n.PoolingAsyncClientConnectionManager : ep-0000000001 connection released [route: {s}->https://localhost:16270][total available: 0; route allocated: 0 of 5; total allocated: 0 of 25]
2024-07-05T14:08:47.652+02:00 DEBUG 38589 --- [io-10666-exec-1] .a.h.c.h.i.a.AbstractHttpAsyncClientBase : Shutdown GRACEFUL
2024-07-05T14:08:47.654+02:00 DEBUG 38589 --- [ient-dispatch-1] o.a.h.c.r.s.SSLIOSession                 : c-0000000000[CLOSED][][CLOSED][r][NEED_WRAP][inbound done][][0][0][0] Close IMMEDIATE
2024-07-05T14:08:47.654+02:00 DEBUG 38589 --- [ient-dispatch-1] o.a.h.c.r.s.SSLIOSession                 : c-0000000000[CLOSED][][CLOSED][r][NEED_WRAP][inbound done][][0][0][0] Close GRACEFUL
2024-07-05T14:08:47.655+02:00 DEBUG 38589 --- [io-10666-exec-1] .i.n.PoolingAsyncClientConnectionManager : Shutdown connection pool GRACEFUL
2024-07-05T14:08:47.655+02:00 DEBUG 38589 --- [io-10666-exec-1] .i.n.PoolingAsyncClientConnectionManager : Connection pool shut down

I assume these are the ones @Robert is referring to:

2024-07-05T14:08:47.651+02:00 DEBUG 38589 --- [ient-dispatch-1] .c.h.i.a.InternalAbstractHttpAsyncClient : ex-0000000001 request failed: PKIX path validation failed: java.security.cert.CertPathValidatorException: validity check failed
2024-07-05T14:08:47.652+02:00 DEBUG 38589 --- [ient-dispatch-1] .i.n.PoolingAsyncClientConnectionManager : ep-0000000001 close IMMEDIATE

Edit 2:

I managed to get this working by using the non-async client instead:

try (CloseableHttpClient client = HttpClients.createDefault()) {
    ClassicHttpRequest classicHttpRequest = new BasicClassicHttpRequest(Method.GET, URI.create("https://expired.badssl.com"));
    CloseableHttpResponse response = client.execute(classicHttpRequest);
    client.close();
}

This does send the TLS alert to the server before closing the connection. The logs look like this:

2024-07-05T17:12:17.645+02:00 DEBUG 46936 --- [io-10666-exec-1] j.e.security                             : X509Certificate: Alg:SHA512withRSA, Serial:7ef6d57d5207...
2024-07-05T17:12:17.732+02:00 DEBUG 46936 --- [io-10666-exec-1] o.a.h.c.h.i.c.InternalHttpClient         : ex-0000000001 preparing request execution
2024-07-05T17:12:17.740+02:00 DEBUG 46936 --- [io-10666-exec-1] o.a.h.c.h.i.c.ProtocolExec               : ex-0000000001 target auth state: UNCHALLENGED
2024-07-05T17:12:17.740+02:00 DEBUG 46936 --- [io-10666-exec-1] o.a.h.c.h.i.c.ProtocolExec               : ex-0000000001 proxy auth state: UNCHALLENGED
2024-07-05T17:12:17.740+02:00 DEBUG 46936 --- [io-10666-exec-1] o.a.h.c.h.i.c.ConnectExec                : ex-0000000001 acquiring connection with route {s}->https://localhost:16270
2024-07-05T17:12:17.741+02:00 DEBUG 46936 --- [io-10666-exec-1] o.a.h.c.h.i.c.InternalHttpClient         : ex-0000000001 acquiring endpoint (3 MINUTES)
2024-07-05T17:12:17.742+02:00 DEBUG 46936 --- [io-10666-exec-1] h.i.i.PoolingHttpClientConnectionManager : ex-0000000001 endpoint lease request (3 MINUTES) [route: {s}->https://localhost:16270][total available: 0; route allocated: 0 of 5; total allocated: 0 of 25]
2024-07-05T17:12:17.745+02:00 DEBUG 46936 --- [io-10666-exec-1] h.i.i.PoolingHttpClientConnectionManager : ex-0000000001 endpoint leased [route: {s}->https://localhost:16270][total available: 0; route allocated: 1 of 5; total allocated: 1 of 25]
2024-07-05T17:12:17.753+02:00 DEBUG 46936 --- [io-10666-exec-1] h.i.i.PoolingHttpClientConnectionManager : ex-0000000001 acquired ep-0000000001
2024-07-05T17:12:17.753+02:00 DEBUG 46936 --- [io-10666-exec-1] o.a.h.c.h.i.c.InternalHttpClient         : ex-0000000001 acquired endpoint ep-0000000001
2024-07-05T17:12:17.753+02:00 DEBUG 46936 --- [io-10666-exec-1] o.a.h.c.h.i.c.ConnectExec                : ex-0000000001 opening connection {s}->https://localhost:16270
2024-07-05T17:12:17.753+02:00 DEBUG 46936 --- [io-10666-exec-1] o.a.h.c.h.i.c.InternalHttpClient         : ep-0000000001 connecting endpoint (null)
2024-07-05T17:12:17.755+02:00 DEBUG 46936 --- [io-10666-exec-1] h.i.i.PoolingHttpClientConnectionManager : ep-0000000001 connecting endpoint to https://localhost:16270 (3 MINUTES)
2024-07-05T17:12:17.755+02:00 DEBUG 46936 --- [io-10666-exec-1] .i.i.DefaultHttpClientConnectionOperator : localhost resolving remote address
2024-07-05T17:12:17.755+02:00 DEBUG 46936 --- [io-10666-exec-1] .i.i.DefaultHttpClientConnectionOperator : localhost resolved to [localhost/127.0.0.1]
2024-07-05T17:12:17.756+02:00 DEBUG 46936 --- [io-10666-exec-1] .i.i.DefaultHttpClientConnectionOperator : localhost:16270 connecting null->localhost/127.0.0.1:16270 (3 MINUTES)
2024-07-05T17:12:17.756+02:00 DEBUG 46936 --- [io-10666-exec-1] o.a.h.c.h.s.SSLConnectionSocketFactory   : Connecting socket to localhost/127.0.0.1:16270 with timeout 3 MINUTES
2024-07-05T17:12:17.780+02:00 DEBUG 46936 --- [io-10666-exec-1] o.a.h.c.h.s.SSLConnectionSocketFactory   : Enabled protocols: [TLSv1.3, TLSv1.2]
2024-07-05T17:12:17.780+02:00 DEBUG 46936 --- [io-10666-exec-1] o.a.h.c.h.s.SSLConnectionSocketFactory   : Enabled cipher suites: [TLS_AES_256_GCM_SHA384, TLS_AES_128_GCM_SHA256, TLS_CHACHA20_POLY1305_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256, TLS_DHE_DSS_WITH_AES_256_GCM_SHA384, TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, TLS_DHE_DSS_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, TLS_DHE_DSS_WITH_AES_256_CBC_SHA256, TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, TLS_DHE_DSS_WITH_AES_128_CBC_SHA256, TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384, TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384, TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_RSA_WITH_AES_256_CBC_SHA, TLS_DHE_DSS_WITH_AES_256_CBC_SHA, TLS_DHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_DSS_WITH_AES_128_CBC_SHA, TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDH_RSA_WITH_AES_256_CBC_SHA, TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_256_GCM_SHA384, TLS_RSA_WITH_AES_128_GCM_SHA256, TLS_RSA_WITH_AES_256_CBC_SHA256, TLS_RSA_WITH_AES_128_CBC_SHA256, TLS_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_EMPTY_RENEGOTIATION_INFO_SCSV]
2024-07-05T17:12:17.780+02:00 DEBUG 46936 --- [io-10666-exec-1] o.a.h.c.h.s.SSLConnectionSocketFactory   : Starting handshake (null)
2024-07-05T17:12:17.882+02:00 DEBUG 46936 --- [io-10666-exec-1] j.e.security                             : X509Certificate: Alg:SHA256withRSA, Serial:10... Valid from:5/13/24, 3:54 PM, Valid until:5/13/24, 3:54 PM
2024-07-05T17:12:17.897+02:00 DEBUG 46936 --- [io-10666-exec-1] .i.i.DefaultHttpClientConnectionOperator : localhost:16270 connection to localhost/127.0.0.1:16270 failed (class javax.net.ssl.SSLHandshakeException); terminating operation
2024-07-05T17:12:17.899+02:00 DEBUG 46936 --- [io-10666-exec-1] h.i.i.DefaultManagedHttpClientConnection : http-outgoing-0 close connection IMMEDIATE
2024-07-05T17:12:17.899+02:00 DEBUG 46936 --- [io-10666-exec-1] o.a.h.c.h.i.c.InternalHttpClient         : ep-0000000001 endpoint closed
2024-07-05T17:12:17.899+02:00 DEBUG 46936 --- [io-10666-exec-1] o.a.h.c.h.i.c.InternalHttpClient         : ep-0000000001 discarding endpoint
2024-07-05T17:12:17.899+02:00 DEBUG 46936 --- [io-10666-exec-1] h.i.i.PoolingHttpClientConnectionManager : ep-0000000001 releasing endpoint
2024-07-05T17:12:17.899+02:00 DEBUG 46936 --- [io-10666-exec-1] h.i.i.PoolingHttpClientConnectionManager : ep-0000000001 connection is not kept alive
2024-07-05T17:12:17.900+02:00 DEBUG 46936 --- [io-10666-exec-1] h.i.i.PoolingHttpClientConnectionManager : ep-0000000001 connection released [route: {s}->https://localhost:16270][total available: 0; route allocated: 0 of 5; total allocated: 0 of 25]
2024-07-05T17:12:17.900+02:00 DEBUG 46936 --- [io-10666-exec-1] h.i.i.PoolingHttpClientConnectionManager : Shutdown connection pool GRACEFUL
2024-07-05T17:12:17.900+02:00 DEBUG 46936 --- [io-10666-exec-1] h.i.i.PoolingHttpClientConnectionManager : Connection pool shut down

@ok2c is there any way to make this work for the async one as well?

Edit 3:

Wireshark with CloseableHttpAsyncClient:

enter image description here

Wireshark with CloseableHttpClient:

enter image description here

I can also see these logs in my server confirming it received the fatal alert:

javax.net.ssl|DEBUG|12|https-jsse-nio-16270-exec-2|2024-07-08 13:57:58.885 CEST|SSLEngineInputRecord.java:176|Raw read (
  0000: 15 03 03 00 02 02 2D                               ......-
)
javax.net.ssl|DEBUG|12|https-jsse-nio-16270-exec-2|2024-07-08 13:57:58.886 CEST|SSLEngineInputRecord.java:213|READ: TLSv1.2 alert, length = 2
javax.net.ssl|DEBUG|12|https-jsse-nio-16270-exec-2|2024-07-08 13:57:58.887 CEST|Alert.java:238|Received alert message (
"Alert": {
  "level"      : "fatal",
  "description": "certificate_expired"
}
)
javax.net.ssl|ERROR|12|https-jsse-nio-16270-exec-2|2024-07-08 13:57:58.887 CEST|TransportContext.java:370|Fatal (CERTIFICATE_EXPIRED): Received fatal alert: certificate_expired (
"throwable" : {
  javax.net.ssl.SSLHandshakeException: Received fatal alert: certificate_expired
    at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:131)
    at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:117)
    at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:365)
    at java.base/sun.security.ssl.Alert$AlertConsumer.consume(Alert.java:293)
    at java.base/sun.security.ssl.TransportContext.dispatch(TransportContext.java:204)
    at java.base/sun.security.ssl.SSLTransport.decode(SSLTransport.java:172)
    at java.base/sun.security.ssl.SSLEngineImpl.decode(SSLEngineImpl.java:736)
    at java.base/sun.security.ssl.SSLEngineImpl.readRecord(SSLEngineImpl.java:691)
    at java.base/sun.security.ssl.SSLEngineImpl.unwrap(SSLEngineImpl.java:506)
    at java.base/sun.security.ssl.SSLEngineImpl.unwrap(SSLEngineImpl.java:482)
    at java.base/javax.net.ssl.SSLEngine.unwrap(SSLEngine.java:679)
    at org.apache.tomcat.util.net.SecureNioChannel.handshakeUnwrap(SecureNioChannel.java:479)
    at org.apache.tomcat.util.net.SecureNioChannel.handshake(SecureNioChannel.java:213)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1719)
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
    at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
    at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.base/java.lang.Thread.run(Thread.java:840)}

)

But when using the CloseableHttpAsyncClient there are no such logs in the server.

The client TLS debug logs when using CloseableHttpClient are also different, specifying the fatal alert:

javax.net.ssl|DEBUG|12|http-nio-10666-exec-1|2024-07-08 14:15:31.852 CEST|SSLSocketOutputRecord.java:71|WRITE: TLSv1.2 alert(certificate_expired), length = 2
javax.net.ssl|DEBUG|12|http-nio-10666-exec-1|2024-07-08 14:15:31.852 CEST|SSLSocketOutputRecord.java:85|Raw write (
  0000: 15 03 03 00 02 02 2D                               ......-
)
javax.net.ssl|DEBUG|12|http-nio-10666-exec-1|2024-07-08 14:15:31.852 CEST|SSLSocketImpl.java:1759|close the underlying socket
javax.net.ssl|DEBUG|12|http-nio-10666-exec-1|2024-07-08 14:15:31.852 CEST|SSLSocketImpl.java:1785|close the SSL connection (passive)

Solution

  • Please try this change-set locally and let me know if solves the problem for you

    https://github.com/apache/httpcomponents-core/compare/5.2.x...ok2c:httpcomponents-core:tls_handshake_shutdown

    There is no guarantee this will force JSSE to generate a fatal alert but at the very least HttpClient will make an attempt to gracefully close out the TLS session.

    Update: found the cause of the problem and updated my patch

    I am now seeing in the TLS debug logs

    javax.net.ssl|WARNING|0F|requester-dispatch-1|2024-07-07 11:39:23.907 CEST|SSLEngineOutputRecord.java:168|outbound has closed, ignore outbound application data
    javax.net.ssl|FINE|0F|requester-dispatch-1|2024-07-07 11:39:23.907 CEST|SSLEngineOutputRecord.java:505|WRITE: TLS12 alert, length = 2
    javax.net.ssl|FINE|0F|requester-dispatch-1|2024-07-07 11:39:23.907 CEST|SSLEngineOutputRecord.java:523|Raw write (
      0000: 15 03 03 00 02 02 2D                               ......-
    )
    2024-07-07 11:39:23,903 ERROR [requester-dispatch-1][org.apache.hc.core5.http.connection] c-0000000000[ACTIVE][r:r][ACTIVE][rw][NEED_WRAP][inbound done][][347][0][0] PKIX path validation failed: java.security.cert.CertPathValidatorException: validity check failed
    javax.net.ssl.SSLHandshakeException: PKIX path validation failed: java.security.cert.CertPathValidatorException: validity check failed
    

    You will need to build HttpCore from source and make HttpClient use that snapshot to test the fix.