React Native SSL Pinning is back! — Android version

Javier Muñoz
Mar 21, 2018 · 5 min read

Disclaimer: First story here, not much of a writter and not native speaker… That said, this is it:

Update: I separeted Andriod and iOS implementations for each one has different approaches and found two of them for Android which is what you are reading here.

After a few hours searching the web for a solution for this rather common topic for those of us who are starting with React Native, I came across with a few possible ways of doing this, here I will mention two of them.

OkHttpClient using CertificatePinner

Thanks to this very comprehensive article and the proposal mentioned there, I thought it is a very straightforward way to nail SSL Pinning. Apparently it was pretty easy, but wait! React Native, somewhere in the whole commit storm it constantly has, removed the ability to replace Android’s OkHttpClient, at least that was what I read and didn’t have the time to verify. What a mess! well, not so much. Version 0.54 added it back! Thank God I just updated the RN version in my project. But the code is still not working!

The thing is, customizing Android’s network client is possible again, but not in the old fashion. Checking out RN’s source code, I realized this time they are using the factory pattern and it’s great! And as simple as that, we just have to pass our custom Factory class and that’s it. Let’s take a look at that code:

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);OkHttpClientProvider.setOkHttpClientFactory(new CustomClientFactory());}

Easy right? Just add that piece of code to your MainActivity’s onCreate method and that’s it! The only thing left is to write our CustomClientFactory and we’re ready to go:

import com.facebook.react.modules.network.OkHttpClientFactory;import com.facebook.react.modules.network.OkHttpClientProvider;import com.facebook.react.modules.network.ReactCookieJarContainer;import java.util.concurrent.TimeUnit;import okhttp3.CertificatePinner;import okhttp3.OkHttpClient;public class CustomClientFactory implements OkHttpClientFactory {private static String hostname = "*.your.service.com";@Overridepublic OkHttpClient createNewNetworkModuleClient() {CertificatePinner certificatePinner = new CertificatePinner.Builder().add(hostname, "sha256/YOUR_PUBLIC_KEY_HASH").add(hostname, "sha256/YOUR_PUBLIC_KEY_HASH_BACKUP1").add(hostname, "sha256/YOUR_PUBLIC_KEY_HASH_BACKUP2").build();OkHttpClient.Builder client = new OkHttpClient.Builder().connectTimeout(0, TimeUnit.MILLISECONDS).readTimeout(0, TimeUnit.MILLISECONDS).writeTimeout(0, TimeUnit.MILLISECONDS).cookieJar(new ReactCookieJarContainer()).certificatePinner(certificatePinner);return OkHttpClientProvider.enableTls12OnPreLollipop(client).build();}}

That’s it 🎉. Where did all that code come from? I used the same lines from OkHttpClientProvider so there are no differences with the default client provided by React Native. Of course, you can add/remove anything that meets your needs foryour custom client, and we’ll be sure it will work for all of your network requests made through React Native’s Networking module in Android. At least for RN 0.54.2, the version used when this was written.

TrustKit for Android

There is another approach in Android and we’ll use the previous one to make use of this great tool and React Native. Instead of adding a CertificatePinner to OkHttpClient, we are going to use TrustKit for Android which I found to be a very useful library as it:

There is gonna be a little bit more configuration to be done but it is worth it. As mentioned before, TrustKit for Android uses Network Security Configuration which is very useful if you are targeting Android Nougat or later, but what if your target is lower? TrustKit for Android to the rescue! It makes security configuration available for targets below API 24, using basically the same xml file and tags you’ll use with official support. Enough talking, let’s get to it.

First of all, we are going to add TrustKit’s dependency to our android project implementation ‘com.datatheorem.android.trustkit:trustkit:+’ . If you need a specific version of TrustKit you should specify it instead of + .

TrustKit for Android, as stated in it’s documentation, only supports the following XML tags, so take that in account when using it along the official API:

  • <domain-config>.
  • <domain> and the includeSubdomains attribute.
  • <pin-set> and the expiration attribute.
  • <pin> and the digest attribute.
  • <debug-overrides>.
  • <trust-anchors>, but only within a <debug-overrides> tag. Hence, custom trust anchors for specific domains cannot be set.
  • <certificates> and the overridePins and src attributes. Only raw certificate files are supported for the src attribute (user and system values will be ignored).

Our final configuration file should look like this:

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config>
<domain includeSubdomains="true">example.com</domain>
<pin-set>
<pin digest="SHA-256">AAAAAAAAAAAAAAAAAAAAAAAAAA=</pin>
<trustkit-config enforcePinning="true">
<report-uri>http://example.com/log_report</report-uri>
</trustkit-config>
</domain-config>
</network-security-config>

There are some more options you could use according to your needs, so please go through the documentation and the Github repository to have a clearer idea on how to configure TrustKit. For example, you’ll find that it is strongly recommended to add more than one PIN for every domain you configure, so you don’t have any problems whenever your domain’s certificate or any of the certificates in the chain expires.

One more thing I had to went through was how to obtain the Public key digest to add it to the configuration! This great web tool came in handy to obtain the hashes for all of the certificates in the chain. Just enter your site’s URL and it’ll have it done for you.

Now initialize TrustKit in the onCreate method of your main activity, or your main application. It’s up to you. Just make sure it’s not initialized more than once through the app’s life-cycle or you’ll get a killer exception. I recommend wrap it in a try-catch block:

@Override
protected void onCreate(Bundle savedInstanceState) {
try {
TrustKit.initializeWithNetworkSecurityConfiguration(this);
} catch (Exception e) {
e.printStackTrace();
}
OkHttpClientProvider.setOkHttpClientFactory(new CustomClientFactory());
super.onCreate(savedInstanceState);
}

Since this will work for Android N or later, we’ll set the corresponding configuration in AndroidManifest.xml inside our application tag:

<application
android:name="your-app"
android:networkSecurityConfig="@xml/network_security_config"
...

This indicates us that the configuration file should be placed inside the res/xml directory. You can always change the file’s location and, if you do so, specify where you’ve put it in the manifest and when initializing TrustKit as we just did.

Finally, time to make this work with React Native. Remember how we added the CertificatePinner in the previous option? It’s quite the same here. TrustKit states that in order to work in devices with Android before N (< API 24), we need to use it with TrustKit SSLSocketFactoryor X509TrustManager. So, based on previous option, just add this configuration to your custom client factory builder’s chain with the hostname (which obviously is within the domain you configured before) and you’re done:

@Override
public OkHttpClient createNewNetworkModuleClient() {
String hostname = null;
try {
hostname = new URL(YOURHOSTNAME).getHost();
} catch (MalformedURLException e) {
e.printStackTrace();
}

OkHttpClient.Builder client = new OkHttpClient.Builder()
.connectTimeout(0, TimeUnit.MILLISECONDS)
.readTimeout(0, TimeUnit.MILLISECONDS)
.writeTimeout(0, TimeUnit.MILLISECONDS)
.cookieJar(new ReactCookieJarContainer())
.sslSocketFactory(TrustKit.getInstance().getSSLSocketFactory(hostname),TrustKit.getInstance().getTrustManager(hostname));
return OkHttpClientProvider.enableTls12OnPreLollipop(client).build();
}

Almost forgot to mention this: TrutKit offers a reporting feature which I believe is very useful when it comes to add awareness to security break attempts to your app. Place your own service URL in report-uri and you’ll receive a report every time a pin validation occurs in the app. Also, TrusKit offers a dashboard for your app with all these reports which are automatically generated and sent to a default URL, just e-mail them and ask for the dashboard, or, disable the default reporting URL.


So this was Android implementation. I will write another story with how I approached this in iOS with all the tricky things I had to went through, and kinda suffer ’cause I’m not an iOS developer (Objective C or Swift).

Thanks for taking the time for reading this. If you have any idea or suggestion please leave a comment bellow, they will be very appreciated. Greetings and keep coding…

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade