Search code examples
javanettyspring-cloudsslhandshakeexceptionreactor-netty

Cipher list configured in Netty client not being sent down to Server


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?


Solution

  • 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);
     }
    });