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.