How to do SSL pinning via public key

What is  SSL ?

SSL stands for Secure Socket Layer. It is a protocol for establishing secure data transfer between networked computers or servers.

Need For SSL-

Many developers assume that using HTTPS in a network layer is enough to be sure that user data transfer will be fully secured and not compromised by a Man-in-the-Middle (MitM) attack. In most cases that is true, but not always.

This is why to secure information passed by a browser, like a customer’s credit card number or password to a web server, most of the applications like an online store or online banking application uses SSL to prevent Man-in-the-Middle.

Android SSL Pinning–

There multiple ways we can perform SSL pinning in android 

1- Certificate pinning

2-Public key pinning 

3- SPKI (SubjectPublicKeyInfo) pinning 

Here we will focus our attention on Public key pinning as it is the most recommended way for safe SSL pinning operations.

To implement the pinning you need to know your certificates SPKI data.

we can retrieve it using OpenSSL.

Pinning on Android N(API 24) and above-:

If minimum SDK is Android N  then the implementation is very simple as Android has a new API in Android SDK from API 24 onwards the Network Security Configuration.

you just need to enter an XML configuration file that defines the pins you require in our  AndroidManifest.xml file.

<?xml version=”1.0″ encoding=”utf-8″?>

<network-security-config>

    <domain-config>

        <domain includeSubdomains=”true”>xyz.com</domain>

        <pin-set>

            <pin digest=”SHA-256″>6jj6tz+scE+XW+mlai6ZipDfFWn1dqvfLG+nU7tq1V8=</pin>

            <pin digest=”SHA-256″>LLh6dUR9y6Kka30RrAn7fKbQG/uEtLMkBgFF2Fuihg=</pin>

        </pin-set>

    </domain-config>

</network-security-config>

Declaration of Configuration file in AndroidManifest.xml:-

<?xml version=”1.0″ encoding=”utf-8″?>

<manifest xmlns:android=”http://schemas.android.com/apk/res/android”

    xmlns:tools=”http://schemas.android.com/tools”

         ………………

         …………….     

    <application

        android:name=”.application.XYZApplication”

        android:allowBackup=”false”

        android:icon=”@mipmap/app_launcher”

        android:label=”@string/app_name”

        android:supportsRtl=”true”

        android:theme=”@style/AppTheme”        

        android:networkSecurityConfig=”@xml/network_security_config”

        tools:replace=”android:allowBackup,android:icon”>

        </application>

</manifest>

if  you want to use network libraries for pinning we can do the following 

Pinning with Retrofit-

Pining with Retrofit is easy being built on top of OkHttp.

CertificatePinner certPinner = new CertificatePinner.Builder()

        .add(“xyz.com”,

              “sha256/8Rw90Ej3Ttt8RRkrg+WYDS9n7IS03bk5bjP/UXPtaY8=”)

        .build();

OkHttpClient okHttpClient = new OkHttpClient.Builder()

        .certificatePinner(certPinner)

        .build();

Retrofit retrofit = new Retrofit.Builder()

        .baseUrl(“https://xyz.com”)

        .addConverterFactory(GsonConverterFactory.create())

        .client(okHttpClient)

        .build();

Pinning with HttpUrlConnection-

if you are still using HttpURlconnection consider upgrading it to some other network libraries.




import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;


public class WebServiceManager {

    private static String TAG = "WebServiceManager";


 

    
    private static String requestHttpsServer(String targetUrl, String jsonRequestString, int httpMethod, int readTimeOutType) throws IELTSException {
        String jsonResponseString = null;
        HttpsURLConnection httpsUrlConnection = null;
        URL url;
        try {
            url = new URL(AppConstants.BASE_URL + targetUrl);


            httpsUrlConnection = (HttpsURLConnection) url.openConnection();

            httpsUrlConnection.setConnectTimeout(AppConstants.TIME_CONNECTION_TIMEOUT);
            if (readTimeOutType == AppConstants.READ_TIMEOUT_SMALL) {
                httpsUrlConnection.setReadTimeout(AppConstants.TIME_CONNECTION_DATA_WAIT_TIMEOUT_SMALL);
            } else {
                httpsUrlConnection.setReadTimeout(AppConstants.TIME_CONNECTION_DATA_WAIT_TIMEOUT_LARGE);
            }

// applying SSL pinning
        
            // getting SPKI keys for validation
            String[] spki = CachingManager.getAppContext().getResources().getStringArray(R.array.spki);
            List  validPins = Arrays.asList(spki);

            // getting trust manager
            X509TrustManagerExtensions trustManagerExt = prepTrustManager();
            // validating pins for ssl pinning
            validatePinning(trustManagerExt, httpsUrlConnection, validPins);


          ......................
		  ...................
        return jsonResponseString;
    }

    


    // this method validate the pins return by the server with the public key  pin and throw an exception
	
    private static void validatePinning(X509TrustManagerExtensions trustManagerExt, HttpsURLConnection conn, List validPins)
            throws SSLException {
        String certChainMsg = "";
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-256");

            // getting list of trust chain
            List trustedChain = trustedChain(trustManagerExt, conn);

            // validating pins for both side
            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);
    }

    // extracting the X509  certificates chains for server end
	
    private static List 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);
        }
    }

    // prepare TrustManagers
	
    private static X509TrustManagerExtensions prepTrustManager() {
        // creating trust manager to get X509 trust manager
        TrustManagerFactory trustManagerFactory = null;
        try {
            trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init((KeyStore) null);

        } catch (NoSuchAlgorithmException | KeyStoreException e) {
            Log.e(TAG, e.toString());
        }

        // Find first X509TrustManager in the TrustManagerFactory
        X509TrustManager x509TrustManager = null;
        for (TrustManager trustManager : trustManagerFactory.getTrustManagers()) {
            if (trustManager instanceof X509TrustManager) {
                x509TrustManager = (X509TrustManager) trustManager;
                break;
            }
        }


        return new X509TrustManagerExtensions(x509TrustManager);

    }

}

Leave a Reply