Android developmentAndroid tutorial

Android Security SSL Pinning

secure flutter application

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

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

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

Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("https://appmattus.com")
        .addConverterFactory(GsonConverterFactory.create())
        .client(okHttpClient)
        .build();

Pinning with Picasso

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

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

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

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);