Issue
I've been playing with Java 17 and TLSv1.3. I'm getting ciphersuite/protocol unavailable while specifying the correct TLS_CHACHA20_POLY1305_SHA256 for TLSv1.3 and TLS_AES_256_GCM_SHA384 for TLSv1.2 with WebTestClient
. Here are the relevant code sections and logs.
application.yml
server:
port: 8443
ssl:
enabled: true
key-store-type: PKCS12
key-store: classpath:keystore/localhost.p12
key-store-password: ${LOCALHOST_KEYSTORE_PASSWORD}
key-alias: localhost
ciphers:
- TLS_CHACHA20_POLY1305_SHA256
- TLS_AES_256_GCM_SHA384
- TLS_AES_128_GCM_SHA256
- TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
- TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
- TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
enabled-protocols:
- TLSv1.3
- TLSv1.2
WebTestClient construction
protected def getHttpClient(String protocol, List<String> cipherSuites) {
def httpClient = HttpClient
.create().secure({
it.sslContext(SslContextBuilder
.forClient()
.trustManager(InsecureTrustManagerFactory.INSTANCE)
.protocols(protocol)
.ciphers(cipherSuites)
.build())
})
WebTestClient
.bindToServer(new ReactorClientHttpConnector(httpClient))
.baseUrl(getBaseUrl())
.build()
}
Tests (in Groovy/Spock, sorry)
def "when TLSv1.2 allowed cipher suite used then accepted"() {
given:
def client = getHttpClient(TestConstants.TLSV1_2, List.of("TLS_AES_256_GCM_SHA384"))
expect:
client
.get()
.uri("/posts/${TestConstants.TEST_UUID}")
.header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
.exchange()
.expectStatus().isOk()
}
def "when TLSv1.3 allowed cipher suite used then accepted"() {
given:
def client = getHttpClient(TestConstants.TLSV1_3, List.of("TLS_CHACHA20_POLY1305_SHA256"))
expect:
client
.get()
.uri("/posts/${TestConstants.TEST_UUID}")
.header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
.exchange()
.expectStatus().isOk()
}
Logs
Comment From: wilkinsona
Thanks for the report, @optimisticninja. The two log files appear to have identical content.
Unfortunately, there are too many unknowns at the moment for us to be able to justify spending some time investigating the behaviour you've reported. For example, you haven't mentioned the version of Spring Boot you're using, which embedded web server you're using, or if the problem is specific to WebTestClient
. To remove (most of) those unknowns and help us to diagnose the problem, can you please turn the code snippets into a minimal sample that we can run ourselves? You can share such a sample with us by zipping it up and attaching it to this issue or pushing it to a separate repository on GitHub.
Comment From: neuroretransmit
Absolutely. I'll post back when I have a few minutes to do so. For the interim:
- Spring Boot: 2.5.6
- Embedded Server: Netty
Comment From: neuroretransmit
@wilkinsona Here is the demo requested with SSL logging enabled.
It appears to only be failing for TLSv1.2. Looking here it appears TLSv1.2 should be using TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
, not TLS_AES_256_GCM_SHA384
. This appears to be a copy-pasta problem from the wrong section. Apologies for wasting your time.
Comment From: wilkinsona
No problem at all. Thanks for letting us know.
Comment From: neuroretransmit
No problem at all. Thanks for letting us know.
Slight update, there is an issue. TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA256
and TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
are non-operational, I have to use the RSA variants.
Comment From: neuroretransmit
Closing issue to re-open one without the cruft specific to cipher suites not working.
Comment From: bclozel
@optimisticninja WebClient
doesn't deal directly with the cipher suites or the TLS setup in general, so if you're encountering such an issue, chances are you can reproduce the same with a vanilla HttpClient
and report the issue directly to reactor-netty. Another way to consider such a problem is to try and reproduce it with another connector, like Jetty client.
Comment From: neuroretransmit
@optimisticninja
WebClient
doesn't deal directly with the cipher suites or the TLS setup in general, so if you're encountering such an issue, chances are you can reproduce the same with a vanillaHttpClient
and report the issue directly to reactor-netty. Another way to consider such a problem is to try and reproduce it with another connector, like Jetty client.
Just realized that after reviewing tests stacktrace. Thank you, crawling their issues looking for a dupe.
Comment From: neuroretransmit
@bclozel Are there changes slated to use the new API to override cipher suites for reactor-netty? Here are the details of the cause.
https://github.com/reactor/reactor-netty/issues/1543#issuecomment-814868341
Comment From: bclozel
What changes? With the code snippet above, you're already taking full control by providing your HttpClient
instance to the connector.
If you mean on the server side, we already took care of that in #25913 (as a follow up of the issue you pointed to). Did we miss something there?
Comment From: neuroretransmit
I was thinking from the perspective of overriding server-side via application.yml/properties. If I am misunderstanding the intended use here, please feel free to explain. Don't want to be a bother, just trying to understand.
Properties still seem to be ignored when explicitly defined in application.yml/properties and gets overridden with the default suites.
ciphers:
- TLS_CHACHA20_POLY1305_SHA256
- TLS_AES_256_GCM_SHA384
- TLS_AES_128_GCM_SHA256
- TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
- TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
- TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
Server logs
2021-11-17 07:20:09.241 DEBUG 561240 --- [ Test worker] io.netty.handler.ssl.JdkSslContext : Default cipher suites (JDK): [TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_AES_128_GCM_SHA256, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA, TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384]
Comment From: bclozel
I've set a debug point in Netty's JdkSslContext
and it seems the ciphers configured with the application properties are taken into account. The line you're seeing in the logs is really Netty logging the default ones.
Comment From: neuroretransmit
Sorry for all the confusion and getting ahead of myself. Thank you for your patience and time.
Comment From: bclozel
No worries at all - I didn't go as far as integration testing and truly replicating what you're trying. So there might be another issue somewhere.
Comment From: neuroretransmit
No worries at all - I didn't go as far as integration testing and truly replicating what you're trying. So there might be another issue somewhere.
I'm at a loss as to what this actually is. Here's a minimal example of failing ECDSA ciphers on TLSv1.2. I'm at a loss, here states these should be the highest priorty and I see their support in netty. Take a look in DemoControllerTest.
Comment From: wilkinsona
Thanks for the sample. The cipher itself matches but, as far as I can tell, your key isn't compatible. I updated application.yml
to only enable TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
and then attempted to make a request using curl:
$ curl -v --insecure https://localhost:8443/demo
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
* CAfile: /etc/ssl/cert.pem
CApath: none
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS alert, handshake failure (552):
* error:14004410:SSL routines:CONNECT_CR_SRVR_HELLO:sslv3 alert handshake failure
* Closing connection 0
curl: (35) error:14004410:SSL routines:CONNECT_CR_SRVR_HELLO:sslv3 alert handshake failure
Running the app with -Djavax.net.debug=all
shows what's happening on the server side during the handshake.
The client handshake indicates that it supports both of the enabled ciphers:
javax.net.ssl|DEBUG|52|reactor-http-nio-2|2021-11-19 10:37:38.272 GMT|ClientHello.java:808|Consuming ClientHello handshake message (
"ClientHello": {
"client version" : "TLSv1.2",
"random" : "F64DE80C16050352377440335C91AF40EA209A5BC39B5DCB8D4F89B280656529",
"session id" : "",
"cipher suites" : "[TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384(0xC030), TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384(0xC02C), TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384(0xC028), TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384(0xC024), TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA(0xC014), TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA(0xC00A), TLS_DHE_RSA_WITH_AES_256_GCM_SHA384(0x009F), TLS_DHE_RSA_WITH_AES_256_CBC_SHA256(0x006B), TLS_DHE_RSA_WITH_AES_256_CBC_SHA(0x0039), TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256(0xCCA9), TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256(0xCCA8), TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256(0xCCAA), UNKNOWN-CIPHER-SUITE(0xFF85)(0xFF85), TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256(0x00C4), TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA(0x0088), UNKNOWN-CIPHER-SUITE(0x0081)(0x0081), TLS_RSA_WITH_AES_256_GCM_SHA384(0x009D), TLS_RSA_WITH_AES_256_CBC_SHA256(0x003D), TLS_RSA_WITH_AES_256_CBC_SHA(0x0035), TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256(0x00C0), TLS_RSA_WITH_CAMELLIA_256_CBC_SHA(0x0084), TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256(0xC02F), TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256(0xC02B), TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256(0xC027), TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256(0xC023), TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA(0xC013), TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA(0xC009), TLS_DHE_RSA_WITH_AES_128_GCM_SHA256(0x009E), TLS_DHE_RSA_WITH_AES_128_CBC_SHA256(0x0067), TLS_DHE_RSA_WITH_AES_128_CBC_SHA(0x0033), TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256(0x00BE), TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA(0x0045), TLS_RSA_WITH_AES_128_GCM_SHA256(0x009C), TLS_RSA_WITH_AES_128_CBC_SHA256(0x003C), TLS_RSA_WITH_AES_128_CBC_SHA(0x002F), TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256(0x00BA), TLS_RSA_WITH_CAMELLIA_128_CBC_SHA(0x0041), TLS_ECDHE_RSA_WITH_RC4_128_SHA(0xC011), TLS_ECDHE_ECDSA_WITH_RC4_128_SHA(0xC007), SSL_RSA_WITH_RC4_128_SHA(0x0005), SSL_RSA_WITH_RC4_128_MD5(0x0004), TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA(0xC012), TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA(0xC008), SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA(0x0016), SSL_RSA_WITH_3DES_EDE_CBC_SHA(0x000A), TLS_EMPTY_RENEGOTIATION_INFO_SCSV(0x00FF)]",
"compression methods" : "00",
"extensions" : [
"server_name (0)": {
type=host_name (0), value=localhost
},
"ec_point_formats (11)": {
"formats": [uncompressed]
},
"supported_groups (10)": {
"versions": [x25519, secp256r1, secp384r1]
},
"signature_algorithms (13)": {
"signature schemes": [rsa_pkcs1_sha512, ecdsa_secp521r1_sha512, UNDEFINED-SIGNATURE(239)_UNDEFINED-HASH(239), rsa_pkcs1_sha384, ecdsa_secp384r1_sha384, rsa_pkcs1_sha256, ecdsa_secp256r1_sha256, UNDEFINED-SIGNATURE(238)_UNDEFINED-HASH(238), UNDEFINED-SIGNATURE(237)_UNDEFINED-HASH(237), rsa_sha224, ecdsa_sha224, rsa_pkcs1_sha1, ecdsa_sha1]
},
"application_layer_protocol_negotiation (16)": {
[h2, http/1.1]
}
]
}
)
Having found matching ciphers, ServerHello
calls sun.security.ssl.SSLKeyExchange.createPossessions(HandshakeContext)
but this returns an empty array as the key is not of either of the supported algorithms. This is reported twice, once for each cipher:
javax.net.ssl|DEBUG|52|reactor-http-nio-2|2021-11-19 10:37:38.278 GMT|X509Authentication.java:331|demo private or public key is not of EC algorithm
javax.net.ssl|DEBUG|52|reactor-http-nio-2|2021-11-19 10:37:38.279 GMT|X509Authentication.java:331|demo private or public key is not of EdDSA algorithm
javax.net.ssl|DEBUG|52|reactor-http-nio-2|2021-11-19 10:37:38.279 GMT|X509Authentication.java:331|demo private or public key is not of EC algorithm
javax.net.ssl|DEBUG|52|reactor-http-nio-2|2021-11-19 10:37:38.279 GMT|X509Authentication.java:331|demo private or public key is not of EdDSA algorithm
As a result, the handshake fails.
I am far from an SSL expert, so take the above with a grain or two of salt, but it looks to me like you need to change your keys.
Comment From: neuroretransmit
@wilkinsona Thank you so much! I was pulling my hair out. You are absolutely correct.