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/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
5
  [![Downloads](https://img.shields.io/npm/dm/react-native-security-suite.svg)](https://www.npmjs.com/package/react-native-security-suite)
6
6
 
7
- **Comprehensive security solutions for React Native applications** - Protect your mobile apps with advanced security features including root/jailbreak detection, SSL certificate pinning, encryption, secure storage, screenshot protection, and network monitoring.
7
+ **Comprehensive security solutions for React Native applications** Protect your mobile apps with root/jailbreak detection, SSL certificate pinning, RFC 7515 JWS request signing, hardware-backed secure storage, configurable ECDH key exchange, screenshot protection, and network monitoring.
8
8
 
9
9
  <div style="display: flex; flex-direction: row; justify-content: center; align-items: center; gap: 20px;">
10
10
  <img src="https://raw.githubusercontent.com/mohamadnavabi/react-native-security-suite/master/pulse.gif" alt="iOS Pulse Network Monitor" width="200" />
@@ -23,17 +23,18 @@
23
23
 
24
24
  ### Data Security & Encryption
25
25
 
26
- - **Text Encryption/Decryption**: Secure data encryption with multiple algorithms
27
- - **Secure Storage**: Encrypted local storage with AsyncStorage integration
28
- - **Diffie-Hellman Key Exchange**: Secure key generation and sharing
29
- - **Hard & Soft Encryption**: Multiple encryption levels for different security needs
26
+ - **Secure Storage**: Hardware-backed encrypted storage (Keychain on iOS, EncryptedSharedPreferences on Android)
27
+ - **Diffie-Hellman Key Exchange**: Application-defined algorithms and GCM parameters via `CryptoOptions`
28
+ - **Shared-Key Encryption**: AES-GCM encrypt/decrypt using a derived shared secret
29
+ - **Obfuscation**: Local string obfuscation with an explicit secret (not for credentials at rest)
30
30
 
31
31
  ### Network Security & Monitoring
32
32
 
33
+ - **JWS Request Signing (RFC 7515)**: HMAC-signed compact JWS tokens for `fetch` requests (HS256/HS384/HS512)
34
+ - **SSL Certificate Pinning**: Pin SPKI SHA-256 hashes with domain allowlists
33
35
  - **Network Logger**: Built-in request/response logging
34
36
  - **Android Chucker Integration**: Advanced network debugging
35
37
  - **iOS Pulse Integration**: Network monitoring for iOS
36
- - **SSL Pinning with Custom Certificates**: Enhanced security for API calls
37
38
 
38
39
  ## 📱 Supported Platforms
39
40
 
@@ -43,18 +44,16 @@
43
44
 
44
45
  ## 🛠 Installation
45
46
 
46
- > **Note:** You must add `@react-native-async-storage/async-storage` in your app's `package.json` as well. React Native only autolinks native modules that are listed in your project's dependencies, so AsyncStorage must be a direct dependency of your app.
47
-
48
47
  ### Using Yarn
49
48
 
50
49
  ```bash
51
- yarn add react-native-security-suite @react-native-async-storage/async-storage
50
+ yarn add react-native-security-suite
52
51
  ```
53
52
 
54
53
  ### Using NPM
55
54
 
56
55
  ```bash
57
- npm install react-native-security-suite @react-native-async-storage/async-storage
56
+ npm install react-native-security-suite
58
57
  ```
59
58
 
60
59
  ### iOS Setup
@@ -63,6 +62,34 @@ npm install react-native-security-suite @react-native-async-storage/async-storag
63
62
  cd ios && pod install
64
63
  ```
65
64
 
65
+ ## 📁 Project Structure
66
+
67
+ The public API is exported from `src/index.tsx`. JWS validation and normalization live in a dedicated module so TypeScript and native code share the same contract.
68
+
69
+ ```
70
+ src/
71
+ ├── index.tsx # Public API (fetch, crypto, SecureStorage, SecureView)
72
+ ├── jws.ts # JWS types, validation, payload normalization
73
+ ├── SecureView.tsx # Screenshot-protected view component
74
+ └── helpers.ts # Internal utilities
75
+
76
+ android/src/main/java/com/securitysuite/
77
+ ├── SecureStorageNative.java # EncryptedSharedPreferences + Android Keystore
78
+ ├── JWSGenerator.java # RFC 7515 compact JWS (sign + verify)
79
+ ├── JwsFetchPayload.java # Default fetch signing payload builder
80
+ ├── SecuritySuiteModule.java
81
+ └── Sslpinning.java # fetch + SSL pinning + JWS header injection
82
+
83
+ ios/
84
+ ├── SecureStorageNative.swift # Keychain-backed secure storage
85
+ ├── KeychainHelper.swift # Keychain read/write/query helpers
86
+ ├── JWSGenerator.swift # RFC 7515 compact JWS (sign + verify)
87
+ ├── SecuritySuite.swift # Native module + fetch
88
+ └── SslPinning.swift
89
+ ```
90
+
91
+ **JWS flow:** JavaScript validates options in `src/jws.ts` (secret, algorithm, headers, payload shape), normalizes the payload to the exact UTF-8 signing string, then calls native `generateJWS` on Android/iOS. For `fetch`, native code builds the signing payload when `jws.payload` is omitted, signs it, and attaches the compact token to the request header.
92
+
66
93
  ## 📖 Usage Examples
67
94
 
68
95
  ### 1. Root/Jailbreak Detection
@@ -108,33 +135,22 @@ const SensitiveScreen = () => {
108
135
  };
109
136
  ```
110
137
 
111
- ### 3. Text Encryption & Decryption
138
+ ### 3. Obfuscation (local only)
112
139
 
113
- Secure your data with multiple encryption methods:
140
+ Obfuscation requires an explicit secret and is intended for non-sensitive local encoding — not for credentials, tokens, or PII at rest. Use `SecureStorage` for persisted secrets.
114
141
 
115
142
  ```javascript
116
- import { encrypt, decrypt } from 'react-native-security-suite';
117
-
118
- const handleEncryption = async () => {
119
- // Soft encryption (faster, less secure)
120
- const softEncrypted = await encrypt('Sensitive data', false);
121
- console.log('Soft encrypted:', softEncrypted);
122
-
123
- const softDecrypted = await decrypt(softEncrypted, false);
124
- console.log('Soft decrypted:', softDecrypted);
143
+ import { obfuscate, deobfuscate } from 'react-native-security-suite';
125
144
 
126
- // Hard encryption (slower, more secure)
127
- const hardEncrypted = await encrypt('Highly sensitive data', true);
128
- console.log('Hard encrypted:', hardEncrypted);
129
-
130
- const hardDecrypted = await decrypt(hardEncrypted, true);
131
- console.log('Hard decrypted:', hardDecrypted);
132
- };
145
+ const encoded = await obfuscate('local-cache-value', 'app-specific-secret');
146
+ const decoded = await deobfuscate(encoded, 'app-specific-secret');
133
147
  ```
134
148
 
149
+ > `encrypt()` / `decrypt()` remain available but are deprecated. They now require an explicit `secretKey` and should be replaced with `obfuscate()` / `deobfuscate()` or `SecureStorage`.
150
+
135
151
  ### 4. Secure Storage
136
152
 
137
- Store sensitive data securely with automatic encryption:
153
+ Store sensitive data in hardware-backed encrypted storage. On **iOS**, values are stored in the Keychain (`kSecClassGenericPassword`). On **Android**, values are encrypted with **EncryptedSharedPreferences** backed by Android Keystore (`AES256_GCM`).
138
154
 
139
155
  ```javascript
140
156
  import { SecureStorage } from 'react-native-security-suite';
@@ -154,60 +170,216 @@ const handleSecureStorage = async () => {
154
170
  })
155
171
  );
156
172
 
157
- // Retrieve and decrypt data
173
+ // Retrieve data
158
174
  const token = await SecureStorage.getItem('userToken');
159
175
  const credentials = await SecureStorage.getItem('userCredentials');
176
+ const allKeys = await SecureStorage.getAllKeys();
160
177
 
161
178
  console.log('Retrieved token:', token);
162
179
  console.log('Retrieved credentials:', JSON.parse(credentials));
180
+ console.log('Stored keys:', allKeys);
163
181
 
164
182
  // Remove sensitive data
165
183
  await SecureStorage.removeItem('userToken');
184
+
185
+ // Clear all secure storage entries
186
+ await SecureStorage.clear();
166
187
  } catch (error) {
167
188
  console.error('Secure storage error:', error);
168
189
  }
169
190
  };
170
191
  ```
171
192
 
193
+ #### Security Guarantee
194
+
195
+ `SecureStorage` no longer uses AsyncStorage or any JavaScript-side persistence. All keys and values are:
196
+
197
+ - **Encrypted at rest** — AES-GCM (Android) / Keychain-protected blobs (iOS)
198
+ - **Hardware-backed where available** — Android Keystore master key; iOS Keychain with `kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly`
199
+ - **System-managed keys** — No hardcoded salts or encryption keys in app code; the OS generates and protects master keys
200
+
201
+ If a native read/write fails (for example, KeyStore initialization errors on Android), the Promise rejects with a clear `Secure storage operation failed` error.
202
+
172
203
  ### 5. Diffie-Hellman Key Exchange
173
204
 
174
- Implement secure key exchange for encrypted communications:
205
+ Derive a shared encryption key from the client and server public keys. **All cryptographic algorithms and GCM lengths must be supplied by your application** — the native layer does not apply defaults.
175
206
 
176
- ```javascript
207
+ Use **`Crypto.establishSharedKey()`** so the derived key stays in native memory. Pass the same `cryptoOptions` to `encryptBySharedKey` / `decryptBySharedKey`.
208
+
209
+ ```typescript
177
210
  import {
211
+ Crypto,
178
212
  getPublicKey,
179
- getSharedKey,
180
213
  encryptBySharedKey,
181
214
  decryptBySharedKey,
215
+ type CryptoOptions,
182
216
  } from 'react-native-security-suite';
183
217
 
218
+ /**
219
+ * Pick one value per field. Unions match exported package types.
220
+ * Use JCA names (e.g. 'HmacSHA256', 'AES/GCM/NoPadding') for native crypto.
221
+ */
222
+ const cryptoOptions: CryptoOptions = {
223
+ keyAgreementAlgorithm: 'X25519', // 'X25519' | 'ECDH'
224
+ keyType: 'EC', // 'OKP' | 'EC'
225
+ encryptionKeyAlgorithm: 'AES-256', // 'AES-256' | 'AES'
226
+ hmacAlgorithm: 'HmacSHA256', // 'HmacSHA256' | 'HmacSHA384' | 'HmacSHA512' | 'HMAC-SHA-256' | 'HMAC-SHA-384' | 'HMAC-SHA-512'
227
+ cipher: 'AES-GCM', // 'AES-GCM' | 'AES/GCM/NoPadding'
228
+ tagLength: 256, // GCM auth tag size in bits
229
+ ivLength: 12, // GCM IV size in bytes
230
+ };
231
+
184
232
  const handleKeyExchange = async () => {
185
- try {
186
- // Generate client public key
187
- const clientPublicKey = await getPublicKey();
188
- console.log('Client public key:', clientPublicKey);
233
+ const clientPublicKey = await getPublicKey();
234
+ const serverPublicKey = 'SERVER_PUBLIC_KEY_FROM_API';
189
235
 
190
- // Send to server and receive server's public key
191
- const serverPublicKey = 'SERVER_PUBLIC_KEY_FROM_API';
236
+ // Keeps the shared key in native memory (recommended)
237
+ await Crypto.establishSharedKey(serverPublicKey, cryptoOptions);
192
238
 
193
- // Generate shared secret key
194
- const sharedKey = await getSharedKey(serverPublicKey);
195
- console.log('Shared key generated:', sharedKey);
239
+ const encryptedMessage = await encryptBySharedKey('Secret message', cryptoOptions);
240
+ const decryptedMessage = await decryptBySharedKey(encryptedMessage, cryptoOptions);
241
+ };
242
+ ```
196
243
 
197
- // Encrypt data with shared key
198
- const encryptedMessage = await encryptBySharedKey('Secret message');
199
- console.log('Encrypted message:', encryptedMessage);
244
+ **Legacy API** (returns the derived key to JavaScript — avoid in production):
200
245
 
201
- // Decrypt data with shared key
202
- const decryptedMessage = await decryptBySharedKey(encryptedMessage);
203
- console.log('Decrypted message:', decryptedMessage);
204
- } catch (error) {
205
- console.error('Key exchange error:', error);
206
- }
246
+ ```javascript
247
+ import { getSharedKey } from 'react-native-security-suite';
248
+
249
+ const sharedKeyBase64 = await getSharedKey(serverPublicKey, cryptoOptions);
250
+ ```
251
+
252
+ #### Algorithm and length options
253
+
254
+ | Option | Required | Allowed values | Description |
255
+ |--------|----------|----------------|-------------|
256
+ | `keyAgreementAlgorithm` | yes | `'X25519' \| 'ECDH'` | Key-agreement algorithm passed to native `KeyAgreement` |
257
+ | `keyType` | yes* | `'OKP' \| 'EC'` | Key-factory algorithm for the server public key (`keyFactoryAlgorithm` alias) |
258
+ | `encryptionKeyAlgorithm` | yes | `'AES-256' \| 'AES'` | Symmetric key algorithm for the derived encryption key |
259
+ | `hmacAlgorithm` | yes* | `'HmacSHA256' \| 'HmacSHA384' \| 'HmacSHA512' \| 'HMAC-SHA-256' \| 'HMAC-SHA-384' \| 'HMAC-SHA-512'` | HMAC for HKDF and MAC key material (`hmacKeyAlgorithm` alias) |
260
+ | `cipher` | yes* | `'AES-GCM' \| 'AES/GCM/NoPadding'` | Cipher transformation for encrypt/decrypt (`cipherTransformation` alias) |
261
+ | `tagLength` | yes* | `number` (e.g. `128`) | GCM authentication tag length in **bits** (`gcmTagLength` alias) |
262
+ | `ivLength` | yes* | `number` (e.g. `12`) | GCM IV/nonce length in **bytes** (`gcmIvLength` alias) |
263
+
264
+ \*Provide via the preferred name or its deprecated alias (see [API Reference](#cryptooptions)).
265
+
266
+ Use **JCA-style names** on both Android and iOS (e.g. `HmacSHA256`, not `HMAC-SHA-256`). Omitting any required option throws before native code runs.
267
+
268
+ **Typical production profile (P-256 ECDH + AES-256-GCM):**
269
+
270
+ ```typescript
271
+ import type { CryptoOptions } from 'react-native-security-suite';
272
+
273
+ const cryptoOptions: CryptoOptions = {
274
+ keyAgreementAlgorithm: 'ECDH',
275
+ keyType: 'EC',
276
+ encryptionKeyAlgorithm: 'AES',
277
+ hmacAlgorithm: 'HmacSHA256',
278
+ cipher: 'AES/GCM/NoPadding',
279
+ tagLength: 128,
280
+ ivLength: 12,
207
281
  };
208
282
  ```
209
283
 
210
- ### 6. SSL Certificate Pinning
284
+ ### 6. JWS Generation (RFC 7515)
285
+
286
+ Generate [RFC 7515](https://datatracker.ietf.org/doc/html/rfc7515) compact JWS tokens. Supported algorithms: **HS256**, **HS384**, **HS512**. An explicit `secret` is always required.
287
+
288
+ **How it works**
289
+
290
+ 1. TypeScript (`src/jws.ts`) validates `secret`, `algorithm`, and custom headers.
291
+ 2. The payload is normalized to the exact UTF-8 string used for signing (objects/arrays are `JSON.stringify`'d; `undefined`/`null`/`''` become an empty payload).
292
+ 3. Native code builds sorted protected headers (always including `alg`), base64url-encodes header and payload, signs `base64url(header).base64url(payload)` with HMAC, and returns the compact token.
293
+
294
+ **Empty payload** (`undefined`, `null`, or `''`) produces three segments with an empty middle segment:
295
+
296
+ ```javascript
297
+ import { generateJWS } from 'react-native-security-suite';
298
+
299
+ const jws = await generateJWS({
300
+ algorithm: 'HS256',
301
+ secret: 'my-temporary-secret',
302
+ headers: { kid: 'key-1' },
303
+ });
304
+
305
+ // compact form: <protectedHeader>.<payload>.<signature>
306
+ // jws.split('.').length === 3
307
+ // jws.split('.')[1] === '' // empty payload segment
308
+ ```
309
+
310
+ **JSON payload:**
311
+
312
+ ```javascript
313
+ const jws = await generateJWS({
314
+ algorithm: 'HS512',
315
+ secret: 'my-temporary-secret',
316
+ payload: { amount: 1000, currency: 'USD' },
317
+ headers: { kid: 'key-1', request_id: 'req-123', typ: 'JWS' },
318
+ });
319
+ ```
320
+
321
+ **Header rules:** keys must match `^[a-zA-Z][a-zA-Z0-9_-]*$`; values must be JSON primitives (`string`, `number`, `boolean`, `null`). String values must be printable ASCII (`0x20`–`0x7E`). If both `algorithm` and `headers.alg` are set, they must match.
322
+
323
+ > **Security note:** HS* JWS on mobile uses a client-side shared secret. It helps with request integrity when combined with TLS, but it is not proof against a fully compromised client.
324
+
325
+ ### 7. Fetch Request Signing with JWS
326
+
327
+ Pass a `jws` object on `fetch` options. The signed token is sent as an HTTP header (default: `X-Request-Signature`).
328
+
329
+ ```javascript
330
+ import { fetch } from 'react-native-security-suite';
331
+
332
+ await fetch('https://api.example.com/payments', {
333
+ method: 'POST',
334
+ headers: { 'Content-Type': 'application/json' },
335
+ body: { amount: 1000 },
336
+ jws: {
337
+ algorithm: 'HS256',
338
+ secret: 'temporary-session-secret',
339
+ headers: {
340
+ kid: 'key-1',
341
+ request_id: 'req-123',
342
+ timestamp: Date.now(),
343
+ nonce: 'unique-nonce-per-request',
344
+ },
345
+ headerName: 'X-Request-Signature',
346
+ detached: false,
347
+ },
348
+ });
349
+ ```
350
+
351
+ **Default signing payload** (when `jws.payload` is omitted): native code builds a sorted JSON object from:
352
+
353
+ | Field | Source |
354
+ |-------|--------|
355
+ | `method` | HTTP method (uppercased) |
356
+ | `path` | URL path (`/` if empty) |
357
+ | `query` | Query string (if present) |
358
+ | `bodyHash` | Base64url SHA-256 of request body (if body present) |
359
+ | `timestamp`, `nonce`, `request_id` | Copied from `jws.headers` when provided |
360
+
361
+ **Explicit fetch payload:** when you set `jws.payload`, it must already be a string (use `JSON.stringify` for objects). For fetch, object payloads are not auto-serialized — only `generateJWS` accepts object payloads directly.
362
+
363
+ ```javascript
364
+ jws: {
365
+ secret: 'temporary-session-secret',
366
+ payload: JSON.stringify({ amount: 1000, currency: 'USD' }),
367
+ }
368
+ ```
369
+
370
+ **Detached fetch signing** — set `jws.detached: true` to send `header..signature` while the raw payload remains in the request body:
371
+
372
+ ```javascript
373
+ jws: {
374
+ secret: 'temporary-session-secret',
375
+ detached: true,
376
+ headers: { kid: 'key-1', request_id: 'req-123' },
377
+ }
378
+ ```
379
+
380
+ **Migration from legacy options:** `options.keyId`, `options.requestId`, and top-level `options.secret` are deprecated. Use `options.jws` with `secret`, `headers.kid`, and `headers.request_id` instead. Legacy signing always used detached HS256 and the `X-JWS-Signature` header.
381
+
382
+ ### 8. SSL Certificate Pinning
211
383
 
212
384
  Secure your API communications with certificate pinning:
213
385
 
@@ -243,7 +415,7 @@ const secureApiCall = async () => {
243
415
  };
244
416
  ```
245
417
 
246
- ### 7. Network Monitoring & Debugging
418
+ ### 9. Network Monitoring & Debugging
247
419
 
248
420
  Monitor network requests in development:
249
421
 
@@ -274,37 +446,87 @@ const monitoredRequest = async () => {
274
446
 
275
447
  ### Security Detection
276
448
 
277
- - `deviceHasSecurityRisk()` - Detect rooted/jailbroken devices
449
+ - `deviceHasSecurityRisk()` Detect rooted/jailbroken devices
278
450
 
279
451
  ### Encryption & Storage
280
452
 
281
- - `encrypt(text, hardEncryption?, secretKey?)` - Encrypt text
282
- - `decrypt(encryptedText, hardEncryption?, secretKey?)` - Decrypt text
283
- - `SecureStorage` - Encrypted storage methods
453
+ - `obfuscate(input, secret)` Local obfuscation with explicit secret
454
+ - `deobfuscate(input, secret)` Reverse `obfuscate`
455
+ - `SecureStorage` — Hardware-backed encrypted storage (`setItem`, `getItem`, `removeItem`, `getAllKeys`, `clear`, `multiGet`, `multiSet`, `multiRemove`)
456
+ - `encrypt(text, hardEncryption?, secretKey?)` — **deprecated**; requires `secretKey`
457
+ - `decrypt(encryptedText, hardEncryption?, secretKey?)` — **deprecated**; requires `secretKey`
284
458
 
285
459
  ### Key Exchange
286
460
 
287
- - `getPublicKey()` - Generate public key
288
- - `getSharedKey(serverPublicKey)` - Generate shared key
289
- - `encryptBySharedKey(text)` - Encrypt with shared key
290
- - `decryptBySharedKey(encryptedText)` - Decrypt with shared key
461
+ - `Crypto.getPublicKey()` Client public key (base64-encoded SPKI / DER)
462
+ - `Crypto.establishSharedKey(serverPublicKey, options)` Derive shared key in native memory; **`options` required**
463
+ - `getPublicKey()` Legacy alias for `Crypto.getPublicKey()`
464
+ - `getSharedKey(serverPublicKey, options)` **Deprecated**; returns derived key to JS; **`options` required**
465
+ - `encryptBySharedKey(text, options)` — Encrypt with derived key; **`options` required**
466
+ - `decryptBySharedKey(encryptedText, options)` — Decrypt with derived key; **`options` required**
467
+
468
+ #### `CryptoOptions`
469
+
470
+ All fields below are **required** on every key-exchange and encrypt/decrypt call. There are no library defaults — define a shared `cryptoOptions` object in your app and reuse it.
471
+
472
+ | Option | Allowed values |
473
+ |--------|----------------|
474
+ | `keyAgreementAlgorithm` | `'X25519' \| 'ECDH'` |
475
+ | `keyType` | `'OKP' \| 'EC'` (alias: `keyFactoryAlgorithm`) |
476
+ | `encryptionKeyAlgorithm` | `'AES-256' \| 'AES'` |
477
+ | `hmacAlgorithm` | `'HmacSHA256' \| 'HmacSHA384' \| 'HmacSHA512' \| 'HMAC-SHA-256' \| 'HMAC-SHA-384' \| 'HMAC-SHA-512'` (alias: `hmacKeyAlgorithm`) |
478
+ | `cipher` | `'AES-GCM' \| 'AES/GCM/NoPadding'` (alias: `cipherTransformation`) |
479
+ | `tagLength` | `number` — GCM tag length in bits (alias: `gcmTagLength`) |
480
+ | `ivLength` | `number` — GCM IV length in bytes (alias: `gcmIvLength`) |
481
+
482
+ Exported types: `CryptoOptions`, `KeyAgreementAlgorithm`, `KeyType`, `EncryptionKeyAlgorithm`, `HmacAlgorithm`, `CipherAlgorithm`.
483
+
484
+ ### JWS
485
+
486
+ - `generateJWS(options: GenerateJWSOptions)` — Generate compact or detached JWS
487
+
488
+ #### `GenerateJWSOptions`
489
+
490
+ | Field | Required | Description |
491
+ |-------|----------|-------------|
492
+ | `secret` | yes | Non-empty HMAC secret |
493
+ | `algorithm` | no | `HS256` (default), `HS384`, or `HS512` |
494
+ | `payload` | no | String, object, array, number, boolean, `null`, or `undefined` |
495
+ | `headers` | no | Custom protected headers (`kid`, `request_id`, etc.) |
496
+
497
+ #### `JwsFetchOptions` (on `fetch` options)
498
+
499
+ | Field | Required | Description |
500
+ |-------|----------|-------------|
501
+ | `secret` | yes | Non-empty HMAC secret |
502
+ | `algorithm` | no | `HS256` (default), `HS384`, or `HS512` |
503
+ | `headers` | no | Protected headers; `timestamp`/`nonce`/`request_id` also feed the default payload |
504
+ | `payload` | no | Explicit signing string; omit to use the default fetch payload |
505
+ | `detached` | no | Detached compact form (default: `false`) |
506
+ | `headerName` | no | Request header name (default: `X-Request-Signature`) |
507
+
508
+ Exported types: `JwsAlgorithm`, `JwsPayload`, `JwsHeaders`, `JwsHeaderValue`, `GenerateJWSOptions`, `JwsFetchOptions`.
291
509
 
292
510
  ### Network Security
293
511
 
294
- - `fetch(url, options, loggerEnabled?)` - Secure fetch with SSL pinning
512
+ - `fetch(url, options, loggerEnabled?)` Secure fetch with SSL pinning and optional JWS signing
513
+
514
+ `fetch` `options` also accepts `certificates` + `validDomains` for SSL pinning, and deprecated top-level `keyId`, `requestId`, `secret` for legacy JWS.
295
515
 
296
516
  ### UI Components
297
517
 
298
- - `SecureView` - Screenshot-protected view component
518
+ - `SecureView` Screenshot-protected view component
299
519
 
300
520
  ## 🛡️ Security Best Practices
301
521
 
302
- 1. **Always validate certificates** - Use SSL pinning for production APIs
303
- 2. **Detect compromised devices** - Check for root/jailbreak before sensitive operations
304
- 3. **Use appropriate encryption levels** - Hard encryption for highly sensitive data
305
- 4. **Protect sensitive UI** - Wrap sensitive content in SecureView
306
- 5. **Monitor network traffic** - Use built-in logging for debugging
307
- 6. **Secure key management** - Implement proper key exchange protocols
522
+ 1. **Always validate certificates** Use SSL pinning for production APIs
523
+ 2. **Detect compromised devices** Check for root/jailbreak before sensitive operations
524
+ 3. **Store secrets in SecureStorage** — Use hardware-backed storage for tokens and credentials
525
+ 4. **Protect sensitive UI** Wrap sensitive content in `SecureView`
526
+ 5. **Sign requests with JWS** Include `timestamp`, `nonce`, and `request_id` in `jws.headers` for replay protection; never embed long-lived secrets in the app binary
527
+ 6. **Use detached JWS for body signing** When the request body is the signed payload, set `jws.detached: true` so the body is not duplicated inside the token
528
+ 7. **Monitor network traffic** — Use built-in logging for debugging only; disable in production
529
+ 8. **Rotate session secrets** — Treat `jws.secret` as a short-lived session or request-scoped value from your backend
308
530
 
309
531
  ## 🐛 Troubleshooting
310
532
 
@@ -76,6 +76,12 @@ android {
76
76
  }
77
77
  }
78
78
 
79
+ testOptions {
80
+ unitTests.all {
81
+ it.useJUnit()
82
+ }
83
+ }
84
+
79
85
  lintOptions {
80
86
  disable "GradleCompatible"
81
87
  }
@@ -90,6 +96,7 @@ android {
90
96
  }
91
97
 
92
98
  buildFeatures {
99
+ buildConfig true
93
100
  dataBinding true
94
101
  }
95
102
  }
@@ -111,5 +118,9 @@ dependencies {
111
118
  implementation "com.squareup.okhttp3:okhttp:4.12.0"
112
119
  implementation "com.scottyab:rootbeer-lib:0.1.0"
113
120
  implementation "com.github.chuckerteam.chucker:library:4.1.0"
121
+ implementation "androidx.security:security-crypto:1.1.0-alpha06"
122
+
123
+ testImplementation "junit:junit:4.13.2"
124
+ testImplementation "com.facebook.react:react-native:+"
114
125
  }
115
126
 
@@ -1,5 +1,5 @@
1
1
  SecuritySuite_kotlinVersion=1.7.0
2
- SecuritySuite_minSdkVersion=21
2
+ SecuritySuite_minSdkVersion=23
3
3
  SecuritySuite_targetSdkVersion=31
4
4
  SecuritySuite_compileSdkVersion=31
5
5
  SecuritySuite_ndkversion=21.4.7075529
@@ -0,0 +1,106 @@
1
+ package com.securitysuite;
2
+
3
+ import com.facebook.react.bridge.ReadableMap;
4
+
5
+ /**
6
+ * Cryptographic parameters supplied by the application.
7
+ */
8
+ public final class CryptoConfig {
9
+ public final String keyAgreementAlgorithm;
10
+ public final String keyFactoryAlgorithm;
11
+ public final String encryptionKeyAlgorithm;
12
+ public final String hmacKeyAlgorithm;
13
+ public final String cipherTransformation;
14
+ public final int gcmTagLength;
15
+ public final int gcmIvLength;
16
+
17
+ private CryptoConfig(
18
+ String keyAgreementAlgorithm,
19
+ String keyFactoryAlgorithm,
20
+ String encryptionKeyAlgorithm,
21
+ String hmacKeyAlgorithm,
22
+ String cipherTransformation,
23
+ int gcmTagLength,
24
+ int gcmIvLength
25
+ ) {
26
+ this.keyAgreementAlgorithm = keyAgreementAlgorithm;
27
+ this.keyFactoryAlgorithm = keyFactoryAlgorithm;
28
+ this.encryptionKeyAlgorithm = encryptionKeyAlgorithm;
29
+ this.hmacKeyAlgorithm = hmacKeyAlgorithm;
30
+ this.cipherTransformation = cipherTransformation;
31
+ this.gcmTagLength = gcmTagLength;
32
+ this.gcmIvLength = gcmIvLength;
33
+ }
34
+
35
+ public static CryptoConfig fromReadableMap(ReadableMap options) {
36
+ if (options == null) {
37
+ throw new IllegalArgumentException("Crypto options are required");
38
+ }
39
+
40
+ return new CryptoConfig(
41
+ requireString(options, "keyAgreementAlgorithm"),
42
+ requireString(options, "keyFactoryAlgorithm"),
43
+ requireString(options, "encryptionKeyAlgorithm"),
44
+ requireString(options, "hmacKeyAlgorithm"),
45
+ requireString(options, "cipherTransformation"),
46
+ requireInt(options, "gcmTagLength"),
47
+ requireInt(options, "gcmIvLength")
48
+ );
49
+ }
50
+
51
+ public CryptoConfig merge(ReadableMap options) {
52
+ return options == null ? this : fromReadableMap(mergeMaps(this, options));
53
+ }
54
+
55
+ private static ReadableMap mergeMaps(CryptoConfig base, ReadableMap overrides) {
56
+ com.facebook.react.bridge.WritableMap map = com.facebook.react.bridge.Arguments.createMap();
57
+ map.putString("keyAgreementAlgorithm", base.keyAgreementAlgorithm);
58
+ map.putString("keyFactoryAlgorithm", base.keyFactoryAlgorithm);
59
+ map.putString("encryptionKeyAlgorithm", base.encryptionKeyAlgorithm);
60
+ map.putString("hmacKeyAlgorithm", base.hmacKeyAlgorithm);
61
+ map.putString("cipherTransformation", base.cipherTransformation);
62
+ map.putInt("gcmTagLength", base.gcmTagLength);
63
+ map.putInt("gcmIvLength", base.gcmIvLength);
64
+
65
+ if (overrides.hasKey("keyAgreementAlgorithm")) {
66
+ map.putString("keyAgreementAlgorithm", overrides.getString("keyAgreementAlgorithm"));
67
+ }
68
+ if (overrides.hasKey("keyFactoryAlgorithm")) {
69
+ map.putString("keyFactoryAlgorithm", overrides.getString("keyFactoryAlgorithm"));
70
+ }
71
+ if (overrides.hasKey("encryptionKeyAlgorithm")) {
72
+ map.putString("encryptionKeyAlgorithm", overrides.getString("encryptionKeyAlgorithm"));
73
+ }
74
+ if (overrides.hasKey("hmacKeyAlgorithm")) {
75
+ map.putString("hmacKeyAlgorithm", overrides.getString("hmacKeyAlgorithm"));
76
+ }
77
+ if (overrides.hasKey("cipherTransformation")) {
78
+ map.putString("cipherTransformation", overrides.getString("cipherTransformation"));
79
+ }
80
+ if (overrides.hasKey("gcmTagLength")) {
81
+ map.putInt("gcmTagLength", overrides.getInt("gcmTagLength"));
82
+ }
83
+ if (overrides.hasKey("gcmIvLength")) {
84
+ map.putInt("gcmIvLength", overrides.getInt("gcmIvLength"));
85
+ }
86
+ return map;
87
+ }
88
+
89
+ private static String requireString(ReadableMap map, String key) {
90
+ if (!map.hasKey(key) || map.getString(key) == null) {
91
+ throw new IllegalArgumentException("Missing required crypto option: " + key);
92
+ }
93
+ String value = map.getString(key).trim();
94
+ if (value.isEmpty()) {
95
+ throw new IllegalArgumentException("Missing required crypto option: " + key);
96
+ }
97
+ return value;
98
+ }
99
+
100
+ private static int requireInt(ReadableMap map, String key) {
101
+ if (!map.hasKey(key)) {
102
+ throw new IllegalArgumentException("Missing required crypto option: " + key);
103
+ }
104
+ return map.getInt(key);
105
+ }
106
+ }