@ccheever/exact-ibex-runtime 0.1.0

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 (161) hide show
  1. package/package.json +63 -0
  2. package/src/abort/AbortController.ts +23 -0
  3. package/src/abort/AbortSignal.ts +152 -0
  4. package/src/abort/index.ts +2 -0
  5. package/src/accessibility.ts +12 -0
  6. package/src/arraybuffer-detach.ts +109 -0
  7. package/src/base64/base64.ts +168 -0
  8. package/src/base64/index.ts +1 -0
  9. package/src/blob/Blob.ts +259 -0
  10. package/src/blob/File.ts +59 -0
  11. package/src/blob/FormData.ts +323 -0
  12. package/src/blob/index.ts +3 -0
  13. package/src/bootstrap.ts +1946 -0
  14. package/src/broadcast/BroadcastChannel.ts +280 -0
  15. package/src/broadcast/index.ts +5 -0
  16. package/src/cache/Cache.ts +349 -0
  17. package/src/cache/CacheStorage.ts +89 -0
  18. package/src/cache/index.ts +27 -0
  19. package/src/camera/index.ts +6202 -0
  20. package/src/camera/processor.worker.ts +194 -0
  21. package/src/camera/scene.ts +195 -0
  22. package/src/clipboard/Clipboard.ts +129 -0
  23. package/src/clipboard/ClipboardItem.ts +97 -0
  24. package/src/clipboard/index.ts +6 -0
  25. package/src/clone/index.ts +1 -0
  26. package/src/clone/structuredClone.ts +389 -0
  27. package/src/clone/transferableSymbols.ts +2 -0
  28. package/src/compression/CompressionStream.ts +146 -0
  29. package/src/compression/DecompressionStream.ts +342 -0
  30. package/src/compression/index.ts +4 -0
  31. package/src/console/Console.ts +341 -0
  32. package/src/console/index.ts +2 -0
  33. package/src/core/accessibility-state.ts +263 -0
  34. package/src/core/accessibility.ts +184 -0
  35. package/src/core/agent-state.ts +37 -0
  36. package/src/core/diagnostics-logs.ts +144 -0
  37. package/src/core/host-call-bridge.ts +16 -0
  38. package/src/core/i18n-helpers.ts +189 -0
  39. package/src/core/locale-state.ts +253 -0
  40. package/src/core/locale.ts +95 -0
  41. package/src/crypto/Crypto.ts +2743 -0
  42. package/src/crypto/index.ts +1 -0
  43. package/src/diagnostics/logs.ts +7 -0
  44. package/src/encoding/TextDecoder.ts +1181 -0
  45. package/src/encoding/TextDecoderStream.ts +58 -0
  46. package/src/encoding/TextEncoder.ts +180 -0
  47. package/src/encoding/TextEncoderStream.ts +39 -0
  48. package/src/encoding/index.ts +8 -0
  49. package/src/events/CloseEvent.ts +91 -0
  50. package/src/events/DOMException.ts +409 -0
  51. package/src/events/ErrorEvent.ts +39 -0
  52. package/src/events/Event.ts +151 -0
  53. package/src/events/EventTarget.ts +280 -0
  54. package/src/events/FocusEvent.ts +27 -0
  55. package/src/events/KeyboardEvent.ts +46 -0
  56. package/src/events/MessageEvent.ts +61 -0
  57. package/src/events/ProgressEvent.ts +33 -0
  58. package/src/events/PromiseRejectionEvent.ts +31 -0
  59. package/src/events/index.ts +52 -0
  60. package/src/eventsource/EventSource.ts +371 -0
  61. package/src/eventsource/index.ts +2 -0
  62. package/src/fetch/Headers.ts +642 -0
  63. package/src/fetch/Request.ts +760 -0
  64. package/src/fetch/Response.ts +543 -0
  65. package/src/fetch/body.ts +1256 -0
  66. package/src/fetch/cookie-jar.ts +566 -0
  67. package/src/fetch/demo.ts +207 -0
  68. package/src/fetch/errors.ts +101 -0
  69. package/src/fetch/fetch.ts +2610 -0
  70. package/src/fetch/index.ts +101 -0
  71. package/src/fetch/native-bridge.ts +65 -0
  72. package/src/fetch/types.ts +258 -0
  73. package/src/filereader/FileReader.ts +236 -0
  74. package/src/filereader/index.ts +1 -0
  75. package/src/fs/Dirent.ts +39 -0
  76. package/src/fs/ExactFile.ts +450 -0
  77. package/src/fs/Stats.ts +80 -0
  78. package/src/fs/index.ts +944 -0
  79. package/src/fs/promises.ts +386 -0
  80. package/src/fs/shared.ts +328 -0
  81. package/src/http-server/index.js +697 -0
  82. package/src/http-server/index.ts +27 -0
  83. package/src/identity.generated.ts +14 -0
  84. package/src/index.ts +283 -0
  85. package/src/indexeddb/IDBCursor.ts +188 -0
  86. package/src/indexeddb/IDBDatabase.ts +343 -0
  87. package/src/indexeddb/IDBFactory.ts +269 -0
  88. package/src/indexeddb/IDBIndex.ts +194 -0
  89. package/src/indexeddb/IDBKeyRange.ts +109 -0
  90. package/src/indexeddb/IDBObjectStore.ts +468 -0
  91. package/src/indexeddb/IDBRequest.ts +163 -0
  92. package/src/indexeddb/IDBTransaction.ts +207 -0
  93. package/src/indexeddb/index.ts +34 -0
  94. package/src/indexeddb/utils.ts +52 -0
  95. package/src/inspect/index.ts +1 -0
  96. package/src/inspect/inspect.ts +465 -0
  97. package/src/internal/detect.ts +104 -0
  98. package/src/locale.ts +10 -0
  99. package/src/location/index.ts +1059 -0
  100. package/src/locks/LockManager.ts +460 -0
  101. package/src/locks/index.ts +12 -0
  102. package/src/media/VideoFrame.ts +58 -0
  103. package/src/messaging/MessageChannel.ts +31 -0
  104. package/src/messaging/MessagePort.ts +180 -0
  105. package/src/messaging/index.ts +2 -0
  106. package/src/messaging.ts +247 -0
  107. package/src/native/NativeModules.ts +354 -0
  108. package/src/native/index.ts +1 -0
  109. package/src/navigator/Navigator.ts +351 -0
  110. package/src/navigator/index.ts +1 -0
  111. package/src/node/Buffer.ts +1786 -0
  112. package/src/node/index.ts +4 -0
  113. package/src/node/path.ts +495 -0
  114. package/src/node/process.ts +2528 -0
  115. package/src/performance/Performance.ts +532 -0
  116. package/src/performance/index.ts +21 -0
  117. package/src/polyfills/array.ts +236 -0
  118. package/src/polyfills/arraybuffer.ts +172 -0
  119. package/src/polyfills/groupby.ts +85 -0
  120. package/src/polyfills/index.ts +85 -0
  121. package/src/polyfills/intl.ts +1956 -0
  122. package/src/polyfills/iterator.ts +479 -0
  123. package/src/polyfills/promise.ts +37 -0
  124. package/src/polyfills/set.ts +245 -0
  125. package/src/polyfills/string.ts +85 -0
  126. package/src/polyfills/typedarray.ts +110 -0
  127. package/src/promise-rejection-tracking.ts +464 -0
  128. package/src/react-native/index.ts +388 -0
  129. package/src/runtime-entry.ts +55 -0
  130. package/src/scheduling/AnimationFrame.ts +105 -0
  131. package/src/scheduling/IdleCallback.ts +167 -0
  132. package/src/scheduling/index.ts +13 -0
  133. package/src/security/Capabilities.ts +1146 -0
  134. package/src/security/Permissions.ts +392 -0
  135. package/src/security/capability-bits.generated.ts +63 -0
  136. package/src/security/index.ts +16 -0
  137. package/src/sqlite/Database.ts +456 -0
  138. package/src/sqlite/Statement.ts +206 -0
  139. package/src/sqlite/constants.ts +79 -0
  140. package/src/sqlite/errors.ts +25 -0
  141. package/src/sqlite/index.ts +34 -0
  142. package/src/sqlite/module.js +438 -0
  143. package/src/storage/Storage.ts +291 -0
  144. package/src/storage/StorageManager.ts +91 -0
  145. package/src/storage/index.ts +3 -0
  146. package/src/stream-compat.ts +47 -0
  147. package/src/streams/ReadableStream.ts +4131 -0
  148. package/src/streams/TransformStream.ts +375 -0
  149. package/src/streams/WritableStream.ts +866 -0
  150. package/src/streams/index.ts +41 -0
  151. package/src/timers/Timers.ts +296 -0
  152. package/src/timers/index.ts +11 -0
  153. package/src/url/URL.ts +656 -0
  154. package/src/url/URLPattern.ts +850 -0
  155. package/src/url/URLSearchParams.ts +244 -0
  156. package/src/url/index.ts +9 -0
  157. package/src/websocket/WebSocket.ts +770 -0
  158. package/src/websocket/WebSocketError.ts +52 -0
  159. package/src/websocket/WebSocketStream.ts +628 -0
  160. package/src/websocket/index.ts +7 -0
  161. package/src/window/index.ts +872 -0
@@ -0,0 +1,2743 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * Crypto - Web Crypto API Implementation
4
+ *
5
+ * @see https://www.w3.org/TR/WebCryptoAPI/
6
+ *
7
+ * Supported algorithms:
8
+ * - Digest: SHA-1, SHA-256, SHA-384, SHA-512
9
+ * - Encrypt/Decrypt: AES-GCM, AES-CBC, AES-CTR, RSA-OAEP
10
+ * - Sign/Verify: HMAC, RSASSA-PKCS1-v1_5, RSA-PSS, ECDSA, Ed25519
11
+ * - Key generation: AES-GCM, AES-CBC, AES-CTR, HMAC, RSA-OAEP, RSASSA-PKCS1-v1_5, RSA-PSS, ECDSA, ECDH, Ed25519, X25519
12
+ * - Key derivation: PBKDF2, HKDF, X25519
13
+ * - Key formats: raw, jwk, spki, pkcs8
14
+ *
15
+ * Note: This uses native crypto when available, with JS fallbacks for testing.
16
+ */
17
+
18
+ import { getNativeCryptoModule } from "../native/NativeModules";
19
+ import { requireCapability, Capabilities, isSilentMode } from "../security/Capabilities";
20
+
21
+ // Declare native crypto bridge functions
22
+ declare global {
23
+ // AES operations (algorithm-specific native bridges)
24
+ var __exactAesCbcEncrypt: ((
25
+ key: Uint8Array,
26
+ iv: Uint8Array,
27
+ data: Uint8Array
28
+ ) => Uint8Array) | undefined;
29
+
30
+ var __exactAesCbcDecrypt: ((
31
+ key: Uint8Array,
32
+ iv: Uint8Array,
33
+ data: Uint8Array
34
+ ) => Uint8Array) | undefined;
35
+
36
+ var __exactAesCtrEncrypt: ((
37
+ key: Uint8Array,
38
+ counter: Uint8Array,
39
+ data: Uint8Array
40
+ ) => Uint8Array) | undefined;
41
+
42
+ var __exactAesGcmEncrypt: ((
43
+ key: Uint8Array,
44
+ iv: Uint8Array,
45
+ data: Uint8Array,
46
+ aad?: Uint8Array,
47
+ tagLength?: number
48
+ ) => Uint8Array) | undefined;
49
+
50
+ var __exactAesGcmDecrypt: ((
51
+ key: Uint8Array,
52
+ iv: Uint8Array,
53
+ data: Uint8Array,
54
+ aad?: Uint8Array,
55
+ tagLength?: number
56
+ ) => Uint8Array) | undefined;
57
+
58
+ // HMAC operation (returns hex string, not bytes)
59
+ var __exactHmacSync: ((
60
+ algorithm: string,
61
+ key: string,
62
+ data: string
63
+ ) => string) | undefined;
64
+
65
+ // RSA sign/verify (expects PEM key strings, data as string)
66
+ var __exactSignSync: ((
67
+ algorithm: string,
68
+ data: string,
69
+ key: string
70
+ ) => Uint8Array) | undefined;
71
+
72
+ var __exactVerifySync: ((
73
+ algorithm: string,
74
+ signature: Uint8Array,
75
+ data: string,
76
+ key: string
77
+ ) => boolean) | undefined;
78
+
79
+ // Key generation (returns PEM strings for asymmetric keys)
80
+ var __exactGenerateKeyPairSync: ((
81
+ keyType: string,
82
+ options?: Record<string, any>
83
+ ) => { publicKey: string; privateKey: string }) | undefined;
84
+
85
+ // Key derivation
86
+ var __exactPbkdf2: ((
87
+ password: Uint8Array,
88
+ salt: Uint8Array,
89
+ iterations: number,
90
+ keyLength: number,
91
+ hashAlgo: string
92
+ ) => Uint8Array) | undefined;
93
+
94
+ var __exactHkdf: ((
95
+ hashAlgo: string,
96
+ ikm: Uint8Array,
97
+ salt: Uint8Array,
98
+ info: Uint8Array,
99
+ length: number
100
+ ) => Uint8Array) | undefined;
101
+
102
+ // Ed25519 operations
103
+ var __exactEd25519Sign: ((
104
+ privateKey: Uint8Array,
105
+ data: Uint8Array
106
+ ) => Uint8Array) | undefined;
107
+
108
+ var __exactEd25519Verify: ((
109
+ publicKey: Uint8Array,
110
+ signature: Uint8Array,
111
+ data: Uint8Array
112
+ ) => boolean) | undefined;
113
+
114
+ // ECDSA operations
115
+ var __exactEcdsaSign: ((
116
+ curve: string,
117
+ hash: string,
118
+ privateKey: Uint8Array,
119
+ data: Uint8Array
120
+ ) => Uint8Array) | undefined;
121
+
122
+ var __exactEcdsaVerify: ((
123
+ curve: string,
124
+ hash: string,
125
+ publicKey: Uint8Array,
126
+ signature: Uint8Array,
127
+ data: Uint8Array
128
+ ) => boolean) | undefined;
129
+
130
+ // X25519 key agreement
131
+ var __exactX25519DeriveBits: ((
132
+ privateKey: Uint8Array,
133
+ publicKey: Uint8Array
134
+ ) => Uint8Array) | undefined;
135
+
136
+ // ECDH key agreement for NIST curves
137
+ var __exactEcdhDeriveBits: ((
138
+ curve: string,
139
+ privateKey: Uint8Array,
140
+ publicKey: Uint8Array
141
+ ) => Uint8Array) | undefined;
142
+
143
+ // RSA-OAEP encrypt/decrypt
144
+ var __exactRsaOaepEncrypt: ((
145
+ publicKeyData: Uint8Array,
146
+ hashAlgorithm: string,
147
+ label: Uint8Array,
148
+ plaintext: Uint8Array
149
+ ) => Uint8Array) | undefined;
150
+
151
+ var __exactRsaOaepDecrypt: ((
152
+ privateKeyData: Uint8Array,
153
+ hashAlgorithm: string,
154
+ label: Uint8Array,
155
+ ciphertext: Uint8Array
156
+ ) => Uint8Array) | undefined;
157
+
158
+ // SPKI/PKCS8 key format support
159
+ var __exactExportKeySpki: ((
160
+ keyType: string,
161
+ keyData: Uint8Array
162
+ ) => Uint8Array) | undefined;
163
+
164
+ var __exactExportKeyPkcs8: ((
165
+ keyType: string,
166
+ keyData: Uint8Array
167
+ ) => Uint8Array) | undefined;
168
+
169
+ var __exactImportKeySpki: ((
170
+ keyData: Uint8Array
171
+ ) => { keyType: string; algorithm: string; rawKeyData: Uint8Array }) | undefined;
172
+
173
+ var __exactImportKeyPkcs8: ((
174
+ keyData: Uint8Array
175
+ ) => { keyType: string; algorithm: string; rawKeyData: Uint8Array }) | undefined;
176
+ }
177
+
178
+ /**
179
+ * Wrap native bridge errors as DOMException with the appropriate name.
180
+ * Native __exact* functions throw generic Error; WPT expects DOMException.
181
+ */
182
+ function wrapNativeError(e: unknown, defaultName: string): DOMException {
183
+ if (e instanceof DOMException) return e;
184
+ const msg = (e instanceof Error) ? e.message : String(e);
185
+ if (/not supported|unsupported|not available|unrecognized/i.test(msg)) {
186
+ return new DOMException(msg, 'NotSupportedError');
187
+ }
188
+ if (/invalid key|bad key/i.test(msg)) {
189
+ return new DOMException(msg, 'DataError');
190
+ }
191
+ if (/not extractable|usage/i.test(msg)) {
192
+ return new DOMException(msg, 'InvalidAccessError');
193
+ }
194
+ return new DOMException(msg, defaultName);
195
+ }
196
+
197
+ export class SubtleCrypto {
198
+ /**
199
+ * Check capability before any subtle crypto operation.
200
+ * @throws NotAllowedError if capability is not granted
201
+ */
202
+ #checkCapability(): void {
203
+ requireCapability(Capabilities.CRYPTO_SUBTLE);
204
+ }
205
+
206
+ /**
207
+ * Generate a cryptographic digest (hash)
208
+ */
209
+ async digest(
210
+ algorithm: AlgorithmIdentifier,
211
+ data: BufferSource
212
+ ): Promise<ArrayBuffer> {
213
+ this.#checkCapability();
214
+ const alg = typeof algorithm === "string" ? algorithm : algorithm.name;
215
+ const normalizedAlg = normalizeHashName(alg);
216
+ const bytes = toUint8Array(data);
217
+
218
+ const native = getNativeCryptoModule();
219
+ if (native) {
220
+ try {
221
+ switch (normalizedAlg) {
222
+ case "SHA-1": {
223
+ if (native.digest) return await native.digest(normalizedAlg, bytes);
224
+ return await native.sha1(bytes);
225
+ }
226
+ case "SHA-256": {
227
+ if (native.digest) return await native.digest(normalizedAlg, bytes);
228
+ return await native.sha256(bytes);
229
+ }
230
+ case "SHA-384": {
231
+ if (native.digest) return await native.digest(normalizedAlg, bytes);
232
+ return await native.sha384(bytes);
233
+ }
234
+ case "SHA-512": {
235
+ if (native.digest) return await native.digest(normalizedAlg, bytes);
236
+ return await native.sha512(bytes);
237
+ }
238
+ }
239
+ } catch (_nativeErr) {
240
+ // Native threw (e.g., stub not implemented), fall through to fallback path
241
+ }
242
+ }
243
+
244
+ // Try platform crypto (Bun/Node/browser globalThis.crypto.subtle) if available
245
+ const platformSubtle = getPlatformSubtle();
246
+ if (platformSubtle) {
247
+ try {
248
+ return await platformSubtle.digest(algorithm, bytes);
249
+ } catch (_platformErr) {
250
+ // Platform crypto failed, fall through to pure JS
251
+ }
252
+ }
253
+
254
+ // Pure JS fallback for supported hashes
255
+ switch (normalizedAlg) {
256
+ case "SHA-1":
257
+ return jsSHA1(bytes);
258
+ case "SHA-256":
259
+ return jsSHA256(bytes);
260
+ case "SHA-384":
261
+ return jsSHA384(bytes);
262
+ case "SHA-512":
263
+ return jsSHA512(bytes);
264
+ default:
265
+ throw new DOMException(
266
+ `Unrecognized algorithm name: ${alg}`,
267
+ "NotSupportedError"
268
+ );
269
+ }
270
+ }
271
+
272
+ /**
273
+ * Encrypt data using the specified algorithm and key
274
+ */
275
+ async encrypt(
276
+ algorithm: AlgorithmIdentifier | AesCbcParams | AesCtrParams | AesGcmParams | RsaOaepParams,
277
+ key: CryptoKey,
278
+ data: BufferSource
279
+ ): Promise<ArrayBuffer> {
280
+ this.#checkCapability();
281
+ validateKey(key, 'encrypt');
282
+ const bytes = toUint8Array(data);
283
+ const alg = normalizeAlgorithm(algorithm);
284
+
285
+ switch (alg.name.toUpperCase()) {
286
+ case 'AES-GCM': {
287
+ const params = algorithm as AesGcmParams;
288
+ const iv = toUint8Array(params.iv);
289
+ const additionalData = params.additionalData ? toUint8Array(params.additionalData) : undefined;
290
+ const tagLength = params.tagLength ?? 128;
291
+
292
+ if (typeof __exactAesGcmEncrypt === 'function') {
293
+ try {
294
+ const result = __exactAesGcmEncrypt((key as ExactCryptoKey)._keyData, iv, bytes, additionalData, tagLength);
295
+ return uint8ArrayToArrayBuffer(result);
296
+ } catch (e) { throw wrapNativeError(e, 'OperationError'); }
297
+ }
298
+ throw new DOMException('Native crypto not available for AES-GCM', 'NotSupportedError');
299
+ }
300
+
301
+ case 'AES-CBC': {
302
+ const params = algorithm as AesCbcParams;
303
+ const iv = toUint8Array(params.iv);
304
+
305
+ if (typeof __exactAesCbcEncrypt === 'function') {
306
+ try {
307
+ const result = __exactAesCbcEncrypt((key as ExactCryptoKey)._keyData, iv, bytes);
308
+ return uint8ArrayToArrayBuffer(result);
309
+ } catch (e) { throw wrapNativeError(e, 'OperationError'); }
310
+ }
311
+ throw new DOMException('Native crypto not available for AES-CBC', 'NotSupportedError');
312
+ }
313
+
314
+ case 'AES-CTR': {
315
+ const params = algorithm as AesCtrParams;
316
+ const counter = toUint8Array(params.counter);
317
+
318
+ if (typeof __exactAesCtrEncrypt === 'function') {
319
+ try {
320
+ const result = __exactAesCtrEncrypt((key as ExactCryptoKey)._keyData, counter, bytes);
321
+ return uint8ArrayToArrayBuffer(result);
322
+ } catch (e) { throw wrapNativeError(e, 'OperationError'); }
323
+ }
324
+ throw new DOMException('Native crypto not available for AES-CTR', 'NotSupportedError');
325
+ }
326
+
327
+ case 'RSA-OAEP': {
328
+ const params = algorithm as RsaOaepParams;
329
+ const hash = (key.algorithm as RsaHashedKeyAlgorithm).hash?.name || 'SHA-256';
330
+ const label = params.label ? toUint8Array(params.label) : new Uint8Array(0);
331
+
332
+ if (typeof __exactRsaOaepEncrypt === 'function') {
333
+ try {
334
+ const result = __exactRsaOaepEncrypt((key as ExactCryptoKey)._keyData, hash, label, bytes);
335
+ return uint8ArrayToArrayBuffer(result);
336
+ } catch (e) { throw wrapNativeError(e, 'OperationError'); }
337
+ }
338
+ throw new DOMException('Native crypto not available for RSA-OAEP', 'NotSupportedError');
339
+ }
340
+
341
+ default:
342
+ throw new DOMException(`Unsupported algorithm: ${alg.name}`, 'NotSupportedError');
343
+ }
344
+ }
345
+
346
+ /**
347
+ * Decrypt data using the specified algorithm and key
348
+ */
349
+ async decrypt(
350
+ algorithm: AlgorithmIdentifier | AesCbcParams | AesCtrParams | AesGcmParams | RsaOaepParams,
351
+ key: CryptoKey,
352
+ data: BufferSource
353
+ ): Promise<ArrayBuffer> {
354
+ this.#checkCapability();
355
+ validateKey(key, 'decrypt');
356
+ const bytes = toUint8Array(data);
357
+ const alg = normalizeAlgorithm(algorithm);
358
+
359
+ switch (alg.name.toUpperCase()) {
360
+ case 'AES-GCM': {
361
+ const params = algorithm as AesGcmParams;
362
+ const iv = toUint8Array(params.iv);
363
+ const additionalData = params.additionalData ? toUint8Array(params.additionalData) : undefined;
364
+ const tagLength = params.tagLength ?? 128;
365
+
366
+ if (typeof __exactAesGcmDecrypt === 'function') {
367
+ try {
368
+ const result = __exactAesGcmDecrypt((key as ExactCryptoKey)._keyData, iv, bytes, additionalData, tagLength);
369
+ return uint8ArrayToArrayBuffer(result);
370
+ } catch (e) { throw wrapNativeError(e, 'OperationError'); }
371
+ }
372
+ throw new DOMException('Native crypto not available for AES-GCM', 'NotSupportedError');
373
+ }
374
+
375
+ case 'AES-CBC': {
376
+ const params = algorithm as AesCbcParams;
377
+ const iv = toUint8Array(params.iv);
378
+
379
+ if (typeof __exactAesCbcDecrypt === 'function') {
380
+ try {
381
+ const result = __exactAesCbcDecrypt((key as ExactCryptoKey)._keyData, iv, bytes);
382
+ return uint8ArrayToArrayBuffer(result);
383
+ } catch (e) { throw wrapNativeError(e, 'OperationError'); }
384
+ }
385
+ throw new DOMException('Native crypto not available for AES-CBC', 'NotSupportedError');
386
+ }
387
+
388
+ case 'AES-CTR': {
389
+ const params = algorithm as AesCtrParams;
390
+ const counter = toUint8Array(params.counter);
391
+
392
+ // CTR mode: encrypt and decrypt are the same operation
393
+ if (typeof __exactAesCtrEncrypt === 'function') {
394
+ try {
395
+ const result = __exactAesCtrEncrypt((key as ExactCryptoKey)._keyData, counter, bytes);
396
+ return uint8ArrayToArrayBuffer(result);
397
+ } catch (e) { throw wrapNativeError(e, 'OperationError'); }
398
+ }
399
+ throw new DOMException('Native crypto not available for AES-CTR', 'NotSupportedError');
400
+ }
401
+
402
+ case 'RSA-OAEP': {
403
+ const params = algorithm as RsaOaepParams;
404
+ const hash = (key.algorithm as RsaHashedKeyAlgorithm).hash?.name || 'SHA-256';
405
+ const label = params.label ? toUint8Array(params.label) : new Uint8Array(0);
406
+
407
+ if (typeof __exactRsaOaepDecrypt === 'function') {
408
+ try {
409
+ const result = __exactRsaOaepDecrypt((key as ExactCryptoKey)._keyData, hash, label, bytes);
410
+ return uint8ArrayToArrayBuffer(result);
411
+ } catch (e) { throw wrapNativeError(e, 'OperationError'); }
412
+ }
413
+ throw new DOMException('Native crypto not available for RSA-OAEP', 'NotSupportedError');
414
+ }
415
+
416
+ default:
417
+ throw new DOMException(`Unsupported algorithm: ${alg.name}`, 'NotSupportedError');
418
+ }
419
+ }
420
+
421
+ /**
422
+ * Sign data using the specified algorithm and key
423
+ */
424
+ async sign(
425
+ algorithm: AlgorithmIdentifier | RsaPssParams | EcdsaParams,
426
+ key: CryptoKey,
427
+ data: BufferSource
428
+ ): Promise<ArrayBuffer> {
429
+ this.#checkCapability();
430
+ validateKey(key, 'sign');
431
+ const bytes = toUint8Array(data);
432
+ const alg = normalizeAlgorithm(algorithm);
433
+ const native = getNativeCryptoModule();
434
+
435
+ switch (alg.name.toUpperCase()) {
436
+ case 'HMAC': {
437
+ const hash = normalizeHashName((key.algorithm as HmacKeyAlgorithm).hash.name);
438
+
439
+ if (native?.hmacSign) {
440
+ return native.hmacSign(hash, (key as ExactCryptoKey)._keyData, bytes);
441
+ }
442
+
443
+ if (typeof __exactHmacSync === 'function') {
444
+ // __exactHmacSync takes (algorithm, keyString, dataString) and returns a hex string
445
+ const keyData = (key as ExactCryptoKey)._keyData;
446
+ const keyStr = uint8ArrayToString(keyData);
447
+ const dataStr = uint8ArrayToString(bytes);
448
+ const hashAlgo = hash.toLowerCase().replace('-', '');
449
+ const hex = __exactHmacSync(hashAlgo, keyStr, dataStr);
450
+ return hexStringToArrayBuffer(hex);
451
+ }
452
+
453
+ return jsHmac((key as ExactCryptoKey)._keyData, bytes, hash);
454
+ }
455
+
456
+ case 'RSASSA-PKCS1-V1_5': {
457
+ const hash = (key.algorithm as RsaHashedKeyAlgorithm).hash.name;
458
+
459
+ if (typeof __exactSignSync === 'function') {
460
+ try {
461
+ const keyStr = uint8ArrayToString((key as ExactCryptoKey)._keyData);
462
+ const dataStr = uint8ArrayToString(bytes);
463
+ const result = __exactSignSync(hash, dataStr, keyStr);
464
+ return uint8ArrayToArrayBuffer(result);
465
+ } catch (e) { throw wrapNativeError(e, 'OperationError'); }
466
+ }
467
+ throw new DOMException('Native crypto not available for RSASSA-PKCS1-v1_5', 'NotSupportedError');
468
+ }
469
+
470
+ case 'RSA-PSS': {
471
+ const hash = (key.algorithm as RsaHashedKeyAlgorithm).hash.name;
472
+
473
+ if (typeof __exactSignSync === 'function') {
474
+ try {
475
+ const keyStr = uint8ArrayToString((key as ExactCryptoKey)._keyData);
476
+ const dataStr = uint8ArrayToString(bytes);
477
+ const result = __exactSignSync(hash, dataStr, keyStr);
478
+ return uint8ArrayToArrayBuffer(result);
479
+ } catch (e) { throw wrapNativeError(e, 'OperationError'); }
480
+ }
481
+ throw new DOMException('Native crypto not available for RSA-PSS', 'NotSupportedError');
482
+ }
483
+
484
+ case 'ECDSA': {
485
+ const params = algorithm as EcdsaParams;
486
+ const hash = typeof params.hash === 'string' ? params.hash : params.hash.name;
487
+ const curve = (key.algorithm as EcKeyAlgorithm).namedCurve;
488
+
489
+ if (typeof __exactEcdsaSign === 'function') {
490
+ try {
491
+ const result = __exactEcdsaSign(curve, hash, (key as ExactCryptoKey)._keyData, bytes);
492
+ return uint8ArrayToArrayBuffer(result);
493
+ } catch (e) { throw wrapNativeError(e, 'OperationError'); }
494
+ }
495
+ throw new DOMException('Native crypto not available for ECDSA', 'NotSupportedError');
496
+ }
497
+
498
+ case 'ED25519':
499
+ case 'EDDSA': {
500
+ if (typeof __exactEd25519Sign === 'function') {
501
+ try {
502
+ // If key data is 64 bytes (d[32] || x[32] from JWK import), pass only d portion
503
+ const keyData = (key as ExactCryptoKey)._keyData;
504
+ const privKeyData = keyData.length === 64 ? keyData.slice(0, 32) : keyData;
505
+ const result = __exactEd25519Sign(privKeyData, bytes);
506
+ return uint8ArrayToArrayBuffer(result);
507
+ } catch (e) { throw wrapNativeError(e, 'OperationError'); }
508
+ }
509
+ throw new DOMException('Native crypto not available for Ed25519', 'NotSupportedError');
510
+ }
511
+
512
+ default:
513
+ throw new DOMException(`Unsupported algorithm: ${alg.name}`, 'NotSupportedError');
514
+ }
515
+ }
516
+
517
+ /**
518
+ * Verify a signature using the specified algorithm and key
519
+ */
520
+ async verify(
521
+ algorithm: AlgorithmIdentifier | RsaPssParams | EcdsaParams,
522
+ key: CryptoKey,
523
+ signature: BufferSource,
524
+ data: BufferSource
525
+ ): Promise<boolean> {
526
+ this.#checkCapability();
527
+ validateKey(key, 'verify');
528
+ const signatureBytes = toUint8Array(signature);
529
+ const dataBytes = toUint8Array(data);
530
+ const alg = normalizeAlgorithm(algorithm);
531
+ const native = getNativeCryptoModule();
532
+
533
+ switch (alg.name.toUpperCase()) {
534
+ case 'HMAC': {
535
+ const hash = normalizeHashName((key.algorithm as HmacKeyAlgorithm).hash.name);
536
+
537
+ if (native?.hmacVerify) {
538
+ return native.hmacVerify(hash, (key as ExactCryptoKey)._keyData, signatureBytes, dataBytes);
539
+ }
540
+
541
+ if (typeof __exactHmacSync === 'function') {
542
+ // Compute HMAC and compare with provided signature
543
+ const keyData = (key as ExactCryptoKey)._keyData;
544
+ const keyStr = uint8ArrayToString(keyData);
545
+ const dataStr = uint8ArrayToString(dataBytes);
546
+ const hashAlgo = hash.toLowerCase().replace('-', '');
547
+ const hex = __exactHmacSync(hashAlgo, keyStr, dataStr);
548
+ const expectedSig = new Uint8Array(hex.length / 2);
549
+ for (let i = 0; i < hex.length; i += 2) {
550
+ expectedSig[i / 2] = parseInt(hex.substring(i, i + 2), 16);
551
+ }
552
+ return constantTimeCompare(expectedSig, signatureBytes);
553
+ }
554
+
555
+ const expectedSig = await jsHmac((key as ExactCryptoKey)._keyData, dataBytes, hash);
556
+ return constantTimeCompare(new Uint8Array(expectedSig), signatureBytes);
557
+ }
558
+
559
+ case 'RSASSA-PKCS1-V1_5': {
560
+ const hash = (key.algorithm as RsaHashedKeyAlgorithm).hash.name;
561
+
562
+ if (typeof __exactVerifySync === 'function') {
563
+ try {
564
+ const keyStr = uint8ArrayToString((key as ExactCryptoKey)._keyData);
565
+ const dataStr = uint8ArrayToString(dataBytes);
566
+ return __exactVerifySync(hash, signatureBytes, dataStr, keyStr);
567
+ } catch (e) { throw wrapNativeError(e, 'OperationError'); }
568
+ }
569
+ throw new DOMException('Native crypto not available for RSASSA-PKCS1-v1_5', 'NotSupportedError');
570
+ }
571
+
572
+ case 'RSA-PSS': {
573
+ const hash = (key.algorithm as RsaHashedKeyAlgorithm).hash.name;
574
+
575
+ if (typeof __exactVerifySync === 'function') {
576
+ try {
577
+ const keyStr = uint8ArrayToString((key as ExactCryptoKey)._keyData);
578
+ const dataStr = uint8ArrayToString(dataBytes);
579
+ return __exactVerifySync(hash, signatureBytes, dataStr, keyStr);
580
+ } catch (e) { throw wrapNativeError(e, 'OperationError'); }
581
+ }
582
+ throw new DOMException('Native crypto not available for RSA-PSS', 'NotSupportedError');
583
+ }
584
+
585
+ case 'ECDSA': {
586
+ const params = algorithm as EcdsaParams;
587
+ const hash = typeof params.hash === 'string' ? params.hash : params.hash.name;
588
+ const curve = (key.algorithm as EcKeyAlgorithm).namedCurve;
589
+
590
+ if (typeof __exactEcdsaVerify === 'function') {
591
+ try {
592
+ return __exactEcdsaVerify(curve, hash, (key as ExactCryptoKey)._keyData, signatureBytes, dataBytes);
593
+ } catch (e) { throw wrapNativeError(e, 'OperationError'); }
594
+ }
595
+ throw new DOMException('Native crypto not available for ECDSA', 'NotSupportedError');
596
+ }
597
+
598
+ case 'ED25519':
599
+ case 'EDDSA': {
600
+ if (typeof __exactEd25519Verify === 'function') {
601
+ try {
602
+ return __exactEd25519Verify((key as ExactCryptoKey)._keyData, signatureBytes, dataBytes);
603
+ } catch (e) { throw wrapNativeError(e, 'OperationError'); }
604
+ }
605
+ throw new DOMException('Native crypto not available for Ed25519', 'NotSupportedError');
606
+ }
607
+
608
+ default:
609
+ throw new DOMException(`Unsupported algorithm: ${alg.name}`, 'NotSupportedError');
610
+ }
611
+ }
612
+
613
+ /**
614
+ * Generate a new key or key pair
615
+ */
616
+ async generateKey(
617
+ algorithm: RsaHashedKeyGenParams | EcKeyGenParams | AesKeyGenParams | HmacKeyGenParams | KmacKeyGenParams,
618
+ extractable: boolean,
619
+ keyUsages: KeyUsage[]
620
+ ): Promise<CryptoKeyPair | CryptoKey> {
621
+ this.#checkCapability();
622
+ const alg = normalizeAlgorithm(algorithm);
623
+ if (typeof alg.name !== 'string') {
624
+ throw new DOMException('Invalid algorithm', 'TypeError');
625
+ }
626
+ const algName = alg.name.toUpperCase();
627
+ if (keyUsages.length === 0) {
628
+ throw new DOMException('Key usages cannot be empty', 'SyntaxError');
629
+ }
630
+
631
+ switch (algName) {
632
+ case 'AES-GCM':
633
+ case 'AES-CBC':
634
+ case 'AES-CTR':
635
+ case 'AES-KW': {
636
+ const params = algorithm as AesKeyGenParams;
637
+ const length = params.length;
638
+
639
+ if (![128, 192, 256].includes(length)) {
640
+ throw new DOMException('Invalid key length', 'DataError');
641
+ }
642
+
643
+ // Generate random key bytes
644
+ const keyData = new Uint8Array(length / 8);
645
+ crypto.getRandomValues(keyData);
646
+
647
+ return new ExactCryptoKey(
648
+ 'secret',
649
+ extractable,
650
+ { name: alg.name, length },
651
+ keyUsages,
652
+ keyData
653
+ );
654
+ }
655
+
656
+ case 'HMAC': {
657
+ const params = algorithm as HmacKeyGenParams;
658
+ const hash = normalizeHashName(typeof params.hash === 'string' ? params.hash : params.hash.name);
659
+ const length = params.length ?? getHashLength(hash);
660
+
661
+ const keyData = new Uint8Array(length / 8);
662
+ crypto.getRandomValues(keyData);
663
+
664
+ return new ExactCryptoKey(
665
+ 'secret',
666
+ extractable,
667
+ { name: 'HMAC', hash: { name: hash }, length },
668
+ keyUsages,
669
+ keyData
670
+ );
671
+ }
672
+
673
+ case 'RSA-OAEP':
674
+ case 'RSASSA-PKCS1-V1_5':
675
+ case 'RSA-PSS': {
676
+ const params = algorithm as RsaHashedKeyGenParams;
677
+ const hash = normalizeRequiredHash(params.hash);
678
+
679
+ if (typeof __exactGenerateKeyPairSync === 'function') {
680
+ try {
681
+ const result = __exactGenerateKeyPairSync('rsa', {
682
+ modulusLength: params.modulusLength,
683
+ publicExponent: params.publicExponent instanceof Uint8Array
684
+ ? new DataView(params.publicExponent.buffer, params.publicExponent.byteOffset, params.publicExponent.byteLength).getUint32(params.publicExponent.byteLength - 4, false)
685
+ : 65537,
686
+ });
687
+
688
+ const algorithmInfo = {
689
+ name: alg.name,
690
+ modulusLength: params.modulusLength,
691
+ publicExponent: params.publicExponent,
692
+ hash: { name: hash },
693
+ };
694
+
695
+ // Keys are PEM strings - store as UTF-8 bytes
696
+ const pubKeyData = new TextEncoder().encode(result.publicKey);
697
+ const privKeyData = new TextEncoder().encode(result.privateKey);
698
+
699
+ return {
700
+ publicKey: new ExactCryptoKey(
701
+ 'public',
702
+ true,
703
+ algorithmInfo,
704
+ keyUsages.filter(u => ['encrypt', 'verify', 'wrapKey'].includes(u)),
705
+ pubKeyData
706
+ ),
707
+ privateKey: new ExactCryptoKey(
708
+ 'private',
709
+ extractable,
710
+ algorithmInfo,
711
+ keyUsages.filter(u => ['decrypt', 'sign', 'unwrapKey'].includes(u)),
712
+ privKeyData
713
+ ),
714
+ };
715
+ } catch (e) { throw wrapNativeError(e, 'OperationError'); }
716
+ }
717
+ throw new DOMException('Native crypto not available for RSA key generation', 'NotSupportedError');
718
+ }
719
+
720
+ case 'ECDSA':
721
+ case 'ECDH': {
722
+ const params = algorithm as EcKeyGenParams;
723
+
724
+ if (typeof __exactGenerateKeyPairSync === 'function') {
725
+ const result = __exactGenerateKeyPairSync('ec', {
726
+ namedCurve: params.namedCurve,
727
+ });
728
+
729
+ const algorithmInfo = {
730
+ name: alg.name,
731
+ namedCurve: params.namedCurve,
732
+ };
733
+
734
+ // Keys are PEM strings - store as UTF-8 bytes
735
+ const pubKeyData = new TextEncoder().encode(result.publicKey);
736
+ const privKeyData = new TextEncoder().encode(result.privateKey);
737
+
738
+ return {
739
+ publicKey: new ExactCryptoKey(
740
+ 'public',
741
+ true,
742
+ algorithmInfo,
743
+ alg.name === 'ECDSA'
744
+ ? keyUsages.filter(u => u === 'verify')
745
+ : keyUsages.filter(u => u === 'deriveKey' || u === 'deriveBits'),
746
+ pubKeyData
747
+ ),
748
+ privateKey: new ExactCryptoKey(
749
+ 'private',
750
+ extractable,
751
+ algorithmInfo,
752
+ alg.name === 'ECDSA'
753
+ ? keyUsages.filter(u => u === 'sign')
754
+ : keyUsages.filter(u => u === 'deriveKey' || u === 'deriveBits'),
755
+ privKeyData
756
+ ),
757
+ };
758
+ }
759
+ throw new DOMException('Native crypto not available for EC key generation', 'NotSupportedError');
760
+ }
761
+
762
+ case 'ED25519':
763
+ case 'EDDSA': {
764
+ if (typeof __exactGenerateKeyPairSync === 'function') {
765
+ const result = __exactGenerateKeyPairSync('ed25519', {});
766
+
767
+ const algorithmInfo = { name: 'Ed25519' };
768
+
769
+ // Keys are PEM strings - store as UTF-8 bytes
770
+ const pubKeyData = new TextEncoder().encode(result.publicKey);
771
+ const privKeyData = new TextEncoder().encode(result.privateKey);
772
+
773
+ return {
774
+ publicKey: new ExactCryptoKey(
775
+ 'public',
776
+ true,
777
+ algorithmInfo,
778
+ keyUsages.filter(u => u === 'verify'),
779
+ pubKeyData
780
+ ),
781
+ privateKey: new ExactCryptoKey(
782
+ 'private',
783
+ extractable,
784
+ algorithmInfo,
785
+ keyUsages.filter(u => u === 'sign'),
786
+ privKeyData
787
+ ),
788
+ };
789
+ }
790
+ throw new DOMException('Native crypto not available for Ed25519 key generation', 'NotSupportedError');
791
+ }
792
+
793
+ case 'X25519': {
794
+ if (typeof __exactGenerateKeyPairSync === 'function') {
795
+ const result = __exactGenerateKeyPairSync('x25519', {});
796
+
797
+ const algorithmInfo = { name: 'X25519' };
798
+
799
+ // Keys are PEM strings - store as UTF-8 bytes
800
+ const pubKeyData = new TextEncoder().encode(result.publicKey);
801
+ const privKeyData = new TextEncoder().encode(result.privateKey);
802
+
803
+ return {
804
+ publicKey: new ExactCryptoKey(
805
+ 'public',
806
+ true,
807
+ algorithmInfo,
808
+ keyUsages.filter(u => u === 'deriveBits' || u === 'deriveKey'),
809
+ pubKeyData
810
+ ),
811
+ privateKey: new ExactCryptoKey(
812
+ 'private',
813
+ extractable,
814
+ algorithmInfo,
815
+ keyUsages.filter(u => u === 'deriveBits' || u === 'deriveKey'),
816
+ privKeyData
817
+ ),
818
+ };
819
+ }
820
+ throw new DOMException('Native crypto not available for X25519 key generation', 'NotSupportedError');
821
+ }
822
+
823
+ case 'KMAC128':
824
+ case 'KMAC256': {
825
+ const params = algorithm as KmacKeyGenParams;
826
+ const length = params.length;
827
+ if (length !== 128 && length !== 160 && length !== 256) {
828
+ throw new DOMException('Invalid key length', 'DataError');
829
+ }
830
+
831
+ for (const usage of keyUsages) {
832
+ if (usage !== 'sign' && usage !== 'verify') {
833
+ throw new DOMException(
834
+ `Invalid key usage '${usage}' for ${algName}`,
835
+ 'SyntaxError'
836
+ );
837
+ }
838
+ }
839
+
840
+ const keyData = new Uint8Array(length / 8);
841
+ crypto.getRandomValues(keyData);
842
+
843
+ return new ExactCryptoKey(
844
+ 'secret',
845
+ extractable,
846
+ { name: alg.name, length },
847
+ keyUsages,
848
+ keyData
849
+ );
850
+ }
851
+
852
+ default:
853
+ throw new DOMException(`Unsupported algorithm: ${alg.name}`, 'NotSupportedError');
854
+ }
855
+ }
856
+
857
+ /**
858
+ * Import a key from external data
859
+ */
860
+ async importKey(
861
+ format: KeyFormat,
862
+ keyData: BufferSource | JsonWebKey,
863
+ algorithm: AlgorithmIdentifier | RsaHashedImportParams | EcKeyImportParams | HmacImportParams | AesKeyAlgorithm,
864
+ extractable: boolean,
865
+ keyUsages: KeyUsage[]
866
+ ): Promise<CryptoKey> {
867
+ this.#checkCapability();
868
+ // Normalize tentative "raw-secret" format to "raw"
869
+ if (format === ('raw-secret' as any)) format = 'raw';
870
+ const alg = normalizeAlgorithm(algorithm);
871
+
872
+ // Handle raw format (for symmetric keys)
873
+ if (format === 'raw') {
874
+ const rawData = toUint8Array(keyData as BufferSource);
875
+
876
+ switch (alg.name.toUpperCase()) {
877
+ case 'AES-GCM':
878
+ case 'AES-CBC':
879
+ case 'AES-CTR':
880
+ case 'AES-KW': {
881
+ if (![16, 24, 32].includes(rawData.length)) {
882
+ throw new DOMException('Invalid key length', 'DataError');
883
+ }
884
+ return new ExactCryptoKey(
885
+ 'secret',
886
+ extractable,
887
+ { name: alg.name, length: rawData.length * 8 },
888
+ keyUsages,
889
+ rawData
890
+ );
891
+ }
892
+
893
+ case 'HMAC': {
894
+ const params = algorithm as HmacImportParams;
895
+ const hash = normalizeHashName(typeof params.hash === 'string' ? params.hash : params.hash.name);
896
+ return new ExactCryptoKey(
897
+ 'secret',
898
+ extractable,
899
+ { name: 'HMAC', hash: { name: hash }, length: rawData.length * 8 },
900
+ keyUsages,
901
+ rawData
902
+ );
903
+ }
904
+
905
+ case 'PBKDF2':
906
+ case 'HKDF': {
907
+ return new ExactCryptoKey(
908
+ 'secret',
909
+ false, // PBKDF2/HKDF keys are never extractable
910
+ { name: alg.name },
911
+ keyUsages,
912
+ rawData
913
+ );
914
+ }
915
+
916
+ case 'ED25519':
917
+ case 'EDDSA': {
918
+ if (rawData.length !== 32) {
919
+ throw new DOMException('Ed25519 key must be 32 bytes', 'DataError');
920
+ }
921
+ return new ExactCryptoKey(
922
+ 'public',
923
+ extractable,
924
+ { name: 'Ed25519' },
925
+ keyUsages,
926
+ rawData
927
+ );
928
+ }
929
+
930
+ case 'X25519': {
931
+ if (rawData.length !== 32) {
932
+ throw new DOMException('X25519 key must be 32 bytes', 'DataError');
933
+ }
934
+ return new ExactCryptoKey(
935
+ 'public',
936
+ extractable,
937
+ { name: 'X25519' },
938
+ keyUsages,
939
+ rawData
940
+ );
941
+ }
942
+
943
+ default:
944
+ throw new DOMException(`Unsupported algorithm for raw import: ${alg.name}`, 'NotSupportedError');
945
+ }
946
+ }
947
+
948
+ // Handle JWK format
949
+ if (format === 'jwk') {
950
+ const jwk = keyData as JsonWebKey;
951
+ return this._importJwk(jwk, algorithm, extractable, keyUsages);
952
+ }
953
+
954
+ // Handle SPKI format (for public keys)
955
+ if (format === 'spki') {
956
+ const rawData = toUint8Array(keyData as BufferSource);
957
+
958
+ if (typeof __exactImportKeySpki === 'function') {
959
+ try {
960
+ const result = __exactImportKeySpki(rawData);
961
+ const rawKeyData = result.rawKeyData instanceof Uint8Array
962
+ ? result.rawKeyData
963
+ : new Uint8Array(result.rawKeyData as any);
964
+
965
+ // Determine algorithm info based on key type and provided algorithm
966
+ const algName = alg.name.toUpperCase();
967
+ let algorithmInfo: KeyAlgorithm;
968
+
969
+ if (algName === 'ED25519' || algName === 'EDDSA') {
970
+ algorithmInfo = { name: 'Ed25519' };
971
+ } else if (algName === 'X25519') {
972
+ algorithmInfo = { name: 'X25519' };
973
+ } else if (algName.startsWith('RSA')) {
974
+ algorithmInfo = {
975
+ name: alg.name,
976
+ hash: { name: normalizeHashName(typeof (algorithm as RsaHashedImportParams).hash === 'string' ? (algorithm as RsaHashedImportParams).hash : ((algorithm as RsaHashedImportParams).hash as any).name) },
977
+ } as any;
978
+ } else if (algName === 'ECDSA' || algName === 'ECDH') {
979
+ algorithmInfo = {
980
+ name: alg.name,
981
+ namedCurve: (algorithm as EcKeyImportParams).namedCurve,
982
+ };
983
+ } else {
984
+ algorithmInfo = { name: alg.name };
985
+ }
986
+
987
+ return new ExactCryptoKey('public', extractable, algorithmInfo, keyUsages, rawKeyData);
988
+ } catch (e) {
989
+ throw wrapNativeError(e, 'NotSupportedError');
990
+ }
991
+ }
992
+ throw new DOMException('SPKI import requires native implementation', 'NotSupportedError');
993
+ }
994
+
995
+ // Handle PKCS8 format (for private keys)
996
+ if (format === 'pkcs8') {
997
+ const rawData = toUint8Array(keyData as BufferSource);
998
+
999
+ if (typeof __exactImportKeyPkcs8 === 'function') {
1000
+ try {
1001
+ const result = __exactImportKeyPkcs8(rawData);
1002
+ const rawKeyData = result.rawKeyData instanceof Uint8Array
1003
+ ? result.rawKeyData
1004
+ : new Uint8Array(result.rawKeyData as any);
1005
+
1006
+ const algName = alg.name.toUpperCase();
1007
+ let algorithmInfo: KeyAlgorithm;
1008
+
1009
+ if (algName === 'ED25519' || algName === 'EDDSA') {
1010
+ algorithmInfo = { name: 'Ed25519' };
1011
+ } else if (algName === 'X25519') {
1012
+ algorithmInfo = { name: 'X25519' };
1013
+ } else if (algName.startsWith('RSA')) {
1014
+ algorithmInfo = {
1015
+ name: alg.name,
1016
+ hash: { name: normalizeHashName(typeof (algorithm as RsaHashedImportParams).hash === 'string' ? (algorithm as RsaHashedImportParams).hash : ((algorithm as RsaHashedImportParams).hash as any).name) },
1017
+ } as any;
1018
+ } else if (algName === 'ECDSA' || algName === 'ECDH') {
1019
+ algorithmInfo = {
1020
+ name: alg.name,
1021
+ namedCurve: (algorithm as EcKeyImportParams).namedCurve,
1022
+ };
1023
+ } else {
1024
+ algorithmInfo = { name: alg.name };
1025
+ }
1026
+
1027
+ return new ExactCryptoKey('private', extractable, algorithmInfo, keyUsages, rawKeyData);
1028
+ } catch (e) {
1029
+ throw wrapNativeError(e, 'NotSupportedError');
1030
+ }
1031
+ }
1032
+ throw new DOMException('PKCS8 import requires native implementation', 'NotSupportedError');
1033
+ }
1034
+
1035
+ throw new DOMException(`Unsupported format: ${format}`, 'NotSupportedError');
1036
+ }
1037
+
1038
+ /**
1039
+ * Import a JWK key
1040
+ */
1041
+ private async _importJwk(
1042
+ jwk: JsonWebKey,
1043
+ algorithm: AlgorithmIdentifier | RsaHashedImportParams | EcKeyImportParams | HmacImportParams | AesKeyAlgorithm,
1044
+ extractable: boolean,
1045
+ keyUsages: KeyUsage[]
1046
+ ): Promise<CryptoKey> {
1047
+ const alg = normalizeAlgorithm(algorithm);
1048
+
1049
+ switch (jwk.kty) {
1050
+ case 'oct': {
1051
+ // Symmetric key
1052
+ if (!jwk.k) {
1053
+ throw new DOMException('Missing k parameter in JWK', 'DataError');
1054
+ }
1055
+ const rawData = base64UrlDecode(jwk.k);
1056
+
1057
+ if (alg.name.toUpperCase().startsWith('AES')) {
1058
+ return new ExactCryptoKey(
1059
+ 'secret',
1060
+ extractable,
1061
+ { name: alg.name, length: rawData.length * 8 },
1062
+ keyUsages,
1063
+ rawData
1064
+ );
1065
+ }
1066
+
1067
+ if (alg.name.toUpperCase() === 'HMAC') {
1068
+ const params = algorithm as HmacImportParams;
1069
+ const hash = normalizeHashName(typeof params.hash === 'string' ? params.hash : params.hash.name);
1070
+ return new ExactCryptoKey(
1071
+ 'secret',
1072
+ extractable,
1073
+ { name: 'HMAC', hash: { name: hash }, length: rawData.length * 8 },
1074
+ keyUsages,
1075
+ rawData
1076
+ );
1077
+ }
1078
+
1079
+ throw new DOMException(`Unsupported algorithm for oct key: ${alg.name}`, 'NotSupportedError');
1080
+ }
1081
+
1082
+ case 'RSA': {
1083
+ // Import RSA JWK - extract modulus and exponent
1084
+ if (!jwk.n || !jwk.e) {
1085
+ throw new DOMException('Missing n or e parameter in RSA JWK', 'DataError');
1086
+ }
1087
+
1088
+ const isPrivate = !!jwk.d;
1089
+ const hash = (algorithm as RsaHashedImportParams).hash;
1090
+ const hashName = normalizeHashName(typeof hash === 'string' ? hash : hash.name);
1091
+
1092
+ // Convert JWK RSA parameters to PEM format for the native sign/verify bridges
1093
+ const pemStr = rsaJwkToPem(jwk, isPrivate);
1094
+ const pemBytes = new TextEncoder().encode(pemStr);
1095
+
1096
+ const algorithmInfo = {
1097
+ name: alg.name,
1098
+ hash: { name: hashName },
1099
+ };
1100
+
1101
+ return new ExactCryptoKey(
1102
+ isPrivate ? 'private' : 'public',
1103
+ extractable,
1104
+ algorithmInfo,
1105
+ keyUsages,
1106
+ pemBytes
1107
+ );
1108
+ }
1109
+
1110
+ case 'EC': {
1111
+ if (!jwk.x || !jwk.y) {
1112
+ throw new DOMException('Missing x or y parameter in EC JWK', 'DataError');
1113
+ }
1114
+
1115
+ const isPrivate = !!jwk.d;
1116
+ const curve = jwk.crv || (algorithm as EcKeyImportParams).namedCurve;
1117
+
1118
+ // Concatenate x, y (and d for private) coordinates as raw key data
1119
+ const x = base64UrlDecode(jwk.x);
1120
+ const y = base64UrlDecode(jwk.y);
1121
+ let keyDataBytes: Uint8Array;
1122
+
1123
+ if (isPrivate && jwk.d) {
1124
+ const d = base64UrlDecode(jwk.d);
1125
+ keyDataBytes = new Uint8Array(1 + x.length + y.length + d.length);
1126
+ keyDataBytes[0] = 0x04; // uncompressed point
1127
+ keyDataBytes.set(x, 1);
1128
+ keyDataBytes.set(y, 1 + x.length);
1129
+ keyDataBytes.set(d, 1 + x.length + y.length);
1130
+ } else {
1131
+ keyDataBytes = new Uint8Array(1 + x.length + y.length);
1132
+ keyDataBytes[0] = 0x04; // uncompressed point
1133
+ keyDataBytes.set(x, 1);
1134
+ keyDataBytes.set(y, 1 + x.length);
1135
+ }
1136
+
1137
+ const ecAlgorithmInfo = {
1138
+ name: alg.name,
1139
+ namedCurve: curve,
1140
+ };
1141
+
1142
+ return new ExactCryptoKey(
1143
+ isPrivate ? 'private' : 'public',
1144
+ extractable,
1145
+ ecAlgorithmInfo,
1146
+ keyUsages,
1147
+ keyDataBytes
1148
+ );
1149
+ }
1150
+
1151
+ case 'OKP': {
1152
+ // Octet Key Pair - Ed25519, X25519
1153
+ if (!jwk.x) {
1154
+ throw new DOMException('Missing x parameter in OKP JWK', 'DataError');
1155
+ }
1156
+
1157
+ const isPrivate = !!jwk.d;
1158
+ const curve = jwk.crv;
1159
+
1160
+ let keyDataBytes: Uint8Array;
1161
+ if (isPrivate && jwk.d) {
1162
+ // Store both d (private) and x (public) so JWK export can include both.
1163
+ // Format: d[32] || x[32] = 64 bytes total
1164
+ const dBytes = base64UrlDecode(jwk.d);
1165
+ const xBytes = base64UrlDecode(jwk.x);
1166
+ keyDataBytes = new Uint8Array(dBytes.length + xBytes.length);
1167
+ keyDataBytes.set(dBytes, 0);
1168
+ keyDataBytes.set(xBytes, dBytes.length);
1169
+ } else {
1170
+ keyDataBytes = base64UrlDecode(jwk.x);
1171
+ }
1172
+
1173
+ let okpAlgName: string;
1174
+ if (curve === 'Ed25519') {
1175
+ okpAlgName = 'Ed25519';
1176
+ } else if (curve === 'X25519') {
1177
+ okpAlgName = 'X25519';
1178
+ } else {
1179
+ throw new DOMException(`Unsupported OKP curve: ${curve}`, 'NotSupportedError');
1180
+ }
1181
+
1182
+ return new ExactCryptoKey(
1183
+ isPrivate ? 'private' : 'public',
1184
+ extractable,
1185
+ { name: okpAlgName },
1186
+ keyUsages,
1187
+ keyDataBytes
1188
+ );
1189
+ }
1190
+
1191
+ default:
1192
+ throw new DOMException(`JWK import for ${jwk.kty} keys is not supported`, 'NotSupportedError');
1193
+ }
1194
+ }
1195
+
1196
+ /**
1197
+ * Export a key to the specified format
1198
+ */
1199
+ async exportKey(format: KeyFormat, key: CryptoKey): Promise<ArrayBuffer | JsonWebKey> {
1200
+ this.#checkCapability();
1201
+ if (!key.extractable) {
1202
+ throw new DOMException('Key is not extractable', 'InvalidAccessError');
1203
+ }
1204
+
1205
+ const exactKey = key as ExactCryptoKey;
1206
+
1207
+ if (format === 'raw') {
1208
+ if (key.type !== 'secret') {
1209
+ throw new DOMException('Raw export only supported for secret keys', 'InvalidAccessError');
1210
+ }
1211
+ return exactKey._keyData.buffer.slice(
1212
+ exactKey._keyData.byteOffset,
1213
+ exactKey._keyData.byteOffset + exactKey._keyData.byteLength
1214
+ );
1215
+ }
1216
+
1217
+ if (format === 'jwk') {
1218
+ return this._exportJwk(exactKey);
1219
+ }
1220
+
1221
+ if (format === 'spki') {
1222
+ if (key.type !== 'public') {
1223
+ throw new DOMException('SPKI export only supported for public keys', 'InvalidAccessError');
1224
+ }
1225
+ if (typeof __exactExportKeySpki === 'function') {
1226
+ const algName = key.algorithm.name.toUpperCase();
1227
+ const result = __exactExportKeySpki(algName, exactKey._keyData);
1228
+ return uint8ArrayToArrayBuffer(result);
1229
+ }
1230
+ throw new DOMException('SPKI export requires native implementation', 'NotSupportedError');
1231
+ }
1232
+
1233
+ if (format === 'pkcs8') {
1234
+ if (key.type !== 'private') {
1235
+ throw new DOMException('PKCS8 export only supported for private keys', 'InvalidAccessError');
1236
+ }
1237
+ if (typeof __exactExportKeyPkcs8 === 'function') {
1238
+ const algName = key.algorithm.name.toUpperCase();
1239
+ const result = __exactExportKeyPkcs8(algName, exactKey._keyData);
1240
+ return uint8ArrayToArrayBuffer(result);
1241
+ }
1242
+ throw new DOMException('PKCS8 export requires native implementation', 'NotSupportedError');
1243
+ }
1244
+
1245
+ throw new DOMException(`Export format ${format} is not supported`, 'NotSupportedError');
1246
+ }
1247
+
1248
+ /**
1249
+ * Export a key as JWK
1250
+ */
1251
+ private _exportJwk(key: ExactCryptoKey): JsonWebKey {
1252
+ if (key.type === 'secret') {
1253
+ const algName = key.algorithm.name.toUpperCase();
1254
+
1255
+ const jwk: JsonWebKey = {
1256
+ kty: 'oct',
1257
+ k: base64UrlEncode(key._keyData),
1258
+ ext: key.extractable,
1259
+ key_ops: [...key.usages],
1260
+ };
1261
+
1262
+ if (algName.startsWith('AES')) {
1263
+ jwk.alg = `A${(key.algorithm as AesKeyAlgorithm).length}${algName.slice(4)}`;
1264
+ } else if (algName === 'HMAC') {
1265
+ const hash = (key.algorithm as HmacKeyAlgorithm).hash.name;
1266
+ jwk.alg = `HS${hash.replace('SHA-', '')}`;
1267
+ }
1268
+
1269
+ return jwk;
1270
+ }
1271
+
1272
+ // Asymmetric key export
1273
+ const algName = key.algorithm.name.toUpperCase();
1274
+
1275
+ if (algName === 'ED25519' || algName === 'EDDSA') {
1276
+ const jwk: JsonWebKey = {
1277
+ kty: 'OKP',
1278
+ crv: 'Ed25519',
1279
+ ext: key.extractable,
1280
+ key_ops: [...key.usages],
1281
+ };
1282
+
1283
+ if (key.type === 'public') {
1284
+ jwk.x = base64UrlEncode(key._keyData);
1285
+ } else {
1286
+ // Private key: key data may be d[32] || x[32] (64 bytes from JWK import)
1287
+ // or a PEM string (from generateKey). Per Web Crypto spec, both x and d
1288
+ // must be present in a private OKP JWK.
1289
+ if (key._keyData.length === 64) {
1290
+ // Raw key data: d[32] || x[32]
1291
+ jwk.d = base64UrlEncode(key._keyData.slice(0, 32));
1292
+ jwk.x = base64UrlEncode(key._keyData.slice(32));
1293
+ } else {
1294
+ // PEM or other format -- export d only (x not available without native bridge)
1295
+ jwk.d = base64UrlEncode(key._keyData);
1296
+ }
1297
+ }
1298
+
1299
+ return jwk;
1300
+ }
1301
+
1302
+ if (algName === 'X25519') {
1303
+ const jwk: JsonWebKey = {
1304
+ kty: 'OKP',
1305
+ crv: 'X25519',
1306
+ ext: key.extractable,
1307
+ key_ops: [...key.usages],
1308
+ };
1309
+
1310
+ if (key.type === 'public') {
1311
+ jwk.x = base64UrlEncode(key._keyData);
1312
+ } else {
1313
+ // Private key: key data may be d[32] || x[32] (64 bytes from JWK import)
1314
+ // or a PEM string (from generateKey). Per Web Crypto spec, both x and d
1315
+ // must be present in a private OKP JWK.
1316
+ if (key._keyData.length === 64) {
1317
+ // Raw key data: d[32] || x[32]
1318
+ jwk.d = base64UrlEncode(key._keyData.slice(0, 32));
1319
+ jwk.x = base64UrlEncode(key._keyData.slice(32));
1320
+ } else {
1321
+ // PEM or other format -- export d only (x not available without native bridge)
1322
+ jwk.d = base64UrlEncode(key._keyData);
1323
+ }
1324
+ }
1325
+
1326
+ return jwk;
1327
+ }
1328
+
1329
+ if (algName === 'ECDSA' || algName === 'ECDH') {
1330
+ const curve = (key.algorithm as EcKeyAlgorithm).namedCurve;
1331
+ const data = key._keyData;
1332
+
1333
+ // Key data format: 0x04 || x || y [|| d]
1334
+ const coordSize = (data.length - 1) / (key.type === 'private' ? 3 : 2);
1335
+
1336
+ const jwk: JsonWebKey = {
1337
+ kty: 'EC',
1338
+ crv: curve,
1339
+ x: base64UrlEncode(data.slice(1, 1 + coordSize)),
1340
+ y: base64UrlEncode(data.slice(1 + coordSize, 1 + 2 * coordSize)),
1341
+ ext: key.extractable,
1342
+ key_ops: [...key.usages],
1343
+ };
1344
+
1345
+ if (key.type === 'private') {
1346
+ jwk.d = base64UrlEncode(data.slice(1 + 2 * coordSize));
1347
+ }
1348
+
1349
+ return jwk;
1350
+ }
1351
+
1352
+ if (algName.startsWith('RSA') || algName === 'RSASSA-PKCS1-V1_5') {
1353
+ // RSA keys are stored as PEM strings encoded as UTF-8 bytes
1354
+ const pemStr = uint8ArrayToString(key._keyData);
1355
+ const rsaComponents = parseRsaPemToJwkComponents(pemStr, key.type === 'private');
1356
+
1357
+ const hash = (key.algorithm as RsaHashedKeyAlgorithm).hash?.name || 'SHA-256';
1358
+ let jwkAlg: string;
1359
+ if (algName === 'RSA-OAEP') {
1360
+ jwkAlg = hash === 'SHA-256' ? 'RSA-OAEP-256' : hash === 'SHA-384' ? 'RSA-OAEP-384' : hash === 'SHA-512' ? 'RSA-OAEP-512' : 'RSA-OAEP';
1361
+ } else if (algName === 'RSA-PSS') {
1362
+ jwkAlg = hash === 'SHA-256' ? 'PS256' : hash === 'SHA-384' ? 'PS384' : 'PS512';
1363
+ } else {
1364
+ jwkAlg = hash === 'SHA-256' ? 'RS256' : hash === 'SHA-384' ? 'RS384' : 'RS512';
1365
+ }
1366
+
1367
+ const jwk: JsonWebKey = {
1368
+ kty: 'RSA',
1369
+ alg: jwkAlg,
1370
+ ext: key.extractable,
1371
+ key_ops: [...key.usages],
1372
+ ...rsaComponents,
1373
+ };
1374
+
1375
+ return jwk;
1376
+ }
1377
+
1378
+ throw new DOMException('JWK export for this key type is not supported', 'NotSupportedError');
1379
+ }
1380
+
1381
+ /**
1382
+ * Derive bits from a base key
1383
+ */
1384
+ async deriveBits(
1385
+ algorithm: AlgorithmIdentifier | Pbkdf2Params | HkdfParams | EcdhKeyDeriveParams,
1386
+ baseKey: CryptoKey,
1387
+ length: number
1388
+ ): Promise<ArrayBuffer> {
1389
+ this.#checkCapability();
1390
+ validateKey(baseKey, 'deriveBits');
1391
+ const alg = normalizeAlgorithm(algorithm);
1392
+
1393
+ switch (alg.name.toUpperCase()) {
1394
+ case 'PBKDF2': {
1395
+ const params = algorithm as Pbkdf2Params;
1396
+ const salt = toUint8Array(params.salt);
1397
+ const hash = typeof params.hash === 'string' ? params.hash : params.hash.name;
1398
+ const normalizedHash = normalizeHashName(hash);
1399
+
1400
+ if (typeof __exactPbkdf2 === 'function') {
1401
+ // __exactPbkdf2(password, salt, iterations, keyLength, hashAlgo)
1402
+ const result = __exactPbkdf2(
1403
+ (baseKey as ExactCryptoKey)._keyData,
1404
+ salt,
1405
+ params.iterations,
1406
+ length / 8,
1407
+ normalizedHash
1408
+ );
1409
+ return uint8ArrayToArrayBuffer(result);
1410
+ }
1411
+
1412
+ // JS fallback for PBKDF2-SHA256
1413
+ if (normalizedHash === 'SHA-256') {
1414
+ return jsPbkdf2Sha256(
1415
+ (baseKey as ExactCryptoKey)._keyData,
1416
+ salt,
1417
+ params.iterations,
1418
+ length / 8
1419
+ );
1420
+ }
1421
+ throw new DOMException('Native crypto not available for PBKDF2', 'NotSupportedError');
1422
+ }
1423
+
1424
+ case 'HKDF': {
1425
+ const params = algorithm as HkdfParams;
1426
+ const salt = toUint8Array(params.salt);
1427
+ const info = toUint8Array(params.info);
1428
+ const hash = typeof params.hash === 'string' ? params.hash : params.hash.name;
1429
+ const normalizedHash = normalizeHashName(hash);
1430
+
1431
+ if (typeof __exactHkdf === 'function') {
1432
+ const result = __exactHkdf(
1433
+ normalizedHash,
1434
+ (baseKey as ExactCryptoKey)._keyData,
1435
+ salt,
1436
+ info,
1437
+ length / 8
1438
+ );
1439
+ return uint8ArrayToArrayBuffer(result);
1440
+ }
1441
+
1442
+ // JS fallback for HKDF
1443
+ return jsHkdf(
1444
+ (baseKey as ExactCryptoKey)._keyData,
1445
+ salt,
1446
+ info,
1447
+ normalizedHash,
1448
+ length / 8
1449
+ );
1450
+ }
1451
+
1452
+ case 'X25519': {
1453
+ const params = algorithm as EcdhKeyDeriveParams;
1454
+ const publicKey = params.public as ExactCryptoKey;
1455
+
1456
+ if (typeof __exactX25519DeriveBits === 'function') {
1457
+ try {
1458
+ // If base key data is 64 bytes (d[32] || x[32] from JWK import), pass only d portion
1459
+ const baseKeyData = (baseKey as ExactCryptoKey)._keyData;
1460
+ const privKeyData = baseKeyData.length === 64 ? baseKeyData.slice(0, 32) : baseKeyData;
1461
+ const result = __exactX25519DeriveBits(
1462
+ privKeyData,
1463
+ publicKey._keyData
1464
+ );
1465
+ return uint8ArrayToArrayBuffer(result);
1466
+ } catch (e) { throw wrapNativeError(e, 'OperationError'); }
1467
+ }
1468
+ throw new DOMException('Native crypto not available for X25519', 'NotSupportedError');
1469
+ }
1470
+
1471
+ case 'ECDH': {
1472
+ const params = algorithm as EcdhKeyDeriveParams;
1473
+ const publicKey = params.public as ExactCryptoKey;
1474
+
1475
+ if (typeof __exactEcdhDeriveBits === 'function') {
1476
+ try {
1477
+ const curve = (baseKey.algorithm as EcKeyAlgorithm).namedCurve;
1478
+ const result = __exactEcdhDeriveBits(
1479
+ curve,
1480
+ (baseKey as ExactCryptoKey)._keyData,
1481
+ publicKey._keyData
1482
+ );
1483
+ return uint8ArrayToArrayBuffer(result);
1484
+ } catch (e) { throw wrapNativeError(e, 'OperationError'); }
1485
+ }
1486
+ throw new DOMException('Native crypto not available for ECDH', 'NotSupportedError');
1487
+ }
1488
+
1489
+ default:
1490
+ throw new DOMException(`Unsupported algorithm: ${alg.name}`, 'NotSupportedError');
1491
+ }
1492
+ }
1493
+
1494
+ /**
1495
+ * Derive a key from a base key
1496
+ */
1497
+ async deriveKey(
1498
+ algorithm: AlgorithmIdentifier | Pbkdf2Params | HkdfParams | EcdhKeyDeriveParams,
1499
+ baseKey: CryptoKey,
1500
+ derivedKeyAlgorithm: AlgorithmIdentifier | AesKeyGenParams | HmacKeyGenParams,
1501
+ extractable: boolean,
1502
+ keyUsages: KeyUsage[]
1503
+ ): Promise<CryptoKey> {
1504
+ this.#checkCapability();
1505
+ validateKey(baseKey, 'deriveKey');
1506
+ const derivedAlg = normalizeAlgorithm(derivedKeyAlgorithm);
1507
+
1508
+ // Determine the length needed
1509
+ let length: number;
1510
+ if (derivedAlg.name.toUpperCase().startsWith('AES')) {
1511
+ length = (derivedKeyAlgorithm as AesKeyGenParams).length;
1512
+ } else if (derivedAlg.name.toUpperCase() === 'HMAC') {
1513
+ const params = derivedKeyAlgorithm as HmacKeyGenParams;
1514
+ const hash = normalizeHashName(typeof params.hash === 'string' ? params.hash : params.hash.name);
1515
+ length = params.length ?? getHashLength(hash);
1516
+ } else {
1517
+ throw new DOMException(`Unsupported derived key algorithm: ${derivedAlg.name}`, 'NotSupportedError');
1518
+ }
1519
+
1520
+ // Create a proxy key with 'deriveBits' usage for the internal deriveBits call
1521
+ const proxyKey = new ExactCryptoKey(
1522
+ (baseKey as ExactCryptoKey).type,
1523
+ (baseKey as ExactCryptoKey).extractable,
1524
+ (baseKey as ExactCryptoKey).algorithm,
1525
+ [...(baseKey as ExactCryptoKey).usages, 'deriveBits'],
1526
+ (baseKey as ExactCryptoKey)._keyData
1527
+ );
1528
+
1529
+ // Derive the bits
1530
+ const bits = await this.deriveBits(algorithm, proxyKey, length);
1531
+
1532
+ // Import as a key
1533
+ return this.importKey('raw', bits, derivedKeyAlgorithm, extractable, keyUsages);
1534
+ }
1535
+
1536
+ /**
1537
+ * Wrap a key with another key
1538
+ */
1539
+ async wrapKey(
1540
+ format: KeyFormat,
1541
+ key: CryptoKey,
1542
+ wrappingKey: CryptoKey,
1543
+ wrapAlgorithm: AlgorithmIdentifier | AesCbcParams | AesCtrParams | AesGcmParams | RsaOaepParams
1544
+ ): Promise<ArrayBuffer> {
1545
+ this.#checkCapability();
1546
+ // Export the key
1547
+ const exported = await this.exportKey(format, key);
1548
+
1549
+ // Get bytes from export
1550
+ let bytes: Uint8Array;
1551
+ if (exported instanceof ArrayBuffer) {
1552
+ bytes = new Uint8Array(exported);
1553
+ } else {
1554
+ // JWK - convert to JSON string
1555
+ bytes = new TextEncoder().encode(JSON.stringify(exported));
1556
+ }
1557
+
1558
+ // Validate wrapping key has 'wrapKey' usage
1559
+ validateKey(wrappingKey, 'wrapKey');
1560
+
1561
+ // Create a proxy key with 'encrypt' usage for the internal encrypt call
1562
+ const proxyKey = new ExactCryptoKey(
1563
+ (wrappingKey as any).type,
1564
+ (wrappingKey as any).extractable,
1565
+ (wrappingKey as any).algorithm,
1566
+ [...(wrappingKey as any).usages, 'encrypt'],
1567
+ (wrappingKey as any)._keyData
1568
+ );
1569
+
1570
+ // Encrypt with wrapping key
1571
+ return this.encrypt(wrapAlgorithm, proxyKey, bytes);
1572
+ }
1573
+
1574
+ /**
1575
+ * Unwrap a key
1576
+ */
1577
+ async unwrapKey(
1578
+ format: KeyFormat,
1579
+ wrappedKey: BufferSource,
1580
+ unwrappingKey: CryptoKey,
1581
+ unwrapAlgorithm: AlgorithmIdentifier | AesCbcParams | AesCtrParams | AesGcmParams | RsaOaepParams,
1582
+ unwrappedKeyAlgorithm: AlgorithmIdentifier | RsaHashedImportParams | EcKeyImportParams | HmacImportParams | AesKeyAlgorithm,
1583
+ extractable: boolean,
1584
+ keyUsages: KeyUsage[]
1585
+ ): Promise<CryptoKey> {
1586
+ this.#checkCapability();
1587
+ // Validate unwrapping key has 'unwrapKey' usage
1588
+ validateKey(unwrappingKey, 'unwrapKey');
1589
+
1590
+ // Create a proxy key with 'decrypt' usage for the internal decrypt call
1591
+ const proxyKey = new ExactCryptoKey(
1592
+ (unwrappingKey as any).type,
1593
+ (unwrappingKey as any).extractable,
1594
+ (unwrappingKey as any).algorithm,
1595
+ [...(unwrappingKey as any).usages, 'decrypt'],
1596
+ (unwrappingKey as any)._keyData
1597
+ );
1598
+
1599
+ // Decrypt the wrapped key
1600
+ const decrypted = await this.decrypt(unwrapAlgorithm, proxyKey, wrappedKey);
1601
+
1602
+ // Import the key
1603
+ if (format === 'jwk') {
1604
+ const jwkString = new TextDecoder().decode(decrypted);
1605
+ const jwk = JSON.parse(jwkString);
1606
+ return this.importKey(format, jwk, unwrappedKeyAlgorithm, extractable, keyUsages);
1607
+ }
1608
+
1609
+ return this.importKey(format, decrypted, unwrappedKeyAlgorithm, extractable, keyUsages);
1610
+ }
1611
+ }
1612
+
1613
+ export class Crypto {
1614
+ private _subtle = new SubtleCrypto();
1615
+
1616
+ get subtle(): SubtleCrypto {
1617
+ return this._subtle;
1618
+ }
1619
+
1620
+ /**
1621
+ * Fill array with cryptographically secure random bytes
1622
+ */
1623
+ getRandomValues<T extends ArrayBufferView>(array: T): T {
1624
+ // Check capability before proceeding
1625
+ requireCapability(Capabilities.CRYPTO_RANDOM);
1626
+
1627
+ if (
1628
+ !(
1629
+ array instanceof Int8Array ||
1630
+ array instanceof Uint8Array ||
1631
+ array instanceof Uint8ClampedArray ||
1632
+ array instanceof Int16Array ||
1633
+ array instanceof Uint16Array ||
1634
+ array instanceof Int32Array ||
1635
+ array instanceof Uint32Array ||
1636
+ array instanceof BigInt64Array ||
1637
+ array instanceof BigUint64Array
1638
+ )
1639
+ ) {
1640
+ throw new TypeError("Argument must be an integer-typed TypedArray");
1641
+ }
1642
+
1643
+ if (array.byteLength > 65536) {
1644
+ throw new DOMException(
1645
+ "The ArrayBufferView's byte length exceeds the limit (65536)",
1646
+ "QuotaExceededError"
1647
+ );
1648
+ }
1649
+
1650
+ const native = getNativeCryptoModule();
1651
+ if (native) {
1652
+ const bytes = new Uint8Array(array.buffer, array.byteOffset, array.byteLength);
1653
+ native.getRandomValues(bytes);
1654
+ return array;
1655
+ }
1656
+
1657
+ // Check if we're in test/debug mode via environment or global flag
1658
+ const isTestMode = (
1659
+ typeof process !== 'undefined' &&
1660
+ (process.env?.NODE_ENV === 'test' || process.env?.EXACT_ALLOW_INSECURE_CRYPTO === 'true')
1661
+ ) || (typeof (globalThis as any).__EXACT_TEST_MODE__ !== 'undefined');
1662
+
1663
+ if (!isTestMode) {
1664
+ // In production, throw an error instead of using insecure fallback
1665
+ throw new DOMException(
1666
+ 'crypto.getRandomValues requires native crypto module. ' +
1667
+ 'The native module is not available and insecure fallbacks are disabled in production. ' +
1668
+ 'Set EXACT_ALLOW_INSECURE_CRYPTO=true or __EXACT_TEST_MODE__=true for testing.',
1669
+ 'NotSupportedError'
1670
+ );
1671
+ }
1672
+
1673
+ // JS fallback for testing only (NOT cryptographically secure!)
1674
+ if (!isSilentMode()) {
1675
+ console.warn(
1676
+ "crypto.getRandomValues: Using INSECURE Math.random() fallback! " +
1677
+ "This is only acceptable in test environments."
1678
+ );
1679
+ }
1680
+ const bytes = new Uint8Array(array.buffer, array.byteOffset, array.byteLength);
1681
+ for (let i = 0; i < bytes.length; i++) {
1682
+ bytes[i] = Math.floor(Math.random() * 256);
1683
+ }
1684
+ return array;
1685
+ }
1686
+
1687
+ /**
1688
+ * Generate a random UUID v4
1689
+ */
1690
+ randomUUID(): `${string}-${string}-${string}-${string}-${string}` {
1691
+ // Check capability before proceeding
1692
+ requireCapability(Capabilities.CRYPTO_RANDOM);
1693
+
1694
+ const native = getNativeCryptoModule();
1695
+ if (native) {
1696
+ return native.randomUUID() as `${string}-${string}-${string}-${string}-${string}`;
1697
+ }
1698
+
1699
+ // JS fallback
1700
+ const bytes = new Uint8Array(16);
1701
+ this.getRandomValues(bytes);
1702
+
1703
+ // Set version (4) and variant (10xx)
1704
+ bytes[6] = (bytes[6] & 0x0f) | 0x40;
1705
+ bytes[8] = (bytes[8] & 0x3f) | 0x80;
1706
+
1707
+ const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
1708
+
1709
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}` as `${string}-${string}-${string}-${string}-${string}`;
1710
+ }
1711
+ }
1712
+
1713
+ // Helper to convert BufferSource to Uint8Array
1714
+ function toUint8Array(data: BufferSource): Uint8Array {
1715
+ if (data instanceof ArrayBuffer) {
1716
+ return new Uint8Array(data);
1717
+ }
1718
+ if (ArrayBuffer.isView(data)) {
1719
+ return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
1720
+ }
1721
+ throw new TypeError("Data must be an ArrayBuffer or ArrayBufferView");
1722
+ }
1723
+
1724
+ // Simple SHA-256 implementation for testing (not optimized)
1725
+ async function jsSHA256(data: Uint8Array): Promise<ArrayBuffer> {
1726
+ // Constants
1727
+ const K = [
1728
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1,
1729
+ 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
1730
+ 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
1731
+ 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
1732
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,
1733
+ 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
1734
+ 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
1735
+ 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
1736
+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
1737
+ 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
1738
+ 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
1739
+ ];
1740
+
1741
+ // Initial hash values
1742
+ let h0 = 0x6a09e667;
1743
+ let h1 = 0xbb67ae85;
1744
+ let h2 = 0x3c6ef372;
1745
+ let h3 = 0xa54ff53a;
1746
+ let h4 = 0x510e527f;
1747
+ let h5 = 0x9b05688c;
1748
+ let h6 = 0x1f83d9ab;
1749
+ let h7 = 0x5be0cd19;
1750
+
1751
+ // Pre-processing: add padding
1752
+ const msgLen = data.length;
1753
+ const bitLen = msgLen * 8;
1754
+
1755
+ // Calculate padded length: message + 1 byte (0x80) + padding + 8 bytes (length)
1756
+ // Total must be multiple of 64
1757
+ let paddedLen = msgLen + 1 + 8; // minimum: msg + 0x80 + 64-bit length
1758
+ const remainder = paddedLen % 64;
1759
+ if (remainder > 0) {
1760
+ paddedLen += 64 - remainder;
1761
+ }
1762
+ if (paddedLen - msgLen - 1 < 8) {
1763
+ paddedLen += 64; // Need more space for length
1764
+ }
1765
+
1766
+ const padded = new Uint8Array(paddedLen);
1767
+ padded.set(data);
1768
+ padded[msgLen] = 0x80;
1769
+
1770
+ // Append length as 64-bit big-endian (we only use 32 bits for length)
1771
+ const view = new DataView(padded.buffer);
1772
+ view.setUint32(paddedLen - 4, bitLen, false);
1773
+
1774
+ // Process each 512-bit (64-byte) chunk
1775
+ for (let offset = 0; offset < paddedLen; offset += 64) {
1776
+ const W = new Uint32Array(64);
1777
+
1778
+ // Copy chunk into first 16 words
1779
+ for (let i = 0; i < 16; i++) {
1780
+ W[i] = view.getUint32(offset + i * 4, false);
1781
+ }
1782
+
1783
+ // Extend to 64 words
1784
+ for (let i = 16; i < 64; i++) {
1785
+ const s0 = rotr(W[i - 15], 7) ^ rotr(W[i - 15], 18) ^ (W[i - 15] >>> 3);
1786
+ const s1 = rotr(W[i - 2], 17) ^ rotr(W[i - 2], 19) ^ (W[i - 2] >>> 10);
1787
+ W[i] = (W[i - 16] + s0 + W[i - 7] + s1) >>> 0;
1788
+ }
1789
+
1790
+ // Initialize working variables
1791
+ let a = h0,
1792
+ b = h1,
1793
+ c = h2,
1794
+ d = h3,
1795
+ e = h4,
1796
+ f = h5,
1797
+ g = h6,
1798
+ h = h7;
1799
+
1800
+ // Main loop
1801
+ for (let i = 0; i < 64; i++) {
1802
+ const S1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
1803
+ const ch = (e & f) ^ (~e & g);
1804
+ const temp1 = (h + S1 + ch + K[i] + W[i]) >>> 0;
1805
+ const S0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22);
1806
+ const maj = (a & b) ^ (a & c) ^ (b & c);
1807
+ const temp2 = (S0 + maj) >>> 0;
1808
+
1809
+ h = g;
1810
+ g = f;
1811
+ f = e;
1812
+ e = (d + temp1) >>> 0;
1813
+ d = c;
1814
+ c = b;
1815
+ b = a;
1816
+ a = (temp1 + temp2) >>> 0;
1817
+ }
1818
+
1819
+ // Add to hash
1820
+ h0 = (h0 + a) >>> 0;
1821
+ h1 = (h1 + b) >>> 0;
1822
+ h2 = (h2 + c) >>> 0;
1823
+ h3 = (h3 + d) >>> 0;
1824
+ h4 = (h4 + e) >>> 0;
1825
+ h5 = (h5 + f) >>> 0;
1826
+ h6 = (h6 + g) >>> 0;
1827
+ h7 = (h7 + h) >>> 0;
1828
+ }
1829
+
1830
+ // Produce final hash
1831
+ const result = new ArrayBuffer(32);
1832
+ const resultView = new DataView(result);
1833
+ resultView.setUint32(0, h0, false);
1834
+ resultView.setUint32(4, h1, false);
1835
+ resultView.setUint32(8, h2, false);
1836
+ resultView.setUint32(12, h3, false);
1837
+ resultView.setUint32(16, h4, false);
1838
+ resultView.setUint32(20, h5, false);
1839
+ resultView.setUint32(24, h6, false);
1840
+ resultView.setUint32(28, h7, false);
1841
+
1842
+ return result;
1843
+ }
1844
+
1845
+ function rotr(x: number, n: number): number {
1846
+ return ((x >>> n) | (x << (32 - n))) >>> 0;
1847
+ }
1848
+
1849
+ // DOMException for this module
1850
+ class DOMException extends Error {
1851
+ readonly code: number = 0;
1852
+ constructor(message: string, name: string) {
1853
+ super(message);
1854
+ this.name = name;
1855
+ }
1856
+ }
1857
+
1858
+ // =============================================================================
1859
+ // Type Definitions
1860
+ // =============================================================================
1861
+
1862
+ type AlgorithmIdentifier = string | { name: string };
1863
+ type KeyFormat = "raw" | "spki" | "pkcs8" | "jwk";
1864
+ type KeyUsage = "encrypt" | "decrypt" | "sign" | "verify" | "deriveKey" | "deriveBits" | "wrapKey" | "unwrapKey";
1865
+
1866
+ interface CryptoKey {
1867
+ readonly type: string;
1868
+ readonly extractable: boolean;
1869
+ readonly algorithm: KeyAlgorithm;
1870
+ readonly usages: KeyUsage[];
1871
+ }
1872
+
1873
+ interface KeyAlgorithm {
1874
+ name: string;
1875
+ }
1876
+
1877
+ interface AesKeyAlgorithm extends KeyAlgorithm {
1878
+ length: number;
1879
+ }
1880
+
1881
+ interface HmacKeyAlgorithm extends KeyAlgorithm {
1882
+ hash: { name: string };
1883
+ length: number;
1884
+ }
1885
+
1886
+ interface RsaHashedKeyAlgorithm extends KeyAlgorithm {
1887
+ modulusLength: number;
1888
+ publicExponent: Uint8Array;
1889
+ hash: { name: string };
1890
+ }
1891
+
1892
+ interface EcKeyAlgorithm extends KeyAlgorithm {
1893
+ namedCurve: string;
1894
+ }
1895
+
1896
+ interface CryptoKeyPair {
1897
+ readonly publicKey: CryptoKey;
1898
+ readonly privateKey: CryptoKey;
1899
+ }
1900
+
1901
+ interface JsonWebKey {
1902
+ kty?: string;
1903
+ k?: string;
1904
+ alg?: string;
1905
+ ext?: boolean;
1906
+ key_ops?: string[];
1907
+ [key: string]: any;
1908
+ }
1909
+
1910
+ // Algorithm parameter interfaces
1911
+ interface AesCbcParams { name: string; iv: BufferSource; }
1912
+ interface AesCtrParams { name: string; counter: BufferSource; length: number; }
1913
+ interface AesGcmParams { name: string; iv: BufferSource; additionalData?: BufferSource; tagLength?: number; }
1914
+ interface RsaOaepParams { name: string; label?: BufferSource; }
1915
+ interface RsaPssParams { name: string; saltLength: number; }
1916
+ interface EcdsaParams { name: string; hash: string | { name: string }; }
1917
+ interface Pbkdf2Params { name: string; salt: BufferSource; iterations: number; hash: string | { name: string }; }
1918
+ interface HkdfParams { name: string; salt: BufferSource; info: BufferSource; hash: string | { name: string }; }
1919
+ interface EcdhKeyDeriveParams { name: string; public: CryptoKey; }
1920
+
1921
+ // Key generation parameter interfaces
1922
+ interface AesKeyGenParams { name: string; length: number; }
1923
+ interface HmacKeyGenParams { name: string; hash: string | { name: string }; length?: number; }
1924
+ interface RsaHashedKeyGenParams { name: string; modulusLength: number; publicExponent: Uint8Array; hash: string | { name: string }; }
1925
+ interface EcKeyGenParams { name: string; namedCurve: string; }
1926
+ interface KmacKeyGenParams { name: string; length: number; }
1927
+
1928
+ // Import parameter interfaces
1929
+ interface HmacImportParams { name: string; hash: string | { name: string }; length?: number; }
1930
+ interface RsaHashedImportParams { name: string; hash: string | { name: string }; }
1931
+ interface EcKeyImportParams { name: string; namedCurve: string; }
1932
+
1933
+ // =============================================================================
1934
+ // ExactCryptoKey - Internal key implementation
1935
+ // =============================================================================
1936
+
1937
+ export class ExactCryptoKey implements CryptoKey {
1938
+ readonly type: 'public' | 'private' | 'secret';
1939
+ readonly extractable: boolean;
1940
+ readonly algorithm: KeyAlgorithm;
1941
+ readonly usages: KeyUsage[];
1942
+ readonly _keyData: Uint8Array;
1943
+
1944
+ constructor(
1945
+ type: 'public' | 'private' | 'secret',
1946
+ extractable: boolean,
1947
+ algorithm: KeyAlgorithm,
1948
+ usages: KeyUsage[],
1949
+ keyData: Uint8Array
1950
+ ) {
1951
+ this.type = type;
1952
+ this.extractable = extractable;
1953
+ this.algorithm = algorithm;
1954
+ this.usages = usages;
1955
+ this._keyData = keyData;
1956
+ }
1957
+
1958
+ get [Symbol.toStringTag](): 'CryptoKey' {
1959
+ return 'CryptoKey';
1960
+ }
1961
+ }
1962
+
1963
+ export { ExactCryptoKey as CryptoKey };
1964
+
1965
+ // =============================================================================
1966
+ // Helper Functions
1967
+ // =============================================================================
1968
+
1969
+ /**
1970
+ * Normalize algorithm identifier to object form
1971
+ */
1972
+ function normalizeAlgorithm(algorithm: AlgorithmIdentifier | any): { name: string; [key: string]: any } {
1973
+ if (typeof algorithm === 'string') {
1974
+ return { name: algorithm };
1975
+ }
1976
+ return algorithm;
1977
+ }
1978
+
1979
+ /**
1980
+ * Validate that a key can be used for the specified operation
1981
+ */
1982
+ function validateKey(key: CryptoKey, operation: KeyUsage): void {
1983
+ if (!key.usages.includes(operation)) {
1984
+ throw new DOMException(
1985
+ `Key does not support the '${operation}' operation`,
1986
+ 'InvalidAccessError'
1987
+ );
1988
+ }
1989
+ }
1990
+
1991
+ /**
1992
+ * Normalize hash names like `sha256`, `SHA-256`, and `sha-256`.
1993
+ */
1994
+ function normalizeHashName(hash: string): string {
1995
+ const normalized = hash.replace(/[^A-Za-z0-9]/g, "").toUpperCase();
1996
+ switch (normalized) {
1997
+ case "SHA1":
1998
+ return "SHA-1";
1999
+ case "SHA224":
2000
+ return "SHA-224";
2001
+ case "SHA256":
2002
+ return "SHA-256";
2003
+ case "SHA384":
2004
+ return "SHA-384";
2005
+ case "SHA512":
2006
+ return "SHA-512";
2007
+ default:
2008
+ throw new DOMException(`Unrecognized hash algorithm: ${hash}`, "NotSupportedError");
2009
+ }
2010
+ }
2011
+
2012
+ /**
2013
+ * Return a platform subtle implementation for digest and HMAC fallbacks.
2014
+ * This avoids recursive calls into the same Exact crypto implementation.
2015
+ */
2016
+ function getPlatformSubtle(): any | null {
2017
+ const g = globalThis as any;
2018
+ const candidate = g.crypto?.subtle;
2019
+ if (candidate && candidate !== crypto.subtle) {
2020
+ return candidate;
2021
+ }
2022
+ if (g.webcrypto?.subtle) {
2023
+ return g.webcrypto.subtle;
2024
+ }
2025
+ return null;
2026
+ }
2027
+
2028
+ /**
2029
+ * Get hash output length in bits
2030
+ */
2031
+ function getHashLength(hash: string): number {
2032
+ const normalized = normalizeHashName(hash);
2033
+ switch (normalized) {
2034
+ case "SHA-1":
2035
+ return 160;
2036
+ case "SHA-256":
2037
+ return 256;
2038
+ case "SHA-384":
2039
+ return 384;
2040
+ case "SHA-512":
2041
+ return 512;
2042
+ default:
2043
+ throw new DOMException(`Unsupported hash algorithm: ${hash}`, "NotSupportedError");
2044
+ }
2045
+ }
2046
+
2047
+ function normalizeRequiredHash(hash: string | { name: string } | undefined): string {
2048
+ if (typeof hash === 'string') {
2049
+ return normalizeHashName(hash);
2050
+ }
2051
+ if (hash && typeof hash === 'object' && typeof hash.name === 'string') {
2052
+ return normalizeHashName(hash.name);
2053
+ }
2054
+ throw new DOMException('Invalid hash algorithm', 'NotSupportedError');
2055
+ }
2056
+
2057
+ /**
2058
+ * Return hash params in normalized format.
2059
+ */
2060
+ function getHashParams(hash: string): { name: string; length: number } {
2061
+ const normalized = normalizeHashName(hash);
2062
+ return { name: normalized, length: getHashLength(normalized) / 8 };
2063
+ }
2064
+
2065
+ /**
2066
+ * Base64URL encode
2067
+ */
2068
+ function base64UrlEncode(data: Uint8Array): string {
2069
+ const base64 = btoa(String.fromCharCode(...data));
2070
+ return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
2071
+ }
2072
+
2073
+ /**
2074
+ * Base64URL decode
2075
+ */
2076
+ function base64UrlDecode(str: string): Uint8Array {
2077
+ // Add padding
2078
+ let padded = str.replace(/-/g, '+').replace(/_/g, '/');
2079
+ while (padded.length % 4) {
2080
+ padded += '=';
2081
+ }
2082
+ const binary = atob(padded);
2083
+ const bytes = new Uint8Array(binary.length);
2084
+ for (let i = 0; i < binary.length; i++) {
2085
+ bytes[i] = binary.charCodeAt(i);
2086
+ }
2087
+ return bytes;
2088
+ }
2089
+
2090
+ /**
2091
+ * Constant-time comparison to prevent timing attacks
2092
+ */
2093
+ function constantTimeCompare(a: Uint8Array, b: Uint8Array): boolean {
2094
+ if (a.length !== b.length) return false;
2095
+ let result = 0;
2096
+ for (let i = 0; i < a.length; i++) {
2097
+ result |= a[i] ^ b[i];
2098
+ }
2099
+ return result === 0;
2100
+ }
2101
+
2102
+ /**
2103
+ * Compute hash digest through platform subtle API when available.
2104
+ */
2105
+ async function digestWithPlatformHash(hash: string, data: Uint8Array): Promise<ArrayBuffer> {
2106
+ const subtle = getPlatformSubtle();
2107
+ if (!subtle?.digest) {
2108
+ throw new DOMException(
2109
+ "Platform crypto.subtle.digest is not available for this fallback path",
2110
+ "NotSupportedError"
2111
+ );
2112
+ }
2113
+ const normalizedHash = normalizeHashName(hash);
2114
+ return subtle.digest({ name: normalizedHash }, data);
2115
+ }
2116
+
2117
+ /**
2118
+ * Pure JS SHA-1 implementation
2119
+ */
2120
+ async function jsSHA1(data: Uint8Array): Promise<ArrayBuffer> {
2121
+ let h0 = 0x67452301;
2122
+ let h1 = 0xEFCDAB89;
2123
+ let h2 = 0x98BADCFE;
2124
+ let h3 = 0x10325476;
2125
+ let h4 = 0xC3D2E1F0;
2126
+
2127
+ const msgLen = data.length;
2128
+ const bitLen = msgLen * 8;
2129
+ let paddedLen = msgLen + 1 + 8;
2130
+ const remainder = paddedLen % 64;
2131
+ if (remainder > 0) paddedLen += 64 - remainder;
2132
+ if (paddedLen - msgLen - 1 < 8) paddedLen += 64;
2133
+
2134
+ const padded = new Uint8Array(paddedLen);
2135
+ padded.set(data);
2136
+ padded[msgLen] = 0x80;
2137
+ const view = new DataView(padded.buffer);
2138
+ view.setUint32(paddedLen - 4, bitLen, false);
2139
+
2140
+ function rotl(x: number, n: number): number {
2141
+ return ((x << n) | (x >>> (32 - n))) >>> 0;
2142
+ }
2143
+
2144
+ for (let offset = 0; offset < paddedLen; offset += 64) {
2145
+ const W = new Uint32Array(80);
2146
+ for (let i = 0; i < 16; i++) {
2147
+ W[i] = view.getUint32(offset + i * 4, false);
2148
+ }
2149
+ for (let i = 16; i < 80; i++) {
2150
+ W[i] = rotl(W[i-3] ^ W[i-8] ^ W[i-14] ^ W[i-16], 1);
2151
+ }
2152
+
2153
+ let a = h0, b = h1, c = h2, d = h3, e = h4;
2154
+
2155
+ for (let i = 0; i < 80; i++) {
2156
+ let f: number, k: number;
2157
+ if (i < 20) {
2158
+ f = (b & c) | (~b & d);
2159
+ k = 0x5A827999;
2160
+ } else if (i < 40) {
2161
+ f = b ^ c ^ d;
2162
+ k = 0x6ED9EBA1;
2163
+ } else if (i < 60) {
2164
+ f = (b & c) | (b & d) | (c & d);
2165
+ k = 0x8F1BBCDC;
2166
+ } else {
2167
+ f = b ^ c ^ d;
2168
+ k = 0xCA62C1D6;
2169
+ }
2170
+
2171
+ const temp = (rotl(a, 5) + f + e + k + W[i]) >>> 0;
2172
+ e = d;
2173
+ d = c;
2174
+ c = rotl(b, 30);
2175
+ b = a;
2176
+ a = temp;
2177
+ }
2178
+
2179
+ h0 = (h0 + a) >>> 0;
2180
+ h1 = (h1 + b) >>> 0;
2181
+ h2 = (h2 + c) >>> 0;
2182
+ h3 = (h3 + d) >>> 0;
2183
+ h4 = (h4 + e) >>> 0;
2184
+ }
2185
+
2186
+ const result = new ArrayBuffer(20);
2187
+ const rv = new DataView(result);
2188
+ rv.setUint32(0, h0, false);
2189
+ rv.setUint32(4, h1, false);
2190
+ rv.setUint32(8, h2, false);
2191
+ rv.setUint32(12, h3, false);
2192
+ rv.setUint32(16, h4, false);
2193
+ return result;
2194
+ }
2195
+
2196
+ /**
2197
+ * Pure JS SHA-512/384 implementation using BigInt for 64-bit operations
2198
+ */
2199
+ async function jsSHA512Core(data: Uint8Array, is384: boolean): Promise<ArrayBuffer> {
2200
+ // SHA-512 round constants (first 80 primes)
2201
+ const K: bigint[] = [
2202
+ 0x428a2f98d728ae22n, 0x7137449123ef65cdn, 0xb5c0fbcfec4d3b2fn, 0xe9b5dba58189dbbcn,
2203
+ 0x3956c25bf348b538n, 0x59f111f1b605d019n, 0x923f82a4af194f9bn, 0xab1c5ed5da6d8118n,
2204
+ 0xd807aa98a3030242n, 0x12835b0145706fben, 0x243185be4ee4b28cn, 0x550c7dc3d5ffb4e2n,
2205
+ 0x72be5d74f27b896fn, 0x80deb1fe3b1696b1n, 0x9bdc06a725c71235n, 0xc19bf174cf692694n,
2206
+ 0xe49b69c19ef14ad2n, 0xefbe4786384f25e3n, 0x0fc19dc68b8cd5b5n, 0x240ca1cc77ac9c65n,
2207
+ 0x2de92c6f592b0275n, 0x4a7484aa6ea6e483n, 0x5cb0a9dcbd41fbd4n, 0x76f988da831153b5n,
2208
+ 0x983e5152ee66dfabn, 0xa831c66d2db43210n, 0xb00327c898fb213fn, 0xbf597fc7beef0ee4n,
2209
+ 0xc6e00bf33da88fc2n, 0xd5a79147930aa725n, 0x06ca6351e003826fn, 0x142929670a0e6e70n,
2210
+ 0x27b70a8546d22ffcn, 0x2e1b21385c26c926n, 0x4d2c6dfc5ac42aedn, 0x53380d139d95b3dfn,
2211
+ 0x650a73548baf63den, 0x766a0abb3c77b2a8n, 0x81c2c92e47edaee6n, 0x92722c851482353bn,
2212
+ 0xa2bfe8a14cf10364n, 0xa81a664bbc423001n, 0xc24b8b70d0f89791n, 0xc76c51a30654be30n,
2213
+ 0xd192e819d6ef5218n, 0xd69906245565a910n, 0xf40e35855771202an, 0x106aa07032bbd1b8n,
2214
+ 0x19a4c116b8d2d0c8n, 0x1e376c085141ab53n, 0x2748774cdf8eeb99n, 0x34b0bcb5e19b48a8n,
2215
+ 0x391c0cb3c5c95a63n, 0x4ed8aa4ae3418acbn, 0x5b9cca4f7763e373n, 0x682e6ff3d6b2b8a3n,
2216
+ 0x748f82ee5defb2fcn, 0x78a5636f43172f60n, 0x84c87814a1f0ab72n, 0x8cc702081a6439ecn,
2217
+ 0x90befffa23631e28n, 0xa4506cebde82bde9n, 0xbef9a3f7b2c67915n, 0xc67178f2e372532bn,
2218
+ 0xca273eceea26619cn, 0xd186b8c721c0c207n, 0xeada7dd6cde0eb1en, 0xf57d4f7fee6ed178n,
2219
+ 0x06f067aa72176fban, 0x0a637dc5a2c898a6n, 0x113f9804bef90daen, 0x1b710b35131c471bn,
2220
+ 0x28db77f523047d84n, 0x32caab7b40c72493n, 0x3c9ebe0a15c9bebcn, 0x431d67c49c100d4cn,
2221
+ 0x4cc5d4becb3e42b6n, 0x597f299cfc657e2an, 0x5fcb6fab3ad6faecn, 0x6c44198c4a475817n,
2222
+ ];
2223
+
2224
+ const MASK64 = 0xffffffffffffffffn;
2225
+
2226
+ function rotr64(x: bigint, n: number): bigint {
2227
+ return ((x >> BigInt(n)) | (x << BigInt(64 - n))) & MASK64;
2228
+ }
2229
+
2230
+ // Initial hash values
2231
+ let H: bigint[];
2232
+ if (is384) {
2233
+ H = [
2234
+ 0xcbbb9d5dc1059ed8n, 0x629a292a367cd507n, 0x9159015a3070dd17n, 0x152fecd8f70e5939n,
2235
+ 0x67332667ffc00b31n, 0x8eb44a8768581511n, 0xdb0c2e0d64f98fa7n, 0x47b5481dbefa4fa4n,
2236
+ ];
2237
+ } else {
2238
+ H = [
2239
+ 0x6a09e667f3bcc908n, 0xbb67ae8584caa73bn, 0x3c6ef372fe94f82bn, 0xa54ff53a5f1d36f1n,
2240
+ 0x510e527fade682d1n, 0x9b05688c2b3e6c1fn, 0x1f83d9abfb41bd6bn, 0x5be0cd19137e2179n,
2241
+ ];
2242
+ }
2243
+
2244
+ // Padding
2245
+ const msgLen = data.length;
2246
+ const bitLen = BigInt(msgLen) * 8n;
2247
+ let paddedLen = msgLen + 1 + 16; // + 0x80 + 128-bit length
2248
+ const remainder = paddedLen % 128;
2249
+ if (remainder > 0) paddedLen += 128 - remainder;
2250
+ if (paddedLen - msgLen - 1 < 16) paddedLen += 128;
2251
+
2252
+ const padded = new Uint8Array(paddedLen);
2253
+ padded.set(data);
2254
+ padded[msgLen] = 0x80;
2255
+ // Append 128-bit length (big-endian) - we only use the low 64 bits
2256
+ const lenView = new DataView(padded.buffer);
2257
+ const lenHigh = Number((bitLen >> 32n) & 0xffffffffn);
2258
+ const lenLow = Number(bitLen & 0xffffffffn);
2259
+ lenView.setUint32(paddedLen - 8, lenHigh, false);
2260
+ lenView.setUint32(paddedLen - 4, lenLow, false);
2261
+
2262
+ // Process each 1024-bit (128-byte) block
2263
+ const dv = new DataView(padded.buffer);
2264
+ for (let offset = 0; offset < paddedLen; offset += 128) {
2265
+ const W: bigint[] = new Array(80);
2266
+ for (let i = 0; i < 16; i++) {
2267
+ const hi = BigInt(dv.getUint32(offset + i * 8, false));
2268
+ const lo = BigInt(dv.getUint32(offset + i * 8 + 4, false));
2269
+ W[i] = ((hi << 32n) | lo) & MASK64;
2270
+ }
2271
+ for (let i = 16; i < 80; i++) {
2272
+ const s0 = rotr64(W[i-15], 1) ^ rotr64(W[i-15], 8) ^ (W[i-15] >> 7n);
2273
+ const s1 = rotr64(W[i-2], 19) ^ rotr64(W[i-2], 61) ^ (W[i-2] >> 6n);
2274
+ W[i] = (W[i-16] + s0 + W[i-7] + s1) & MASK64;
2275
+ }
2276
+
2277
+ let [a, b, c, d, e, f, g, h] = H;
2278
+
2279
+ for (let i = 0; i < 80; i++) {
2280
+ const S1 = rotr64(e, 14) ^ rotr64(e, 18) ^ rotr64(e, 41);
2281
+ const ch = (e & f) ^ (~e & MASK64 & g);
2282
+ const temp1 = (h + S1 + ch + K[i] + W[i]) & MASK64;
2283
+ const S0 = rotr64(a, 28) ^ rotr64(a, 34) ^ rotr64(a, 39);
2284
+ const maj = (a & b) ^ (a & c) ^ (b & c);
2285
+ const temp2 = (S0 + maj) & MASK64;
2286
+
2287
+ h = g; g = f; f = e;
2288
+ e = (d + temp1) & MASK64;
2289
+ d = c; c = b; b = a;
2290
+ a = (temp1 + temp2) & MASK64;
2291
+ }
2292
+
2293
+ H[0] = (H[0] + a) & MASK64;
2294
+ H[1] = (H[1] + b) & MASK64;
2295
+ H[2] = (H[2] + c) & MASK64;
2296
+ H[3] = (H[3] + d) & MASK64;
2297
+ H[4] = (H[4] + e) & MASK64;
2298
+ H[5] = (H[5] + f) & MASK64;
2299
+ H[6] = (H[6] + g) & MASK64;
2300
+ H[7] = (H[7] + h) & MASK64;
2301
+ }
2302
+
2303
+ const outputWords = is384 ? 6 : 8;
2304
+ const result = new ArrayBuffer(outputWords * 8);
2305
+ const rv = new DataView(result);
2306
+ for (let i = 0; i < outputWords; i++) {
2307
+ rv.setUint32(i * 8, Number((H[i] >> 32n) & 0xffffffffn), false);
2308
+ rv.setUint32(i * 8 + 4, Number(H[i] & 0xffffffffn), false);
2309
+ }
2310
+ return result;
2311
+ }
2312
+
2313
+ /**
2314
+ * SHA-384 digest (pure JS)
2315
+ */
2316
+ async function jsSHA384(data: Uint8Array): Promise<ArrayBuffer> {
2317
+ return jsSHA512Core(data, true);
2318
+ }
2319
+
2320
+ /**
2321
+ * SHA-512 digest (pure JS)
2322
+ */
2323
+ async function jsSHA512(data: Uint8Array): Promise<ArrayBuffer> {
2324
+ return jsSHA512Core(data, false);
2325
+ }
2326
+
2327
+ /**
2328
+ * Generic HMAC fallback used when native bridge callbacks are unavailable.
2329
+ */
2330
+ async function jsHmac(key: Uint8Array, data: Uint8Array, hash: string): Promise<ArrayBuffer> {
2331
+ const normalizedHash = normalizeHashName(hash);
2332
+ if (normalizedHash === "SHA-256") {
2333
+ return jsHmacSha256(key, data);
2334
+ }
2335
+
2336
+ const subtle = getPlatformSubtle();
2337
+ if (!subtle?.importKey || !subtle?.sign) {
2338
+ throw new DOMException(
2339
+ `Native crypto module not available for HMAC: ${normalizedHash}`,
2340
+ "NotSupportedError"
2341
+ );
2342
+ }
2343
+
2344
+ const importedKey = await subtle.importKey(
2345
+ "raw",
2346
+ key,
2347
+ { name: "HMAC", hash: { name: normalizedHash } },
2348
+ false,
2349
+ ["sign"]
2350
+ );
2351
+ return subtle.sign({ name: "HMAC" }, importedKey, data);
2352
+ }
2353
+
2354
+ /**
2355
+ * HMAC-SHA256 implementation (for JS fallback)
2356
+ */
2357
+ async function jsHmacSha256(key: Uint8Array, data: Uint8Array): Promise<ArrayBuffer> {
2358
+ const blockSize = 64;
2359
+ const hashSize = 32;
2360
+
2361
+ // If key is longer than block size, hash it
2362
+ let keyBytes = key;
2363
+ if (keyBytes.length > blockSize) {
2364
+ keyBytes = new Uint8Array(await jsSHA256(keyBytes));
2365
+ }
2366
+
2367
+ // Pad key to block size
2368
+ const paddedKey = new Uint8Array(blockSize);
2369
+ paddedKey.set(keyBytes);
2370
+
2371
+ // Create ipad and opad
2372
+ const ipad = new Uint8Array(blockSize);
2373
+ const opad = new Uint8Array(blockSize);
2374
+ for (let i = 0; i < blockSize; i++) {
2375
+ ipad[i] = paddedKey[i] ^ 0x36;
2376
+ opad[i] = paddedKey[i] ^ 0x5c;
2377
+ }
2378
+
2379
+ // Inner hash: H(ipad || data)
2380
+ const innerData = new Uint8Array(blockSize + data.length);
2381
+ innerData.set(ipad);
2382
+ innerData.set(data, blockSize);
2383
+ const innerHash = new Uint8Array(await jsSHA256(innerData));
2384
+
2385
+ // Outer hash: H(opad || inner_hash)
2386
+ const outerData = new Uint8Array(blockSize + hashSize);
2387
+ outerData.set(opad);
2388
+ outerData.set(innerHash, blockSize);
2389
+
2390
+ return jsSHA256(outerData);
2391
+ }
2392
+
2393
+ /**
2394
+ * Convert Uint8Array to its underlying ArrayBuffer (handling offset/length)
2395
+ */
2396
+ function uint8ArrayToArrayBuffer(data: Uint8Array): ArrayBuffer {
2397
+ return data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
2398
+ }
2399
+
2400
+ /**
2401
+ * Convert Uint8Array to a binary string (one char per byte)
2402
+ */
2403
+ function uint8ArrayToString(data: Uint8Array): string {
2404
+ let str = '';
2405
+ for (let i = 0; i < data.length; i++) {
2406
+ str += String.fromCharCode(data[i]);
2407
+ }
2408
+ return str;
2409
+ }
2410
+
2411
+ /**
2412
+ * Convert a hex string to an ArrayBuffer
2413
+ */
2414
+ function hexStringToArrayBuffer(hex: string): ArrayBuffer {
2415
+ const bytes = new Uint8Array(hex.length / 2);
2416
+ for (let i = 0; i < hex.length; i += 2) {
2417
+ bytes[i / 2] = parseInt(hex.substring(i, i + 2), 16);
2418
+ }
2419
+ return bytes.buffer;
2420
+ }
2421
+
2422
+ /**
2423
+ * HKDF implementation (JS fallback) per RFC 5869
2424
+ * HKDF-Extract: PRK = HMAC-Hash(salt, IKM)
2425
+ * HKDF-Expand: iteratively compute T(i) = HMAC-Hash(PRK, T(i-1) || info || i) until enough bytes
2426
+ */
2427
+ async function jsHkdf(
2428
+ ikm: Uint8Array,
2429
+ salt: Uint8Array,
2430
+ info: Uint8Array,
2431
+ hash: string,
2432
+ length: number
2433
+ ): Promise<ArrayBuffer> {
2434
+ const hashLen = getHashLength(hash) / 8;
2435
+
2436
+ // If salt is empty, use a hash-length block of zeros
2437
+ const actualSalt = salt.length > 0 ? salt : new Uint8Array(hashLen);
2438
+
2439
+ // Extract: PRK = HMAC-Hash(salt, IKM)
2440
+ const prk = new Uint8Array(await jsHmac(actualSalt, ikm, hash));
2441
+
2442
+ // Expand
2443
+ const n = Math.ceil(length / hashLen);
2444
+ const okm = new Uint8Array(n * hashLen);
2445
+ let prev = new Uint8Array(0);
2446
+
2447
+ for (let i = 1; i <= n; i++) {
2448
+ const input = new Uint8Array(prev.length + info.length + 1);
2449
+ input.set(prev, 0);
2450
+ input.set(info, prev.length);
2451
+ input[prev.length + info.length] = i;
2452
+ prev = new Uint8Array(await jsHmac(prk, input, hash));
2453
+ okm.set(prev, (i - 1) * hashLen);
2454
+ }
2455
+
2456
+ return okm.buffer.slice(0, length);
2457
+ }
2458
+
2459
+ /**
2460
+ * Parse RSA PEM key (public or private) and extract JWK components.
2461
+ * This parses the base64-encoded DER within the PEM and extracts the
2462
+ * RSA parameters (n, e, d, p, q, dp, dq, qi) from the ASN.1 structure.
2463
+ */
2464
+ function parseRsaPemToJwkComponents(pem: string, isPrivate: boolean): Record<string, string> {
2465
+ // Strip PEM headers and decode base64
2466
+ const b64 = pem.replace(/-----[A-Z ]+-----/g, '').replace(/\s/g, '');
2467
+ const der = base64Decode(b64);
2468
+
2469
+ // Parse ASN.1 DER
2470
+ const parsed = parseAsn1(der, 0);
2471
+
2472
+ if (isPrivate) {
2473
+ // PKCS#1 RSAPrivateKey or PKCS#8 PrivateKeyInfo
2474
+ // PKCS#8: SEQUENCE { version, AlgorithmIdentifier, OCTET STRING { RSAPrivateKey } }
2475
+ // PKCS#1: SEQUENCE { version, n, e, d, p, q, dp, dq, qi }
2476
+ let seq = parsed;
2477
+ if (seq.children && seq.children.length === 3 && seq.children[2].tag === 0x04) {
2478
+ // This is PKCS#8 - the RSAPrivateKey is inside the OCTET STRING
2479
+ seq = parseAsn1(seq.children[2].value as Uint8Array, 0);
2480
+ }
2481
+ if (!seq.children || seq.children.length < 9) {
2482
+ throw new DOMException('Invalid RSA private key structure', 'DataError');
2483
+ }
2484
+ // Skip version (index 0)
2485
+ return {
2486
+ n: base64UrlEncode(trimLeadingZero(seq.children[1].value as Uint8Array)),
2487
+ e: base64UrlEncode(trimLeadingZero(seq.children[2].value as Uint8Array)),
2488
+ d: base64UrlEncode(trimLeadingZero(seq.children[3].value as Uint8Array)),
2489
+ p: base64UrlEncode(trimLeadingZero(seq.children[4].value as Uint8Array)),
2490
+ q: base64UrlEncode(trimLeadingZero(seq.children[5].value as Uint8Array)),
2491
+ dp: base64UrlEncode(trimLeadingZero(seq.children[6].value as Uint8Array)),
2492
+ dq: base64UrlEncode(trimLeadingZero(seq.children[7].value as Uint8Array)),
2493
+ qi: base64UrlEncode(trimLeadingZero(seq.children[8].value as Uint8Array)),
2494
+ };
2495
+ } else {
2496
+ // PKCS#1 RSAPublicKey or SPKI SubjectPublicKeyInfo
2497
+ let seq = parsed;
2498
+ if (seq.children && seq.children.length === 2 && seq.children[1].tag === 0x03) {
2499
+ // SPKI: SEQUENCE { AlgorithmIdentifier, BIT STRING { RSAPublicKey } }
2500
+ const bitStr = seq.children[1].value as Uint8Array;
2501
+ // BIT STRING has a leading byte for unused bits count (usually 0x00)
2502
+ seq = parseAsn1(bitStr.slice(1), 0);
2503
+ }
2504
+ if (!seq.children || seq.children.length < 2) {
2505
+ throw new DOMException('Invalid RSA public key structure', 'DataError');
2506
+ }
2507
+ return {
2508
+ n: base64UrlEncode(trimLeadingZero(seq.children[0].value as Uint8Array)),
2509
+ e: base64UrlEncode(trimLeadingZero(seq.children[1].value as Uint8Array)),
2510
+ };
2511
+ }
2512
+ }
2513
+
2514
+ /**
2515
+ * Minimal ASN.1 DER parser
2516
+ */
2517
+ interface Asn1Node {
2518
+ tag: number;
2519
+ value: Uint8Array;
2520
+ children?: Asn1Node[];
2521
+ }
2522
+
2523
+ function parseAsn1(data: Uint8Array, offset: number): Asn1Node {
2524
+ const tag = data[offset];
2525
+ let lengthOffset = offset + 1;
2526
+ let length: number;
2527
+
2528
+ if (data[lengthOffset] & 0x80) {
2529
+ const numLengthBytes = data[lengthOffset] & 0x7f;
2530
+ length = 0;
2531
+ for (let i = 0; i < numLengthBytes; i++) {
2532
+ length = (length << 8) | data[lengthOffset + 1 + i];
2533
+ }
2534
+ lengthOffset += 1 + numLengthBytes;
2535
+ } else {
2536
+ length = data[lengthOffset];
2537
+ lengthOffset += 1;
2538
+ }
2539
+
2540
+ const value = data.slice(lengthOffset, lengthOffset + length);
2541
+ const node: Asn1Node = { tag, value };
2542
+
2543
+ // If it's a constructed type (SEQUENCE, SET), parse children
2544
+ if (tag === 0x30 || tag === 0x31) {
2545
+ node.children = [];
2546
+ let childOffset = 0;
2547
+ while (childOffset < value.length) {
2548
+ const child = parseAsn1(value, childOffset);
2549
+ node.children.push(child);
2550
+ // Compute child's total size
2551
+ let childLength = child.value.length;
2552
+ let headerLen = 2; // tag + length byte
2553
+ if (childLength >= 128) {
2554
+ let tmp = childLength;
2555
+ let numBytes = 0;
2556
+ while (tmp > 0) { numBytes++; tmp >>= 8; }
2557
+ headerLen = 2 + numBytes;
2558
+ }
2559
+ childOffset += headerLen + childLength;
2560
+ }
2561
+ }
2562
+
2563
+ // For INTEGER, store raw value bytes (may have leading zero for positive)
2564
+ return node;
2565
+ }
2566
+
2567
+ /**
2568
+ * Trim the leading zero byte from an ASN.1 INTEGER value
2569
+ * (ASN.1 uses a leading 0x00 to indicate a positive number when the high bit is set)
2570
+ */
2571
+ function trimLeadingZero(data: Uint8Array): Uint8Array {
2572
+ if (data.length > 1 && data[0] === 0x00) {
2573
+ return data.slice(1);
2574
+ }
2575
+ return data;
2576
+ }
2577
+
2578
+ /**
2579
+ * Standard base64 decode (not base64url)
2580
+ */
2581
+ function base64Decode(str: string): Uint8Array {
2582
+ const binary = atob(str);
2583
+ const bytes = new Uint8Array(binary.length);
2584
+ for (let i = 0; i < binary.length; i++) {
2585
+ bytes[i] = binary.charCodeAt(i);
2586
+ }
2587
+ return bytes;
2588
+ }
2589
+
2590
+ /**
2591
+ * Standard base64 encode
2592
+ */
2593
+ function base64Encode(data: Uint8Array): string {
2594
+ return btoa(String.fromCharCode(...data));
2595
+ }
2596
+
2597
+ /**
2598
+ * Convert RSA JWK to PEM format
2599
+ * Constructs ASN.1 DER for PKCS#1 and wraps in PEM
2600
+ */
2601
+ function rsaJwkToPem(jwk: JsonWebKey, isPrivate: boolean): string {
2602
+ if (isPrivate) {
2603
+ // PKCS#1 RSAPrivateKey
2604
+ const version = encodeAsn1Integer(new Uint8Array([0]));
2605
+ const n = encodeAsn1Integer(addLeadingZero(base64UrlDecode(jwk.n!)));
2606
+ const e = encodeAsn1Integer(addLeadingZero(base64UrlDecode(jwk.e!)));
2607
+ const d = encodeAsn1Integer(addLeadingZero(base64UrlDecode(jwk.d!)));
2608
+ const p = encodeAsn1Integer(addLeadingZero(base64UrlDecode(jwk.p!)));
2609
+ const q = encodeAsn1Integer(addLeadingZero(base64UrlDecode(jwk.q!)));
2610
+ const dp = encodeAsn1Integer(addLeadingZero(base64UrlDecode(jwk.dp!)));
2611
+ const dq = encodeAsn1Integer(addLeadingZero(base64UrlDecode(jwk.dq!)));
2612
+ const qi = encodeAsn1Integer(addLeadingZero(base64UrlDecode(jwk.qi!)));
2613
+
2614
+ const inner = concatUint8Arrays([version, n, e, d, p, q, dp, dq, qi]);
2615
+ const seq = encodeAsn1Sequence(inner);
2616
+
2617
+ // Wrap in PKCS#8 PrivateKeyInfo
2618
+ // SEQUENCE { INTEGER 0, SEQUENCE { OID rsaEncryption, NULL }, OCTET STRING { seq } }
2619
+ const pkcs8Version = encodeAsn1Integer(new Uint8Array([0]));
2620
+ const rsaOid = new Uint8Array([0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01]); // OID 1.2.840.113549.1.1.1
2621
+ const nullTag = new Uint8Array([0x05, 0x00]);
2622
+ const algSeq = encodeAsn1Sequence(concatUint8Arrays([rsaOid, nullTag]));
2623
+ const octetStr = encodeAsn1Tag(0x04, seq);
2624
+ const pkcs8 = encodeAsn1Sequence(concatUint8Arrays([pkcs8Version, algSeq, octetStr]));
2625
+
2626
+ const b64 = base64Encode(pkcs8);
2627
+ const lines = b64.match(/.{1,64}/g) || [];
2628
+ return '-----BEGIN PRIVATE KEY-----\n' + lines.join('\n') + '\n-----END PRIVATE KEY-----\n';
2629
+ } else {
2630
+ // PKCS#1 RSAPublicKey wrapped in SubjectPublicKeyInfo (SPKI)
2631
+ const n = encodeAsn1Integer(addLeadingZero(base64UrlDecode(jwk.n!)));
2632
+ const e = encodeAsn1Integer(addLeadingZero(base64UrlDecode(jwk.e!)));
2633
+ const rsaPubKey = encodeAsn1Sequence(concatUint8Arrays([n, e]));
2634
+
2635
+ // SubjectPublicKeyInfo: SEQUENCE { AlgorithmIdentifier, BIT STRING }
2636
+ const rsaOid = new Uint8Array([0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01]);
2637
+ const nullTag = new Uint8Array([0x05, 0x00]);
2638
+ const algSeq = encodeAsn1Sequence(concatUint8Arrays([rsaOid, nullTag]));
2639
+ // BIT STRING: 0x00 prefix (no unused bits) + DER-encoded RSAPublicKey
2640
+ const bitStrContent = concatUint8Arrays([new Uint8Array([0x00]), rsaPubKey]);
2641
+ const bitStr = encodeAsn1Tag(0x03, bitStrContent);
2642
+ const spki = encodeAsn1Sequence(concatUint8Arrays([algSeq, bitStr]));
2643
+
2644
+ const b64 = base64Encode(spki);
2645
+ const lines = b64.match(/.{1,64}/g) || [];
2646
+ return '-----BEGIN PUBLIC KEY-----\n' + lines.join('\n') + '\n-----END PUBLIC KEY-----\n';
2647
+ }
2648
+ }
2649
+
2650
+ /** Encode ASN.1 INTEGER */
2651
+ function encodeAsn1Integer(data: Uint8Array): Uint8Array {
2652
+ return encodeAsn1Tag(0x02, data);
2653
+ }
2654
+
2655
+ /** Encode ASN.1 SEQUENCE */
2656
+ function encodeAsn1Sequence(content: Uint8Array): Uint8Array {
2657
+ return encodeAsn1Tag(0x30, content);
2658
+ }
2659
+
2660
+ /** Encode an ASN.1 TLV (tag-length-value) */
2661
+ function encodeAsn1Tag(tag: number, content: Uint8Array): Uint8Array {
2662
+ const length = content.length;
2663
+ let header: Uint8Array;
2664
+ if (length < 128) {
2665
+ header = new Uint8Array([tag, length]);
2666
+ } else if (length < 256) {
2667
+ header = new Uint8Array([tag, 0x81, length]);
2668
+ } else if (length < 65536) {
2669
+ header = new Uint8Array([tag, 0x82, (length >> 8) & 0xff, length & 0xff]);
2670
+ } else {
2671
+ header = new Uint8Array([tag, 0x83, (length >> 16) & 0xff, (length >> 8) & 0xff, length & 0xff]);
2672
+ }
2673
+ return concatUint8Arrays([header, content]);
2674
+ }
2675
+
2676
+ /** Add leading zero byte if high bit is set (for ASN.1 positive INTEGER) */
2677
+ function addLeadingZero(data: Uint8Array): Uint8Array {
2678
+ if (data.length > 0 && (data[0] & 0x80) !== 0) {
2679
+ const result = new Uint8Array(data.length + 1);
2680
+ result[0] = 0x00;
2681
+ result.set(data, 1);
2682
+ return result;
2683
+ }
2684
+ return data;
2685
+ }
2686
+
2687
+ /** Concatenate multiple Uint8Arrays */
2688
+ function concatUint8Arrays(arrays: Uint8Array[]): Uint8Array {
2689
+ let totalLen = 0;
2690
+ for (const arr of arrays) totalLen += arr.length;
2691
+ const result = new Uint8Array(totalLen);
2692
+ let offset = 0;
2693
+ for (const arr of arrays) {
2694
+ result.set(arr, offset);
2695
+ offset += arr.length;
2696
+ }
2697
+ return result;
2698
+ }
2699
+
2700
+ /**
2701
+ * PBKDF2-SHA256 implementation (for JS fallback)
2702
+ */
2703
+ async function jsPbkdf2Sha256(
2704
+ password: Uint8Array,
2705
+ salt: Uint8Array,
2706
+ iterations: number,
2707
+ keyLength: number
2708
+ ): Promise<ArrayBuffer> {
2709
+ const hashLength = 32; // SHA-256 output
2710
+ const numBlocks = Math.ceil(keyLength / hashLength);
2711
+ const result = new Uint8Array(numBlocks * hashLength);
2712
+
2713
+ for (let block = 1; block <= numBlocks; block++) {
2714
+ // U1 = PRF(Password, Salt || INT(i))
2715
+ const blockData = new Uint8Array(salt.length + 4);
2716
+ blockData.set(salt);
2717
+ blockData[salt.length] = (block >> 24) & 0xff;
2718
+ blockData[salt.length + 1] = (block >> 16) & 0xff;
2719
+ blockData[salt.length + 2] = (block >> 8) & 0xff;
2720
+ blockData[salt.length + 3] = block & 0xff;
2721
+
2722
+ let u = new Uint8Array(await jsHmacSha256(password, blockData));
2723
+ const f = new Uint8Array(u);
2724
+
2725
+ // U2...Uc
2726
+ for (let i = 1; i < iterations; i++) {
2727
+ u = new Uint8Array(await jsHmacSha256(password, u));
2728
+ for (let j = 0; j < hashLength; j++) {
2729
+ f[j] ^= u[j];
2730
+ }
2731
+ }
2732
+
2733
+ result.set(f, (block - 1) * hashLength);
2734
+ }
2735
+
2736
+ return result.buffer.slice(0, keyLength);
2737
+ }
2738
+
2739
+ // =============================================================================
2740
+ // Singleton Export
2741
+ // =============================================================================
2742
+
2743
+ export const crypto = new Crypto();