react-native-ssl-manager 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Trần Đình Huy
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"), to deal
6
+ in the Software without restriction, including without limitation the rights
7
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is
9
+ furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all
12
+ copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,264 @@
1
+ # react-native-ssl-manager
2
+
3
+ React Native SSL Pinning provides seamless SSL certificate pinning integration for enhanced network security in React Native apps. This module enables developers to easily implement and manage certificate pinning, protecting applications against man-in-the-middle (MITM) attacks. With dynamic configuration options and the ability to toggle SSL pinning, it's particularly useful for development and testing scenarios.
4
+
5
+ ## Features
6
+
7
+ - 🔒 Easy SSL certificate pinning implementation
8
+ - 🔄 Dynamic enabling/disabling of SSL pinning
9
+ - ⚡ Optimized for development and testing workflows
10
+ - 📱 Cross-platform support (iOS & Android)
11
+ - 🛠️ Simple configuration using JSON
12
+ - 🚀 Performance-optimized implementation
13
+
14
+ ## Installation
15
+
16
+ ```sh
17
+ npm install react-native-ssl-manager
18
+ ```
19
+
20
+ ## Usage
21
+
22
+ ### Basic Setup
23
+
24
+ ```typescript
25
+ import {
26
+ initializeSslPinning,
27
+ setUseSSLPinning,
28
+ getUseSSLPinning
29
+ } from 'react-native-ssl-manager';
30
+
31
+ // Initialize SSL pinning with configuration
32
+ const sslConfig = {
33
+ "domains": {
34
+ "development": "api.dev.example.com",
35
+ "production": "api.example.com"
36
+ },
37
+ "sha256Keys": {
38
+ "api.dev.example.com": [
39
+ "sha256/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX=",
40
+ "sha256/YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY="
41
+ ],
42
+ "api.example.com": [
43
+ "sha256/ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ=",
44
+ "sha256/WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW="
45
+ ]
46
+ }
47
+ };
48
+
49
+ // Initialize the SSL pinning
50
+ await initializeSslPinning(JSON.stringify(sslConfig));
51
+
52
+ // Enable SSL pinning
53
+ await setUseSSLPinning(true);
54
+
55
+ // Check if SSL pinning is enabled
56
+ const isEnabled = await getUseSSLPinning();
57
+ console.log('SSL Pinning enabled:', isEnabled);
58
+ ```
59
+
60
+ ### Configuration File (ssl_config.json)
61
+
62
+ Create a configuration file with your domain certificates. Example structure:
63
+
64
+ ```json
65
+ {
66
+ "domains": {
67
+ "development": "api.dev.example.com",
68
+ "production": "api.example.com"
69
+ },
70
+ "sha256Keys": {
71
+ "api.dev.example.com": [
72
+ "sha256/certificate-hash-1=",
73
+ "sha256/certificate-hash-2="
74
+ ],
75
+ "api.example.com": [
76
+ "sha256/certificate-hash-3=",
77
+ "sha256/certificate-hash-4="
78
+ ]
79
+ }
80
+ }
81
+ ```
82
+
83
+ ### API Reference
84
+
85
+ #### `initializeSslPinning(configJsonString: string): Promise<any>`
86
+ Initializes the SSL pinning configuration with the provided JSON string configuration.
87
+
88
+ ```typescript
89
+ await initializeSslPinning(JSON.stringify(sslConfig));
90
+ ```
91
+
92
+ #### `setUseSSLPinning(usePinning: boolean): void`
93
+ Enables or disables SSL pinning dynamically.
94
+
95
+ ```typescript
96
+ await setUseSSLPinning(true); // Enable SSL pinning
97
+ await setUseSSLPinning(false); // Disable SSL pinning
98
+ ```
99
+
100
+ #### `getUseSSLPinning(): Promise<boolean>`
101
+ Retrieves the current state of SSL pinning.
102
+
103
+ ```typescript
104
+ const isEnabled = await getUseSSLPinning();
105
+ ```
106
+
107
+ ## Important Notes ⚠️
108
+
109
+ ### Restarting After SSL Pinning Changes
110
+
111
+ When using `setUseSSLPinning`, a restart of the application is required for changes to take effect. This is because SSL pinning is implemented at the native level.
112
+
113
+ #### Using React Native Restart
114
+
115
+ First, install react-native-restart:
116
+
117
+ ```sh
118
+ # Using npm
119
+ npm install react-native-restart
120
+
121
+ # Using yarn
122
+ yarn add react-native-restart
123
+ ```
124
+
125
+ For iOS, run pod install:
126
+ ```sh
127
+ cd ios && pod install
128
+ ```
129
+
130
+ Then use it in your code:
131
+ ```typescript
132
+ import RNRestart from 'react-native-restart';
133
+
134
+ const toggleSSLPinning = async (enabled: boolean) => {
135
+ await setUseSSLPinning(enabled);
136
+ // Restart the app to apply changes
137
+ RNRestart.Restart();
138
+ };
139
+
140
+ // Example with user confirmation
141
+ const handleSSLToggle = async (enabled: boolean) => {
142
+ // Save any necessary state
143
+ await saveAppState();
144
+
145
+ // Update SSL pinning
146
+ await setUseSSLPinning(enabled);
147
+
148
+ // Show user message
149
+ Alert.alert(
150
+ 'Restart Required',
151
+ 'The app needs to restart to apply security changes.',
152
+ [
153
+ {
154
+ text: 'Restart Now',
155
+ onPress: () => RNRestart.Restart()
156
+ }
157
+ ]
158
+ );
159
+ };
160
+ ```
161
+
162
+ ## Development and Testing Benefits
163
+
164
+ ### For Developers
165
+ - **Quick Toggling**: Easily switch SSL pinning on/off during development
166
+ - **Performance Optimization**: Minimize SSL verification overhead during development
167
+ - **Flexible Configuration**: Support multiple environments with different certificates
168
+
169
+ ### For QA Teams
170
+ - **Efficient Testing**: Quickly verify API behavior with and without SSL pinning
171
+ - **Issue Investigation**: Easily isolate SSL-related issues
172
+ - **Environment Switching**: Seamlessly test across different environments
173
+
174
+ ## Best Practices
175
+
176
+ 1. **Environment Management**
177
+ - Keep separate configurations for development and production
178
+ - Store production certificates securely
179
+
180
+ 2. **Performance Optimization**
181
+ - Enable SSL pinning only when necessary during development
182
+ - Use development certificates for testing environments
183
+
184
+ 3. **Security Considerations**
185
+ - Always enable SSL pinning in production
186
+ - Regularly update certificates before expiration
187
+ - Maintain multiple backup certificates
188
+
189
+ ## Roadmap 🗺️
190
+
191
+ We're actively working on expanding the capabilities of react-native-ssl-manager. Here are our planned features:
192
+
193
+ ### Upcoming Features
194
+
195
+ - 📱 **Expo Plugin Integration**
196
+ - Native SSL pinning support for Expo projects
197
+ - Seamless configuration through expo-config-plugin
198
+ - Auto-linking capabilities for Expo development builds
199
+ - Support for Expo's development client
200
+
201
+ ## Testing with Proxyman 🔍
202
+
203
+ Proxyman is a powerful tool for testing SSL pinning implementation. Here's how you can verify your SSL pinning configuration:
204
+
205
+ ### Setup Verification
206
+
207
+ 1. **Install Proxyman**
208
+ - Download and install [Proxyman](https://proxyman.io/)
209
+ - Install Proxyman's SSL certificate on your device/simulator
210
+
211
+ 2. **Testing SSL Pinning**
212
+ ```typescript
213
+ // Enable SSL Pinning
214
+ await setUseSSLPinning(true);
215
+
216
+ // Make API requests through your app
217
+ // If SSL pinning is working correctly:
218
+ // - Requests will fail when Proxyman tries to intercept them
219
+ // - You'll see SSL/TLS handshake errors
220
+
221
+ // Disable SSL Pinning for debugging
222
+ await setUseSSLPinning(false);
223
+ // Now you can intercept and inspect API calls with Proxyman
224
+ ```
225
+
226
+ ### Common Test Scenarios
227
+
228
+ 1. **Verify SSL Pinning is Active**
229
+ - Enable SSL pinning
230
+ - Attempt to intercept traffic with Proxyman
231
+ - Requests should fail with SSL handshake errors
232
+
233
+ 2. **Debug API Calls**
234
+ - Disable SSL pinning temporarily
235
+ - Use Proxyman to inspect API requests/responses
236
+ - Helpful for QA testing and development
237
+
238
+ 3. **Certificate Validation**
239
+ - Verify your SSL configuration matches the certificates in ssl_config.json
240
+ - Test against both development and production endpoints
241
+
242
+ ### Troubleshooting Tips
243
+
244
+ - If requests succeed with Proxyman while SSL pinning is enabled, check your configuration
245
+ - Verify that the SHA256 hashes in your config match your server certificates
246
+ - Test both development and production environments separately
247
+
248
+ This integration with Proxyman makes it easy to:
249
+ - Verify SSL pinning implementation
250
+ - Debug API communications
251
+ - Validate security configurations
252
+ - Speed up development and testing workflows
253
+
254
+ ## Contributing
255
+
256
+ See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow.
257
+
258
+ ## License
259
+
260
+ For open source projects, say how it is licensed.
261
+
262
+ ---
263
+
264
+ Made with [create-react-native-library](https://github.com/callstack/react-native-builder-bob)
@@ -0,0 +1,97 @@
1
+ buildscript {
2
+ // Buildscript is evaluated before everything else so we can't use getExtOrDefault
3
+ def kotlin_version = rootProject.ext.has("kotlinVersion") ? rootProject.ext.get("kotlinVersion") : project.properties["UseSslPinning_kotlinVersion"]
4
+
5
+ repositories {
6
+ google()
7
+ mavenCentral()
8
+ }
9
+
10
+ dependencies {
11
+ classpath "com.android.tools.build:gradle:7.2.1"
12
+ // noinspection DifferentKotlinGradleVersion
13
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
14
+ }
15
+ }
16
+
17
+ def isNewArchitectureEnabled() {
18
+ return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
19
+ }
20
+
21
+ apply plugin: "com.android.library"
22
+ apply plugin: "kotlin-android"
23
+
24
+ if (isNewArchitectureEnabled()) {
25
+ apply plugin: "com.facebook.react"
26
+ }
27
+
28
+ def getExtOrDefault(name) {
29
+ return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["UseSslPinning_" + name]
30
+ }
31
+
32
+ def getExtOrIntegerDefault(name) {
33
+ return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["UseSslPinning_" + name]).toInteger()
34
+ }
35
+
36
+ def supportsNamespace() {
37
+ def parsed = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')
38
+ def major = parsed[0].toInteger()
39
+ def minor = parsed[1].toInteger()
40
+
41
+ // Namespace support was added in 7.3.0
42
+ return (major == 7 && minor >= 3) || major >= 8
43
+ }
44
+
45
+ android {
46
+ if (supportsNamespace()) {
47
+ namespace "com.usesslpinning"
48
+
49
+ sourceSets {
50
+ main {
51
+ manifest.srcFile "src/main/AndroidManifestNew.xml"
52
+ }
53
+ }
54
+ }
55
+
56
+ compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
57
+
58
+ defaultConfig {
59
+ minSdkVersion getExtOrIntegerDefault("minSdkVersion")
60
+ targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
61
+
62
+ }
63
+
64
+ buildTypes {
65
+ release {
66
+ minifyEnabled false
67
+ }
68
+ }
69
+
70
+ lintOptions {
71
+ disable "GradleCompatible"
72
+ }
73
+
74
+ compileOptions {
75
+ sourceCompatibility JavaVersion.VERSION_1_8
76
+ targetCompatibility JavaVersion.VERSION_1_8
77
+ }
78
+
79
+ }
80
+
81
+ repositories {
82
+ mavenCentral()
83
+ google()
84
+ }
85
+
86
+ def kotlin_version = getExtOrDefault("kotlinVersion")
87
+
88
+ dependencies {
89
+ // For < 0.71, this will be from the local maven repo
90
+ // For > 0.71, this will be replaced by `com.facebook.react:react-android:$version` by react gradle plugin
91
+ //noinspection GradleDynamicVersion
92
+ implementation "com.facebook.react:react-native:+"
93
+ implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
94
+ // Add OkHttp dependency for SSL pinning
95
+ implementation "com.squareup.okhttp3:okhttp:4.9.1"
96
+ }
97
+
@@ -0,0 +1,5 @@
1
+ UseSslPinning_kotlinVersion=1.7.0
2
+ UseSslPinning_minSdkVersion=21
3
+ UseSslPinning_targetSdkVersion=31
4
+ UseSslPinning_compileSdkVersion=31
5
+ UseSslPinning_ndkversion=21.4.7075529
@@ -0,0 +1,3 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
2
+ package="com.usesslpinning">
3
+ </manifest>
@@ -0,0 +1,2 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ </manifest>
@@ -0,0 +1,50 @@
1
+ package com.usesslpinning
2
+
3
+ import android.content.Context
4
+ import android.util.Log
5
+ import com.facebook.react.modules.network.OkHttpClientFactory
6
+ import com.facebook.react.modules.network.ReactCookieJarContainer
7
+ import okhttp3.CertificatePinner
8
+ import okhttp3.OkHttpClient
9
+ import org.json.JSONObject
10
+
11
+ class UseSslPinningFactory(
12
+ private val context: Context,
13
+ private val configJsonString: String
14
+ ) : OkHttpClientFactory {
15
+
16
+ override fun createNewNetworkModuleClient(): OkHttpClient {
17
+ val sharedPreferences = context.getSharedPreferences("AppSettings", Context.MODE_PRIVATE)
18
+ val useSSLPinning = sharedPreferences.getBoolean("useSSLPinning", true)
19
+
20
+ val clientBuilder = OkHttpClient.Builder().cookieJar(ReactCookieJarContainer()).apply {
21
+ if (useSSLPinning) {
22
+ val certificatePinnerBuilder = CertificatePinner.Builder()
23
+
24
+ try {
25
+ val configJson = JSONObject(configJsonString)
26
+ addCertificatesToPinner(certificatePinnerBuilder, configJson)
27
+ val certificatePinner = certificatePinnerBuilder.build()
28
+ certificatePinner(certificatePinner)
29
+ } catch (e: Exception) {
30
+ e.printStackTrace()
31
+ }
32
+ }
33
+ }.cache(null).build()
34
+
35
+ return clientBuilder
36
+ }
37
+ private fun addCertificatesToPinner(certificatePinnerBuilder: CertificatePinner.Builder, configJson: JSONObject) {
38
+ val sha256Keys = configJson.getJSONObject("sha256Keys")
39
+ val hostnames = sha256Keys.keys()
40
+ while (hostnames.hasNext()) {
41
+ val hostname = hostnames.next()
42
+ val keysArray = sha256Keys.getJSONArray(hostname)
43
+ for (i in 0 until keysArray.length()) {
44
+ val sha256Key = keysArray.getString(i)
45
+ certificatePinnerBuilder.add(hostname, sha256Key)
46
+ }
47
+ }
48
+ }
49
+ }
50
+
@@ -0,0 +1,45 @@
1
+ package com.usesslpinning
2
+
3
+ import android.content.Context // Thêm dòng này
4
+ import android.util.Log
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.bridge.ReactContextBaseJavaModule
7
+ import com.facebook.react.bridge.ReactMethod
8
+ import com.facebook.react.bridge.Promise
9
+ import com.facebook.react.modules.network.OkHttpClientProvider
10
+
11
+ class UseSslPinningModule(reactContext: ReactApplicationContext) :
12
+ ReactContextBaseJavaModule(reactContext) {
13
+
14
+ override fun getName(): String {
15
+ return NAME
16
+ }
17
+
18
+ @ReactMethod
19
+ fun setUseSSLPinning(usePinning: Boolean) {
20
+ val sharedPreferences = reactApplicationContext.getSharedPreferences("AppSettings", Context.MODE_PRIVATE)
21
+ sharedPreferences.edit().putBoolean("useSSLPinning", usePinning).apply()
22
+ }
23
+
24
+ @ReactMethod
25
+ fun getUseSSLPinning(promise: Promise) {
26
+ val sharedPreferences = reactApplicationContext.getSharedPreferences("AppSettings", Context.MODE_PRIVATE)
27
+ val usePinning = sharedPreferences.getBoolean("useSSLPinning", true)
28
+ promise.resolve(usePinning)
29
+ }
30
+
31
+ @ReactMethod
32
+ fun initializeSslPinning(configJsonString: String, promise: Promise) {
33
+ try {
34
+ OkHttpClientProvider.setOkHttpClientFactory(UseSslPinningFactory(reactApplicationContext, configJsonString))
35
+ Log.d("MyLibrary", "SSL Pinning initialized successfully")
36
+ promise.resolve("SSL Pinning initialized successfully")
37
+ } catch (e: Exception) {
38
+ promise.reject("SSL_PINNING_ERROR", "Failed to initialize SSL Pinning", e)
39
+ }
40
+ }
41
+
42
+ companion object {
43
+ const val NAME = "UseSslPinning"
44
+ }
45
+ }
@@ -0,0 +1,17 @@
1
+ package com.usesslpinning
2
+
3
+ import com.facebook.react.ReactPackage
4
+ import com.facebook.react.bridge.NativeModule
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.uimanager.ViewManager
7
+
8
+
9
+ class UseSslPinningPackage : ReactPackage {
10
+ override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
11
+ return listOf(UseSslPinningModule(reactContext))
12
+ }
13
+
14
+ override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
15
+ return emptyList()
16
+ }
17
+ }
@@ -0,0 +1,2 @@
1
+ #import <React/RCTBridgeModule.h>
2
+ #import <React/RCTViewManager.h>
@@ -0,0 +1,19 @@
1
+ #import <React/RCTBridgeModule.h>
2
+
3
+ @interface RCT_EXTERN_MODULE(UseSslPinning, NSObject)
4
+
5
+ RCT_EXTERN_METHOD(setUseSSLPinning:(BOOL)usePinning)
6
+
7
+ RCT_EXTERN_METHOD(getUseSSLPinning:(RCTPromiseResolveBlock)resolve
8
+ rejecter:(RCTPromiseRejectBlock)reject)
9
+
10
+ RCT_EXTERN_METHOD(initializeSslPinning:(NSString *)configJsonString
11
+ resolver:(RCTPromiseResolveBlock)resolve
12
+ rejecter:(RCTPromiseRejectBlock)reject)
13
+
14
+ + (BOOL)requiresMainQueueSetup
15
+ {
16
+ return NO;
17
+ }
18
+
19
+ @end
@@ -0,0 +1,169 @@
1
+ import Foundation
2
+ import TrustKit
3
+ import TrustKit.TSKPinningValidator
4
+ import TrustKit.TSKPinningValidatorCallback
5
+
6
+ // Add SSLPinningError enum definition
7
+ enum SSLPinningError: Error {
8
+ case invalidConfiguration
9
+ case invalidPinConfiguration(domain: String)
10
+
11
+ var message: String {
12
+ switch self {
13
+ case .invalidConfiguration:
14
+ return "Invalid SSL pinning configuration format"
15
+ case .invalidPinConfiguration(let domain):
16
+ return "Invalid pin configuration for domain: \(domain)"
17
+ }
18
+ }
19
+ }
20
+
21
+ @objc(UseSslPinning)
22
+ class UseSslPinning: NSObject {
23
+ private static var sharedTrustKit: TrustKit?
24
+ private let userDefaults = UserDefaults.standard
25
+ private let useSSLPinningKey = "useSSLPinning"
26
+
27
+ private func cleanJsonString(_ jsonString: String) -> String {
28
+ var cleaned = jsonString
29
+ .replacingOccurrences(of: "\n", with: "")
30
+ .replacingOccurrences(of: "| ", with: "")
31
+ .replacingOccurrences(of: "\\ ", with: "")
32
+ .replacingOccurrences(of: "\\\"", with: "\"")
33
+
34
+ // Remove any remaining backslashes before quotes
35
+ cleaned = cleaned.replacingOccurrences(of: "\\(?!\")", with: "")
36
+
37
+ // Clean up any double spaces
38
+ cleaned = cleaned.replacingOccurrences(of: " ", with: " ")
39
+
40
+ NSLog("Original JSON: %@", jsonString)
41
+ NSLog("Cleaned JSON: %@", cleaned)
42
+
43
+ return cleaned
44
+ }
45
+
46
+ private func validateAndCleanPins(_ pins: [String], for domain: String) throws -> [String] {
47
+ return try pins.map { pin -> String in
48
+ var cleanPin = pin.trimmingCharacters(in: .whitespacesAndNewlines)
49
+
50
+ // Verify pin format
51
+ guard cleanPin.starts(with: "sha256/") else {
52
+ NSLog("Invalid pin format (missing sha256/): %@", cleanPin)
53
+ throw SSLPinningError.invalidPinConfiguration(domain: domain)
54
+ }
55
+
56
+ // Remove sha256/ prefix for TrustKit
57
+ cleanPin = cleanPin.replacingOccurrences(of: "sha256/", with: "")
58
+
59
+ // Verify base64 format
60
+ guard cleanPin.range(of: "^[A-Za-z0-9+/=]+$", options: .regularExpression) != nil else {
61
+ NSLog("Invalid pin format (not base64): %@", cleanPin)
62
+ throw SSLPinningError.invalidPinConfiguration(domain: domain)
63
+ }
64
+
65
+ return cleanPin
66
+ }
67
+ }
68
+
69
+ @objc
70
+ func initializeSslPinning(_ configJsonString: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
71
+ // Check if SSL pinning is enabled
72
+ let isSSLPinningEnabled = userDefaults.bool(forKey: useSSLPinningKey)
73
+
74
+ if isSSLPinningEnabled {
75
+ do {
76
+ // Parse JSON configuration
77
+ guard let jsonData = configJsonString.data(using: .utf8),
78
+ let config = try JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: Any],
79
+ let sha256Keys = config["sha256Keys"] as? [String: [String]] else {
80
+ throw SSLPinningError.invalidConfiguration
81
+ }
82
+
83
+ // Build pinned domains configuration
84
+ var pinnedDomains: [String: Any] = [:]
85
+
86
+ // Process each domain and its pins from JSON
87
+ for (domain, pins) in sha256Keys {
88
+ let cleanedPins = try pins.map { pin -> String in
89
+ // Validate and clean the pin
90
+ var cleanPin = pin.trimmingCharacters(in: .whitespacesAndNewlines)
91
+
92
+ // Verify pin format
93
+ guard cleanPin.starts(with: "sha256/") else {
94
+ NSLog("Invalid pin format for domain %@: %@", domain, cleanPin)
95
+ throw SSLPinningError.invalidPinConfiguration(domain: domain)
96
+ }
97
+
98
+ // Remove sha256/ prefix for TrustKit
99
+ cleanPin = cleanPin.replacingOccurrences(of: "sha256/", with: "")
100
+
101
+ return cleanPin
102
+ }
103
+
104
+ pinnedDomains[domain] = [
105
+ kTSKIncludeSubdomains: true,
106
+ kTSKEnforcePinning: true,
107
+ kTSKDisableDefaultReportUri: true,
108
+ kTSKPublicKeyHashes: cleanedPins
109
+ ]
110
+ }
111
+
112
+ let trustKitConfig: [String: Any] = [
113
+ kTSKSwizzleNetworkDelegates: true,
114
+ kTSKPinnedDomains: pinnedDomains
115
+ ]
116
+
117
+ DispatchQueue.main.async {
118
+ // Initialize TrustKit with the configuration
119
+ TrustKit.initSharedInstance(withConfiguration: trustKitConfig)
120
+
121
+ // Set up validation callback
122
+ TrustKit.sharedInstance().pinningValidatorCallback = { result, notedHostname, policy in
123
+ switch result.finalTrustDecision {
124
+ case .shouldBlockConnection:
125
+ NSLog("⛔️ SSL Pinning failed for domain: %@", notedHostname)
126
+ NSLog("Policy details: %@", policy)
127
+ case .shouldAllowConnection:
128
+ NSLog("✅ SSL Pinning succeeded for domain: %@", notedHostname)
129
+ default:
130
+ NSLog("⚠️ Unexpected SSL Pinning result for domain: %@", notedHostname)
131
+ }
132
+ }
133
+
134
+ NSLog("✅ TrustKit initialized with config: %@", trustKitConfig)
135
+ resolve([
136
+ "message": "SSL Pinning initialized successfully",
137
+ "domains": Array(pinnedDomains.keys)
138
+ ])
139
+ }
140
+
141
+ } catch let error as SSLPinningError {
142
+ NSLog("❌ SSL Pinning Error: %@", error.message)
143
+ reject("SSL_PINNING_ERROR", error.message, error)
144
+ } catch {
145
+ NSLog("❌ Unexpected Error: %@", error.localizedDescription)
146
+ reject("SSL_PINNING_ERROR", "Unexpected error during SSL pinning initialization", error)
147
+ }
148
+ } else {
149
+ NSLog("⚠️ SSL Pinning is disabled",isSSLPinningEnabled)
150
+ resolve([
151
+ "message": "SSL Pinning is disabled",
152
+ "domains": [],
153
+ "isSSLPinningEnabled": isSSLPinningEnabled
154
+ ])
155
+ }
156
+ }
157
+
158
+ @objc
159
+ func setUseSSLPinning(_ usePinning: Bool) {
160
+ userDefaults.set(usePinning, forKey: useSSLPinningKey)
161
+ userDefaults.synchronize()
162
+ }
163
+
164
+ @objc
165
+ func getUseSSLPinning(_ resolve: RCTPromiseResolveBlock, rejecter reject: RCTPromiseRejectBlock) {
166
+ let usePinning = userDefaults.bool(forKey: useSSLPinningKey)
167
+ resolve(usePinning)
168
+ }
169
+ }
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.setUseSSLPinning = exports.initializeSslPinning = exports.getUseSSLPinning = void 0;
7
+ var _reactNative = require("react-native");
8
+ const LINKING_ERROR = `The package 'react-native-ssl-manager' doesn't seem to be linked. Make sure: \n\n` + _reactNative.Platform.select({
9
+ ios: "- You have run 'pod install'\n",
10
+ default: ''
11
+ }) + '- You rebuilt the app after installing the package\n' + '- You are not using Expo Go\n';
12
+ const UseSslPinning = _reactNative.NativeModules.UseSslPinning ? _reactNative.NativeModules.UseSslPinning : new Proxy({}, {
13
+ get() {
14
+ throw new Error(LINKING_ERROR);
15
+ }
16
+ });
17
+ const setUseSSLPinning = usePinning => {
18
+ UseSslPinning.setUseSSLPinning(usePinning);
19
+ };
20
+
21
+ /**
22
+ * Retrieves the current state of SSL pinning usage.
23
+ *
24
+ * @returns A promise that resolves to a boolean indicating whether SSL pinning is being used.
25
+ */
26
+ exports.setUseSSLPinning = setUseSSLPinning;
27
+ const getUseSSLPinning = async () => {
28
+ return await UseSslPinning.getUseSSLPinning();
29
+ };
30
+
31
+ /**
32
+ * Initializes SSL pinning with the provided configuration.
33
+ *
34
+ * @param {string} configJsonString - The JSON string containing the SSL pinning configuration.
35
+ * @returns {Promise<any>} A promise that resolves when the SSL pinning is initialized.
36
+ */
37
+ exports.getUseSSLPinning = getUseSSLPinning;
38
+ const initializeSslPinning = async configJsonString => {
39
+ return await UseSslPinning.initializeSslPinning(configJsonString);
40
+ };
41
+ exports.initializeSslPinning = initializeSslPinning;
42
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["_reactNative","require","LINKING_ERROR","Platform","select","ios","default","UseSslPinning","NativeModules","Proxy","get","Error","setUseSSLPinning","usePinning","exports","getUseSSLPinning","initializeSslPinning","configJsonString"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;;;;;AAAA,IAAAA,YAAA,GAAAC,OAAA;AAEA,MAAMC,aAAa,GACjB,mFAAmF,GACnFC,qBAAQ,CAACC,MAAM,CAAC;EAAEC,GAAG,EAAE,gCAAgC;EAAEC,OAAO,EAAE;AAAG,CAAC,CAAC,GACvE,sDAAsD,GACtD,+BAA+B;AAEjC,MAAMC,aAAa,GAAGC,0BAAa,CAACD,aAAa,GAC7CC,0BAAa,CAACD,aAAa,GAC3B,IAAIE,KAAK,CACP,CAAC,CAAC,EACF;EACEC,GAAGA,CAAA,EAAG;IACJ,MAAM,IAAIC,KAAK,CAACT,aAAa,CAAC;EAChC;AACF,CACF,CAAC;AAEE,MAAMU,gBAAgB,GAAIC,UAAmB,IAAW;EAC7DN,aAAa,CAACK,gBAAgB,CAACC,UAAU,CAAC;AAC5C,CAAC;;AAED;AACA;AACA;AACA;AACA;AAJAC,OAAA,CAAAF,gBAAA,GAAAA,gBAAA;AAKO,MAAMG,gBAAgB,GAAG,MAAAA,CAAA,KAA8B;EAC5D,OAAO,MAAMR,aAAa,CAACQ,gBAAgB,CAAC,CAAC;AAC/C,CAAC;;AAED;AACA;AACA;AACA;AACA;AACA;AALAD,OAAA,CAAAC,gBAAA,GAAAA,gBAAA;AAMO,MAAMC,oBAAoB,GAAG,MAClCC,gBAAwB,IACP;EACjB,OAAO,MAAMV,aAAa,CAACS,oBAAoB,CAACC,gBAAgB,CAAC;AACnE,CAAC;AAACH,OAAA,CAAAE,oBAAA,GAAAA,oBAAA","ignoreList":[]}
@@ -0,0 +1,33 @@
1
+ import { NativeModules, Platform } from 'react-native';
2
+ const LINKING_ERROR = `The package 'react-native-ssl-manager' doesn't seem to be linked. Make sure: \n\n` + Platform.select({
3
+ ios: "- You have run 'pod install'\n",
4
+ default: ''
5
+ }) + '- You rebuilt the app after installing the package\n' + '- You are not using Expo Go\n';
6
+ const UseSslPinning = NativeModules.UseSslPinning ? NativeModules.UseSslPinning : new Proxy({}, {
7
+ get() {
8
+ throw new Error(LINKING_ERROR);
9
+ }
10
+ });
11
+ export const setUseSSLPinning = usePinning => {
12
+ UseSslPinning.setUseSSLPinning(usePinning);
13
+ };
14
+
15
+ /**
16
+ * Retrieves the current state of SSL pinning usage.
17
+ *
18
+ * @returns A promise that resolves to a boolean indicating whether SSL pinning is being used.
19
+ */
20
+ export const getUseSSLPinning = async () => {
21
+ return await UseSslPinning.getUseSSLPinning();
22
+ };
23
+
24
+ /**
25
+ * Initializes SSL pinning with the provided configuration.
26
+ *
27
+ * @param {string} configJsonString - The JSON string containing the SSL pinning configuration.
28
+ * @returns {Promise<any>} A promise that resolves when the SSL pinning is initialized.
29
+ */
30
+ export const initializeSslPinning = async configJsonString => {
31
+ return await UseSslPinning.initializeSslPinning(configJsonString);
32
+ };
33
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["NativeModules","Platform","LINKING_ERROR","select","ios","default","UseSslPinning","Proxy","get","Error","setUseSSLPinning","usePinning","getUseSSLPinning","initializeSslPinning","configJsonString"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":"AAAA,SAASA,aAAa,EAAEC,QAAQ,QAAQ,cAAc;AAEtD,MAAMC,aAAa,GACjB,mFAAmF,GACnFD,QAAQ,CAACE,MAAM,CAAC;EAAEC,GAAG,EAAE,gCAAgC;EAAEC,OAAO,EAAE;AAAG,CAAC,CAAC,GACvE,sDAAsD,GACtD,+BAA+B;AAEjC,MAAMC,aAAa,GAAGN,aAAa,CAACM,aAAa,GAC7CN,aAAa,CAACM,aAAa,GAC3B,IAAIC,KAAK,CACP,CAAC,CAAC,EACF;EACEC,GAAGA,CAAA,EAAG;IACJ,MAAM,IAAIC,KAAK,CAACP,aAAa,CAAC;EAChC;AACF,CACF,CAAC;AAEL,OAAO,MAAMQ,gBAAgB,GAAIC,UAAmB,IAAW;EAC7DL,aAAa,CAACI,gBAAgB,CAACC,UAAU,CAAC;AAC5C,CAAC;;AAED;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMC,gBAAgB,GAAG,MAAAA,CAAA,KAA8B;EAC5D,OAAO,MAAMN,aAAa,CAACM,gBAAgB,CAAC,CAAC;AAC/C,CAAC;;AAED;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMC,oBAAoB,GAAG,MAClCC,gBAAwB,IACP;EACjB,OAAO,MAAMR,aAAa,CAACO,oBAAoB,CAACC,gBAAgB,CAAC;AACnE,CAAC","ignoreList":[]}
@@ -0,0 +1,15 @@
1
+ export declare const setUseSSLPinning: (usePinning: boolean) => void;
2
+ /**
3
+ * Retrieves the current state of SSL pinning usage.
4
+ *
5
+ * @returns A promise that resolves to a boolean indicating whether SSL pinning is being used.
6
+ */
7
+ export declare const getUseSSLPinning: () => Promise<boolean>;
8
+ /**
9
+ * Initializes SSL pinning with the provided configuration.
10
+ *
11
+ * @param {string} configJsonString - The JSON string containing the SSL pinning configuration.
12
+ * @returns {Promise<any>} A promise that resolves when the SSL pinning is initialized.
13
+ */
14
+ export declare const initializeSslPinning: (configJsonString: string) => Promise<any>;
15
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAmBA,eAAO,MAAM,gBAAgB,eAAgB,OAAO,KAAG,IAEtD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,QAAa,QAAQ,OAAO,CAExD,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,qBACb,MAAM,KACvB,QAAQ,GAAG,CAEb,CAAC"}
package/package.json ADDED
@@ -0,0 +1,172 @@
1
+ {
2
+ "name": "react-native-ssl-manager",
3
+ "version": "1.0.0",
4
+ "description": "React Native SSL Pinning provides seamless SSL certificate pinning integration for enhanced network security in React Native apps. This module enables developers to easily implement and manage certificate pinning, protecting applications against man-in-the-middle (MITM) attacks. With dynamic configuration options and the ability to toggle SSL pinning, it's particularly useful for development and testing scenarios.",
5
+ "main": "lib/commonjs/index",
6
+ "module": "lib/module/index",
7
+ "types": "lib/typescript/src/index.d.ts",
8
+ "react-native": {
9
+ "android": {
10
+ "source": "android/src/main/java/com/usesslpinning/UseSslPinningPackage.kt"
11
+ },
12
+ "ios": {
13
+ "podspec": "react-native-ssl-manager.podspec"
14
+ }
15
+ },
16
+ "source": "src/index",
17
+ "files": [
18
+ "src",
19
+ "lib",
20
+ "!**/__tests__",
21
+ "!**/__fixtures__",
22
+ "!**/__mocks__",
23
+ "android",
24
+ "ios",
25
+ "cpp",
26
+ "*.podspec",
27
+ "!lib/typescript/example",
28
+ "!ios/build",
29
+ "!android/build",
30
+ "!android/gradle",
31
+ "!android/gradlew",
32
+ "!android/gradlew.bat",
33
+ "!android/local.properties",
34
+ "!**/.*"
35
+ ],
36
+ "scripts": {
37
+ "example": "yarn workspace react-native-ssl-manager-example",
38
+ "test": "jest",
39
+ "typecheck": "tsc --noEmit",
40
+ "lint": "eslint \"**/*.{js,ts,tsx}\"",
41
+ "clean": "del-cli android/build example/android/build example/android/app/build example/ios/build lib",
42
+ "prepare": "bob build",
43
+ "release": "release-it"
44
+ },
45
+ "keywords": [
46
+ "react-native",
47
+ "ios",
48
+ "android"
49
+ ],
50
+ "repository": {
51
+ "type": "git",
52
+ "url": "git+https://github.com/huytdps13400/react-native-ssl-manager.git"
53
+ },
54
+ "author": "Trần Đình Huy <kingonwork@gmail.com> (https://github.com/huytdps13400)",
55
+ "license": "MIT",
56
+ "bugs": {
57
+ "url": "https://github.com/huytdps13400/react-native-ssl-manager/issues"
58
+ },
59
+ "homepage": "https://github.com/huytdps13400/react-native-ssl-manager#readme",
60
+ "publishConfig": {
61
+ "registry": "https://registry.npmjs.org/",
62
+ "access": "public"
63
+ },
64
+ "devDependencies": {
65
+ "@commitlint/config-conventional": "^17.0.2",
66
+ "@evilmartians/lefthook": "^1.5.0",
67
+ "@react-native/eslint-config": "^0.73.1",
68
+ "@release-it/conventional-changelog": "^5.0.0",
69
+ "@types/jest": "^29.5.5",
70
+ "@types/react": "^18.2.44",
71
+ "commitlint": "^17.0.2",
72
+ "del-cli": "^5.1.0",
73
+ "eslint": "^8.51.0",
74
+ "eslint-config-prettier": "^9.0.0",
75
+ "eslint-plugin-prettier": "^5.0.1",
76
+ "jest": "^29.7.0",
77
+ "prettier": "^3.0.3",
78
+ "react": "18.2.0",
79
+ "react-native": "0.73.6",
80
+ "react-native-builder-bob": "^0.20.0",
81
+ "release-it": "^15.0.0",
82
+ "turbo": "^1.10.7",
83
+ "typescript": "5.1.x"
84
+ },
85
+ "resolutions": {
86
+ "@types/react": "^18.2.44"
87
+ },
88
+ "peerDependencies": {
89
+ "react": "*",
90
+ "react-native": "*"
91
+ },
92
+ "workspaces": [
93
+ "example"
94
+ ],
95
+ "packageManager": "yarn@3.6.1",
96
+ "jest": {
97
+ "preset": "react-native",
98
+ "modulePathIgnorePatterns": [
99
+ "<rootDir>/example/node_modules",
100
+ "<rootDir>/lib/"
101
+ ]
102
+ },
103
+ "commitlint": {
104
+ "extends": [
105
+ "@commitlint/config-conventional"
106
+ ]
107
+ },
108
+ "release-it": {
109
+ "git": {
110
+ "commitMessage": "chore: release ${version}",
111
+ "tagName": "v${version}"
112
+ },
113
+ "npm": {
114
+ "publish": true
115
+ },
116
+ "github": {
117
+ "release": true
118
+ },
119
+ "plugins": {
120
+ "@release-it/conventional-changelog": {
121
+ "preset": "angular"
122
+ }
123
+ }
124
+ },
125
+ "eslintConfig": {
126
+ "root": true,
127
+ "extends": [
128
+ "@react-native",
129
+ "prettier"
130
+ ],
131
+ "rules": {
132
+ "prettier/prettier": [
133
+ "error",
134
+ {
135
+ "quoteProps": "consistent",
136
+ "singleQuote": true,
137
+ "tabWidth": 2,
138
+ "trailingComma": "es5",
139
+ "useTabs": false
140
+ }
141
+ ]
142
+ }
143
+ },
144
+ "eslintIgnore": [
145
+ "node_modules/",
146
+ "lib/"
147
+ ],
148
+ "prettier": {
149
+ "quoteProps": "consistent",
150
+ "singleQuote": true,
151
+ "tabWidth": 2,
152
+ "trailingComma": "es5",
153
+ "useTabs": false
154
+ },
155
+ "react-native-builder-bob": {
156
+ "source": "src",
157
+ "output": "lib",
158
+ "targets": [
159
+ "commonjs",
160
+ "module",
161
+ [
162
+ "typescript",
163
+ {
164
+ "project": "tsconfig.build.json"
165
+ }
166
+ ]
167
+ ]
168
+ },
169
+ "dependencies": {
170
+ "postinstall": "^0.10.3"
171
+ }
172
+ }
@@ -0,0 +1,43 @@
1
+ require "json"
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, "package.json")))
4
+ folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32'
5
+
6
+ Pod::Spec.new do |s|
7
+ s.name = "react-native-ssl-manager"
8
+ s.version = package["version"]
9
+ s.summary = package["description"]
10
+ s.homepage = package["homepage"]
11
+ s.license = package["license"]
12
+ s.authors = package["author"]
13
+
14
+ s.platforms = { :ios => min_ios_version_supported }
15
+ s.source = { :git => "https://github.com/huytdps13400/react-native-ssl-manager.git", :tag => "#{s.version}" }
16
+
17
+ s.source_files = "ios/**/*.{h,m,mm,swift}"
18
+
19
+ # Use install_modules_dependencies helper to install the dependencies if React Native version >=0.71.0.
20
+ # See https://github.com/facebook/react-native/blob/febf6b7f33fdb4904669f99d795eba4c0f95d7bf/scripts/cocoapods/new_architecture.rb#L79.
21
+ if respond_to?(:install_modules_dependencies, true)
22
+ install_modules_dependencies(s)
23
+ else
24
+ s.dependency "React-Core"
25
+
26
+ # Don't install the dependencies when we run `pod install` in the old architecture.
27
+ if ENV['RCT_NEW_ARCH_ENABLED'] == '1' then
28
+ s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1"
29
+ s.pod_target_xcconfig = {
30
+ "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"",
31
+ "OTHER_CPLUSPLUSFLAGS" => "-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1",
32
+ "CLANG_CXX_LANGUAGE_STANDARD" => "c++17"
33
+ }
34
+ s.dependency "React-Codegen"
35
+ s.dependency "RCT-Folly"
36
+ s.dependency "RCTRequired"
37
+ s.dependency "RCTTypeSafety"
38
+ s.dependency "ReactCommon/turbomodule/core"
39
+ end
40
+ end
41
+
42
+ s.dependency "TrustKit"
43
+ end
package/src/index.tsx ADDED
@@ -0,0 +1,43 @@
1
+ import { NativeModules, Platform } from 'react-native';
2
+
3
+ const LINKING_ERROR =
4
+ `The package 'react-native-ssl-manager' doesn't seem to be linked. Make sure: \n\n` +
5
+ Platform.select({ ios: "- You have run 'pod install'\n", default: '' }) +
6
+ '- You rebuilt the app after installing the package\n' +
7
+ '- You are not using Expo Go\n';
8
+
9
+ const UseSslPinning = NativeModules.UseSslPinning
10
+ ? NativeModules.UseSslPinning
11
+ : new Proxy(
12
+ {},
13
+ {
14
+ get() {
15
+ throw new Error(LINKING_ERROR);
16
+ },
17
+ }
18
+ );
19
+
20
+ export const setUseSSLPinning = (usePinning: boolean): void => {
21
+ UseSslPinning.setUseSSLPinning(usePinning);
22
+ };
23
+
24
+ /**
25
+ * Retrieves the current state of SSL pinning usage.
26
+ *
27
+ * @returns A promise that resolves to a boolean indicating whether SSL pinning is being used.
28
+ */
29
+ export const getUseSSLPinning = async (): Promise<boolean> => {
30
+ return await UseSslPinning.getUseSSLPinning();
31
+ };
32
+
33
+ /**
34
+ * Initializes SSL pinning with the provided configuration.
35
+ *
36
+ * @param {string} configJsonString - The JSON string containing the SSL pinning configuration.
37
+ * @returns {Promise<any>} A promise that resolves when the SSL pinning is initialized.
38
+ */
39
+ export const initializeSslPinning = async (
40
+ configJsonString: string
41
+ ): Promise<any> => {
42
+ return await UseSslPinning.initializeSslPinning(configJsonString);
43
+ };