What is SSL?
SSL stands for Secure Sockets Layer. SSL is the standard security technology for establishing an encrypted link between a client and a server. This link ensures that all data passed between the web server and browser remain private.
These two systems can be a server (our backend server) and a client (our android app) or a server and another server (our backend server interacting with another server).
This is the most widely deployed cryptographic protocol to provide a secure communication channel.
How does SSL ensure security of data?
A Man-in-the-Middle attack occurs when an attacker places himself between the server/host and the client, impersonating one of them. In other words, when the client is connecting to the server, it is actually dealing with the hacker and vice versa. Thus, although the client “thinks” that it has established an encrypted connection with the server, but in reality both of them are actually “talking” to the attacker who can view and modify the data. For this reason, everyone calls it a “Man-in-the-Middle” attack.
SSL encrypts the data being transmitted so that a third party or any “Man-in-the-Middle” cannot “eavesdrop” on the transmission and view the data being transmitted. Only the client and the secure server are able to recognize and understand the data. This means that anyone who tries to intercept this data will only see a garbled mix of characters that’s nearly impossible to decrypt.
This technique derives from the concept of the SSL Certificate and the Certificate Authority’s infrastructure. It is based on the usage of the private key, which establishes a valid connection when it is associated with the corresponding certificate. This initiates an authentication process called a handshake between two communicating devices to ensure that both devices are really who they claim to be. SSL also digitally signs data in order to provide data integrity, verifying that the data is not tampered with before reaching its intended recipient.
Difference between TLS and SSL
TLS (Transport Layer Security) is a successor to SSL.
It’s more improved and secure version of SSL. Although the SSL protocol was deprecated with the release of TLS 1.0 in 1999, it is still common to refer to these related technologies as “SSL” or “SSL/TLS.” Hence, we use the term SSL Pinning. The most current version is TLS 1.3, defined in RFC 8446 (August 2018).
What is SSL Pinning and how to achieve it?
SSL pinning is a process of associating a host with their expected X509 certificate or public key. Once a certificate or public key is known or seen for a host, the certificate or public key is associated or ‘pinned’ to the host. If more than one certificate or public key is acceptable then advertised identity must match one of the elements in the certificate chainset. This allows the application to trust only the valid or pre-defined certificates or public Keys. We should use SSL pinning technique as an additional security layer for application traffic and to validate the remote host’s identity. If we do not implement SSL Pinning, application trusts custom certificate and allows proxy tools to intercept the traffic.
This can be achieved in 3 ways — Certificate Pinning, Public Key Pinning & Hash Pinning.
Certificate Pinning is easiest to achieve. We can store the certificate in our application and when the certificate expires, we would update our application with the new certificate. At runtime, we retrieve the server’s certificate in the callback. Within the callback, we compare the retrieved certificate with the certificate embedded within our app. If it matches, we can trust the host else we will throw a SSL certificate error.
However, there is a downside to pinning a certificate. Each time our server rotates it certificate, we need to update our application. So if the server rotates its certificate on a frequent basis, then our application would need to be updated frequently as well.
Public Key Pinning
Public key pinning is more flexible to achieve but a little trickier as it requires some extra steps which are necessary to extract the public key from a certificate. In this approach, we generate a keypair, put the private key in our server and the public key in our app. And just like in certificate pinning, we check the extracted public key with its embedded copy of the public key. If it matches, we can trust the host else we will throw a SSL certificate error. By using public key pinning, we can avoid frequent application updates as the public key can remain same for longer periods.
However, there are two downsides to public key pinning. First, it’s harder to work with keys since it involves the process of extracting the key from the certificate. Second, the key is static and may violate key rotation policies.
In Hash Pinning, we pin the hash of the Public Key of our server’s certificate and match it with the hash of the certificate’s public key received during a network request. This technique is more complex than others, but it’s worth the effort. After having the certificate, we can hash it with whatever hashing algorithm we prefer (only make sure that it is a secure algorithm). This gives anonymity to a certificate or public key. I personally prefer SHA-256 to hash my key. After calculating the hash, I simply encoded it with Base64 encoding, to make it easier to store, and read.
Ways to implement SSL Pinning in Android
TrustManager is responsible for deciding whether the app should accept credentials submitted by the host or not. This interface is sourced from javax.net.ssl package
Steps to be followed are -
- Add the certificate file to the app.
We can add it inside res directory or under assets directory
2. Load KeyStore with the Certificate file as InputStream and create a Keystore. Since I stored it inside res directory, I’ll load it from there.
val inputStream = resources.openRawResource(R.raw.my_cert)
val keyStoreType = KeyStore.getDefaultType()
val keyStore = KeyStore.getInstance(keyStoreType)
3. Create a TrustManager.
val tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm()
val trustManagerFactory = TrustManagerFactory.getInstance(tmfAlgorithm)
4. Create SSLContext that uses our TrustManager and tell the URLConnection to use a SocketFactory from our SSLContext
val sslContext = SSLContext.getInstance("TLS")
sslContext.init(null, trustManagerFactory.trustManagers, null)
val url = URL("http://www.yourdomain.com/")
val urlConnection = url.openConnection() as HttpsURLConnection
urlConnection.sslSocketFactory = sslContext.socketFactory
This technique requires interacting directly to the framework API. This means that the implementation is conducted on a fairly low level. Hence, we should be very careful.
(b) OkHttp and CertificatePinner
Certificate Pinning using OkHttp is easy, as it only requires creating an instance of CertificatePinner using a dedicated builder with its corresponding fingerprints. The fingerprints need to be hard-coded into the app or we can inject such keys during the build process, using the buildConfigField method. Then, we need to build an OkHttpClient instance with the CertificatePinner.
Steps to be followed are -
val certificatePinner = CertificatePinner.Builder()
val okHttpClient = OkHttpClient.Builder()
We can add multiple fingerprints for different domains. Fingerprints can be retrieved directly from the certificate.
Unfortunately early versions suffer from a Vulnerability in OkHttp’s CertificatePinner so we should use at least OkHttp 3.2.0 or OkHttp 2.7.5.
(c) 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().
val retrofit = Retrofit.Builder()
.client(okHttpClient) //created above
(d) Network Security Configuration
It has been available since Android 7.0 (API level 24 or higher) and is the preferred way of implementing pinning. This lets us customize our network security settings in a safe, declarative configuration file without modifying app code. With NSC, we can declare communication methods, including Certificate Pinning, using XML files. To enable the configuration, we need to bind a configuration file with the AndroidManifest.xml file.
Steps to be followed are -
- Create a network security config file under
2. Set up the configuration file and add fingerprints.
<?xml version="1.0" encoding="utf-8"?>
3. Add the android:networkSecurityConfig attribute to the application tag.
<?xml version="1.0" encoding="utf-8"?>
It is possible to configure an expiration date by using <pin-set expiration=”YYYY–MM–DD"> but it is worth noting we 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 we need to support self-signed certificates or certificate authorities that are not trusted system root certificates.
Which certificate should we pin against in the chain?
Our choice of certificate impacts the level of security we achieve, decreasing as you reach the root certificate. So it is important for us to choose carefully. A Certificate Chain is an ordered list of certificates, containing an SSL Certificate and Certificate Authority (CA) Certificates, that enable the receiver to verify that the sender and all CA’s are trustworthy. The chain or path begins with the SSL certificate, and each certificate in the chain is signed by the entity identified by the next certificate in the chain.
Any certificate that sits between the Leaf/SSL Certificate and the Root Certificate is called a chain or Intermediate Certificate. The Root CA Certificate is the signer/issuer of the Intermediate Certificate. The Intermediate Certificate is the signer/issuer of the SSL Certificate.
If the Intermediate Certificate is not installed on the server (where the SSL certificate is installed) it may prevent some browsers, mobile devices, applications, etc. from trusting the SSL certificate. In order to make the SSL certificate compatible with all clients, it is necessary that the Intermediate Certificate be installed. The chain terminates with a Root CA Certificate. The Root CA Certificate is always signed by the CA itself. The signatures of all certificates in the chain must be verified up to the Root CA Certificate.
All together, they form the SSL chain of trust — an ordered list of certificates that allow the receiver (our app) to verify that the host (our secure server) and the CA are reliable. If one component in the chain is missing, the receiver won’t trust the server’s SSL certificate and will issue an HTTPS warning.
SSL/Leaf certificate — By pinning against our leaf certificate we are guaranteeing with close to 100% certainty that this is our certificate and thus the chain is valid. Leaf certificates tend to have a short expiry time and if, for instance, the SSL certificates are re-issued because the private key is compromised our app will be bricked until we can push an update out. Of course the same may also be true if you frequently cycle your certificates.
Intermediate certificate — By pinning against the intermediate certificate we are trusting that intermediate certificate authority to not mis-issue a certificate for our server(s). This also has the advantage that as long as we stick to the same certificate provider then any changes to our leaf certificates will work without having to update our app.
Root certificate — By pinning against the root certificate we 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 we are trusting, however that’s not always the case.
Without certificate pinning, an application commonly accepts any certificate which matches the requested hostname and is issued by a locally trusted CA (certificate authority). As there are usually more than 100 CA’s in the local trust store there is a good possibility that one of these get successfully attacked. Thus it makes sense to limit the certificate you accept to a specific one. Hence, SSL pinning helps build secure mobile applications but it will not secure connections if the pinned host is compromised. It mainly protects the client however it also helps protect the servers by making it harder for hackers to snoop on the traffic and figure out your API and exploit other security holes.
However, SSL Pinning can be bypassed using several ways, if it is not properly implemented or configured. Some of the techniques are :-
1. Using automated tools
2. By Reverse engineering ( Modifying Smali code)
Some concerns with respect to SSL pinning are —
- Cost of Certificate — It is possible to get a free SSL certificate, but this isn’t recommended for a lot of reasons. So, when you consider the benefits like SEO ranking, security, and customer trust it delivers, this cost should not be a cause for concern.
- Mixed Modes — If the SSL implementation isn’t setup correctly and there are still some files being served via HTTP rather than HTTPS, this can lead to confusing and unpleasant behaviour.
- Also, in the case of multi-domain SSL certificates, there is a high chance of error which will potentially cause unexpected behaviour.
- Implementing the strategy requires an understanding of the uses and applications of Public Key Infrastructure (PKI). Having put a framework in place, it then becomes a priority for certificate pinning keys to be safely stored and guarded, with backup keys held in a separate storage location, in case the primary server goes down.
Prevention of SSL Pinning Bypass -
SSL Pinning Bypass can be prevented using two-way SSL authentication. Using this technique, application acts as SSL client and send its certificate to the SSL server to validate after SSL server validates itself to the SSL client.
Mostly the implementation of two-way SSL is complex, so if we can prevent the modification or reverse engineering of android application that would basically avoid the SSL Pinning bypass using reverse engineering or Hooking method or some other automated tools.
Sharing some useful links below —