react-native-security-suite 0.9.22 → 1.0.0-rc.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 +235 -69
- package/android/build.gradle +11 -0
- package/android/gradle.properties +1 -1
- package/android/src/main/java/com/securitysuite/CryptoConfig.java +158 -0
- package/android/src/main/java/com/securitysuite/CryptoUtils.java +152 -0
- package/android/src/main/java/com/securitysuite/EcdhKeyStore.java +60 -0
- package/android/src/main/java/com/securitysuite/HeaderSanitizer.java +75 -0
- package/android/src/main/java/com/securitysuite/JWSGenerator.java +237 -32
- package/android/src/main/java/com/securitysuite/JwsFetchPayload.java +81 -0
- package/android/src/main/java/com/securitysuite/Obfuscation.java +57 -0
- package/android/src/main/java/com/securitysuite/SecureStorageNative.java +211 -0
- package/android/src/main/java/com/securitysuite/SecureView.java +2 -10
- package/android/src/main/java/com/securitysuite/SecureWindowHelper.java +30 -0
- package/android/src/main/java/com/securitysuite/SecuritySuiteModule.java +310 -102
- package/android/src/main/java/com/securitysuite/Sslpinning.java +219 -106
- package/android/src/main/java/com/securitysuite/security/AppIntegrityChecker.java +133 -0
- package/android/src/main/java/com/securitysuite/security/EmulatorDetector.java +145 -0
- package/android/src/main/java/com/securitysuite/security/RuntimeDetector.java +234 -0
- package/android/src/test/java/com/securitysuite/JWSGeneratorTest.java +153 -0
- package/android/src/test/java/com/securitysuite/SecureStorageNativeTest.java +37 -0
- package/ios/CryptoConfig.swift +124 -0
- package/ios/JWSGenerator.swift +288 -0
- package/ios/JWSGeneratorTests.swift +168 -0
- package/ios/KeychainHelper.swift +104 -0
- package/ios/Obfuscation.swift +42 -0
- package/ios/SecureStorageNative.swift +84 -0
- package/ios/Security/AppIntegrityChecker.swift +85 -0
- package/ios/Security/EmulatorDetector.swift +45 -0
- package/ios/Security/RuntimeDetector.swift +107 -0
- package/ios/SecuritySuite.mm +28 -4
- package/ios/SecuritySuite.swift +407 -131
- package/ios/SslPinning.swift +242 -263
- package/lib/commonjs/clipboard/index.js +3 -0
- package/lib/commonjs/clipboard/index.js.map +1 -0
- package/lib/commonjs/crypto/index.js +39 -0
- package/lib/commonjs/crypto/index.js.map +1 -0
- package/lib/commonjs/device/index.js +40 -0
- package/lib/commonjs/device/index.js.map +1 -0
- package/lib/commonjs/errors.js +62 -0
- package/lib/commonjs/errors.js.map +1 -0
- package/lib/commonjs/index.js +220 -151
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/integrity/index.js +40 -0
- package/lib/commonjs/integrity/index.js.map +1 -0
- package/lib/commonjs/jws.js +141 -0
- package/lib/commonjs/jws.js.map +1 -0
- package/lib/commonjs/legacy/cryptoOptions.js +20 -0
- package/lib/commonjs/legacy/cryptoOptions.js.map +1 -0
- package/lib/commonjs/native/bridge.js +23 -0
- package/lib/commonjs/native/bridge.js.map +1 -0
- package/lib/commonjs/network/index.js +3 -0
- package/lib/commonjs/network/index.js.map +1 -0
- package/lib/commonjs/risk/score.js +36 -0
- package/lib/commonjs/risk/score.js.map +1 -0
- package/lib/commonjs/runtime/index.js +31 -0
- package/lib/commonjs/runtime/index.js.map +1 -0
- package/lib/commonjs/screen/index.js +13 -0
- package/lib/commonjs/screen/index.js.map +1 -0
- package/lib/commonjs/securitySuite/index.js +42 -0
- package/lib/commonjs/securitySuite/index.js.map +1 -0
- package/lib/commonjs/storage/index.js +3 -0
- package/lib/commonjs/storage/index.js.map +1 -0
- package/lib/commonjs/types/detection.js +2 -0
- package/lib/commonjs/types/detection.js.map +1 -0
- package/lib/module/clipboard/index.js +3 -0
- package/lib/module/clipboard/index.js.map +1 -0
- package/lib/module/crypto/index.js +35 -0
- package/lib/module/crypto/index.js.map +1 -0
- package/lib/module/device/index.js +36 -0
- package/lib/module/device/index.js.map +1 -0
- package/lib/module/errors.js +55 -0
- package/lib/module/errors.js.map +1 -0
- package/lib/module/index.js +147 -148
- package/lib/module/index.js.map +1 -1
- package/lib/module/integrity/index.js +36 -0
- package/lib/module/integrity/index.js.map +1 -0
- package/lib/module/jws.js +127 -0
- package/lib/module/jws.js.map +1 -0
- package/lib/module/legacy/cryptoOptions.js +16 -0
- package/lib/module/legacy/cryptoOptions.js.map +1 -0
- package/lib/module/native/bridge.js +19 -0
- package/lib/module/native/bridge.js.map +1 -0
- package/lib/module/network/index.js +3 -0
- package/lib/module/network/index.js.map +1 -0
- package/lib/module/risk/score.js +32 -0
- package/lib/module/risk/score.js.map +1 -0
- package/lib/module/runtime/index.js +27 -0
- package/lib/module/runtime/index.js.map +1 -0
- package/lib/module/screen/index.js +5 -0
- package/lib/module/screen/index.js.map +1 -0
- package/lib/module/securitySuite/index.js +38 -0
- package/lib/module/securitySuite/index.js.map +1 -0
- package/lib/module/storage/index.js +3 -0
- package/lib/module/storage/index.js.map +1 -0
- package/lib/module/types/detection.js +2 -0
- package/lib/module/types/detection.js.map +1 -0
- package/lib/typescript/commonjs/docs/api-v1-proposal.d.ts +215 -0
- package/lib/typescript/commonjs/docs/api-v1-proposal.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/SecureView.d.ts +1 -1
- package/lib/typescript/commonjs/src/SecureView.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/clipboard/index.d.ts +2 -0
- package/lib/typescript/commonjs/src/clipboard/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/crypto/index.d.ts +15 -0
- package/lib/typescript/commonjs/src/crypto/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/device/index.d.ts +11 -0
- package/lib/typescript/commonjs/src/device/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/errors.d.ts +17 -0
- package/lib/typescript/commonjs/src/errors.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/helpers.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/index.d.ts +77 -24
- package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/integrity/index.d.ts +6 -0
- package/lib/typescript/commonjs/src/integrity/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/jws.d.ts +44 -0
- package/lib/typescript/commonjs/src/jws.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/legacy/cryptoOptions.d.ts +35 -0
- package/lib/typescript/commonjs/src/legacy/cryptoOptions.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/native/bridge.d.ts +12 -0
- package/lib/typescript/commonjs/src/native/bridge.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/network/index.d.ts +2 -0
- package/lib/typescript/commonjs/src/network/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/risk/score.d.ts +12 -0
- package/lib/typescript/commonjs/src/risk/score.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/runtime/index.d.ts +6 -0
- package/lib/typescript/commonjs/src/runtime/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/screen/index.d.ts +3 -0
- package/lib/typescript/commonjs/src/screen/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/securitySuite/index.d.ts +6 -0
- package/lib/typescript/commonjs/src/securitySuite/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/storage/index.d.ts +2 -0
- package/lib/typescript/commonjs/src/storage/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/types/detection.d.ts +41 -0
- package/lib/typescript/commonjs/src/types/detection.d.ts.map +1 -0
- package/lib/typescript/module/docs/api-v1-proposal.d.ts +215 -0
- package/lib/typescript/module/docs/api-v1-proposal.d.ts.map +1 -0
- package/lib/typescript/module/src/SecureView.d.ts +1 -1
- package/lib/typescript/module/src/SecureView.d.ts.map +1 -1
- package/lib/typescript/module/src/clipboard/index.d.ts +2 -0
- package/lib/typescript/module/src/clipboard/index.d.ts.map +1 -0
- package/lib/typescript/module/src/crypto/index.d.ts +15 -0
- package/lib/typescript/module/src/crypto/index.d.ts.map +1 -0
- package/lib/typescript/module/src/device/index.d.ts +11 -0
- package/lib/typescript/module/src/device/index.d.ts.map +1 -0
- package/lib/typescript/module/src/errors.d.ts +17 -0
- package/lib/typescript/module/src/errors.d.ts.map +1 -0
- package/lib/typescript/module/src/helpers.d.ts.map +1 -1
- package/lib/typescript/module/src/index.d.ts +77 -24
- package/lib/typescript/module/src/index.d.ts.map +1 -1
- package/lib/typescript/module/src/integrity/index.d.ts +6 -0
- package/lib/typescript/module/src/integrity/index.d.ts.map +1 -0
- package/lib/typescript/module/src/jws.d.ts +44 -0
- package/lib/typescript/module/src/jws.d.ts.map +1 -0
- package/lib/typescript/module/src/legacy/cryptoOptions.d.ts +35 -0
- package/lib/typescript/module/src/legacy/cryptoOptions.d.ts.map +1 -0
- package/lib/typescript/module/src/native/bridge.d.ts +12 -0
- package/lib/typescript/module/src/native/bridge.d.ts.map +1 -0
- package/lib/typescript/module/src/network/index.d.ts +2 -0
- package/lib/typescript/module/src/network/index.d.ts.map +1 -0
- package/lib/typescript/module/src/risk/score.d.ts +12 -0
- package/lib/typescript/module/src/risk/score.d.ts.map +1 -0
- package/lib/typescript/module/src/runtime/index.d.ts +6 -0
- package/lib/typescript/module/src/runtime/index.d.ts.map +1 -0
- package/lib/typescript/module/src/screen/index.d.ts +3 -0
- package/lib/typescript/module/src/screen/index.d.ts.map +1 -0
- package/lib/typescript/module/src/securitySuite/index.d.ts +6 -0
- package/lib/typescript/module/src/securitySuite/index.d.ts.map +1 -0
- package/lib/typescript/module/src/storage/index.d.ts +2 -0
- package/lib/typescript/module/src/storage/index.d.ts.map +1 -0
- package/lib/typescript/module/src/types/detection.d.ts +41 -0
- package/lib/typescript/module/src/types/detection.d.ts.map +1 -0
- package/package.json +2 -10
- package/src/clipboard/index.ts +1 -0
- package/src/crypto/index.ts +49 -0
- package/src/device/index.ts +47 -0
- package/src/errors.ts +84 -0
- package/src/index.tsx +293 -195
- package/src/integrity/index.ts +46 -0
- package/src/jws.ts +213 -0
- package/src/legacy/cryptoOptions.ts +49 -0
- package/src/native/bridge.ts +37 -0
- package/src/network/index.ts +1 -0
- package/src/risk/score.ts +49 -0
- package/src/runtime/index.ts +43 -0
- package/src/screen/index.ts +2 -0
- package/src/securitySuite/index.ts +45 -0
- package/src/storage/index.ts +1 -0
- package/src/types/detection.ts +46 -0
- package/android/src/main/java/com/securitysuite/StorageEncryption.java +0 -52
- package/ios/StorageEncryption.swift +0 -89
package/ios/SecuritySuite.swift
CHANGED
|
@@ -1,143 +1,384 @@
|
|
|
1
1
|
import Foundation
|
|
2
2
|
import CryptoKit
|
|
3
|
-
import SwiftUI
|
|
4
3
|
import Security
|
|
5
|
-
import
|
|
4
|
+
import UIKit
|
|
6
5
|
import IOSSecuritySuite
|
|
7
6
|
|
|
8
7
|
@objc(SecuritySuite)
|
|
9
8
|
class SecuritySuite: NSObject {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
private let hkdfSalt = "react-native-security-suite".data(using: .utf8)!
|
|
10
|
+
private let hkdfInfoEncryption = "rss-encryption-v1".data(using: .utf8)!
|
|
11
|
+
private let hkdfInfoHmac = "rss-hmac-v1".data(using: .utf8)!
|
|
12
|
+
|
|
13
|
+
private var privateKeyData: Data?
|
|
14
|
+
private var encryptionKeyData: Data?
|
|
15
|
+
private var hmacKeyData: Data?
|
|
16
|
+
private var cryptoConfig = CryptoConfig.defaults()
|
|
17
|
+
|
|
18
|
+
private func loadOrCreateECDHKeyPair() throws -> P256.KeyAgreement.PrivateKey {
|
|
19
|
+
if let stored = try KeychainHelper.shared.loadECDHKeyPair() {
|
|
20
|
+
privateKeyData = stored.privateKey
|
|
21
|
+
return try P256.KeyAgreement.PrivateKey(derRepresentation: stored.privateKey)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let key = P256.KeyAgreement.PrivateKey()
|
|
25
|
+
privateKeyData = key.derRepresentation
|
|
26
|
+
try KeychainHelper.shared.saveECDHKeyPair(
|
|
27
|
+
privateKey: key.derRepresentation,
|
|
28
|
+
publicKey: key.publicKey.derRepresentation
|
|
29
|
+
)
|
|
30
|
+
return key
|
|
31
|
+
}
|
|
15
32
|
|
|
16
33
|
@objc(getPublicKey:withRejecter:)
|
|
17
|
-
func getPublicKey(resolve: @escaping RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock)
|
|
34
|
+
func getPublicKey(resolve: @escaping RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
|
|
18
35
|
do {
|
|
19
|
-
let key =
|
|
20
|
-
|
|
21
|
-
publicKey = key.publicKey.derRepresentation.base64EncodedString()
|
|
22
|
-
|
|
23
|
-
resolve(publicKey)
|
|
36
|
+
let key = try loadOrCreateECDHKeyPair()
|
|
37
|
+
resolve(key.publicKey.derRepresentation.base64EncodedString())
|
|
24
38
|
} catch {
|
|
25
|
-
reject("
|
|
39
|
+
reject("GET_PUBLIC_KEY_ERROR", error.localizedDescription, error)
|
|
26
40
|
}
|
|
27
41
|
}
|
|
28
|
-
|
|
29
|
-
@objc(
|
|
30
|
-
func
|
|
42
|
+
|
|
43
|
+
@objc(establishSharedKey:withOptions:withResolver:withRejecter:)
|
|
44
|
+
func establishSharedKey(
|
|
45
|
+
serverPK: NSString,
|
|
46
|
+
options: NSDictionary?,
|
|
47
|
+
resolve: @escaping RCTPromiseResolveBlock,
|
|
48
|
+
reject: RCTPromiseRejectBlock
|
|
49
|
+
) {
|
|
31
50
|
do {
|
|
32
|
-
|
|
33
|
-
|
|
51
|
+
cryptoConfig = try CryptoConfig.from(dictionary: options)
|
|
52
|
+
|
|
53
|
+
guard cryptoConfig.keyAgreementAlgorithm == "ECDH",
|
|
54
|
+
cryptoConfig.keyFactoryAlgorithm == "EC" else {
|
|
55
|
+
reject("GET_SHARED_KEY_ERROR", "Only ECDH/EC key agreement is supported on iOS", nil)
|
|
56
|
+
return
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let serverKeyString = (serverPK as String).trimmingCharacters(in: .whitespacesAndNewlines)
|
|
60
|
+
guard !serverKeyString.isEmpty,
|
|
61
|
+
let serverPublicKeyData = Data(base64Encoded: serverKeyString) else {
|
|
62
|
+
reject("GET_SHARED_KEY_ERROR", "Invalid server public key", nil)
|
|
63
|
+
return
|
|
64
|
+
}
|
|
34
65
|
|
|
35
|
-
|
|
66
|
+
let privateKey = try loadOrCreateECDHKeyPair()
|
|
67
|
+
let sharedSecret = try privateKey.sharedSecretFromKeyAgreement(
|
|
68
|
+
with: P256.KeyAgreement.PublicKey(derRepresentation: serverPublicKeyData)
|
|
69
|
+
)
|
|
36
70
|
|
|
37
|
-
|
|
71
|
+
encryptionKeyData = sharedSecret.hkdfDerivedSymmetricKey(
|
|
72
|
+
using: SHA256.self,
|
|
73
|
+
salt: hkdfSalt,
|
|
74
|
+
sharedInfo: hkdfInfoEncryption,
|
|
75
|
+
outputByteCount: 32
|
|
76
|
+
).withUnsafeBytes { Data($0) }
|
|
77
|
+
|
|
78
|
+
hmacKeyData = sharedSecret.hkdfDerivedSymmetricKey(
|
|
79
|
+
using: SHA256.self,
|
|
80
|
+
salt: hkdfSalt,
|
|
81
|
+
sharedInfo: hkdfInfoHmac,
|
|
82
|
+
outputByteCount: 32
|
|
83
|
+
).withUnsafeBytes { Data($0) }
|
|
84
|
+
|
|
85
|
+
resolve(nil)
|
|
38
86
|
} catch {
|
|
39
|
-
reject("
|
|
87
|
+
reject("GET_SHARED_KEY_ERROR", error.localizedDescription, error)
|
|
40
88
|
}
|
|
41
89
|
}
|
|
42
|
-
|
|
43
|
-
@objc(
|
|
44
|
-
func
|
|
90
|
+
|
|
91
|
+
@objc(getSharedKey:withOptions:withResolver:withRejecter:)
|
|
92
|
+
func getSharedKey(
|
|
93
|
+
serverPK: NSString,
|
|
94
|
+
options: NSDictionary?,
|
|
95
|
+
resolve: @escaping RCTPromiseResolveBlock,
|
|
96
|
+
reject: RCTPromiseRejectBlock
|
|
97
|
+
) {
|
|
45
98
|
do {
|
|
46
|
-
|
|
47
|
-
|
|
99
|
+
cryptoConfig = try CryptoConfig.from(dictionary: options)
|
|
100
|
+
|
|
101
|
+
guard cryptoConfig.keyAgreementAlgorithm == "ECDH",
|
|
102
|
+
cryptoConfig.keyFactoryAlgorithm == "EC" else {
|
|
103
|
+
reject("GET_SHARED_KEY_ERROR", "Only ECDH/EC key agreement is supported on iOS", nil)
|
|
48
104
|
return
|
|
49
105
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
106
|
+
|
|
107
|
+
let serverKeyString = (serverPK as String).trimmingCharacters(in: .whitespacesAndNewlines)
|
|
108
|
+
guard !serverKeyString.isEmpty,
|
|
109
|
+
let serverPublicKeyData = Data(base64Encoded: serverKeyString) else {
|
|
110
|
+
reject("GET_SHARED_KEY_ERROR", "Invalid server public key", nil)
|
|
53
111
|
return
|
|
54
112
|
}
|
|
55
|
-
let key = SymmetricKey(data: keyData)
|
|
56
113
|
|
|
57
|
-
let
|
|
114
|
+
let privateKey = try loadOrCreateECDHKeyPair()
|
|
115
|
+
let sharedSecret = try privateKey.sharedSecretFromKeyAgreement(
|
|
116
|
+
with: P256.KeyAgreement.PublicKey(derRepresentation: serverPublicKeyData)
|
|
117
|
+
)
|
|
58
118
|
|
|
59
|
-
|
|
119
|
+
encryptionKeyData = sharedSecret.hkdfDerivedSymmetricKey(
|
|
120
|
+
using: SHA256.self,
|
|
121
|
+
salt: hkdfSalt,
|
|
122
|
+
sharedInfo: hkdfInfoEncryption,
|
|
123
|
+
outputByteCount: 32
|
|
124
|
+
).withUnsafeBytes { Data($0) }
|
|
125
|
+
|
|
126
|
+
hmacKeyData = sharedSecret.hkdfDerivedSymmetricKey(
|
|
127
|
+
using: SHA256.self,
|
|
128
|
+
salt: hkdfSalt,
|
|
129
|
+
sharedInfo: hkdfInfoHmac,
|
|
130
|
+
outputByteCount: 32
|
|
131
|
+
).withUnsafeBytes { Data($0) }
|
|
132
|
+
|
|
133
|
+
resolve(encryptionKeyData?.base64EncodedString())
|
|
60
134
|
} catch {
|
|
61
|
-
reject("
|
|
135
|
+
reject("GET_SHARED_KEY_ERROR", error.localizedDescription, error)
|
|
62
136
|
}
|
|
63
137
|
}
|
|
64
|
-
|
|
65
|
-
@objc(
|
|
66
|
-
func
|
|
138
|
+
|
|
139
|
+
@objc(encrypt:withOptions:withResolver:withRejecter:)
|
|
140
|
+
func encrypt(
|
|
141
|
+
input: NSString,
|
|
142
|
+
options: NSDictionary?,
|
|
143
|
+
resolve: @escaping RCTPromiseResolveBlock,
|
|
144
|
+
reject: RCTPromiseRejectBlock
|
|
145
|
+
) {
|
|
146
|
+
guard let keyData = encryptionKeyData else {
|
|
147
|
+
reject("ENCRYPT_ERROR", "Encryption key not established. Call getSharedKey first.", nil)
|
|
148
|
+
return
|
|
149
|
+
}
|
|
67
150
|
do {
|
|
68
|
-
|
|
69
|
-
|
|
151
|
+
let config = try cryptoConfig.merged(with: options)
|
|
152
|
+
guard config.cipherTransformation == "AES/GCM/NoPadding",
|
|
153
|
+
config.encryptionKeyAlgorithm == "AES" else {
|
|
154
|
+
reject("ENCRYPT_ERROR", "Only AES/GCM/NoPadding is supported on iOS", nil)
|
|
70
155
|
return
|
|
71
156
|
}
|
|
72
|
-
|
|
73
|
-
|
|
157
|
+
|
|
158
|
+
let data = Data((input as String).utf8)
|
|
159
|
+
let key = SymmetricKey(data: keyData)
|
|
160
|
+
guard let output = try AES.GCM.seal(data, using: key).combined?.base64EncodedString() else {
|
|
161
|
+
reject("ENCRYPT_ERROR", "Encryption failed", nil)
|
|
74
162
|
return
|
|
75
163
|
}
|
|
76
|
-
let key = SymmetricKey(data: keyData)
|
|
77
|
-
let output = ( try? AES.GCM.open(.init(combined: data), using: key)).map({String(decoding: $0, as: UTF8.self)})
|
|
78
|
-
|
|
79
164
|
resolve(output)
|
|
80
165
|
} catch {
|
|
81
|
-
reject("
|
|
166
|
+
reject("ENCRYPT_ERROR", error.localizedDescription, error)
|
|
82
167
|
}
|
|
83
168
|
}
|
|
84
|
-
|
|
85
|
-
@objc(
|
|
86
|
-
func
|
|
169
|
+
|
|
170
|
+
@objc(decrypt:withOptions:withResolver:withRejecter:)
|
|
171
|
+
func decrypt(
|
|
172
|
+
input: NSString,
|
|
173
|
+
options: NSDictionary?,
|
|
174
|
+
resolve: @escaping RCTPromiseResolveBlock,
|
|
175
|
+
reject: RCTPromiseRejectBlock
|
|
176
|
+
) {
|
|
177
|
+
guard let keyData = encryptionKeyData else {
|
|
178
|
+
reject("DECRYPT_ERROR", "Encryption key not established. Call getSharedKey first.", nil)
|
|
179
|
+
return
|
|
180
|
+
}
|
|
181
|
+
guard let data = Data(base64Encoded: input as String) else {
|
|
182
|
+
reject("DECRYPT_ERROR", "Invalid ciphertext", nil)
|
|
183
|
+
return
|
|
184
|
+
}
|
|
87
185
|
do {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
186
|
+
let config = try cryptoConfig.merged(with: options)
|
|
187
|
+
guard config.cipherTransformation == "AES/GCM/NoPadding",
|
|
188
|
+
config.encryptionKeyAlgorithm == "AES" else {
|
|
189
|
+
reject("DECRYPT_ERROR", "Only AES/GCM/NoPadding is supported on iOS", nil)
|
|
190
|
+
return
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
let key = SymmetricKey(data: keyData)
|
|
194
|
+
let output = try AES.GCM.open(AES.GCM.SealedBox(combined: data), using: key)
|
|
195
|
+
resolve(String(decoding: output, as: UTF8.self))
|
|
196
|
+
} catch {
|
|
197
|
+
reject("DECRYPT_ERROR", error.localizedDescription, error)
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
@objc(generateJWS:withResolver:withRejecter:)
|
|
202
|
+
func generateJWS(options: NSDictionary, resolve: @escaping RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
|
|
203
|
+
guard let secret = options["secret"] as? String,
|
|
204
|
+
!secret.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
|
|
205
|
+
reject("JWS_ERROR", "JWS secret is required and must be a non-empty string", nil)
|
|
206
|
+
return
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
do {
|
|
210
|
+
let payloadString: String
|
|
211
|
+
if let payload = options["payload"] {
|
|
212
|
+
if payload is NSNull {
|
|
213
|
+
payloadString = ""
|
|
214
|
+
} else if let value = payload as? String {
|
|
215
|
+
payloadString = value
|
|
216
|
+
} else {
|
|
217
|
+
reject("JWS_ERROR", "JWS payload must be normalized to a string before calling native code", nil)
|
|
218
|
+
return
|
|
219
|
+
}
|
|
220
|
+
} else {
|
|
221
|
+
payloadString = ""
|
|
91
222
|
}
|
|
92
|
-
|
|
93
|
-
let
|
|
223
|
+
|
|
224
|
+
let algorithm = options["algorithm"] as? String
|
|
225
|
+
let headers = options["headers"] as? [String: Any]
|
|
226
|
+
let detached = options["detached"] as? Bool ?? false
|
|
227
|
+
let jws = try JWSGenerator.generate(
|
|
228
|
+
payloadString: payloadString,
|
|
229
|
+
secret: secret,
|
|
230
|
+
algorithm: algorithm,
|
|
231
|
+
headers: headers,
|
|
232
|
+
detached: detached
|
|
233
|
+
)
|
|
234
|
+
resolve(jws)
|
|
235
|
+
} catch {
|
|
236
|
+
reject("JWS_ERROR", error.localizedDescription, error)
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
@objc(obfuscate:withSecret:withResolver:withRejecter:)
|
|
241
|
+
func obfuscate(input: NSString, secret: NSString, resolve: @escaping RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
|
|
242
|
+
do {
|
|
243
|
+
let result = try Obfuscation().obfuscate(plain: input as String, secret: secret as String)
|
|
244
|
+
resolve(result)
|
|
245
|
+
} catch {
|
|
246
|
+
reject("OBFUSCATE_ERROR", error.localizedDescription, error)
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
@objc(deobfuscate:withSecret:withResolver:withRejecter:)
|
|
251
|
+
func deobfuscate(input: NSString, secret: NSString, resolve: @escaping RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
|
|
252
|
+
do {
|
|
253
|
+
let result = try Obfuscation().deobfuscate(encoded: input as String, secret: secret as String)
|
|
254
|
+
resolve(result)
|
|
255
|
+
} catch {
|
|
256
|
+
reject("DEOBFUSCATE_ERROR", error.localizedDescription, error)
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
@objc(storageEncrypt:withSecretKey:withHardEncryption:withCallback:)
|
|
261
|
+
func storageEncrypt(input: NSString, secretKey: NSString, hardEncryption: Bool, callback: RCTResponseSenderBlock) {
|
|
262
|
+
guard secretKey != nil, !(secretKey as String).trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
|
|
263
|
+
callback([NSNull(), "secretKey is required. Device identifiers are not accepted as encryption keys."])
|
|
264
|
+
return
|
|
265
|
+
}
|
|
266
|
+
if hardEncryption {
|
|
267
|
+
callback([NSNull(), "hardEncryption is deprecated. Use SecureStorage for encrypted-at-rest data."])
|
|
268
|
+
return
|
|
269
|
+
}
|
|
270
|
+
do {
|
|
271
|
+
let encrypted = try Obfuscation().obfuscate(plain: input as String, secret: secretKey as String)
|
|
94
272
|
callback([encrypted, NSNull()])
|
|
95
273
|
} catch {
|
|
96
|
-
callback([NSNull(),
|
|
274
|
+
callback([NSNull(), error.localizedDescription])
|
|
97
275
|
}
|
|
98
276
|
}
|
|
99
|
-
|
|
277
|
+
|
|
100
278
|
@objc(storageDecrypt:withSecretKey:withHardEncryption:withCallback:)
|
|
101
|
-
func storageDecrypt(input: NSString, secretKey: NSString, hardEncryption: Bool, callback: RCTResponseSenderBlock)
|
|
279
|
+
func storageDecrypt(input: NSString, secretKey: NSString, hardEncryption: Bool, callback: RCTResponseSenderBlock) {
|
|
280
|
+
guard secretKey != nil, !(secretKey as String).trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
|
|
281
|
+
callback([NSNull(), "secretKey is required. Device identifiers are not accepted as encryption keys."])
|
|
282
|
+
return
|
|
283
|
+
}
|
|
284
|
+
if hardEncryption {
|
|
285
|
+
callback([NSNull(), "hardEncryption is deprecated. Use SecureStorage for encrypted-at-rest data."])
|
|
286
|
+
return
|
|
287
|
+
}
|
|
102
288
|
do {
|
|
103
|
-
|
|
104
|
-
if secretKey != nil {
|
|
105
|
-
encryptionKey = secretKey as String;
|
|
106
|
-
}
|
|
107
|
-
let storageEncryption: StorageEncryption = StorageEncryption()
|
|
108
|
-
let decrypted = try? storageEncryption.decrypt(decoded: "\(input)", encryptionKey: encryptionKey, hardEncryption: hardEncryption)
|
|
289
|
+
let decrypted = try Obfuscation().deobfuscate(encoded: input as String, secret: secretKey as String)
|
|
109
290
|
callback([decrypted, NSNull()])
|
|
110
291
|
} catch {
|
|
111
|
-
callback([NSNull(),
|
|
292
|
+
callback([NSNull(), error.localizedDescription])
|
|
112
293
|
}
|
|
113
294
|
}
|
|
114
|
-
|
|
115
|
-
@objc(
|
|
116
|
-
func
|
|
295
|
+
|
|
296
|
+
@objc(secureStorageSetItem:withValue:withResolver:withRejecter:)
|
|
297
|
+
func secureStorageSetItem(key: NSString, value: NSString, resolve: @escaping RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
|
|
298
|
+
do {
|
|
299
|
+
try SecureStorageNative.shared.setItem(key: key as String, value: value as String)
|
|
300
|
+
resolve(nil)
|
|
301
|
+
} catch {
|
|
302
|
+
reject("SECURE_STORAGE_ERROR", error.localizedDescription, error)
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
@objc(secureStorageGetItem:withResolver:withRejecter:)
|
|
307
|
+
func secureStorageGetItem(key: NSString, resolve: @escaping RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
|
|
308
|
+
do {
|
|
309
|
+
resolve(try SecureStorageNative.shared.getItem(key: key as String))
|
|
310
|
+
} catch {
|
|
311
|
+
reject("SECURE_STORAGE_ERROR", error.localizedDescription, error)
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
@objc(secureStorageRemoveItem:withResolver:withRejecter:)
|
|
316
|
+
func secureStorageRemoveItem(key: NSString, resolve: @escaping RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
|
|
317
|
+
do {
|
|
318
|
+
try SecureStorageNative.shared.removeItem(key: key as String)
|
|
319
|
+
resolve(nil)
|
|
320
|
+
} catch {
|
|
321
|
+
reject("SECURE_STORAGE_ERROR", error.localizedDescription, error)
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
@objc(secureStorageClear:withRejecter:)
|
|
326
|
+
func secureStorageClear(resolve: @escaping RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
|
|
117
327
|
do {
|
|
118
|
-
|
|
328
|
+
try SecureStorageNative.shared.clear()
|
|
329
|
+
resolve(nil)
|
|
119
330
|
} catch {
|
|
120
|
-
|
|
331
|
+
reject("SECURE_STORAGE_ERROR", error.localizedDescription, error)
|
|
121
332
|
}
|
|
122
333
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
334
|
+
|
|
335
|
+
@objc(secureStorageGetAllKeys:withRejecter:)
|
|
336
|
+
func secureStorageGetAllKeys(resolve: @escaping RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
|
|
337
|
+
do {
|
|
338
|
+
resolve(try SecureStorageNative.shared.getAllKeys())
|
|
339
|
+
} catch {
|
|
340
|
+
reject("SECURE_STORAGE_ERROR", error.localizedDescription, error)
|
|
341
|
+
}
|
|
126
342
|
}
|
|
127
|
-
|
|
343
|
+
|
|
128
344
|
@objc(fetch:withData:withCallback:)
|
|
129
|
-
func fetch(url: NSString, data: NSDictionary, callback: @escaping RCTResponseSenderBlock)
|
|
345
|
+
func fetch(url: NSString, data: NSDictionary, callback: @escaping RCTResponseSenderBlock) {
|
|
346
|
+
let urlString = url as String
|
|
347
|
+
guard SSLPinning.isHttpsURL(urlString) else {
|
|
348
|
+
callback([NSNull(), "Only HTTPS URLs are allowed"])
|
|
349
|
+
return
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
let pinningConfig = PinningConfiguration(data: data)
|
|
353
|
+
if let error = pinningConfig.error {
|
|
354
|
+
callback([NSNull(), error])
|
|
355
|
+
return
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
guard let requestUrl = URL(string: urlString) else {
|
|
359
|
+
callback([NSNull(), "Invalid URL"])
|
|
360
|
+
return
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if pinningConfig.enabled {
|
|
364
|
+
guard let host = requestUrl.host,
|
|
365
|
+
SSLPinning.hostnameMatchesValidDomains(host, validDomains: pinningConfig.validDomains) else {
|
|
366
|
+
callback([NSNull(), "Hostname is not in validDomains"])
|
|
367
|
+
return
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
130
371
|
let configuration = URLSessionConfiguration.default
|
|
131
372
|
configuration.httpShouldSetCookies = false
|
|
132
373
|
configuration.httpCookieAcceptPolicy = .never
|
|
133
374
|
configuration.networkServiceType = .responsiveData
|
|
134
375
|
configuration.shouldUseExtendedBackgroundIdleMode = true
|
|
135
|
-
|
|
136
|
-
var request = URLRequest(url:
|
|
137
|
-
request.httpMethod = data["method"] as? String ?? "
|
|
376
|
+
|
|
377
|
+
var request = URLRequest(url: requestUrl)
|
|
378
|
+
request.httpMethod = data["method"] as? String ?? "GET"
|
|
138
379
|
request.timeoutInterval = data["timeout"] as? TimeInterval ?? 60.0
|
|
139
|
-
request.allHTTPHeaderFields = data["headers"] as? [String
|
|
140
|
-
|
|
380
|
+
request.allHTTPHeaderFields = data["headers"] as? [String: String]
|
|
381
|
+
|
|
141
382
|
if let body = data["body"] {
|
|
142
383
|
if let bodyString = body as? String {
|
|
143
384
|
request.httpBody = bodyString.data(using: .utf8)
|
|
@@ -145,62 +386,97 @@ class SecuritySuite: NSObject {
|
|
|
145
386
|
request.httpBody = try? JSONSerialization.data(withJSONObject: bodyDict, options: [])
|
|
146
387
|
}
|
|
147
388
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
389
|
+
|
|
390
|
+
var jwsHeaderName = "X-Request-Signature"
|
|
391
|
+
if let jwsOptions = data["jws"] as? [String: Any] {
|
|
392
|
+
guard let secret = jwsOptions["secret"] as? String,
|
|
393
|
+
!secret.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
|
|
394
|
+
callback([NSNull(), "JWS secret is required and must be a non-empty string"])
|
|
395
|
+
return
|
|
396
|
+
}
|
|
397
|
+
do {
|
|
398
|
+
if let customHeaderName = jwsOptions["headerName"] as? String, !customHeaderName.isEmpty {
|
|
399
|
+
jwsHeaderName = customHeaderName
|
|
400
|
+
}
|
|
401
|
+
let headers = jwsOptions["headers"] as? [String: Any]
|
|
402
|
+
let algorithm = jwsOptions["algorithm"] as? String
|
|
403
|
+
let detached = jwsOptions["detached"] as? Bool ?? false
|
|
404
|
+
let method = data["method"] as? String ?? "GET"
|
|
405
|
+
let payloadString = try JwsFetchPayload.build(
|
|
406
|
+
url: urlString,
|
|
407
|
+
method: method,
|
|
408
|
+
requestBody: request.httpBody,
|
|
409
|
+
jwsOptions: jwsOptions
|
|
410
|
+
)
|
|
411
|
+
let signature = try JWSGenerator.generate(
|
|
412
|
+
payloadString: payloadString,
|
|
413
|
+
secret: secret,
|
|
414
|
+
algorithm: algorithm,
|
|
415
|
+
headers: headers,
|
|
416
|
+
detached: detached
|
|
417
|
+
)
|
|
418
|
+
request.setValue(signature, forHTTPHeaderField: jwsHeaderName)
|
|
419
|
+
} catch {
|
|
420
|
+
callback([NSNull(), error.localizedDescription])
|
|
421
|
+
return
|
|
422
|
+
}
|
|
423
|
+
} else if data["keyId"] != nil && data["requestId"] != nil {
|
|
424
|
+
guard let secret = data["secret"] as? String,
|
|
425
|
+
!secret.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty,
|
|
426
|
+
let keyId = data["keyId"] as? String,
|
|
427
|
+
let requestId = data["requestId"] as? String else {
|
|
428
|
+
callback([NSNull(), "JWS secret is required. Pass options.jws.secret or options.secret for legacy signing."])
|
|
429
|
+
return
|
|
430
|
+
}
|
|
431
|
+
do {
|
|
432
|
+
let payloadString = request.httpBody.flatMap { String(data: $0, encoding: .utf8) } ?? ""
|
|
433
|
+
let signature = try JWSGenerator.generate(
|
|
434
|
+
payloadString: payloadString,
|
|
435
|
+
secret: secret,
|
|
436
|
+
algorithm: "HS256",
|
|
437
|
+
headers: ["kid": keyId, "request_id": requestId],
|
|
438
|
+
detached: true
|
|
439
|
+
)
|
|
440
|
+
jwsHeaderName = "X-JWS-Signature"
|
|
441
|
+
request.setValue(signature, forHTTPHeaderField: jwsHeaderName)
|
|
442
|
+
} catch {
|
|
443
|
+
callback([NSNull(), error.localizedDescription])
|
|
444
|
+
return
|
|
445
|
+
}
|
|
176
446
|
}
|
|
177
|
-
}
|
|
178
447
|
|
|
179
|
-
|
|
180
|
-
let
|
|
181
|
-
|
|
182
|
-
.replacingOccurrences(of: "+", with: "-")
|
|
183
|
-
.replacingOccurrences(of: "/", with: "_")
|
|
184
|
-
.trimmingCharacters(in: CharacterSet(charactersIn: "="))
|
|
185
|
-
|
|
186
|
-
return base64URL
|
|
448
|
+
let sslPinning = SSLPinning(url: url, data: data, callback: callback)
|
|
449
|
+
let session = URLSession(configuration: configuration, delegate: sslPinning, delegateQueue: .main)
|
|
450
|
+
session.dataTask(with: request).resume()
|
|
187
451
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}
|
|
195
|
-
.flatMap { (data: Data) -> String? in
|
|
196
|
-
let value = data + Data([0x2e] /* "." */) + payload
|
|
197
|
-
let signature = HMAC<SHA256>.authenticationCode(for: value, using: SymmetricKey(data: Data(base64Encoded: self.sharedKey)!))
|
|
198
|
-
let base64URL = convertHMACToBase64URL(hmac: signature.withUnsafeBytes({ Data($0) }))
|
|
199
|
-
|
|
200
|
-
return String(decoding: data, as: UTF8.self) + ".." + base64URL
|
|
201
|
-
} ?? ""
|
|
202
|
-
} catch {
|
|
203
|
-
return "";
|
|
452
|
+
|
|
453
|
+
@objc(getDeviceId:)
|
|
454
|
+
func getDeviceId(callback: RCTResponseSenderBlock) {
|
|
455
|
+
guard let id = UIDevice.current.identifierForVendor?.uuidString else {
|
|
456
|
+
callback([NSNull(), "GET_DEVICE_ID_ERROR"])
|
|
457
|
+
return
|
|
204
458
|
}
|
|
459
|
+
callback([id.replacingOccurrences(of: "-", with: ""), NSNull()])
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
@objc(runtimeDetect:withRejecter:)
|
|
463
|
+
func runtimeDetect(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
|
|
464
|
+
resolve(RuntimeDetector.detect())
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
@objc(appIntegrityVerify:withRejecter:)
|
|
468
|
+
func appIntegrityVerify(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
|
|
469
|
+
resolve(AppIntegrityChecker.verify())
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
@objc(deviceGetEnvironment:withRejecter:)
|
|
473
|
+
func deviceGetEnvironment(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
|
|
474
|
+
resolve(EmulatorDetector.detect())
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
@objc(deviceHasSecurityRisk:withRejecter:)
|
|
478
|
+
func deviceHasSecurityRisk(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
|
|
479
|
+
let jailbreakStatus = IOSSecuritySuite.amIJailbrokenWithFailMessage()
|
|
480
|
+
resolve(jailbreakStatus.jailbroken)
|
|
205
481
|
}
|
|
206
482
|
}
|