I have below piece of code in order to configure a cipher list and send down to the server in Netty
client.
Pair<String, String> config = Pair.of(keyStore, keyStorePassword);
if (!filters.containsKey(config)) {
HttpClient securedClient = this.httpClient.secure(sslContextSpec -> {
SslContextBuilder sslContextBuilder = SslContextBuilder.forClient();
sslContextBuilder.keyManager(loadKeyStore(keyStore, keyStorePassword));
sslContextBuilder.trustManager(InsecureTrustManagerFactory.INSTANCE);
if(!StringUtils.isBlank(ciphers)) {
sslContextBuilder.ciphers(getCiphersList(ciphers));
}
sslContextSpec.sslContext(sslContextBuilder);
});
filters.put(config, new NettyRoutingFilter(securedClient, headersFilters, properties));
}
return filters.get(config).filter(exchange, chain);
This code was working fine. Recently, the spring boot and cloud version upgraded and it gives the SSL handshake failure afterwards.
As per the server side SSL debug logs, this failure is due to no ciphers suites in common
error.
I checked it further and figured out that the client is trying to initiate the handshake with TLSv1.2
and looks to me Netty
is filtered out configured cipher (TLS_DHE_DSS_WITH_AES_256_GCM_SHA384
).
I could see below two logs parts in client's SSL logs and only the second part reached the server. (without TLS_DHE_DSS_WITH_AES_256_GCM_SHA384
)
javax.net.ssl|DEBUG|34|es-writer-6|2021-03-25 10:48:25.081 SGT|ClientHello.java:651|Produced ClientHello handshake message (
"ClientHello": {
"client version" : "TLSv1.2",
"random" : "47 5B 0F EB C7 9C 1B A1 21 C0 60 15 AC FD 5B 88 67 07 F9 2B 04 4A 74 ED 9B 37 CD 84 81 65 87 F8",
"session id" : "33 FD 38 2C 7D 71 62 79 2E F1 99 7D FB 5F 69 F6 04 23 9A 31 04 9B 5D 0A 16 B2 76 10 A3 C5 74 E6",
"cipher suites" : "[TLS_AES_128_GCM_SHA256(0x1301), TLS_AES_256_GCM_SHA384(0x1302), TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384(0xC02C), TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256(0xC02B), TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384(0xC030), TLS_RSA_WITH_AES_256_GCM_SHA384(0x009D), TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384(0xC02E), TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384(0xC032), TLS_DHE_RSA_WITH_AES_256_GCM_SHA384(0x009F), TLS_DHE_DSS_WITH_AES_256_GCM_SHA384(0x00A3), TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256(0xC02F), TLS_RSA_WITH_AES_128_GCM_SHA256(0x009C), TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256(0xC02D), TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256(0xC031), TLS_DHE_RSA_WITH_AES_128_GCM_SHA256(0x009E), TLS_DHE_DSS_WITH_AES_128_GCM_SHA256(0x00A2), TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384(0xC024), TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384(0xC028), TLS_RSA_WITH_AES_256_CBC_SHA256(0x003D), TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384(0xC026), TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384(0xC02A), TLS_DHE_RSA_WITH_AES_256_CBC_SHA256(0x006B), TLS_DHE_DSS_WITH_AES_256_CBC_SHA256(0x006A), TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA(0xC00A), TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA(0xC014), TLS_RSA_WITH_AES_256_CBC_SHA(0x0035), TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA(0xC005), TLS_ECDH_RSA_WITH_AES_256_CBC_SHA(0xC00F), TLS_DHE_RSA_WITH_AES_256_CBC_SHA(0x0039), TLS_DHE_DSS_WITH_AES_256_CBC_SHA(0x0038), TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256(0xC023), TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256(0xC027), TLS_RSA_WITH_AES_128_CBC_SHA256(0x003C), TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256(0xC025), TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256(0xC029), TLS_DHE_RSA_WITH_AES_128_CBC_SHA256(0x0067), TLS_DHE_DSS_WITH_AES_128_CBC_SHA256(0x0040), TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA(0xC009), TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA(0xC013), TLS_RSA_WITH_AES_128_CBC_SHA(0x002F), TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA(0xC004), TLS_ECDH_RSA_WITH_AES_128_CBC_SHA(0xC00E), TLS_DHE_RSA_WITH_AES_128_CBC_SHA(0x0033), TLS_DHE_DSS_WITH_AES_128_CBC_SHA(0x0032), TLS_EMPTY_RENEGOTIATION_INFO_SCSV(0x00FF)]",
javax.net.ssl|DEBUG|2D|reactor-http-nio-1|2021-03-25 10:48:25.275 SGT|ClientHello.java:651|Produced ClientHello handshake message (
"ClientHello": {
"client version" : "TLSv1.2",
"random" : "48 3A 89 68 7F CE 49 83 FE EF 12 81 2D E3 EE 54 47 90 A8 33 EE 95 F6 16 76 B9 81 41 E9 DA B6 2E",
"session id" : "69 CC E2 A3 BA 6A AF 47 8B 3E 83 53 4E 5F 62 90 B3 91 F3 E3 9D 5C 1A 2D C3 F7 F7 6D 2E EA 38 7C",
"cipher suites" : "[TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384(0xC02C), TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256(0xC02B), TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256(0xC02F), TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384(0xC030), TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA(0xC013), TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA(0xC014), TLS_RSA_WITH_AES_128_GCM_SHA256(0x009C), TLS_RSA_WITH_AES_128_CBC_SHA(0x002F), TLS_RSA_WITH_AES_256_CBC_SHA(0x0035), TLS_AES_128_GCM_SHA256(0x1301), TLS_AES_256_GCM_SHA384(0x1302)]",
How to send down the configured ciphers even they are outdated(weak)? any issue with SslContextBuilder
configuration here?
Update:
Further analysis helped to figured out the actual reason to miss out configured cipher.
as part of Spring boot version upgrade, reactor Netty
version has upgraded from 0.9.12 to 1.0.3.
I could see below differences in the logic of reactor.netty.tcp.SslProvider
0.9.12
void updateDefaultConfiguration() {
switch(this.type) {
case H2:
this.sslContextBuilder.sslProvider(io.netty.handler.ssl.SslProvider.isAlpnSupported(io.netty.handler.ssl.SslProvider.OPENSSL) ? io.netty.handler.ssl.SslProvider.OPENSSL : io.netty.handler.ssl.SslProvider.JDK).ciphers(Http2SecurityUtil.CIPHERS, SupportedCipherSuiteFilter.INSTANCE).applicationProtocolConfig(new ApplicationProtocolConfig(Protocol.ALPN, SelectorFailureBehavior.NO_ADVERTISE, SelectedListenerFailureBehavior.ACCEPT, new String[]{"h2", "http/1.1"}));
break;
case TCP:
this.sslContextBuilder.sslProvider(OpenSsl.isAvailable() ? io.netty.handler.ssl.SslProvider.OPENSSL : io.netty.handler.ssl.SslProvider.JDK);
case NONE:
}
}
1.0.3
void updateDefaultConfiguration() {
switch(this.type) {
case H2:
this.sslContextBuilder.sslProvider(io.netty.handler.ssl.SslProvider.isAlpnSupported(io.netty.handler.ssl.SslProvider.OPENSSL) ? io.netty.handler.ssl.SslProvider.OPENSSL : io.netty.handler.ssl.SslProvider.JDK).ciphers(HTTP2_CIPHERS, SupportedCipherSuiteFilter.INSTANCE).applicationProtocolConfig(new ApplicationProtocolConfig(Protocol.ALPN, SelectorFailureBehavior.NO_ADVERTISE, SelectedListenerFailureBehavior.ACCEPT, new String[]{"h2", "http/1.1"}));
break;
case TCP:
this.sslContextBuilder.sslProvider(OpenSsl.isAvailable() ? io.netty.handler.ssl.SslProvider.OPENSSL : io.netty.handler.ssl.SslProvider.JDK).ciphers((Iterable)null, IdentityCipherSuiteFilter.INSTANCE).applicationProtocolConfig((ApplicationProtocolConfig)null);
case NONE:
}
}
Here in 1.0.3 it is forcefully overriding the ciphers to null and no longer able to set ciphers manually.
How can I resolve this issue?
Finally, I figured out SSL context needs to build before assigning to context spec in new reactor Netty
version.
Working code:
HttpClient securedClient = this.httpClient.secure(sslContextSpec -> {
SslContextBuilder sslContextBuilder = SslContextBuilder.forClient();
sslContextBuilder.keyManager(loadKeyStore(keyStore, keyStorePassword));
sslContextBuilder.trustManager(InsecureTrustManagerFactory.INSTANCE);
if (!StringUtils.isBlank(ciphers)) {
sslContextBuilder.ciphers(getCiphersList(ciphers));
}
try {
sslContextSpec.sslContext(sslContextBuilder.build());
} catch (SSLException e) {
log.error("Error in building the SSL context ",e);
}
});