Saurabh Tripathi
Saurabh Tripathi

Reputation: 111

Android SSL certificate pinning with retrofit

I want to do certificate pinning in android app. I am totally frustrated to understand this. Please help me

What i have:-

  1. .cert type certificate file.
  2. .key type key file in which private key stored.

I don't have domain name where this certificate will be implemented. I have only IP address. I am using okHttp and retrofit in my project. I have seen many example in stack overflow to implement this.

But I am not getting some things:- 1. If i have not domain name, Is it possible to implement ssl into IP address? 2. what will pins field contains, i don't have any private key. where will i found the private key(sha256/XXXXXXXXXX)?

Retrofit provideRetrofit(OkHttpClient okHttpClient, Gson gson) {
    CertificatePinner certPinner = new CertificatePinner.Builder()
            .add("patternField","pins")
            .build();

       OkHttpClient okHttpClientForPinning = new OkHttpClient.Builder()
            .certificatePinner(certPinner)
            .build();
    OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder();
    try {
       clientBuilder.sslSocketFactory(getSSLConfig(context).getSocketFactory());
    }
    catch (Exception e) {
        e.printStackTrace();
    }

    clientBuilder.hostnameVerifier(new HostnameVerifier() {
        @Override
        public boolean verify(String hostname, SSLSession session) {
            boolean value = true;
            //TODO:Some logic to verify your host and set value
            return value;
        }
    });

    return new Retrofit.Builder()
            .baseUrl(backendUrl)
            .client(okHttpClient)                      
            .client(okHttpClientForPinning)
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .addConverterFactory(GsonConverterFactory.create(gson))
            .build();


}

Upvotes: 4

Views: 13202

Answers (2)

thelearner
thelearner

Reputation: 126

Certificate pins in android using Retrofit and okHttp can be implemented like following. Replace the <PIN> with your sha256 key

   OkHttpClient.Builder httpBuilder = new OkHttpClient.Builder();
   CertificatePinner certificatePinner = new CertificatePinner.Builder()
            .add("<SERVER_URL>", "sha256/<PIN>")
            .build();
   OkHttpClient client = httpBuilder
            .certificatePinner(certificatePinner)
            .build();
   Retrofit retrofit = new Retrofit.Builder()
            .client(client)
            .baseUrl("<BASE_URL>")
            .addConverterFactory(GsonConverterFactory.create())
            .build();

Upvotes: 0

Exadra37
Exadra37

Reputation: 13104

The Certificate Pin

I don't have domain name where this certificate will be implemented. I have only IP address.

To get the pin for the server certificate by IP address you can try this bash script:

#!/bin/bash
# Heavily inspired on:
#   * https://medium.com/@appmattus/android-security-ssl-pinning-1db8acb6621e#ecea

set -eu

Main()
{
    local domain="${1? Missing domain name to extract and hash the certificate public key !!!}"

    local domain="${domain##*://}"

    local certs=$( openssl s_client -servername "${domain}" -host "${domain}" -port 443 -showcerts </dev/null 2>/dev/null | sed -n '/Certificate chain/,/Server certificate/p' )

    local rest=$certs

    while [[ "$rest" =~ '-----BEGIN CERTIFICATE-----' ]]; do

        cert="${rest%%-----END CERTIFICATE-----*}-----END CERTIFICATE-----"
        rest=${rest#*-----END CERTIFICATE-----}

        local certificate_name="$( echo "$cert" | grep 's:' | sed 's/.*s:\(.*\)/\1/' )"

        if [ -n "${certificate_name}" ]; then
            printf "\nCERTIFICATE NAME:\n\n${certificate_name} \n\n"
        fi

        printf "\nCERTIFICATE PUBLIC KEY HASH:\n\n"

        echo "$cert" |
            openssl x509 -pubkey -noout |
            openssl rsa -pubin -outform der 2>/dev/null |
            openssl dgst -sha256 -binary |
            openssl enc -base64

        echo

        exit 0

    done
}

Main ${@}

Save the bash script to a file and invoke it like:

bash hash-certificate-public-key-from-domain.bash ip-address-here

A real life example of using it against my personal website:

╭─exadra37@exadra37-Vostro-470 ~/Developer/Approov/currency-converter-demo  ‹volley-pinning-with-approov_cleanup-proguard*› 
╰─➤  ./bin/hash-certificate-public-key-from-domain.bash 68.183.252.187                                                                                                                                       130 ↵

CERTIFICATE NAME:

CN = exadra37.com 


CERTIFICATE PUBLIC KEY HASH:

1O0wDRM/roe6UTctDVQ5aN/ASNYsGQFVzXYhO34t5GE=

╭─exadra37@exadra37-Vostro-470 ~/Developer/Approov/currency-converter-demo  ‹volley-pinning-with-approov_cleanup-proguard*› 
╰─➤  ./bin/hash-certificate-public-key-from-domain.bash exadra37.com  

CERTIFICATE NAME:

CN = exadra37.com 


CERTIFICATE PUBLIC KEY HASH:

1O0wDRM/roe6UTctDVQ5aN/ASNYsGQFVzXYhO34t5GE=

╭─exadra37@exadra37-Vostro-470 ~/Developer/Approov/currency-converter-demo  ‹volley-pinning-with-approov_cleanup-proguard*› 
╰─➤  ./bin/hash-certificate-public-key-from-domain.bash https://exadra37.com

CERTIFICATE NAME:

CN = exadra37.com 


CERTIFICATE PUBLIC KEY HASH:

1O0wDRM/roe6UTctDVQ5aN/ASNYsGQFVzXYhO34t5GE=

As you can see the hash for the pin 1O0wDRM/roe6UTctDVQ5aN/ASNYsGQFVzXYhO34t5GE= is always the same.

  1. what will pins field contains, i don't have any private key. where will i found the private key(sha256/XXXXXXXXXX)?

The private key is private, thus if you could find it then all the security would be lost, unless you also control the server you are connecting to. Anyway you don't need it to perform Certificate pinning, just pin against the public key of the certificate, by generating an hash of it, aka a pin, like the above bash script does.

So the sha256 you are looking for is the output for the pin in the above script, aka in my examples 1O0wDRM/roe6UTctDVQ5aN/ASNYsGQFVzXYhO34t5GE=.

EDIT

An alternative is to use the free Mobile Certificate Pinning Generator online tool that will extract the public key pin with the added benefit of also generating the configuration file for you:

Config tab on the web page for the Mobile Certificate Pinning Generator tool

The resulting configuration file for Android:

Android tab on the web page for the Mobile Certificate Pinning Generator tool

Implementing Certificate Pinning

While I don't know how to help you with Retrofit I can show you a simpler way of implementing pinning.

Nowadays for Android a simpler way exists, and I describe it in my blog post Securing HTTPS With Certificate Pinning, where you can learn that it can be done just by adding the certificate pin to the network security config file, or if you need to support below API level 24, then you may want to use the TrustKit package in conjunction with the file.

Example network_security_config.xml with TrustKit:

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>

    <!-- Official Android N API -->
    <!--https://android-developers.googleblog.com/2016/07/changes-to-trusted-certificate.html-->
    <domain-config>
        <domain>currency-converter-demo.pdm.approov.io</domain>
        <trust-anchors>
            <!--<certificates src="user" />-->
            <certificates src="system" />
        </trust-anchors>
        <pin-set>
            <!-- Pin for: currency-converter-demo.pdm.approov.io -->
            <pin digest="SHA-256">qXHiE7hFX2Kj4ZCtnr8u8yffl8w9CTv6kE0U5j0o1XY=</pin>

            <!-- Backup Pin for: currency-converter-demo.pdm.approov.io -->
            <pin digest="SHA-256">47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=</pin>
        </pin-set>

        <!-- TrustKit Android API -->
        <!-- enforce pinning validation -->
        <trustkit-config enforcePinning="true" disableDefaultReportUri="true">
            <!-- Add a reporting URL for pin validation reports -->
            <report-uri>https://report.domain</report-uri>
        </trustkit-config>
    </domain-config>

</network-security-config>

Upvotes: 10

Related Questions