Skip to content

Commit

Permalink
iOS SSL Pinning with TrustKit
Browse files Browse the repository at this point in the history
  • Loading branch information
ywongweb committed Sep 6, 2020
1 parent cd6cddc commit be2871e
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 92 deletions.
161 changes: 70 additions & 91 deletions App.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,109 +6,88 @@
* @flow strict-local
*/

import React from 'react';
import {
SafeAreaView,
StyleSheet,
ScrollView,
View,
Text,
StatusBar,
} from 'react-native';
import React, {useState} from 'react'
import {StyleSheet, View, Text, Button} from 'react-native'

import {
Header,
LearnMoreLinks,
Colors,
DebugInstructions,
ReloadInstructions,
} from 'react-native/Libraries/NewAppScreen';
const URL = 'https://busdue.com'

const App: () => React$Node = () => {
const [validationMsg, setValidationMsg] = useState('Waiting')
const [validationStatus, setValidationStatus] = useState('')
const onConnectPress = () => {
fetch(URL)
.then((res) => {
console.log('**************')
console.log(res)
console.log('**************')
setValidationMsg('Valid certificate, connected.')
setValidationStatus('success')
})
.catch(() => {
setValidationMsg('Certificate does not match, connection refused')
setValidationStatus('failed')
})
}

return (
<>
<StatusBar barStyle="dark-content" />
<SafeAreaView>
<ScrollView
contentInsetAdjustmentBehavior="automatic"
style={styles.scrollView}>
<Header />
{global.HermesInternal == null ? null : (
<View style={styles.engine}>
<Text style={styles.footer}>Engine: Hermes</Text>
</View>
)}
<View style={styles.body}>
<View style={styles.sectionContainer}>
<Text style={styles.sectionTitle}>Step One</Text>
<Text style={styles.sectionDescription}>
Edit <Text style={styles.highlight}>App.js</Text> to change this
screen and then come back to see your edits.
</Text>
</View>
<View style={styles.sectionContainer}>
<Text style={styles.sectionTitle}>See Your Changes</Text>
<Text style={styles.sectionDescription}>
<ReloadInstructions />
</Text>
</View>
<View style={styles.sectionContainer}>
<Text style={styles.sectionTitle}>Debug</Text>
<Text style={styles.sectionDescription}>
<DebugInstructions />
</Text>
</View>
<View style={styles.sectionContainer}>
<Text style={styles.sectionTitle}>Learn More</Text>
<Text style={styles.sectionDescription}>
Read the docs to discover what to do next:
</Text>
</View>
<LearnMoreLinks />
</View>
</ScrollView>
</SafeAreaView>
</>
);
};
<View style={styles.container}>
<Text style={styles.title}>React Native SSL Pinning</Text>
<Text style={styles.title}>(iOS)</Text>
<Text style={styles.header}>Certificate status:</Text>
<Text
style={[
styles.status,
validationStatus === 'success' && styles.success,
validationStatus === 'failed' && styles.failed,
]}>
{validationMsg}
</Text>
<View style={styles.btnContainer}>
<Button title={`Test ${URL}`} onPress={onConnectPress} />
</View>
</View>
)
}

const styles = StyleSheet.create({
scrollView: {
backgroundColor: Colors.lighter,
},
engine: {
position: 'absolute',
right: 0,
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
padding: 24,
},
body: {
backgroundColor: Colors.white,
title: {
fontSize: 24,
fontWeight: 'bold',
textAlign: 'center',
},
sectionContainer: {
marginTop: 32,
paddingHorizontal: 24,
header: {
paddingTop: 46,
fontSize: 18,
textAlign: 'center',
},
sectionTitle: {
fontSize: 24,
fontWeight: '600',
color: Colors.black,
status: {
fontSize: 18,
fontWeight: 'bold',
textAlign: 'center',
},
sectionDescription: {
marginTop: 8,
success: {
fontSize: 18,
fontWeight: '400',
color: Colors.dark,
fontWeight: 'bold',
color: 'green',
},
highlight: {
fontWeight: '700',
failed: {
fontSize: 18,
fontWeight: 'bold',
color: 'red',
},
footer: {
color: Colors.dark,
fontSize: 12,
fontWeight: '600',
padding: 4,
paddingRight: 12,
textAlign: 'right',
btnContainer: {
borderWidth: 1,
borderColor: 'grey',
borderRadius: 4,
paddingHorizontal: 16,
marginTop: 24,
},
});
})

export default App;
export default App
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
33 changes: 33 additions & 0 deletions ios/BlogReactNativeSSLPinning/AppDelegate.m
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
#import <FlipperKitNetworkPlugin/FlipperKitNetworkPlugin.h>
#import <SKIOSNetworkPlugin/SKIOSNetworkAdapter.h>
#import <FlipperKitReactPlugin/FlipperKitReactPlugin.h>
#import <TrustKit/TrustKit.h>
#import <TrustKit/TSKPinningValidator.h>
#import <TrustKit/TSKPinningValidatorCallback.h>

static void InitializeFlipper(UIApplication *application) {
FlipperClient *client = [FlipperClient sharedClient];
Expand All @@ -30,6 +33,36 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
#ifdef FB_SONARKIT_ENABLED
InitializeFlipper(application);
#endif

// Override TrustKit's logger method, useful for local debugging
void (^loggerBlock)(NSString *) = ^void(NSString *message)
{
NSLog(@"TrustKit log: %@", message);
};
[TrustKit setLoggerBlock:loggerBlock];

NSDictionary *trustKitConfig =
@{
// Swizzling because we can't access the NSURLSession instance used in React Native's fetch method
kTSKSwizzleNetworkDelegates: @YES,
kTSKPinnedDomains: @{
@"busdue.com" : @{
kTSKIncludeSubdomains: @YES, // Pin all subdomains
kTSKEnforcePinning: @YES, // Block connections if pinning validation failed
kTSKDisableDefaultReportUri: @YES,
kTSKPublicKeyHashes : @[
@"dz0GbS1i4LnBsJwhRw3iuZmVcgqpn+AlxSBRxUbOz0k=",
@"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=", // Fake backup key but we need to provide 2 pins
],
},
}};
[TrustKit initSharedInstanceWithConfiguration:trustKitConfig];
[TrustKit sharedInstance].pinningValidatorCallback = ^(TSKPinningValidatorResult *result, NSString *notedHostname, TKSDomainPinningPolicy *policy) {
if (result.finalTrustDecision == TSKTrustEvaluationFailedNoMatchingPin) {
NSLog(@"TrustKit certificate matching failed");
// Add more logging here. i.e. Sentry, BugSnag etc
}
};

RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
Expand Down
1 change: 1 addition & 0 deletions ios/Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ target 'BlogReactNativeSSLPinning' do
config = use_native_modules!

use_react_native!(:path => config["reactNativePath"])
pod 'TrustKit', '1.6.5'

target 'BlogReactNativeSSLPinningTests' do
inherit! :complete
Expand Down
6 changes: 5 additions & 1 deletion ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@ PODS:
- React-Core (= 0.63.2)
- React-cxxreact (= 0.63.2)
- React-jsi (= 0.63.2)
- TrustKit (1.6.5)
- Yoga (1.14.0)
- YogaKit (1.18.1):
- Yoga (~> 1.14)
Expand Down Expand Up @@ -347,6 +348,7 @@ DEPENDENCIES:
- React-RCTText (from `../node_modules/react-native/Libraries/Text`)
- React-RCTVibration (from `../node_modules/react-native/Libraries/Vibration`)
- ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`)
- TrustKit (= 1.6.5)
- Yoga (from `../node_modules/react-native/ReactCommon/yoga`)

SPEC REPOS:
Expand All @@ -362,6 +364,7 @@ SPEC REPOS:
- Flipper-RSocket
- FlipperKit
- OpenSSL-Universal
- TrustKit
- YogaKit

EXTERNAL SOURCES:
Expand Down Expand Up @@ -455,9 +458,10 @@ SPEC CHECKSUMS:
React-RCTText: 1b6773e776e4b33f90468c20fe3b16ca3e224bb8
React-RCTVibration: 4d2e726957f4087449739b595f107c0d4b6c2d2d
ReactCommon: a0a1edbebcac5e91338371b72ffc66aa822792ce
TrustKit: 073855e3adecd317417bda4ac9e9ac54a2e3b9f2
Yoga: 7740b94929bbacbddda59bf115b5317e9a161598
YogaKit: f782866e155069a2cca2517aafea43200b01fd5a

PODFILE CHECKSUM: 50523be1be54cbba666e09f1a8382518f82150c2
PODFILE CHECKSUM: 018c00e8b36345c97ac2f711e853894785ce18f1

COCOAPODS: 1.8.4

0 comments on commit be2871e

Please sign in to comment.