react-native-ssl-manager 1.0.0 → 1.0.1
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/README.md +319 -58
- package/android/build.gradle +23 -1
- package/android/src/main/java/com/usesslpinning/SslPinningFactory.kt +94 -0
- package/android/src/main/java/com/usesslpinning/UseSslPinningModuleImpl.kt +34 -0
- package/android/src/newarch/com/usesslpinning/UseSslPinningModule.kt +26 -0
- package/android/src/newarch/com/usesslpinning/UseSslPinningPackage.kt +36 -0
- package/android/src/oldarch/com/usesslpinning/UseSslPinningModule.kt +32 -0
- package/android/src/{main/java → oldarch}/com/usesslpinning/UseSslPinningPackage.kt +0 -1
- package/android/ssl-pinning-setup.gradle +148 -0
- package/expo-module.config.json +10 -0
- package/ios/SharedLogic.swift +247 -0
- package/ios/UseSslPinning.h +5 -0
- package/ios/{UseSslPinning.mm → UseSslPinningModule.mm} +9 -6
- package/ios/UseSslPinningModule.swift +65 -0
- package/lib/NativeUseSslPinning.d.ts +8 -0
- package/lib/NativeUseSslPinning.d.ts.map +1 -0
- package/lib/NativeUseSslPinning.js +4 -0
- package/lib/UseSslPinning.types.d.ts +17 -0
- package/lib/UseSslPinning.types.d.ts.map +1 -0
- package/lib/UseSslPinning.types.js +2 -0
- package/lib/index.d.ts +15 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +58 -0
- package/package.json +82 -39
- package/react-native-ssl-manager.podspec +87 -38
- package/react-native.config.js +34 -0
- package/scripts/build.sh +52 -0
- package/src/NativeUseSslPinning.ts +9 -0
- package/src/UseSslPinning.types.ts +17 -0
- package/src/index.tsx +53 -33
- package/android/src/main/java/com/usesslpinning/UseSslPinningFactory.kt +0 -50
- package/android/src/main/java/com/usesslpinning/UseSslPinningModule.kt +0 -45
- package/ios/UseSslPinning-Bridging-Header.h +0 -2
- package/ios/UseSslPinning.swift +0 -169
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
package com.usesslpinning
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
5
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
6
|
+
import com.facebook.react.bridge.ReactMethod
|
|
7
|
+
import com.facebook.react.bridge.Promise
|
|
8
|
+
import com.facebook.react.modules.network.OkHttpClientProvider
|
|
9
|
+
import com.facebook.react.module.annotations.ReactModule
|
|
10
|
+
|
|
11
|
+
@ReactModule(name = UseSslPinningModuleImpl.NAME)
|
|
12
|
+
class UseSslPinningModule(reactContext: ReactApplicationContext) :
|
|
13
|
+
ReactContextBaseJavaModule(reactContext) {
|
|
14
|
+
|
|
15
|
+
init {
|
|
16
|
+
UseSslPinningModuleImpl.initialize(reactContext)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
override fun getName(): String {
|
|
20
|
+
return UseSslPinningModuleImpl.NAME
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@ReactMethod
|
|
24
|
+
fun setUseSSLPinning(usePinning: Boolean, promise: Promise) {
|
|
25
|
+
UseSslPinningModuleImpl.setUseSSLPinning(reactApplicationContext, usePinning, promise)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@ReactMethod
|
|
29
|
+
fun getUseSSLPinning(promise: Promise) {
|
|
30
|
+
UseSslPinningModuleImpl.getUseSSLPinning(reactApplicationContext, promise)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -5,7 +5,6 @@ import com.facebook.react.bridge.NativeModule
|
|
|
5
5
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
6
|
import com.facebook.react.uimanager.ViewManager
|
|
7
7
|
|
|
8
|
-
|
|
9
8
|
class UseSslPinningPackage : ReactPackage {
|
|
10
9
|
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
|
|
11
10
|
return listOf(UseSslPinningModule(reactContext))
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
def findSslConfigFile() {
|
|
2
|
+
def candidates = [
|
|
3
|
+
file("${project.rootDir}/../ssl_config.json"), // repo root (android/ là thư mục con)
|
|
4
|
+
file("${project.rootDir}/ssl_config.json"), // android root
|
|
5
|
+
file("${projectDir}/../../ssl_config.json"), // monorepo fallback
|
|
6
|
+
file("${projectDir}/../ssl_config.json")
|
|
7
|
+
]
|
|
8
|
+
for (f in candidates) {
|
|
9
|
+
if (f.exists()) return f
|
|
10
|
+
}
|
|
11
|
+
return null
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
def ensureAssetsDir() {
|
|
15
|
+
def d = file("${projectDir}/src/main/assets")
|
|
16
|
+
if (!d.exists()) d.mkdirs()
|
|
17
|
+
return d
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
def assetsDir = file("${projectDir}/src/main/assets")
|
|
21
|
+
def destFile = file("${projectDir}/src/main/assets/ssl_config.json")
|
|
22
|
+
|
|
23
|
+
plugins.withId('com.android.application') {
|
|
24
|
+
|
|
25
|
+
// ===== AGP 7/8: androidComponents DSL =====
|
|
26
|
+
if (project.extensions.findByName("androidComponents") != null) {
|
|
27
|
+
androidComponents {
|
|
28
|
+
onVariants(selector().all()) { variant -> // <-- Sửa chỗ này
|
|
29
|
+
def vName = variant.name
|
|
30
|
+
def vNameCap = vName.substring(0,1).toUpperCase() + vName.substring(1)
|
|
31
|
+
def taskName = "copySslConfig${vNameCap}"
|
|
32
|
+
|
|
33
|
+
tasks.register(taskName, Copy) {
|
|
34
|
+
group = "SSL Pinning"
|
|
35
|
+
description = "Copy ssl_config.json to assets for ${vName}"
|
|
36
|
+
|
|
37
|
+
def sourceFile = findSslConfigFile()
|
|
38
|
+
|
|
39
|
+
onlyIf {
|
|
40
|
+
if (sourceFile) {
|
|
41
|
+
println "📋 SSL Config found: ${sourceFile.absolutePath}"
|
|
42
|
+
true
|
|
43
|
+
} else {
|
|
44
|
+
println "⚠️ SSL Config not found in expected locations"
|
|
45
|
+
false
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
doFirst {
|
|
50
|
+
ensureAssetsDir()
|
|
51
|
+
println "🔄 Copying SSL config for variant: ${vName}"
|
|
52
|
+
println "📂 To: ${assetsDir}"
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
from { sourceFile }
|
|
56
|
+
into assetsDir
|
|
57
|
+
|
|
58
|
+
doLast {
|
|
59
|
+
if (destFile.exists()) {
|
|
60
|
+
println "✅ SSL config copied: ${destFile} (${destFile.length()} bytes)"
|
|
61
|
+
} else {
|
|
62
|
+
println "❌ SSL config copy failed"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Hook chắc chắn cho assemble/install/mergeAssets
|
|
68
|
+
tasks.matching { it.name == "merge${vNameCap}Assets" }.configureEach { dependsOn taskName }
|
|
69
|
+
tasks.matching { it.name == "install${vNameCap}" }.configureEach { dependsOn taskName }
|
|
70
|
+
tasks.matching { it.name == "assemble${vNameCap}" }.configureEach { dependsOn taskName }
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
} else {
|
|
75
|
+
// ===== Fallback: API cũ applicationVariants =====
|
|
76
|
+
afterEvaluate {
|
|
77
|
+
android.applicationVariants.all { variant ->
|
|
78
|
+
def bt = variant.buildType.name
|
|
79
|
+
def fl = variant.flavorName ?: ""
|
|
80
|
+
def vNameCap = (fl ? "${fl.capitalize()}${bt.capitalize()}" : bt.capitalize())
|
|
81
|
+
def taskName = "copySslConfig${vNameCap}"
|
|
82
|
+
|
|
83
|
+
def copyTask = tasks.create(taskName, Copy) {
|
|
84
|
+
group = "SSL Pinning"
|
|
85
|
+
description = "Copy ssl_config.json to assets for ${vNameCap}"
|
|
86
|
+
|
|
87
|
+
def sourceFile = findSslConfigFile()
|
|
88
|
+
|
|
89
|
+
onlyIf {
|
|
90
|
+
if (sourceFile) {
|
|
91
|
+
println "📋 SSL Config found: ${sourceFile.absolutePath}"
|
|
92
|
+
true
|
|
93
|
+
} else {
|
|
94
|
+
println "⚠️ SSL Config not found in expected locations"
|
|
95
|
+
false
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
doFirst {
|
|
100
|
+
ensureAssetsDir()
|
|
101
|
+
println "🔄 Copying SSL config for variant: ${vNameCap}"
|
|
102
|
+
println "📂 To: ${assetsDir}"
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
from { sourceFile }
|
|
106
|
+
into assetsDir
|
|
107
|
+
|
|
108
|
+
doLast {
|
|
109
|
+
if (destFile.exists()) {
|
|
110
|
+
println "✅ SSL config copied: ${destFile} (${destFile.length()} bytes)"
|
|
111
|
+
} else {
|
|
112
|
+
println "❌ SSL config copy failed"
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
if (variant.hasProperty("mergeAssetsProvider")) {
|
|
119
|
+
variant.mergeAssetsProvider.configure { dependsOn copyTask }
|
|
120
|
+
} else {
|
|
121
|
+
tasks.named("merge${vNameCap}Assets").configure { dependsOn copyTask }
|
|
122
|
+
}
|
|
123
|
+
} catch (ignored) { }
|
|
124
|
+
try { tasks.named("install${vNameCap}").configure { dependsOn copyTask } } catch (ignored) { }
|
|
125
|
+
try { tasks.named("assemble${vNameCap}").configure { dependsOn copyTask } } catch (ignored) { }
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ===== Utility =====
|
|
131
|
+
tasks.register("checkSslConfig") {
|
|
132
|
+
group = "SSL Pinning"
|
|
133
|
+
description = "Check SSL config file status"
|
|
134
|
+
doLast {
|
|
135
|
+
def source = findSslConfigFile()
|
|
136
|
+
println "\n📋 SSL Pinning Configuration Status:"
|
|
137
|
+
println " Source config: " + (source ? "✅ Found at ${source.absolutePath}" : "❌ Not found")
|
|
138
|
+
println " Assets config: " + (destFile.exists() ? "✅ Found at ${destFile.absolutePath}" : "❌ Not found")
|
|
139
|
+
if (!source) {
|
|
140
|
+
println "💡 Create ssl_config.json tại repo root (ngang 'android/') hoặc trong 'android/'."
|
|
141
|
+
} else if (!destFile.exists()) {
|
|
142
|
+
println "💡 Run: ./gradlew copySslConfigDebug (hoặc biến thể tương ứng)"
|
|
143
|
+
} else {
|
|
144
|
+
println "✅ SSL config is properly setup"
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import TrustKit
|
|
3
|
+
import TrustKit.TSKPinningValidator
|
|
4
|
+
import TrustKit.TSKPinningValidatorCallback
|
|
5
|
+
|
|
6
|
+
enum SSLPinningError: Error {
|
|
7
|
+
case invalidConfiguration
|
|
8
|
+
case invalidPinConfiguration(domain: String)
|
|
9
|
+
|
|
10
|
+
var message: String {
|
|
11
|
+
switch self {
|
|
12
|
+
case .invalidConfiguration:
|
|
13
|
+
return "Invalid SSL pinning configuration format"
|
|
14
|
+
case .invalidPinConfiguration(let domain):
|
|
15
|
+
return "Invalid pin configuration for domain: \(domain)"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Shared logic for SSL pinning functionality
|
|
22
|
+
* Contains common methods used by both CLI and Expo modules
|
|
23
|
+
*/
|
|
24
|
+
@objc class SharedLogic: NSObject {
|
|
25
|
+
static var sharedTrustKit: TrustKit?
|
|
26
|
+
private static let useSSLPinningKey = "useSSLPinning"
|
|
27
|
+
private static let userDefaults = UserDefaults.standard
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Set SSL pinning enabled/disabled state
|
|
31
|
+
* Auto-initialize SSL pinning when enabled
|
|
32
|
+
*/
|
|
33
|
+
@objc static func setUseSSLPinning(_ usePinning: Bool) {
|
|
34
|
+
userDefaults.set(usePinning, forKey: useSSLPinningKey)
|
|
35
|
+
userDefaults.synchronize()
|
|
36
|
+
|
|
37
|
+
// Auto-initialize SSL pinning when enabled
|
|
38
|
+
if usePinning {
|
|
39
|
+
do {
|
|
40
|
+
let _ = try initializeSslPinningFromBundle()
|
|
41
|
+
|
|
42
|
+
} catch {
|
|
43
|
+
|
|
44
|
+
}
|
|
45
|
+
} else {
|
|
46
|
+
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Get current SSL pinning state
|
|
52
|
+
*/
|
|
53
|
+
@objc static func getUseSSLPinning() -> Bool {
|
|
54
|
+
// Check if key exists, if not return default true
|
|
55
|
+
if userDefaults.object(forKey: useSSLPinningKey) == nil {
|
|
56
|
+
|
|
57
|
+
return true // Default to enabled
|
|
58
|
+
}
|
|
59
|
+
let value = userDefaults.bool(forKey: useSSLPinningKey)
|
|
60
|
+
|
|
61
|
+
return value
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Clean JSON string from various formatting issues
|
|
66
|
+
*/
|
|
67
|
+
static func cleanJsonString(_ jsonString: String) -> String {
|
|
68
|
+
var cleaned = jsonString
|
|
69
|
+
.replacingOccurrences(of: "\n", with: "")
|
|
70
|
+
.replacingOccurrences(of: "| ", with: "")
|
|
71
|
+
.replacingOccurrences(of: "\\ ", with: "")
|
|
72
|
+
.replacingOccurrences(of: "\\\"", with: "\"")
|
|
73
|
+
|
|
74
|
+
// Remove any remaining backslashes before quotes
|
|
75
|
+
cleaned = cleaned.replacingOccurrences(of: "\\(?!\")", with: "")
|
|
76
|
+
|
|
77
|
+
// Clean up any double spaces
|
|
78
|
+
cleaned = cleaned.replacingOccurrences(of: " ", with: " ")
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
return cleaned
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Validate and clean pins for a domain
|
|
88
|
+
*/
|
|
89
|
+
static func validateAndCleanPins(_ pins: [String], for domain: String) throws -> [String] {
|
|
90
|
+
guard !pins.isEmpty else {
|
|
91
|
+
|
|
92
|
+
throw SSLPinningError.invalidPinConfiguration(domain: domain)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return try pins.map { pin -> String in
|
|
96
|
+
var cleanPin = pin.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
// Verify pin format
|
|
100
|
+
guard cleanPin.starts(with: "sha256/") else {
|
|
101
|
+
|
|
102
|
+
throw SSLPinningError.invalidPinConfiguration(domain: domain)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Remove sha256/ prefix for TrustKit
|
|
106
|
+
cleanPin = cleanPin.replacingOccurrences(of: "sha256/", with: "")
|
|
107
|
+
|
|
108
|
+
// Check base64 format
|
|
109
|
+
guard cleanPin.range(of: "^[A-Za-z0-9+/=]+$", options: .regularExpression) != nil else {
|
|
110
|
+
|
|
111
|
+
throw SSLPinningError.invalidPinConfiguration(domain: domain)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Check length - SHA256 pins should be 44 characters when base64 encoded
|
|
115
|
+
guard cleanPin.count == 44 else {
|
|
116
|
+
|
|
117
|
+
throw SSLPinningError.invalidPinConfiguration(domain: domain)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
return cleanPin
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Initialize SSL pinning from bundle (auto-read ssl_config.json)
|
|
127
|
+
*/
|
|
128
|
+
@objc static func initializeSslPinningFromBundle() throws -> [String: Any] {
|
|
129
|
+
// Try to read ssl_config.json from main bundle (auto-copied by script phase)
|
|
130
|
+
guard let path = Bundle.main.path(forResource: "ssl_config", ofType: "json"),
|
|
131
|
+
let configData = NSData(contentsOfFile: path),
|
|
132
|
+
let configJsonString = String(data: configData as Data, encoding: .utf8) else {
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
throw SSLPinningError.invalidConfiguration
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
return try initializeSslPinning(configJsonString)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Initialize SSL pinning with TrustKit
|
|
145
|
+
* Ported from original working code with minimal modifications
|
|
146
|
+
*/
|
|
147
|
+
static func initializeSslPinning(_ configJsonString: String) throws -> [String: Any] {
|
|
148
|
+
// Check if SSL pinning is enabled
|
|
149
|
+
let isSSLPinningEnabled = getUseSSLPinning()
|
|
150
|
+
|
|
151
|
+
if isSSLPinningEnabled {
|
|
152
|
+
do {
|
|
153
|
+
// Clean JSON first
|
|
154
|
+
let cleanedJson = cleanJsonString(configJsonString)
|
|
155
|
+
|
|
156
|
+
// Parse JSON configuration
|
|
157
|
+
guard let jsonData = cleanedJson.data(using: .utf8),
|
|
158
|
+
let config = try JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: Any],
|
|
159
|
+
let sha256Keys = config["sha256Keys"] as? [String: [String]] else {
|
|
160
|
+
throw SSLPinningError.invalidConfiguration
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Build pinned domains configuration
|
|
164
|
+
var pinnedDomains: [String: Any] = [:]
|
|
165
|
+
|
|
166
|
+
// Process each domain and its pins from JSON
|
|
167
|
+
for (domain, pins) in sha256Keys {
|
|
168
|
+
let cleanedPins = try validateAndCleanPins(pins, for: domain)
|
|
169
|
+
|
|
170
|
+
pinnedDomains[domain] = [
|
|
171
|
+
kTSKIncludeSubdomains: true,
|
|
172
|
+
kTSKEnforcePinning: true,
|
|
173
|
+
kTSKDisableDefaultReportUri: true,
|
|
174
|
+
kTSKPublicKeyHashes: cleanedPins
|
|
175
|
+
]
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
let trustKitConfig: [String: Any] = [
|
|
179
|
+
kTSKSwizzleNetworkDelegates: true,
|
|
180
|
+
kTSKPinnedDomains: pinnedDomains
|
|
181
|
+
]
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
// Initialize TrustKit synchronously to catch initialization errors
|
|
186
|
+
do {
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
// Initialize TrustKit with the configuration
|
|
191
|
+
|
|
192
|
+
TrustKit.initSharedInstance(withConfiguration: trustKitConfig)
|
|
193
|
+
SharedLogic.sharedTrustKit = TrustKit.sharedInstance()
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
// Verify TrustKit is properly initialized
|
|
198
|
+
if let trustKit = SharedLogic.sharedTrustKit {
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
// Set up validation callback
|
|
203
|
+
trustKit.pinningValidatorCallback = { result, notedHostname, policy in
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
switch result.finalTrustDecision {
|
|
207
|
+
case .shouldBlockConnection:
|
|
208
|
+
break
|
|
209
|
+
case .shouldAllowConnection:
|
|
210
|
+
break
|
|
211
|
+
default:
|
|
212
|
+
break
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
} else {
|
|
218
|
+
|
|
219
|
+
throw SSLPinningError.invalidConfiguration
|
|
220
|
+
}
|
|
221
|
+
} catch {
|
|
222
|
+
|
|
223
|
+
throw error
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return [
|
|
227
|
+
"message": "SSL Pinning initialized successfully",
|
|
228
|
+
"domains": Array(pinnedDomains.keys)
|
|
229
|
+
]
|
|
230
|
+
|
|
231
|
+
} catch let error as SSLPinningError {
|
|
232
|
+
|
|
233
|
+
throw error
|
|
234
|
+
} catch {
|
|
235
|
+
|
|
236
|
+
throw error
|
|
237
|
+
}
|
|
238
|
+
} else {
|
|
239
|
+
|
|
240
|
+
return [
|
|
241
|
+
"message": "SSL Pinning is disabled",
|
|
242
|
+
"domains": [],
|
|
243
|
+
"isSSLPinningEnabled": isSSLPinningEnabled
|
|
244
|
+
]
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
#import <React/RCTBridgeModule.h>
|
|
2
2
|
|
|
3
|
+
#if RCT_NEW_ARCH_ENABLED
|
|
4
|
+
// New Architecture - TurboModule will be handled by Swift
|
|
5
|
+
#else
|
|
6
|
+
// Legacy Architecture - Bridge exports for Swift module
|
|
3
7
|
@interface RCT_EXTERN_MODULE(UseSslPinning, NSObject)
|
|
4
8
|
|
|
5
|
-
RCT_EXTERN_METHOD(setUseSSLPinning:(BOOL)usePinning
|
|
6
|
-
|
|
7
|
-
RCT_EXTERN_METHOD(getUseSSLPinning:(RCTPromiseResolveBlock)resolve
|
|
9
|
+
RCT_EXTERN_METHOD(setUseSSLPinning:(BOOL)usePinning
|
|
10
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
8
11
|
rejecter:(RCTPromiseRejectBlock)reject)
|
|
9
12
|
|
|
10
|
-
RCT_EXTERN_METHOD(
|
|
11
|
-
resolver:(RCTPromiseResolveBlock)resolve
|
|
13
|
+
RCT_EXTERN_METHOD(getUseSSLPinning:(RCTPromiseResolveBlock)resolve
|
|
12
14
|
rejecter:(RCTPromiseRejectBlock)reject)
|
|
13
15
|
|
|
14
16
|
+ (BOOL)requiresMainQueueSetup
|
|
15
17
|
{
|
|
16
|
-
|
|
18
|
+
return NO;
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
@end
|
|
22
|
+
#endif
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import React
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* React Native SSL Pinning module - supports both architectures
|
|
6
|
+
* Uses conditional compilation for New Architecture vs Legacy
|
|
7
|
+
*/
|
|
8
|
+
@objc(UseSslPinning)
|
|
9
|
+
class UseSslPinning: NSObject {
|
|
10
|
+
|
|
11
|
+
override init() {
|
|
12
|
+
super.init()
|
|
13
|
+
|
|
14
|
+
// Early initialization - setup SSL pinning if enabled
|
|
15
|
+
let isEnabled = SharedLogic.getUseSSLPinning()
|
|
16
|
+
if isEnabled {
|
|
17
|
+
do {
|
|
18
|
+
let _ = try SharedLogic.initializeSslPinningFromBundle()
|
|
19
|
+
} catch {
|
|
20
|
+
// SSL config not found or invalid - continue silently
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// MARK: - Shared Implementation Methods
|
|
26
|
+
|
|
27
|
+
private func setUseSSLPinningImpl(_ usePinning: Bool, resolve: @escaping RCTPromiseResolveBlock) {
|
|
28
|
+
SharedLogic.setUseSSLPinning(usePinning)
|
|
29
|
+
resolve(nil)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
private func getUseSSLPinningImpl(_ resolve: @escaping RCTPromiseResolveBlock) {
|
|
33
|
+
let usePinning = SharedLogic.getUseSSLPinning()
|
|
34
|
+
resolve(usePinning)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// MARK: - Architecture-specific exports
|
|
38
|
+
|
|
39
|
+
#if RCT_NEW_ARCH_ENABLED
|
|
40
|
+
|
|
41
|
+
// New Architecture (TurboModule) - no @objc needed
|
|
42
|
+
func setUseSSLPinning(_ usePinning: Bool, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
43
|
+
setUseSSLPinningImpl(usePinning, resolve: resolve)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
func getUseSSLPinning(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
47
|
+
getUseSSLPinningImpl(resolve)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
#else
|
|
51
|
+
|
|
52
|
+
// Legacy Architecture (Bridge) - needs @objc
|
|
53
|
+
@objc
|
|
54
|
+
func setUseSSLPinning(_ usePinning: Bool, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
|
|
55
|
+
setUseSSLPinningImpl(usePinning, resolve: resolve)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
@objc
|
|
59
|
+
func getUseSSLPinning(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
|
|
60
|
+
getUseSSLPinningImpl(resolve)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
#endif
|
|
64
|
+
|
|
65
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { TurboModule } from 'react-native';
|
|
2
|
+
export interface Spec extends TurboModule {
|
|
3
|
+
readonly setUseSSLPinning: (usePinning: boolean) => Promise<void>;
|
|
4
|
+
readonly getUseSSLPinning: () => Promise<boolean>;
|
|
5
|
+
}
|
|
6
|
+
declare const _default: Spec;
|
|
7
|
+
export default _default;
|
|
8
|
+
//# sourceMappingURL=NativeUseSslPinning.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NativeUseSslPinning.d.ts","sourceRoot":"","sources":["../src/NativeUseSslPinning.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAGhD,MAAM,WAAW,IAAK,SAAQ,WAAW;IACvC,QAAQ,CAAC,gBAAgB,EAAE,CAAC,UAAU,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAClE,QAAQ,CAAC,gBAAgB,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;CACnD;;AAED,wBAAuE"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSL Pinning Configuration Interface
|
|
3
|
+
* Defines the structure for SSL pinning configuration
|
|
4
|
+
*/
|
|
5
|
+
export interface SslPinningConfig {
|
|
6
|
+
sha256Keys: {
|
|
7
|
+
[domain: string]: string[];
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* SSL Pinning Error Interface
|
|
12
|
+
*/
|
|
13
|
+
export interface SslPinningError extends Error {
|
|
14
|
+
code?: string;
|
|
15
|
+
message: string;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=UseSslPinning.types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"UseSslPinning.types.d.ts","sourceRoot":"","sources":["../src/UseSslPinning.types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE;QACV,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;KAC5B,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,eAAgB,SAAQ,KAAK;IAC5C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB"}
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type { SslPinningConfig, SslPinningError } from './UseSslPinning.types';
|
|
2
|
+
/**
|
|
3
|
+
* Sets whether SSL pinning should be used.
|
|
4
|
+
*
|
|
5
|
+
* @param {boolean} usePinning - Whether to enable SSL pinning
|
|
6
|
+
* @returns {Promise<void>} A promise that resolves when the setting is saved
|
|
7
|
+
*/
|
|
8
|
+
export declare const setUseSSLPinning: (usePinning: boolean) => Promise<void>;
|
|
9
|
+
/**
|
|
10
|
+
* Retrieves the current state of SSL pinning usage.
|
|
11
|
+
*
|
|
12
|
+
* @returns A promise that resolves to a boolean indicating whether SSL pinning is being used.
|
|
13
|
+
*/
|
|
14
|
+
export declare const getUseSSLPinning: () => Promise<boolean>;
|
|
15
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAGA,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AA0C/E;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,eAAgB,OAAO,KAAG,QAAQ,IAAI,CAElE,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,QAAa,QAAQ,OAAO,CAExD,CAAC"}
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Remove unused Platform import since we no longer check OS
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.getUseSSLPinning = exports.setUseSSLPinning = void 0;
|
|
5
|
+
// New Architecture and Legacy Architecture support
|
|
6
|
+
let UseSslPinning;
|
|
7
|
+
try {
|
|
8
|
+
// Try Legacy NativeModules first (more reliable)
|
|
9
|
+
const { NativeModules } = require('react-native');
|
|
10
|
+
// Look for our universal module (works in both CLI and Expo)
|
|
11
|
+
UseSslPinning = NativeModules.UseSslPinning;
|
|
12
|
+
if (UseSslPinning) {
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
// Fallback to TurboModule if available
|
|
16
|
+
try {
|
|
17
|
+
UseSslPinning = require('./NativeUseSslPinning').default;
|
|
18
|
+
}
|
|
19
|
+
catch (turboModuleError) {
|
|
20
|
+
console.log('❌ TurboModule failed:', turboModuleError.message);
|
|
21
|
+
UseSslPinning = null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
console.log('❌ Overall module loading failed:', error.message);
|
|
27
|
+
UseSslPinning = null;
|
|
28
|
+
}
|
|
29
|
+
// Fallback implementation if native module is not available
|
|
30
|
+
if (!UseSslPinning) {
|
|
31
|
+
UseSslPinning = {
|
|
32
|
+
setUseSSLPinning: (_usePinning) => {
|
|
33
|
+
return Promise.resolve();
|
|
34
|
+
},
|
|
35
|
+
getUseSSLPinning: () => {
|
|
36
|
+
return Promise.resolve(true);
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Sets whether SSL pinning should be used.
|
|
42
|
+
*
|
|
43
|
+
* @param {boolean} usePinning - Whether to enable SSL pinning
|
|
44
|
+
* @returns {Promise<void>} A promise that resolves when the setting is saved
|
|
45
|
+
*/
|
|
46
|
+
const setUseSSLPinning = (usePinning) => {
|
|
47
|
+
return UseSslPinning.setUseSSLPinning(usePinning);
|
|
48
|
+
};
|
|
49
|
+
exports.setUseSSLPinning = setUseSSLPinning;
|
|
50
|
+
/**
|
|
51
|
+
* Retrieves the current state of SSL pinning usage.
|
|
52
|
+
*
|
|
53
|
+
* @returns A promise that resolves to a boolean indicating whether SSL pinning is being used.
|
|
54
|
+
*/
|
|
55
|
+
const getUseSSLPinning = async () => {
|
|
56
|
+
return await UseSslPinning.getUseSSLPinning();
|
|
57
|
+
};
|
|
58
|
+
exports.getUseSSLPinning = getUseSSLPinning;
|