Using SSL Pinning for Mobile App Security

May 4, 2022 | Mobix news

The article will help developers and mobile application security specialists to understand how the process of attaching (pinning) SSL certificates works. We will focus on a helicopter view of network activity and for those who want to dive deeper we will provide reference links. Let’s start with some basic information about data transfer protocols. 

HTTP

HTTP is a widely used data transfer protocol, originally intended for the transfer of hypertext documents (those that may have links that allow you to navigate through other documents). 

HTTP stands for HyperText Transfer Protocol. According to the OSI specification, HTTP is the 7th layer application protocol. The current version of the protocol, HTTP 1.1, is described in the RFC 2616 specification. 

The HTTP protocol assumes the use of a client-server data transfer structure. The client application forms a request and sends it to the server, after which the server software processes it, generates a response and sends it back to the client. After that, the client application can continue to send other requests, which will be processed in a similar way. 

HTTP protocol allows us to exchange data between a user application that accesses web resources (usually a web browser) and a web server. The API of many software products also implies usage of HTTP for data transfer. The information itself can be in any format, such as XML or JSON. 

Usually, data transmission via the HTTP protocol is carried out over TCP / IP connections. Server software typically uses TCP port 80, but other ports may be used. If the port is not explicitly specified, the client software usually uses port 80 by default for opening HTTP connections. 

 

HTTPS 

As you know, the HTTP protocol openly transmits data, which means that anyone can highjack traffic and see all the messages exchanged between two parties. To prevent the possibility of reading and changing requests, SSL was added on top of HTTP and HTTPS protocol appeared, where the letter S stands for Secure. With an HTTPS connection, all data is encrypted and only those to whom it is intended (sending and receiving parties) can decrypt it. This is implemented using SSL / TLS security levels (Secure Sockets Layer / Transport Level Security).  SSL and TLS are protocols for secure data transfer. These protocols contain various cipher suites that can be used to perform encryption operations, as well as various communication rules and much more. The first protocol appeared around 1995. From version to version, it was slightly transformed: new cipher suites were added to it, obsolete ones were removed, vulnerabilities were fixed, and the protocol itself was slightly changed. 

 

Cipher suites  

What are cipher suites and why are they important to us? The cipher suite is a string that can look like this: 

 “TLS_DH_RSA_WITH_AES_256_CBC_SHA256” 

This string determines which algorithms and rules will be used to communicate between the client and the server. Let’s look at what follows and what handles what – we will need to understand this in the future. 

  • Protocol – The protocol used for connection, namely TLS or SSL. 
  • Key Exchange – The algorithm by which the key is exchanged. It can be RSA / DH (Diffie Hellman) / DHE (Diffie–Hellman ephemeral) / ECDH (Diffie Helman on elliptic curves) / ECDHE (Elliptic-curve Diffie–Hellman ephemeral) and others. Later, we will discuss them closely. 
  • Authentication – decides which algorithm will be used for authentication (RSA / DSA / ECDSA / …) 
  • Stream encryption – decides which protocols will be used to encrypt the stream. This specifies the key length and mode (RC4_128 / AES_256_CBC / 3DES_EDE_CBC / …) 
  • Message Authentication – decides which algorithm will be used to sign messages (MD5 / SHA / SHA256 / …) 

Some regulators and international standards recommend paying attention to protocol version and cipher suites. If you turn to PCI DSS, you can find a recommendation to disable SSL / early TLS and use “Strong Cryptography”. 

The variety of algorithms at each stage gives us a broad range of cipher suites that can be used to set up a secure connection. It also allows us to correctly distinguish between what can be used and what cannot. It is a huge task for engineers while configuring both the server and the client so that they can support cipher suites. Moreover, there are vulnerabilities on the side of an incorrectly configured server, the exploitation of which can lower the cipher suite level to insecure, so that later encrypted traffic can be read. Also, we should not forget about the weaknesses of SSL, which received quite a lot of publicity.  

Even some regulators and security standards recommend paying close attention to the version of the protocol and the cipher suites used. If you turn to PCI DSS, you can find a recommendation to disable SSL / early TLS and Strong Cryptography usage. 

tls handshake

Let’s dive right into the process of TLS handshake (setting up a secured connection): 

  1. To connect to the server, the client sends a hello-request to it. 
  2. Two keys are stored on the server: a private key (it cannot be transferred to anyone and its compromise leads to the most unfortunate consequences) and a public key. In response to a client request, the server sends its certificate, which contains the public key. 
  3. The client generates a premaster secret (a large random number), encrypts it with the server’s public key, and sends this encrypted secret over the network to the server. 
  4. The server, using its private key, decrypts the premaster secret, and both parties independently work out the master secret using cryptographic magic. 
  5. After both sides have the same master secret, session keys are generated. On their basis, data encryption is already taking place. From this point on, a secure connection is considered established, and you can start transferring data. 

This scheme is simple and clear, but there is one detail. With this approach, the pre-secret is encrypted with the server’s public key and transmitted over the network. If an attacker listens to the traffic in our network, he can record such traffic for years in the hope of compromising the server’s private key. If a new Heartbleed vulnerability is discovered or an intruder plugs a flash drive into the server and the attacker gets the private key, he will be able to decrypt all the traffic that he has been accumulating for years and get all the information he is interested in. 

TLS handshake (DHE/ECDHE) 

To avoid such situations, just use the property of ephemeral algorithms. Let’s see how it works and how the process of interaction between the client and the server changes when such algorithms are used: 

 Let’s look at what’s going on and find the differences: 

  1. There are no differences in the first part: the client sends a connection request to the server. 
  2. The server then returns its public key from the key pair along with the certificate to the client. 
  3. But from now on, the properties of ephemeral cipher suites are involved, as well as a bit of the cryptographic magic. Instead of generating a pre-secret and encrypting it with the public key, the client side generates its own Diffie Hellman key pair and sends the public (public) key to the server. 
  4. The server on its side also generates keys using an ephemeral algorithm and passes the generated public key to the client side. 
  5. Here the real cryptography magic happens, and the sides independently generate Preliminary and Master secrets. 
  6. After that, based on the Master secret, session keys are generated and the process of transferring encrypted data begins. 

Thus, Preliminary and Master secrets are generated independently on the client and server and are not transmitted over the network in any form. Considering a situation when an attacker records traffic, then even if all the keys are compromised, the only thing the attacker can do is decrypt the last communication session, since the keys live only within one session. With each new connection of the client to the server, the entire procedure described above is repeated anew (generation and exchange of keys, secrets, etc.). 

Let’s summarize: When using TLS_DHE/ECDHE_*, the session key is not transmitted over the network. The shared secret is generated from the DH with temporary keys valid only for the current session. 

By the way, in iOS, with App Transport Security enabled and configured, the application will use PFS (Perfect Forward Secrecy) by default. For detailed customization, it is possible to control this function using the NSExceptionRequiresForwardSecrecy key in the plist file. Disabling this mode will allow the use of TLS versions that do not support PFS. 

“Man In the Middle” 

Now that we know what HTTPS is and how establishing a secure connection works, we can talk about such a famous attack as “Man in the Middle”. This is an attack on the communication channel in which the attacker is on the same network as you and has control over the access point or can somehow redirect you to his proxy server within the network. The attacker appears to the client as the destination server, and to the server as the client. It stands “in the middle” between them and can read and modify the traffic passing through it. But how can this be implemented? After all, the traffic is encrypted, and it’s impossible to read it just like that.

The process of connecting a client (mobile application) to its server through an attacker’s-controlled access point will look like this: 

  1. The application accesses the server through an access point controlled by the attacker. 
  2. A proxy server is deployed on this access point, through which the traffic passes. 
  3. When requesting a connection to its server, the Proxy “represents itself” as the final destination, and in response to the client’s SSL request returns its own certificate with its public key. 
  4. The client generates and encrypts a pre-shared secret or generates a key pair and exchanges them with the attacker’s server, not with its own. 
  5. Thus, a secure connection is established with the “malicious” server, which watches all the traffic. 
  6. On the other hand, the Proxy “represents itself” as a client for the target server and exchanges data with it, in fact, becoming an intermediary between the client and the server. 

Of course, the scheme is not easy to implement. The key condition is the certificate of the root certification authority of the Proxy server must be trusted on the user’s device. It does not matter what kind of device it is: a computer, a tablet or a smartphone.  

Communication channel protection, or SSL Pinning 

To protect a mobile application from such attacks, a mechanism called SSL Pinning is used. There is always a lot of controversy about it (whether to do it, whether it is needed, how to avoid certificate “fading” in the application, etc.). First, it’s worth understanding why your application needs SSL Pinning. Generally, there are three reasons: 

  1. To protect the client, if they got into an untrusted / compromised network, if someone tries to listen to its traffic, etc. 
  2. To make it impossible for attackers to analyze the backend and look for errors, for example, in business logic (classic Security through obscurity). 
  3. To be sure that the request comes from a legitimate application or, in other words, to protect against bots. 

SSL Pinning is designed to solve one single task: to protect the client. We will briefly comment on the remaining two points. Yes, SSL Pinning can affect them indirectly. That is, it will help both close your API and protect against bots (in theory), but this is not a silver bullet, only a way to protect your customers. 

To avoid copying your applications and protect against bots, it is best to use alternative implementations rather than pinning. For example, it is possible to apply a message signature, that is, to generate some unique string that would depend on the composition of the transmitted information in the request and could be calculated independently on the client (for signing) and on the server (for verification). Such a message signature is some analogue of a digital signature. Something similar was implemented by Snapchat and for a very long time no one could understand how this token was generated for signing. Even two articles have been published on this topic (part one and part two), where you can try to peep some approaches. Or you can use ready-made services that, such as Cloudfare, or similar ones. 

And now let’s talk about what SSL Pinning is, how it works and how to use it. The essence of this method is that at the SSL Handshake stage after the second step, when the server sends us its certificate with a public key, the application checks that certain parameter of this certificate match what the application expects to receive (that is, some data that “hardwired” in the application and which we expect to receive from our server).  

Now let’s see what exactly can be checked at what stages, and how this can be implemented. 

  

Types of SSL Pinning 

Certificate Pinning 

The first implementation is Certificate Pinning. The certificate itself is checked directly, including metadata (to whom it was issued, expiration date, owner data, etc.). This implementation is the most secure, because even a small change to the certificate will cause a mismatch and result in a connection won’t be established. 

But the certificate has an expiration date, so every time a new certificate is issued, a new version of the application must be released. 

Public key Pinning 

This is a simplified implementation. The process only validates the public key instead of the entire certificate. Since it is possible to renew the certificate without changing the public key, this method avoids releasing an application update every time the certificate is changed. 

But it should be considered that the company must have a policy for such keys rotation, to update keys accordingly. 

Which certificates can be checked: 

The certificate of the destination server: 

  • Guarantees with almost 100% certainty that this is your certificate, even if the root authority has been compromised. 
  • If the certificate becomes invalid for any reason (either due to expiration or compromise), then it will not be possible to connect to the server until an application update is released. 
  • Allows the use of self-signed certificates, which can be useful during development. 

Intermediate Certification Authority Certificate: 

  • By verifying an intermediate certificate, you trust the intermediate CA; 
  • As long as you use the same certificate provider, any changes to the destination server certificates will work without updating the application. 

Certification Authority Certificate (Root Certificate, CA): 

  • Validating a root certificate means that you trust the root CA as well as any resellers that use that CA. 
  • If the root certificate is compromised, then the connection cannot be considered secure, and all certificates must be changed urgently. 

Certificate chain: 

  • The most reliable check in terms of security since all possible changes in any of the certificates are checked. 
  • This is the most difficult check in support, because if any of the certificates in the chain changes, the application needs to be updated. 
  • In most cases, the first option is used and only the certificate of the destination server is checked, although there are implementations that are more complicated. 

Another interesting technique to protect clients that we have seen in application analysis is shifting the responsibility to the server side. While the application is running, a request is sent with a part of the certificate (or with the full certificate) that the application received during the SSL Handshake stage, and the server itself decides whether this is its own certificate. Based on the response from the server, the mobile application shows the user a message that he is on an insecure network. Of course, such a solution should not be the only one, but as an additional check it may come in handy. 

It is worth mentioning an interesting observation from practice, often the certificate with which the server fingerprint will be compared is stored as a file on the file system (in the directory or application resources). We automated the search for such keys/certificates, and after collecting a good result by running several hundred applications through the scanner, we wanted to see what else the applications store, what certificates or key files? 

As a result, we additionally added search for various file keystores, public and private certificate to them. And if we suddenly find something of the above in the application, and additionally protected by a password, then we try to pick it up and see what’s inside. This approach allows you to control the appearance of unwanted certificates in the application, for example, if a private key gets into the assembly by mistake.

How to check SSL pinning 

Each of the possible libraries will have its own implementation. For Android, for example, there is a built-in mechanism for implementing Pinning at the system level, namely the network communication configuration (an XML file in which network security settings are configured). This setting is set by the special attribute android:networkSecurityConfig in AndroidManifest.xml. 

Connection example: 

<?xml version="1.0" encoding="utf-8"?>
<manifest ... >
<application android:networkSecurityConfig="@xml/network_security_config"
... >
...
</application>
</manifest>

Network Security Config allows you to simply connect the Certificate Pinning mechanism to the application. But it is worth considering certain nuances. Let’s look at what at first glance looks like a well-configured configuration and see how it can be improved a bit: 

<network-security-config>
<domain-config>
<domain includeSubdomains="true">example.com</domain>
<pin-set>
<pin digest="SHA-256">7HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=</pin>
</pin-set>
</domain-config>
</network-security-config>

 

This example has two small drawbacks: 

  1. Certificate thumbprint (pin-set) has no expiration date. 
  2. There is no backup certificate. 

If your certificate expires and it is not specified in the settings, the application will stop connecting to the server and will generate an error. If the deadline is set and approaching, the application will switch to using the trusted CAs installed on the system. And instead of getting a broken application, you will get no SSL Pinning for some time until you renew the certificate in the application. 

To avoid such a pause, it is worth adding the following certificate in the “backup certificates” settings. 

Here is an example of correct use of the Certificate Pinning functionality: 

<network-security-config>
<domain-config>
<domain includeSubdomains="true">example.com</domain>
<pin-set expiration="2021-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>

Despite the convenience of using Network Security Config, some checks must be done manually in the code. For example, you will still need to decide if your application performs hostname validation, as Network Security Config will not protect against this type of problem. 

Also, before implementation, you need to make sure that third-party libraries support Network Security Config. Otherwise, these protections may cause problems in your application. Also, Network Security Config is not supported by lower-level network connections such as web sockets. 

As for iOS, the built-in check mechanism in AppTransport Security appeared only starting with iOS 14 and is very similar to what is in Android: 

<key>NSAppTransportSecurity</key>
<dict>
<key>NSPinnedDomains</key>
<dict>
<key>example.org</key>
<dict>
<key>NSIncludesSubdomains</key>
<true/>
<key>NSPinnedCAIdentities</key>
<array>
<dict>
<key>SPKI-SHA256-BASE64</key>
<string>r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E=</string>
</dict>
</array>
</dict>
</dict>
</dict>

In this configuration example, we specify that the public key fingerprint is associated with the example.org domain and its subdomains, such as test.example.org. But subdomains of the third level and above do not fall into this check (for example, notinclude.test.example.org). 

As in Android, you can specify multiple fingerprints at once, which can be useful when rotating certificates on the server: 

<key>NSAppTransportSecurity</key>
<dict>
<key>NSPinnedDomains</key>
<dict>
<key>example.net</key>
<dict>
<key>NSPinnedLeafIdentities</key>
<array>
<dict>
<key>SPKI-SHA256-BASE64</key>
<string>i9HaIScvf6T/skE3/A7QOq5n5cTYs8UHNOEFCnkguSI=</string>
</dict>
<dict>
<key>SPKI-SHA256-BASE64</key>
<string>i9HaIScvf6T/skE3/A7QOq5n5cTYs8UHNOEFCnkguSI=</string>
</dict>
</array>
</dict>
</dict>
</dict>


 

Next, let’s look at the most popular libraries for implementing networking and how SSL Pinning works internally. In general, these code examples can provide a good starting point for further development. Additionally, at the end of the article there will be a list of links from which these recommendations are collected, as well as code snippets. They can be referred to as a starting point. 

OkHttp 

When implemented in OkHttp, you can use the CertificatePinner class. 

CertificatePinner certPinner = new CertificatePinner.Builder()
.add("appmattus.com",
"sha256/4hw5tz+scE+TW+mlai5YipDfFWn1dqvfLG+nU7tq1V8=")
.build();

OkHttpClient okHttpClient = new OkHttpClient.Builder()
.certificatePinner(certPinner)
.build();

It is possible to use this functionality in OkHttp starting from version 2.1. 

But unfortunately, earlier versions are subject to a vulnerability that is fixed only in version 2.7.5 and higher than 3.2.0. You must ensure that the version of the library you are using is not affected by this vulnerability. 

Retrofit 

Retrofit is applied on top of OkHttp, so using it is similar to using OkHttpClient as shown in the example above. 

Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://appmattus.com")
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.build();

HttpUrlConnection 

If HttpUrlConnection is used, it is recommended to reconsider the approach towards OkHttp. The version of HttpUrlConnection built into Android is fixed, so updates can be difficult. 

In the Android document “Security with HTTPS and SSL“, the proposed implementation is based on pinning certificates with its own TrustManager and SSLSocketFactory. However, as with other APIs, this recommendation will include examples using SPKI. 

private void validatePinning(
X509TrustManagerExtensions trustManagerExt,
HttpsURLConnection conn, Set<String> validPins)
throws SSLException {
String certChainMsg = "";
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
List<X509Certificate> trustedChain =
trustedChain(trustManagerExt, conn);
for (X509Certificate cert : trustedChain) {
byte[] publicKey = cert.getPublicKey().getEncoded();
md.update(publicKey, 0, publicKey.length);
String pin = Base64.encodeToString(md.digest(),
Base64.NO_WRAP);
certChainMsg += " sha256/" + pin + " : " +
cert.getSubjectDN().toString() + "\n";
if (validPins.contains(pin)) {
return;
}
}
} catch (NoSuchAlgorithmException e) {
throw new SSLException(e);
}
throw new SSLPeerUnverifiedException("Certificate pinning " +
"failure\n Peer certificate chain:\n" + certChainMsg);
}
private List<X509Certificate> trustedChain(
X509TrustManagerExtensions trustManagerExt,
HttpsURLConnection conn) throws SSLException {
Certificate[] serverCerts = conn.getServerCertificates();
X509Certificate[] untrustedCerts = Arrays.copyOf(serverCerts,
serverCerts.length, X509Certificate[].class);
String host = conn.getURL().getHost();
try {
return trustManagerExt.checkServerTrusted(untrustedCerts,
"RSA", host);
} catch (CertificateException e) {
throw new SSLException(e);
}
}

This implementation should look like this: 

TrustManagerFactory trustManagerFactory =
TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore) null);
// Find first X509TrustManager in the TrustManagerFactory
X509TrustManager x509TrustManager = null;
for (TrustManager trustManager : trustManagerFactory.getTrustManagers()) {
if (trustManager instanceof X509TrustManager) {
x509TrustManager = (X509TrustManager) trustManager;
break;
}
}
X509TrustManagerExtensions trustManagerExt =
new X509TrustManagerExtensions(x509TrustManager);
...
URL url = new URL("https://www.appmattus.com/");
HttpsURLConnection urlConnection = 
(HttpsURLConnection) url.openConnection();
urlConnection.connect();
Set<String> validPins = Collections.singleton
("4hw5tz+scE+TW+mlai5YipDfFWn1dqvfLG+nU7tq1V8=");
validatePinning(trustManagerExt, urlConnection, validPins);

Volley 

The standard way to use the Volley library is to pinning certificates, as described in the “Android Security Tip: Public Key Pinning with Volley Library” article. The Github Public Key Pinning with Android Volley library project shows how you can set up an SSLSocketFactory to bind to an SPKI. 

There is also an alternative method, in addition to the approaches listed above. It consists of using the HostnameVerifier class to verify that the hostname in the URL matches the one in the certificate. 

You can override HostnameVerifier like this: 

RequestQueue requestQueue = Volley.newRequestQueue(appContext,
new HurlStack() {
@Override
protected HttpURLConnection createConnection(URL url) throws IOException {
HttpURLConnection connection = super.createConnection(url);

if (connection instanceof HttpsURLConnection) {
HostnameVerifier delegate =
urlConnection.getHostnameVerifier();
HostnameVerifier pinningVerifier =
new PinningHostnameVerifier(delegate);

urlConnection.setHostnameVerifier(pinningVerifier);
}

return connection;
}
});
...
public static class PinningHostnameVerifier
implements HostnameVerifier {
private final HostnameVerifier delegate;

private PinningHostnameVerifier(HostnameVerifier delegate) {
this.delegate = delegate;
}

@Override
public boolean verify(String host, SSLSession sslSession) {
if (delegate.verify(host, sslSession)) {
try {
validatePinning(sslSession.getPeerCertificates(),
host, validPins);
return true;
} catch (SSLException e) {
throw new RuntimeException(e);
}
}

return false;
}
}

Do not forget that SSL Pinning is not the only thing that can help secure a network connection. Another crucial factor is the creation of the correct protocol for communication between the application and the server, for example, a ban on open communication via HTTP or a ban on the transfer of sensitive information in the parameters of GET requests.  

If sensitive information is transmitted in URLs, it may be logged in a variety of places, including: 

  • Web server 
  • Any forward or reverse proxy between two endpoints 
  • Header Referrer 
  • Web server logs 
  • Browser history 
  • Browser cache 

Vulnerabilities that expose sensitive user information can lead to compromises that are extremely difficult to investigate. By the way, in our practice there was a case when money was stolen from one of the client’s accounts. As a result of the investigation, it turned out that the user data (password and two-factor authentication code) were obtained precisely from the Web server logs, which were accessible to a sufficient number of company employees. 

The recommendation here is quite simple. All requests containing sensitive information must use the POST method and contain sensitive information in the request body. This ensures that it does not end up in web server logs and other places that are accessible to a large number of people. If it is not possible to transfer the method from GET to POST requests, you can additionally apply encryption or hashing of confidential information. 

 

Detection of vulnerabilities in the absence or incorrect implementation of SSL Pinning 

Unfortunately, many applications that we check daily do not implement SSL Pinning. And this is sad, considering that its implementation does not require as much effort as it might seem. And its implementation will help ensure the protection of your customers. 

Automatic detection of vulnerabilities related to the protection of the communication channel is designed to make life easier for security analysts, testers and developers, because there is no longer a need to manually configure proxies, add certificates, launch an application, and perform many manual and monotonous operations. In our product, we have provided full automation for checking the correctness of the implementation of Pinning and related things to properly configure network interaction. 

Conclusion 

 Should the application implement network connection security? The answer is not obvious to all developers. Some people are stopped by the fear of “fading” certificates, others – by the lack of time. In our opinion, this is one of the first things you need to plan for implementation to protect your customers. In the end, there are special products and solutions that help with settings, searching for certificates and the correct implementation of network interaction.