Securing data transmission in mobile applications is critical. One way to secure this communication is through SSL Pinning. SSL Pinning ensures that your app only communicates with your server using trusted certificates, enhancing the security against man-in-the-middle (MITM) attacks.
This guide will walk you through setting up SSL pinning in both iOS and Android in your React Native app.
Gathering Information for SSL Pinning
Step 1: Test Your SSL Certificate
Start by going to the SSL Labs SSL Test, enter your domain or api base url(like your-domain.com
), and initiate a scan. The results will include details about your SSL certificate.
Step 2: Requirements for iOS SSL Pinning
Note: iOS requires at least two public key hashes (primary and backup) to enable SSL pinning.
Step 3: Copy the SHA256 Pin and Domain:
Locate and copy the Pin SHA256 hash.
Note:
- We can get SHA256 Pin from
Additional Certificates (if supplied)
also.- Check Valid until on the page. After this the certificate will expire so you again have to add new ones and make new app builds.
- Copy Domain for eg: The domain for https://dev.to/ is dev.to.
Implementing SSL Pinning on iOS
Step 1: Update Your Podfile
Add TrustKit
to your Podfile
for SSL pinning support.
require_relative '../node_modules/react-native/scripts/react_native_pods'
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'
platform :ios, '12.4'
install! 'cocoapods', :deterministic_uuids => false
target 'sslPinning' do
config = use_native_modules!
pod 'TrustKit' # Add TrustKit for SSL Pinning
...
Run pod install
to install the new dependency.
Step 2: Configure AppDelegate.mm
- Import TrustKit and Configure TrustKit in the
application:didFinishLaunchingWithOptions:
method:
...
#import <TrustKit/TrustKit.h>
...
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
...
NSDictionary *trustKitConfig = @{
kTSKSwizzleNetworkDelegates: @YES,
kTSKPinnedDomains: @{
@"yourdomain.com" : @{
kTSKIncludeSubdomains: @YES,
kTSKEnforcePinning: @YES,
kTSKDisableDefaultReportUri: @YES,
kTSKPublicKeyHashes : @[
@"<Primary SHA256 key>", // Replace with your actual SHA256 key
@"<Backup SHA256 key>", // Replace with a backup key
],
},
}};
[TrustKit initSharedInstanceWithConfiguration:trustKitConfig];
...
Update
"yourdomain.com"
with your domain and replace<Primary SHA256 key>
and<Backup SHA256 key>
with your actual SHA256 keys.
Implementing SSL Pinning on Android
a) JAVA
Step 1: Create SSLPinningFactory.java
Inside android/app/src/main/java/com/yourappname/
, create a new file named SSLPinningFactory.java
and add the following code:
package com.yourappname; // Update with your package name
import com.facebook.react.modules.network.OkHttpClientFactory;
import com.facebook.react.modules.network.OkHttpClientProvider;
import okhttp3.CertificatePinner;
import okhttp3.OkHttpClient;
public class SSLPinningFactory implements OkHttpClientFactory {
private static String hostname = "yourdomain.com"; // Update with your domain
public OkHttpClient createNewNetworkModuleClient() {
CertificatePinner certificatePinner = new CertificatePinner.Builder()
.add(hostname, "sha256/<your SHA256 key>") // Replace with your SHA256 key
.build();
OkHttpClient.Builder clientBuilder = OkHttpClientProvider.createClientBuilder();
return clientBuilder.certificatePinner(certificatePinner).build();
}
}
Note: Replace
"your SHA256 key"
with your actual SHA256 hash.
Step 2: Register the SSL Pinning Factory in MainApplication.java
In MainApplication.java
, configure SSL Pinning by adding the following line in the onCreate
method:
+ import com.facebook.react.modules.network.OkHttpClientProvider;
...
public class MainApplication extends Application implements ReactApplication {
@Override
public void onCreate() {
super.onCreate();
ReactFeatureFlags.useTurboModules = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
SoLoader.init(this, /* native exopackage */ false);
initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
+ OkHttpClientProvider.setOkHttpClientFactory(new SSLPinningFactory()); // Register the SSLPinningFactory
}
}
---------------------OR----------------------
b) KOTLIN
SSLPinningFactory.kt
package com.-----
import com.facebook.react.modules.network.OkHttpClientFactory
import com.facebook.react.modules.network.OkHttpClientProvider
import okhttp3.CertificatePinner
import okhttp3.OkHttpClient
class SSLPinningFactory : OkHttpClientFactory {
companion object {
private const val hostname = "-----"
private val sha256Keys = listOf(
"sha256/------",
"sha256/------")
}
override fun createNewNetworkModuleClient(): OkHttpClient {
val certificatePinnerBuilder = CertificatePinner.Builder()
for (key in sha256Keys) {
certificatePinnerBuilder.add(hostname, key)
}
val certificatePinner = certificatePinnerBuilder.build()
val clientBuilder = OkHttpClientProvider.createClientBuilder()
return clientBuilder.certificatePinner(certificatePinner).build()
}
}
MainApplication.kt
...
+ import com.facebook.react.modules.network.OkHttpClientProvider // Import OkHttpClientProvider
class MainApplication : Application(), ReactApplication {
...
override fun onCreate() {
super.onCreate()
SoLoader.init(this, false)
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
load() // Load native entry point for New Architecture
}
ReactNativeFlipper.initializeFlipper(this, reactNativeHost.reactInstanceManager)
// Register SSLPinningFactory for OkHttpClientProvider
+ OkHttpClientProvider.setOkHttpClientFactory(SSLPinningFactory())
}
}
For Multiple Domain with .env do like this:
.env
# Domain 1
SSL_PINNING_HOSTNAME_1="-----.com"
SSL_PINNING_KEY_PRIMARY_1="---="
SSL_PINNING_KEY_BACKUP_1="---="
# Domain 2
SSL_PINNING_HOSTNAME_2="---.com"
SSL_PINNING_KEY_PRIMARY_2="---="
SSL_PINNING_KEY_BACKUP_2="---="
SSLPinningFactory.kt
package com.yourappname
import com.facebook.react.modules.network.OkHttpClientFactory
import com.facebook.react.modules.network.OkHttpClientProvider
import okhttp3.CertificatePinner
import okhttp3.OkHttpClient
class SSLPinningFactory : OkHttpClientFactory {
companion object {
private val domains = mapOf(
BuildConfig.SSL_PINNING_HOSTNAME_1 to listOf(
"sha256/${BuildConfig.SSL_PINNING_KEY_PRIMARY_1}",
"sha256/${BuildConfig.SSL_PINNING_KEY_BACKUP_1}"
),
BuildConfig.SSL_PINNING_HOSTNAME_2 to listOf(
"sha256/${BuildConfig.SSL_PINNING_KEY_PRIMARY_2}",
"sha256/${BuildConfig.SSL_PINNING_KEY_BACKUP_2}"
)
)
}
override fun createNewNetworkModuleClient(): OkHttpClient {
val certificatePinnerBuilder = CertificatePinner.Builder()
// Iterate through domains and keys to configure SSL pinning
for ((hostname, keys) in domains) {
for (key in keys) {
certificatePinnerBuilder.add(hostname, key)
}
}
val certificatePinner = certificatePinnerBuilder.build()
val clientBuilder = OkHttpClientProvider.createClientBuilder()
return clientBuilder.certificatePinner(certificatePinner).build()
}
}
AppDelegate.mm
#import <TrustKit/TrustKit.h>
#import <ReactNativeConfig/ReactNativeConfig.h>
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
...
...
NSString *sslPinningHostname1 = [ReactNativeConfig envFor:@"SSL_PINNING_HOSTNAME_1"];
NSString *sslPinningKeyPrimary1 = [ReactNativeConfig envFor:@"SSL_PINNING_KEY_PRIMARY_1"];
NSString *sslPinningKeyBackup1 = [ReactNativeConfig envFor:@"SSL_PINNING_KEY_BACKUP_1"];
NSString *sslPinningHostname2 = [ReactNativeConfig envFor:@"SSL_PINNING_HOSTNAME_2"];
NSString *sslPinningKeyPrimary2 = [ReactNativeConfig envFor:@"SSL_PINNING_KEY_PRIMARY_2"];
NSString *sslPinningKeyBackup2 = [ReactNativeConfig envFor:@"SSL_PINNING_KEY_BACKUP_2"];
NSDictionary *trustKitConfig = @{
kTSKSwizzleNetworkDelegates: @YES,
kTSKPinnedDomains: @{
sslPinningHostname1: @{ // First domain
kTSKIncludeSubdomains: @YES,
kTSKEnforcePinning: @YES,
kTSKDisableDefaultReportUri: @YES,
kTSKPublicKeyHashes: @[
sslPinningKeyPrimary1,
sslPinningKeyBackup1,
],
},
sslPinningHostname2: @{ // Second domain
kTSKIncludeSubdomains: @YES,
kTSKEnforcePinning: @YES,
kTSKDisableDefaultReportUri: @YES,
kTSKPublicKeyHashes: @[
sslPinningKeyPrimary2,
sslPinningKeyBackup2,
],
},
}
};
// Initialize TrustKit with the configuration
[TrustKit initSharedInstanceWithConfiguration:trustKitConfig];
...
...
Conclusion
That’s it! You’ve successfully implemented SSL pinning for both iOS and Android in your React Native app. This setup ensures that only secure, trusted connections to your server are allowed, protecting your app from potential MITM attacks. 🥳
For more information, refer to the detailed guide from Callstack's SSL Pinning Blog.
Top comments (0)