Recently at Mobiconf in Kraków I saw Marcos Placona give an excellent talk about making apps more secure. One of the subjects that he covered was that of certificate pinning which can really help to secure your API calls to your server. In this short series we’ll take a look at what certificate pinning is, why it can be a good thing, some pitfalls and, of course, how to implement it on Android.
Certificate Pinning can help us to make our server API calls more secure but to understand why, and appreciate some of the pitfalls, we’ll start with a quick explanation of how secure communication over HTTPS works. SSL is based upon public key cryptography which is a mechanism for ensuring two things: That a third party cannot monitor the data passing between the app and the server; and that the server is our trusted server, and not a third party impersonating our server. The first is achieved by encrypting all of the traffic passing between the app and the server using public key encryption, and the second is achieved by validating the certificate that the server provides us.
Public key encryption is a mechanism for allowing two people (typically explanations refer to these as Alice and Bob) to communicate without a third person (named Charlie) from being able to understand the conversation even if he is able to see the messages passing backwards and forwards. The foundation of public key cryptography is a pair of cryptographic keys called the public and private keys. As the names suggest, the public key is publicly available, but the private key is only known to the person to whom the key pair belongs. Both Alice and Bob have their own key pair, so each has both a public key (which is available to everyone, including Charlie) and private key which is only known by the owner of that key. The important thing about the public and private keys in any given pair is that they are symmetric keys meaning that anything that gets encrypted using one of the keys can be decrypted again using the other key. In other words, if Bob were to encrypt a string using Alice’s public key, then only Alice’s private key will be able to decrypt that string – so only Alice can decode that string. To establish a secure session Alice and Bob both send each other their public keys. They can then each use the other person’s public key to encrypt a message that they want to send the other person, and then only that person can decrypt it using their private key. So even if Charlie sees the messages being passed between Alice and Bob (and is able to see the public keys as they are initially exchanged) he does not have either person’s private key so cannot decrypt the messages. This is a simplified explanation – the reality is that there is a bit more going on, but that simplified explanation is sufficient for what we need to understand here.
While this seems secure enough, there is a potential problem. During the initial exchange, Charlie could intercept the communications. Alice attempts to contact Bob, but the message goes to Charlie instead. Charlie then responds with his own public key, and also initiates a conversation with Bob sending his own public key but claiming to be Alice. He can then sit in the middle of the conversation between Alice and Bob who both think that they are communicating directly with the other person. As he receives a message from Alice encrypted using his own public key he can decrypt it using his own private key, and then re-encrypt it using Bobs public key to pass on to Bob. As a result Charlie is able to read the messages. This is commonly known as a man-in-the-middle attack.
In terms of how our apps work, a man-in-the-middle attack would involve a third party pretending to be our server and therefore able to eavesdrop on the API calls before passing them on to the real server.
One aspect of SSL which helps protect against man-in-the-middle attacks is the SSL certificate. If Bob is actually our server, and Alice is the app, then we can improve security by asking a trusted authority (known as a Certificate Authority) to provide a certificate linked to Bob’s public key. This certificate will only be generated by the CA once it has proof that Bob’s public key actually belongs to Bob. The certificate is linked to Bob’s public key, and is signed using the private key of the CA. Alice can then verify the signing using the public key of the CA (proof that it was the CA that signed the certificate, and not Charlie or someone else). Therefore Alice knows that the public key that Bob is offering has been validated by the CA and it is therefore Bob.
The actual mechanics here are actually a little more complicated than that. The CA will actually have a root certificate from which it can sign intermediate certificates, and these intermediate certificates will be used to sign Bob’s certificate, or even sign further intermediate certificates. There is actually a trust chain which starts at Bobs certificate, and may pass through zero or more intermediate certificates, before terminating at the root certificate. It is the root certificate that is the overall trusted authority. On Android there are a number of trusted root certificates built in to the OS, and these are used to validate the trust chain of Bob’s certificate.
It is actually possible for Bob to generate a self-signed certificate that is signed by his own root certificate rather than that of a CA. In such cases Android certificate validation will fail because the trust chain does not resolve to a trusted root certificate. It is possible to bypass the trust chain and accept any certificate that is provided by Bob, but it is not a good idea to do so unless we are fully aware of the security risks.
If Bob is using a self-signed certificate, then Charlie can generate his own self-signed certificate which identifies his own public key as belonging to Bob. He is then able to execute the same man-in-the-middle attack made possible because Bob’s use of a self-signed certificate requires Alice to disable the trust chain check.
One solution to this is to install Bob’s own root certificate as a trusted root on the Android device, which is pretty easy to do. Once this has been installed, then the trust chain validation will work again, and Charlie’s man-in-the-middle attack will no longer work.
The final edge-case is if Charlie were able to convince a CA that he is actually Bob and obtain a certificate for his own public key which identified it as belonging to Bob. While this is certainly not easy to do, neither is it impossible. Charlie could then execute his man-in-the-middle attack and trust chain validations would succeed so Alice would have confidence that she was communicating with Bob, even though it was passing via Charlie (who is able to see all of the unencrypted communications).
Certificate Pinning is a solution which can be used to either secure a self-signed certificate from Bob, or help prevent this case where Charlie was able to obtain a certificate which identifies him as Bob and has a trust-chain which resolves to a trusted root certificate. In the next article in this series we’ll take a loot what certificate pinning is, and look at some potential pitfalls.
© 2018, Mark Allison. All rights reserved.
Copyright © 2018 Styling Android. All Rights Reserved.
Information about how to reuse or republish this work may be available at http://blog.stylingandroid.com/license-information.