How to add SSLContext with TLSv1.2 in Android Kitkat

coding

Recently, I came across an issue where a server was not responding in my android application running in Kitkat version of Android. Yes, Kitkat is old, and why one should use it when we can always upgrade to a higher version of Android. We can argue, find pros and cons, but that’s not the point. If we have the technology, it should be more flexible and sometimes, a customer will not be able to go for a higher version of Android. When technology plays with time, certain things are not in control. So if websites had made TLSv1.2 compulsory when Android Kitkat was released, Google had no choice but to release a solution. But that wasn’t the case.

Back to the present time, my Android application is trying to contact a server that has TLSv1.2 enabled. In my Android application, I used Android provided DefaultHttpClient.

So the issue is “How do we add a SSLContext in this DefaultHttpClient“?

Solution –

Create a HTTP Client Socket Factory –

We will build a socket factory that will implement LayeredSocketFactory like below:

public class TlsSniSocketFactory implements LayeredSocketFactory {

  private final static HostnameVerifier hostnameVerifier = new StrictHostnameVerifier();

  private final boolean acceptAllCertificates;
  private final String selfSignedCertificateKey;

  public TlsSniSocketFactory() 
        {
    this.acceptAllCertificates = false;
    this.selfSignedCertificateKey = null;
  }

  public TlsSniSocketFactory(String certKey) 
        {
    this.acceptAllCertificates = false;
    this.selfSignedCertificateKey = certKey;
  }

  public TlsSniSocketFactory(boolean acceptAllCertificates) 
        {
    this.acceptAllCertificates = acceptAllCertificates;
    this.selfSignedCertificateKey = null;
  }

  // Plain TCP/IP (layer below TLS)

  @Override
  public Socket connectSocket(Socket s, String host, int port, InetAddress localAddress, int localPort,
                HttpParams params) throws IOException {
    return null;
  }

  @Override
  public Socket createSocket() throws IOException {
    return null;
  }

  @Override
  public boolean isSecure(Socket s) throws IllegalArgumentException {
    if (s instanceof SSLSocket) {
      return s.isConnected();
    }
    return false;
  }

  // TLS layer

  @Override	
  public Socket createSocket(Socket plainSocket, String host, int port, boolean autoClose) throws IOException 
        {
    if (autoClose) 
                {
      plainSocket.close();
    }

    SSLCertificateSocketFactory sslSocketFactory =
        (SSLCertificateSocketFactory) SSLCertificateSocketFactory.getDefault(0);

    // For self-signed certificates use a custom trust manager
    if (acceptAllCertificates) {
      sslSocketFactory.setTrustManagers(new TrustManager[]{new IgnoreSSLTrustManager()});
    } else if (selfSignedCertificateKey != null) {
      sslSocketFactory.setTrustManagers(new TrustManager[]{new SelfSignedTrustManager(selfSignedCertificateKey)});
    }

    // create and connect SSL socket, but don't do hostname/certificate verification yet
    SSLSocket ssl = (SSLSocket) sslSocketFactory.createSocket(InetAddress.getByName(host), port);

    // enable TLSv1.1/1.2 if available
    // ssl.setEnabledProtocols(ssl.getSupportedProtocols());
                // this can be hard coded too
                ssl.setEnabledProtocols(new String[] {"TLSv1.2"});

    // set up SNI before the handshake
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
      sslSocketFactory.setHostname(ssl, host);
    } else {
      try {
        java.lang.reflect.Method setHostnameMethod = ssl.getClass().getMethod("setHostname", String.class);
        setHostnameMethod.invoke(ssl, host);
      } catch (Exception e) {
        Log.d(TlsSniSocketFactory.class.getSimpleName(), "SNI not usable: " + e);
      }
    }

    // verify hostname and certificate
    SSLSession session = ssl.getSession();
    if (!(acceptAllCertificates || selfSignedCertificateKey != null) && !hostnameVerifier.verify(host, session)) {
      throw new SSLPeerUnverifiedException("Cannot verify hostname: " + host);
    }

    return ssl;
  }

}

Register a HTTPS Scheme

We will register a scheme that will use our custom socket factory.

SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(new Scheme("https", new TlsSniSocketFactory(),443));

ClientConnectionManager ccm = new ThreadSafeClientConnManager(httpparams, schemeRegistry);
DefaultHttpClient defaultHttpClient = new DefaultHttpClient(ccm, httpparams);

Now if use defaultHttpClient to call a GET or POST request, we should be able to connect to a server that is enabled with TLSv1.2.

Conclusion

In this post, we showed how to use DefaultHttpClient in Android Kitkat with TLSv1.2. If you enjoyed this post, subscribe to my blog here.

References

  1. TLS Socket Factory
  2. Default HTTP Client