react-native-quick-crypto 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/QuickCrypto.podspec +19 -52
  2. package/android/CMakeLists.txt +4 -2
  3. package/android/build.gradle +1 -1
  4. package/cpp/cipher/HybridCipher.cpp +20 -3
  5. package/cpp/cipher/HybridRsaCipher.cpp +20 -1
  6. package/cpp/ed25519/HybridEdKeyPair.cpp +8 -2
  7. package/cpp/keys/HybridKeyObjectHandle.cpp +8 -0
  8. package/cpp/keys/KeyObjectData.hpp +1 -1
  9. package/cpp/mldsa/HybridMlDsaKeyPair.cpp +264 -0
  10. package/cpp/mldsa/HybridMlDsaKeyPair.hpp +47 -0
  11. package/cpp/sign/HybridSignHandle.cpp +97 -22
  12. package/cpp/sign/HybridVerifyHandle.cpp +90 -21
  13. package/deps/ncrypto/.bazelignore +4 -0
  14. package/deps/ncrypto/.bazelrc +2 -0
  15. package/deps/ncrypto/.bazelversion +1 -0
  16. package/deps/ncrypto/.clang-format +111 -0
  17. package/deps/ncrypto/.github/workflows/bazel.yml +58 -0
  18. package/deps/ncrypto/.github/workflows/linter.yml +38 -0
  19. package/deps/ncrypto/.github/workflows/macos.yml +43 -0
  20. package/deps/ncrypto/.github/workflows/ubuntu.yml +46 -0
  21. package/deps/ncrypto/.github/workflows/visual-studio.yml +49 -0
  22. package/deps/ncrypto/.python-version +1 -0
  23. package/deps/ncrypto/BUILD.bazel +36 -0
  24. package/deps/ncrypto/CMakeLists.txt +55 -0
  25. package/deps/ncrypto/LICENSE +21 -0
  26. package/deps/ncrypto/MODULE.bazel +1 -0
  27. package/deps/ncrypto/MODULE.bazel.lock +280 -0
  28. package/deps/ncrypto/README.md +18 -0
  29. package/deps/ncrypto/WORKSPACE +15 -0
  30. package/deps/ncrypto/cmake/CPM.cmake +1225 -0
  31. package/deps/ncrypto/cmake/ncrypto-flags.cmake +16 -0
  32. package/deps/ncrypto/include/dh-primes.h +67 -0
  33. package/deps/ncrypto/{ncrypto.h → include/ncrypto.h} +361 -89
  34. package/deps/ncrypto/patches/0001-Expose-libdecrepit-so-NodeJS-can-use-it-for-ncrypto.patch +28 -0
  35. package/deps/ncrypto/pyproject.toml +38 -0
  36. package/deps/ncrypto/src/CMakeLists.txt +15 -0
  37. package/deps/ncrypto/src/engine.cpp +93 -0
  38. package/deps/ncrypto/{ncrypto.cc → src/ncrypto.cpp} +1168 -234
  39. package/deps/ncrypto/tests/BUILD.bazel +9 -0
  40. package/deps/ncrypto/tests/CMakeLists.txt +7 -0
  41. package/deps/ncrypto/tests/basic.cpp +86 -0
  42. package/deps/ncrypto/tools/run-clang-format.sh +42 -0
  43. package/lib/commonjs/ed.js +68 -0
  44. package/lib/commonjs/ed.js.map +1 -1
  45. package/lib/commonjs/keys/classes.js +6 -0
  46. package/lib/commonjs/keys/classes.js.map +1 -1
  47. package/lib/commonjs/mldsa.js +69 -0
  48. package/lib/commonjs/mldsa.js.map +1 -0
  49. package/lib/commonjs/specs/mlDsaKeyPair.nitro.js +6 -0
  50. package/lib/commonjs/specs/mlDsaKeyPair.nitro.js.map +1 -0
  51. package/lib/commonjs/subtle.js +483 -13
  52. package/lib/commonjs/subtle.js.map +1 -1
  53. package/lib/commonjs/utils/types.js.map +1 -1
  54. package/lib/module/ed.js +66 -0
  55. package/lib/module/ed.js.map +1 -1
  56. package/lib/module/keys/classes.js +6 -0
  57. package/lib/module/keys/classes.js.map +1 -1
  58. package/lib/module/mldsa.js +63 -0
  59. package/lib/module/mldsa.js.map +1 -0
  60. package/lib/module/specs/mlDsaKeyPair.nitro.js +4 -0
  61. package/lib/module/specs/mlDsaKeyPair.nitro.js.map +1 -0
  62. package/lib/module/subtle.js +484 -14
  63. package/lib/module/subtle.js.map +1 -1
  64. package/lib/module/utils/types.js.map +1 -1
  65. package/lib/tsconfig.tsbuildinfo +1 -1
  66. package/lib/typescript/ed.d.ts +4 -1
  67. package/lib/typescript/ed.d.ts.map +1 -1
  68. package/lib/typescript/index.d.ts +2 -0
  69. package/lib/typescript/index.d.ts.map +1 -1
  70. package/lib/typescript/keys/classes.d.ts +2 -0
  71. package/lib/typescript/keys/classes.d.ts.map +1 -1
  72. package/lib/typescript/mldsa.d.ts +18 -0
  73. package/lib/typescript/mldsa.d.ts.map +1 -0
  74. package/lib/typescript/specs/mlDsaKeyPair.nitro.d.ts +16 -0
  75. package/lib/typescript/specs/mlDsaKeyPair.nitro.d.ts.map +1 -0
  76. package/lib/typescript/subtle.d.ts +4 -1
  77. package/lib/typescript/subtle.d.ts.map +1 -1
  78. package/lib/typescript/utils/types.d.ts +14 -6
  79. package/lib/typescript/utils/types.d.ts.map +1 -1
  80. package/nitrogen/generated/android/QuickCrypto+autolinking.cmake +1 -0
  81. package/nitrogen/generated/android/QuickCryptoOnLoad.cpp +10 -0
  82. package/nitrogen/generated/ios/QuickCryptoAutolinking.mm +10 -0
  83. package/nitrogen/generated/shared/c++/AsymmetricKeyType.hpp +12 -0
  84. package/nitrogen/generated/shared/c++/HybridMlDsaKeyPairSpec.cpp +29 -0
  85. package/nitrogen/generated/shared/c++/HybridMlDsaKeyPairSpec.hpp +73 -0
  86. package/package.json +7 -3
  87. package/src/ed.ts +102 -0
  88. package/src/keys/classes.ts +9 -0
  89. package/src/mldsa.ts +125 -0
  90. package/src/specs/mlDsaKeyPair.nitro.ts +29 -0
  91. package/src/subtle.ts +667 -17
  92. package/src/utils/types.ts +27 -6
@@ -22,6 +22,7 @@ var _random = require("./random");
22
22
  var _hmac = require("./hmac");
23
23
  var _signVerify = require("./keys/signVerify");
24
24
  var _ed = require("./ed");
25
+ var _mldsa = require("./mldsa");
25
26
  /* eslint-disable @typescript-eslint/no-unused-vars */
26
27
  // import { pbkdf2DeriveBits } from './pbkdf2';
27
28
  // import { aesCipher, aesGenerateKey, aesImportKey, getAlgorithmName } from './aes';
@@ -270,6 +271,120 @@ async function aesGcmCipher(mode, key, data, algorithm) {
270
271
  return result.buffer;
271
272
  }
272
273
  }
274
+ async function aesKwCipher(mode, key, data) {
275
+ const isWrap = mode === CipherOrWrapMode.kWebCryptoCipherEncrypt;
276
+
277
+ // AES-KW requires input to be a multiple of 8 bytes (64 bits)
278
+ if (data.byteLength % 8 !== 0) {
279
+ throw (0, _errors.lazyDOMException)(`AES-KW input length must be a multiple of 8 bytes, got ${data.byteLength}`, 'OperationError');
280
+ }
281
+
282
+ // AES-KW requires at least 16 bytes of input (128 bits)
283
+ if (isWrap && data.byteLength < 16) {
284
+ throw (0, _errors.lazyDOMException)(`AES-KW input must be at least 16 bytes, got ${data.byteLength}`, 'OperationError');
285
+ }
286
+
287
+ // Get cipher type based on key length
288
+ const keyLength = key.algorithm.length;
289
+ // Use aes*-wrap for both operations (matching Node.js)
290
+ const cipherType = `aes${keyLength}-wrap`;
291
+
292
+ // Export key material
293
+ const exportedKey = key.keyObject.export();
294
+ const cipherKey = (0, _conversion.bufferLikeToArrayBuffer)(exportedKey);
295
+
296
+ // AES-KW uses a default IV as specified in RFC 3394
297
+ const defaultWrapIV = new Uint8Array([0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6]);
298
+ const factory = _reactNativeNitroModules.NitroModules.createHybridObject('CipherFactory');
299
+ const cipher = factory.createCipher({
300
+ isCipher: isWrap,
301
+ cipherType,
302
+ cipherKey,
303
+ iv: defaultWrapIV.buffer // RFC 3394 default IV for AES-KW
304
+ });
305
+
306
+ // Process data
307
+ const updated = cipher.update(data);
308
+ const final = cipher.final();
309
+
310
+ // Concatenate results
311
+ const result = new Uint8Array(updated.byteLength + final.byteLength);
312
+ result.set(new Uint8Array(updated), 0);
313
+ result.set(new Uint8Array(final), updated.byteLength);
314
+ return result.buffer;
315
+ }
316
+ async function chaCha20Poly1305Cipher(mode, key, data, algorithm) {
317
+ const {
318
+ iv,
319
+ additionalData,
320
+ tagLength = 128
321
+ } = algorithm;
322
+
323
+ // Validate IV (must be 12 bytes for ChaCha20-Poly1305)
324
+ const ivBuffer = (0, _conversion.bufferLikeToArrayBuffer)(iv);
325
+ if (!ivBuffer || ivBuffer.byteLength !== 12) {
326
+ throw (0, _errors.lazyDOMException)('ChaCha20-Poly1305 IV must be exactly 12 bytes', 'OperationError');
327
+ }
328
+
329
+ // Validate tag length (only 128-bit supported)
330
+ if (tagLength !== 128) {
331
+ throw (0, _errors.lazyDOMException)('ChaCha20-Poly1305 only supports 128-bit auth tags', 'NotSupportedError');
332
+ }
333
+ const tagByteLength = 16; // 128 bits = 16 bytes
334
+
335
+ // Create cipher using existing ChaCha20-Poly1305 implementation
336
+ const factory = _reactNativeNitroModules.NitroModules.createHybridObject('CipherFactory');
337
+ const cipher = factory.createCipher({
338
+ isCipher: mode === CipherOrWrapMode.kWebCryptoCipherEncrypt,
339
+ cipherType: 'chacha20-poly1305',
340
+ cipherKey: (0, _conversion.bufferLikeToArrayBuffer)(key.keyObject.export()),
341
+ iv: ivBuffer,
342
+ authTagLen: tagByteLength
343
+ });
344
+ let processData;
345
+ let authTag;
346
+ if (mode === CipherOrWrapMode.kWebCryptoCipherDecrypt) {
347
+ // For decryption, extract auth tag from end of data
348
+ const dataView = new Uint8Array(data);
349
+ if (dataView.byteLength < tagByteLength) {
350
+ throw (0, _errors.lazyDOMException)('The provided data is too small.', 'OperationError');
351
+ }
352
+
353
+ // Split data and tag
354
+ const ciphertextLength = dataView.byteLength - tagByteLength;
355
+ processData = dataView.slice(0, ciphertextLength).buffer;
356
+ authTag = dataView.slice(ciphertextLength).buffer;
357
+
358
+ // Set auth tag for verification
359
+ cipher.setAuthTag(authTag);
360
+ } else {
361
+ processData = data;
362
+ }
363
+
364
+ // Set additional authenticated data if provided
365
+ if (additionalData) {
366
+ cipher.setAAD((0, _conversion.bufferLikeToArrayBuffer)(additionalData));
367
+ }
368
+
369
+ // Process data
370
+ const updated = cipher.update(processData);
371
+ const final = cipher.final();
372
+ if (mode === CipherOrWrapMode.kWebCryptoCipherEncrypt) {
373
+ // For encryption, append auth tag to result
374
+ const tag = cipher.getAuthTag();
375
+ const result = new Uint8Array(updated.byteLength + final.byteLength + tag.byteLength);
376
+ result.set(new Uint8Array(updated), 0);
377
+ result.set(new Uint8Array(final), updated.byteLength);
378
+ result.set(new Uint8Array(tag), updated.byteLength + final.byteLength);
379
+ return result.buffer;
380
+ } else {
381
+ // For decryption, just concatenate plaintext
382
+ const result = new Uint8Array(updated.byteLength + final.byteLength);
383
+ result.set(new Uint8Array(updated), 0);
384
+ result.set(new Uint8Array(final), updated.byteLength);
385
+ return result.buffer;
386
+ }
387
+ }
273
388
  async function aesGenerateKey(algorithm, extractable, keyUsages) {
274
389
  const {
275
390
  length
@@ -350,10 +465,13 @@ async function hmacGenerateKey(algorithm, extractable, keyUsages) {
350
465
  // Create secret key
351
466
  const keyObject = (0, _keys.createSecretKey)(keyBytes);
352
467
 
353
- // Construct algorithm object
468
+ // Construct algorithm object with hash normalized to { name: string } format per WebCrypto spec
469
+ const webCryptoHashName = (0, _hashnames.normalizeHashName)(hash, _hashnames.HashContext.WebCrypto);
354
470
  const keyAlgorithm = {
355
471
  name: 'HMAC',
356
- hash: hashName,
472
+ hash: {
473
+ name: webCryptoHashName
474
+ },
357
475
  length
358
476
  };
359
477
  return new _keys.CryptoKey(keyObject, keyAlgorithm, keyUsages, extractable);
@@ -427,10 +545,17 @@ function rsaImportKey(format, data, algorithm, extractable, keyUsages) {
427
545
  }
428
546
  publicExponentBytes = new Uint8Array(bytes.length > 0 ? bytes : [0]);
429
547
  }
548
+
549
+ // Normalize hash to { name: string } format per WebCrypto spec
550
+ const hashName = (0, _hashnames.normalizeHashName)(algorithm.hash, _hashnames.HashContext.WebCrypto);
551
+ const normalizedHash = {
552
+ name: hashName
553
+ };
430
554
  const algorithmWithDetails = {
431
555
  ...algorithm,
432
556
  modulusLength: keyDetails?.modulusLength,
433
- publicExponent: publicExponentBytes
557
+ publicExponent: publicExponentBytes,
558
+ hash: normalizedHash
434
559
  };
435
560
  return new _keys.CryptoKey(keyObject, algorithmWithDetails, keyUsages, extractable);
436
561
  }
@@ -477,10 +602,17 @@ async function hmacImportKey(algorithm, format, data, extractable, keyUsages) {
477
602
  } else {
478
603
  throw new Error(`Unable to import HMAC key with format ${format}`);
479
604
  }
480
- return new _keys.CryptoKey(keyObject, {
605
+
606
+ // Normalize hash to { name: string } format per WebCrypto spec
607
+ const hashName = (0, _hashnames.normalizeHashName)(algorithm.hash, _hashnames.HashContext.WebCrypto);
608
+ const normalizedAlgorithm = {
481
609
  ...algorithm,
482
- name: 'HMAC'
483
- }, keyUsages, extractable);
610
+ name: 'HMAC',
611
+ hash: {
612
+ name: hashName
613
+ }
614
+ };
615
+ return new _keys.CryptoKey(keyObject, normalizedAlgorithm, keyUsages, extractable);
484
616
  }
485
617
  async function aesImportKey(algorithm, format, data, extractable, keyUsages) {
486
618
  const {
@@ -540,7 +672,9 @@ function edImportKey(format, data, algorithm, extractable, keyUsages) {
540
672
  } = algorithm;
541
673
 
542
674
  // Validate usages
543
- if (hasAnyNotIn(keyUsages, ['sign', 'verify'])) {
675
+ const isX = name === 'X25519' || name === 'X448';
676
+ const allowedUsages = isX ? ['deriveKey', 'deriveBits'] : ['sign', 'verify'];
677
+ if (hasAnyNotIn(keyUsages, allowedUsages)) {
544
678
  throw (0, _errors.lazyDOMException)(`Unsupported key usage for ${name} key`, 'SyntaxError');
545
679
  }
546
680
  let keyObject;
@@ -567,6 +701,31 @@ function edImportKey(format, data, algorithm, extractable, keyUsages) {
567
701
  name
568
702
  }, keyUsages, extractable);
569
703
  }
704
+ function mldsaImportKey(format, data, algorithm, extractable, keyUsages) {
705
+ const {
706
+ name
707
+ } = algorithm;
708
+
709
+ // Validate usages
710
+ if (hasAnyNotIn(keyUsages, ['sign', 'verify'])) {
711
+ throw (0, _errors.lazyDOMException)(`Unsupported key usage for ${name} key`, 'SyntaxError');
712
+ }
713
+ let keyObject;
714
+ if (format === 'spki') {
715
+ // Import public key
716
+ const keyData = (0, _conversion.bufferLikeToArrayBuffer)(data);
717
+ keyObject = _keys.KeyObject.createKeyObject('public', keyData, _utils.KFormatType.DER, _utils.KeyEncoding.SPKI);
718
+ } else if (format === 'pkcs8') {
719
+ // Import private key
720
+ const keyData = (0, _conversion.bufferLikeToArrayBuffer)(data);
721
+ keyObject = _keys.KeyObject.createKeyObject('private', keyData, _utils.KFormatType.DER, _utils.KeyEncoding.PKCS8);
722
+ } else {
723
+ throw (0, _errors.lazyDOMException)(`Unsupported format for ${name} import: ${format}`, 'NotSupportedError');
724
+ }
725
+ return new _keys.CryptoKey(keyObject, {
726
+ name
727
+ }, keyUsages, extractable);
728
+ }
570
729
  const exportKeySpki = async key => {
571
730
  switch (key.algorithm.name) {
572
731
  case 'RSASSA-PKCS1-v1_5':
@@ -588,8 +747,22 @@ const exportKeySpki = async key => {
588
747
  case 'Ed25519':
589
748
  // Fall through
590
749
  case 'Ed448':
750
+ // Fall through
751
+ case 'X25519':
752
+ // Fall through
753
+ case 'X448':
754
+ if (key.type === 'public') {
755
+ // Export Ed/X key in SPKI DER format
756
+ return (0, _conversion.bufferLikeToArrayBuffer)(key.keyObject.handle.exportKey(_utils.KFormatType.DER, _utils.KeyEncoding.SPKI));
757
+ }
758
+ break;
759
+ case 'ML-DSA-44':
760
+ // Fall through
761
+ case 'ML-DSA-65':
762
+ // Fall through
763
+ case 'ML-DSA-87':
591
764
  if (key.type === 'public') {
592
- // Export Ed key in SPKI DER format
765
+ // Export ML-DSA key in SPKI DER format
593
766
  return (0, _conversion.bufferLikeToArrayBuffer)(key.keyObject.handle.exportKey(_utils.KFormatType.DER, _utils.KeyEncoding.SPKI));
594
767
  }
595
768
  break;
@@ -617,8 +790,22 @@ const exportKeyPkcs8 = async key => {
617
790
  case 'Ed25519':
618
791
  // Fall through
619
792
  case 'Ed448':
793
+ // Fall through
794
+ case 'X25519':
795
+ // Fall through
796
+ case 'X448':
620
797
  if (key.type === 'private') {
621
- // Export Ed key in PKCS8 DER format
798
+ // Export Ed/X key in PKCS8 DER format
799
+ return (0, _conversion.bufferLikeToArrayBuffer)(key.keyObject.handle.exportKey(_utils.KFormatType.DER, _utils.KeyEncoding.PKCS8));
800
+ }
801
+ break;
802
+ case 'ML-DSA-44':
803
+ // Fall through
804
+ case 'ML-DSA-65':
805
+ // Fall through
806
+ case 'ML-DSA-87':
807
+ if (key.type === 'private') {
808
+ // Export ML-DSA key in PKCS8 DER format
622
809
  return (0, _conversion.bufferLikeToArrayBuffer)(key.keyObject.handle.exportKey(_utils.KFormatType.DER, _utils.KeyEncoding.PKCS8));
623
810
  }
624
811
  break;
@@ -634,6 +821,19 @@ const exportKeyRaw = key => {
634
821
  return ecExportKey(key, KWebCryptoKeyFormat.kWebCryptoKeyFormatRaw);
635
822
  }
636
823
  break;
824
+ case 'Ed25519':
825
+ // Fall through
826
+ case 'Ed448':
827
+ // Fall through
828
+ case 'X25519':
829
+ // Fall through
830
+ case 'X448':
831
+ if (key.type === 'public') {
832
+ // Export raw public key
833
+ const exported = key.keyObject.handle.exportKey();
834
+ return (0, _conversion.bufferLikeToArrayBuffer)(exported);
835
+ }
836
+ break;
637
837
  case 'AES-CTR':
638
838
  // Fall through
639
839
  case 'AES-CBC':
@@ -642,6 +842,8 @@ const exportKeyRaw = key => {
642
842
  // Fall through
643
843
  case 'AES-KW':
644
844
  // Fall through
845
+ case 'ChaCha20-Poly1305':
846
+ // Fall through
645
847
  case 'HMAC':
646
848
  {
647
849
  const exported = key.keyObject.export();
@@ -681,6 +883,8 @@ const exportKeyJWK = key => {
681
883
  case 'AES-GCM':
682
884
  // Fall through
683
885
  case 'AES-KW':
886
+ // Fall through
887
+ case 'ChaCha20-Poly1305':
684
888
  if (key.algorithm.length === undefined) {
685
889
  throw (0, _errors.lazyDOMException)(`Algorithm ${key.algorithm.name} missing required length property`, 'InvalidAccessError');
686
890
  }
@@ -816,6 +1020,29 @@ function edSignVerify(key, data, signature) {
816
1020
  return ed.verifySync(signatureBuffer, dataBuffer, rawKey);
817
1021
  }
818
1022
  }
1023
+ function mldsaSignVerify(key, data, signature) {
1024
+ const isSign = signature === undefined;
1025
+ const expectedKeyType = isSign ? 'private' : 'public';
1026
+ if (key.type !== expectedKeyType) {
1027
+ throw (0, _errors.lazyDOMException)(`Key must be a ${expectedKeyType} key`, 'InvalidAccessError');
1028
+ }
1029
+ const dataBuffer = (0, _conversion.bufferLikeToArrayBuffer)(data);
1030
+ if (isSign) {
1031
+ const signer = (0, _signVerify.createSign)('');
1032
+ signer.update(dataBuffer);
1033
+ const sig = signer.sign({
1034
+ key: key
1035
+ });
1036
+ return sig.buffer.slice(sig.byteOffset, sig.byteOffset + sig.byteLength);
1037
+ } else {
1038
+ const signatureBuffer = (0, _conversion.bufferLikeToArrayBuffer)(signature);
1039
+ const verifier = (0, _signVerify.createVerify)('');
1040
+ verifier.update(dataBuffer);
1041
+ return verifier.verify({
1042
+ key: key
1043
+ }, signatureBuffer);
1044
+ }
1045
+ }
819
1046
  const signVerify = (algorithm, key, data, signature) => {
820
1047
  const usage = signature === undefined ? 'sign' : 'verify';
821
1048
  algorithm = normalizeAlgorithm(algorithm, usage);
@@ -834,6 +1061,10 @@ const signVerify = (algorithm, key, data, signature) => {
834
1061
  case 'Ed25519':
835
1062
  case 'Ed448':
836
1063
  return edSignVerify(key, data, signature);
1064
+ case 'ML-DSA-44':
1065
+ case 'ML-DSA-65':
1066
+ case 'ML-DSA-87':
1067
+ return mldsaSignVerify(key, data, signature);
837
1068
  }
838
1069
  throw (0, _errors.lazyDOMException)(`Unrecognized algorithm name '${algorithm.name}' for '${usage}'`, 'NotSupportedError');
839
1070
  };
@@ -851,6 +1082,10 @@ const cipherOrWrap = async (mode, algorithm, key, data, op) => {
851
1082
  // Fall through
852
1083
  case 'AES-GCM':
853
1084
  return aesCipher(mode, key, data, algorithm);
1085
+ case 'AES-KW':
1086
+ return aesKwCipher(mode, key, data);
1087
+ case 'ChaCha20-Poly1305':
1088
+ return chaCha20Poly1305Cipher(mode, key, data, algorithm);
854
1089
  }
855
1090
  };
856
1091
  class Subtle {
@@ -863,16 +1098,49 @@ class Subtle {
863
1098
  return (0, _hash.asyncDigest)(normalizedAlgorithm, data);
864
1099
  }
865
1100
  async deriveBits(algorithm, baseKey, length) {
866
- if (!baseKey.keyUsages.includes('deriveBits')) {
867
- throw new Error('baseKey does not have deriveBits usage');
1101
+ // Allow either deriveBits OR deriveKey usage (WebCrypto spec allows both)
1102
+ if (!baseKey.keyUsages.includes('deriveBits') && !baseKey.keyUsages.includes('deriveKey')) {
1103
+ throw new Error('baseKey does not have deriveBits or deriveKey usage');
868
1104
  }
869
1105
  if (baseKey.algorithm.name !== algorithm.name) throw new Error('Key algorithm mismatch');
870
1106
  switch (algorithm.name) {
871
1107
  case 'PBKDF2':
872
1108
  return (0, _pbkdf.pbkdf2DeriveBits)(algorithm, baseKey, length);
1109
+ case 'X25519':
1110
+ // Fall through
1111
+ case 'X448':
1112
+ return (0, _ed.xDeriveBits)(algorithm, baseKey, length);
873
1113
  }
874
1114
  throw new Error(`'subtle.deriveBits()' for ${algorithm.name} is not implemented.`);
875
1115
  }
1116
+ async deriveKey(algorithm, baseKey, derivedKeyAlgorithm, extractable, keyUsages) {
1117
+ // Validate baseKey usage
1118
+ if (!baseKey.usages.includes('deriveKey') && !baseKey.usages.includes('deriveBits')) {
1119
+ throw (0, _errors.lazyDOMException)('baseKey does not have deriveKey or deriveBits usage', 'InvalidAccessError');
1120
+ }
1121
+
1122
+ // Calculate required key length
1123
+ const length = getKeyLength(derivedKeyAlgorithm);
1124
+
1125
+ // Step 1: Derive bits
1126
+ let derivedBits;
1127
+ if (baseKey.algorithm.name !== algorithm.name) throw new Error('Key algorithm mismatch');
1128
+ switch (algorithm.name) {
1129
+ case 'PBKDF2':
1130
+ derivedBits = await (0, _pbkdf.pbkdf2DeriveBits)(algorithm, baseKey, length);
1131
+ break;
1132
+ case 'X25519':
1133
+ // Fall through
1134
+ case 'X448':
1135
+ derivedBits = await (0, _ed.xDeriveBits)(algorithm, baseKey, length);
1136
+ break;
1137
+ default:
1138
+ throw new Error(`'subtle.deriveKey()' for ${algorithm.name} is not implemented.`);
1139
+ }
1140
+
1141
+ // Step 2: Import as key
1142
+ return this.importKey('raw', derivedBits, derivedKeyAlgorithm, extractable, keyUsages);
1143
+ }
876
1144
  async encrypt(algorithm, key, data) {
877
1145
  const normalizedAlgorithm = normalizeAlgorithm(algorithm, 'encrypt');
878
1146
  return cipherOrWrap(CipherOrWrapMode.kWebCryptoCipherEncrypt, normalizedAlgorithm, key, (0, _conversion.bufferLikeToArrayBuffer)(data), 'encrypt');
@@ -890,6 +1158,76 @@ class Subtle {
890
1158
  return exportKeyRaw(key);
891
1159
  }
892
1160
  }
1161
+ async wrapKey(format, key, wrappingKey, wrapAlgorithm) {
1162
+ // Validate wrappingKey usage
1163
+ if (!wrappingKey.usages.includes('wrapKey')) {
1164
+ throw (0, _errors.lazyDOMException)('wrappingKey does not have wrapKey usage', 'InvalidAccessError');
1165
+ }
1166
+
1167
+ // Step 1: Export the key
1168
+ const exported = await this.exportKey(format, key);
1169
+
1170
+ // Step 2: Convert to ArrayBuffer if JWK
1171
+ let keyData;
1172
+ if (format === 'jwk') {
1173
+ const jwkString = JSON.stringify(exported);
1174
+ const buffer = _safeBuffer.Buffer.from(jwkString, 'utf8');
1175
+
1176
+ // For AES-KW, pad to multiple of 8 bytes (accounting for null terminator)
1177
+ if (wrapAlgorithm.name === 'AES-KW') {
1178
+ const length = buffer.length;
1179
+ // Add 1 for null terminator, then pad to multiple of 8
1180
+ const paddedLength = Math.ceil((length + 1) / 8) * 8;
1181
+ const paddedBuffer = _safeBuffer.Buffer.alloc(paddedLength);
1182
+ buffer.copy(paddedBuffer);
1183
+ // Null terminator for JSON string (remaining bytes are already zeros from alloc)
1184
+ paddedBuffer.writeUInt8(0, length);
1185
+ keyData = (0, _conversion.bufferLikeToArrayBuffer)(paddedBuffer);
1186
+ } else {
1187
+ keyData = (0, _conversion.bufferLikeToArrayBuffer)(buffer);
1188
+ }
1189
+ } else {
1190
+ keyData = exported;
1191
+ }
1192
+
1193
+ // Step 3: Encrypt the exported key
1194
+ return cipherOrWrap(CipherOrWrapMode.kWebCryptoCipherEncrypt, wrapAlgorithm, wrappingKey, keyData, 'wrapKey');
1195
+ }
1196
+ async unwrapKey(format, wrappedKey, unwrappingKey, unwrapAlgorithm, unwrappedKeyAlgorithm, extractable, keyUsages) {
1197
+ // Validate unwrappingKey usage
1198
+ if (!unwrappingKey.usages.includes('unwrapKey')) {
1199
+ throw (0, _errors.lazyDOMException)('unwrappingKey does not have unwrapKey usage', 'InvalidAccessError');
1200
+ }
1201
+
1202
+ // Step 1: Decrypt the wrapped key
1203
+ const decrypted = await cipherOrWrap(CipherOrWrapMode.kWebCryptoCipherDecrypt, unwrapAlgorithm, unwrappingKey, (0, _conversion.bufferLikeToArrayBuffer)(wrappedKey), 'unwrapKey');
1204
+
1205
+ // Step 2: Convert to appropriate format
1206
+ let keyData;
1207
+ if (format === 'jwk') {
1208
+ const buffer = _safeBuffer.Buffer.from(decrypted);
1209
+ // For AES-KW, the data may be padded - find the null terminator
1210
+ let jwkString;
1211
+ if (unwrapAlgorithm.name === 'AES-KW') {
1212
+ // Find the null terminator (if present) to get the original string
1213
+ const nullIndex = buffer.indexOf(0);
1214
+ if (nullIndex !== -1) {
1215
+ jwkString = buffer.toString('utf8', 0, nullIndex);
1216
+ } else {
1217
+ // No null terminator, try to parse the whole buffer
1218
+ jwkString = buffer.toString('utf8').trim();
1219
+ }
1220
+ } else {
1221
+ jwkString = buffer.toString('utf8');
1222
+ }
1223
+ keyData = JSON.parse(jwkString);
1224
+ } else {
1225
+ keyData = decrypted;
1226
+ }
1227
+
1228
+ // Step 3: Import the key
1229
+ return this.importKey(format, keyData, unwrappedKeyAlgorithm, extractable, keyUsages);
1230
+ }
893
1231
  async generateKey(algorithm, extractable, keyUsages) {
894
1232
  algorithm = normalizeAlgorithm(algorithm, 'generateKey');
895
1233
  let result;
@@ -916,6 +1254,18 @@ class Subtle {
916
1254
  case 'AES-KW':
917
1255
  result = await aesGenerateKey(algorithm, extractable, keyUsages);
918
1256
  break;
1257
+ case 'ChaCha20-Poly1305':
1258
+ {
1259
+ const length = algorithm.length ?? 256;
1260
+ if (length !== 256) {
1261
+ throw (0, _errors.lazyDOMException)('ChaCha20-Poly1305 only supports 256-bit keys', 'NotSupportedError');
1262
+ }
1263
+ result = await aesGenerateKey({
1264
+ name: 'ChaCha20-Poly1305',
1265
+ length: 256
1266
+ }, extractable, keyUsages);
1267
+ break;
1268
+ }
919
1269
  case 'HMAC':
920
1270
  result = await hmacGenerateKey(algorithm, extractable, keyUsages);
921
1271
  break;
@@ -925,6 +1275,20 @@ class Subtle {
925
1275
  result = await (0, _ed.ed_generateKeyPairWebCrypto)(algorithm.name.toLowerCase(), extractable, keyUsages);
926
1276
  checkCryptoKeyPairUsages(result);
927
1277
  break;
1278
+ case 'ML-DSA-44':
1279
+ // Fall through
1280
+ case 'ML-DSA-65':
1281
+ // Fall through
1282
+ case 'ML-DSA-87':
1283
+ result = await (0, _mldsa.mldsa_generateKeyPairWebCrypto)(algorithm.name, extractable, keyUsages);
1284
+ checkCryptoKeyPairUsages(result);
1285
+ break;
1286
+ case 'X25519':
1287
+ // Fall through
1288
+ case 'X448':
1289
+ result = await (0, _ed.x_generateKeyPairWebCrypto)(algorithm.name.toLowerCase(), extractable, keyUsages);
1290
+ checkCryptoKeyPairUsages(result);
1291
+ break;
928
1292
  default:
929
1293
  throw new Error(`'subtle.generateKey()' is not implemented for ${algorithm.name}.
930
1294
  Unrecognized algorithm name`);
@@ -957,16 +1321,29 @@ class Subtle {
957
1321
  case 'AES-GCM':
958
1322
  // Fall through
959
1323
  case 'AES-KW':
1324
+ // Fall through
1325
+ case 'ChaCha20-Poly1305':
960
1326
  result = await aesImportKey(normalizedAlgorithm, format, data, extractable, keyUsages);
961
1327
  break;
962
1328
  case 'PBKDF2':
963
1329
  result = await importGenericSecretKey(normalizedAlgorithm, format, data, extractable, keyUsages);
964
1330
  break;
1331
+ case 'X25519':
1332
+ // Fall through
1333
+ case 'X448':
1334
+ // Fall through
965
1335
  case 'Ed25519':
966
1336
  // Fall through
967
1337
  case 'Ed448':
968
1338
  result = edImportKey(format, data, normalizedAlgorithm, extractable, keyUsages);
969
1339
  break;
1340
+ case 'ML-DSA-44':
1341
+ // Fall through
1342
+ case 'ML-DSA-65':
1343
+ // Fall through
1344
+ case 'ML-DSA-87':
1345
+ result = mldsaImportKey(format, data, normalizedAlgorithm, extractable, keyUsages);
1346
+ break;
970
1347
  default:
971
1348
  throw new Error(`"subtle.importKey()" is not implemented for ${normalizedAlgorithm.name}`);
972
1349
  }
@@ -976,12 +1353,105 @@ class Subtle {
976
1353
  return result;
977
1354
  }
978
1355
  async sign(algorithm, key, data) {
979
- return signVerify(algorithm, key, data);
1356
+ const normalizedAlgorithm = normalizeAlgorithm(algorithm, 'sign');
1357
+ if (normalizedAlgorithm.name === 'HMAC') {
1358
+ // Validate key usage
1359
+ if (!key.usages.includes('sign')) {
1360
+ throw (0, _errors.lazyDOMException)('Key does not have sign usage', 'InvalidAccessError');
1361
+ }
1362
+
1363
+ // Get hash algorithm from key or algorithm params
1364
+ // Hash can be either a string or an object with name property
1365
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1366
+ const alg = normalizedAlgorithm;
1367
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1368
+ const keyAlg = key.algorithm;
1369
+ let hashAlgorithm = 'SHA-256';
1370
+ if (typeof alg.hash === 'string') {
1371
+ hashAlgorithm = alg.hash;
1372
+ } else if (alg.hash?.name) {
1373
+ hashAlgorithm = alg.hash.name;
1374
+ } else if (typeof keyAlg.hash === 'string') {
1375
+ hashAlgorithm = keyAlg.hash;
1376
+ } else if (keyAlg.hash?.name) {
1377
+ hashAlgorithm = keyAlg.hash.name;
1378
+ }
1379
+
1380
+ // Create HMAC and sign
1381
+ const keyData = key.keyObject.export();
1382
+ const hmac = (0, _hmac.createHmac)(hashAlgorithm, keyData);
1383
+ hmac.update((0, _conversion.bufferLikeToArrayBuffer)(data));
1384
+ return (0, _conversion.bufferLikeToArrayBuffer)(hmac.digest());
1385
+ }
1386
+ return signVerify(normalizedAlgorithm, key, data);
980
1387
  }
981
1388
  async verify(algorithm, key, signature, data) {
982
- return signVerify(algorithm, key, data, signature);
1389
+ const normalizedAlgorithm = normalizeAlgorithm(algorithm, 'verify');
1390
+ if (normalizedAlgorithm.name === 'HMAC') {
1391
+ // Validate key usage
1392
+ if (!key.usages.includes('verify')) {
1393
+ throw (0, _errors.lazyDOMException)('Key does not have verify usage', 'InvalidAccessError');
1394
+ }
1395
+
1396
+ // Get hash algorithm
1397
+ // Hash can be either a string or an object with name property
1398
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1399
+ const alg = normalizedAlgorithm;
1400
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1401
+ const keyAlg = key.algorithm;
1402
+ let hashAlgorithm = 'SHA-256';
1403
+ if (typeof alg.hash === 'string') {
1404
+ hashAlgorithm = alg.hash;
1405
+ } else if (alg.hash?.name) {
1406
+ hashAlgorithm = alg.hash.name;
1407
+ } else if (typeof keyAlg.hash === 'string') {
1408
+ hashAlgorithm = keyAlg.hash;
1409
+ } else if (keyAlg.hash?.name) {
1410
+ hashAlgorithm = keyAlg.hash.name;
1411
+ }
1412
+
1413
+ // Create HMAC and compute expected signature
1414
+ const keyData = key.keyObject.export();
1415
+ const hmac = (0, _hmac.createHmac)(hashAlgorithm, keyData);
1416
+ const dataBuffer = (0, _conversion.bufferLikeToArrayBuffer)(data);
1417
+ hmac.update(dataBuffer);
1418
+ const expectedDigest = hmac.digest();
1419
+ const expected = new Uint8Array((0, _conversion.bufferLikeToArrayBuffer)(expectedDigest));
1420
+
1421
+ // Constant-time comparison
1422
+ const signatureArray = new Uint8Array((0, _conversion.bufferLikeToArrayBuffer)(signature));
1423
+ if (expected.length !== signatureArray.length) {
1424
+ return false;
1425
+ }
1426
+
1427
+ // Manual constant-time comparison
1428
+ let result = 0;
1429
+ for (let i = 0; i < expected.length; i++) {
1430
+ result |= expected[i] ^ signatureArray[i];
1431
+ }
1432
+ return result === 0;
1433
+ }
1434
+ return signVerify(normalizedAlgorithm, key, data, signature);
983
1435
  }
984
1436
  }
985
1437
  exports.Subtle = Subtle;
986
1438
  const subtle = exports.subtle = new Subtle();
1439
+ function getKeyLength(algorithm) {
1440
+ const name = algorithm.name;
1441
+ switch (name) {
1442
+ case 'AES-CTR':
1443
+ case 'AES-CBC':
1444
+ case 'AES-GCM':
1445
+ case 'AES-KW':
1446
+ case 'ChaCha20-Poly1305':
1447
+ return algorithm.length || 256;
1448
+ case 'HMAC':
1449
+ {
1450
+ const hmacAlg = algorithm;
1451
+ return hmacAlg.length || 256;
1452
+ }
1453
+ default:
1454
+ throw (0, _errors.lazyDOMException)(`Cannot determine key length for ${name}`, 'NotSupportedError');
1455
+ }
1456
+ }
987
1457
  //# sourceMappingURL=subtle.js.map