@umutcansu/react-native-pinvault 0.0.0-stage → 2.3.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 (48) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/LICENSE +21 -0
  3. package/README.md +386 -2
  4. package/RNPinVault.podspec +43 -0
  5. package/android/build.gradle +68 -0
  6. package/android/consumer-rules.pro +25 -0
  7. package/android/src/main/AndroidManifest.xml +16 -0
  8. package/android/src/main/java/io/github/umutcansu/pinvault/reactnative/ConfigParser.kt +300 -0
  9. package/android/src/main/java/io/github/umutcansu/pinvault/reactnative/JsEnvironmentGuard.kt +53 -0
  10. package/android/src/main/java/io/github/umutcansu/pinvault/reactnative/NativeSecurity.kt +239 -0
  11. package/android/src/main/java/io/github/umutcansu/pinvault/reactnative/PinVaultModule.kt +410 -0
  12. package/android/src/main/java/io/github/umutcansu/pinvault/reactnative/PinVaultNetworking.kt +296 -0
  13. package/android/src/main/java/io/github/umutcansu/pinvault/reactnative/PinVaultPackage.kt +25 -0
  14. package/android/src/main/java/io/github/umutcansu/pinvault/reactnative/PinnedFetch.kt +131 -0
  15. package/android/src/main/java/io/github/umutcansu/pinvault/reactnative/ResultMapper.kt +188 -0
  16. package/android/src/main/java/io/github/umutcansu/pinvault/reactnative/StrictJson.kt +225 -0
  17. package/android/src/main/java/io/github/umutcansu/pinvault/reactnative/VaultTokenStore.kt +63 -0
  18. package/ios/Core/ConfigParser.swift +276 -0
  19. package/ios/Core/JSEnvironmentGuard.swift +129 -0
  20. package/ios/Core/NativeSecurity.swift +197 -0
  21. package/ios/Core/PinnedFetch.swift +119 -0
  22. package/ios/Core/ReactNetworking.swift +172 -0
  23. package/ios/Core/ResultMapper.swift +163 -0
  24. package/ios/Core/StrictJSON.swift +214 -0
  25. package/ios/Core/VaultTokenStore.swift +66 -0
  26. package/ios/PinVaultBridge.swift +412 -0
  27. package/ios/PinVaultReactNetworking.swift +47 -0
  28. package/ios/RNPinVault.h +7 -0
  29. package/ios/RNPinVault.mm +293 -0
  30. package/ios/RNPinVaultURLRequestHandler.mm +92 -0
  31. package/lib/module/NativePinVault.js +13 -0
  32. package/lib/module/NativePinVault.js.map +1 -0
  33. package/lib/module/index.js +312 -0
  34. package/lib/module/index.js.map +1 -0
  35. package/lib/module/package.json +1 -0
  36. package/lib/module/types.js +2 -0
  37. package/lib/module/types.js.map +1 -0
  38. package/lib/typescript/package.json +1 -0
  39. package/lib/typescript/src/NativePinVault.d.ts +50 -0
  40. package/lib/typescript/src/NativePinVault.d.ts.map +1 -0
  41. package/lib/typescript/src/index.d.ts +150 -0
  42. package/lib/typescript/src/index.d.ts.map +1 -0
  43. package/lib/typescript/src/types.d.ts +346 -0
  44. package/lib/typescript/src/types.d.ts.map +1 -0
  45. package/package.json +128 -4
  46. package/src/NativePinVault.ts +72 -0
  47. package/src/index.ts +385 -0
  48. package/src/types.ts +351 -0
package/src/types.ts ADDED
@@ -0,0 +1,351 @@
1
+ // Public types. Names, fields and enum values are the native ones
2
+ // (Kotlin `PinVaultConfig.Builder`, `ConfigApiBlock.Builder`,
3
+ // `VaultFileConfig.Builder`, `InitResult`, … and their Swift twins).
4
+ // Enum values are the Kotlin constant names, which are also the Swift raw values.
5
+
6
+ // ── Configuration ────────────────────────────────────────────────────────────
7
+
8
+ export type TimeUnit = 'MILLISECONDS' | 'SECONDS' | 'MINUTES' | 'HOURS' | 'DAYS';
9
+
10
+ /** `amount` + `unit`, as the builder methods take them (`expiredConfigGrace(1, TimeUnit.HOURS)`). */
11
+ export type Duration = { amount: number; unit: TimeUnit };
12
+
13
+ /** A pin entry (`HostPin`). At least two pins (primary + backup), Base64 SPKI SHA-256 without `sha256/`. */
14
+ export type HostPin = {
15
+ hostname: string;
16
+ sha256: string[];
17
+ version?: number;
18
+ forceUpdate?: boolean;
19
+ mtls?: boolean;
20
+ clientCertVersion?: number;
21
+ };
22
+
23
+ /** One Config API (`configApi(id, url) { … }`). Keys are the `ConfigApiBlock.Builder` methods. */
24
+ export type ConfigApiBlock = {
25
+ id: string;
26
+ url: string;
27
+ bootstrapPins?: HostPin[];
28
+ configEndpoint?: string;
29
+ healthEndpoint?: string;
30
+ /** `signaturePublicKey(key)`: one key. Use either this or `signaturePublicKeys`. */
31
+ signaturePublicKey?: string;
32
+ signaturePublicKeys?: string[];
33
+ requiredSignatures?: number;
34
+ recoveryPublicKeys?: string[];
35
+ requiredRecoverySignatures?: number;
36
+ allowUnsigned?: boolean;
37
+ serverScope?: string;
38
+ allowUnpinnedConfigApi?: boolean;
39
+ allowServerGeneratedKey?: boolean;
40
+ clientCaPins?: string[];
41
+ maxClientCertLifetimeDays?: number;
42
+ clientCertHosts?: string[];
43
+ enrollmentEndpoint?: string;
44
+ clientCertEndpoint?: string;
45
+ vaultReportEndpoint?: string;
46
+ clientCertLabel?: string;
47
+ wantPinsFor?: string[];
48
+ renewalUrl?: string;
49
+ enrollmentUrl?: string;
50
+ clientCertRenewalThreshold?: number;
51
+ disableClientCertRenewal?: boolean;
52
+ attestation?: boolean;
53
+ attestationInterval?: Duration;
54
+ tokenHosts?: string[];
55
+ };
56
+
57
+ export type StorageStrategy = 'ENCRYPTED_PREFS' | 'ENCRYPTED_FILE';
58
+ export type VaultFileAccessPolicy = 'PUBLIC' | 'API_KEY' | 'TOKEN' | 'TOKEN_MTLS';
59
+ export type VaultFileEncryption = 'PLAIN' | 'AT_REST' | 'END_TO_END' | 'USER_AUTH';
60
+ export type UserAuth = 'NONE' | 'REQUIRED' | 'IF_SCREEN_LOCK';
61
+
62
+ /**
63
+ * One vault file (`vaultFile(key) { … }`). For `TOKEN` / `TOKEN_MTLS` files the
64
+ * native side installs the `accessToken { … }` provider itself: it reads the token
65
+ * you set with `setVaultToken(key, token)` (or pass to `fetchFile(key, { token })`),
66
+ * which stays in native memory only.
67
+ */
68
+ export type VaultFileConfig = {
69
+ key: string;
70
+ endpoint: string;
71
+ signaturePublicKey?: string;
72
+ updateWithPins?: boolean;
73
+ storage?: StorageStrategy;
74
+ configApi?: string;
75
+ accessPolicy?: VaultFileAccessPolicy;
76
+ encryption?: VaultFileEncryption;
77
+ userAuth?: UserAuth;
78
+ maxOfflineAge?: Duration;
79
+ wipeWhenStale?: boolean;
80
+ };
81
+
82
+ /** Operations the environment guard is asked about (`GuardedOperation`). */
83
+ export type GuardedOperation = 'INIT' | 'ENROLL' | 'FETCH_FILE' | 'UNLOCK_FILE';
84
+
85
+ /**
86
+ * The app's device verdict (root / jailbreak / hooking detection of your choice)
87
+ * before every guarded operation. Runs in JS, so code that hooks the JS runtime
88
+ * can answer for it: treat it as one more signal, the decisive check is the
89
+ * server's attestation verdict. Fail closed: a timeout, a thrown error or a
90
+ * non-`true` value refuses the operation.
91
+ */
92
+ export type EnvironmentGuard = (operation: GuardedOperation) => boolean | Promise<boolean>;
93
+
94
+ /** `PinVaultConfig.Builder`, as JSON. */
95
+ export type PinVaultConfig = {
96
+ configApis?: ConfigApiBlock[];
97
+ vaultFiles?: VaultFileConfig[];
98
+ staticPins?: { pins: HostPin[]; version?: number; forceUpdate?: boolean };
99
+ maxRetryCount?: number;
100
+ updateIntervalHours?: number;
101
+ updateIntervalMinutes?: number;
102
+ deviceAlias?: string;
103
+ expiredConfigGrace?: Duration;
104
+ requireCaTrust?: string[];
105
+ wipeVaultFilesOnRevocation?: boolean;
106
+ vaultFileMaxOfflineAge?: Duration;
107
+ requireUnlockedDevice?: boolean;
108
+ requireHardwareBackedKeys?: boolean;
109
+ managedTrustRoots?: boolean;
110
+ expectedSignerSha256?: string[];
111
+ environmentGuard?: EnvironmentGuard;
112
+ /** How long the native side waits for `environmentGuard` (ms, 100–30000, default 5000). */
113
+ environmentGuardTimeoutMs?: number;
114
+ /**
115
+ * `start` fails with `E_NETWORKING_NOT_PINNED` when React Native's own networking
116
+ * does not go through PinVault: on Android when another library replaced the
117
+ * OkHttp hooks (or they are not installed), on iOS when RCTNetworking does not
118
+ * pick the plugin's request handler for https. Default false (a warning is logged).
119
+ */
120
+ requirePinnedReactNativeNetworking?: boolean;
121
+ /** Android-only settings (ignored on iOS). */
122
+ android?: {
123
+ /** `requireUnlockedDevice(allowFallback = true)`. */
124
+ requireUnlockedDeviceAllowFallback?: boolean;
125
+ /**
126
+ * Pin React Native's own `fetch` / `XMLHttpRequest` / `WebSocket` / images (default true).
127
+ * The plugin's content provider installs the hooks before the app starts; `false`
128
+ * is refused while they are installed — opt out natively (README, "Networking").
129
+ */
130
+ pinGlobalNetworking?: boolean;
131
+ /** Keep React Native's 10 MiB disk HTTP cache for its `fetch` / XHR (default false: no disk cache). */
132
+ keepReactNativeHttpCache?: boolean;
133
+ /** Keep React Native's persistent cookie jar for its `fetch` / XHR (default false: no cookies). */
134
+ keepReactNativeCookies?: boolean;
135
+ };
136
+ /** iOS-only settings (ignored on Android). */
137
+ ios?: {
138
+ /** `resolve(host:to:)`: connect to `address` while pinning and verifying `host`. */
139
+ resolve?: Record<string, string>;
140
+ expectedBundleIds?: string[];
141
+ expectedTeamIds?: string[];
142
+ /** `userAuthStrength(_:)`; `BIOMETRIC_CURRENT_SET` = `userAuthBiometricOnly()`. */
143
+ userAuthStrength?: 'DEVICE_OWNER' | 'BIOMETRIC_CURRENT_SET';
144
+ /** The largest answer React Native's own https request may get (bytes, default 50 MiB, at most 256 MiB). */
145
+ reactNativeMaxResponseBytes?: number;
146
+ };
147
+ };
148
+
149
+ // ── Results ─────────────────────────────────────────────────────────────────
150
+
151
+ /** A native exception: its class name (`SSLPinningException`, …) and message. */
152
+ export type NativeException = { name: string; message: string | null };
153
+
154
+ export type InitResult =
155
+ | { type: 'ready'; version: number }
156
+ | { type: 'failed'; reason: string; exception: NativeException | null };
157
+
158
+ export type UpdateResult =
159
+ | { type: 'updated'; newVersion: number }
160
+ | { type: 'alreadyCurrent' }
161
+ | { type: 'failed'; reason: string; exception: NativeException | null };
162
+
163
+ export type KeySecurityLevel =
164
+ | 'STRONGBOX'
165
+ | 'TRUSTED_ENVIRONMENT'
166
+ | 'SOFTWARE'
167
+ | 'UNKNOWN'
168
+ | 'SECURE_ENCLAVE';
169
+
170
+ export type EnrollmentRefusal =
171
+ | 'INVALID_TOKEN'
172
+ | 'TOKEN_REQUIRED'
173
+ | 'DEVICE_ALREADY_ENROLLED'
174
+ | 'REVOKED'
175
+ | 'REJECTED'
176
+ | 'LIMIT_REACHED'
177
+ | 'EXPIRED'
178
+ | 'ATTESTATION_FAILED'
179
+ | 'CSR_REQUIRED'
180
+ | 'OTHER';
181
+
182
+ export type ClientCertEnrollmentResult =
183
+ | { type: 'enrolled'; alreadyEnrolled: boolean; keySecurityLevel: KeySecurityLevel | null }
184
+ | {
185
+ type: 'refused';
186
+ reason: EnrollmentRefusal;
187
+ httpStatus: number;
188
+ serverError: string | null;
189
+ message: string | null;
190
+ }
191
+ | {
192
+ type: 'pending';
193
+ requestId: string;
194
+ clientId: string | null;
195
+ message: string | null;
196
+ retryAfterSeconds: number | null;
197
+ verificationCode: string | null;
198
+ }
199
+ | { type: 'failed'; message: string; cause: NativeException | null };
200
+
201
+ /**
202
+ * `VaultFileResult`. `updated` carries no content: read it with `loadFile(key)`
203
+ * (or `unlockFile` for files behind the screen lock).
204
+ */
205
+ export type VaultFileResult =
206
+ | { type: 'updated'; key: string; version: number }
207
+ | { type: 'alreadyCurrent'; key: string; version: number }
208
+ | { type: 'failed'; key: string; reason: string; code: string; exception: NativeException | null };
209
+
210
+ export type VaultFileStatus =
211
+ | 'AVAILABLE'
212
+ | 'LOCKED'
213
+ | 'NOT_STORED'
214
+ | 'STALE'
215
+ | 'NEEDS_FETCH'
216
+ | 'INTEGRITY_FAILED'
217
+ | 'STORAGE_UNAVAILABLE';
218
+
219
+ export type ContentEncoding = 'utf8' | 'base64';
220
+
221
+ export type VaultFileUnlockPrompt = {
222
+ title: string;
223
+ subtitle?: string;
224
+ description?: string;
225
+ negativeButtonText?: string;
226
+ };
227
+
228
+ export type VaultFileUnlockResult =
229
+ | { type: 'unlocked'; key: string; version: number; content: string; encoding: ContentEncoding }
230
+ | { type: 'notFound'; key: string }
231
+ | { type: 'cancelled'; key: string }
232
+ | { type: 'invalidated'; key: string }
233
+ | { type: 'stale'; key: string }
234
+ | { type: 'failed'; key: string; reason: string; exception: NativeException | null };
235
+
236
+ export type AttestationResult = 'PASS' | 'REJECT' | 'FAILED' | 'NOT_ATTESTED' | 'UNSUPPORTED';
237
+
238
+ export type AttestationStatus = {
239
+ configApiId: string;
240
+ result: AttestationResult;
241
+ arc: string | null;
242
+ rejectionReasons: string[];
243
+ warnings: string[];
244
+ tokenExpiresAt: number | null;
245
+ lastAttestedAt: number | null;
246
+ nextAttestAt: number | null;
247
+ clockSkewMs: number | null;
248
+ lastError: string | null;
249
+ policyVersion: number | null;
250
+ };
251
+
252
+ /** `PinVault-Token` for your own HTTP client. The value is a bearer credential: never log it. */
253
+ export type AttestationTokenResult =
254
+ | { type: 'token'; value: string; expiresAt: number }
255
+ | { type: 'rejected'; status: AttestationStatus }
256
+ | { type: 'failed'; message: string }
257
+ | { type: 'unsupported' };
258
+
259
+ export type SigningStatus = {
260
+ configApiId: string;
261
+ trustedKeyIds: string[];
262
+ requiredSignatures: number;
263
+ keySetVersion: number;
264
+ recoveryKeyIds: string[];
265
+ lastConfigSignedBy: string[];
266
+ };
267
+
268
+ // ── Events ──────────────────────────────────────────────────────────────────
269
+
270
+ /** `PinVaultConnectionEvent`. Never carries a token, a password or file content. */
271
+ export type PinVaultConnectionEvent =
272
+ | {
273
+ type: 'connection';
274
+ hostname: string;
275
+ success: boolean;
276
+ pinVersion: number;
277
+ deviceManufacturer: string;
278
+ deviceModel: string;
279
+ actualPin: string;
280
+ expectedPins: string[];
281
+ }
282
+ | {
283
+ type: 'configUpdate';
284
+ status: 'UPDATED' | 'UNCHANGED' | 'FAILED';
285
+ newVersion: number;
286
+ deviceManufacturer: string;
287
+ deviceModel: string;
288
+ failureReason: string | null;
289
+ }
290
+ | {
291
+ type: 'clientCertRenewal';
292
+ status: 'RENEWED' | 'NOT_NEEDED' | 'REENROLL_REQUIRED' | 'FAILED';
293
+ notAfterEpochMs: number;
294
+ via: string | null;
295
+ configApiId: string;
296
+ deviceManufacturer: string;
297
+ deviceModel: string;
298
+ failureReason: string | null;
299
+ }
300
+ | {
301
+ type: 'attestation';
302
+ configApiId: string;
303
+ status: 'PASS' | 'REJECT' | 'FAILED';
304
+ arc: string | null;
305
+ rejectionReasons: string[];
306
+ warnings: string[];
307
+ tokenExpiresAt: number | null;
308
+ deviceManufacturer: string;
309
+ deviceModel: string;
310
+ failureReason: string | null;
311
+ };
312
+
313
+ // ── Pinned HTTP ─────────────────────────────────────────────────────────────
314
+
315
+ export type HttpMethod = 'GET' | 'HEAD' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS';
316
+
317
+ /**
318
+ * `HttpConnectionSettings` (seconds). With settings the request uses
319
+ * `getClient(settings)` / `session(settings:)`: pinning without pin-mismatch recovery.
320
+ */
321
+ export type HttpConnectionSettings = {
322
+ connectTimeout?: number;
323
+ readTimeout?: number;
324
+ writeTimeout?: number;
325
+ callTimeout?: number;
326
+ };
327
+
328
+ export type PinVaultRequestInit = {
329
+ method?: HttpMethod;
330
+ headers?: Record<string, string>;
331
+ /** Request body: text, or Base64 with `bodyEncoding: 'base64'`. At most 10 MiB. */
332
+ body?: string;
333
+ bodyEncoding?: ContentEncoding;
334
+ /** How the response body comes back (default `utf8`). */
335
+ responseEncoding?: ContentEncoding;
336
+ /** Whole-call timeout in ms (default: the client's). */
337
+ timeoutMs?: number;
338
+ /** Larger responses are refused (default 10 MiB, at most 50 MiB). */
339
+ maxResponseBytes?: number;
340
+ settings?: HttpConnectionSettings;
341
+ };
342
+
343
+ export type PinVaultResponse = {
344
+ status: number;
345
+ url: string;
346
+ /** Lower-case names; repeated headers joined with `, `. */
347
+ headers: Record<string, string>;
348
+ body: string;
349
+ bodyEncoding: ContentEncoding;
350
+ ok: boolean;
351
+ };