Certificate or Public Key Pinning is an extension to TLS that is highly effective for bot mitigation by protecting the HTTPS connection between your app and API from snooping by third parties (otherwise known as a Man in the Middle attack). The technique makes use of the TLS protocol which requires the server to provide a certificate containing its public key. If the client has a copy of the expected certificate (or just the public key) and checks for a match before completing the TLS handshake then the client is considered pinned to the server.
Most popular HTTP libraries provide straightforward support for pinning and even if you are using lower level HTTP implementations there are plenty of resources available to ensure you get it right. For example, you can pin a connection made with OkHttp by adding a CertificatePinner object to your OkHttpClient build as follows.
CertificatePinner certificatePinner = new CertificatePinner.Builder() .add("publicobject.com", "sha256/afwiKY3RxoMmLkuRW1l7QsPZTJPwDS2pdDROQjXw8ig=") .add("bikewise.org", "sha256/x9SZw6TwIqfmvrLZ/kz1o0Ossjmn728BnBKpUFqGNVM=") .build(); OkHttpClient client = OkHttpClient.Builder() .certificatePinner(certificatePinner) .build();
Here we are pinning the connection to two servers using secure hashes (SHA256) of their public keys. The OkHttpClient object handles the checking of the public key contained in the certificate. The certificate is sent by the server as part of the TLS negotiation at the start of request execution. If the hashes do not match, the OkHttpClient throws an SSLPeerUnverifiedException exception informing the app the connection is (probably) being intercepted.
Job done. Right ?
The main difficulty with pinning is not technical but operational. By embedding fixed information about the server (the certificate) into the app you create a dependency between the two, as the term pinning implies. This means that whenever you (or your ops team) are planning to change the certificate on the server you must:
Generate the certificate in advance
Build, test and publish a new version of the app with both the new certificate and the old one.
Wait for most (80%, 90%, 99% ?) of your users to upgrade to the new version
Change the cert on the server
Build, test and publish a new version of the app with the old certificate removed.
OK so, that is a bit of a pain but it can be worked into the usual release cycle like any other API change, albeit a breaking one. In addition you could pin to the public key (as in the OkHttp example) rather than the certificate on the assumption that keys have a longer lifetime. This would reduce the number of forced app updates due to certificate renewal. So everything is sort of fine.
Alas, it is not:
Server certificates are typically changed at least once per year as a matter of routine.
It's not uncommon for Ops to generate a new key pair for a new certificate. Longer term secure key storage is tricky.
Do you really only access one API with your app? How many of the other APIs need pinned too? Now all the backend server certs need to be in sync too.
How on earth do you sync with the certificates on 3rd party apps which are legitimately allowed to access the API?!
How do you handle quickly replacing a compromised certificate or key? Having a secondary backup certificate pre-built into the app is a possible solution but multiplies all of your key management issues and how do you know the backup has not been compromised if the primary has?
It quickly becomes clear why many organizations decide that perhaps it's not worth the bother, especially when they are looking at retrofitting pinning into a complex legacy system.
Even if you successfully navigate the operational difficulties inherent in implementing pinning, there is still the giant pink elephant in the room that is unpinning. The development of hooking frameworks such as Xposed has reached such a level of sophistication that a rich ecosystem of modules exists to target various aspects of Android apps including the pinning functionality of HTTP libraries. Using them is very straightforward.
Unpinning works by hooking, or intercepting, function calls in the app as it runs. Once intercepted the hooking framework can alter the values passed to or from the function. When you use an HTTP library to implement pinning, the functions called by the library are well known so people have written modules which specifically hook these checking functions so they always pass regardless of the actual certificates used in the TLS handshake. Similar approaches exist for iOS too.
As you can see, these tools do not require a huge investment of time or specialist skill to use. Anyone can quickly bypass pinning protection if they want to analyze and reverse engineer your API at will.
As part of the Approov SDK, we already have various techniques to defend against Man in the Middle attacks on the link between our SDK in your app and our servers used for attestation. So that our customers can pin their server connections dynamically, without worrying about syncing certificates, we have extended this protection to their APIs also. Read all about it here!