Search code examples
javasslspring-integrationkeytool

SSLHandshakeException:no cipher suites in common


I've seen other questions with this error, but most are not using Spring Integration. The one I saw that did use Spring Integration was due to a corrupt keystore. I have re-created my keystores and truststores many times, so that doesn't appear to be the issue.

I am trying to enable TLS between my TcpNetClientConnectionFactory and TcpNetServerConnectionFactory. Here is my Spring config:

@Bean
public TcpNetClientConnectionFactory clientConnectionFactory() {
    TcpNetClientConnectionFactory factory = new TcpNetClientConnectionFactory("localhost", 6000);
    factory.setTcpSocketFactorySupport(sslSocketFactorySupport(
            "security/empty.jks",
            "security/client-truststore.jks",
            "changeit",
            "changeit"));
    factory.setTcpSocketSupport(new DefaultTcpSocketSupport());
    factory.setSerializer(TcpCodecs.lengthHeader2());
    factory.setDeserializer(TcpCodecs.lengthHeader2());
    return factory;
}

@Bean
TcpNetServerConnectionFactory serverConnectionFactory() {
    TcpNetServerConnectionFactory factory = new TcpNetServerConnectionFactory(6000);
    factory.setTcpSocketFactorySupport(sslSocketFactorySupport(
            "security/server.jks",
            "security/empty.jks",
            "changeit",
            "changeit"));
    factory.setTcpSocketSupport(new DefaultTcpSocketSupport());
    factory.setSerializer(TcpCodecs.lengthHeader2());
    factory.setDeserializer(TcpCodecs.lengthHeader2());
    return factory;
}

@Bean
public DefaultTcpNetSSLSocketFactorySupport sslSocketFactorySupport(String keyStore, String trustStore, String keyStorePassword, String trustStorePassword) {
    TcpSSLContextSupport contextSupport = new DefaultTcpSSLContextSupport(keyStore, trustStore, keyStorePassword, trustStorePassword);
    return new DefaultTcpNetSSLSocketFactorySupport(contextSupport);
}

@Bean
public IntegrationFlow requestFlow() {
    return IntegrationFlows
            .from(Tcp.inboundAdapter(serverConnectionFactory()))
            .<byte[]>handle((p, h) -> MessageBuilder
                    .withPayload(p)
                    .build())
            .handle(Tcp.outboundAdapter(serverConnectionFactory()))
            .get();
}

@Bean
public IntegrationFlow responseFlow() {
    return IntegrationFlows
            .from(Tcp.inboundAdapter(clientConnectionFactory()))
            .channel(MessageChannels
                    .queue("responseChannel")
                    .get())
            .get();
}

The empty.jks file is an empty keystore. Here are the commands I used to generate my server keystore and client truststore:

#!/bin/bash

keytool -genkey \
        -keypass changeit \
        -storepass changeit \
        -keystore src/main/resources/security/server.jks

keytool -export \
        -storepass changeit \
        -file src/main/resources/security/server.cer \
        -keystore src/main/resources/security/server.jks

keytool -import \
        -v \
        -trustcacerts \
        -file src/main/resources/security/server.cer \
        -keypass changeit \
        -storepass changeit \
        -keystore src/main/resources/security/client-truststore.jks

Finally, here's a simple test to send a Message back and forth between my two connection factories. Without TLS, it works fine. With TLS, I get SSL read exceptions about cipher suites.

@Autowired
private TcpNetClientConnectionFactory client;

@Autowired
private QueueChannel responseChannel;

@Test
public void requestAndResponse() throws Exception {
    Message<byte[]> request = MessageBuilder
            .withPayload("foo".getBytes())
            .build();

    client.getConnection().send(request);

    assertNotNull(responseChannel.receive(1000));
}

What is wrong with my SSL setup?

UPDATE

I am using Java 8. Here are the client ciphers:

TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, TLS_RSA_WITH_AES_256_CBC_SHA256, TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384, TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384, TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, TLS_DHE_DSS_WITH_AES_256_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA, TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDH_RSA_WITH_AES_256_CBC_SHA, TLS_DHE_RSA_WITH_AES_256_CBC_SHA, TLS_DHE_DSS_WITH_AES_256_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, TLS_RSA_WITH_AES_128_CBC_SHA256, TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256, TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, TLS_DHE_DSS_WITH_AES_128_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_DSS_WITH_AES_128_CBC_SHA, 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_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384, TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, TLS_DHE_DSS_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256, TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, TLS_DHE_DSS_WITH_AES_128_GCM_SHA256, TLS_EMPTY_RENEGOTIATION_INFO_SCSV

server ciphers:

TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, TLS_RSA_WITH_AES_256_CBC_SHA256, TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384, TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384, TLS_DHE_RSA_WITH_AES_256_CB    C_SHA256, TLS_DHE_DSS_WITH_AES_256_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA, TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDH_RSA_WITH_AES_256_CBC_SHA, TLS_DHE_RSA    _WITH_AES_256_CBC_SHA, TLS_DHE_DSS_WITH_AES_256_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, TLS_RSA_WITH_AES_128_CBC_SHA256, TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDH_RSA_WITH_AES_12    8_CBC_SHA256, TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, TLS_DHE_DSS_WITH_AES_128_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, TLS_E    CDH_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_DSS_WITH_AES_128_CBC_SHA, 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_RSA_WITH_AE    S_256_GCM_SHA384, TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384, TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, TLS_DHE_DSS_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_RSA_WITH_AES_128_GCM_    SHA256, TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256, TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, TLS_DHE_DSS_WITH_AES_128_GCM_SHA256, TLS_EMPTY_RENEGOTIATION_INFO_SCSV

I can see right off the bat that the first one matches.

UPDATE 2

Using javax.net.debug=all, here is the ClientHello:

*** ClientHello, TLSv1.2
RandomCookie:  GMT: 1567538676 bytes = { 28, 158, 236, 83, 48, 241, 225, 168, 30, 197, 146, 201, 129, 246, 199, 156, 9, 78, 61, 157, 11, 64, 25, 26, 41, 181, 233, 45 }
Session ID:  {}
Cipher Suites: [TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, TLS_RSA_WITH_AES_256_CBC_SHA256, TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384, TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384, TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, TLS_DHE_DSS_WITH_AES_256_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA, TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDH_RSA_WITH_AES_256_CBC_SHA, TLS_DHE_RSA_WITH_AES_256_CBC_SHA, TLS_DHE_DSS_WITH_AES_256_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, TLS_RSA_WITH_AES_128_CBC_SHA256, TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256, TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, TLS_DHE_DSS_WITH_AES_128_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_DSS_WITH_AES_128_CBC_SHA, 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_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384, TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, TLS_DHE_DSS_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256, TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, TLS_DHE_DSS_WITH_AES_128_GCM_SHA256, TLS_EMPTY_RENEGOTIATION_INFO_SCSV]
Compression Methods:  { 0 }

I see the raw write of the ClientHello (204 bytes), and then a raw read of 5 bytes and then 199 bytes. Interestingly enough, after the raw read is formatted, it appears to be another ClientHello, not a ServerHello:

pool-1-thread-2, READ: TLSv1.2 Handshake, length = 199
 C0 0A C0 14  .(.=.&*** ClientHello, TLSv1.2
RandomCookie:  GMT: 1567539109 bytes = { 119, 78, 120, 53, 121, 162, 190, 126, 136, 74, 228, 139, 71, 227, 132, 120, 177, 116, 234, 187, 14, 134, 253, 158, 109, 174, 41, 17 }
Session ID:  {}
Cipher Suites: [TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, TLS_RSA_WITH_AES_256_CBC_SHA256, TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384, TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384, TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, TLS_DHE_DSS_WITH_AES_256_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA, TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDH_RSA_WITH_AES_256_CBC_SHA, TLS_DHE_RSA_WITH_AES_256_CBC_SHA, TLS_DHE_DSS_WITH_AES_256_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, TLS_RSA_WITH_AES_128_CBC_SHA256, TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256, TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, TLS_DHE_DSS_WITH_AES_128_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_DSS_WITH_AES_128_CBC_SHA, 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_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384, TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, TLS_DHE_DSS_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256, TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, TLS_DHE_DSS_WITH_AES_128_GCM_SHA256, TLS_EMPTY_RENEGOTIATION_INFO_SCSV]
Compression Methods:  { 0 }

followed quickly by this:

pool-1-thread-2, SEND TLSv1.2 ALERT:  fatal, description = handshake_failure
pool-1-thread-2, WRITE: TLSv1.2 Alert, length = 2
[Raw write]: length = 7
0000: 15 03 03 00 02 02 28                               ......(
pool-1-thread-2, called closeSocket()
pool-1-thread-2, handling exception: javax.net.ssl.SSLHandshakeException: no cipher suites in common
pool-1-thread-2, called close()
pool-1-thread-2, called closeInternal(true)
2019-09-04 08:48:21.637 ERROR 1755 --- [pool-1-thread-2] o.s.i.i.tcp.connection.TcpNetConnection  : Read exception localhost:63727:6000:51c3e8e9-194c-4f56-b238-960fa3b7c05c SSLHandshakeException:no cipher suites in common

Any idea why the second Hello is another ClientHello, not ServerHello?


Solution

  • This turned out to be an Application Context issue. I was defining the server SSL configuration beneath my src/test/java. One of my Autowired fields was pulling a client SSL configuration bean from src/main/java, not the server bean I thought it was. Hence the double ClientHellos.

    Added a couple @Qualifiers to make sure my server config was being Autowired where I thought it was