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