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.
SSL Pinning with react-native-ssl-public-key-pinning
You can use the react-native-ssl-public-key-pinning
library to implement SSL Pinning in your React Native application to enhance security by ensuring that your app communicates only with trusted servers.
Steps to Implement SSL Pinning
Step 1: Install the Library
First, install the library in your project:
yarn add react-native-ssl-public-key-pinning
Step 2: Create a Utility File for SSL Pinning (sslPinning.ts
)
In this file, we will configure the SSL pinning for multiple hostnames and load public keys from environment variables.
import Config from 'react-native-config';
import { initializeSslPinning, isSslPinningAvailable } from 'react-native-ssl-public-key-pinning';
/**
* SSL Pinning Configuration Interface
*/
interface SslPinningConfig {
[hostname: string]: {
includeSubdomains: boolean;
publicKeyHashes: string[];
expirationDate?: string;
};
}
/**
* Initialize SSL Pinning with environment variables for multiple hostnames
* @returns Promise<boolean> - true if successful, false otherwise
*/
export const initializeSslPinningLib = async (): Promise<boolean> => {
try {
// Check if SSL Pinning is supported on the device
if (!isSslPinningAvailable()) {
console.warn('SSL pinning is not available on this device');
return false;
}
// Get SSL pinning configuration values from environment variables
const hostname1 = Config.SSL_PINNING_HOSTNAME_1;
const primaryKey1 = Config.SSL_PINNING_KEY_PRIMARY_1;
const backupKey1 = Config.SSL_PINNING_KEY_BACKUP_1;
const hostname2 = Config.SSL_PINNING_HOSTNAME_2;
const primaryKey2 = Config.SSL_PINNING_KEY_PRIMARY_2;
const backupKey2 = Config.SSL_PINNING_KEY_BACKUP_2;
// Validate the required environment variables for at least one hostname
if (!hostname1 || !primaryKey1 || !backupKey1) {
console.error('SSL pinning environment variables are not properly configured');
console.error(
'Required: SSL_PINNING_HOSTNAME_1, SSL_PINNING_KEY_PRIMARY_1, SSL_PINNING_KEY_BACKUP_1'
);
return false;
}
// Build the configuration object for SSL pinning
const sslPinningConfig: SslPinningConfig = {
[hostname1]: {
includeSubdomains: true,
publicKeyHashes: [primaryKey1, backupKey1],
},
};
// Add the second hostname if configured
if (hostname2 && primaryKey2 && backupKey2) {
sslPinningConfig[hostname2] = {
includeSubdomains: true,
publicKeyHashes: [primaryKey2, backupKey2],
};
}
// Initialize SSL Pinning with the configuration
await initializeSslPinning(sslPinningConfig);
const hostnames = Object.keys(sslPinningConfig);
console.log(
`SSL pinning initialized for hostnames: ${hostnames.join(', ')}`
);
alert(
`SSL pinning initialized for hostnames: ${hostnames.join(', ')}`
);
return true;
} catch (error) {
console.error('Failed to initialize SSL pinning:', error);
return false;
}
};
Step 3: Using the Utility in Your App
You can now use the initializeSslPinningLib
function to initialize SSL pinning when the app starts or on a specific event.
import { useEffect } from 'react';
import { initializeSslPinningLib } from './sslPinning'; // Adjust the import according to your file structure
useEffect(() => {
const initializeSecurityTest = async () => {
const success = await initializeSslPinningLib();
if (success) {
console.log('SSL pinning successfully initialized');
} else {
console.log('Failed to initialize SSL pinning');
}
};
initializeSecurityTest();
}, []);
Step 4: Testing SSL Pinning
When testing the implementation:
- Invalid Public Keys: API calls should not proceed if the SSL certificate doesn't match the pinned keys.
- Valid Public Keys: The API should work as expected with valid SHA256 public key hashes.
If the app tries to connect to a server with an incorrect certificate, the connection should be terminated, and you should prevent any API calls from occurring, thereby ensuring no communication with untrusted servers.
Final Notes:
- SSL Pinning is essential for security, but you must ensure that your public keys are properly configured, and your environment variables are securely managed.
- This library works for both iOS and Android as long as you configure the environment variables and public keys correctly.
-
Security Best Practices: Do not hardcode public keys directly in the app; always fetch them securely, and use
react-native-config
or similar secure configuration management.
OR YOU CAN IMPLEMENT WITHOUT ANY LIBRARY USING NATIVE CODE AS PER BELOW:
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="paperless---.com"
SSL_PINNING_KEY_PRIMARY_2="asas---="
SSL_PINNING_KEY_BACKUP_2="sadsd---="
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 "RNCConfig.h" // Import react-native-config
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
...
...
NSString *sslPinningHostname1 = [RNCConfig envFor:@"SSL_PINNING_HOSTNAME_1"];
NSString *sslPinningKeyPrimary1 = [RNCConfig envFor:@"SSL_PINNING_KEY_PRIMARY_1"];
NSString *sslPinningKeyBackup1 = [RNCConfig envFor:@"SSL_PINNING_KEY_BACKUP_1"];
NSString *sslPinningHostname2 = [RNCConfig envFor:@"SSL_PINNING_HOSTNAME_2"];
NSString *sslPinningKeyPrimary2 = [RNCConfig envFor:@"SSL_PINNING_KEY_PRIMARY_2"];
NSString *sslPinningKeyBackup2 = [RNCConfig 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)