
Hi Everyone in this Android tutorial, I am sharing how to use Android security SSL Pinning. Core to SSL (Secure Socket Layer) is the X509 certificate, and trust in the chain of certificates that leads from your leaf certificate through an intermediate certificate authority (CA) to a root certificate authority. This chain is determined when the SSL connection is established.The First Few Milliseconds of an HTTPS Connectiontalks a little about the process of creating a secure connection.
Using SSL in the Android app is easy, however ensuring that the connection is actually secure is a different matter. A man-in-the-middle attack can be carried out using several methods includingARP cache poisoningandDNS spoofing.
Root certificates come pre-installed on Android devices with around 150 included in Android N. You can check what’s on your own device by going toSettings > Security > Trusted Credentials. There is an assumption that none of these root CAs or the 1000’s of intermediate CAs these root certificates trust will miss-issue leaf certificates for domain names they shouldn’t. If you don’t believe me read about the CAsDigiNotar,GlobalSign, andComodo. In addition to all this, the user’s device could be compromised with a rogue certificate installed on it through social engineering.
SSL pinning also knew as Public Key Pinning is an attempt to solve these issues, ensuring that the certificate chain used is the one your app expects by checking a particular public key or certificate appears in the chain.
So let’s Start on Code Part of Android Security SSL Pinning.
Pinning on Android N
If your minimum SDK is Android N (API 24) then the implementation couldn’t be simpler as Android has a new API in town theNetwork Security Configuration. Even better this configuration even works for WebViews with no additional effort on your part.
Through a simple entry in your AndroidManifest.xml file, you specify an XML configuration file that defines the pins you require. Of course, being XML based this isn’t useful if you want to dynamically specify your pins.
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config>
<domain includeSubdomains="true">Codeplayon.com</domain>
<pin-set>
<pin digest="SHA-256">-----------------------------------------</pin>
<pin digest="SHA-256">-----------------------------------------</pin>
</pin-set>
</domain-config>
</network-security-config>
It is possible to configure an expiration date by using<pin-set expiration=“2017–02–28“>but it is worth noting you are then accepting insecure connections once that date has passed for users that don’t/can’t upgrade your app.
The Network Security Configuration also makes it easy if you need to support self-signed certificates or certificate authorities that are not trusted system root certificates.
Pinning with OkHttp
Implementation with OkHttp is pretty straightforward with theCertificatePinnerclass.
CertificatePinner certPinner = new CertificatePinner.Builder()
.add("appmattus.com",
"sha256/4hw5tz+scE+TW+mlai5YipDfFWn1dqvfLG+nU7tq1V8=")
.build();
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.certificatePinner(certPinner)
.build();
OkHttp has offered certificate pinning since OkHttp 2.1. Unfortunately, early versions suffer from aVulnerability in OkHttp’s Certificate Pinnerso ensure you use at least OkHttp 3.2.0 or OkHttp 2.7.5.
Pinning with Retrofit
With Retrofit being built on top of OkHttp, configuring it for pinning is as simple as setting up anOkHttpClientas shown above and supplying that to yourRetrofit.Builder.
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://appmattus.com")
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.build();
Pinning with Picasso
Picasso, as with Retrofit, is just a matter of configuring the downloader.
If you use OkHttp then you just provide the configuredOkHttpClient. Currently, Picasso 2 doesn’t support OkHttp 3 out of the box so you may need to use thePicasso 2 OkHttp3 Downloader.
Picasso picasso = new Picasso.Builder(getApplicationContext())
.downloader(new OkHttpDownloader(okHttpClient))
.build();
Picasso.setSingletonInstance(picasso);
The implementation with theUrlConnectionDownloaderis slightly more work but you can implement a similar technique as shown for Volley by overloading theopenConnectionmethod of the downloader and overriding theHostnameVerifier.
Pinning with HttpUrlConnection
Firstly if you are still using HttpUrlConnection consider upgrading to OkHttp. The version built into Android, naturally, is a fixed version so you won’t get any security updates or bug fixes.
In the Android training document,Security with HTTPS and SSL, the implementation suggested is based off pinning your certificates through a customTrustManagerandSSLSocketFactory. However as with the other APIs presented here I will show you how to pin against the SPKI instead.
Instead the certificates must be sanitised usingX509TrustManagerExtensionsintroduced in API 17 to return a trusted chain back to the devices trust store that is checked instead. Below is a modified version doing just this along with base 64 encoding for the pins to match the other APIs presented here.
private void validatePinning( X509TrustManagerExtensions trustManagerExt, HttpsURLConnection conn, Set<String> validPins) throws SSLException { String certChainMsg = ""; try { MessageDigest md = MessageDigest.getInstance("SHA-256"); List<X509Certificate> trustedChain = trustedChain(trustManagerExt, conn); for (X509Certificate cert : trustedChain) { byte[] publicKey = cert.getPublicKey().getEncoded(); md.update(publicKey, 0, publicKey.length); String pin = Base64.encodeToString(md.digest(), Base64.NO_WRAP); certChainMsg += " sha256/" + pin + " : " + cert.getSubjectDN().toString() + "\n"; if (validPins.contains(pin)) { return; } } } catch (NoSuchAlgorithmException e) { throw new SSLException(e); } throw new SSLPeerUnverifiedException("Certificate pinning " + "failure\n Peer certificate chain:\n" + certChainMsg); }private List<X509Certificate> trustedChain( X509TrustManagerExtensions trustManagerExt, HttpsURLConnection conn) throws SSLException { Certificate[] serverCerts = conn.getServerCertificates(); X509Certificate[] untrustedCerts = Arrays.copyOf(serverCerts, serverCerts.length, X509Certificate[].class); String host = conn.getURL().getHost(); try { return trustManagerExt.checkServerTrusted(untrustedCerts, "RSA", host); } catch (CertificateException e) { throw new SSLException(e); } }
This would then be called as follows:
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance( TrustManagerFactory.getDefaultAlgorithm()); trustManagerFactory.init((KeyStore) null); // Find first X509TrustManager in the TrustManagerFactory X509TrustManager x509TrustManager = null; for (TrustManager trustManager : trustManagerFactory.getTrustManagers()) { if (trustManager instanceof X509TrustManager) { x509TrustManager = (X509TrustManager) trustManager; break; } } X509TrustManagerExtensions trustManagerExt = new X509TrustManagerExtensions(x509TrustManager);...URL url = new URL("https://www.appmattus.com/"); HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection(); urlConnection.connect();Set<String> validPins = Collections.singleton ("4hw5tz+scE+TW+mlai5YipDfFWn1dqvfLG+nU7tq1V8="); validatePinning(trustManagerExt, urlConnection, validPins);
The call tourlConnection.connect()performs the SSL handshake however does not transmit any data until you callurlConnection.getInputStream().
Pinning with Volley
The usual suggested route for pinning with Volley is to pin against certificates as shown inPublic Key Pinning with Volley Library. This involves creating a custom SSLSocketFactory which adds in an inherent risk of introducing other security vulnerabilities.
Overriding theHostnameVerifiercan be achieved with the following:
RequestQueue requestQueue = Volley.newRequestQueue(appContext, new HurlStack() { @Override protected HttpURLConnection createConnection(URL url) throws IOException { HttpURLConnection connection = super.createConnection(url); if (connection instanceof HttpsURLConnection) { HostnameVerifier delegate = urlConnection.getHostnameVerifier(); HostnameVerifier pinningVerifier = new PinningHostnameVerifier(delegate); urlConnection.setHostnameVerifier(pinningVerifier); } return connection; } });...public static class PinningHostnameVerifier implements HostnameVerifier { private final HostnameVerifier delegate; private PinningHostnameVerifier(HostnameVerifier delegate) { this.delegate = delegate; } @Override public boolean verify(String host, SSLSession sslSession) { if (delegate.verify(host, sslSession)) { try { validatePinning(sslSession.getPeerCertificates(), host, validPins); return true; } catch (SSLException e) { throw new RuntimeException(e); } } return false; } }
The above can be used on a plain implementation of HttpUrlConnection also if you don’t want to set up.
Pinning with Apache HttpClient
As with HttpUrlConnection you really shouldn’t be using HttpClient anymore especially with theApache HTTP Client Removalin Android 6.0. Android was frozen at Apache HttpClient v4.0 since API 1.
The same technique shown for Volley also works for Apache HttpClient with some tweaks to thePinningHostnameVerifier:
SSLSocketFactory socketFactory = (SSLSocketFactory) client
.getConnectionManager()
.getSchemeRegistry()
.getScheme("https")
.getSocketFactory();
X509HostnameVerifier delegate = socketFactory.getHostnameVerifier();
X509HostnameVerifier pinningHostnameVerifier =
new PinningHostnameVerifier(delegate);
socketFactory.setHostnameVerifier(pinningHostnameVerifier);