react-native-security-suite 0.9.22 → 1.0.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +235 -69
- package/android/build.gradle +11 -0
- package/android/gradle.properties +1 -1
- package/android/src/main/java/com/securitysuite/CryptoConfig.java +158 -0
- package/android/src/main/java/com/securitysuite/CryptoUtils.java +152 -0
- package/android/src/main/java/com/securitysuite/EcdhKeyStore.java +60 -0
- package/android/src/main/java/com/securitysuite/HeaderSanitizer.java +75 -0
- package/android/src/main/java/com/securitysuite/JWSGenerator.java +237 -32
- package/android/src/main/java/com/securitysuite/JwsFetchPayload.java +81 -0
- package/android/src/main/java/com/securitysuite/Obfuscation.java +57 -0
- package/android/src/main/java/com/securitysuite/SecureStorageNative.java +211 -0
- package/android/src/main/java/com/securitysuite/SecureView.java +2 -10
- package/android/src/main/java/com/securitysuite/SecureWindowHelper.java +30 -0
- package/android/src/main/java/com/securitysuite/SecuritySuiteModule.java +310 -102
- package/android/src/main/java/com/securitysuite/Sslpinning.java +219 -106
- package/android/src/main/java/com/securitysuite/security/AppIntegrityChecker.java +133 -0
- package/android/src/main/java/com/securitysuite/security/EmulatorDetector.java +145 -0
- package/android/src/main/java/com/securitysuite/security/RuntimeDetector.java +234 -0
- package/android/src/test/java/com/securitysuite/JWSGeneratorTest.java +153 -0
- package/android/src/test/java/com/securitysuite/SecureStorageNativeTest.java +37 -0
- package/ios/CryptoConfig.swift +124 -0
- package/ios/JWSGenerator.swift +288 -0
- package/ios/JWSGeneratorTests.swift +168 -0
- package/ios/KeychainHelper.swift +104 -0
- package/ios/Obfuscation.swift +42 -0
- package/ios/SecureStorageNative.swift +84 -0
- package/ios/Security/AppIntegrityChecker.swift +85 -0
- package/ios/Security/EmulatorDetector.swift +45 -0
- package/ios/Security/RuntimeDetector.swift +107 -0
- package/ios/SecuritySuite.mm +28 -4
- package/ios/SecuritySuite.swift +407 -131
- package/ios/SslPinning.swift +242 -263
- package/lib/commonjs/clipboard/index.js +3 -0
- package/lib/commonjs/clipboard/index.js.map +1 -0
- package/lib/commonjs/crypto/index.js +39 -0
- package/lib/commonjs/crypto/index.js.map +1 -0
- package/lib/commonjs/device/index.js +40 -0
- package/lib/commonjs/device/index.js.map +1 -0
- package/lib/commonjs/errors.js +62 -0
- package/lib/commonjs/errors.js.map +1 -0
- package/lib/commonjs/index.js +220 -151
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/integrity/index.js +40 -0
- package/lib/commonjs/integrity/index.js.map +1 -0
- package/lib/commonjs/jws.js +141 -0
- package/lib/commonjs/jws.js.map +1 -0
- package/lib/commonjs/legacy/cryptoOptions.js +20 -0
- package/lib/commonjs/legacy/cryptoOptions.js.map +1 -0
- package/lib/commonjs/native/bridge.js +23 -0
- package/lib/commonjs/native/bridge.js.map +1 -0
- package/lib/commonjs/network/index.js +3 -0
- package/lib/commonjs/network/index.js.map +1 -0
- package/lib/commonjs/risk/score.js +36 -0
- package/lib/commonjs/risk/score.js.map +1 -0
- package/lib/commonjs/runtime/index.js +31 -0
- package/lib/commonjs/runtime/index.js.map +1 -0
- package/lib/commonjs/screen/index.js +13 -0
- package/lib/commonjs/screen/index.js.map +1 -0
- package/lib/commonjs/securitySuite/index.js +42 -0
- package/lib/commonjs/securitySuite/index.js.map +1 -0
- package/lib/commonjs/storage/index.js +3 -0
- package/lib/commonjs/storage/index.js.map +1 -0
- package/lib/commonjs/types/detection.js +2 -0
- package/lib/commonjs/types/detection.js.map +1 -0
- package/lib/module/clipboard/index.js +3 -0
- package/lib/module/clipboard/index.js.map +1 -0
- package/lib/module/crypto/index.js +35 -0
- package/lib/module/crypto/index.js.map +1 -0
- package/lib/module/device/index.js +36 -0
- package/lib/module/device/index.js.map +1 -0
- package/lib/module/errors.js +55 -0
- package/lib/module/errors.js.map +1 -0
- package/lib/module/index.js +147 -148
- package/lib/module/index.js.map +1 -1
- package/lib/module/integrity/index.js +36 -0
- package/lib/module/integrity/index.js.map +1 -0
- package/lib/module/jws.js +127 -0
- package/lib/module/jws.js.map +1 -0
- package/lib/module/legacy/cryptoOptions.js +16 -0
- package/lib/module/legacy/cryptoOptions.js.map +1 -0
- package/lib/module/native/bridge.js +19 -0
- package/lib/module/native/bridge.js.map +1 -0
- package/lib/module/network/index.js +3 -0
- package/lib/module/network/index.js.map +1 -0
- package/lib/module/risk/score.js +32 -0
- package/lib/module/risk/score.js.map +1 -0
- package/lib/module/runtime/index.js +27 -0
- package/lib/module/runtime/index.js.map +1 -0
- package/lib/module/screen/index.js +5 -0
- package/lib/module/screen/index.js.map +1 -0
- package/lib/module/securitySuite/index.js +38 -0
- package/lib/module/securitySuite/index.js.map +1 -0
- package/lib/module/storage/index.js +3 -0
- package/lib/module/storage/index.js.map +1 -0
- package/lib/module/types/detection.js +2 -0
- package/lib/module/types/detection.js.map +1 -0
- package/lib/typescript/commonjs/docs/api-v1-proposal.d.ts +215 -0
- package/lib/typescript/commonjs/docs/api-v1-proposal.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/SecureView.d.ts +1 -1
- package/lib/typescript/commonjs/src/SecureView.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/clipboard/index.d.ts +2 -0
- package/lib/typescript/commonjs/src/clipboard/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/crypto/index.d.ts +15 -0
- package/lib/typescript/commonjs/src/crypto/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/device/index.d.ts +11 -0
- package/lib/typescript/commonjs/src/device/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/errors.d.ts +17 -0
- package/lib/typescript/commonjs/src/errors.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/helpers.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/index.d.ts +77 -24
- package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/integrity/index.d.ts +6 -0
- package/lib/typescript/commonjs/src/integrity/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/jws.d.ts +44 -0
- package/lib/typescript/commonjs/src/jws.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/legacy/cryptoOptions.d.ts +35 -0
- package/lib/typescript/commonjs/src/legacy/cryptoOptions.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/native/bridge.d.ts +12 -0
- package/lib/typescript/commonjs/src/native/bridge.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/network/index.d.ts +2 -0
- package/lib/typescript/commonjs/src/network/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/risk/score.d.ts +12 -0
- package/lib/typescript/commonjs/src/risk/score.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/runtime/index.d.ts +6 -0
- package/lib/typescript/commonjs/src/runtime/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/screen/index.d.ts +3 -0
- package/lib/typescript/commonjs/src/screen/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/securitySuite/index.d.ts +6 -0
- package/lib/typescript/commonjs/src/securitySuite/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/storage/index.d.ts +2 -0
- package/lib/typescript/commonjs/src/storage/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/types/detection.d.ts +41 -0
- package/lib/typescript/commonjs/src/types/detection.d.ts.map +1 -0
- package/lib/typescript/module/docs/api-v1-proposal.d.ts +215 -0
- package/lib/typescript/module/docs/api-v1-proposal.d.ts.map +1 -0
- package/lib/typescript/module/src/SecureView.d.ts +1 -1
- package/lib/typescript/module/src/SecureView.d.ts.map +1 -1
- package/lib/typescript/module/src/clipboard/index.d.ts +2 -0
- package/lib/typescript/module/src/clipboard/index.d.ts.map +1 -0
- package/lib/typescript/module/src/crypto/index.d.ts +15 -0
- package/lib/typescript/module/src/crypto/index.d.ts.map +1 -0
- package/lib/typescript/module/src/device/index.d.ts +11 -0
- package/lib/typescript/module/src/device/index.d.ts.map +1 -0
- package/lib/typescript/module/src/errors.d.ts +17 -0
- package/lib/typescript/module/src/errors.d.ts.map +1 -0
- package/lib/typescript/module/src/helpers.d.ts.map +1 -1
- package/lib/typescript/module/src/index.d.ts +77 -24
- package/lib/typescript/module/src/index.d.ts.map +1 -1
- package/lib/typescript/module/src/integrity/index.d.ts +6 -0
- package/lib/typescript/module/src/integrity/index.d.ts.map +1 -0
- package/lib/typescript/module/src/jws.d.ts +44 -0
- package/lib/typescript/module/src/jws.d.ts.map +1 -0
- package/lib/typescript/module/src/legacy/cryptoOptions.d.ts +35 -0
- package/lib/typescript/module/src/legacy/cryptoOptions.d.ts.map +1 -0
- package/lib/typescript/module/src/native/bridge.d.ts +12 -0
- package/lib/typescript/module/src/native/bridge.d.ts.map +1 -0
- package/lib/typescript/module/src/network/index.d.ts +2 -0
- package/lib/typescript/module/src/network/index.d.ts.map +1 -0
- package/lib/typescript/module/src/risk/score.d.ts +12 -0
- package/lib/typescript/module/src/risk/score.d.ts.map +1 -0
- package/lib/typescript/module/src/runtime/index.d.ts +6 -0
- package/lib/typescript/module/src/runtime/index.d.ts.map +1 -0
- package/lib/typescript/module/src/screen/index.d.ts +3 -0
- package/lib/typescript/module/src/screen/index.d.ts.map +1 -0
- package/lib/typescript/module/src/securitySuite/index.d.ts +6 -0
- package/lib/typescript/module/src/securitySuite/index.d.ts.map +1 -0
- package/lib/typescript/module/src/storage/index.d.ts +2 -0
- package/lib/typescript/module/src/storage/index.d.ts.map +1 -0
- package/lib/typescript/module/src/types/detection.d.ts +41 -0
- package/lib/typescript/module/src/types/detection.d.ts.map +1 -0
- package/package.json +2 -10
- package/src/clipboard/index.ts +1 -0
- package/src/crypto/index.ts +49 -0
- package/src/device/index.ts +47 -0
- package/src/errors.ts +84 -0
- package/src/index.tsx +293 -195
- package/src/integrity/index.ts +46 -0
- package/src/jws.ts +213 -0
- package/src/legacy/cryptoOptions.ts +49 -0
- package/src/native/bridge.ts +37 -0
- package/src/network/index.ts +1 -0
- package/src/risk/score.ts +49 -0
- package/src/runtime/index.ts +43 -0
- package/src/screen/index.ts +2 -0
- package/src/securitySuite/index.ts +45 -0
- package/src/storage/index.ts +1 -0
- package/src/types/detection.ts +46 -0
- package/android/src/main/java/com/securitysuite/StorageEncryption.java +0 -52
- package/ios/StorageEncryption.swift +0 -89
package/src/jws.ts
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
export type JwsAlgorithm = 'HS256' | 'HS384' | 'HS512';
|
|
2
|
+
|
|
3
|
+
export type JwsPayload =
|
|
4
|
+
| string
|
|
5
|
+
| Record<string, unknown>
|
|
6
|
+
| unknown[]
|
|
7
|
+
| number
|
|
8
|
+
| boolean
|
|
9
|
+
| null
|
|
10
|
+
| undefined;
|
|
11
|
+
|
|
12
|
+
export type JwsHeaderValue = string | number | boolean | null;
|
|
13
|
+
|
|
14
|
+
export type JwsHeaders = Record<string, JwsHeaderValue>;
|
|
15
|
+
|
|
16
|
+
const SUPPORTED_ALGORITHMS: readonly JwsAlgorithm[] = [
|
|
17
|
+
'HS256',
|
|
18
|
+
'HS384',
|
|
19
|
+
'HS512',
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
const SAFE_HEADER_KEY = /^[a-zA-Z][a-zA-Z0-9_-]*$/;
|
|
23
|
+
|
|
24
|
+
export interface GenerateJWSOptions {
|
|
25
|
+
payload?: JwsPayload;
|
|
26
|
+
algorithm?: JwsAlgorithm;
|
|
27
|
+
headers?: JwsHeaders;
|
|
28
|
+
secret: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface JwsFetchOptions {
|
|
32
|
+
algorithm?: JwsAlgorithm;
|
|
33
|
+
headers?: JwsHeaders;
|
|
34
|
+
secret: string;
|
|
35
|
+
payload?: JwsPayload;
|
|
36
|
+
detached?: boolean;
|
|
37
|
+
headerName?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function isEmptyJwsPayload(payload: JwsPayload | undefined): boolean {
|
|
41
|
+
return (
|
|
42
|
+
payload === undefined ||
|
|
43
|
+
payload === null ||
|
|
44
|
+
(typeof payload === 'string' && payload.length === 0)
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Normalizes a JWS payload to the exact UTF-8 string used for signing.
|
|
50
|
+
* Empty payload cases return an empty string (never "null" or "undefined").
|
|
51
|
+
*/
|
|
52
|
+
export function normalizeJwsPayload(payload: JwsPayload | undefined): string {
|
|
53
|
+
if (isEmptyJwsPayload(payload)) {
|
|
54
|
+
return '';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (typeof payload === 'string') {
|
|
58
|
+
return payload;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (typeof payload === 'number' || typeof payload === 'boolean') {
|
|
62
|
+
return JSON.stringify(payload);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return JSON.stringify(payload);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function validateJwsAlgorithm(
|
|
69
|
+
algorithm: string | undefined
|
|
70
|
+
): JwsAlgorithm {
|
|
71
|
+
if (!algorithm) {
|
|
72
|
+
return 'HS256';
|
|
73
|
+
}
|
|
74
|
+
if (!SUPPORTED_ALGORITHMS.includes(algorithm as JwsAlgorithm)) {
|
|
75
|
+
throw new Error(`Unsupported JWS algorithm: ${algorithm}`);
|
|
76
|
+
}
|
|
77
|
+
return algorithm as JwsAlgorithm;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function validateJwsSecret(secret: unknown): string {
|
|
81
|
+
if (typeof secret !== 'string' || secret.trim().length === 0) {
|
|
82
|
+
throw new Error('JWS secret is required and must be a non-empty string');
|
|
83
|
+
}
|
|
84
|
+
return secret;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function validateJwsHeaderKey(key: string): void {
|
|
88
|
+
if (!SAFE_HEADER_KEY.test(key)) {
|
|
89
|
+
throw new Error(`Invalid JWS header key: ${key}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function validateJwsHeaderValue(
|
|
94
|
+
key: string,
|
|
95
|
+
value: unknown
|
|
96
|
+
): JwsHeaderValue {
|
|
97
|
+
if (
|
|
98
|
+
value === null ||
|
|
99
|
+
typeof value === 'string' ||
|
|
100
|
+
typeof value === 'number' ||
|
|
101
|
+
typeof value === 'boolean'
|
|
102
|
+
) {
|
|
103
|
+
if (typeof value === 'string' && value.length > 0) {
|
|
104
|
+
for (let i = 0; i < value.length; i++) {
|
|
105
|
+
const code = value.charCodeAt(i);
|
|
106
|
+
if (code < 0x20 || code > 0x7e) {
|
|
107
|
+
throw new Error(`Invalid JWS header value for key: ${key}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return value as JwsHeaderValue;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
throw new Error(
|
|
115
|
+
`JWS header values must be JSON-serializable primitives: ${key}`
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function validateJwsHeaders(headers: unknown): JwsHeaders {
|
|
120
|
+
if (headers === undefined || headers === null) {
|
|
121
|
+
return {};
|
|
122
|
+
}
|
|
123
|
+
if (typeof headers !== 'object' || Array.isArray(headers)) {
|
|
124
|
+
throw new Error('JWS headers must be an object when provided');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const result: JwsHeaders = {};
|
|
128
|
+
for (const [key, value] of Object.entries(headers as Record<string, unknown>)) {
|
|
129
|
+
validateJwsHeaderKey(key);
|
|
130
|
+
result[key] = validateJwsHeaderValue(key, value);
|
|
131
|
+
}
|
|
132
|
+
return result;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Resolves the JWS algorithm from options and/or protected headers.
|
|
137
|
+
*/
|
|
138
|
+
export function resolveJwsAlgorithm(
|
|
139
|
+
algorithm: JwsAlgorithm | undefined,
|
|
140
|
+
headers: JwsHeaders
|
|
141
|
+
): JwsAlgorithm {
|
|
142
|
+
const headerAlg =
|
|
143
|
+
headers.alg !== undefined && headers.alg !== null
|
|
144
|
+
? String(headers.alg)
|
|
145
|
+
: undefined;
|
|
146
|
+
|
|
147
|
+
if (algorithm && headerAlg && algorithm !== headerAlg) {
|
|
148
|
+
throw new Error(
|
|
149
|
+
'JWS algorithm mismatch: options.algorithm and headers.alg must match'
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (algorithm) {
|
|
154
|
+
return validateJwsAlgorithm(algorithm);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (headerAlg) {
|
|
158
|
+
return validateJwsAlgorithm(headerAlg);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return 'HS256';
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export interface NativeGenerateJWSOptions {
|
|
165
|
+
payload: string;
|
|
166
|
+
algorithm: JwsAlgorithm;
|
|
167
|
+
secret: string;
|
|
168
|
+
headers: JwsHeaders;
|
|
169
|
+
detached: boolean;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function toNativeGenerateJWSOptions(
|
|
173
|
+
options: GenerateJWSOptions,
|
|
174
|
+
detached = false
|
|
175
|
+
): NativeGenerateJWSOptions {
|
|
176
|
+
const secret = validateJwsSecret(options.secret);
|
|
177
|
+
const headers = validateJwsHeaders(options.headers);
|
|
178
|
+
const algorithm = resolveJwsAlgorithm(options.algorithm, headers);
|
|
179
|
+
const payload = normalizeJwsPayload(options.payload);
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
payload,
|
|
183
|
+
algorithm,
|
|
184
|
+
secret,
|
|
185
|
+
headers: { ...headers, alg: algorithm },
|
|
186
|
+
detached,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export function toNativeJwsFetchOptions(
|
|
191
|
+
jws: JwsFetchOptions
|
|
192
|
+
): NativeGenerateJWSOptions {
|
|
193
|
+
const secret = validateJwsSecret(jws.secret);
|
|
194
|
+
const headers = validateJwsHeaders(jws.headers);
|
|
195
|
+
const algorithm = resolveJwsAlgorithm(jws.algorithm, headers);
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
payload: normalizeJwsPayload(jws.payload),
|
|
199
|
+
algorithm,
|
|
200
|
+
secret,
|
|
201
|
+
headers: { ...headers, alg: algorithm },
|
|
202
|
+
detached: jws.detached ?? false,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function assertCompactJwsShape(jws: string): void {
|
|
207
|
+
const segments = jws.split('.');
|
|
208
|
+
if (segments.length !== 3) {
|
|
209
|
+
throw new Error(
|
|
210
|
+
`Invalid compact JWS: expected 3 segments, got ${segments.length}`
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/** Shared crypto option types used by legacy exports and the Crypto namespace. */
|
|
2
|
+
export type KeyAgreementAlgorithm = 'X25519' | 'ECDH' | (string & {});
|
|
3
|
+
|
|
4
|
+
export type KeyType = 'OKP' | 'EC' | (string & {});
|
|
5
|
+
|
|
6
|
+
export type EncryptionKeyAlgorithm = 'AES-256' | 'AES' | (string & {});
|
|
7
|
+
|
|
8
|
+
export type HmacAlgorithm =
|
|
9
|
+
| 'HMAC-SHA-256'
|
|
10
|
+
| 'HMAC-SHA-384'
|
|
11
|
+
| 'HMAC-SHA-512'
|
|
12
|
+
| 'HmacSHA256'
|
|
13
|
+
| 'HmacSHA384'
|
|
14
|
+
| 'HmacSHA512'
|
|
15
|
+
| (string & {});
|
|
16
|
+
|
|
17
|
+
export type CipherAlgorithm = 'AES-GCM' | 'AES/GCM/NoPadding' | (string & {});
|
|
18
|
+
|
|
19
|
+
export interface CryptoOptions {
|
|
20
|
+
keyAgreementAlgorithm?: KeyAgreementAlgorithm;
|
|
21
|
+
keyType?: KeyType;
|
|
22
|
+
encryptionKeyAlgorithm?: EncryptionKeyAlgorithm;
|
|
23
|
+
hmacAlgorithm?: HmacAlgorithm;
|
|
24
|
+
cipher?: CipherAlgorithm;
|
|
25
|
+
tagLength?: number;
|
|
26
|
+
ivLength?: number;
|
|
27
|
+
/** @deprecated Use `keyType` instead. */
|
|
28
|
+
keyFactoryAlgorithm?: KeyType;
|
|
29
|
+
/** @deprecated Use `hmacAlgorithm` instead. */
|
|
30
|
+
hmacKeyAlgorithm?: HmacAlgorithm;
|
|
31
|
+
/** @deprecated Use `cipher` instead. */
|
|
32
|
+
cipherTransformation?: CipherAlgorithm;
|
|
33
|
+
/** @deprecated Use `tagLength` instead. */
|
|
34
|
+
gcmTagLength?: number;
|
|
35
|
+
/** @deprecated Use `ivLength` instead. */
|
|
36
|
+
gcmIvLength?: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function toNativeCryptoOptions(options?: CryptoOptions | null) {
|
|
40
|
+
return {
|
|
41
|
+
keyAgreementAlgorithm: options?.keyAgreementAlgorithm ?? 'X25519',
|
|
42
|
+
keyFactoryAlgorithm: options?.keyType ?? options?.keyFactoryAlgorithm ?? 'OKP',
|
|
43
|
+
encryptionKeyAlgorithm: options?.encryptionKeyAlgorithm ?? 'AES-256',
|
|
44
|
+
hmacKeyAlgorithm: options?.hmacAlgorithm ?? options?.hmacKeyAlgorithm ?? 'HMAC-SHA-512',
|
|
45
|
+
cipherTransformation: options?.cipher ?? options?.cipherTransformation ?? 'AES-GCM',
|
|
46
|
+
gcmTagLength: options?.tagLength ?? options?.gcmTagLength ?? 128,
|
|
47
|
+
gcmIvLength: options?.ivLength ?? options?.gcmIvLength ?? 12,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { NativeModules, Platform } from 'react-native';
|
|
2
|
+
|
|
3
|
+
const LINKING_ERROR =
|
|
4
|
+
`The package 'react-native-security-suite' doesn't seem to be linked. Make sure: \n\n` +
|
|
5
|
+
Platform.select({ ios: "- You have run 'pod install'\n", default: '' }) +
|
|
6
|
+
'- You rebuilt the app after installing the package\n' +
|
|
7
|
+
'- You are not using Expo managed workflow\n';
|
|
8
|
+
|
|
9
|
+
export interface SecuritySuiteNativeModule {
|
|
10
|
+
getPublicKey(): Promise<string>;
|
|
11
|
+
getSharedKey(serverPK: string, options: Record<string, unknown>): Promise<string>;
|
|
12
|
+
establishSharedKey?(
|
|
13
|
+
serverPK: string,
|
|
14
|
+
options: Record<string, unknown>
|
|
15
|
+
): Promise<void>;
|
|
16
|
+
runtimeDetect(): Promise<Record<string, unknown>>;
|
|
17
|
+
appIntegrityVerify(): Promise<Record<string, unknown>>;
|
|
18
|
+
deviceGetEnvironment(): Promise<Record<string, unknown>>;
|
|
19
|
+
deviceHasSecurityRisk(): Promise<boolean>;
|
|
20
|
+
[key: string]: unknown;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function getNativeModule(): SecuritySuiteNativeModule {
|
|
24
|
+
const module = NativeModules.SecuritySuite as
|
|
25
|
+
| SecuritySuiteNativeModule
|
|
26
|
+
| undefined;
|
|
27
|
+
|
|
28
|
+
if (module) {
|
|
29
|
+
return module;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return new Proxy({} as SecuritySuiteNativeModule, {
|
|
33
|
+
get() {
|
|
34
|
+
throw new Error(LINKING_ERROR);
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/** Phase 2+ namespace placeholder — fetch remains on legacy export for v0.9 compat. */
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AppIntegrityReport,
|
|
3
|
+
DeviceEnvironment,
|
|
4
|
+
RiskLevel,
|
|
5
|
+
RuntimeThreatReport,
|
|
6
|
+
} from '../types/detection';
|
|
7
|
+
|
|
8
|
+
export function computeRiskScore(input: {
|
|
9
|
+
isRooted: boolean;
|
|
10
|
+
isJailbroken: boolean;
|
|
11
|
+
runtime: RuntimeThreatReport;
|
|
12
|
+
app: AppIntegrityReport;
|
|
13
|
+
environment: DeviceEnvironment;
|
|
14
|
+
}): { riskScore: number; riskLevel: RiskLevel } {
|
|
15
|
+
let riskScore = 0;
|
|
16
|
+
|
|
17
|
+
if (input.isRooted || input.isJailbroken) {
|
|
18
|
+
riskScore += 40;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (input.runtime.fridaDetected) {
|
|
22
|
+
riskScore += 40;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (input.runtime.xposedDetected) {
|
|
26
|
+
riskScore += 40;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (input.runtime.substrateDetected) {
|
|
30
|
+
riskScore += 40;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (input.runtime.debuggerAttached) {
|
|
34
|
+
riskScore += 20;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (input.environment.isEmulator || input.environment.isSimulator) {
|
|
38
|
+
riskScore += 20;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (input.app.tampered) {
|
|
42
|
+
riskScore += 50;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const riskLevel: RiskLevel =
|
|
46
|
+
riskScore >= 70 ? 'high' : riskScore >= 30 ? 'medium' : 'low';
|
|
47
|
+
|
|
48
|
+
return { riskScore: Math.min(100, riskScore), riskLevel };
|
|
49
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { getNativeModule } from '../native/bridge';
|
|
2
|
+
import type { RuntimeThreatReport } from '../types/detection';
|
|
3
|
+
|
|
4
|
+
function parseRuntimeReport(raw: Record<string, unknown>): RuntimeThreatReport {
|
|
5
|
+
const report: RuntimeThreatReport = {
|
|
6
|
+
debuggerAttached: Boolean(raw.debuggerAttached),
|
|
7
|
+
fridaDetected: Boolean(raw.fridaDetected),
|
|
8
|
+
suspiciousLibraries: Array.isArray(raw.suspiciousLibraries)
|
|
9
|
+
? raw.suspiciousLibraries.filter(
|
|
10
|
+
(item): item is string => typeof item === 'string'
|
|
11
|
+
)
|
|
12
|
+
: [],
|
|
13
|
+
suspiciousPorts: Array.isArray(raw.suspiciousPorts)
|
|
14
|
+
? raw.suspiciousPorts.filter(
|
|
15
|
+
(item): item is number => typeof item === 'number'
|
|
16
|
+
)
|
|
17
|
+
: [],
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
if (raw.xposedDetected !== undefined) {
|
|
21
|
+
report.xposedDetected = Boolean(raw.xposedDetected);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (raw.substrateDetected !== undefined) {
|
|
25
|
+
report.substrateDetected = Boolean(raw.substrateDetected);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (raw.magiskDetected !== undefined) {
|
|
29
|
+
report.magiskDetected = Boolean(raw.magiskDetected);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return report;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const RuntimeSecurity = {
|
|
36
|
+
detect(): Promise<RuntimeThreatReport> {
|
|
37
|
+
return getNativeModule()
|
|
38
|
+
.runtimeDetect()
|
|
39
|
+
.then((result) => parseRuntimeReport(result));
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export type { RuntimeThreatReport };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Platform } from 'react-native';
|
|
2
|
+
|
|
3
|
+
import { AppIntegrity } from '../integrity';
|
|
4
|
+
import { DeviceSecurity } from '../device';
|
|
5
|
+
import { RuntimeSecurity } from '../runtime';
|
|
6
|
+
import { computeRiskScore } from '../risk/score';
|
|
7
|
+
import type { SecurityReport } from '../types/detection';
|
|
8
|
+
|
|
9
|
+
export const SecuritySuite = {
|
|
10
|
+
async getSecurityReport(): Promise<SecurityReport> {
|
|
11
|
+
const [runtime, app, environment, isCompromised] = await Promise.all([
|
|
12
|
+
RuntimeSecurity.detect(),
|
|
13
|
+
AppIntegrity.verify(),
|
|
14
|
+
DeviceSecurity.getEnvironment(),
|
|
15
|
+
DeviceSecurity.isCompromised(),
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
const isRooted = Platform.OS === 'android' && isCompromised;
|
|
19
|
+
const isJailbroken = Platform.OS === 'ios' && isCompromised;
|
|
20
|
+
|
|
21
|
+
const { riskScore, riskLevel } = computeRiskScore({
|
|
22
|
+
isRooted,
|
|
23
|
+
isJailbroken,
|
|
24
|
+
runtime,
|
|
25
|
+
app,
|
|
26
|
+
environment,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
device: {
|
|
31
|
+
isRooted,
|
|
32
|
+
isJailbroken,
|
|
33
|
+
isEmulator: environment.isEmulator,
|
|
34
|
+
isSimulator: environment.isSimulator,
|
|
35
|
+
environmentIndicators: environment.indicators,
|
|
36
|
+
},
|
|
37
|
+
runtime,
|
|
38
|
+
app,
|
|
39
|
+
riskScore,
|
|
40
|
+
riskLevel,
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export type { SecurityReport };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/** Phase 2+ namespace placeholder — SecureStorage remains on the legacy export for v0.9 compat. */
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export interface RuntimeThreatReport {
|
|
2
|
+
debuggerAttached: boolean;
|
|
3
|
+
fridaDetected: boolean;
|
|
4
|
+
xposedDetected?: boolean;
|
|
5
|
+
substrateDetected?: boolean;
|
|
6
|
+
magiskDetected?: boolean;
|
|
7
|
+
suspiciousLibraries: string[];
|
|
8
|
+
suspiciousPorts: number[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type BuildType = 'debug' | 'release' | 'testflight';
|
|
12
|
+
|
|
13
|
+
export interface AppIntegrityReport {
|
|
14
|
+
validSignature: boolean;
|
|
15
|
+
installerTrusted?: boolean;
|
|
16
|
+
debuggable: boolean;
|
|
17
|
+
tampered: boolean;
|
|
18
|
+
buildType: BuildType;
|
|
19
|
+
signingCertificateSha256?: string;
|
|
20
|
+
installerPackage?: string | null;
|
|
21
|
+
bundleIdentifier?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface DeviceEnvironment {
|
|
25
|
+
isEmulator: boolean;
|
|
26
|
+
isSimulator: boolean;
|
|
27
|
+
indicators: string[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface DeviceSecurityReport {
|
|
31
|
+
isRooted: boolean;
|
|
32
|
+
isJailbroken: boolean;
|
|
33
|
+
isEmulator: boolean;
|
|
34
|
+
isSimulator: boolean;
|
|
35
|
+
environmentIndicators: string[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type RiskLevel = 'low' | 'medium' | 'high';
|
|
39
|
+
|
|
40
|
+
export interface SecurityReport {
|
|
41
|
+
device: DeviceSecurityReport;
|
|
42
|
+
runtime: RuntimeThreatReport;
|
|
43
|
+
app: AppIntegrityReport;
|
|
44
|
+
riskScore: number;
|
|
45
|
+
riskLevel: RiskLevel;
|
|
46
|
+
}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
package com.securitysuite;
|
|
2
|
-
|
|
3
|
-
import android.util.Base64;
|
|
4
|
-
|
|
5
|
-
import java.security.SecureRandom;
|
|
6
|
-
import java.util.Arrays;
|
|
7
|
-
|
|
8
|
-
import javax.crypto.Cipher;
|
|
9
|
-
import javax.crypto.spec.IvParameterSpec;
|
|
10
|
-
import javax.crypto.spec.SecretKeySpec;
|
|
11
|
-
|
|
12
|
-
public class StorageEncryption {
|
|
13
|
-
|
|
14
|
-
public static String encrypt(String input, String encryptionKey, Boolean hardEncryption) {
|
|
15
|
-
try {
|
|
16
|
-
byte[] iv = new byte[16];
|
|
17
|
-
if (hardEncryption) {
|
|
18
|
-
new SecureRandom().nextBytes(iv);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
|
22
|
-
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(encryptionKey.getBytes("utf-8"), "AES"), new IvParameterSpec(iv));
|
|
23
|
-
byte[] cipherText = cipher.doFinal(input.getBytes("utf-8"));
|
|
24
|
-
byte[] ivAndCipherText = getCombinedArray(iv, cipherText);
|
|
25
|
-
return Base64.encodeToString(ivAndCipherText, Base64.NO_WRAP);
|
|
26
|
-
} catch (Exception e) {
|
|
27
|
-
return null;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
public static String decrypt(String encoded, String encryptionKey) {
|
|
32
|
-
try {
|
|
33
|
-
byte[] ivAndCipherText = Base64.decode(encoded, Base64.NO_WRAP);
|
|
34
|
-
byte[] iv = Arrays.copyOfRange(ivAndCipherText, 0, 16);
|
|
35
|
-
byte[] cipherText = Arrays.copyOfRange(ivAndCipherText, 16, ivAndCipherText.length);
|
|
36
|
-
|
|
37
|
-
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
|
38
|
-
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(encryptionKey.getBytes("utf-8"), "AES"), new IvParameterSpec(iv));
|
|
39
|
-
return new String(cipher.doFinal(cipherText), "utf-8");
|
|
40
|
-
} catch (Exception e) {
|
|
41
|
-
return null;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
private static byte[] getCombinedArray(byte[] one, byte[] two) {
|
|
46
|
-
byte[] combined = new byte[one.length + two.length];
|
|
47
|
-
for (int i = 0; i < combined.length; ++i) {
|
|
48
|
-
combined[i] = i < one.length ? one[i] : two[i - one.length];
|
|
49
|
-
}
|
|
50
|
-
return combined;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// SoftEncryption.swift
|
|
3
|
-
// EncryptionSecurity
|
|
4
|
-
//
|
|
5
|
-
// Created by Mohammad on 5/9/1401 AP.
|
|
6
|
-
// Copyright © 1401 AP Facebook. All rights reserved.
|
|
7
|
-
//
|
|
8
|
-
|
|
9
|
-
import Foundation
|
|
10
|
-
import CryptoKit
|
|
11
|
-
|
|
12
|
-
@available(iOS 13.0, *)
|
|
13
|
-
class StorageEncryption {
|
|
14
|
-
let nonce = try! AES.GCM.Nonce(data: Data(base64Encoded: "bj1nixTVoYpSvpdA")!)
|
|
15
|
-
|
|
16
|
-
func encrypt(plain: String, encryptionKey: String, hardEncryption: Bool) throws -> String {
|
|
17
|
-
do {
|
|
18
|
-
guard let encryptionKeyData = Data(base64Encoded: encryptionKey) else {
|
|
19
|
-
return "Could not decode encryptionKey text: \(encryptionKey)"
|
|
20
|
-
}
|
|
21
|
-
guard let plainData = plain.data(using: .utf8) else {
|
|
22
|
-
return "Could not decode plain text: \(plain)"
|
|
23
|
-
}
|
|
24
|
-
let symmetricKey = SymmetricKey(data: encryptionKeyData)
|
|
25
|
-
var encrypted = try AES.GCM.seal(plainData, using: symmetricKey, nonce: nonce, authenticating: ASN1.ec256)
|
|
26
|
-
if (hardEncryption) {
|
|
27
|
-
encrypted = try AES.GCM.seal(plainData, using: symmetricKey)
|
|
28
|
-
}
|
|
29
|
-
return encrypted.combined!.base64EncodedString()
|
|
30
|
-
} catch let error {
|
|
31
|
-
return "Error encrypting message: \(error.localizedDescription)"
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
func decrypt(decoded: String, encryptionKey: String, hardEncryption: Bool) throws -> String {
|
|
36
|
-
do {
|
|
37
|
-
guard let encryptionKeyData = Data(base64Encoded: encryptionKey) else {
|
|
38
|
-
return "Could not decode encryption key: \(encryptionKey)"
|
|
39
|
-
}
|
|
40
|
-
guard let decodedData = Data(base64Encoded: decoded) else {
|
|
41
|
-
return "Could not decode decoded text: \(decoded)"
|
|
42
|
-
}
|
|
43
|
-
let symmetricKey = SymmetricKey(data: encryptionKeyData)
|
|
44
|
-
if (hardEncryption) {
|
|
45
|
-
let sealedBoxToOpen = try AES.GCM.SealedBox(combined: decodedData)
|
|
46
|
-
let decrypted = try AES.GCM.open(sealedBoxToOpen, using: symmetricKey)
|
|
47
|
-
return String(data: decrypted, encoding: .utf8)!
|
|
48
|
-
} else {
|
|
49
|
-
let sealedBoxRestored = try AES.GCM.SealedBox(combined: decodedData)
|
|
50
|
-
let decrypted = try AES.GCM.open(sealedBoxRestored, using: symmetricKey, authenticating: ASN1.ec256)
|
|
51
|
-
return String(data: decrypted, encoding: .utf8)!
|
|
52
|
-
}
|
|
53
|
-
} catch let error {
|
|
54
|
-
return "Error decrypting message: \(error.localizedDescription)"
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
public extension Data {
|
|
60
|
-
init?(hexString: String) {
|
|
61
|
-
let len = hexString.count / 2
|
|
62
|
-
var data = Data(capacity: len)
|
|
63
|
-
var i = hexString.startIndex
|
|
64
|
-
for _ in 0..<len {
|
|
65
|
-
let j = hexString.index(i, offsetBy: 2)
|
|
66
|
-
let bytes = hexString[i..<j]
|
|
67
|
-
if var num = UInt8(bytes, radix: 16) {
|
|
68
|
-
data.append(&num, count: 1)
|
|
69
|
-
} else {
|
|
70
|
-
return nil
|
|
71
|
-
}
|
|
72
|
-
i = j
|
|
73
|
-
}
|
|
74
|
-
self = data
|
|
75
|
-
}
|
|
76
|
-
/// Hexadecimal string representation of `Data` object.
|
|
77
|
-
var hexadecimal: String {
|
|
78
|
-
return map { String(format: "%02x", $0) }
|
|
79
|
-
.joined()
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
struct ASN1 {
|
|
84
|
-
static let rsa2048 = Data(base64Encoded: "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A")!
|
|
85
|
-
static let rsa4096 = Data(base64Encoded: "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8A")!
|
|
86
|
-
static let ec256 = Data(base64Encoded: "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgA=")!
|
|
87
|
-
static let ec384 = Data(base64Encoded: "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgA=")!
|
|
88
|
-
static let ec521 = Data(base64Encoded: "MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQ=")!
|
|
89
|
-
}
|