react-native-quick-crypto 1.1.3 → 1.1.4

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.
@@ -29,7 +29,6 @@ var _mldsa = require("./mldsa");
29
29
  var _slhdsa = require("./slhdsa");
30
30
  var _mlkem = require("./mlkem");
31
31
  var _hkdf = require("./hkdf");
32
- /* eslint-disable @typescript-eslint/no-unused-vars */
33
32
  // Temporary enums that need to be defined
34
33
  var KWebCryptoKeyFormat = /*#__PURE__*/function (KWebCryptoKeyFormat) {
35
34
  KWebCryptoKeyFormat[KWebCryptoKeyFormat["kWebCryptoKeyFormatRaw"] = 0] = "kWebCryptoKeyFormatRaw";
@@ -81,21 +80,682 @@ function getCanonicalAlgorithmNames() {
81
80
  }
82
81
  return _canonicalAlgorithmNames;
83
82
  }
84
- function normalizeAlgorithm(algorithm, _operation) {
85
- const map = getCanonicalAlgorithmNames();
83
+
84
+ // Per-algorithm WebIDL converter table. Mirrors Node's kAlgorithmDefinitions
85
+ // (lib/internal/crypto/util.js): each (algorithm, operation) pair maps to a
86
+ // dictionary converter name, or null when only the `name` member is required.
87
+ // Operation keys are missing when an algorithm cannot perform that operation,
88
+ // causing `normalizeAlgorithm` to reject the call.
89
+ const kAlgorithmDefinitions = {
90
+ 'AES-CBC': {
91
+ generateKey: 'AesKeyGenParams',
92
+ exportKey: null,
93
+ importKey: null,
94
+ encrypt: 'AesCbcParams',
95
+ decrypt: 'AesCbcParams',
96
+ 'get key length': 'AesDerivedKeyParams'
97
+ },
98
+ 'AES-CTR': {
99
+ generateKey: 'AesKeyGenParams',
100
+ exportKey: null,
101
+ importKey: null,
102
+ encrypt: 'AesCtrParams',
103
+ decrypt: 'AesCtrParams',
104
+ 'get key length': 'AesDerivedKeyParams'
105
+ },
106
+ 'AES-GCM': {
107
+ generateKey: 'AesKeyGenParams',
108
+ exportKey: null,
109
+ importKey: null,
110
+ encrypt: 'AeadParams',
111
+ decrypt: 'AeadParams',
112
+ 'get key length': 'AesDerivedKeyParams'
113
+ },
114
+ 'AES-KW': {
115
+ generateKey: 'AesKeyGenParams',
116
+ exportKey: null,
117
+ importKey: null,
118
+ 'get key length': 'AesDerivedKeyParams',
119
+ wrapKey: null,
120
+ unwrapKey: null
121
+ },
122
+ 'AES-OCB': {
123
+ generateKey: 'AesKeyGenParams',
124
+ exportKey: null,
125
+ importKey: null,
126
+ encrypt: 'AeadParams',
127
+ decrypt: 'AeadParams',
128
+ 'get key length': 'AesDerivedKeyParams'
129
+ },
130
+ Argon2d: {
131
+ deriveBits: 'Argon2Params',
132
+ 'get key length': null,
133
+ importKey: null
134
+ },
135
+ Argon2i: {
136
+ deriveBits: 'Argon2Params',
137
+ 'get key length': null,
138
+ importKey: null
139
+ },
140
+ Argon2id: {
141
+ deriveBits: 'Argon2Params',
142
+ 'get key length': null,
143
+ importKey: null
144
+ },
145
+ 'ChaCha20-Poly1305': {
146
+ generateKey: null,
147
+ exportKey: null,
148
+ importKey: null,
149
+ encrypt: 'AeadParams',
150
+ decrypt: 'AeadParams',
151
+ 'get key length': null
152
+ },
153
+ ECDH: {
154
+ generateKey: 'EcKeyGenParams',
155
+ exportKey: null,
156
+ importKey: 'EcKeyImportParams',
157
+ deriveBits: 'EcdhKeyDeriveParams'
158
+ },
159
+ ECDSA: {
160
+ generateKey: 'EcKeyGenParams',
161
+ exportKey: null,
162
+ importKey: 'EcKeyImportParams',
163
+ sign: 'EcdsaParams',
164
+ verify: 'EcdsaParams'
165
+ },
166
+ Ed25519: {
167
+ generateKey: null,
168
+ exportKey: null,
169
+ importKey: null,
170
+ sign: null,
171
+ verify: null
172
+ },
173
+ Ed448: {
174
+ generateKey: null,
175
+ exportKey: null,
176
+ importKey: null,
177
+ sign: 'ContextParams',
178
+ verify: 'ContextParams'
179
+ },
180
+ HKDF: {
181
+ importKey: null,
182
+ deriveBits: 'HkdfParams',
183
+ 'get key length': null
184
+ },
185
+ HMAC: {
186
+ generateKey: 'HmacKeyGenParams',
187
+ exportKey: null,
188
+ importKey: 'HmacImportParams',
189
+ sign: null,
190
+ verify: null,
191
+ 'get key length': 'HmacImportParams'
192
+ },
193
+ KMAC128: {
194
+ generateKey: 'KmacKeyGenParams',
195
+ exportKey: null,
196
+ importKey: 'KmacImportParams',
197
+ sign: 'KmacParams',
198
+ verify: 'KmacParams',
199
+ 'get key length': 'KmacImportParams'
200
+ },
201
+ KMAC256: {
202
+ generateKey: 'KmacKeyGenParams',
203
+ exportKey: null,
204
+ importKey: 'KmacImportParams',
205
+ sign: 'KmacParams',
206
+ verify: 'KmacParams',
207
+ 'get key length': 'KmacImportParams'
208
+ },
209
+ 'ML-DSA-44': {
210
+ generateKey: null,
211
+ exportKey: null,
212
+ importKey: null,
213
+ sign: 'ContextParams',
214
+ verify: 'ContextParams'
215
+ },
216
+ 'ML-DSA-65': {
217
+ generateKey: null,
218
+ exportKey: null,
219
+ importKey: null,
220
+ sign: 'ContextParams',
221
+ verify: 'ContextParams'
222
+ },
223
+ 'ML-DSA-87': {
224
+ generateKey: null,
225
+ exportKey: null,
226
+ importKey: null,
227
+ sign: 'ContextParams',
228
+ verify: 'ContextParams'
229
+ },
230
+ 'ML-KEM-512': {
231
+ generateKey: null,
232
+ exportKey: null,
233
+ importKey: null,
234
+ encapsulateBits: null,
235
+ decapsulateBits: null,
236
+ encapsulateKey: null,
237
+ decapsulateKey: null
238
+ },
239
+ 'ML-KEM-768': {
240
+ generateKey: null,
241
+ exportKey: null,
242
+ importKey: null,
243
+ encapsulateBits: null,
244
+ decapsulateBits: null,
245
+ encapsulateKey: null,
246
+ decapsulateKey: null
247
+ },
248
+ 'ML-KEM-1024': {
249
+ generateKey: null,
250
+ exportKey: null,
251
+ importKey: null,
252
+ encapsulateBits: null,
253
+ decapsulateBits: null,
254
+ encapsulateKey: null,
255
+ decapsulateKey: null
256
+ },
257
+ PBKDF2: {
258
+ importKey: null,
259
+ deriveBits: 'Pbkdf2Params',
260
+ 'get key length': null
261
+ },
262
+ 'RSA-OAEP': {
263
+ generateKey: 'RsaHashedKeyGenParams',
264
+ exportKey: null,
265
+ importKey: 'RsaHashedImportParams',
266
+ encrypt: 'RsaOaepParams',
267
+ decrypt: 'RsaOaepParams'
268
+ },
269
+ 'RSA-PSS': {
270
+ generateKey: 'RsaHashedKeyGenParams',
271
+ exportKey: null,
272
+ importKey: 'RsaHashedImportParams',
273
+ sign: 'RsaPssParams',
274
+ verify: 'RsaPssParams'
275
+ },
276
+ 'RSASSA-PKCS1-v1_5': {
277
+ generateKey: 'RsaHashedKeyGenParams',
278
+ exportKey: null,
279
+ importKey: 'RsaHashedImportParams',
280
+ sign: null,
281
+ verify: null
282
+ },
283
+ 'SHA-1': {
284
+ digest: null
285
+ },
286
+ 'SHA-256': {
287
+ digest: null
288
+ },
289
+ 'SHA-384': {
290
+ digest: null
291
+ },
292
+ 'SHA-512': {
293
+ digest: null
294
+ },
295
+ 'SHA3-256': {
296
+ digest: null
297
+ },
298
+ 'SHA3-384': {
299
+ digest: null
300
+ },
301
+ 'SHA3-512': {
302
+ digest: null
303
+ },
304
+ cSHAKE128: {
305
+ digest: 'CShakeParams'
306
+ },
307
+ cSHAKE256: {
308
+ digest: 'CShakeParams'
309
+ },
310
+ KT128: {
311
+ digest: 'KangarooTwelveParams'
312
+ },
313
+ KT256: {
314
+ digest: 'KangarooTwelveParams'
315
+ },
316
+ TurboSHAKE128: {
317
+ digest: 'TurboShakeParams'
318
+ },
319
+ TurboSHAKE256: {
320
+ digest: 'TurboShakeParams'
321
+ },
322
+ X25519: {
323
+ generateKey: null,
324
+ exportKey: null,
325
+ importKey: null,
326
+ deriveBits: 'EcdhKeyDeriveParams'
327
+ },
328
+ X448: {
329
+ generateKey: null,
330
+ exportKey: null,
331
+ importKey: null,
332
+ deriveBits: 'EcdhKeyDeriveParams'
333
+ }
334
+ };
335
+ for (const v of _slhdsa.SLH_DSA_VARIANTS) {
336
+ kAlgorithmDefinitions[v] = {
337
+ generateKey: null,
338
+ exportKey: null,
339
+ importKey: null,
340
+ sign: null,
341
+ verify: null
342
+ };
343
+ }
344
+
345
+ // WebIDL dictionary member specs. Mirrors Node's per-converter
346
+ // `createDictionaryConverter` definitions in lib/internal/crypto/webidl.js.
347
+ // `required: true` causes `normalizeAlgorithm` to throw a TypeError when the
348
+ // member is missing — matching the spec'd WebCrypto behavior that
349
+ // `SubtleCrypto.supports` relies on via try/catch.
350
+
351
+ const kRequiredFields = {
352
+ AesKeyGenParams: [{
353
+ key: 'length',
354
+ required: true
355
+ }],
356
+ AesDerivedKeyParams: [{
357
+ key: 'length',
358
+ required: true
359
+ }],
360
+ AesCbcParams: [{
361
+ key: 'iv',
362
+ required: true
363
+ }],
364
+ AesCtrParams: [{
365
+ key: 'counter',
366
+ required: true
367
+ }, {
368
+ key: 'length',
369
+ required: true
370
+ }],
371
+ AeadParams: [{
372
+ key: 'iv',
373
+ required: true
374
+ }, {
375
+ key: 'tagLength'
376
+ }, {
377
+ key: 'additionalData'
378
+ }],
379
+ EcKeyGenParams: [{
380
+ key: 'namedCurve',
381
+ required: true
382
+ }],
383
+ EcKeyImportParams: [{
384
+ key: 'namedCurve',
385
+ required: true
386
+ }],
387
+ EcdsaParams: [{
388
+ key: 'hash',
389
+ required: true
390
+ }],
391
+ EcdhKeyDeriveParams: [{
392
+ key: 'public',
393
+ required: true
394
+ }],
395
+ HmacKeyGenParams: [{
396
+ key: 'hash',
397
+ required: true
398
+ }, {
399
+ key: 'length'
400
+ }],
401
+ HmacImportParams: [{
402
+ key: 'hash',
403
+ required: true
404
+ }, {
405
+ key: 'length'
406
+ }],
407
+ HkdfParams: [{
408
+ key: 'hash',
409
+ required: true
410
+ }, {
411
+ key: 'salt',
412
+ required: true
413
+ }, {
414
+ key: 'info',
415
+ required: true
416
+ }],
417
+ Pbkdf2Params: [{
418
+ key: 'hash',
419
+ required: true
420
+ }, {
421
+ key: 'iterations',
422
+ required: true
423
+ }, {
424
+ key: 'salt',
425
+ required: true
426
+ }],
427
+ RsaHashedKeyGenParams: [{
428
+ key: 'modulusLength',
429
+ required: true
430
+ }, {
431
+ key: 'publicExponent',
432
+ required: true
433
+ }, {
434
+ key: 'hash',
435
+ required: true
436
+ }],
437
+ RsaHashedImportParams: [{
438
+ key: 'hash',
439
+ required: true
440
+ }],
441
+ RsaOaepParams: [{
442
+ key: 'label'
443
+ }],
444
+ RsaPssParams: [{
445
+ key: 'saltLength',
446
+ required: true
447
+ }],
448
+ ContextParams: [{
449
+ key: 'context'
450
+ }],
451
+ Argon2Params: [{
452
+ key: 'nonce',
453
+ required: true
454
+ }, {
455
+ key: 'parallelism',
456
+ required: true
457
+ }, {
458
+ key: 'memory',
459
+ required: true
460
+ }, {
461
+ key: 'passes',
462
+ required: true
463
+ }, {
464
+ key: 'version'
465
+ }, {
466
+ key: 'secretValue'
467
+ }, {
468
+ key: 'associatedData'
469
+ }],
470
+ KmacKeyGenParams: [{
471
+ key: 'length'
472
+ }],
473
+ KmacImportParams: [{
474
+ key: 'length'
475
+ }],
476
+ KmacParams: [{
477
+ key: 'outputLength',
478
+ required: true
479
+ }, {
480
+ key: 'customization'
481
+ }],
482
+ CShakeParams: [{
483
+ key: 'outputLength',
484
+ required: true
485
+ }, {
486
+ key: 'functionName'
487
+ }, {
488
+ key: 'customization'
489
+ }],
490
+ KangarooTwelveParams: [{
491
+ key: 'outputLength',
492
+ required: true
493
+ }, {
494
+ key: 'customization'
495
+ }],
496
+ TurboShakeParams: [{
497
+ key: 'outputLength',
498
+ required: true
499
+ }, {
500
+ key: 'domainSeparation'
501
+ }]
502
+ };
503
+ function isBufferSource(value) {
504
+ return value instanceof ArrayBuffer || ArrayBuffer.isView(value);
505
+ }
506
+ function validateBufferSource(algorithm, key) {
507
+ const value = algorithm[key];
508
+ if (value === undefined) return undefined;
509
+ if (!isBufferSource(value)) {
510
+ throw new TypeError(`Failed to normalize algorithm: '${key}' must be a BufferSource`);
511
+ }
512
+ return (0, _conversion.bufferLikeToArrayBuffer)(value);
513
+ }
514
+ function validateBinaryLike(algorithm, key) {
515
+ const value = algorithm[key];
516
+ if (value === undefined) return undefined;
517
+ try {
518
+ return (0, _conversion.binaryLikeToArrayBuffer)(value);
519
+ } catch {
520
+ throw new TypeError(`Failed to normalize algorithm: '${key}' must be a BufferSource`);
521
+ }
522
+ }
523
+ function validateByteLength(buffer, key, length, message) {
524
+ if (buffer !== undefined && buffer.byteLength !== length) {
525
+ throw (0, _errors.lazyDOMException)(message ?? `${key} must be ${length} bytes`, 'OperationError');
526
+ }
527
+ }
528
+ function validateUnsignedInteger(algorithm, key) {
529
+ const value = algorithm[key];
530
+ if (value === undefined) return undefined;
531
+ const numberValue = Number(value);
532
+ if (!Number.isFinite(numberValue) || !Number.isInteger(numberValue) || numberValue < 0) {
533
+ throw new TypeError(`Failed to normalize algorithm: '${key}' must be an unsigned integer`);
534
+ }
535
+ algorithm[key] = numberValue;
536
+ return numberValue;
537
+ }
538
+ function validateHashAlgorithm(algorithm, converterName) {
539
+ const hash = algorithm.hash;
540
+ if (hash === undefined) return;
541
+ const normalizedHash = (0, _hashnames.normalizeHashName)(hash, _hashnames.HashContext.WebCrypto);
542
+ if (!['SHA-1', 'SHA-256', 'SHA-384', 'SHA-512', 'SHA3-256', 'SHA3-384', 'SHA3-512'].includes(normalizedHash)) {
543
+ throw (0, _errors.lazyDOMException)(`Unsupported ${converterName}.hash`, 'NotSupportedError');
544
+ }
545
+ algorithm.hash = {
546
+ name: normalizedHash
547
+ };
548
+ }
549
+ function validateAesLength(length) {
550
+ if (length !== undefined && length !== 128 && length !== 192 && length !== 256) {
551
+ throw (0, _errors.lazyDOMException)('Invalid key length', 'OperationError');
552
+ }
553
+ }
554
+ function validateMacLength(algorithm, key, zeroError) {
555
+ const length = validateUnsignedInteger(algorithm, key);
556
+ if (length === undefined) return;
557
+ if (length === 0) {
558
+ throw (0, _errors.lazyDOMException)(`${key} cannot be 0`, zeroError);
559
+ }
560
+ if (length % 8) {
561
+ throw (0, _errors.lazyDOMException)(`Unsupported ${key}`, 'NotSupportedError');
562
+ }
563
+ }
564
+ function validateAeadParams(algorithm) {
565
+ const iv = validateBufferSource(algorithm, 'iv');
566
+ const tagLength = validateUnsignedInteger(algorithm, 'tagLength');
567
+ validateBufferSource(algorithm, 'additionalData');
568
+ switch (algorithm.name) {
569
+ case 'AES-GCM':
570
+ if (tagLength !== undefined && ![32, 64, 96, 104, 112, 120, 128].includes(tagLength)) {
571
+ throw (0, _errors.lazyDOMException)(`${tagLength} is not a valid AES-GCM tag length`, 'OperationError');
572
+ }
573
+ break;
574
+ case 'AES-OCB':
575
+ if (iv !== undefined && (iv.byteLength < 1 || iv.byteLength > 15)) {
576
+ throw (0, _errors.lazyDOMException)('AES-OCB algorithm.iv must be between 1 and 15 bytes', 'OperationError');
577
+ }
578
+ if (tagLength !== undefined && ![64, 96, 128].includes(tagLength)) {
579
+ throw (0, _errors.lazyDOMException)(`${tagLength} is not a valid AES-OCB tag length`, 'OperationError');
580
+ }
581
+ break;
582
+ case 'ChaCha20-Poly1305':
583
+ validateByteLength(iv, 'algorithm.iv', 12, 'ChaCha20-Poly1305 IV must be exactly 12 bytes');
584
+ if (tagLength !== undefined && tagLength !== 128) {
585
+ throw (0, _errors.lazyDOMException)(`${tagLength} is not a valid ChaCha20-Poly1305 tag length`, 'OperationError');
586
+ }
587
+ break;
588
+ }
589
+ }
590
+ function validateNormalizedAlgorithm(converterName, algorithm) {
591
+ switch (converterName) {
592
+ case 'AesKeyGenParams':
593
+ case 'AesDerivedKeyParams':
594
+ validateAesLength(validateUnsignedInteger(algorithm, 'length'));
595
+ break;
596
+ case 'AesCbcParams':
597
+ validateByteLength(validateBufferSource(algorithm, 'iv'), 'algorithm.iv', 16, 'algorithm.iv must contain exactly 16 bytes');
598
+ break;
599
+ case 'AesCtrParams':
600
+ {
601
+ validateByteLength(validateBufferSource(algorithm, 'counter'), 'algorithm.counter', 16);
602
+ const length = validateUnsignedInteger(algorithm, 'length');
603
+ if (length !== undefined && (length === 0 || length > 128)) {
604
+ throw (0, _errors.lazyDOMException)('AES-CTR algorithm.length must be between 1 and 128', 'OperationError');
605
+ }
606
+ break;
607
+ }
608
+ case 'AeadParams':
609
+ validateAeadParams(algorithm);
610
+ break;
611
+ case 'EcdsaParams':
612
+ case 'HmacKeyGenParams':
613
+ case 'HmacImportParams':
614
+ case 'HkdfParams':
615
+ case 'Pbkdf2Params':
616
+ case 'RsaHashedKeyGenParams':
617
+ case 'RsaHashedImportParams':
618
+ validateHashAlgorithm(algorithm, converterName);
619
+ if (converterName === 'HmacKeyGenParams') {
620
+ validateMacLength(algorithm, 'length', 'OperationError');
621
+ }
622
+ if (converterName === 'HkdfParams') {
623
+ validateBinaryLike(algorithm, 'salt');
624
+ validateBinaryLike(algorithm, 'info');
625
+ } else if (converterName === 'Pbkdf2Params') {
626
+ const iterations = validateUnsignedInteger(algorithm, 'iterations');
627
+ if (iterations === 0) {
628
+ throw (0, _errors.lazyDOMException)('iterations cannot be zero', 'OperationError');
629
+ }
630
+ validateBinaryLike(algorithm, 'salt');
631
+ } else if (converterName === 'RsaHashedKeyGenParams') {
632
+ validateUnsignedInteger(algorithm, 'modulusLength');
633
+ validateBufferSource(algorithm, 'publicExponent');
634
+ }
635
+ break;
636
+ case 'RsaPssParams':
637
+ validateUnsignedInteger(algorithm, 'saltLength');
638
+ break;
639
+ case 'RsaOaepParams':
640
+ validateBufferSource(algorithm, 'label');
641
+ break;
642
+ case 'ContextParams':
643
+ validateBufferSource(algorithm, 'context');
644
+ break;
645
+ case 'EcdhKeyDeriveParams':
646
+ if (!(algorithm.public instanceof _keys.CryptoKey)) {
647
+ throw (0, _errors.lazyDOMException)('algorithm.public must be a public key', 'InvalidAccessError');
648
+ }
649
+ break;
650
+ case 'Argon2Params':
651
+ {
652
+ validateBufferSource(algorithm, 'nonce');
653
+ const parallelism = validateUnsignedInteger(algorithm, 'parallelism');
654
+ const memory = validateUnsignedInteger(algorithm, 'memory');
655
+ validateUnsignedInteger(algorithm, 'passes');
656
+ const version = validateUnsignedInteger(algorithm, 'version');
657
+ validateBufferSource(algorithm, 'secretValue');
658
+ validateBufferSource(algorithm, 'associatedData');
659
+ if (parallelism !== undefined && (parallelism === 0 || parallelism > 2 ** 24 - 1)) {
660
+ throw (0, _errors.lazyDOMException)('parallelism must be > 0 and < 16777215', 'OperationError');
661
+ }
662
+ if (memory !== undefined && parallelism !== undefined && memory < 8 * parallelism) {
663
+ throw (0, _errors.lazyDOMException)('memory must be at least 8 times the degree of parallelism', 'OperationError');
664
+ }
665
+ if (version !== undefined && version !== 0x13) {
666
+ throw (0, _errors.lazyDOMException)(`${version} is not a valid Argon2 version`, 'OperationError');
667
+ }
668
+ break;
669
+ }
670
+ case 'KmacKeyGenParams':
671
+ validateMacLength(algorithm, 'length', 'OperationError');
672
+ break;
673
+ case 'KmacImportParams':
674
+ validateMacLength(algorithm, 'length', 'DataError');
675
+ break;
676
+ case 'KmacParams':
677
+ validateMacLength(algorithm, 'outputLength', 'OperationError');
678
+ validateBufferSource(algorithm, 'customization');
679
+ break;
680
+ case 'CShakeParams':
681
+ case 'KangarooTwelveParams':
682
+ {
683
+ const outputLength = validateUnsignedInteger(algorithm, 'outputLength');
684
+ if (outputLength !== undefined && (outputLength === 0 || outputLength % 8)) {
685
+ throw (0, _errors.lazyDOMException)(`Invalid ${converterName} outputLength`, 'OperationError');
686
+ }
687
+ validateBufferSource(algorithm, 'functionName');
688
+ validateBufferSource(algorithm, 'customization');
689
+ break;
690
+ }
691
+ case 'TurboShakeParams':
692
+ {
693
+ const outputLength = validateUnsignedInteger(algorithm, 'outputLength');
694
+ const domainSeparation = validateUnsignedInteger(algorithm, 'domainSeparation');
695
+ if (outputLength !== undefined && (outputLength === 0 || outputLength % 8)) {
696
+ throw (0, _errors.lazyDOMException)('Invalid TurboShakeParams outputLength', 'OperationError');
697
+ }
698
+ if (domainSeparation !== undefined && (domainSeparation < 0x01 || domainSeparation > 0x7f)) {
699
+ throw (0, _errors.lazyDOMException)('TurboShakeParams.domainSeparation must be in range 0x01-0x7f', 'OperationError');
700
+ }
701
+ break;
702
+ }
703
+ }
704
+ }
705
+
706
+ // WebCrypto §18.4.4 algorithm normalization. Mirrors Node's normalizeAlgorithm
707
+ // in lib/internal/crypto/util.js: canonicalizes `name`, looks up the
708
+ // (name, op) → converter mapping, and rejects inputs that omit required
709
+ // dictionary members. Callers in Subtle.supports rely on this throwing for
710
+ // invalid params — without it, supports() over-reports capability (#1025).
711
+ function normalizeAlgorithm(algorithm, operation) {
86
712
  if (typeof algorithm === 'string') {
713
+ return normalizeAlgorithm({
714
+ name: algorithm
715
+ }, operation);
716
+ }
717
+ const name = algorithm.name;
718
+ if (typeof name !== 'string') {
719
+ throw new TypeError("Algorithm: 'name' is required");
720
+ }
721
+ const map = getCanonicalAlgorithmNames();
722
+ const canonical = map.get(name.toLowerCase());
723
+ if (canonical === undefined) {
724
+ throw (0, _errors.lazyDOMException)('Unrecognized algorithm name', 'NotSupportedError');
725
+ }
726
+ const opMap = kAlgorithmDefinitions[canonical];
727
+ if (!opMap || !(operation in opMap)) {
728
+ throw (0, _errors.lazyDOMException)('Unrecognized algorithm name', 'NotSupportedError');
729
+ }
730
+ const converterName = opMap[operation];
731
+ if (converterName == null) {
87
732
  return {
88
- name: map.get(algorithm.toLowerCase()) ?? algorithm
733
+ name: canonical
89
734
  };
90
735
  }
91
- if (typeof algorithm.name === 'string') {
92
- const canonical = map.get(algorithm.name.toLowerCase()) ?? algorithm.name;
736
+ const fields = kRequiredFields[converterName];
737
+ if (!fields) {
93
738
  return {
94
739
  ...algorithm,
95
740
  name: canonical
96
741
  };
97
742
  }
98
- return algorithm;
743
+ const out = {
744
+ name: canonical
745
+ };
746
+ const src = algorithm;
747
+ for (const field of fields) {
748
+ const value = src[field.key];
749
+ if (value === undefined) {
750
+ if (field.required) {
751
+ throw new TypeError(`Failed to normalize algorithm: '${field.key}' is required in '${converterName}'`);
752
+ }
753
+ continue;
754
+ }
755
+ out[field.key] = value;
756
+ }
757
+ validateNormalizedAlgorithm(converterName, out);
758
+ return out;
99
759
  }
100
760
  function getAlgorithmName(name, length) {
101
761
  switch (name) {
@@ -1972,7 +2632,6 @@ class Subtle {
1972
2632
  }
1973
2633
  async deriveBits(algorithm, baseKey, length = null) {
1974
2634
  requireArgs(arguments.length, 2, 'deriveBits');
1975
- const normalizedAlgorithm = normalizeAlgorithm(algorithm, 'deriveBits');
1976
2635
  // WebCrypto §SubtleCrypto.deriveBits step 11: throw InvalidAccessError
1977
2636
  // unless `baseKey.[[usages]]` contains "deriveBits" specifically. The
1978
2637
  // previous `deriveBits || deriveKey` accept-either branch silently
@@ -1981,6 +2640,7 @@ class Subtle {
1981
2640
  if (!baseKey.usages.includes('deriveBits')) {
1982
2641
  throw (0, _errors.lazyDOMException)('baseKey does not have deriveBits usage', 'InvalidAccessError');
1983
2642
  }
2643
+ const normalizedAlgorithm = normalizeAlgorithm(algorithm, 'deriveBits');
1984
2644
  if (baseKey.algorithm.name !== normalizedAlgorithm.name) {
1985
2645
  throw (0, _errors.lazyDOMException)('Key algorithm mismatch', 'InvalidAccessError');
1986
2646
  }
@@ -2014,7 +2674,9 @@ class Subtle {
2014
2674
  async deriveKey(algorithm, baseKey, derivedKeyAlgorithm, extractable, keyUsages) {
2015
2675
  requireArgs(arguments.length, 5, 'deriveKey');
2016
2676
  const normalizedAlgorithm = normalizeAlgorithm(algorithm, 'deriveBits');
2017
- const normalizedDerivedKeyAlgorithm = normalizeAlgorithm(derivedKeyAlgorithm, 'importKey');
2677
+ // Validate the derived-key algorithm up front (mirrors Node webcrypto.js:341).
2678
+ // The normalized form is unused — `this.importKey` re-normalizes below.
2679
+ normalizeAlgorithm(derivedKeyAlgorithm, 'importKey');
2018
2680
 
2019
2681
  // Validate baseKey usage
2020
2682
  if (!baseKey.usages.includes('deriveKey')) {
@@ -2025,7 +2687,10 @@ class Subtle {
2025
2687
  }
2026
2688
 
2027
2689
  // Calculate required key length (may be null for KDF-derived material).
2028
- const length = getKeyLength(normalizedDerivedKeyAlgorithm);
2690
+ // Mirrors Node webcrypto.js:350 — uses the raw derivedKeyAlgorithm with
2691
+ // op='get key length' so AES `length` survives normalization (the
2692
+ // 'importKey' converter for AES is null and strips dictionary members).
2693
+ const length = getKeyLength(normalizeAlgorithm(derivedKeyAlgorithm, 'get key length'));
2029
2694
 
2030
2695
  // Step 1: Derive bits
2031
2696
  let derivedBits;
@@ -2105,7 +2770,15 @@ class Subtle {
2105
2770
  }
2106
2771
  async wrapKey(format, key, wrappingKey, wrapAlgorithm) {
2107
2772
  requireArgs(arguments.length, 4, 'wrapKey');
2108
- const normalizedWrapAlgorithm = normalizeAlgorithm(wrapAlgorithm, 'wrapKey');
2773
+ // Mirrors Node webcrypto.js:923-927: prefer the 'wrapKey' op (only
2774
+ // AES-KW defines it) and fall back to 'encrypt' for cipher-based wrap
2775
+ // algorithms like AES-GCM and RSA-OAEP.
2776
+ let normalizedWrapAlgorithm;
2777
+ try {
2778
+ normalizedWrapAlgorithm = normalizeAlgorithm(wrapAlgorithm, 'wrapKey');
2779
+ } catch {
2780
+ normalizedWrapAlgorithm = normalizeAlgorithm(wrapAlgorithm, 'encrypt');
2781
+ }
2109
2782
  if (normalizedWrapAlgorithm.name !== wrappingKey.algorithm.name) {
2110
2783
  throw (0, _errors.lazyDOMException)('Key algorithm mismatch', 'InvalidAccessError');
2111
2784
  }
@@ -2144,7 +2817,14 @@ class Subtle {
2144
2817
  }
2145
2818
  async unwrapKey(format, wrappedKey, unwrappingKey, unwrapAlgorithm, unwrappedKeyAlgorithm, extractable, keyUsages) {
2146
2819
  requireArgs(arguments.length, 7, 'unwrapKey');
2147
- const normalizedUnwrapAlgorithm = normalizeAlgorithm(unwrapAlgorithm, 'unwrapKey');
2820
+ // Mirrors Node webcrypto.js:1006-1010: prefer 'unwrapKey', fall back to
2821
+ // 'decrypt' for cipher-based unwrap algorithms.
2822
+ let normalizedUnwrapAlgorithm;
2823
+ try {
2824
+ normalizedUnwrapAlgorithm = normalizeAlgorithm(unwrapAlgorithm, 'unwrapKey');
2825
+ } catch {
2826
+ normalizedUnwrapAlgorithm = normalizeAlgorithm(unwrapAlgorithm, 'decrypt');
2827
+ }
2148
2828
  if (normalizedUnwrapAlgorithm.name !== unwrappingKey.algorithm.name) {
2149
2829
  throw (0, _errors.lazyDOMException)('Key algorithm mismatch', 'InvalidAccessError');
2150
2830
  }