Imagine this: You're sending a message, making a payment, or simply browsing your favorite app, and without you noticing, an unseen intruder intercepts your data in transit. It is digital eavesdropping, and the consequences can be devastating.
From banking and shopping to communication and entertainment, we entrust our smartphones with a treasure trove of sensitive information. As a result, the need to enhance security on mobile apps has never been more critical.
A lot of your data requires protection, ranging from personal identification details and financial information to health records and confidential communications. Malicious actors are constantly on the lookout to exploit vulnerabilities in mobile apps, making it imperative to safeguard these data streams.
Mobile apps are vulnerable to a broad range of security threat
s and attacks. The "Man-in-the-Middle" (MITM) attack is arguably one of the most prominent attacks that threaten mobile apps. In a MITM attack, cybercriminals position themselves between the user's device and the server to intercept and manipulate data exchanged between them. This means that sensitive information, such as login credentials, credit card numbers, and private messages, can be covertly intercepted and exposed.
To protect apps from MITM attacks, developers and security-conscious organizations have turned to a defense mechanism known as SSL Pinning. SSL Pinning involves binding a mobile app to information from the server, ensuring that the app only communicates with trusted servers. By doing so, SSL Pinning effectively counters MITM attackers, preventing them from intercepting or altering the encrypted data flowing between the mobile app and its server.
In this article, we will focus on SSL Pinning, explaining how it can fortify your mobile app against MITM attacks and enhance the protection of sensitive data. Let’s discover how to secure your react native app with this essential security measure!
Here is a reminder of the notions of certificate and Certificate Authority (CA).
First, let’s take a look at how your app communicates with a remote server. The TLS protocol (previously known as SSL) is the mainstream cryptographic protocol designed to provide secure communications on the Internet. It is used whenever you try to reach an HTTPS website. Even though TLS has become the mainstream protocol for communication, the term SSL is still used to talk about certificates or pinning (we should talk about “TLS pinning” or “TLS certificates” to be accurate). SSL/TLS communications involve different phases during the “handshake”, which occurs before any exchange of information. The handshake steps are the following:
If one of the steps fails, the connection is refused.
Other optional steps might occur depending on the encryption algorithm chosen, but this goes beyond the scope of this article. To obtain further details, I suggest you read this article.
Steps 2 and 3 of the handshake phase are especially relevant to our topic. A certificate works like your ID card, it is delivered by the server to ensure its trustworthiness. Certificates are delivered by certificate authorities (CA). CAs play a crucial role in establishing secure communication by confirming that a website or entity is legitimate and can be trusted in online transactions. The client does not immediately trust the server, but it trusts CAs ! And here comes the threat of MITM attacks…
During a MITM attack, a malicious entity positions itself between the client and server and intercepts communications between both of them. However it must pass the SSL/TLS protocol to obtain confidential data from the client. To obtain a ‘fraudulent’ certificate, there are mainly two options for the attacker:
Then the attacker presents to the client the fraudulent certificate it obtained during step 2 and tricks the client into considering the attacker trustworthy. The connection is established, and the attacker will receive everything you think you are sending to the server.
SSL Pinning is a security technique that grants the client (your app) protection against MITM attacks. The idea is to pin (hardcode inside a configuration during development time) expected information from the server, such as the public key or the certificate. If a certificate is pinned, the client will compare the certificate sent by the server with the pinned certificate during the authentication step of the SSL handshake. If both certificates are identical, the other steps of the SSL/TLS protocol continue. Otherwise, the connection is refused.
There are two pinning options: you can either pin the certificate or the public key. Both have their own advantages. A certificate contains many fields that are being checked, and the public key is one of them.
Pinning a certificate is easier to implement, however, if the key changes, so does the certificate. Any key change or other changes in the certificate will require updating the app.
Pinning a public key is harder because it requires extracting this data from the certificate, which can be a bit tricky. However, public keys are not as likely to change as certificates.
Therefore, if the certificate changes frequently, your app might require more updates than with public key pinning.
Your chosen pinning option highly depends on how the server handles certificates and public keys. Consider using certificate pinning if you either require an easier implementation or if the server’s public keys do not respect key rotation. Key rotation is a security practice where cryptographic keys, such as those used for encryption or authentication, are regularly changed to enhance security. It helps reduce the impact of a compromised key. If there is no key rotation policy in place, public keys remain static, and certificate pinning becomes a more reliable option.
You might want to pin more than one certificate in the chain of trust. The chain of trust is a hierarchical relationship among Certificate Authorities that extends from the top-level, or root, CA down to the end-entity certificates. When you connect to a server, your app follows this chain to verify the trustworthiness of the presented certificate. Each CA in the chain signs the certificate of the one below it. In the authentication step of the handshake, if the certificate doesn’t come from a trusted CA, the client will check the certificate of this CA. The certificate of the CA is delivered by another CA, which produces a chain of trust until the client reaches the ‘root certificate’, delivered by an authority trusted by the client.
That is why you might need to pin the intermediate certificates coming from CAs not trusted by your app.
The solution suggested in this article implements certificate pinning using SHA fingerprints. Because a certificate contains many fields, it’s easier to store the value of its SHA fingerprint. A SHA fingerprint is a unique, fixed-length string of characters generated by applying a cryptographic hash function. The incoming certificate is converted to its SHA fingerprint and compared with the stored certificate.
For iOS, the library Trustkit manages SSL Pinning.
For Android, you can modify the OkHttp configuration. The OkHttpClient is a widely used open-source HTTP client for making network requests in Android applications. This client simplifies the process of sending and receiving HTTP requests and responses in Android apps. For instance, requests made with the fetch function of React Native are handled by OkHttp.
You can implement the OkHttpClientFactory interface and configure an OkHttpClient instance using OkHttpClient.Builder, which allows you to set various parameters and behaviors for HTTP requests and to configure SSL Pinning.
Create a java file called SSLPinningFactory. Pin your certificate with CertificatePinner from the okhttp3 library, and create a new instance of OkHttpClient that contains your certificatePinner object.
Next, override the default OkHttp client used by react-native in your MainActivity file
In your AppDelegate.mm, add the following imports:
Just before ++code> RCTBridge *bridge = [self.reactDelegate createBridgeWithDelegate:self launchOptions:launchOptions] ++/code> add:
Replace your.packageName with the right value. Use rootCertificateSHA, intermediateCertificateSHA, rootCertificateSHA depending on your strategy.
You can explore other libraries like OpenSSL.
You can use BAM’s security package by adding @bam.tech/react-native-app-security to your development dependencies. Here is how to use it:
In your app.config.ts, add the following attributes.
A prebuild is required before building your app. You can check the documentation for the plugin if needed.
For Expo projects, we cannot directly write our configuration inside android and ios files anymore. The idea is to use an expo-module instead to modify the OkHttp client by setting up SSL Pinning in the onCreate function of MainActivity for android. The onCreate function is called when an activity or a service is created and is then useful to initialize processes such as SSL Pinning. The expo-module also installs TrustKit for iOS. This part of the process is quite similar to what we would do for a project that does not use expo. The plugin writes the host name and the certificates that are being fetched by the expo-module in your graddle.properties file. This implementation helps projects that have various environments (with different hostnames or certificates) to pin certificates according to their logic.
Proxyman allows you to set up a proxy that will act as an intermediary between you and the server.
After downloading and launching the app, you might see this warning that indicates that you should trust Proxyman root certificates. Otherwise, the chain of trust will break, and you will be unable to determine whether your implementation is correct or not.
Click on ‘Install new Certificate’ on the domain you would like to track.
Click on ‘Install & Trust’ (this requires root privilege).
Then, you need to install Proxyman’s certificate on your emulator or device. Example for iOS emulator: Click on Certificate → Install Certificate on iOS → Simulators… Then click on “Install and Trust the certificate to running Simulators” and reboot your simulator.
Once it is done, you will be able to observe requests being intercepted by Proxyman.
When you try to contact the server of your app, you should receive failed requests from the pinned server. If your app fails to contact your server when Proxyman is enabled and succeeds when it’s not, then congratulations! Your implementation is working!
On iOS, you should see an error generated by TrustKit in your console the first time you try to reach the server: TrustKit: Background upload - task completed successfully: pinning failure report sent.
The next time you try to reach the server, you will see the following lines in your console:
Your simulator will also display an empty warning and will fail to contact the server.
On Android, you will receive an empty warning in your console, and it will be displayed by your emulator or device. This warning prevents you from sending data to the server.
You can also check that SSL Pinning works by changing the SHA fingerprint of the certificates you store. In doing so, you will need to prebuild and build your app since you are changing the configuration of native files. You will receive an error while launching the app, displayed in your console by TrustKit for iOS and on your simulator for Android.
Certificate transparency enhances the detection of fraudulent certificates by certifying the certificate authority itself. Every CA is monitored by external actors, which strengthens the chain of trust.
When Certificate Transparency is enabled, all CAs in the chain of trust of SSL Pinning are guaranteed to be safe. Therefore, it is a great complementary security feature to have for your project. Certificate transparency is relatively easy to implement once SSL Pinning is already implemented. You can implement certificate transparency with the object CTInterceptorBuilder that you add to your network interceptor.
Certificate transparency doesn’t suffer from the issue of changing certificates. If a pinned certificate is modified, SSL Pinning will block your app. This forces you to update your app whenever a pinned certificate changes. In addition, SSL Pinning can be bypassed with reverse engineering. Determined attackers, especially those with expertise in reverse engineering, can analyze your app's binary code to find the embedded key. While it is uneasy, it's not an insurmountable obstacle, and attackers with sufficient time, skill, and motivation can achieve this.
However, Certificate Transparency does not protect your app from malicious certificates that were publicly logged. Depending on your requirements, you might want to implement either SSL Pinning, Certificate Transparency or both of them.
For further details on this topic, I suggest you read this article, which explains how certificate transparency improves the detection of fraudulent certificates.