loading...

Certificate Pinning your Android and iOS apps.

drankolq profile image Daniel Luque Quintana ・3 min read

When we, developers, are working in the development of any kind of software, we can't forget about security 🔐. The minimum security measure we should use is HTTPS as the protocol to share information between a client (in this case, an Android/iOS app) and a server, followed by an updated cryptographic protocol like TLS 1.2 (SSL 3.0 is vulnerable!)
You may think that using an HTTPS is enough but in some cases like banking applications, where sensitive data may be send between our client and our server, could be risky.
By default, when making a TLS connection, the client check two things:

  1. The server's certificate matches the requested hostname.
  2. The server's certificate has a chain of truth back to a trusted root certificate.

What it doesn't do is check if the certificate is the specific certificate you know your server is using, and that's a possible security vulnerability: if the client is compromised and a unsafe certificate is installed, someone could do a man-in-the-middle attack.


The solution to this problem is certificate pinning: storing a certificate on our client to ensure that any SSL request made matches the one our server has. Let me explain you how to do it on both Android and iOS apps.

 Android

OkHttp lib provide a CertificatePinner class to be added to an OkHttpClient instance. The easiest way to pin a host is turn on pinning with a broken configuration and read the expected configuration when the connection fails.

CertificatePinner certificatePinner = new CertificatePinner.Builder()
         .add("mydomain.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
         .build();
     OkHttpClient client = OkHttpClient.Builder()
         .certificatePinner(certificatePinner)
         .build();

After a request is executed, you'll see this message on the console:

javax.net.ssl.SSLPeerUnverifiedException: Certificate pinning failure!
   Peer certificate chain:
     sha256/afwiKY3RxoMmLkuRW1l7QsPZTJPwDS2pdDROQjXw8ig=: CN=mydomain.com, OU=PositiveSSL
     sha256/klO23nT2ehFDXCfx3eHTDRESMz3asj1muO+4aIdjiuY=: CN=COMODO RSA Secure Server CA
     sha256/grX4Ta9HpZx6tSHkmCrvpApTQGo67CYDnvprLg5yRME=: CN=COMODO RSA Certification Authority
     sha256/lCppFqbkrlJ3EcVFAkeip0+44VaoJUymbnOaEUk7tEU=: CN=AddTrust External CA Root
   Pinned certificates for mydomain.com:
     sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
   at okhttp3.CertificatePinner.check(CertificatePinner.java)
   at okhttp3.Connection.upgradeToTls(Connection.java)
   at okhttp3.Connection.connect(Connection.java)
   at okhttp3.Connection.connectAndSetOwner(Connection.java)

The exception will provide you the server's certificate public key hashes. Paste them on the CertifinatePinner and done! ✔

CertificatePinner certificatePinner = new CertificatePinner.Builder()
       .add("mydomain.com", "sha256/afwiKY3RxoMmLkuRW1l7QsPZTJPwDS2pdDROQjXw8ig=")
       .add("mydomain.com", "sha256/klO23nT2ehFDXCfx3eHTDRESMz3asj1muO+4aIdjiuY=")
       .add("mydomain.com", "sha256/grX4Ta9HpZx6tSHkmCrvpApTQGo67CYDnvprLg5yRME=")
       .add("mydomain.com", "sha256/lCppFqbkrlJ3EcVFAkeip0+44VaoJUymbnOaEUk7tEU=")
       .build();

iOS

The iOS solution is not so straightforward because you need to store the certificate itself inside your app. In my case, I've used Alamofire as HTTP client lib for Swift.
First, you need to get the server's certificate in .der format and add it to your iOS project.

openssl s_client -showcerts -servername mydomain.com -connect mydomain.com:443
 </dev/null | openssl x509 -outform DER > mydomainCert.der

And now, lets enable certificate pinning: to do it we need both ServerTrustPolicy and SessionManager objects. The first one will define the hostname and certificates that will be used in the process:

var serverTrustPolicies = [
    "mydomain.com": .pinCertificates(
    certificates: ServerTrustPolicy.certificates(),
    validateCertificateChain: true,
    validateHost: true
  ),
]

ServerTrustPolicy.certificates() will return all stored certificates and the booleans will validate the certificate chain and the hostname.
Lastly, create a SessionManager object using this trust policies:

var sessionManager = SessionManager(serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies!))

Done! ✔. Just use this sessionManager object to execute request

sessionManager.request("https://mydomain.com/api", method: .get, headers: headers)...

Feedback is welcome! Hope it's useful ☺️

Sources

OkHttp: https://github.com/square/okhttp/wiki/HTTPS
Alamofire: https://github.com/Alamofire/Alamofire#security

Discussion

pic
Editor guide
Collapse
silentsudo profile image
Ashish Agre

HI Daniel, this is great blog post, i was thinking to write one, but found this one, so instead creating new one i would append my post content here :),

Along with the content metioned by Daniel, we can also generate sha key from terminal using public certificate received from openssl s_connect command.

First create a file using touch command say touch test.crt, then run openssl s_client -connect www.google.com:443 copy content beginning from -----BEGIN CERTIFICATE----- to... -----END CERTIFICATE----- in test.crt after that run this command openssl x509 -in test.crt -pubkey -noout | openssl rsa -pubin -outform der | openssl dgst -sha256 -binary | \
openssl enc -base64
this most probably should give you correct value. For more information please refer this page developer.mozilla.org/en-US/docs/W...