SSL pinning to prevent a man-in-the-middle (MITM) attack on Android/iOS Application — PART -2
Implementing SSL pinning
Before writing any code there are a few decisions you need to make from the certificate you use for pinning to what you do when things go wrong. So let’s start by talking about the certificates.
In order to address these downsides, servers are typically configured with certificates from well-known issuers called Certificate Authorities (CAs). The host platform generally contains a list of well-known CAs that it trusts. As of Android 4.2 (Jelly Bean), Android currently contains over 100 CAs that are updated in each release. Similar to a server, a CA has a certificate and a private key. When issuing a certificate for a server, the CA signs the server certificate using its private key. The client can then verify that the server has a certificate issued by a CA known to the platform.However, while solving some problems, using CAs introduces another. Because of the CA issues certificates for many servers, you still need some way to make sure you are talking to the server you want. To address this, the certificate issued by the CA identifies the server either with a specific name such as gmail.com or a wildcarded set of hosts such as *.google.com.The following example will make these concepts a little more concrete. In the snippet below from a command line, the tool’s
s_client
command looks at Wikipedia's server certificate information. It specifies port 443 because that is the default for HTTPS. The command sends the output ofopenssl s_client
toopenssl x509
, which formats information about certificates according to the X.509 standard. Specifically, the command asks for the subject, which contains the server name information, and the issuer, which identifies the CA.
You can see that the certificate was issued for servers matching *.wikipedia.org by the RapidSSL CA.
for Example:
Root certificates come pre-installed on Android devices with around 150 included in Android N. You can check what’s on your own device by going to Settings > Security > Trusted Credentials. There is an assumption that none of these root CAs or the thousands of intermediate CAs these root certificates trust will mis-issue leaf certificates for domain names they shouldn’t. If you don’t believe me read about the CAs DigiNotar, GlobalSign, and Comodo. In addition to all this, the user's device could be compromised with a rogue certificate installed on it through social engineering.
SSL pinning also known as Public Key Pinning is an attempt to solve these issues, ensuring that the certificate chain used is the one your app expects by checking a particular public key or certificate that appears in the chain.
Intermediate certificate. By pinning against the intermediate certificate you are trusting that intermediate certificate authority to not mis-issue a certificate for your server(s). This also has the advantage that as long as you stick to the same certificate provider then any changes to your leaf certificates will work without having to update your app.
Root certificate. By pinning against the root certificate you are trusting the root certificate authority as well as any intermediaries they trust not to mis-issue certificates. Often the root and intermediate authorities are the same company in which case there’s not much difference in the number of people you are trusting, however, that’s not always the case.
You don’t have to pin against just one certificate in the chain. Indeed, the general recommendation is to pin against multiple levels to decrease the chances of bricking your app at the expense of trusting more certificate issuers. In my mind, it would seem prudent to pin at the intermediate and leaf levels to give a sensible balance.
Certificate or public key pinning?
In the Android training documentation about pinning their example pins against the certificate. However often it is better to pin against the public key, or more specifically the SubjectPublicKeyInfo (SPKI).
“Some websites rotate their certificates on a monthly basis and so pinning against a certificate would mean an app no one can use after a month or pushing out frequent updates” “Typically though the public key inside these rotated certificates stays the same. By pinning against the key you are reducing the chances of bricking your app by limiting the values that are checked”
With most network APIs you can choose to pin against the certificate or the SPKI although typically it is far easier to pin using the SPKI as most APIs provide a built-in mechanism, such as OkHttp’s CertificatePinner. With HttpUrlConnection there’s not much difference in the development cost so for consistency I have shown code using the SPKI.
Handling compromise
If the pins stored in your app don’t match those returned by the HTTPS connection how do you inform the user?
Fail hard. Stop the app from establishing the connection. This is the most secure and easiest to implement but introduces the chance of self-induced denial of service and user experience issues when connections cannot be established.
To implement that we can look into
Create a where we use PinningSSLSocketFactory PinningTrustManager to manage the PKI verification.
these are key files we can use for certificate pinning and checking the key from the server and a pinned key is the same.
where the stored certificate is loaded and checked with the server certificate when the initial handshake happens.
Using these util classes for pinned connection
we can get the HTTP URL connection by passing the pin (PKI) to PinningTrustanager.
Or Configure HttpClient using PinningSSLSocketFactory.
Handling private key leaks
If your private key were to ever get into the wrong hands you would need to setup certificates with a new key pretty quickly. This is why you are recommended to have backup SSL certificates ahead of time so you can quickly switch out the old ones at a moment's notice.
We are checking the certificate chain to see if it contains at least one of a set of keys, after all this is how pinning against, say, both the leaf and root certificates would work — you only need one of the pins to match. This also means you can store pins for any backup certificates ahead of time so when disaster strikes your app will continue to work with no new app release.
Where do I store the certificate/public key?
With time the pins your app needs to validate the certificate chain will change so your storage of these pins can have a big impact on the maintenance of your app.
There are a few options when it comes to this, each with its own caveats.
Preloading. Storing the pins hardcoded as part of the app is by far the easiest to implement. It can also be one of the more complex to maintain as a change in server certificates can mean you need to force your clients to upgrade your app.
Trust on first use. The app can on first execution determine the public key by making a call to the server and storing this for all future executions. This can help when your certificates rotate frequently, however, leads the app open to key tainting on initialization and pin expiration. If you don’t know your endpoints ahead of time though this can be a good option however you still need to carefully manage pin failures.
Over the air. For maximum flexibility creating a pin server that returns the current set of valid pins is a great option. You still need to pin the pin server but it's then incredibly easy to manage the pins stored and update this when disaster strikes.
Generally, it is best to store the pins out-of-band embedded as part of the app. If you are concerned with data leakage when your app is decompiled or reverse-engineered then obfuscating the keys is not hard to do — ProGuardand DexGuard can help in this regard. Obfuscation may be especially important if you store a backup key that is not currently in use to help hide it from prying eyes.
Retrieving your public keys
To implement the pinning examples shown here you need to know your certificate's SPKI data. Some of the APIs presented here will throw an exception with the keys returned by the server when pinning fails however if you want to retrieve these yourself it is possible to use OpenSSL.
Given a domain name, the code below prints out the public keys in the chain as an SHA-256 hash using base 64 encoding.
we could use a bash script to readcerts.sh:
Running the script gives the following results:
Note that this doesn’t print out the root certificate used.
Pinning on Android N
If your minimum SDK is Android N (API 24) then the implementation couldn’t be simpler as Android has a new API in town the Network Security Configuration. Even better this configuration even works for WebViews with no additional effort on your part.
Through a simple entry in your AndroidManifest.xml file you specify an XML configuration file that defines the pins you require. Of course, being XML-based this isn’t useful if you want to dynamically specify your pins.
<?xml version=”1.0″ encoding=”utf-8″?>
<network-security-config>
<domain-config>
<domain includeSubdomains=”true”>appmattus.com</domain>
<pin-set>
<pin digest=”SHA-256″>4hw5tz+scE+TW+mlai5YipDfFWn1dqvfLG+nU7tq1V8=</pin>
<pin digest=”SHA-256″>YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg=</pin>
</pin-set>
</domain-config>
</network-security-config>
It is possible to configure an expiration date by using “2017–02–28”> but it is worth noting you are then accepting insecure connections once that date has passed for users that don’t/can’t upgrade your app.
The Network Security Configuration also makes it easy if you need to support self-signed certificates or certificate authorities that are not trusted system root certificates.
CWAC-NetSecurity, an unofficial backport, offers support for this file back to Android 4.2 (API 17) however the current version, 0.3, has limited support if you use HttpUrlConnection but might be worth investigating if you use OkHttp.
Pinning with OkHttp
Implementation with OkHttp is pretty straightforward with the CertificatePinner class.
OkHttp has offered certificate pinning since OkHttp 2.1. Unfortunately, early versions suffer from a Vulnerability in OkHttp’s Certificate Pinner so ensure you use at least OkHttp 3.2.0 or OkHttp 2.7.5.
Pinning with Retrofit
With Retrofit being built on top of OkHttp, configuring it for pinning is as simple as setting up an OkHttpClient as shown above, and supplying that to your Retrofit. Builder.
Pinning with Picasso
Picasso, as with Retrofit, is just a matter of configuring the downloader.
If you use OkHttp then you just provide the configured OkHttpClient. Currently, Picasso 2 doesn’t support OkHttp 3 out of the box so you may need to use the Picasso 2 OkHttp3 Downloader.
The implementation with the UrlConnectionDownloader is slightly more work but you can implement a similar technique as shown for Volley by overloading the openConnection method of the downloader and overriding the HostnameVerifier.
Testing
Given you have implemented SSL Pinning how do you ensure that your implementation actually works?
mitmproxy
This is where a tool such as mitmproxy comes into play. This is a man-in-the-middle proxy for HTTP and HTTPS with an interactive console interface that allows network traffic to be intercepted, inspected, modified, and replayed.
Start mitmproxy using the command below and install mitmproxy’s root cert by visiting http://mitm.it/ on your device.
mitmproxy --add-upstream-certs-to-client-chain --insecure
If when you run your app the connection is refused because of a pinning failure then you are all set. In this scenario, you also shouldn’t see your requests in the mitmproxy console window. Note that apps on Android N devices do not trust user-installed certificates by default so in debug mode you have to enable this through the Network Security Configuration.
Alternative tools
Another tool worth investigating is SSLsplit — transparent SSL/TLS interception. On a rooted device, you can also try apps such as Android-SSL-TrustKiller or JustTrustMe to see if your SSL pinning can be beaten.
Conclusions
SSL pinning helps build secure mobile apps but it will not secure connections if the pinned host is compromised. It mainly protects the client however it also helps protect your servers by making it harder for hackers to snoop on the traffic figure out your API and exploit other security holes.
You should never trust the client talking to your server especially when it is possible to circumvent SSL pinning on a rooted device with such ease. With a little more technical know-how a user of your app can reverse engineer your app to disable it so they will be able to inspect the traffic. As an example see Bypassing Certificate Pinning on Android for fun and profit and Bypassing SSL Pinning on Android via Reverse Engineering.
Originally published at http://androidtechieblog.wordpress.com on June 27, 2018.