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