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.
Files changed (189) hide show
  1. package/README.md +291 -69
  2. package/android/build.gradle +11 -0
  3. package/android/gradle.properties +1 -1
  4. package/android/src/main/java/com/securitysuite/CryptoConfig.java +106 -0
  5. package/android/src/main/java/com/securitysuite/CryptoUtils.java +155 -0
  6. package/android/src/main/java/com/securitysuite/EcdhKeyStore.java +60 -0
  7. package/android/src/main/java/com/securitysuite/HeaderSanitizer.java +75 -0
  8. package/android/src/main/java/com/securitysuite/JWSGenerator.java +237 -32
  9. package/android/src/main/java/com/securitysuite/JwsFetchPayload.java +81 -0
  10. package/android/src/main/java/com/securitysuite/Obfuscation.java +57 -0
  11. package/android/src/main/java/com/securitysuite/SecureStorageNative.java +211 -0
  12. package/android/src/main/java/com/securitysuite/SecureView.java +2 -10
  13. package/android/src/main/java/com/securitysuite/SecureWindowHelper.java +30 -0
  14. package/android/src/main/java/com/securitysuite/SecuritySuiteModule.java +317 -102
  15. package/android/src/main/java/com/securitysuite/Sslpinning.java +219 -106
  16. package/android/src/main/java/com/securitysuite/security/AppIntegrityChecker.java +133 -0
  17. package/android/src/main/java/com/securitysuite/security/EmulatorDetector.java +145 -0
  18. package/android/src/main/java/com/securitysuite/security/RuntimeDetector.java +234 -0
  19. package/android/src/test/java/com/securitysuite/JWSGeneratorTest.java +153 -0
  20. package/android/src/test/java/com/securitysuite/SecureStorageNativeTest.java +37 -0
  21. package/ios/CryptoConfig.swift +73 -0
  22. package/ios/JWSGenerator.swift +288 -0
  23. package/ios/JWSGeneratorTests.swift +168 -0
  24. package/ios/KeychainHelper.swift +104 -0
  25. package/ios/Obfuscation.swift +42 -0
  26. package/ios/SecureStorageNative.swift +84 -0
  27. package/ios/Security/AppIntegrityChecker.swift +85 -0
  28. package/ios/Security/EmulatorDetector.swift +45 -0
  29. package/ios/Security/RuntimeDetector.swift +107 -0
  30. package/ios/SecuritySuite.mm +28 -4
  31. package/ios/SecuritySuite.swift +427 -134
  32. package/ios/SslPinning.swift +242 -263
  33. package/lib/commonjs/clipboard/index.js +3 -0
  34. package/lib/commonjs/clipboard/index.js.map +1 -0
  35. package/lib/commonjs/crypto/index.js +29 -0
  36. package/lib/commonjs/crypto/index.js.map +1 -0
  37. package/lib/commonjs/device/index.js +40 -0
  38. package/lib/commonjs/device/index.js.map +1 -0
  39. package/lib/commonjs/errors.js +62 -0
  40. package/lib/commonjs/errors.js.map +1 -0
  41. package/lib/commonjs/index.js +220 -151
  42. package/lib/commonjs/index.js.map +1 -1
  43. package/lib/commonjs/integrity/index.js +40 -0
  44. package/lib/commonjs/integrity/index.js.map +1 -0
  45. package/lib/commonjs/jws.js +141 -0
  46. package/lib/commonjs/jws.js.map +1 -0
  47. package/lib/commonjs/legacy/cryptoOptions.js +29 -0
  48. package/lib/commonjs/legacy/cryptoOptions.js.map +1 -0
  49. package/lib/commonjs/native/bridge.js +23 -0
  50. package/lib/commonjs/native/bridge.js.map +1 -0
  51. package/lib/commonjs/network/index.js +3 -0
  52. package/lib/commonjs/network/index.js.map +1 -0
  53. package/lib/commonjs/risk/score.js +36 -0
  54. package/lib/commonjs/risk/score.js.map +1 -0
  55. package/lib/commonjs/runtime/index.js +31 -0
  56. package/lib/commonjs/runtime/index.js.map +1 -0
  57. package/lib/commonjs/screen/index.js +13 -0
  58. package/lib/commonjs/screen/index.js.map +1 -0
  59. package/lib/commonjs/securitySuite/index.js +42 -0
  60. package/lib/commonjs/securitySuite/index.js.map +1 -0
  61. package/lib/commonjs/storage/index.js +3 -0
  62. package/lib/commonjs/storage/index.js.map +1 -0
  63. package/lib/commonjs/types/detection.js +2 -0
  64. package/lib/commonjs/types/detection.js.map +1 -0
  65. package/lib/module/clipboard/index.js +3 -0
  66. package/lib/module/clipboard/index.js.map +1 -0
  67. package/lib/module/crypto/index.js +25 -0
  68. package/lib/module/crypto/index.js.map +1 -0
  69. package/lib/module/device/index.js +36 -0
  70. package/lib/module/device/index.js.map +1 -0
  71. package/lib/module/errors.js +55 -0
  72. package/lib/module/errors.js.map +1 -0
  73. package/lib/module/index.js +147 -148
  74. package/lib/module/index.js.map +1 -1
  75. package/lib/module/integrity/index.js +36 -0
  76. package/lib/module/integrity/index.js.map +1 -0
  77. package/lib/module/jws.js +127 -0
  78. package/lib/module/jws.js.map +1 -0
  79. package/lib/module/legacy/cryptoOptions.js +25 -0
  80. package/lib/module/legacy/cryptoOptions.js.map +1 -0
  81. package/lib/module/native/bridge.js +19 -0
  82. package/lib/module/native/bridge.js.map +1 -0
  83. package/lib/module/network/index.js +3 -0
  84. package/lib/module/network/index.js.map +1 -0
  85. package/lib/module/risk/score.js +32 -0
  86. package/lib/module/risk/score.js.map +1 -0
  87. package/lib/module/runtime/index.js +27 -0
  88. package/lib/module/runtime/index.js.map +1 -0
  89. package/lib/module/screen/index.js +5 -0
  90. package/lib/module/screen/index.js.map +1 -0
  91. package/lib/module/securitySuite/index.js +38 -0
  92. package/lib/module/securitySuite/index.js.map +1 -0
  93. package/lib/module/storage/index.js +3 -0
  94. package/lib/module/storage/index.js.map +1 -0
  95. package/lib/module/types/detection.js +2 -0
  96. package/lib/module/types/detection.js.map +1 -0
  97. package/lib/typescript/commonjs/docs/api-v1-proposal.d.ts +215 -0
  98. package/lib/typescript/commonjs/docs/api-v1-proposal.d.ts.map +1 -0
  99. package/lib/typescript/commonjs/src/SecureView.d.ts +1 -1
  100. package/lib/typescript/commonjs/src/SecureView.d.ts.map +1 -1
  101. package/lib/typescript/commonjs/src/clipboard/index.d.ts +2 -0
  102. package/lib/typescript/commonjs/src/clipboard/index.d.ts.map +1 -0
  103. package/lib/typescript/commonjs/src/crypto/index.d.ts +15 -0
  104. package/lib/typescript/commonjs/src/crypto/index.d.ts.map +1 -0
  105. package/lib/typescript/commonjs/src/device/index.d.ts +11 -0
  106. package/lib/typescript/commonjs/src/device/index.d.ts.map +1 -0
  107. package/lib/typescript/commonjs/src/errors.d.ts +17 -0
  108. package/lib/typescript/commonjs/src/errors.d.ts.map +1 -0
  109. package/lib/typescript/commonjs/src/helpers.d.ts.map +1 -1
  110. package/lib/typescript/commonjs/src/index.d.ts +77 -24
  111. package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
  112. package/lib/typescript/commonjs/src/integrity/index.d.ts +6 -0
  113. package/lib/typescript/commonjs/src/integrity/index.d.ts.map +1 -0
  114. package/lib/typescript/commonjs/src/jws.d.ts +44 -0
  115. package/lib/typescript/commonjs/src/jws.d.ts.map +1 -0
  116. package/lib/typescript/commonjs/src/legacy/cryptoOptions.d.ts +35 -0
  117. package/lib/typescript/commonjs/src/legacy/cryptoOptions.d.ts.map +1 -0
  118. package/lib/typescript/commonjs/src/native/bridge.d.ts +12 -0
  119. package/lib/typescript/commonjs/src/native/bridge.d.ts.map +1 -0
  120. package/lib/typescript/commonjs/src/network/index.d.ts +2 -0
  121. package/lib/typescript/commonjs/src/network/index.d.ts.map +1 -0
  122. package/lib/typescript/commonjs/src/risk/score.d.ts +12 -0
  123. package/lib/typescript/commonjs/src/risk/score.d.ts.map +1 -0
  124. package/lib/typescript/commonjs/src/runtime/index.d.ts +6 -0
  125. package/lib/typescript/commonjs/src/runtime/index.d.ts.map +1 -0
  126. package/lib/typescript/commonjs/src/screen/index.d.ts +3 -0
  127. package/lib/typescript/commonjs/src/screen/index.d.ts.map +1 -0
  128. package/lib/typescript/commonjs/src/securitySuite/index.d.ts +6 -0
  129. package/lib/typescript/commonjs/src/securitySuite/index.d.ts.map +1 -0
  130. package/lib/typescript/commonjs/src/storage/index.d.ts +2 -0
  131. package/lib/typescript/commonjs/src/storage/index.d.ts.map +1 -0
  132. package/lib/typescript/commonjs/src/types/detection.d.ts +41 -0
  133. package/lib/typescript/commonjs/src/types/detection.d.ts.map +1 -0
  134. package/lib/typescript/module/docs/api-v1-proposal.d.ts +215 -0
  135. package/lib/typescript/module/docs/api-v1-proposal.d.ts.map +1 -0
  136. package/lib/typescript/module/src/SecureView.d.ts +1 -1
  137. package/lib/typescript/module/src/SecureView.d.ts.map +1 -1
  138. package/lib/typescript/module/src/clipboard/index.d.ts +2 -0
  139. package/lib/typescript/module/src/clipboard/index.d.ts.map +1 -0
  140. package/lib/typescript/module/src/crypto/index.d.ts +15 -0
  141. package/lib/typescript/module/src/crypto/index.d.ts.map +1 -0
  142. package/lib/typescript/module/src/device/index.d.ts +11 -0
  143. package/lib/typescript/module/src/device/index.d.ts.map +1 -0
  144. package/lib/typescript/module/src/errors.d.ts +17 -0
  145. package/lib/typescript/module/src/errors.d.ts.map +1 -0
  146. package/lib/typescript/module/src/helpers.d.ts.map +1 -1
  147. package/lib/typescript/module/src/index.d.ts +77 -24
  148. package/lib/typescript/module/src/index.d.ts.map +1 -1
  149. package/lib/typescript/module/src/integrity/index.d.ts +6 -0
  150. package/lib/typescript/module/src/integrity/index.d.ts.map +1 -0
  151. package/lib/typescript/module/src/jws.d.ts +44 -0
  152. package/lib/typescript/module/src/jws.d.ts.map +1 -0
  153. package/lib/typescript/module/src/legacy/cryptoOptions.d.ts +35 -0
  154. package/lib/typescript/module/src/legacy/cryptoOptions.d.ts.map +1 -0
  155. package/lib/typescript/module/src/native/bridge.d.ts +12 -0
  156. package/lib/typescript/module/src/native/bridge.d.ts.map +1 -0
  157. package/lib/typescript/module/src/network/index.d.ts +2 -0
  158. package/lib/typescript/module/src/network/index.d.ts.map +1 -0
  159. package/lib/typescript/module/src/risk/score.d.ts +12 -0
  160. package/lib/typescript/module/src/risk/score.d.ts.map +1 -0
  161. package/lib/typescript/module/src/runtime/index.d.ts +6 -0
  162. package/lib/typescript/module/src/runtime/index.d.ts.map +1 -0
  163. package/lib/typescript/module/src/screen/index.d.ts +3 -0
  164. package/lib/typescript/module/src/screen/index.d.ts.map +1 -0
  165. package/lib/typescript/module/src/securitySuite/index.d.ts +6 -0
  166. package/lib/typescript/module/src/securitySuite/index.d.ts.map +1 -0
  167. package/lib/typescript/module/src/storage/index.d.ts +2 -0
  168. package/lib/typescript/module/src/storage/index.d.ts.map +1 -0
  169. package/lib/typescript/module/src/types/detection.d.ts +41 -0
  170. package/lib/typescript/module/src/types/detection.d.ts.map +1 -0
  171. package/package.json +2 -10
  172. package/src/clipboard/index.ts +1 -0
  173. package/src/crypto/index.ts +40 -0
  174. package/src/device/index.ts +47 -0
  175. package/src/errors.ts +84 -0
  176. package/src/index.tsx +293 -195
  177. package/src/integrity/index.ts +46 -0
  178. package/src/jws.ts +213 -0
  179. package/src/legacy/cryptoOptions.ts +84 -0
  180. package/src/native/bridge.ts +37 -0
  181. package/src/network/index.ts +1 -0
  182. package/src/risk/score.ts +49 -0
  183. package/src/runtime/index.ts +43 -0
  184. package/src/screen/index.ts +2 -0
  185. package/src/securitySuite/index.ts +45 -0
  186. package/src/storage/index.ts +1 -0
  187. package/src/types/detection.ts +46 -0
  188. package/android/src/main/java/com/securitysuite/StorageEncryption.java +0 -52
  189. package/ios/StorageEncryption.swift +0 -89
@@ -1,143 +1,401 @@
1
1
  import Foundation
2
2
  import CryptoKit
3
- import SwiftUI
4
3
  import Security
5
- import CommonCrypto
4
+ import UIKit
6
5
  import IOSSecuritySuite
7
6
 
8
7
  @objc(SecuritySuite)
9
8
  class SecuritySuite: NSObject {
10
- var privateKey: String!,
11
- publicKey: String!,
12
- sharedKey: String!,
13
- keyData: Data!,
14
- session: URLSession!
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) -> Void {
71
+ func getPublicKey(resolve: @escaping RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
18
72
  do {
19
- let key = P256.KeyAgreement.PrivateKey()
20
- privateKey = key.derRepresentation.base64EncodedString()
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("error", "GET_PUBLIC_KEY_ERROR", nil)
76
+ reject("GET_PUBLIC_KEY_ERROR", error.localizedDescription, error)
26
77
  }
27
78
  }
28
-
29
- @objc(getSharedKey:withResolver:withRejecter:)
30
- func getSharedKey(serverPK: NSString, resolve: @escaping RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void {
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
- guard let serverPublicKeyData = Data(base64Encoded: serverPK as String),
33
- let privateKeyData = Data(base64Encoded: privateKey as String) else { return }
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
- sharedKey = try? P256.KeyAgreement.PrivateKey(derRepresentation: privateKeyData).sharedSecretFromKeyAgreement(with: .init(derRepresentation: serverPublicKeyData)).withUnsafeBytes { Data($0).base64EncodedString() }
98
+ let privateKey = try loadOrCreateECDHKeyPair()
99
+ let sharedSecret = try privateKey.sharedSecretFromKeyAgreement(
100
+ with: P256.KeyAgreement.PublicKey(derRepresentation: serverPublicKeyData)
101
+ )
36
102
 
37
- resolve(sharedKey)
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("error", "GET_SHARED_KEY_ERROR", nil)
119
+ reject("GET_SHARED_KEY_ERROR", error.localizedDescription, error)
40
120
  }
41
121
  }
42
-
43
- @objc(encrypt:withResolver:withRejecter:)
44
- func encrypt(input: NSString, resolve: @escaping RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void {
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
- guard let keyData = Data(base64Encoded: sharedKey) else {
47
- reject("error", "DECRYPT_SHARED_KEY_ERROR", nil)
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
- if keyData == nil {
52
- reject("error", "keyData is null", nil)
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
- let key = SymmetricKey(data: keyData)
186
+ resolve(output)
187
+ } catch {
188
+ reject("ENCRYPT_ERROR", error.localizedDescription, error)
189
+ }
190
+ }
56
191
 
57
- let output = try? AES.GCM.seal(data, using: key).combined?.base64EncodedString()
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
- resolve(output)
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("error", "ENCRYPT_ERROR", nil)
214
+ reject("DECRYPT_ERROR", error.localizedDescription, error)
62
215
  }
63
216
  }
64
-
65
- @objc(decrypt:withResolver:withRejecter:)
66
- func decrypt(input: NSString, resolve: @escaping RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void {
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
- guard let keyData = Data(base64Encoded: sharedKey) else {
69
- reject("error", "DECRYPT_SHARED_KEY_ERROR", nil)
70
- return
71
- }
72
- guard let data = Data(base64Encoded: "\(input)") else {
73
- reject("error", "INPUT_ERROR", nil)
74
- return
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
- 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
- resolve(output)
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("error", "DECRYPT_ERROR", nil)
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) -> Void {
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
- var encryptionKey = getDeviceId();
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(), "SOFT_DECRYPT_ERROR"])
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) -> Void {
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
- var encryptionKey = getDeviceId()
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(), "SOFT_DECRYPT_ERROR"])
309
+ callback([NSNull(), error.localizedDescription])
112
310
  }
113
311
  }
114
-
115
- @objc(getDeviceId:)
116
- func getDeviceId(callback: RCTResponseSenderBlock) -> Void {
312
+
313
+ @objc(secureStorageSetItem:withValue:withResolver:withRejecter:)
314
+ func secureStorageSetItem(key: NSString, value: NSString, resolve: @escaping RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
117
315
  do {
118
- callback([getDeviceId(), NSNull()]);
316
+ try SecureStorageNative.shared.setItem(key: key as String, value: value as String)
317
+ resolve(nil)
119
318
  } catch {
120
- callback([NSNull(), "GET_DEVICE_ID_ERROR"]);
319
+ reject("SECURE_STORAGE_ERROR", error.localizedDescription, error)
121
320
  }
122
321
  }
123
-
124
- func getDeviceId() -> String {
125
- return UIDevice.current.identifierForVendor!.uuidString.replacingOccurrences(of: "-", with: "", options: [], range: nil)
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) -> Void {
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: URL(string: url as String)!)
137
- request.httpMethod = data["method"] as? String ?? "POST"
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 : String]
140
- // Prepare body
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
- if data["keyId"] != nil && data["requestId"] != nil {
149
- request
150
- .setValue(
151
- jwsHeader(
152
- payload: request.httpBody ?? .init(),
153
- keyId: data["keyId"] as! String,
154
- requestId: data["requestId"] as! String
155
- ),
156
- forHTTPHeaderField: "X-JWS-Signature"
157
- )
158
- }
159
-
160
- let sslPinning = SSLPinning(url: url, data: data, callback: callback)
161
- let session = URLSession(
162
- configuration: configuration,
163
- delegate: sslPinning,
164
- delegateQueue: .main
165
- )
166
- session.dataTask(with: request).resume()
167
- }
168
-
169
- @objc(deviceHasSecurityRisk:withRejecter:)
170
- func deviceHasSecurityRisk(resolve:RCTPromiseResolveBlock, reject:RCTPromiseRejectBlock) -> Void {
171
- do {
172
- let jailbreakStatus = IOSSecuritySuite.amIJailbrokenWithFailMessage()
173
- resolve(jailbreakStatus.jailbroken)
174
- } catch {
175
- reject("ERROR", nil, nil)
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
- private func convertHMACToBase64URL(hmac: Data) -> String {
180
- let base64Encoded = hmac.base64EncodedString()
181
- let base64URL = base64Encoded
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
- private func jwsHeader(payload: Data, keyId: String, requestId: String) -> String {
190
- do {
191
- return (try? JSONEncoder().encode(JoseHeader(kid: keyId, requestId: requestId)))
192
- .flatMap { data in
193
- data.base64EncodedData().urlsafeBase64
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
  }