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
package/src/index.tsx CHANGED
@@ -1,12 +1,89 @@
1
1
  import { NativeModules, Platform } from 'react-native';
2
- import AsyncStorage from '@react-native-async-storage/async-storage';
3
- import { isJsonString, jsonParse } from './helpers';
2
+ import { jsonParse } from './helpers';
3
+ import {
4
+ toNativeCryptoOptions,
5
+ type CryptoOptions,
6
+ } from './legacy/cryptoOptions';
7
+ import {
8
+ toNativeGenerateJWSOptions,
9
+ toNativeJwsFetchOptions,
10
+ type GenerateJWSOptions,
11
+ type JwsFetchOptions,
12
+ } from './jws';
4
13
 
5
14
  export * from './SecureView';
15
+ export type {
16
+ GenerateJWSOptions,
17
+ JwsAlgorithm,
18
+ JwsFetchOptions,
19
+ JwsHeaderValue,
20
+ JwsHeaders,
21
+ JwsPayload,
22
+ } from './jws';
23
+
24
+ export type {
25
+ CryptoOptions,
26
+ KeyAgreementAlgorithm,
27
+ KeyType,
28
+ EncryptionKeyAlgorithm,
29
+ HmacAlgorithm,
30
+ CipherAlgorithm,
31
+ } from './legacy/cryptoOptions';
32
+
33
+ export { SecurityError, SecurityErrorCode, mapNativeError, isSecurityError } from './errors';
34
+ export { DeviceSecurity } from './device';
35
+ export { RuntimeSecurity } from './runtime';
36
+ export { AppIntegrity } from './integrity';
37
+ export { Crypto } from './crypto';
38
+ export { SecuritySuite } from './securitySuite';
39
+ export type {
40
+ RuntimeThreatReport,
41
+ AppIntegrityReport,
42
+ DeviceEnvironment,
43
+ DeviceSecurityReport,
44
+ SecurityReport,
45
+ RiskLevel,
46
+ BuildType,
47
+ } from './types/detection';
48
+
49
+ /** @deprecated Use `JwsHeaders` (optional `Record<string, JwsHeaderValue>`) instead. */
50
+ export interface LegacyJwsHeaders {
51
+ kid: string;
52
+ request_id: string;
53
+ [key: string]: string;
54
+ }
55
+
56
+ export interface SslPinningOptions {
57
+ /** Base64-encoded SPKI SHA-256 hashes (with or without `sha256/` prefix). */
58
+ certificates: string[];
59
+ /** Allowed hostnames. Request host must match one of these before pinning is evaluated. */
60
+ validDomains: string[];
61
+ }
62
+
63
+ interface Header {
64
+ [key: string]: string;
65
+ }
66
+
67
+ export interface Options {
68
+ body?: string | object;
69
+ headers: Header;
70
+ method?: 'DELETE' | 'GET' | 'POST' | 'PUT' | 'PATCH';
71
+ timeout?: number;
72
+ /** SSL pinning configuration. Both certificates and validDomains are required together. */
73
+ certificates?: string[];
74
+ validDomains?: string[];
75
+ /** @deprecated Use `jws` instead. */
76
+ keyId?: string;
77
+ /** @deprecated Use `jws` instead. */
78
+ requestId?: string;
79
+ /**
80
+ * @deprecated Legacy signing secret. Use `jws.secret` instead.
81
+ */
82
+ secret?: string;
83
+ /** JWS request-signing configuration. */
84
+ jws?: JwsFetchOptions;
85
+ }
6
86
 
7
- /*
8
- * SSL Pinnning start
9
- */
10
87
  interface Response {
11
88
  status: number;
12
89
  url: string;
@@ -26,19 +103,6 @@ export interface ErrorResponse extends Response {
26
103
  code: string;
27
104
  }
28
105
 
29
- interface Header {
30
- [key: string]: string;
31
- }
32
-
33
- export interface Options {
34
- body?: string | object;
35
- headers: Header;
36
- method?: 'DELETE' | 'GET' | 'POST' | 'PUT';
37
- certificates?: string[];
38
- validDomains?: string[];
39
- timeout?: number;
40
- }
41
-
42
106
  export interface FetchEventResponse {
43
107
  url: string;
44
108
  options: Options;
@@ -61,7 +125,7 @@ const LINKING_ERROR =
61
125
  '- You rebuilt the app after installing the package\n' +
62
126
  '- You are not using Expo managed workflow\n';
63
127
 
64
- const SecuritySuite = NativeModules.SecuritySuite
128
+ const NativeSecuritySuiteModule = NativeModules.SecuritySuite
65
129
  ? NativeModules.SecuritySuite
66
130
  : new Proxy(
67
131
  {},
@@ -72,234 +136,268 @@ const SecuritySuite = NativeModules.SecuritySuite
72
136
  }
73
137
  );
74
138
 
75
- export const getPublicKey = (): Promise<string> => SecuritySuite.getPublicKey();
139
+ export const getPublicKey = (): Promise<string> =>
140
+ NativeSecuritySuiteModule.getPublicKey();
141
+
142
+ /**
143
+ * @deprecated Prefer `Crypto.establishSharedKey()` which keeps the derived key in native memory.
144
+ */
145
+ export const getSharedKey = (
146
+ serverPublicKey: string,
147
+ options?: CryptoOptions
148
+ ): Promise<string> =>
149
+ NativeSecuritySuiteModule.getSharedKey(serverPublicKey, toNativeCryptoOptions(options));
150
+
151
+ export const encryptBySharedKey = (
152
+ input: string,
153
+ options?: CryptoOptions
154
+ ): Promise<string> => {
155
+ if (!input || typeof input !== 'string') {
156
+ return Promise.reject(new Error('Input must be a non-empty string'));
157
+ }
158
+ return NativeSecuritySuiteModule.encrypt(input, toNativeCryptoOptions(options));
159
+ };
160
+
161
+ export const decryptBySharedKey = (
162
+ input: string,
163
+ options?: CryptoOptions
164
+ ): Promise<string> => {
165
+ if (!input || typeof input !== 'string') {
166
+ return Promise.reject(new Error('Input must be a non-empty string'));
167
+ }
168
+ return NativeSecuritySuiteModule.decrypt(input, toNativeCryptoOptions(options));
169
+ };
170
+
171
+ export const generateJWS = (options: GenerateJWSOptions): Promise<string> => {
172
+ const nativeOptions = toNativeGenerateJWSOptions(options);
173
+ return NativeSecuritySuiteModule.generateJWS(nativeOptions);
174
+ };
76
175
 
77
- export const getSharedKey = (serverPublicKey: string): Promise<string> =>
78
- SecuritySuite.getSharedKey(serverPublicKey);
176
+ function normalizeFetchOptions(options: Options): Options {
177
+ if (!options.jws) {
178
+ return options;
179
+ }
79
180
 
80
- export const encryptBySharedKey = (input: string): Promise<string> =>
81
- input && typeof input === 'string' ? SecuritySuite.encrypt(input) : '';
181
+ const nativeJws = toNativeJwsFetchOptions(options.jws);
182
+ return {
183
+ ...options,
184
+ jws: {
185
+ algorithm: nativeJws.algorithm,
186
+ secret: nativeJws.secret,
187
+ headers: nativeJws.headers,
188
+ detached: nativeJws.detached,
189
+ ...(options.jws.headerName ? { headerName: options.jws.headerName } : {}),
190
+ ...(options.jws.payload !== undefined
191
+ ? { payload: nativeJws.payload }
192
+ : {}),
193
+ },
194
+ };
195
+ }
82
196
 
83
- export const decryptBySharedKey = (input: string) =>
84
- input && typeof input === 'string' ? SecuritySuite.decrypt(input) : '';
197
+ /**
198
+ * Local obfuscation only NOT secure encryption. Requires an explicit secret.
199
+ * Never use for credentials, tokens, or PII at rest.
200
+ */
201
+ export const obfuscate = (input: string, secret: string): Promise<string> =>
202
+ NativeSecuritySuiteModule.obfuscate(input, secret);
203
+
204
+ export const deobfuscate = (input: string, secret: string): Promise<string> =>
205
+ NativeSecuritySuiteModule.deobfuscate(input, secret);
85
206
 
86
207
  export const getDeviceId = (): Promise<string> =>
87
- new Promise((resolve: any, reject: any) => {
88
- SecuritySuite.getDeviceId((result: string | null, error: string | null) => {
208
+ new Promise((resolve, reject) => {
209
+ NativeSecuritySuiteModule.getDeviceId((result: string | null, error: string | null) => {
89
210
  if (error !== null) reject(error);
90
- else resolve(result);
211
+ else if (result !== null) resolve(result);
212
+ else reject(new Error('GET_DEVICE_ID_ERROR'));
91
213
  });
92
214
  });
93
215
 
216
+ /**
217
+ * @deprecated Use `obfuscate()` with an explicit secret, or `SecureStorage` for at-rest data.
218
+ */
94
219
  export const encrypt = (
95
220
  input: string,
96
221
  hardEncryption = true,
97
- secretKey = null
222
+ secretKey: string | null = null
98
223
  ): Promise<string> =>
99
- new Promise((resolve: any, reject: any) => {
100
- if (!input) resolve(input);
101
-
102
- SecuritySuite.storageEncrypt(
224
+ new Promise((resolve, reject) => {
225
+ if (!input) {
226
+ resolve(input);
227
+ return;
228
+ }
229
+ if (!secretKey) {
230
+ reject(
231
+ new Error(
232
+ 'secretKey is required. Device identifiers are not accepted as encryption keys.'
233
+ )
234
+ );
235
+ return;
236
+ }
237
+ NativeSecuritySuiteModule.storageEncrypt(
103
238
  input,
104
239
  secretKey,
105
240
  hardEncryption,
106
241
  (result: string | null, error: string | null) => {
107
242
  if (error !== null) reject(error);
108
- else resolve(result);
243
+ else if (result !== null) resolve(result);
244
+ else reject(new Error('ENCRYPT_ERROR'));
109
245
  }
110
246
  );
111
247
  });
112
248
 
249
+ /**
250
+ * @deprecated Use `deobfuscate()` with an explicit secret, or `SecureStorage` for at-rest data.
251
+ */
113
252
  export const decrypt = (
114
253
  input: string,
115
254
  hardEncryption = true,
116
- secretKey = null
255
+ secretKey: string | null = null
117
256
  ): Promise<string> =>
118
- new Promise((resolve: any, reject: any) => {
119
- if (!input) resolve(input);
120
-
121
- SecuritySuite.storageDecrypt(
257
+ new Promise((resolve, reject) => {
258
+ if (!input) {
259
+ resolve(input);
260
+ return;
261
+ }
262
+ if (!secretKey) {
263
+ reject(
264
+ new Error(
265
+ 'secretKey is required. Device identifiers are not accepted as encryption keys.'
266
+ )
267
+ );
268
+ return;
269
+ }
270
+ NativeSecuritySuiteModule.storageDecrypt(
122
271
  input,
123
272
  secretKey,
124
273
  hardEncryption,
125
274
  (result: string | null, error: string | null) => {
126
275
  if (error !== null) reject(error);
127
- else resolve(result);
276
+ else if (result !== null) resolve(result);
277
+ else reject(new Error('DECRYPT_ERROR'));
128
278
  }
129
279
  );
130
280
  });
131
281
 
282
+ const SECURE_STORAGE_FAILED = 'Secure storage operation failed';
283
+
284
+ function wrapSecureStorage<T>(operation: string, promise: Promise<T>): Promise<T> {
285
+ return promise.catch((error: unknown) => {
286
+ const detail =
287
+ error instanceof Error
288
+ ? error.message
289
+ : typeof error === 'string'
290
+ ? error
291
+ : 'Unknown error';
292
+ throw new Error(`${SECURE_STORAGE_FAILED} (${operation}): ${detail}`);
293
+ });
294
+ }
295
+
296
+ /** Hardware-backed encrypted storage (Keychain on iOS, EncryptedSharedPreferences on Android). */
132
297
  export const SecureStorage = {
133
- setItem: async (key: string, value: string): Promise<void> => {
134
- try {
135
- const encryptedKey = await encrypt(key, false);
136
- const encryptedValue = await encrypt(value);
137
- return AsyncStorage.setItem(encryptedKey, encryptedValue);
138
- } catch (e) {
139
- console.error('setItem error: ', e);
140
- }
141
- },
142
- getItem: async (key: string): Promise<string | null> => {
143
- try {
144
- const encryptedKey = await encrypt(key, false);
145
- const encryptedData = await AsyncStorage.getItem(encryptedKey);
146
- return decrypt(encryptedData ?? '');
147
- } catch (e) {
148
- console.error('getItem error: ', e);
149
- return '';
150
- }
151
- },
152
- mergeItem: async (key: string, value: string): Promise<void> => {
153
- try {
154
- const encryptedKey = await encrypt(key, false);
155
- const encryptedData = await AsyncStorage.getItem(encryptedKey);
156
- const data = await decrypt(encryptedData ?? '');
157
- if (!isJsonString(data) || !isJsonString(value)) return;
158
- const mergedData = await JSON.stringify(
159
- Object.assign(JSON.parse(data), JSON.parse(value))
160
- );
161
- const encryptedValue = await encrypt(mergedData);
162
- return AsyncStorage.setItem(encryptedKey, encryptedValue);
163
- } catch (e) {
164
- console.error('mergeItem error: ', e);
165
- }
166
- },
167
- removeItem: async (key: string): Promise<void> => {
168
- try {
169
- const encryptedKey = await encrypt(key, false);
170
- return AsyncStorage.removeItem(encryptedKey);
171
- } catch (e) {
172
- console.error('removeItem error: ', e);
173
- }
174
- },
175
- getAllKeys: async (): Promise<readonly string[]> => {
176
- try {
177
- const encryptedKeys = await AsyncStorage.getAllKeys();
178
- return await Promise.all(
179
- encryptedKeys.map(async (item: string): Promise<string> => {
180
- const decryptedKey = await decrypt(item, false);
181
- return decryptedKey ? decryptedKey : item;
182
- })
183
- );
184
- } catch (e) {
185
- console.error('getAllKeys error: ', e);
186
- return [];
187
- }
188
- },
189
- multiSet: async (keyValuePairs: Array<Array<string>>): Promise<void> => {
190
- try {
191
- const encryptedKeyValuePairs: any = await Promise.all(
192
- keyValuePairs.map(async (item: Array<string>) => {
193
- if (item.length === 2 && item[0] && item[1]) {
194
- const encryptedKey = await encrypt(item[0], false);
195
- const encryptedValue = await encrypt(item[1]);
196
- return [encryptedKey, encryptedValue];
197
- }
198
-
199
- return null;
200
- })
201
- );
202
- AsyncStorage.multiSet(encryptedKeyValuePairs);
203
- } catch (e) {
204
- console.error('multiSet error: ', e);
205
- }
298
+ setItem: (key: string, value: string): Promise<void> =>
299
+ wrapSecureStorage(
300
+ 'setItem',
301
+ NativeSecuritySuiteModule.secureStorageSetItem(key, value)
302
+ ),
303
+
304
+ getItem: (key: string): Promise<string | null> =>
305
+ wrapSecureStorage(
306
+ 'getItem',
307
+ NativeSecuritySuiteModule.secureStorageGetItem(key)
308
+ ),
309
+
310
+ removeItem: (key: string): Promise<void> =>
311
+ wrapSecureStorage(
312
+ 'removeItem',
313
+ NativeSecuritySuiteModule.secureStorageRemoveItem(key)
314
+ ),
315
+
316
+ getAllKeys: (): Promise<string[]> =>
317
+ wrapSecureStorage(
318
+ 'getAllKeys',
319
+ NativeSecuritySuiteModule.secureStorageGetAllKeys()
320
+ ),
321
+
322
+ clear: (): Promise<void> =>
323
+ wrapSecureStorage('clear', NativeSecuritySuiteModule.secureStorageClear()),
324
+
325
+ multiSet: async (keyValuePairs: Array<[string, string]>): Promise<void> => {
326
+ await Promise.all(
327
+ keyValuePairs.map(([key, value]) => SecureStorage.setItem(key, value))
328
+ );
206
329
  },
330
+
207
331
  multiGet: async (
208
- keys: Array<string>
209
- ): Promise<readonly [string, string | null][]> => {
210
- try {
211
- if (!Array.isArray(keys)) return [];
212
- const encryptedKeys = await Promise.all(
213
- keys.map(
214
- async (item: string): Promise<string> => await encrypt(item, false)
215
- )
216
- );
217
- const encryptedItems = await AsyncStorage.multiGet(encryptedKeys);
218
- return await Promise.all(
219
- encryptedItems && encryptedItems.length
220
- ? encryptedItems.map(async (item: any): Promise<[string, string]> => {
221
- const decryptedKey = await decrypt(item[0], false);
222
- const decryptedalue = await decrypt(item[1]);
223
- return [decryptedKey, decryptedalue];
224
- })
225
- : []
226
- );
227
- } catch (e) {
228
- console.error('multiGet error: ', e);
229
- return [];
230
- }
332
+ keys: string[]
333
+ ): Promise<readonly [string, string | null][]> =>
334
+ Promise.all(
335
+ keys.map(async (key): Promise<[string, string | null]> => [
336
+ key,
337
+ await SecureStorage.getItem(key),
338
+ ])
339
+ ),
340
+
341
+ multiRemove: async (keys: string[]): Promise<void> => {
342
+ await Promise.all(keys.map((key) => SecureStorage.removeItem(key)));
231
343
  },
232
- multiMerge: async (keyValuePairs: Array<Array<string>>): Promise<void> => {
233
- try {
234
- keyValuePairs.map(async (item: Array<string>) => {
235
- if (item.length === 2 && item[0] && item[1]) {
236
- const encryptedKey = await encrypt(item[0], false);
237
- const encryptedData = await AsyncStorage.getItem(item[0]);
238
- const data = await decrypt(encryptedData ?? '');
239
- if (!isJsonString(data) || !isJsonString(item[1])) return null;
240
- const mergedData = await JSON.stringify(
241
- Object.assign(JSON.parse(data), JSON.parse(item[1]))
242
- );
243
- const encryptedValue = await encrypt(mergedData, false);
244
- return AsyncStorage.setItem(encryptedKey, encryptedValue);
245
- }
246
344
 
247
- return null;
248
- });
249
- } catch (e) {
250
- console.error('multiMerge error: ', e);
345
+ /** @deprecated Use multiSet instead. */
346
+ mergeItem: async (key: string, value: string): Promise<void> => {
347
+ const existing = await SecureStorage.getItem(key);
348
+ if (!existing) {
349
+ await SecureStorage.setItem(key, value);
350
+ return;
251
351
  }
252
- },
253
- multiRemove: async (keys: Array<string>): Promise<void> => {
254
352
  try {
255
- if (!Array.isArray(keys)) return keys;
256
- const encryptedKeys = await Promise.all(
257
- keys.map(
258
- async (item: string): Promise<string> => await encrypt(item, false)
259
- )
260
- );
261
- return AsyncStorage.multiRemove(encryptedKeys);
262
- } catch (e) {
263
- console.error('multiRemove error: ', e);
353
+ const merged = JSON.stringify({
354
+ ...JSON.parse(existing),
355
+ ...JSON.parse(value),
356
+ });
357
+ await SecureStorage.setItem(key, merged);
358
+ } catch {
359
+ throw new Error('mergeItem requires valid JSON strings');
264
360
  }
265
361
  },
266
- clear: async (): Promise<void> => {
267
- try {
268
- return AsyncStorage.clear();
269
- } catch (e) {
270
- console.error('clear error: ', e);
271
- }
362
+
363
+ /** @deprecated Use multiSet instead. */
364
+ multiMerge: async (keyValuePairs: Array<[string, string]>): Promise<void> => {
365
+ await Promise.all(
366
+ keyValuePairs.map(([key, value]) => SecureStorage.mergeItem(key, value))
367
+ );
272
368
  },
273
369
  };
274
370
 
275
371
  export function fetch(
276
372
  url: string,
277
373
  options: Options,
278
- loggerIsEnabled = false
374
+ loggerIsEnabled = __DEV__
279
375
  ): Promise<SuccessResponse | ErrorResponse> {
280
376
  return new Promise((resolve, reject) => {
281
- SecuritySuite.fetch(
377
+ NativeSecuritySuiteModule.fetch(
282
378
  url,
283
- { ...options, loggerIsEnabled },
379
+ { ...normalizeFetchOptions(options), loggerIsEnabled },
284
380
  (result: SuccessResponse, error: ErrorResponse) => {
285
- try {
286
- if (error === null) {
287
- resolve({
288
- ...result,
289
- json: () => jsonParse(result.response),
290
- });
291
- } else {
292
- const errorJson = jsonParse(error.error);
293
- reject({
294
- json: () => errorJson,
295
- error: error?.error ?? error,
296
- status: error?.status ?? '',
297
- url: error?.url ?? '',
298
- ...errorJson,
299
- });
300
- }
301
- } catch (e) {
302
- console.error('SSL Pinnning fetch error: ', e);
381
+ if (error === null) {
382
+ resolve({
383
+ ...result,
384
+ json: () => jsonParse(result.response),
385
+ });
386
+ } else {
387
+ const errorJson = jsonParse(
388
+ typeof error?.error === 'string' ? error.error : JSON.stringify(error)
389
+ );
390
+ reject({
391
+ json: () => errorJson,
392
+ error: error?.error ?? error,
393
+ status: error?.status ?? 0,
394
+ url: error?.url ?? url,
395
+ path: errorJson?.path ?? '',
396
+ message: errorJson?.message ?? String(error?.error ?? error),
397
+ code: errorJson?.code ?? '',
398
+ duration: error?.duration ?? '',
399
+ ...errorJson,
400
+ });
303
401
  }
304
402
  }
305
403
  );
@@ -307,7 +405,7 @@ export function fetch(
307
405
  }
308
406
 
309
407
  export function deviceHasSecurityRisk(): Promise<boolean> {
310
- return SecuritySuite.deviceHasSecurityRisk();
408
+ return NativeSecuritySuiteModule.deviceHasSecurityRisk();
311
409
  }
312
410
 
313
- export default SecuritySuite;
411
+ export default NativeSecuritySuiteModule;
@@ -0,0 +1,46 @@
1
+ import { getNativeModule } from '../native/bridge';
2
+ import type { AppIntegrityReport, BuildType } from '../types/detection';
3
+
4
+ function parseBuildType(value: unknown): BuildType {
5
+ if (value === 'debug' || value === 'release' || value === 'testflight') {
6
+ return value;
7
+ }
8
+ return 'release';
9
+ }
10
+
11
+ function parseIntegrityReport(raw: Record<string, unknown>): AppIntegrityReport {
12
+ const report: AppIntegrityReport = {
13
+ validSignature: Boolean(raw.validSignature),
14
+ debuggable: Boolean(raw.debuggable),
15
+ tampered: Boolean(raw.tampered),
16
+ buildType: parseBuildType(raw.buildType),
17
+ };
18
+
19
+ if (raw.installerTrusted !== undefined) {
20
+ report.installerTrusted = Boolean(raw.installerTrusted);
21
+ }
22
+
23
+ if (typeof raw.signingCertificateSha256 === 'string') {
24
+ report.signingCertificateSha256 = raw.signingCertificateSha256;
25
+ }
26
+
27
+ if (raw.installerPackage === null || typeof raw.installerPackage === 'string') {
28
+ report.installerPackage = raw.installerPackage as string | null;
29
+ }
30
+
31
+ if (typeof raw.bundleIdentifier === 'string') {
32
+ report.bundleIdentifier = raw.bundleIdentifier;
33
+ }
34
+
35
+ return report;
36
+ }
37
+
38
+ export const AppIntegrity = {
39
+ verify(): Promise<AppIntegrityReport> {
40
+ return getNativeModule()
41
+ .appIntegrityVerify()
42
+ .then((result) => parseIntegrityReport(result));
43
+ },
44
+ };
45
+
46
+ export type { AppIntegrityReport, BuildType };