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/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,84 @@
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
+ encryptionKeyAlgorithm: EncryptionKeyAlgorithm;
22
+ keyType?: KeyType;
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
+ function requireCryptoOption<T>(
40
+ value: T | undefined | null,
41
+ key: string
42
+ ): T {
43
+ if (value === undefined || value === null || value === '') {
44
+ throw new Error(`Missing required crypto option: ${key}`);
45
+ }
46
+ return value;
47
+ }
48
+
49
+ export function toNativeCryptoOptions(options?: CryptoOptions | null) {
50
+ if (!options) {
51
+ throw new Error('Crypto options are required');
52
+ }
53
+
54
+ return {
55
+ keyAgreementAlgorithm: requireCryptoOption(
56
+ options.keyAgreementAlgorithm,
57
+ 'keyAgreementAlgorithm'
58
+ ),
59
+ keyFactoryAlgorithm: requireCryptoOption(
60
+ options.keyType ?? options.keyFactoryAlgorithm,
61
+ 'keyFactoryAlgorithm'
62
+ ),
63
+ encryptionKeyAlgorithm: requireCryptoOption(
64
+ options.encryptionKeyAlgorithm,
65
+ 'encryptionKeyAlgorithm'
66
+ ),
67
+ hmacKeyAlgorithm: requireCryptoOption(
68
+ options.hmacAlgorithm ?? options.hmacKeyAlgorithm,
69
+ 'hmacKeyAlgorithm'
70
+ ),
71
+ cipherTransformation: requireCryptoOption(
72
+ options.cipher ?? options.cipherTransformation,
73
+ 'cipherTransformation'
74
+ ),
75
+ gcmTagLength: requireCryptoOption(
76
+ options.tagLength ?? options.gcmTagLength,
77
+ 'gcmTagLength'
78
+ ),
79
+ gcmIvLength: requireCryptoOption(
80
+ options.ivLength ?? options.gcmIvLength,
81
+ 'gcmIvLength'
82
+ ),
83
+ };
84
+ }
@@ -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,2 @@
1
+ /** Phase 2+ namespace placeholder — SecureView remains on legacy export for v0.9 compat. */
2
+ export { SecureView } from '../SecureView';
@@ -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
- }