square / okhttp

Square’s meticulous HTTP client for the JVM, Android, and GraalVM.
https://square.github.io/okhttp/
Apache License 2.0
45.87k stars 9.16k forks source link

Websphere 8.5: SSL ClientHello with TLSv1 and only one cipher (SSL_RSA_WITH_3DES_EDE_CBC_SHA) #3173

Closed enricods closed 7 years ago

enricods commented 7 years ago

It seems that okhttp does not load a correct a SSL context configuration during the ClientHello phase during a SSL handshake, when deployed on Websphere 8.5 (tested on 8.5.5.0 and 8.5.5.10).

Here the code we use in our test servlet:

OkHttpClient client = new OkHttpClient();
X509TrustManager trustManager;
SSLSocketFactory sslSocketFactory;
try {

    TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
            TrustManagerFactory.getDefaultAlgorithm());
    trustManagerFactory.init((KeyStore) null);
    TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
    if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
        throw new IllegalStateException("Unexpected default trust managers:"
                + Arrays.toString(trustManagers));
    }
    trustManager = (X509TrustManager) trustManagers[0];

    SSLContext sslContext = SSLContext.getInstance("TLS");
    sslContext.init(null, new TrustManager[] { trustManager }, null);
    sslSocketFactory = sslContext.getSocketFactory();
} catch (GeneralSecurityException e) {
    throw new RuntimeException(e);
}

client = new OkHttpClient.Builder()
        .sslSocketFactory(sslSocketFactory, trustManager)
        .build();

Request requestOkhttp = new Request.Builder()
        .url("https://ps.pndsn.com")
        .build();

Response responseOkhttp = client.newCall(requestOkhttp).execute();
if (!responseOkhttp.isSuccessful()) throw new IOException("Unexpected code " + responseOkhttp);

This code generates the following ClientHello logs:

*** ClientHello, TLSv1
RandomCookie:  GMT: 1470746692 bytes = { 13, 37, 250, 150, 220, 1, 92, 228, 78, 1, 121, 129, 39, 94, 206, 28, 248, 18, 97, 143, 169, 18, 61, 105, 150, 84, 46, 62 }
Session ID:  {}
Cipher Suites: [SSL_RSA_WITH_3DES_EDE_CBC_SHA]
Compression Methods:  { 0 }
Extension renegotiation_info, ri_length: 0, ri_connection_data: { null }
Extension server_name, server_name: [host_name: ps.pndsn.com]

The proposed protocol is TLSv1 and only one (old) cipher in present in the list: because of that sometimes the SSL handshake terminates with error when the called server does not support this single cipher.

But the Websphere server has full cipher suite support: in fact if we setup a connection using directly the javax.net API, the cipher list is complete.

The Java net connection code:

try {
    TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
            TrustManagerFactory.getDefaultAlgorithm());
    trustManagerFactory.init((KeyStore) null);
    TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
    if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
        throw new IllegalStateException("Unexpected default trust managers:"
                + Arrays.toString(trustManagers));
    }
    trustManager = (X509TrustManager) trustManagers[0];
} catch (GeneralSecurityException e) {
    throw new AssertionError(); // The system has no TLS. Just give up.
}

SSLContext sslContext;
sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[] { trustManager }, null);

url = new URL("https://ps.pndsn.com");

HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
con.setSSLSocketFactory(sslContext.getSocketFactory());

The corresponding ClientHello logs:

*** ClientHello, TLSv1
RandomCookie: GMT: 1468749730 bytes = { 95, 206, 134, 169, 85, 159, 44, 73, 117, 200, 60, 67, 132, 132, 16, 172, 103, 107, 180, 190, 72, 136, 109, 177, 136, 182, 89, 192 }
Session ID: {}
Cipher Suites: [SSL_RSA_WITH_AES_256_CBC_SHA, SSL_DHE_RSA_WITH_AES_256_CBC_SHA, SSL_DHE_DSS_WITH_AES_256_CBC_SHA, SSL_RSA_WITH_AES_128_CBC_SHA, SSL_DHE_RSA_WITH_AES_128_CBC_SHA, SSL_DHE_DSS_WITH_AES_128_CBC_SHA, SSL_RSA_WITH_3DES_EDE_CBC_SHA, SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA, SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA]
Compression Methods: { 0 }
Extension renegotiation_info, ri_length: 0, ri_connection_data: { null }
Extension server_name, server_name: [host_name: ps.pndsn.com]

It seems that okhttp is not working correctly on Websphere 8.5.

These tests are based on okhttp version 3.6.0.

What kind of issue is this?

swankjesse commented 7 years ago

You'll need to configure your ConnectionSpec to enable obsolete cipher suites in OkHttp. https://github.com/square/okhttp/blob/master/samples/guide/src/main/java/okhttp3/recipes/CustomCipherSuites.java

enricods commented 7 years ago

It seems that IBMJSSE2, the JSSE implementation used by Websphere, in not compatible with this solution. The problem is that for this implementation, the cipher suite names may starts with SSL or TLS (IBM documentation), both accepted: but that is not true for OkHttp.

Example: The cipher TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 is not legacy (like the SSL_RSA_WITH_3DES_EDE_CBC_SHA cipher) and its Java name is the same as its id in the CipherSuite class.

The socket implementation correctly registers the cipher

socket.setEnabledCipherSuites(javaNames(spec.cipherSuites()));

but in the getEnabledCipherSuites list, the name is returned with the SSL_ prefix. For that reason the following lines of code, in the method CipherSuite.isCompatible(SSLSocket socket), return false

if (cipherSuites != null
        && !nonEmptyIntersection(cipherSuites, socket.getEnabledCipherSuites())) {
      return false;
}

and the class ConnectionSpecSelector throws the "Unable to find acceptable protocols" exception.

This problem seems very similar to the one descripted in this page https://github.com/cloudant/java-cloudant/issues/215

Is it possible that this is the real cause of the presence of the single cipher SSL_RSA_WITH_3DES_EDE_CBC_SHA in the client hello phase of my test servlet?

swankjesse commented 7 years ago

Oh interesting. We can probably fix to handle TLS or SSL prefixes

MaxPresman commented 7 years ago

@swankjesse Our implementation would benefit greatly from this proposal, does the team have an estimated ETA on bringing this patch into a release?

tlindener commented 7 years ago

I experienced the same problem today running an okhttp based client within IBM Bluemix. A solution would be appreciated!

tlindener commented 7 years ago

@swankjesse Hey, any update on an ETA?

swankjesse commented 7 years ago

No ETA. Any advice on reproducing this as a person who doesn’t use WebSphere? Ideally on a Mac?

bruceadams commented 7 years ago

This issue probably does not need all of WebSphere to be seen. It is likely that only IBM Java is needed. IBM publishes Docker images with IBM Java in it

docker run -it --rm ibmcom/ibmjava:8-sdk java -version
java version "1.8.0"
Java(TM) SE Runtime Environment (build pxa6480sr4fp1-20170215_01(SR4 FP1))
IBM J9 VM (build 2.8, JRE 1.8.0 Linux amd64-64 Compressed References 20170209_336038 (JIT enabled, AOT enabled)
J9VM - R28_20170209_0201_B336038
JIT  - tr.r14.java.green_20170125_131456
GC   - R28_20170209_0201_B336038_CMPRSS
J9CL - 20170209_336038)
JCL - 20170215_01 based on Oracle jdk8u121-b13

Similar issue for Apache's HTTP client: https://issues.apache.org/jira/browse/HTTPCLIENT-1784

Some relevant IBM documentation: http://www.ibm.com/support/knowledgecenter/SSYKE2_8.0.0/com.ibm.java.security.component.80.doc/security-component/jsse2Docs/matchsslcontext_tls.html

swankjesse commented 7 years ago

That helps a lot. Thanks @bruceadams !

coxd commented 7 years ago

Please note that the IBM doc @bruceadams linked to points out that OkHttpClient's usage of TLS in the creation of the default SSLContext will also cause problems for IBM Java users when attempting to connect to a server that does not offer TLS 1.0. Wanted to make sure that was clear given the initial focus was on 3DES being the only suite chosen.

I'm not a real coder these days - but I believe you could explicitly supply TLSv1,TLSv1.1,TLSv1.2 to get support for all 3 protocols consistently across Oracle and IBM. I do not know about other JDKs though.

swankjesse commented 7 years ago

Good news: I was able to establish connectivity with OkHttp and this particular server using the IBM docker image. That was a really good help.

But I need to make a code change. I need to change OkHttp’s CipherSuite class to ignore the TLS_ or SSL prefix when identifying a cipher suite. I didn’t realize that IBM and Oracle used different prefixes, and our code is too simple. This is ugly, but should be straightforward to overcome.

The other trick to connecting to this particular server is enabling TLS 1.2 on in the SSLSocket. This is awkward code. In my test I used DelegatingSSLSocketFactory configured like so:

      // Force TLS 1.2 on. This isn’t the on by default for IBM Java8.
      sslSocketFactory = new DelegatingSSLSocketFactory(sslSocketFactory) {
        @Override protected SSLSocket configureSocket(SSLSocket socket) throws IOException {
          socket.setEnabledProtocols(new String[] {
              TlsVersion.TLS_1_0.javaName(),
              TlsVersion.TLS_1_1.javaName(),
              TlsVersion.TLS_1_2.javaName()
          });
          return socket;
        }
      };

I’ll make the OkHttp change to ignore TLS_ vs. SSL_ prefixes.

For reference, here’s my updates to your test harness: https://gist.github.com/swankjesse/d094cb17d0562520cdbf64254542694a

germanattanasio commented 7 years ago

@JakeWharton @swankjesse, Thanks for fixing this. Are you guys planning on doing a release?

The reason I'm asking is because based on your CHANGELOG, it could be weeks before you release a new version.

In the meantime, I'm going to add a workaround similar to the one @swankjesse described in his gist.

Thanks again for fixing this.

coxd commented 7 years ago

@JakeWharton @swankjesse Adding to the request to see if you can accelerate 3.7.0 if your not already on the verge of releasing it. With the sweet32 vulnerability more and more people are removing 3DES as a supported suite on their servers - including us (IBM). I realize we apparently made a bad choice at some point to be different and unfortunately stuck with it are now suffering for it. Because Oracle still used SSL_ for older suites there have been suites available when using IBM Java until now when the last of these - the 3DES suites - are going away.

We did look into creating our own SslSocketFactory as a temporary solution with 3.6.0 but you don't just accept the suites configured in that factory - you require overlap with your hard-coded list.

coxd commented 7 years ago

@JakeWharton @swankjesse please ignore the entreaty above (which I think you already were 😄 ). The ability to simply turn off the cleansing of the default cipher suites provided by the socket by via providing a ConnectionSpec which has had .allEnabledCipherSuites() invoked on it was totally missed when looking at this.

swankjesse commented 7 years ago

Glad you're​ not blocked. Sorry for the radio silence; just lots going on at the moment.

iosnewbie2016 commented 6 years ago

Hi @swankjesse - Am using version 3.9.1 of the jar, but am still running into the handshake_failure exception. javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure at com.ibm.jsse2.j.a(j.java:23)

Am trying to communicate to APNS, using okhttp library. We are using IBM WAS 8.5.5 and Java 1.7. Can you pls let me know how can we work around this problem? thanks for your help.