Certificate Pinning for Mobile Devs
Why?
Pinning is needed to protect data going from your app to your backend. Without certificate pinning your users traffic may be intercepted by a Man-in-the-middle attack (MITM). Even if you are using SSL connections (which everyone should be doing for ALL traffic), your app can fall victim to a MITM attack.
How can this happen? Isn’t SSL suppose to protect data while being transferred? Yes, however it all comes down to trust. By default, Android & iOS trusts certain certificate authorities (CAs) which issue certificates. Older versions of Android ( < Android 7.0 API 24 Nougat) Apps trust user installed custom certifications by default, which is a big security hole. Once installed, apps on < Android 7 will trust connections made with these certs, and herein lies the danger. iOS can also have malicious profiles installed which will allow traffic to non-OS trusted sites. Malicious actors (apps, websites, wifi portals) can prompt the user to install a certificate.
What is Pinning?
By default Android & iOS apps trust any connections that use a cert installed on the device. “Pinning” specifies one, or multiple(pinset) certificates to trust while connecting to a given domain. Once a cert is pinned in your app, any traffic that does not contain that cert will fail during the SSL handshake and the connection will fail.
Certification vs Public Key Pinning
There are basically two options for pinning:
1) pin the certificate
2) pin the public key used to sign the certificate
Typically pinning the certificate has the downside of a shorter lifespan. By pinning to a specific certificate you will need to push an update to your app with a new certificate. Certificates will look like this:
-----BEGIN CERTIFICATE----- zc3RhdGljmyyEY1kkZOCKt0xRu4CVWhJlpNdoRZenT9BrD8Fo22kt5MxAvCVrjT/g1BHDQd4S8pPK8kRwmMA8mdo8TiHJQMy0DBCDCDg== -----END CERTIFICATE-----
There is the option of fetching the certificate at runtime over the network, however this is less secure. Since there is no pinning on the initial request to fetch the certificate it is vulnerable to a MITM attack. Shipping the cert with the app is strongly encouraged.
Option 2 is pinning the public key. Since the same set of keys are used to sign certificates these do not change when certificates are rotated. The actual value used is a hash of the public key and will look something like this:
7HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=
How?
Luckily implementing pinning is very straight forward, however you do need to be careful to make sure all situations are covered. You are dealing with your apps connection to the server and if that is broken you app can become bricked.
Android 7.0+ (API 24+ Nougat) have certificate/key pinning via Network Security Configuration. Once correctly configured this will apply to all connections in your application and no code changes are needed. But few of us are using a minSdk of 24 in 2020, so additional code is needed.
The third-party library TrustKit-Android backport Network Security Config to API 17+. There is a bit more config needed for older versions, however it is simple to implement and the Trustkit docs do an excellent job. Trustkit provides a helper for OkHttp and UrlConnection.
Example network_security_config.xml:
<?xml version="1.0" encoding="utf-8"?> <network-security-config> <domain-config> <domain includeSubdomains="true">example.com</domain> <pin-set expiration="2018-01-01"> <pin digest="SHA-256">7HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=</pin> <!-- backup pin --> <pin digest="SHA-256">fwza0LRMXouZHRC8Ei+4PyuldPDcf3UKgO/04cDM1oE=</pin> </pin-set> </domain-config> </network-security-config>
TrustKit also has an iOS library which swizzles the apps NSURLConnection
and NSURLSession
delegates in order to automatically add pinning validation to the App’s HTTPS connections. Configuration of the public key hashes or certs is added to the Info.plist
.
Which Certificate?
SSL connections send a chain of certificates which include end user certs, intermediate certs, and a root cert.
end user -> intermediate -> root
End user certs will rotate periodically, which is problematic for mobile applications. Generally mobile applications will pin to an intermediate cert.
Getting public key
What you really need to pin is the sha256 hash of the public key digest. Since this is the public key, it is easily accessible and does not need to be protected. The openssl cli tool can give this to you. Below is a sample bash script to help with getting the sha256 public key digest:
#!/bin/bash certs=`openssl s_client -servername $1 -host $1 -port 443 -showcerts </dev/null 2>/dev/null | sed -n '/Certificate chain/,/Server certificate/p'` rest=$certs while [[ "$rest" =~ '-----BEGIN CERTIFICATE-----' ]] do cert="${rest%%-----END CERTIFICATE-----*}-----END CERTIFICATE-----" rest=${rest#*-----END CERTIFICATE-----} echo `echo "$cert" | grep 's:' | sed 's/.*s:\(.*\)/\1/'` echo "$cert" | openssl x509 -pubkey -noout | openssl rsa -pubin -outform der 2>/dev/null | openssl dgst -sha256 -binary | openssl enc -base64 done
Also can be obtained from websites such as: https://www.ssllabs.com/ssltest/analyze.html
HPKP vs Mobile Public Key Pinning
You may know that public key pinning was popular a few years ago for websites and now is now deprecated in most browsers. This method is called HTTP Public Key Pinning (HPKP) and involved sending a pinset to the browsers in a header. So why do we do this in mobile apps? The idea with HPKP (Http Public Key Pinning) are very similar to public key pinning in mobile apps, however there are some key differences. One being app developers have the ability to push updates which can contain updated public keys and code. Once a website is pinned with a public key pinset it will deny traffic until it expires or cache is cleared.
HPKP is deprecated in favor of Expect-CT (Certificate Transparency). iOS has added built in support for CT in iOS 12.1.1. Android does not provide CT automatically, and instead relies on developers to implement some network security. That said iOS apps may still want to be explicit there network security and add certificate pinning as well. While there has been some usage of CT for mobile apps, it has not caught on as it has in browsers. For more info you can start here or here.
Backup certificate/keys
Always have a backup plan when dealing with security. What happens if the private keys from your CA are stolen? This will be a bad day for lots of people. This means rogue certifications can be created for you domain with the same public key. With the new certificate a MITM attack can be executed and your app will never know. The solution is to move to a new certificate and possibly a new CA that can be trusted. If you app is still pinning the old certificate what happens?
Your app is bricked – totally useless to the user – or worse they are communicating with a malicious server. The only secure way to fix the situation is to update your app with a new cert.
To prevent this ship the app with a backup or multiple backup certs. These can be public keys that your organization generates and stores securely for disaster recovery. In the event of compromised primary cert you can then create a new cert with the key for use with your server and the app will continue to work. The app should also be updated ASAP to remove the compromised certificate.
Adding the backup is easy as adding another entry to the pinset.
Protect your users!!
Adding certificate pinning to your app will protect your users data. This post only scratches the surface of SSL & network security. To continue learning here are some helpful resources:
Great iOS & overall Cert pinning intro from Raywenderlich
Android & iOS MITM attack prevention @Now Secure
OWASP certificate and public key pinning
Official Android Network Security Config training article
iOS certificate pinning technique
Google I/0 Session on Android security with some certificate pinning (2012)
Interesting argument against Lets Encypt with links to counter argument –
Securing Mobile Applications with Cert Pinning