(1) Create a new Android Project, targeting Android N and above.

(2) Create a config file "network_security_config.xml" under res/xml directory.

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <domain-config>
        <domain includeSubdomains="false">api.your-domain.com</domain>
        <pin-set expiration="2018-12-31">
            <pin digest="SHA-256">see next step</pin>
        </pin-set>
    </domain-config>
</network-security-config>

(3) In this config file, you can specify which are the domains that require Pinning. You can set an expiration date as well as a list of valid PINs. The PIN can be obtained using the following command. It will print the pin hash in the last row of the output. Copy this hash and put it in your config file.

openssl s_client -servername api.your-domain.com -connect api.your-domain.com:443 | openssl x509 -pubkey -noout | openssl rsa -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64

depth=3 /C=SE/O=AddTrust AB/OU=AddTrust External TTP Network/CN=AddTrust External CA Root
verify error:num=19:self signed certificate in certificate chain
verify return:0
writing RSA key
fqiLxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=

Alternatively, you can use ssllabs test tool to get it.


(4) Next, let's create a simple API Service using Retrofit.

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    try {
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("https://api.your-domain.com/")
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        TestService service = retrofit.create(TestService.class);

        Call<OAuthToken> call = service.getOAuthToken();
        call.enqueue(new Callback<OAuthToken>() {
            @Override
            public void onResponse(Call<OAuthToken> call, Response<OAuthToken> response) {
                Log.i("Response", response.toString());
            }

            @Override
            public void onFailure(Call<OAuthToken> call, Throwable t) {
                Log.i("Failure", t.toString());
            }
        });

        call.execute();

    } catch (Exception ex) {
        ex.printStackTrace();
    }

}

(5) Run the code, you should get a positive response.

(6) To test if the Pinning is really working, you can modify the PIN hash string in your network security config file. E.g. change the first 2 characters.

(7) Run the code again, you should not be able to connect to the remote server. In logcat, you will see:

I/Failure: javax.net.ssl.SSLHandshakeException: Pin verification failed

(8) Remember to revert the PIN hash after testing.

Now your App is secured with SSL Certificate Pinning.


Note:

To support Certificate Pinning in an older version of Android. You can take a look at CWAC-netsecurity or Dexprotector.