SSL/TLS protocols and cipher suites with the AndroidHttpClient

14,708

When using the AndroidHttpClient to make REST requests via HTTPS, how can I specify which SSL protocols and ciphers to use?

I don't believe you can do it with AndroidHttpClient. Everything I've done to harden the channel (like cipher lists, certificate pinning, and public key pinning) required a custom class somewhere, whether it was SSLSocketFactory or X509TrustManager. That's Java and that's Android. See How to override the cipherlist sent to the server by Android when using HttpsURLConnection?.

Share:
14,708
Jimmy Dee
Author by

Jimmy Dee

Updated on June 07, 2022

Comments

  • Jimmy Dee
    Jimmy Dee almost 2 years

    EDIT: Apologies if my original post was poorly worded. It led to some confusion, represented by comments to the original post. So let me try again:

    I started with a question. I wanted to solve a problem on Android, but didn't know how. I spent a lot of time looking around the net for solutions, but found not one single discussion of the matter anywhere. Nevertheless, a number of discussions, including StackOverflow threads, led me to a technique that looked promising. I solved the problem. But the solution was a little involved. So I decided to post the question here, thinking a) there must be a better solution, and hopefully someone will know and post the answere here; or b) maybe this is a good solution, and since I found no discussion of the matter anywhere else on the net, maybe my solution to the problem would be useful to others trying to do the same thing. Either way, the result would be a new contribution to StackOverflow: a question that is not answered elsewhere, with, eventually, the correct answer one way or the other. In fact, StackOverflow even invited me to answer my own question by way of sharing my knowledge when I originally posted it. That was, in fact, part of my motivation. Even the facts of this matter are not collected anywhere that I have found.

    So:

    Q. When using the AndroidHttpClient to make REST requests via HTTPS, how can I specify which SSL protocols and ciphers to use?

    This is important. The point is well taken that there is much that can be done on the server, but there are limits. The same server has to serve browsers, including old ones, as well as other clients. That means the server has to support a broad array of protocols and ciphers. Even within Android, if you have to support a lot of different versions, you're going to have to support a number of different protocols and ciphers.

    More importantly, by default, OpenSSL honors the client's cipher preference, not the server's, during the SSL handshake. See this post, for example, which says that you can override that behavior in the client by setting SSL_OP_CIPHER_SERVER_PREFERENCE. It's not entirely clear if this option can even be set on an SSLSocket in Java. Even if it can, you can set the cipher list yourself or tell your client to honor the server's list. Otherwise, you're getting the Android default, whatever it may be for the version you're running on (not the version you link against).

    If you take the defaults, the preference list sent by the client to the server by a Jellybean 4.2+ client can be seen here, starting around line 504. The default list of protocols starts around line 620. Though Jellybean 4.2+ includes support for OpenSSL 1.0.1, particularly TLSv1.1 and TLSv1.2, these protocols are not enabled by default. If you do not do something like what I've done, you cannot take advantage of TLSv1.2 support, despite the fact that support for TLSv1.2 is advertised on recent versions of Android. And the details vary quite a bit as you go back through previous Android versions. At least, you may wish to take a close look at the defaults on all supported versions and see what your client is actually doing. You might be surprised.

    There is lots more you can say about support for various protocols and ciphers. The point is, there can at times be a need to change these settings in a client.

    A. Use a custom SSLSocketFactory

    This worked well for me, and in the end, it was not very much code. But it was a little thorny for a couple of reasons:

    • There are two different SSLSocketFactory classes. The client needs an org.apache.http.conn.ssl.SSLSocketFactory, but OpenSSL returns a javax.net.ssl.SSLSocketFactory. This is definitely confusing. I used delegation to make the one call the other, without much problem.
    • Watch out for the difference between the OpenSSLContextImpl and the SSLContextImpl. One just wraps the other, but they are not interchangeable. When I used the SSLContextImpl.engineGetSocketFactory method—I forget what exactly happened, but something quietly failed. Be sure to use an OpenSSLContextImpl to get your socket factory, not an SSLContextImpl. You might also be able to use javax.net.ssl.SSLSocketFactory.getDefault(), but I'm not sure.
    • You cannot easily subclass AndroidHttpClient, because its constructor is private. This is unfortunate, since it provides some other nice goodies, like making sure you shut it down properly instead of leaking resources. The DefaultHttpClient works just fine. I borrowed the newInstance method from AndroidHttpClient (around line 105).

    The key points:

    public class SecureSocketFactory extends SSLSocketFactory {
        @Override
        public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
            // order should not matter here; the server should select the highest
            // one from this list that it supports
            s.setEnabledProtocols(new String[] { "TLSv1.2", "TLSv1" });
    
            // order matters here; specify in preference order
            s.setEnabledCipherSuites(new String[] { "ECDHE-RSA-RC4-SHA", "RC4-SHA" });
    

    Then:

    // when creating client
    HttpParams params;
    SchemeRegistry schemeRegistry = new SchemeRegistry();
    
    // use custom socket factory for https
    SSLSocketFactory sf = new SecureSocketFactory();
    schemeRegistry.register(new Scheme("https", sf, 443));
    
    // use the default for http
    schemeRegistry.register(new Scheme("http",
                PlainSocketFactory.getSocketFactory(), 80));
    
    ClientConnectionManager manager =
                new ThreadSafeClientConnManager(params, schemeRegistry);
    
    HttpClient client = new DefaultHttpClient(manager, params);
    

    Below Android 3.0 (Honeycomb/SDK 11), the supported cipher choices become more limited, and there's less motivation to override the defaults. On FROYO/SDK 8, my SecureSocketFactory blows up for some reason, and the jury is out on 10. But it seems to work for 11 upward just fine.

    The full solution is in a public github repo.

    Another solution might be to use an HttpsUrlConnection, which makes it easy to use a custom socket factory, but I imagine you'll probably lose even more of the convenience of the high-level HTTP client. I don't have any experience with HttpsUrlConnection.

  • Jimmy Dee
    Jimmy Dee over 10 years
    You're right, noloader. But I was definitely confused. EJP was correct in that if you set the server to use its own preferences, the order specified by the client is not important. This is not the default for openssl clients or servers, but it is common practice. That solved most of my problems. However, the main thing I learned is that you will not get OpenSSL 1.0.1 (and hence support for TLSv1.1 or higher) by default on Android 4.1 or higher, where it is available, without some custom code, at least if you are using AndroidHttpClient.
  • jww
    jww over 10 years
    Jimmy - "you will not get OpenSSL 1.0.1... if you are using AndroidHttpClient". Yeah, Android is totally borked. They are using a downlevel version of OpenSSL (0.9.8, I think). Plus, they screwed their cipher list up (RC4-MD5, I believe). Its been borked since Android 2.3 or so. See, for example, Why Android SSL was downgraded from AES256-SHA to RC4-MD5 in late 2010.
  • jww
    jww over 10 years
    Jimmy - Regarding your choice of RC4-SHA: don't do it. Bernstein, AlFardan, et al showed how broken RC4 is in TLS. There's nothing Ron or RSA can do about it. Plus, the BEAST attacks (padding oracles from 2011) that caused the use of RC4 (the lesser of the evils) has been remediated. The IETF has their collective heads up their respective asses. See, for example, On the Security of RC4 in TLS and WPA.
  • Jimmy Dee
    Jimmy Dee over 10 years
    noloader: No worries. The code snippet above is long obsolete. In the end, all I did was supply the OpenSSL 1.0.1 SSLSocketFactory to the Apache DefaultHttpClient, the immediate parent class of the AndroidHttpClient. Without customizing any further settings, that gave me support for TLSv1.2, and I just use the ciphers from the server. The real problem is that as of API level 19, OpenSSL seems to have vanished from the SDK. See stackoverflow.com/questions/20573215/…
  • KK_07k11A0585
    KK_07k11A0585 over 8 years
    @jww Thanks for the solution. How can I implement the same in android Webview ?