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/src/index.tsx
CHANGED
|
@@ -1,12 +1,89 @@
|
|
|
1
1
|
import { NativeModules, Platform } from 'react-native';
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
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
|
|
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> =>
|
|
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
|
-
|
|
78
|
-
|
|
176
|
+
function normalizeFetchOptions(options: Options): Options {
|
|
177
|
+
if (!options.jws) {
|
|
178
|
+
return options;
|
|
179
|
+
}
|
|
79
180
|
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
84
|
-
|
|
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
|
|
88
|
-
|
|
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
|
|
100
|
-
if (!input)
|
|
101
|
-
|
|
102
|
-
|
|
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
|
|
119
|
-
if (!input)
|
|
120
|
-
|
|
121
|
-
|
|
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:
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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:
|
|
209
|
-
): Promise<readonly [string, string | null][]> =>
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
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
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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 =
|
|
374
|
+
loggerIsEnabled = __DEV__
|
|
279
375
|
): Promise<SuccessResponse | ErrorResponse> {
|
|
280
376
|
return new Promise((resolve, reject) => {
|
|
281
|
-
|
|
377
|
+
NativeSecuritySuiteModule.fetch(
|
|
282
378
|
url,
|
|
283
|
-
{ ...options, loggerIsEnabled },
|
|
379
|
+
{ ...normalizeFetchOptions(options), loggerIsEnabled },
|
|
284
380
|
(result: SuccessResponse, error: ErrorResponse) => {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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
|
|
408
|
+
return NativeSecuritySuiteModule.deviceHasSecurityRisk();
|
|
311
409
|
}
|
|
312
410
|
|
|
313
|
-
export default
|
|
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 };
|