I’m adding an expo plugin that configures iOS and Android to use pinned certificates for our domains.
4.4 KiB
SSL Pinning Expo Config Plugin
This plugin implements SSL certificate pinning for React Native apps using Expo, supporting both iOS and Android platforms.
Features
- iOS SSL Pinning: Uses TrustKit library for secure certificate validation
- Android SSL Pinning: Uses OkHttp's built-in certificate pinner
- Automatic native code modification: Handles all necessary iOS and Android code changes
How SSL Pinning Works
SSL pinning enhances security by embedding specific certificate hashes directly into your app code. This prevents man-in-the-middle attacks even if an attacker manages to install malicious certificates on the device or compromises a certificate authority.
Getting Certificate Hashes
Before configuring the plugin, you need to obtain the public key hashes for your domains:
Method 1: Use the Certificate Extraction Script (Recommended)
# This script automatically extracts certificates and provides ready-to-use SSL pinning configuration
cd apps/mobile
node scripts/getCertificates.js safe-client.safe.global
# Multiple domains at once
node scripts/getCertificates.js safe-client.safe.global safe-client.staging.5afe.dev
Method 2: Manual OpenSSL Commands
Step 1: Get Full Certificate Chain
# Get the complete certificate chain
openssl s_client -servername example.com -connect example.com:443 -showcerts > fullchain.pem
Step 2: Extract Specific Certificates
# Extract leaf certificate (first certificate)
awk '/BEGIN CERTIFICATE/,/END CERTIFICATE/' fullchain.pem | sed -n '1,/END CERTIFICATE/p' > leaf.pem
# Extract intermediate certificate (second certificate)
awk '/BEGIN CERTIFICATE/,/END CERTIFICATE/' fullchain.pem | sed -n '2,/END CERTIFICATE/p' > intermediate.pem
# Extract root certificate (last certificate)
awk '/BEGIN CERTIFICATE/,/END CERTIFICATE/' fullchain.pem | tail -n +2 > root.pem
Step 3: Generate Certificate Hashes
# Leaf certificate hash
openssl x509 -in leaf.pem -pubkey -noout | openssl rsa -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
# Intermediate certificate hash (recommended for backup)
openssl x509 -in intermediate.pem -pubkey -noout | openssl rsa -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
# Root certificate hash
openssl x509 -in root.pem -pubkey -noout | openssl rsa -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
Configuration
1. Configure SSL Pinning Domains
Update apps/mobile/app.config.js to include your domains and certificate hashes in the SSL pinning plugin configuration:
[
'./expo-plugins/ssl-pinning/withSSLPinning.js',
{
domains: {
'api.safeglobal.io': [
'PRIMARY_CERT_HASH_BASE64'
],
'dev-api.safeglobal.io': [
'DEV_PRIMARY_CERT_HASH_BASE64'
],
},
},
],
2. Plugin Configuration
The plugin is already integrated into the app configuration. Simply update the domains object with your certificate hashes.
Environment Configuration
The plugin uses the same domain configuration for all builds. You can include both production and development/staging domains in the same configuration:
- All configured domains will be pinned across all build variants
- Include both production and staging/development domains as needed
- Each domain can have multiple certificate hashes for backup
Testing SSL Pinning
Testing Valid Certificates
- Build and run your app
- Make network requests to pinned domains
- Requests should work normally
Testing Invalid Certificates
- Change one of the certificate hashes to an invalid value (e.g., all A's)
- Rebuild the app completely
- Clear app cache/data or reinstall
- Network requests to pinned domains should fail
Important: iOS maintains a TLS session cache, so you may need to:
- Delete and reinstall the app
- Reset the iOS Simulator
- Wait for the cache to expire
Using Proxy Tools
You can test SSL pinning using tools like:
When SSL pinning is working correctly, requests through these proxies should fail.