react-native-quick-crypto 1.1.1 → 1.1.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 (181) hide show
  1. package/QuickCrypto.podspec +1 -0
  2. package/android/CMakeLists.txt +4 -0
  3. package/cpp/cipher/CCMCipher.cpp +7 -11
  4. package/cpp/cipher/ChaCha20Cipher.cpp +6 -10
  5. package/cpp/cipher/ChaCha20Poly1305Cipher.cpp +10 -16
  6. package/cpp/cipher/GCMCipher.cpp +3 -5
  7. package/cpp/cipher/HybridCipher.cpp +7 -13
  8. package/cpp/cipher/HybridRsaCipher.cpp +19 -27
  9. package/cpp/cipher/OCBCipher.cpp +2 -3
  10. package/cpp/cipher/XChaCha20Poly1305Cipher.cpp +13 -19
  11. package/cpp/cipher/XSalsa20Cipher.cpp +8 -12
  12. package/cpp/cipher/XSalsa20Poly1305Cipher.cpp +11 -16
  13. package/cpp/keys/HybridKeyObjectHandle.cpp +630 -2
  14. package/cpp/keys/HybridKeyObjectHandle.hpp +21 -1
  15. package/cpp/sign/HybridSignHandle.cpp +26 -8
  16. package/cpp/sign/HybridVerifyHandle.cpp +28 -11
  17. package/cpp/slhdsa/HybridSlhDsaKeyPair.cpp +245 -0
  18. package/cpp/slhdsa/HybridSlhDsaKeyPair.hpp +48 -0
  19. package/cpp/turboshake/HybridTurboShake.cpp +379 -0
  20. package/cpp/turboshake/HybridTurboShake.hpp +28 -0
  21. package/cpp/utils/HybridUtils.cpp +26 -14
  22. package/deps/blake3/README.md +6 -7
  23. package/deps/blake3/c/blake3.c +3 -2
  24. package/deps/blake3/c/blake3.h +2 -2
  25. package/deps/blake3/c/blake3_dispatch.c +2 -2
  26. package/deps/blake3/c/blake3_impl.h +1 -1
  27. package/deps/blake3/c/blake3_neon.c +5 -4
  28. package/deps/ncrypto/include/ncrypto/version.h +2 -2
  29. package/deps/ncrypto/include/ncrypto.h +9 -2
  30. package/deps/ncrypto/src/ncrypto.cpp +130 -35
  31. package/lib/commonjs/dhKeyPair.js +3 -0
  32. package/lib/commonjs/dhKeyPair.js.map +1 -1
  33. package/lib/commonjs/dsa.js +3 -0
  34. package/lib/commonjs/dsa.js.map +1 -1
  35. package/lib/commonjs/ec.js +37 -30
  36. package/lib/commonjs/ec.js.map +1 -1
  37. package/lib/commonjs/ed.js +60 -6
  38. package/lib/commonjs/ed.js.map +1 -1
  39. package/lib/commonjs/hash.js +52 -5
  40. package/lib/commonjs/hash.js.map +1 -1
  41. package/lib/commonjs/keys/classes.js +33 -7
  42. package/lib/commonjs/keys/classes.js.map +1 -1
  43. package/lib/commonjs/keys/generateKeyPair.js +85 -4
  44. package/lib/commonjs/keys/generateKeyPair.js.map +1 -1
  45. package/lib/commonjs/keys/index.js +50 -2
  46. package/lib/commonjs/keys/index.js.map +1 -1
  47. package/lib/commonjs/keys/signVerify.js +9 -2
  48. package/lib/commonjs/keys/signVerify.js.map +1 -1
  49. package/lib/commonjs/keys/utils.js +59 -1
  50. package/lib/commonjs/keys/utils.js.map +1 -1
  51. package/lib/commonjs/random.js +63 -9
  52. package/lib/commonjs/random.js.map +1 -1
  53. package/lib/commonjs/rsa.js +3 -0
  54. package/lib/commonjs/rsa.js.map +1 -1
  55. package/lib/commonjs/slhdsa.js +70 -0
  56. package/lib/commonjs/slhdsa.js.map +1 -0
  57. package/lib/commonjs/specs/slhDsaKeyPair.nitro.js +6 -0
  58. package/lib/commonjs/specs/slhDsaKeyPair.nitro.js.map +1 -0
  59. package/lib/commonjs/specs/turboshake.nitro.js +6 -0
  60. package/lib/commonjs/specs/turboshake.nitro.js.map +1 -0
  61. package/lib/commonjs/subtle.js +926 -275
  62. package/lib/commonjs/subtle.js.map +1 -1
  63. package/lib/commonjs/utils/conversion.js +53 -19
  64. package/lib/commonjs/utils/conversion.js.map +1 -1
  65. package/lib/commonjs/utils/errors.js +63 -4
  66. package/lib/commonjs/utils/errors.js.map +1 -1
  67. package/lib/commonjs/utils/types.js.map +1 -1
  68. package/lib/commonjs/utils/validation.js +46 -0
  69. package/lib/commonjs/utils/validation.js.map +1 -1
  70. package/lib/module/dhKeyPair.js +3 -0
  71. package/lib/module/dhKeyPair.js.map +1 -1
  72. package/lib/module/dsa.js +3 -0
  73. package/lib/module/dsa.js.map +1 -1
  74. package/lib/module/ec.js +38 -31
  75. package/lib/module/ec.js.map +1 -1
  76. package/lib/module/ed.js +61 -7
  77. package/lib/module/ed.js.map +1 -1
  78. package/lib/module/hash.js +52 -5
  79. package/lib/module/hash.js.map +1 -1
  80. package/lib/module/keys/classes.js +31 -5
  81. package/lib/module/keys/classes.js.map +1 -1
  82. package/lib/module/keys/generateKeyPair.js +86 -5
  83. package/lib/module/keys/generateKeyPair.js.map +1 -1
  84. package/lib/module/keys/index.js +50 -2
  85. package/lib/module/keys/index.js.map +1 -1
  86. package/lib/module/keys/signVerify.js +9 -2
  87. package/lib/module/keys/signVerify.js.map +1 -1
  88. package/lib/module/keys/utils.js +57 -1
  89. package/lib/module/keys/utils.js.map +1 -1
  90. package/lib/module/random.js +63 -10
  91. package/lib/module/random.js.map +1 -1
  92. package/lib/module/rsa.js +3 -0
  93. package/lib/module/rsa.js.map +1 -1
  94. package/lib/module/slhdsa.js +64 -0
  95. package/lib/module/slhdsa.js.map +1 -0
  96. package/lib/module/specs/slhDsaKeyPair.nitro.js +4 -0
  97. package/lib/module/specs/slhDsaKeyPair.nitro.js.map +1 -0
  98. package/lib/module/specs/turboshake.nitro.js +4 -0
  99. package/lib/module/specs/turboshake.nitro.js.map +1 -0
  100. package/lib/module/subtle.js +927 -276
  101. package/lib/module/subtle.js.map +1 -1
  102. package/lib/module/utils/conversion.js +51 -19
  103. package/lib/module/utils/conversion.js.map +1 -1
  104. package/lib/module/utils/errors.js +61 -4
  105. package/lib/module/utils/errors.js.map +1 -1
  106. package/lib/module/utils/types.js.map +1 -1
  107. package/lib/module/utils/validation.js +44 -0
  108. package/lib/module/utils/validation.js.map +1 -1
  109. package/lib/typescript/dhKeyPair.d.ts.map +1 -1
  110. package/lib/typescript/dsa.d.ts.map +1 -1
  111. package/lib/typescript/ec.d.ts.map +1 -1
  112. package/lib/typescript/ed.d.ts.map +1 -1
  113. package/lib/typescript/hash.d.ts.map +1 -1
  114. package/lib/typescript/index.d.ts +12 -7
  115. package/lib/typescript/index.d.ts.map +1 -1
  116. package/lib/typescript/keys/classes.d.ts +10 -1
  117. package/lib/typescript/keys/classes.d.ts.map +1 -1
  118. package/lib/typescript/keys/generateKeyPair.d.ts +12 -1
  119. package/lib/typescript/keys/generateKeyPair.d.ts.map +1 -1
  120. package/lib/typescript/keys/index.d.ts +3 -1
  121. package/lib/typescript/keys/index.d.ts.map +1 -1
  122. package/lib/typescript/keys/signVerify.d.ts.map +1 -1
  123. package/lib/typescript/keys/utils.d.ts +21 -4
  124. package/lib/typescript/keys/utils.d.ts.map +1 -1
  125. package/lib/typescript/random.d.ts +5 -1
  126. package/lib/typescript/random.d.ts.map +1 -1
  127. package/lib/typescript/rsa.d.ts.map +1 -1
  128. package/lib/typescript/slhdsa.d.ts +19 -0
  129. package/lib/typescript/slhdsa.d.ts.map +1 -0
  130. package/lib/typescript/specs/keyObjectHandle.nitro.d.ts +9 -0
  131. package/lib/typescript/specs/keyObjectHandle.nitro.d.ts.map +1 -1
  132. package/lib/typescript/specs/slhDsaKeyPair.nitro.d.ts +16 -0
  133. package/lib/typescript/specs/slhDsaKeyPair.nitro.d.ts.map +1 -0
  134. package/lib/typescript/specs/turboshake.nitro.d.ts +11 -0
  135. package/lib/typescript/specs/turboshake.nitro.d.ts.map +1 -0
  136. package/lib/typescript/subtle.d.ts +3 -2
  137. package/lib/typescript/subtle.d.ts.map +1 -1
  138. package/lib/typescript/utils/conversion.d.ts +4 -3
  139. package/lib/typescript/utils/conversion.d.ts.map +1 -1
  140. package/lib/typescript/utils/errors.d.ts +12 -0
  141. package/lib/typescript/utils/errors.d.ts.map +1 -1
  142. package/lib/typescript/utils/types.d.ts +32 -15
  143. package/lib/typescript/utils/types.d.ts.map +1 -1
  144. package/lib/typescript/utils/validation.d.ts +3 -1
  145. package/lib/typescript/utils/validation.d.ts.map +1 -1
  146. package/nitrogen/generated/android/QuickCrypto+autolinking.cmake +2 -0
  147. package/nitrogen/generated/android/QuickCryptoOnLoad.cpp +20 -0
  148. package/nitrogen/generated/ios/QuickCryptoAutolinking.mm +20 -0
  149. package/nitrogen/generated/shared/c++/AsymmetricKeyType.hpp +48 -0
  150. package/nitrogen/generated/shared/c++/HybridKeyObjectHandleSpec.cpp +9 -0
  151. package/nitrogen/generated/shared/c++/HybridKeyObjectHandleSpec.hpp +9 -0
  152. package/nitrogen/generated/shared/c++/HybridSlhDsaKeyPairSpec.cpp +29 -0
  153. package/nitrogen/generated/shared/c++/HybridSlhDsaKeyPairSpec.hpp +72 -0
  154. package/nitrogen/generated/shared/c++/HybridTurboShakeSpec.cpp +22 -0
  155. package/nitrogen/generated/shared/c++/HybridTurboShakeSpec.hpp +70 -0
  156. package/nitrogen/generated/shared/c++/JWK.hpp +9 -1
  157. package/nitrogen/generated/shared/c++/JWKkty.hpp +4 -0
  158. package/nitrogen/generated/shared/c++/KangarooTwelveVariant.hpp +76 -0
  159. package/nitrogen/generated/shared/c++/TurboShakeVariant.hpp +76 -0
  160. package/package.json +2 -3
  161. package/src/dhKeyPair.ts +8 -0
  162. package/src/dsa.ts +8 -0
  163. package/src/ec.ts +52 -29
  164. package/src/ed.ts +95 -16
  165. package/src/hash.ts +108 -5
  166. package/src/keys/classes.ts +46 -5
  167. package/src/keys/generateKeyPair.ts +151 -5
  168. package/src/keys/index.ts +73 -3
  169. package/src/keys/signVerify.ts +13 -2
  170. package/src/keys/utils.ts +78 -5
  171. package/src/random.ts +93 -9
  172. package/src/rsa.ts +8 -0
  173. package/src/slhdsa.ts +146 -0
  174. package/src/specs/keyObjectHandle.nitro.ts +17 -0
  175. package/src/specs/slhDsaKeyPair.nitro.ts +29 -0
  176. package/src/specs/turboshake.nitro.ts +21 -0
  177. package/src/subtle.ts +1191 -360
  178. package/src/utils/conversion.ts +72 -21
  179. package/src/utils/errors.ts +72 -4
  180. package/src/utils/types.ts +80 -15
  181. package/src/utils/validation.ts +70 -1
@@ -3,11 +3,12 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.Subtle = void 0;
6
+ exports.Subtle = exports.PQC_SEEDLESS_PKCS8_LENGTHS = void 0;
7
7
  exports.isCryptoKeyPair = isCryptoKeyPair;
8
8
  exports.subtle = void 0;
9
9
  var _safeBuffer = require("safe-buffer");
10
10
  var _utils = require("./utils");
11
+ var _reactNativeBuffer = require("@craftzdog/react-native-buffer");
11
12
  var _keys = require("./keys");
12
13
  var _conversion = require("./utils/conversion");
13
14
  var _argon = require("./argon2");
@@ -25,6 +26,7 @@ var _timingSafeEqual = require("./utils/timingSafeEqual");
25
26
  var _signVerify = require("./keys/signVerify");
26
27
  var _ed = require("./ed");
27
28
  var _mldsa = require("./mldsa");
29
+ var _slhdsa = require("./slhdsa");
28
30
  var _mlkem = require("./mlkem");
29
31
  var _hkdf = require("./hkdf");
30
32
  /* eslint-disable @typescript-eslint/no-unused-vars */
@@ -44,6 +46,16 @@ function hasAnyNotIn(usages, allowed) {
44
46
  return usages.some(usage => !allowed.includes(usage));
45
47
  }
46
48
 
49
+ // Mirrors webidl.requiredArguments. Node throws TypeError when a SubtleCrypto
50
+ // method is called with fewer than the spec-required number of arguments
51
+ // (webcrypto.js:866 etc.); we relied on TypeScript types alone, which apps
52
+ // catching `instanceof TypeError` could not see at runtime.
53
+ function requireArgs(actual, required, method) {
54
+ if (actual < required) {
55
+ throw new TypeError(`Failed to execute '${method}' on 'SubtleCrypto': ${required} arguments required, but only ${actual} present.`);
56
+ }
57
+ }
58
+
47
59
  // WebCrypto §18.4.4: algorithm name lookup is case-insensitive, but the
48
60
  // canonical mixed-case form is preserved in the resulting `name` field
49
61
  // (e.g. "aes-gcm" → "AES-GCM"). This map is built lazily on first call so
@@ -85,27 +97,6 @@ function normalizeAlgorithm(algorithm, _operation) {
85
97
  }
86
98
  return algorithm;
87
99
  }
88
-
89
- // WebCrypto §25.7.6 (JWK import): if the JWK's `ext` member is present and
90
- // false, the requested `extractable` parameter must also be false. If the
91
- // JWK's `key_ops` member is present, every requested usage must appear in
92
- // it. We centralize the check here so every importKey path that accepts
93
- // `format === 'jwk'` can reuse it.
94
- function validateJwkExtAndKeyOps(jwk, extractable, keyUsages) {
95
- if (jwk.ext === false && extractable) {
96
- throw (0, _errors.lazyDOMException)('JWK "ext" is false but extractable was requested', 'DataError');
97
- }
98
- if (jwk.key_ops !== undefined) {
99
- if (!Array.isArray(jwk.key_ops)) {
100
- throw (0, _errors.lazyDOMException)('JWK "key_ops" must be an array', 'DataError');
101
- }
102
- for (const usage of keyUsages) {
103
- if (!jwk.key_ops.includes(usage)) {
104
- throw (0, _errors.lazyDOMException)(`JWK "key_ops" does not include requested usage "${usage}"`, 'DataError');
105
- }
106
- }
107
- }
108
- }
109
100
  function getAlgorithmName(name, length) {
110
101
  switch (name) {
111
102
  case 'AES-CBC':
@@ -125,17 +116,55 @@ function getAlgorithmName(name, length) {
125
116
  }
126
117
  }
127
118
 
128
- // Placeholder implementations for missing functions
119
+ // Mirrors Node's aliasKeyFormat (lib/internal/crypto/webcrypto.js): for
120
+ // algorithms whose import/export accepts both 'raw' and the disambiguated
121
+ // 'raw-secret' / 'raw-public', collapse the latter to 'raw'. Used per-algorithm
122
+ // — algorithms that demand the disambiguated form (KMAC, AES-OCB,
123
+ // ChaCha20-Poly1305, Argon2*, ML-DSA, ML-KEM) MUST NOT alias.
124
+ function aliasKeyFormat(format) {
125
+ if (format === 'raw-secret' || format === 'raw-public') return 'raw';
126
+ return format;
127
+ }
128
+ const kUncompressedSpkiLength = {
129
+ 'P-256': 91,
130
+ 'P-384': 120,
131
+ 'P-521': 158
132
+ };
129
133
  function ecExportKey(key, format) {
130
134
  const keyObject = key.keyObject;
131
135
  if (format === KWebCryptoKeyFormat.kWebCryptoKeyFormatRaw) {
132
136
  return (0, _conversion.bufferLikeToArrayBuffer)(keyObject.handle.exportKey());
133
137
  } else if (format === KWebCryptoKeyFormat.kWebCryptoKeyFormatSPKI) {
134
- const exported = keyObject.export({
138
+ const exported = (0, _conversion.bufferLikeToArrayBuffer)(keyObject.export({
135
139
  format: 'der',
136
140
  type: 'spki'
137
- });
138
- return (0, _conversion.bufferLikeToArrayBuffer)(exported);
141
+ }));
142
+
143
+ // WebCrypto requires uncompressed point format for SPKI exports.
144
+ // If the key was imported in compressed form, re-export as uncompressed
145
+ // by reconstructing the point from the JWK x,y coordinates and
146
+ // round-tripping through initECRaw.
147
+ const namedCurve = key.algorithm.namedCurve;
148
+ const expected = namedCurve === undefined ? undefined : kUncompressedSpkiLength[namedCurve];
149
+ if (expected !== undefined && exported.byteLength !== expected) {
150
+ const jwk = keyObject.handle.exportJwk({}, false);
151
+ if (!jwk.x || !jwk.y) {
152
+ throw (0, _errors.lazyDOMException)('Failed to re-export EC public key as uncompressed SPKI', 'OperationError');
153
+ }
154
+ const x = _reactNativeBuffer.Buffer.from(jwk.x, 'base64url');
155
+ const y = _reactNativeBuffer.Buffer.from(jwk.y, 'base64url');
156
+ const raw = new Uint8Array(1 + x.length + y.length);
157
+ raw[0] = 0x04;
158
+ raw.set(x, 1);
159
+ raw.set(y, 1 + x.length);
160
+ const tmp = _reactNativeNitroModules.NitroModules.createHybridObject('KeyObjectHandle');
161
+ const curveAlias = _utils.kNamedCurveAliases[namedCurve];
162
+ if (!tmp.initECRaw(curveAlias, raw.buffer)) {
163
+ throw (0, _errors.lazyDOMException)('Failed to re-export EC public key as uncompressed SPKI', 'OperationError');
164
+ }
165
+ return (0, _conversion.bufferLikeToArrayBuffer)(tmp.exportKey(_utils.KFormatType.DER, _utils.KeyEncoding.SPKI));
166
+ }
167
+ return exported;
139
168
  } else if (format === KWebCryptoKeyFormat.kWebCryptoKeyFormatPKCS8) {
140
169
  const exported = keyObject.export({
141
170
  format: 'der',
@@ -570,10 +599,17 @@ function kmacSignVerify(key, data, algorithm, signature) {
570
599
  const {
571
600
  name
572
601
  } = algorithm;
573
- const defaultLength = name === 'KMAC128' ? 256 : 512;
574
- const outputLengthBits = algorithm.length ?? defaultLength;
602
+
603
+ // KmacParams.outputLength is required per
604
+ // https://wicg.github.io/webcrypto-modern-algos/#KmacParams-dictionary
605
+ // and the rename from `length` (commit ab8dc2b84c2). Mirror Node's
606
+ // mac.js:213-223 by reading `outputLength` (in bits).
607
+ if (typeof algorithm.outputLength !== 'number') {
608
+ throw (0, _errors.lazyDOMException)(`${name}Params.outputLength is required`, 'OperationError');
609
+ }
610
+ const outputLengthBits = algorithm.outputLength;
575
611
  if (outputLengthBits % 8 !== 0) {
576
- throw (0, _errors.lazyDOMException)('KMAC output length must be a multiple of 8', 'OperationError');
612
+ throw (0, _errors.lazyDOMException)(`Unsupported ${name}Params outputLength`, 'NotSupportedError');
577
613
  }
578
614
  const outputLengthBytes = outputLengthBits / 8;
579
615
  const keyData = key.keyObject.export();
@@ -598,30 +634,43 @@ async function kmacImportKey(algorithm, format, data, extractable, keyUsages) {
598
634
  const {
599
635
  name
600
636
  } = algorithm;
601
- if (hasAnyNotIn(keyUsages, ['sign', 'verify'])) {
602
- throw (0, _errors.lazyDOMException)(`Unsupported key usage for ${name} key`, 'SyntaxError');
603
- }
604
637
  let keyObject;
605
638
  if (format === 'jwk') {
606
639
  const jwk = data;
607
640
  if (!jwk || typeof jwk !== 'object') {
608
641
  throw (0, _errors.lazyDOMException)('Invalid keyData', 'DataError');
609
642
  }
610
- validateJwkExtAndKeyOps(jwk, extractable, keyUsages);
611
643
  if (jwk.kty !== 'oct') {
612
- throw (0, _errors.lazyDOMException)('Invalid JWK format for KMAC key', 'DataError');
644
+ throw (0, _errors.lazyDOMException)('Invalid JWK "kty" Parameter', 'DataError');
613
645
  }
646
+ (0, _validation.validateJwkStructure)(jwk, extractable, keyUsages, 'sig');
614
647
  const expectedAlg = name === 'KMAC128' ? 'K128' : 'K256';
615
648
  if (jwk.alg !== undefined && jwk.alg !== expectedAlg) {
616
- throw (0, _errors.lazyDOMException)('JWK "alg" does not match the requested algorithm', 'DataError');
649
+ throw (0, _errors.lazyDOMException)('JWK "alg" Parameter and algorithm name mismatch', 'DataError');
650
+ }
651
+ if (hasAnyNotIn(keyUsages, ['sign', 'verify'])) {
652
+ throw (0, _errors.lazyDOMException)(`Unsupported key usage for ${name} key`, 'SyntaxError');
617
653
  }
618
654
  const handle = _reactNativeNitroModules.NitroModules.createHybridObject('KeyObjectHandle');
619
- const keyType = handle.initJwk(jwk, undefined);
655
+ let keyType;
656
+ try {
657
+ keyType = handle.initJwk(jwk, undefined);
658
+ } catch (err) {
659
+ throw (0, _errors.lazyDOMException)('Invalid keyData', {
660
+ name: 'DataError',
661
+ cause: err
662
+ });
663
+ }
620
664
  if (keyType === undefined || keyType !== 0) {
621
- throw (0, _errors.lazyDOMException)('Failed to import KMAC JWK', 'DataError');
665
+ throw (0, _errors.lazyDOMException)('Invalid keyData', 'DataError');
622
666
  }
623
667
  keyObject = new _keys.SecretKeyObject(handle);
624
- } else if (format === 'raw' || format === 'raw-secret') {
668
+ } else if (format === 'raw-secret') {
669
+ // KMAC accepts only the disambiguated 'raw-secret' form (Node mac.js:141-145
670
+ // returns undefined for plain 'raw' when not HMAC).
671
+ if (hasAnyNotIn(keyUsages, ['sign', 'verify'])) {
672
+ throw (0, _errors.lazyDOMException)(`Unsupported key usage for ${name} key`, 'SyntaxError');
673
+ }
625
674
  keyObject = (0, _keys.createSecretKey)(data);
626
675
  } else {
627
676
  throw (0, _errors.lazyDOMException)(`Unable to import ${name} key with format ${format}`, 'NotSupportedError');
@@ -644,8 +693,6 @@ function rsaImportKey(format, data, algorithm, extractable, keyUsages) {
644
693
  const {
645
694
  name
646
695
  } = algorithm;
647
-
648
- // Validate usages
649
696
  let checkSet;
650
697
  switch (name) {
651
698
  case 'RSASSA-PKCS1-v1_5':
@@ -658,40 +705,70 @@ function rsaImportKey(format, data, algorithm, extractable, keyUsages) {
658
705
  default:
659
706
  throw new Error(`Unsupported RSA algorithm: ${name}`);
660
707
  }
661
- if (hasAnyNotIn(keyUsages, checkSet)) {
662
- throw new Error(`Unsupported key usage for ${name}`);
663
- }
708
+ const checkUsages = () => {
709
+ if (hasAnyNotIn(keyUsages, checkSet)) {
710
+ throw (0, _errors.lazyDOMException)(`Unsupported key usage for ${name} key`, 'SyntaxError');
711
+ }
712
+ };
664
713
  let keyObject;
665
714
  if (format === 'jwk') {
666
715
  const jwk = data;
667
-
668
- // Validate JWK
716
+ if (!jwk || typeof jwk !== 'object') {
717
+ throw (0, _errors.lazyDOMException)('Invalid keyData', 'DataError');
718
+ }
669
719
  if (jwk.kty !== 'RSA') {
670
- throw new Error('Invalid JWK format for RSA key');
720
+ throw (0, _errors.lazyDOMException)('Invalid JWK "kty" Parameter', 'DataError');
721
+ }
722
+ const expectedUse = name === 'RSA-OAEP' ? 'enc' : 'sig';
723
+ (0, _validation.validateJwkStructure)(jwk, extractable, keyUsages, expectedUse);
724
+ checkUsages();
725
+ if (jwk.alg !== undefined) {
726
+ let jwkContext;
727
+ switch (name) {
728
+ case 'RSASSA-PKCS1-v1_5':
729
+ jwkContext = _hashnames.HashContext.JwkRsa;
730
+ break;
731
+ case 'RSA-PSS':
732
+ jwkContext = _hashnames.HashContext.JwkRsaPss;
733
+ break;
734
+ default:
735
+ jwkContext = _hashnames.HashContext.JwkRsaOaep;
736
+ }
737
+ const expectedAlg = (0, _hashnames.normalizeHashName)(algorithm.hash, jwkContext);
738
+ if (jwk.alg !== expectedAlg) {
739
+ throw (0, _errors.lazyDOMException)('JWK "alg" does not match the requested algorithm', 'DataError');
740
+ }
671
741
  }
672
- validateJwkExtAndKeyOps(jwk, extractable, keyUsages);
673
742
  const handle = _reactNativeNitroModules.NitroModules.createHybridObject('KeyObjectHandle');
674
- const keyType = handle.initJwk(jwk, undefined);
743
+ let keyType;
744
+ try {
745
+ keyType = handle.initJwk(jwk, undefined);
746
+ } catch (err) {
747
+ throw (0, _errors.lazyDOMException)('Invalid keyData', {
748
+ name: 'DataError',
749
+ cause: err
750
+ });
751
+ }
675
752
  if (keyType === undefined) {
676
- throw new Error('Failed to import RSA JWK');
753
+ throw (0, _errors.lazyDOMException)('Invalid keyData', 'DataError');
677
754
  }
678
-
679
- // Create the appropriate KeyObject based on type
680
- if (keyType === 1) {
755
+ if (keyType === _utils.KeyType.PUBLIC) {
681
756
  keyObject = new _keys.PublicKeyObject(handle);
682
- } else if (keyType === 2) {
757
+ } else if (keyType === _utils.KeyType.PRIVATE) {
683
758
  keyObject = new _keys.PrivateKeyObject(handle);
684
759
  } else {
685
- throw new Error('Unexpected key type from RSA JWK import');
760
+ throw (0, _errors.lazyDOMException)('Invalid keyData', 'DataError');
686
761
  }
687
762
  } else if (format === 'spki') {
763
+ checkUsages();
688
764
  const keyData = (0, _conversion.bufferLikeToArrayBuffer)(data);
689
765
  keyObject = _keys.KeyObject.createKeyObject('public', keyData, _utils.KFormatType.DER, _utils.KeyEncoding.SPKI);
690
766
  } else if (format === 'pkcs8') {
767
+ checkUsages();
691
768
  const keyData = (0, _conversion.bufferLikeToArrayBuffer)(data);
692
769
  keyObject = _keys.KeyObject.createKeyObject('private', keyData, _utils.KFormatType.DER, _utils.KeyEncoding.PKCS8);
693
770
  } else {
694
- throw new Error(`Unsupported format for RSA import: ${format}`);
771
+ throw (0, _errors.lazyDOMException)(`Unsupported format for ${name} import: ${format}`, 'NotSupportedError');
695
772
  }
696
773
 
697
774
  // Get the modulus length from the key and add it to the algorithm
@@ -725,29 +802,26 @@ function rsaImportKey(format, data, algorithm, extractable, keyUsages) {
725
802
  return new _keys.CryptoKey(keyObject, algorithmWithDetails, keyUsages, extractable);
726
803
  }
727
804
  async function hmacImportKey(algorithm, format, data, extractable, keyUsages) {
728
- // Validate usages
729
- if (hasAnyNotIn(keyUsages, ['sign', 'verify'])) {
730
- throw new Error('Unsupported key usage for an HMAC key');
731
- }
805
+ const checkUsages = () => {
806
+ if (hasAnyNotIn(keyUsages, ['sign', 'verify'])) {
807
+ throw new Error('Unsupported key usage for an HMAC key');
808
+ }
809
+ };
732
810
  let keyObject;
733
811
  if (format === 'jwk') {
734
812
  const jwk = data;
735
-
736
- // Validate JWK
737
813
  if (!jwk || typeof jwk !== 'object') {
738
814
  throw new Error('Invalid keyData');
739
815
  }
740
- validateJwkExtAndKeyOps(jwk, extractable, keyUsages);
741
816
  if (jwk.kty !== 'oct') {
742
817
  throw new Error('Invalid JWK format for HMAC key');
743
818
  }
744
-
745
- // Validate key length if specified
819
+ (0, _validation.validateJwkStructure)(jwk, extractable, keyUsages, 'sig');
820
+ checkUsages();
746
821
  if (algorithm.length !== undefined) {
747
822
  if (!jwk.k) {
748
823
  throw new Error('JWK missing key data');
749
824
  }
750
- // Decode to check length
751
825
  const decoded = _safeBuffer.Buffer.from(jwk.k, 'base64');
752
826
  const keyBitLength = decoded.length * 8;
753
827
  if (algorithm.length === 0) {
@@ -758,15 +832,25 @@ async function hmacImportKey(algorithm, format, data, extractable, keyUsages) {
758
832
  }
759
833
  }
760
834
  const handle = _reactNativeNitroModules.NitroModules.createHybridObject('KeyObjectHandle');
761
- const keyType = handle.initJwk(jwk, undefined);
835
+ let keyType;
836
+ try {
837
+ keyType = handle.initJwk(jwk, undefined);
838
+ } catch (err) {
839
+ throw (0, _errors.lazyDOMException)('Invalid keyData', {
840
+ name: 'DataError',
841
+ cause: err
842
+ });
843
+ }
762
844
  if (keyType === undefined || keyType !== 0) {
763
- throw new Error('Failed to import HMAC JWK');
845
+ throw (0, _errors.lazyDOMException)('Invalid keyData', 'DataError');
764
846
  }
765
847
  keyObject = new _keys.SecretKeyObject(handle);
766
- } else if (format === 'raw') {
848
+ } else if (format === 'raw' || format === 'raw-secret') {
849
+ // HMAC accepts both 'raw' and 'raw-secret' (Node mac.js:141-145).
850
+ checkUsages();
767
851
  keyObject = (0, _keys.createSecretKey)(data);
768
852
  } else {
769
- throw new Error(`Unable to import HMAC key with format ${format}`);
853
+ throw (0, _errors.lazyDOMException)(`Unable to import HMAC key with format ${format}`, 'NotSupportedError');
770
854
  }
771
855
 
772
856
  // Normalize hash to { name: string } format per WebCrypto spec
@@ -785,43 +869,57 @@ async function aesImportKey(algorithm, format, data, extractable, keyUsages) {
785
869
  name,
786
870
  length
787
871
  } = algorithm;
788
-
789
- // Validate usages
790
872
  const validUsages = ['encrypt', 'decrypt', 'wrapKey', 'unwrapKey'];
791
- if (hasAnyNotIn(keyUsages, validUsages)) {
792
- throw new Error(`Unsupported key usage for ${name}`);
793
- }
873
+ const checkUsages = () => {
874
+ if (hasAnyNotIn(keyUsages, validUsages)) {
875
+ throw new Error(`Unsupported key usage for ${name}`);
876
+ }
877
+ };
878
+
879
+ // AES-OCB and ChaCha20-Poly1305 require the disambiguated 'raw-secret' form
880
+ // and reject 'raw' (Node aes.js:243-249, chacha20_poly1305.js:104-134).
881
+ // Other AES variants accept both 'raw' and 'raw-secret'.
882
+ const requiresRawSecret = name === 'AES-OCB' || name === 'ChaCha20-Poly1305';
883
+ const acceptsRaw = format === 'raw-secret' || format === 'raw' && !requiresRawSecret;
794
884
  let keyObject;
795
885
  let actualLength;
796
886
  if (format === 'jwk') {
797
887
  const jwk = data;
798
-
799
- // Validate JWK
800
888
  if (jwk.kty !== 'oct') {
801
889
  throw new Error('Invalid JWK format for AES key');
802
890
  }
803
- validateJwkExtAndKeyOps(jwk, extractable, keyUsages);
891
+ (0, _validation.validateJwkStructure)(jwk, extractable, keyUsages, 'enc');
892
+ checkUsages();
804
893
  const handle = _reactNativeNitroModules.NitroModules.createHybridObject('KeyObjectHandle');
805
- const keyType = handle.initJwk(jwk, undefined);
894
+ let keyType;
895
+ try {
896
+ keyType = handle.initJwk(jwk, undefined);
897
+ } catch (err) {
898
+ throw (0, _errors.lazyDOMException)('Invalid keyData', {
899
+ name: 'DataError',
900
+ cause: err
901
+ });
902
+ }
806
903
  if (keyType === undefined || keyType !== 0) {
807
- throw new Error('Failed to import AES JWK');
904
+ throw (0, _errors.lazyDOMException)('Invalid keyData', 'DataError');
808
905
  }
809
906
  keyObject = new _keys.SecretKeyObject(handle);
810
-
811
- // Get actual key length from imported key
812
907
  const exported = keyObject.export();
813
908
  actualLength = exported.byteLength * 8;
814
- } else if (format === 'raw') {
909
+ } else if (acceptsRaw) {
910
+ checkUsages();
815
911
  const keyData = (0, _conversion.bufferLikeToArrayBuffer)(data);
816
912
  actualLength = keyData.byteLength * 8;
817
-
818
- // Validate key length
819
- if (![128, 192, 256].includes(actualLength)) {
913
+ if (name === 'ChaCha20-Poly1305') {
914
+ if (actualLength !== 256) {
915
+ throw (0, _errors.lazyDOMException)('Invalid ChaCha20-Poly1305 key length', 'DataError');
916
+ }
917
+ } else if (![128, 192, 256].includes(actualLength)) {
820
918
  throw new Error('Invalid AES key length');
821
919
  }
822
920
  keyObject = (0, _keys.createSecretKey)(keyData);
823
921
  } else {
824
- throw new Error(`Unsupported format for AES import: ${format}`);
922
+ throw (0, _errors.lazyDOMException)(`Unable to import ${name} key with format ${format}`, 'NotSupportedError');
825
923
  }
826
924
 
827
925
  // Validate length if specified
@@ -837,35 +935,57 @@ function edImportKey(format, data, algorithm, extractable, keyUsages) {
837
935
  const {
838
936
  name
839
937
  } = algorithm;
840
-
841
- // Validate usages
842
938
  const isX = name === 'X25519' || name === 'X448';
843
939
  const allowedUsages = isX ? ['deriveKey', 'deriveBits'] : ['sign', 'verify'];
844
- if (hasAnyNotIn(keyUsages, allowedUsages)) {
845
- throw (0, _errors.lazyDOMException)(`Unsupported key usage for ${name} key`, 'SyntaxError');
846
- }
940
+ const checkUsages = () => {
941
+ if (hasAnyNotIn(keyUsages, allowedUsages)) {
942
+ throw (0, _errors.lazyDOMException)(`Unsupported key usage for ${name} key`, 'SyntaxError');
943
+ }
944
+ };
847
945
  let keyObject;
848
946
  if (format === 'spki') {
849
- // Import public key
947
+ checkUsages();
850
948
  const keyData = (0, _conversion.bufferLikeToArrayBuffer)(data);
851
949
  keyObject = _keys.KeyObject.createKeyObject('public', keyData, _utils.KFormatType.DER, _utils.KeyEncoding.SPKI);
852
950
  } else if (format === 'pkcs8') {
853
- // Import private key
951
+ checkUsages();
854
952
  const keyData = (0, _conversion.bufferLikeToArrayBuffer)(data);
855
953
  keyObject = _keys.KeyObject.createKeyObject('private', keyData, _utils.KFormatType.DER, _utils.KeyEncoding.PKCS8);
856
954
  } else if (format === 'raw') {
857
- // Raw format - public key only for Ed keys
955
+ checkUsages();
858
956
  const keyData = (0, _conversion.bufferLikeToArrayBuffer)(data);
859
957
  const handle = _reactNativeNitroModules.NitroModules.createHybridObject('KeyObjectHandle');
860
- // For raw Ed keys, we need to create them differently
861
- // Raw public keys are just the key bytes
862
- handle.init(1, keyData); // 1 = public key type
958
+ handle.init(1, keyData);
863
959
  keyObject = new _keys.PublicKeyObject(handle);
864
960
  } else if (format === 'jwk') {
865
961
  const jwkData = data;
866
- validateJwkExtAndKeyOps(jwkData, extractable, keyUsages);
962
+ if (!jwkData || typeof jwkData !== 'object') {
963
+ throw (0, _errors.lazyDOMException)('Invalid keyData', 'DataError');
964
+ }
965
+ if (jwkData.kty !== 'OKP') {
966
+ throw (0, _errors.lazyDOMException)('Invalid JWK "kty" Parameter', 'DataError');
967
+ }
968
+ const expectedUse = isX ? 'enc' : 'sig';
969
+ (0, _validation.validateJwkStructure)(jwkData, extractable, keyUsages, expectedUse);
970
+ if (jwkData.crv !== name) {
971
+ throw (0, _errors.lazyDOMException)('JWK "crv" Parameter and algorithm name mismatch', 'DataError');
972
+ }
973
+ if (!isX && jwkData.alg !== undefined) {
974
+ if (jwkData.alg !== name && jwkData.alg !== 'EdDSA') {
975
+ throw (0, _errors.lazyDOMException)('JWK "alg" does not match the requested algorithm', 'DataError');
976
+ }
977
+ }
978
+ checkUsages();
867
979
  const handle = _reactNativeNitroModules.NitroModules.createHybridObject('KeyObjectHandle');
868
- const keyType = handle.initJwk(jwkData);
980
+ let keyType;
981
+ try {
982
+ keyType = handle.initJwk(jwkData);
983
+ } catch (err) {
984
+ throw (0, _errors.lazyDOMException)('Invalid JWK data', {
985
+ name: 'DataError',
986
+ cause: err
987
+ });
988
+ }
869
989
  if (keyType === undefined) {
870
990
  throw (0, _errors.lazyDOMException)('Invalid JWK data', 'DataError');
871
991
  }
@@ -881,48 +1001,171 @@ function edImportKey(format, data, algorithm, extractable, keyUsages) {
881
1001
  name
882
1002
  }, keyUsages, extractable);
883
1003
  }
1004
+
1005
+ // Lengths (in bytes) of seedless ML-DSA / ML-KEM PKCS#8 encodings. A PKCS#8
1006
+ // blob of exactly this length contains only the expanded private key with no
1007
+ // seed; Node rejects these to keep cross-implementation interop intact.
1008
+ // Refs: node lib/internal/crypto/ml_dsa.js (mlDsaImportKey, pkcs8 case)
1009
+ // node lib/internal/crypto/ml_kem.js (mlKemImportKey, pkcs8 case)
1010
+ const PQC_SEEDLESS_PKCS8_LENGTHS = exports.PQC_SEEDLESS_PKCS8_LENGTHS = {
1011
+ 'ML-DSA-44': 2588,
1012
+ 'ML-DSA-65': 4060,
1013
+ 'ML-DSA-87': 4924,
1014
+ 'ML-KEM-512': 1660,
1015
+ 'ML-KEM-768': 2428,
1016
+ 'ML-KEM-1024': 3196
1017
+ };
1018
+
1019
+ // Map from PQC algorithm name to display family. Used to render the
1020
+ // import-rejection error message in the same form Node emits.
1021
+ const PQC_FAMILY = {
1022
+ 'ML-DSA-44': 'ML-DSA',
1023
+ 'ML-DSA-65': 'ML-DSA',
1024
+ 'ML-DSA-87': 'ML-DSA',
1025
+ 'ML-KEM-512': 'ML-KEM',
1026
+ 'ML-KEM-768': 'ML-KEM',
1027
+ 'ML-KEM-1024': 'ML-KEM',
1028
+ 'SLH-DSA-SHA2-128s': 'SLH-DSA',
1029
+ 'SLH-DSA-SHA2-128f': 'SLH-DSA',
1030
+ 'SLH-DSA-SHA2-192s': 'SLH-DSA',
1031
+ 'SLH-DSA-SHA2-192f': 'SLH-DSA',
1032
+ 'SLH-DSA-SHA2-256s': 'SLH-DSA',
1033
+ 'SLH-DSA-SHA2-256f': 'SLH-DSA',
1034
+ 'SLH-DSA-SHAKE-128s': 'SLH-DSA',
1035
+ 'SLH-DSA-SHAKE-128f': 'SLH-DSA',
1036
+ 'SLH-DSA-SHAKE-192s': 'SLH-DSA',
1037
+ 'SLH-DSA-SHAKE-192f': 'SLH-DSA',
1038
+ 'SLH-DSA-SHAKE-256s': 'SLH-DSA',
1039
+ 'SLH-DSA-SHAKE-256f': 'SLH-DSA'
1040
+ };
884
1041
  function pqcImportKeyObject(format, data, name) {
885
1042
  if (format === 'spki') {
886
- return _keys.KeyObject.createKeyObject('public', (0, _conversion.bufferLikeToArrayBuffer)(data), _utils.KFormatType.DER, _utils.KeyEncoding.SPKI);
1043
+ return {
1044
+ keyObject: _keys.KeyObject.createKeyObject('public', (0, _conversion.bufferLikeToArrayBuffer)(data), _utils.KFormatType.DER, _utils.KeyEncoding.SPKI),
1045
+ isPublic: true
1046
+ };
887
1047
  } else if (format === 'pkcs8') {
888
- return _keys.KeyObject.createKeyObject('private', (0, _conversion.bufferLikeToArrayBuffer)(data), _utils.KFormatType.DER, _utils.KeyEncoding.PKCS8);
889
- } else if (format === 'raw') {
1048
+ const ab = (0, _conversion.bufferLikeToArrayBuffer)(data);
1049
+ const family = PQC_FAMILY[name];
1050
+ if (family !== undefined && ab.byteLength === PQC_SEEDLESS_PKCS8_LENGTHS[name]) {
1051
+ throw (0, _errors.lazyDOMException)(`Importing an ${family} PKCS#8 key without a seed is not supported`, 'NotSupportedError');
1052
+ }
1053
+ return {
1054
+ keyObject: _keys.KeyObject.createKeyObject('private', ab, _utils.KFormatType.DER, _utils.KeyEncoding.PKCS8),
1055
+ isPublic: false
1056
+ };
1057
+ } else if (format === 'raw-public') {
1058
+ // ML-DSA / ML-KEM reject plain 'raw' — only 'raw-public' is accepted for
1059
+ // public-key import (Node webcrypto.js:493-499, 506-511).
890
1060
  const handle = _reactNativeNitroModules.NitroModules.createHybridObject('KeyObjectHandle');
891
1061
  if (!handle.initPqcRaw(name, (0, _conversion.bufferLikeToArrayBuffer)(data), true)) {
892
1062
  throw (0, _errors.lazyDOMException)(`Failed to import ${name} raw public key`, 'DataError');
893
1063
  }
894
- return new _keys.PublicKeyObject(handle);
1064
+ return {
1065
+ keyObject: new _keys.PublicKeyObject(handle),
1066
+ isPublic: true
1067
+ };
895
1068
  } else if (format === 'raw-seed') {
896
1069
  const handle = _reactNativeNitroModules.NitroModules.createHybridObject('KeyObjectHandle');
897
1070
  if (!handle.initPqcRaw(name, (0, _conversion.bufferLikeToArrayBuffer)(data), false)) {
898
1071
  throw (0, _errors.lazyDOMException)(`Failed to import ${name} raw seed`, 'DataError');
899
1072
  }
900
- return new _keys.PrivateKeyObject(handle);
1073
+ return {
1074
+ keyObject: new _keys.PrivateKeyObject(handle),
1075
+ isPublic: false
1076
+ };
1077
+ } else if (format === 'jwk') {
1078
+ const jwkData = data;
1079
+ const isPublic = jwkData.priv === undefined;
1080
+ const handle = _reactNativeNitroModules.NitroModules.createHybridObject('KeyObjectHandle');
1081
+ let keyType;
1082
+ try {
1083
+ keyType = handle.initJwk(jwkData);
1084
+ } catch (err) {
1085
+ throw (0, _errors.lazyDOMException)('Invalid JWK data', {
1086
+ name: 'DataError',
1087
+ cause: err
1088
+ });
1089
+ }
1090
+ if (keyType === undefined) {
1091
+ throw (0, _errors.lazyDOMException)('Invalid JWK data', 'DataError');
1092
+ }
1093
+ return {
1094
+ keyObject: isPublic ? new _keys.PublicKeyObject(handle) : new _keys.PrivateKeyObject(handle),
1095
+ isPublic
1096
+ };
901
1097
  }
902
1098
  throw (0, _errors.lazyDOMException)(`Unsupported format for ${name} import: ${format}`, 'NotSupportedError');
903
1099
  }
1100
+
1101
+ // Per WebCrypto AKP JWK rules, public-vs-private is determined by the presence
1102
+ // of `priv`. For binary formats it follows from the format itself.
1103
+ function pqcIsPublicImport(format, data) {
1104
+ if (format === 'jwk') {
1105
+ return typeof data === 'object' && data !== null && data.priv === undefined;
1106
+ }
1107
+ return format === 'spki' || format === 'raw-public';
1108
+ }
1109
+ function validatePqcJwk(data, name, extractable, keyUsages, expectedUse) {
1110
+ if (typeof data !== 'object' || data === null) {
1111
+ throw (0, _errors.lazyDOMException)('Invalid keyData', 'DataError');
1112
+ }
1113
+ const jwk = data;
1114
+ if (jwk.kty !== 'AKP') {
1115
+ throw (0, _errors.lazyDOMException)('Invalid JWK "kty" Parameter', 'DataError');
1116
+ }
1117
+ (0, _validation.validateJwkStructure)(jwk, extractable, keyUsages, expectedUse);
1118
+ if (jwk.alg !== name) {
1119
+ throw (0, _errors.lazyDOMException)('JWK "alg" Parameter and algorithm name mismatch', 'DataError');
1120
+ }
1121
+ }
1122
+
1123
+ // Validates that `format` is one of the formats PQC algorithms accept; rejects
1124
+ // plain 'raw' early so the format error wins over usage-based errors.
1125
+ function validatePqcFormat(format, name) {
1126
+ if (format !== 'spki' && format !== 'pkcs8' && format !== 'raw-public' && format !== 'raw-seed' && format !== 'jwk') {
1127
+ throw (0, _errors.lazyDOMException)(`Unsupported format for ${name} import: ${format}`, 'NotSupportedError');
1128
+ }
1129
+ }
904
1130
  function mldsaImportKey(format, data, algorithm, extractable, keyUsages) {
905
1131
  const {
906
1132
  name
907
1133
  } = algorithm;
908
- const isPublicFormat = format === 'spki' || format === 'raw';
909
- if (hasAnyNotIn(keyUsages, isPublicFormat ? ['verify'] : ['sign'])) {
1134
+ validatePqcFormat(format, name);
1135
+ if (format === 'jwk') {
1136
+ validatePqcJwk(data, name, extractable, keyUsages, 'sig');
1137
+ }
1138
+ const isPublic = pqcIsPublicImport(format, data);
1139
+ if (hasAnyNotIn(keyUsages, isPublic ? ['verify'] : ['sign'])) {
910
1140
  throw (0, _errors.lazyDOMException)(`Unsupported key usage for ${name} key`, 'SyntaxError');
911
1141
  }
912
- return new _keys.CryptoKey(pqcImportKeyObject(format, data, name), {
1142
+ const {
1143
+ keyObject
1144
+ } = pqcImportKeyObject(format, data, name);
1145
+ return new _keys.CryptoKey(keyObject, {
913
1146
  name
914
1147
  }, keyUsages, extractable);
915
1148
  }
1149
+ function slhdsaImportKey(format, data, algorithm, extractable, keyUsages) {
1150
+ return mldsaImportKey(format, data, algorithm, extractable, keyUsages);
1151
+ }
916
1152
  function mlkemImportKey(format, data, algorithm, extractable, keyUsages) {
917
1153
  const {
918
1154
  name
919
1155
  } = algorithm;
920
- const isPublicFormat = format === 'spki' || format === 'raw';
921
- const allowedUsages = isPublicFormat ? ['encapsulateBits', 'encapsulateKey'] : ['decapsulateBits', 'decapsulateKey'];
1156
+ validatePqcFormat(format, name);
1157
+ if (format === 'jwk') {
1158
+ validatePqcJwk(data, name, extractable, keyUsages, 'enc');
1159
+ }
1160
+ const isPublic = pqcIsPublicImport(format, data);
1161
+ const allowedUsages = isPublic ? ['encapsulateBits', 'encapsulateKey'] : ['decapsulateBits', 'decapsulateKey'];
922
1162
  if (hasAnyNotIn(keyUsages, allowedUsages)) {
923
1163
  throw (0, _errors.lazyDOMException)(`Unsupported key usage for ${name} key`, 'SyntaxError');
924
1164
  }
925
- return new _keys.CryptoKey(pqcImportKeyObject(format, data, name), {
1165
+ const {
1166
+ keyObject
1167
+ } = pqcImportKeyObject(format, data, name);
1168
+ return new _keys.CryptoKey(keyObject, {
926
1169
  name
927
1170
  }, keyUsages, extractable);
928
1171
  }
@@ -961,8 +1204,21 @@ const exportKeySpki = async key => {
961
1204
  case 'ML-DSA-65':
962
1205
  // Fall through
963
1206
  case 'ML-DSA-87':
1207
+ // Fall through
1208
+ case 'SLH-DSA-SHA2-128s':
1209
+ case 'SLH-DSA-SHA2-128f':
1210
+ case 'SLH-DSA-SHA2-192s':
1211
+ case 'SLH-DSA-SHA2-192f':
1212
+ case 'SLH-DSA-SHA2-256s':
1213
+ case 'SLH-DSA-SHA2-256f':
1214
+ case 'SLH-DSA-SHAKE-128s':
1215
+ case 'SLH-DSA-SHAKE-128f':
1216
+ case 'SLH-DSA-SHAKE-192s':
1217
+ case 'SLH-DSA-SHAKE-192f':
1218
+ case 'SLH-DSA-SHAKE-256s':
1219
+ case 'SLH-DSA-SHAKE-256f':
964
1220
  if (key.type === 'public') {
965
- // Export ML-DSA key in SPKI DER format
1221
+ // Export ML-DSA / SLH-DSA key in SPKI DER format
966
1222
  return (0, _conversion.bufferLikeToArrayBuffer)(key.keyObject.handle.exportKey(_utils.KFormatType.DER, _utils.KeyEncoding.SPKI));
967
1223
  }
968
1224
  break;
@@ -1013,16 +1269,35 @@ const exportKeyPkcs8 = async key => {
1013
1269
  case 'ML-DSA-65':
1014
1270
  // Fall through
1015
1271
  case 'ML-DSA-87':
1016
- if (key.type === 'private') {
1017
- // Export ML-DSA key in PKCS8 DER format
1018
- return (0, _conversion.bufferLikeToArrayBuffer)(key.keyObject.handle.exportKey(_utils.KFormatType.DER, _utils.KeyEncoding.PKCS8));
1019
- }
1020
- break;
1272
+ // Fall through
1021
1273
  case 'ML-KEM-512':
1022
1274
  // Fall through
1023
1275
  case 'ML-KEM-768':
1024
1276
  // Fall through
1025
1277
  case 'ML-KEM-1024':
1278
+ if (key.type === 'private') {
1279
+ const ab = (0, _conversion.bufferLikeToArrayBuffer)(key.keyObject.handle.exportKey(_utils.KFormatType.DER, _utils.KeyEncoding.PKCS8));
1280
+ // 22 bytes of PKCS#8 ASN.1 + seed (32 ML-DSA, 64 ML-KEM). Guards
1281
+ // against a seedless KeyObject that was wrapped via toCryptoKey.
1282
+ const expected = key.algorithm.name.startsWith('ML-DSA') ? 54 : 86;
1283
+ if (ab.byteLength !== expected) {
1284
+ throw (0, _errors.lazyDOMException)('The operation failed for an operation-specific reason', 'OperationError');
1285
+ }
1286
+ return ab;
1287
+ }
1288
+ break;
1289
+ case 'SLH-DSA-SHA2-128s':
1290
+ case 'SLH-DSA-SHA2-128f':
1291
+ case 'SLH-DSA-SHA2-192s':
1292
+ case 'SLH-DSA-SHA2-192f':
1293
+ case 'SLH-DSA-SHA2-256s':
1294
+ case 'SLH-DSA-SHA2-256f':
1295
+ case 'SLH-DSA-SHAKE-128s':
1296
+ case 'SLH-DSA-SHAKE-128f':
1297
+ case 'SLH-DSA-SHAKE-192s':
1298
+ case 'SLH-DSA-SHAKE-192f':
1299
+ case 'SLH-DSA-SHAKE-256s':
1300
+ case 'SLH-DSA-SHAKE-256f':
1026
1301
  if (key.type === 'private') {
1027
1302
  return (0, _conversion.bufferLikeToArrayBuffer)(key.keyObject.handle.exportKey(_utils.KFormatType.DER, _utils.KeyEncoding.PKCS8));
1028
1303
  }
@@ -1030,71 +1305,87 @@ const exportKeyPkcs8 = async key => {
1030
1305
  }
1031
1306
  throw new Error(`Unable to export a pkcs8 ${key.algorithm.name} ${key.type} key`);
1032
1307
  };
1033
- const exportKeyRaw = key => {
1034
- switch (key.algorithm.name) {
1308
+
1309
+ // Mirrors Node's export key matrix (lib/internal/crypto/webcrypto.js
1310
+ // exportKeyRawSecret / exportKeyRawPublic, lines 472-563):
1311
+ //
1312
+ // raw — AES-CTR/CBC/GCM/KW + HMAC (secret); ECDSA/ECDH/Ed/X (public)
1313
+ // raw-secret — AES-CTR/CBC/GCM/KW + HMAC + AES-OCB + KMAC + ChaCha20-Poly1305
1314
+ // raw-public — ECDSA/ECDH + Ed/X + ML-DSA + ML-KEM (public)
1315
+ const exportKeyRaw = (key, format) => {
1316
+ const name = key.algorithm.name;
1317
+ const isPublic = key.type === 'public';
1318
+ const isSecret = key.type === 'secret';
1319
+ const exportSecret = () => {
1320
+ const exported = key.keyObject.export();
1321
+ return exported.buffer.slice(exported.byteOffset, exported.byteOffset + exported.byteLength);
1322
+ };
1323
+ const exportRawPublic = () => (0, _conversion.bufferLikeToArrayBuffer)(key.keyObject.handle.exportKey());
1324
+ const fail = () => {
1325
+ throw (0, _errors.lazyDOMException)(`Unable to export ${name} ${key.type} key using ${format} format`, 'NotSupportedError');
1326
+ };
1327
+
1328
+ // Symmetric: AES-CTR/CBC/GCM/KW and HMAC accept both 'raw' and 'raw-secret';
1329
+ // AES-OCB / KMAC* / ChaCha20-Poly1305 only 'raw-secret'.
1330
+ switch (name) {
1331
+ case 'AES-CTR':
1332
+ case 'AES-CBC':
1333
+ case 'AES-GCM':
1334
+ case 'AES-KW':
1335
+ case 'HMAC':
1336
+ if (!isSecret) return fail();
1337
+ if (format === 'raw' || format === 'raw-secret') return exportSecret();
1338
+ return fail();
1339
+ case 'AES-OCB':
1340
+ case 'KMAC128':
1341
+ case 'KMAC256':
1342
+ case 'ChaCha20-Poly1305':
1343
+ if (!isSecret) return fail();
1344
+ if (format === 'raw-secret') return exportSecret();
1345
+ return fail();
1035
1346
  case 'ECDSA':
1036
- // Fall through
1037
1347
  case 'ECDH':
1038
- if (key.type === 'public') {
1348
+ if (!isPublic) return fail();
1349
+ if (format === 'raw' || format === 'raw-public') {
1039
1350
  return ecExportKey(key, KWebCryptoKeyFormat.kWebCryptoKeyFormatRaw);
1040
1351
  }
1041
- break;
1352
+ return fail();
1042
1353
  case 'Ed25519':
1043
- // Fall through
1044
1354
  case 'Ed448':
1045
- // Fall through
1046
1355
  case 'X25519':
1047
- // Fall through
1048
1356
  case 'X448':
1049
- if (key.type === 'public') {
1050
- const exported = key.keyObject.handle.exportKey();
1051
- return (0, _conversion.bufferLikeToArrayBuffer)(exported);
1052
- }
1053
- break;
1054
- case 'ML-KEM-512':
1055
- // Fall through
1056
- case 'ML-KEM-768':
1057
- // Fall through
1058
- case 'ML-KEM-1024':
1059
- // Fall through
1357
+ if (!isPublic) return fail();
1358
+ if (format === 'raw' || format === 'raw-public') return exportRawPublic();
1359
+ return fail();
1060
1360
  case 'ML-DSA-44':
1061
- // Fall through
1062
1361
  case 'ML-DSA-65':
1063
- // Fall through
1064
1362
  case 'ML-DSA-87':
1065
- if (key.type === 'public') {
1066
- const exported = key.keyObject.handle.exportKey();
1067
- return (0, _conversion.bufferLikeToArrayBuffer)(exported);
1068
- }
1069
- break;
1070
- case 'AES-CTR':
1071
- // Fall through
1072
- case 'AES-CBC':
1073
- // Fall through
1074
- case 'AES-GCM':
1075
- // Fall through
1076
- case 'AES-KW':
1077
- // Fall through
1078
- case 'AES-OCB':
1079
- // Fall through
1080
- case 'ChaCha20-Poly1305':
1081
- // Fall through
1082
- case 'HMAC':
1083
- // Fall through
1084
- case 'KMAC128':
1085
- // Fall through
1086
- case 'KMAC256':
1087
- {
1088
- const exported = key.keyObject.export();
1089
- // Convert Buffer to ArrayBuffer
1090
- return exported.buffer.slice(exported.byteOffset, exported.byteOffset + exported.byteLength);
1091
- }
1092
- }
1093
- throw (0, _errors.lazyDOMException)(`Unable to export a raw ${key.algorithm.name} ${key.type} key`, 'InvalidAccessError');
1363
+ case 'ML-KEM-512':
1364
+ case 'ML-KEM-768':
1365
+ case 'ML-KEM-1024':
1366
+ case 'SLH-DSA-SHA2-128s':
1367
+ case 'SLH-DSA-SHA2-128f':
1368
+ case 'SLH-DSA-SHA2-192s':
1369
+ case 'SLH-DSA-SHA2-192f':
1370
+ case 'SLH-DSA-SHA2-256s':
1371
+ case 'SLH-DSA-SHA2-256f':
1372
+ case 'SLH-DSA-SHAKE-128s':
1373
+ case 'SLH-DSA-SHAKE-128f':
1374
+ case 'SLH-DSA-SHAKE-192s':
1375
+ case 'SLH-DSA-SHAKE-192f':
1376
+ case 'SLH-DSA-SHAKE-256s':
1377
+ case 'SLH-DSA-SHAKE-256f':
1378
+ // ML-DSA / ML-KEM / SLH-DSA keys do not recognize plain 'raw' (Node
1379
+ // webcrypto.js lines 488-510).
1380
+ if (!isPublic) return fail();
1381
+ if (format === 'raw-public') return exportRawPublic();
1382
+ return fail();
1383
+ }
1384
+ return fail();
1094
1385
  };
1095
1386
  const exportKeyJWK = key => {
1096
1387
  const jwk = key.keyObject.handle.exportJwk({
1097
- key_ops: key.usages,
1388
+ key_ops: [...key.usages],
1098
1389
  ext: key.extractable
1099
1390
  }, true);
1100
1391
  switch (key.algorithm.name) {
@@ -1129,6 +1420,31 @@ const exportKeyJWK = key => {
1129
1420
  // Fall through
1130
1421
  case 'X448':
1131
1422
  return jwk;
1423
+ case 'ML-DSA-44':
1424
+ // Fall through
1425
+ case 'ML-DSA-65':
1426
+ // Fall through
1427
+ case 'ML-DSA-87':
1428
+ // Fall through
1429
+ case 'ML-KEM-512':
1430
+ // Fall through
1431
+ case 'ML-KEM-768':
1432
+ // Fall through
1433
+ case 'ML-KEM-1024':
1434
+ // Fall through
1435
+ case 'SLH-DSA-SHA2-128s':
1436
+ case 'SLH-DSA-SHA2-128f':
1437
+ case 'SLH-DSA-SHA2-192s':
1438
+ case 'SLH-DSA-SHA2-192f':
1439
+ case 'SLH-DSA-SHA2-256s':
1440
+ case 'SLH-DSA-SHA2-256f':
1441
+ case 'SLH-DSA-SHAKE-128s':
1442
+ case 'SLH-DSA-SHAKE-128f':
1443
+ case 'SLH-DSA-SHAKE-192s':
1444
+ case 'SLH-DSA-SHAKE-192f':
1445
+ case 'SLH-DSA-SHAKE-256s':
1446
+ case 'SLH-DSA-SHAKE-256f':
1447
+ return jwk;
1132
1448
  case 'AES-CTR':
1133
1449
  // Fall through
1134
1450
  case 'AES-CBC':
@@ -1150,33 +1466,59 @@ const exportKeyJWK = key => {
1150
1466
  }
1151
1467
  throw (0, _errors.lazyDOMException)(`JWK export not yet supported: ${key.algorithm.name}`, 'NotSupportedError');
1152
1468
  };
1153
- const importGenericSecretKey = async ({
1469
+
1470
+ // PBKDF2 import. Mirrors Node's importGenericSecretKey ordering
1471
+ // (keys.js:945-971): extractable → usage → format → length. Callers pre-alias
1472
+ // 'raw-secret' / 'raw-public' to 'raw' via aliasKeyFormat
1473
+ // (webcrypto.js:798-808).
1474
+ const pbkdf2ImportKey = async ({
1154
1475
  name,
1155
1476
  length
1156
1477
  }, format, keyData, extractable, keyUsages) => {
1157
1478
  if (extractable) {
1158
- throw new Error(`${name} keys are not extractable`);
1479
+ throw (0, _errors.lazyDOMException)(`${name} keys are not extractable`, 'SyntaxError');
1159
1480
  }
1160
1481
  if (hasAnyNotIn(keyUsages, ['deriveKey', 'deriveBits'])) {
1161
- throw new Error(`Unsupported key usage for a ${name} key`);
1482
+ throw (0, _errors.lazyDOMException)(`Unsupported key usage for a ${name} key`, 'SyntaxError');
1162
1483
  }
1163
- switch (format) {
1164
- case 'raw':
1165
- {
1166
- if (hasAnyNotIn(keyUsages, ['deriveKey', 'deriveBits'])) {
1167
- throw new Error(`Unsupported key usage for a ${name} key`);
1168
- }
1169
- const checkLength = typeof keyData === 'string' || _safeBuffer.Buffer.isBuffer(keyData) ? keyData.length * 8 : keyData.byteLength * 8;
1170
- if (length !== undefined && length !== checkLength) {
1171
- throw new Error('Invalid key length');
1172
- }
1173
- const keyObject = (0, _keys.createSecretKey)(keyData);
1174
- return new _keys.CryptoKey(keyObject, {
1175
- name
1176
- }, keyUsages, false);
1177
- }
1484
+ if (format !== 'raw') {
1485
+ throw (0, _errors.lazyDOMException)(`Unable to import ${name} key with format ${format}`, 'NotSupportedError');
1486
+ }
1487
+ const checkLength = typeof keyData === 'string' || _safeBuffer.Buffer.isBuffer(keyData) ? keyData.length * 8 : keyData.byteLength * 8;
1488
+ if (length !== undefined && length !== checkLength) {
1489
+ throw (0, _errors.lazyDOMException)('Invalid key length', 'DataError');
1490
+ }
1491
+ const keyObject = (0, _keys.createSecretKey)(keyData);
1492
+ return new _keys.CryptoKey(keyObject, {
1493
+ name
1494
+ }, keyUsages, false);
1495
+ };
1496
+
1497
+ // Argon2 import. Node gates the format at the dispatcher level — only
1498
+ // 'raw-secret' enters importGenericSecretKey (webcrypto.js:813-822). To match
1499
+ // that, format is the first check here; remaining ordering matches Node's
1500
+ // importGenericSecretKey.
1501
+ const argon2ImportKey = async ({
1502
+ name,
1503
+ length
1504
+ }, format, keyData, extractable, keyUsages) => {
1505
+ if (format !== 'raw-secret') {
1506
+ throw (0, _errors.lazyDOMException)(`Unable to import ${name} key with format ${format}`, 'NotSupportedError');
1507
+ }
1508
+ if (extractable) {
1509
+ throw (0, _errors.lazyDOMException)(`${name} keys are not extractable`, 'SyntaxError');
1178
1510
  }
1179
- throw new Error(`Unable to import ${name} key with format ${format}`);
1511
+ if (hasAnyNotIn(keyUsages, ['deriveKey', 'deriveBits'])) {
1512
+ throw (0, _errors.lazyDOMException)(`Unsupported key usage for a ${name} key`, 'SyntaxError');
1513
+ }
1514
+ const checkLength = typeof keyData === 'string' || _safeBuffer.Buffer.isBuffer(keyData) ? keyData.length * 8 : keyData.byteLength * 8;
1515
+ if (length !== undefined && length !== checkLength) {
1516
+ throw (0, _errors.lazyDOMException)('Invalid key length', 'DataError');
1517
+ }
1518
+ const keyObject = (0, _keys.createSecretKey)(keyData);
1519
+ return new _keys.CryptoKey(keyObject, {
1520
+ name
1521
+ }, keyUsages, false);
1180
1522
  };
1181
1523
  const hkdfImportKey = async (format, keyData, algorithm, extractable, keyUsages) => {
1182
1524
  const {
@@ -1350,7 +1692,10 @@ function mldsaSignVerify(key, data, signature) {
1350
1692
  const signVerify = (algorithm, key, data, signature) => {
1351
1693
  const usage = signature === undefined ? 'sign' : 'verify';
1352
1694
  algorithm = normalizeAlgorithm(algorithm, usage);
1353
- if (!key.usages.includes(usage) || algorithm.name !== key.algorithm.name) {
1695
+ if (algorithm.name !== key.algorithm.name) {
1696
+ throw (0, _errors.lazyDOMException)('Key algorithm mismatch', 'InvalidAccessError');
1697
+ }
1698
+ if (!key.usages.includes(usage)) {
1354
1699
  throw (0, _errors.lazyDOMException)(`Unable to use this key to ${usage}`, 'InvalidAccessError');
1355
1700
  }
1356
1701
  switch (algorithm.name) {
@@ -1368,6 +1713,18 @@ const signVerify = (algorithm, key, data, signature) => {
1368
1713
  case 'ML-DSA-44':
1369
1714
  case 'ML-DSA-65':
1370
1715
  case 'ML-DSA-87':
1716
+ case 'SLH-DSA-SHA2-128s':
1717
+ case 'SLH-DSA-SHA2-128f':
1718
+ case 'SLH-DSA-SHA2-192s':
1719
+ case 'SLH-DSA-SHA2-192f':
1720
+ case 'SLH-DSA-SHA2-256s':
1721
+ case 'SLH-DSA-SHA2-256f':
1722
+ case 'SLH-DSA-SHAKE-128s':
1723
+ case 'SLH-DSA-SHAKE-128f':
1724
+ case 'SLH-DSA-SHAKE-192s':
1725
+ case 'SLH-DSA-SHAKE-192f':
1726
+ case 'SLH-DSA-SHAKE-256s':
1727
+ case 'SLH-DSA-SHAKE-256f':
1371
1728
  return mldsaSignVerify(key, data, signature);
1372
1729
  case 'KMAC128':
1373
1730
  case 'KMAC256':
@@ -1375,10 +1732,12 @@ const signVerify = (algorithm, key, data, signature) => {
1375
1732
  }
1376
1733
  throw (0, _errors.lazyDOMException)(`Unrecognized algorithm name '${algorithm.name}' for '${usage}'`, 'NotSupportedError');
1377
1734
  };
1378
- const cipherOrWrap = async (mode, algorithm, key, data, op) => {
1379
- if (key.algorithm.name !== algorithm.name || !key.usages.includes(op)) {
1380
- throw (0, _errors.lazyDOMException)('The requested operation is not valid for the provided key', 'InvalidAccessError');
1381
- }
1735
+
1736
+ // Algorithm-mismatch and usage checks live at the public-method call sites
1737
+ // (encrypt / decrypt / wrapKey / unwrapKey) so spec-mandated message and
1738
+ // ordering — algorithm-mismatch first, then usage — is preserved
1739
+ // (Node webcrypto.js, commit 4cb1f284136).
1740
+ const cipherOrWrap = async (mode, algorithm, key, data) => {
1382
1741
  (0, _validation.validateMaxBufferLength)(data, 'data');
1383
1742
  switch (algorithm.name) {
1384
1743
  case 'RSA-OAEP':
@@ -1400,12 +1759,12 @@ const cipherOrWrap = async (mode, algorithm, key, data, op) => {
1400
1759
  const SUPPORTED_ALGORITHMS = {
1401
1760
  encrypt: new Set(['RSA-OAEP', 'AES-CTR', 'AES-CBC', 'AES-GCM', 'AES-OCB', 'ChaCha20-Poly1305']),
1402
1761
  decrypt: new Set(['RSA-OAEP', 'AES-CTR', 'AES-CBC', 'AES-GCM', 'AES-OCB', 'ChaCha20-Poly1305']),
1403
- sign: new Set(['RSASSA-PKCS1-v1_5', 'RSA-PSS', 'ECDSA', 'HMAC', 'KMAC128', 'KMAC256', 'Ed25519', 'Ed448', 'ML-DSA-44', 'ML-DSA-65', 'ML-DSA-87']),
1404
- verify: new Set(['RSASSA-PKCS1-v1_5', 'RSA-PSS', 'ECDSA', 'HMAC', 'KMAC128', 'KMAC256', 'Ed25519', 'Ed448', 'ML-DSA-44', 'ML-DSA-65', 'ML-DSA-87']),
1405
- digest: new Set(['SHA-1', 'SHA-256', 'SHA-384', 'SHA-512', 'SHA3-256', 'SHA3-384', 'SHA3-512', 'cSHAKE128', 'cSHAKE256']),
1406
- generateKey: new Set(['RSASSA-PKCS1-v1_5', 'RSA-PSS', 'RSA-OAEP', 'ECDSA', 'ECDH', 'Ed25519', 'Ed448', 'X25519', 'X448', 'AES-CTR', 'AES-CBC', 'AES-GCM', 'AES-KW', 'AES-OCB', 'ChaCha20-Poly1305', 'HMAC', 'KMAC128', 'KMAC256', 'ML-DSA-44', 'ML-DSA-65', 'ML-DSA-87', 'ML-KEM-512', 'ML-KEM-768', 'ML-KEM-1024']),
1407
- importKey: new Set(['RSASSA-PKCS1-v1_5', 'RSA-PSS', 'RSA-OAEP', 'ECDSA', 'ECDH', 'Ed25519', 'Ed448', 'X25519', 'X448', 'AES-CTR', 'AES-CBC', 'AES-GCM', 'AES-KW', 'AES-OCB', 'ChaCha20-Poly1305', 'HMAC', 'KMAC128', 'KMAC256', 'HKDF', 'PBKDF2', 'Argon2d', 'Argon2i', 'Argon2id', 'ML-DSA-44', 'ML-DSA-65', 'ML-DSA-87', 'ML-KEM-512', 'ML-KEM-768', 'ML-KEM-1024']),
1408
- exportKey: new Set(['RSASSA-PKCS1-v1_5', 'RSA-PSS', 'RSA-OAEP', 'ECDSA', 'ECDH', 'Ed25519', 'Ed448', 'X25519', 'X448', 'AES-CTR', 'AES-CBC', 'AES-GCM', 'AES-KW', 'AES-OCB', 'ChaCha20-Poly1305', 'HMAC', 'KMAC128', 'KMAC256', 'ML-DSA-44', 'ML-DSA-65', 'ML-DSA-87', 'ML-KEM-512', 'ML-KEM-768', 'ML-KEM-1024']),
1762
+ sign: new Set(['RSASSA-PKCS1-v1_5', 'RSA-PSS', 'ECDSA', 'HMAC', 'KMAC128', 'KMAC256', 'Ed25519', 'Ed448', 'ML-DSA-44', 'ML-DSA-65', 'ML-DSA-87', ..._slhdsa.SLH_DSA_VARIANTS]),
1763
+ verify: new Set(['RSASSA-PKCS1-v1_5', 'RSA-PSS', 'ECDSA', 'HMAC', 'KMAC128', 'KMAC256', 'Ed25519', 'Ed448', 'ML-DSA-44', 'ML-DSA-65', 'ML-DSA-87', ..._slhdsa.SLH_DSA_VARIANTS]),
1764
+ digest: new Set(['SHA-1', 'SHA-256', 'SHA-384', 'SHA-512', 'SHA3-256', 'SHA3-384', 'SHA3-512', 'cSHAKE128', 'cSHAKE256', 'TurboSHAKE128', 'TurboSHAKE256', 'KT128', 'KT256']),
1765
+ generateKey: new Set(['RSASSA-PKCS1-v1_5', 'RSA-PSS', 'RSA-OAEP', 'ECDSA', 'ECDH', 'Ed25519', 'Ed448', 'X25519', 'X448', 'AES-CTR', 'AES-CBC', 'AES-GCM', 'AES-KW', 'AES-OCB', 'ChaCha20-Poly1305', 'HMAC', 'KMAC128', 'KMAC256', 'ML-DSA-44', 'ML-DSA-65', 'ML-DSA-87', 'ML-KEM-512', 'ML-KEM-768', 'ML-KEM-1024', ..._slhdsa.SLH_DSA_VARIANTS]),
1766
+ importKey: new Set(['RSASSA-PKCS1-v1_5', 'RSA-PSS', 'RSA-OAEP', 'ECDSA', 'ECDH', 'Ed25519', 'Ed448', 'X25519', 'X448', 'AES-CTR', 'AES-CBC', 'AES-GCM', 'AES-KW', 'AES-OCB', 'ChaCha20-Poly1305', 'HMAC', 'KMAC128', 'KMAC256', 'HKDF', 'PBKDF2', 'Argon2d', 'Argon2i', 'Argon2id', 'ML-DSA-44', 'ML-DSA-65', 'ML-DSA-87', 'ML-KEM-512', 'ML-KEM-768', 'ML-KEM-1024', ..._slhdsa.SLH_DSA_VARIANTS]),
1767
+ exportKey: new Set(['RSASSA-PKCS1-v1_5', 'RSA-PSS', 'RSA-OAEP', 'ECDSA', 'ECDH', 'Ed25519', 'Ed448', 'X25519', 'X448', 'AES-CTR', 'AES-CBC', 'AES-GCM', 'AES-KW', 'AES-OCB', 'ChaCha20-Poly1305', 'HMAC', 'KMAC128', 'KMAC256', 'ML-DSA-44', 'ML-DSA-65', 'ML-DSA-87', 'ML-KEM-512', 'ML-KEM-768', 'ML-KEM-1024', ..._slhdsa.SLH_DSA_VARIANTS]),
1409
1768
  deriveBits: new Set(['PBKDF2', 'HKDF', 'ECDH', 'X25519', 'X448', 'Argon2d', 'Argon2i', 'Argon2id']),
1410
1769
  wrapKey: new Set(['AES-CTR', 'AES-CBC', 'AES-GCM', 'AES-KW', 'AES-OCB', 'ChaCha20-Poly1305', 'RSA-OAEP']),
1411
1770
  unwrapKey: new Set(['AES-CTR', 'AES-CBC', 'AES-GCM', 'AES-KW', 'AES-OCB', 'ChaCha20-Poly1305', 'RSA-OAEP']),
@@ -1414,119 +1773,315 @@ const SUPPORTED_ALGORITHMS = {
1414
1773
  encapsulateKey: new Set(['ML-KEM-512', 'ML-KEM-768', 'ML-KEM-1024']),
1415
1774
  decapsulateKey: new Set(['ML-KEM-512', 'ML-KEM-768', 'ML-KEM-1024'])
1416
1775
  };
1417
- const ASYMMETRIC_ALGORITHMS = new Set(['RSASSA-PKCS1-v1_5', 'RSA-PSS', 'RSA-OAEP', 'ECDSA', 'ECDH', 'Ed25519', 'Ed448', 'X25519', 'X448', 'ML-DSA-44', 'ML-DSA-65', 'ML-DSA-87', 'ML-KEM-512', 'ML-KEM-768', 'ML-KEM-1024']);
1418
- class Subtle {
1419
- static supports(operation, algorithm, _lengthOrAdditionalAlgorithm) {
1420
- let normalizedAlgorithm;
1776
+ const ASYMMETRIC_ALGORITHMS = new Set(['RSASSA-PKCS1-v1_5', 'RSA-PSS', 'RSA-OAEP', 'ECDSA', 'ECDH', 'Ed25519', 'Ed448', 'X25519', 'X448', 'ML-DSA-44', 'ML-DSA-65', 'ML-DSA-87', 'ML-KEM-512', 'ML-KEM-768', 'ML-KEM-1024', ..._slhdsa.SLH_DSA_VARIANTS]);
1777
+
1778
+ // Per-algorithm validators for deriveBits (mirrors Node's hkdf.js:141-149,
1779
+ // pbkdf2.js:96-105, argon2.js:194-209). Used by Subtle.supports to reject
1780
+ // length values that the actual deriveBits implementation would reject.
1781
+ function validateKdfDeriveBitsLength(length, algName) {
1782
+ if (length === null || length === undefined) {
1783
+ throw (0, _errors.lazyDOMException)('length cannot be null', 'OperationError');
1784
+ }
1785
+ if (length % 8) {
1786
+ throw (0, _errors.lazyDOMException)('length must be a multiple of 8', 'OperationError');
1787
+ }
1788
+ if (algName.startsWith('Argon2') && length < 32) {
1789
+ throw (0, _errors.lazyDOMException)('length must be >= 32', 'OperationError');
1790
+ }
1791
+ }
1792
+
1793
+ // Mirrors Node's webcrypto.js:1652-1731 `check`. Normalizes the algorithm,
1794
+ // looks it up in SUPPORTED_ALGORITHMS, and runs per-algorithm validation
1795
+ // (deriveBits length validators, HMAC+SHA3 generateKey rejection).
1796
+ // `op` is the operation key in SUPPORTED_ALGORITHMS — wrapKey/unwrapKey fall
1797
+ // back to encrypt/decrypt to mirror Node's normalize fallback.
1798
+ function supportsCheck(op, alg, length) {
1799
+ let normalizedAlgorithm;
1800
+ try {
1801
+ normalizedAlgorithm = normalizeAlgorithm(alg, op);
1802
+ } catch {
1803
+ if (op === 'wrapKey') return supportsCheck('encrypt', alg);
1804
+ if (op === 'unwrapKey') return supportsCheck('decrypt', alg);
1805
+ return false;
1806
+ }
1807
+ const supported = SUPPORTED_ALGORITHMS[op];
1808
+ if (!supported || !supported.has(normalizedAlgorithm.name)) {
1809
+ if (op === 'wrapKey') return supportsCheck('encrypt', alg);
1810
+ if (op === 'unwrapKey') return supportsCheck('decrypt', alg);
1811
+ return false;
1812
+ }
1813
+ if (op === 'deriveBits') {
1814
+ const name = normalizedAlgorithm.name;
1815
+ if (name === 'HKDF' || name === 'PBKDF2' || name.startsWith('Argon2')) {
1816
+ try {
1817
+ validateKdfDeriveBitsLength(length, name);
1818
+ } catch {
1819
+ return false;
1820
+ }
1821
+ }
1822
+ }
1823
+ if (op === 'generateKey' && normalizedAlgorithm.name === 'HMAC') {
1824
+ const hashName = typeof normalizedAlgorithm.hash === 'string' ? normalizedAlgorithm.hash : normalizedAlgorithm.hash?.name;
1825
+ if (normalizedAlgorithm.length === undefined && typeof hashName === 'string' && hashName.startsWith('SHA3-')) {
1826
+ return false;
1827
+ }
1828
+ }
1829
+ return true;
1830
+ }
1831
+ function supportsImpl(operation, algorithm, lengthOrAdditionalAlgorithm) {
1832
+ switch (operation) {
1833
+ case 'decapsulateBits':
1834
+ case 'decapsulateKey':
1835
+ case 'decrypt':
1836
+ case 'deriveBits':
1837
+ case 'deriveKey':
1838
+ case 'digest':
1839
+ case 'encapsulateBits':
1840
+ case 'encapsulateKey':
1841
+ case 'encrypt':
1842
+ case 'exportKey':
1843
+ case 'generateKey':
1844
+ case 'getPublicKey':
1845
+ case 'importKey':
1846
+ case 'sign':
1847
+ case 'unwrapKey':
1848
+ case 'verify':
1849
+ case 'wrapKey':
1850
+ break;
1851
+ default:
1852
+ return false;
1853
+ }
1854
+ let length;
1855
+ if (operation === 'deriveKey') {
1856
+ // deriveKey decomposes to importKey of derived alg + deriveBits with that
1857
+ // alg's key length. Node webcrypto.js:1547-1563.
1858
+ if (lengthOrAdditionalAlgorithm != null) {
1859
+ const additional = lengthOrAdditionalAlgorithm;
1860
+ if (!supportsCheck('importKey', additional)) return false;
1861
+ try {
1862
+ length = getKeyLength(normalizeAlgorithm(additional, 'get key length'));
1863
+ } catch {
1864
+ return false;
1865
+ }
1866
+ return supportsCheck('deriveBits', algorithm, length);
1867
+ }
1868
+ // No additional algorithm given — only check the deriveBits side.
1869
+ return supportsCheck('deriveBits', algorithm);
1870
+ }
1871
+ if (operation === 'wrapKey') {
1872
+ // wrapKey decomposes to encrypt of wrapping alg + exportKey of wrapped alg.
1873
+ // Node webcrypto.js:1564-1572.
1874
+ if (lengthOrAdditionalAlgorithm != null) {
1875
+ const additional = lengthOrAdditionalAlgorithm;
1876
+ if (!supportsCheck('exportKey', additional)) return false;
1877
+ }
1878
+ return supportsCheck('wrapKey', algorithm);
1879
+ }
1880
+ if (operation === 'unwrapKey') {
1881
+ // unwrapKey decomposes to decrypt of wrapping alg + importKey of wrapped
1882
+ // alg. Node webcrypto.js:1573-1581.
1883
+ if (lengthOrAdditionalAlgorithm != null) {
1884
+ const additional = lengthOrAdditionalAlgorithm;
1885
+ if (!supportsCheck('importKey', additional)) return false;
1886
+ }
1887
+ return supportsCheck('unwrapKey', algorithm);
1888
+ }
1889
+ if (operation === 'deriveBits') {
1890
+ if (lengthOrAdditionalAlgorithm == null) {
1891
+ length = null;
1892
+ } else if (typeof lengthOrAdditionalAlgorithm === 'number') {
1893
+ length = lengthOrAdditionalAlgorithm;
1894
+ } else {
1895
+ return false;
1896
+ }
1897
+ return supportsCheck('deriveBits', algorithm, length);
1898
+ }
1899
+ if (operation === 'getPublicKey') {
1900
+ let normalized;
1421
1901
  try {
1422
- normalizedAlgorithm = normalizeAlgorithm(algorithm, operation === 'getPublicKey' ? 'exportKey' : operation);
1902
+ normalized = normalizeAlgorithm(algorithm, 'exportKey');
1423
1903
  } catch {
1424
1904
  return false;
1425
1905
  }
1426
- const name = normalizedAlgorithm.name;
1427
- if (operation === 'getPublicKey') {
1428
- return ASYMMETRIC_ALGORITHMS.has(name);
1429
- }
1430
- if (operation === 'deriveKey') {
1431
- // deriveKey decomposes to deriveBits + importKey of additional algorithm
1432
- if (!SUPPORTED_ALGORITHMS.deriveBits?.has(name)) return false;
1433
- if (_lengthOrAdditionalAlgorithm != null) {
1434
- try {
1435
- const additionalAlg = normalizeAlgorithm(_lengthOrAdditionalAlgorithm, 'importKey');
1436
- return SUPPORTED_ALGORITHMS.importKey?.has(additionalAlg.name) ?? false;
1437
- } catch {
1438
- return false;
1906
+ return ASYMMETRIC_ALGORITHMS.has(normalized.name);
1907
+ }
1908
+ if (operation === 'encapsulateKey' || operation === 'decapsulateKey') {
1909
+ // sharedKeyAlgorithm must support importKey, with HMAC/KMAC limited to
1910
+ // length === undefined or 256 (Node webcrypto.js:1610-1645).
1911
+ const additional = lengthOrAdditionalAlgorithm;
1912
+ let normalizedAdd;
1913
+ try {
1914
+ normalizedAdd = normalizeAlgorithm(additional, 'importKey');
1915
+ } catch {
1916
+ return false;
1917
+ }
1918
+ switch (normalizedAdd.name) {
1919
+ case 'AES-OCB':
1920
+ case 'AES-KW':
1921
+ case 'AES-GCM':
1922
+ case 'AES-CTR':
1923
+ case 'AES-CBC':
1924
+ case 'ChaCha20-Poly1305':
1925
+ case 'HKDF':
1926
+ case 'PBKDF2':
1927
+ case 'Argon2i':
1928
+ case 'Argon2d':
1929
+ case 'Argon2id':
1930
+ break;
1931
+ case 'HMAC':
1932
+ case 'KMAC128':
1933
+ case 'KMAC256':
1934
+ {
1935
+ const addLen = normalizedAdd.length;
1936
+ if (addLen !== undefined && addLen !== 256) return false;
1937
+ break;
1439
1938
  }
1440
- }
1441
- return true;
1939
+ default:
1940
+ return false;
1442
1941
  }
1443
- const supported = SUPPORTED_ALGORITHMS[operation];
1444
- if (!supported) return false;
1445
- return supported.has(name);
1942
+ return supportsCheck(operation, algorithm);
1943
+ }
1944
+ return supportsCheck(operation, algorithm);
1945
+ }
1946
+ class Subtle {
1947
+ // Spec-compliant capability detector. Mirrors Node's webcrypto.js:1506-1649
1948
+ // `SubtleCrypto.supports`, including:
1949
+ // • composed-operation decomposition (deriveKey, wrapKey, unwrapKey,
1950
+ // encapsulateKey, decapsulateKey, getPublicKey)
1951
+ // • per-algorithm length validators for deriveBits (HKDF/PBKDF2/Argon2)
1952
+ // • HMAC + SHA3 generateKey with no length → false
1953
+ // Static-only per the WICG spec.
1954
+ static supports(operation, algorithm, lengthOrAdditionalAlgorithm = null) {
1955
+ return supportsImpl(operation, algorithm, lengthOrAdditionalAlgorithm);
1446
1956
  }
1447
1957
  async decrypt(algorithm, key, data) {
1958
+ requireArgs(arguments.length, 3, 'decrypt');
1448
1959
  const normalizedAlgorithm = normalizeAlgorithm(algorithm, 'decrypt');
1449
- return cipherOrWrap(CipherOrWrapMode.kWebCryptoCipherDecrypt, normalizedAlgorithm, key, (0, _conversion.bufferLikeToArrayBuffer)(data), 'decrypt');
1960
+ if (normalizedAlgorithm.name !== key.algorithm.name) {
1961
+ throw (0, _errors.lazyDOMException)('Key algorithm mismatch', 'InvalidAccessError');
1962
+ }
1963
+ if (!key.usages.includes('decrypt')) {
1964
+ throw (0, _errors.lazyDOMException)('Unable to use this key to decrypt', 'InvalidAccessError');
1965
+ }
1966
+ return cipherOrWrap(CipherOrWrapMode.kWebCryptoCipherDecrypt, normalizedAlgorithm, key, (0, _conversion.bufferLikeToArrayBuffer)(data));
1450
1967
  }
1451
1968
  async digest(algorithm, data) {
1969
+ requireArgs(arguments.length, 2, 'digest');
1452
1970
  const normalizedAlgorithm = normalizeAlgorithm(algorithm, 'digest');
1453
1971
  return (0, _hash.asyncDigest)(normalizedAlgorithm, data);
1454
1972
  }
1455
- async deriveBits(algorithm, baseKey, length) {
1973
+ async deriveBits(algorithm, baseKey, length = null) {
1974
+ requireArgs(arguments.length, 2, 'deriveBits');
1975
+ const normalizedAlgorithm = normalizeAlgorithm(algorithm, 'deriveBits');
1456
1976
  // WebCrypto §SubtleCrypto.deriveBits step 11: throw InvalidAccessError
1457
1977
  // unless `baseKey.[[usages]]` contains "deriveBits" specifically. The
1458
1978
  // previous `deriveBits || deriveKey` accept-either branch silently
1459
1979
  // promoted deriveKey-only keys into deriveBits use, contradicting the
1460
1980
  // spec usage gate.
1461
- if (!baseKey.keyUsages.includes('deriveBits')) {
1981
+ if (!baseKey.usages.includes('deriveBits')) {
1462
1982
  throw (0, _errors.lazyDOMException)('baseKey does not have deriveBits usage', 'InvalidAccessError');
1463
1983
  }
1464
- if (baseKey.algorithm.name !== algorithm.name) throw new Error('Key algorithm mismatch');
1465
- switch (algorithm.name) {
1984
+ if (baseKey.algorithm.name !== normalizedAlgorithm.name) {
1985
+ throw (0, _errors.lazyDOMException)('Key algorithm mismatch', 'InvalidAccessError');
1986
+ }
1987
+ switch (normalizedAlgorithm.name) {
1466
1988
  case 'PBKDF2':
1467
- return (0, _pbkdf.pbkdf2DeriveBits)(algorithm, baseKey, length);
1989
+ if (length === null) {
1990
+ throw (0, _errors.lazyDOMException)('length cannot be null', 'OperationError');
1991
+ }
1992
+ return (0, _pbkdf.pbkdf2DeriveBits)(normalizedAlgorithm, baseKey, length);
1468
1993
  case 'X25519':
1469
1994
  // Fall through
1470
1995
  case 'X448':
1471
- return (0, _ed.xDeriveBits)(algorithm, baseKey, length);
1996
+ return (0, _ed.xDeriveBits)(normalizedAlgorithm, baseKey, length);
1472
1997
  case 'ECDH':
1473
- return (0, _ec.ecDeriveBits)(algorithm, baseKey, length);
1998
+ return (0, _ec.ecDeriveBits)(normalizedAlgorithm, baseKey, length);
1474
1999
  case 'HKDF':
1475
- return (0, _hkdf.hkdfDeriveBits)(algorithm, baseKey, length);
2000
+ if (length === null) {
2001
+ throw (0, _errors.lazyDOMException)('length cannot be null', 'OperationError');
2002
+ }
2003
+ return (0, _hkdf.hkdfDeriveBits)(normalizedAlgorithm, baseKey, length);
1476
2004
  case 'Argon2d':
1477
2005
  case 'Argon2i':
1478
2006
  case 'Argon2id':
1479
- return argon2DeriveBits(algorithm, baseKey, length);
2007
+ if (length === null) {
2008
+ throw (0, _errors.lazyDOMException)('length cannot be null', 'OperationError');
2009
+ }
2010
+ return argon2DeriveBits(normalizedAlgorithm, baseKey, length);
1480
2011
  }
1481
- throw new Error(`'subtle.deriveBits()' for ${algorithm.name} is not implemented.`);
2012
+ throw (0, _errors.lazyDOMException)(`'subtle.deriveBits()' for ${normalizedAlgorithm.name} is not implemented.`, 'NotSupportedError');
1482
2013
  }
1483
2014
  async deriveKey(algorithm, baseKey, derivedKeyAlgorithm, extractable, keyUsages) {
2015
+ requireArgs(arguments.length, 5, 'deriveKey');
2016
+ const normalizedAlgorithm = normalizeAlgorithm(algorithm, 'deriveBits');
2017
+ const normalizedDerivedKeyAlgorithm = normalizeAlgorithm(derivedKeyAlgorithm, 'importKey');
2018
+
1484
2019
  // Validate baseKey usage
1485
- if (!baseKey.usages.includes('deriveKey') && !baseKey.usages.includes('deriveBits')) {
1486
- throw (0, _errors.lazyDOMException)('baseKey does not have deriveKey or deriveBits usage', 'InvalidAccessError');
2020
+ if (!baseKey.usages.includes('deriveKey')) {
2021
+ throw (0, _errors.lazyDOMException)('baseKey does not have deriveKey usage', 'InvalidAccessError');
2022
+ }
2023
+ if (baseKey.algorithm.name !== normalizedAlgorithm.name) {
2024
+ throw (0, _errors.lazyDOMException)('Key algorithm mismatch', 'InvalidAccessError');
1487
2025
  }
1488
2026
 
1489
- // Calculate required key length
1490
- const length = getKeyLength(derivedKeyAlgorithm);
2027
+ // Calculate required key length (may be null for KDF-derived material).
2028
+ const length = getKeyLength(normalizedDerivedKeyAlgorithm);
1491
2029
 
1492
2030
  // Step 1: Derive bits
1493
2031
  let derivedBits;
1494
- if (baseKey.algorithm.name !== algorithm.name) throw new Error('Key algorithm mismatch');
1495
- switch (algorithm.name) {
2032
+ switch (normalizedAlgorithm.name) {
1496
2033
  case 'PBKDF2':
1497
- derivedBits = await (0, _pbkdf.pbkdf2DeriveBits)(algorithm, baseKey, length);
2034
+ if (length === null) {
2035
+ throw (0, _errors.lazyDOMException)('length cannot be null', 'OperationError');
2036
+ }
2037
+ derivedBits = await (0, _pbkdf.pbkdf2DeriveBits)(normalizedAlgorithm, baseKey, length);
1498
2038
  break;
1499
2039
  case 'X25519':
1500
2040
  // Fall through
1501
2041
  case 'X448':
1502
- derivedBits = await (0, _ed.xDeriveBits)(algorithm, baseKey, length);
2042
+ derivedBits = await (0, _ed.xDeriveBits)(normalizedAlgorithm, baseKey, length);
1503
2043
  break;
1504
2044
  case 'ECDH':
1505
- derivedBits = await (0, _ec.ecDeriveBits)(algorithm, baseKey, length);
2045
+ derivedBits = await (0, _ec.ecDeriveBits)(normalizedAlgorithm, baseKey, length);
1506
2046
  break;
1507
2047
  case 'HKDF':
1508
- derivedBits = (0, _hkdf.hkdfDeriveBits)(algorithm, baseKey, length);
2048
+ if (length === null) {
2049
+ throw (0, _errors.lazyDOMException)('length cannot be null', 'OperationError');
2050
+ }
2051
+ derivedBits = (0, _hkdf.hkdfDeriveBits)(normalizedAlgorithm, baseKey, length);
1509
2052
  break;
1510
2053
  case 'Argon2d':
1511
2054
  case 'Argon2i':
1512
2055
  case 'Argon2id':
1513
- derivedBits = argon2DeriveBits(algorithm, baseKey, length);
2056
+ if (length === null) {
2057
+ throw (0, _errors.lazyDOMException)('length cannot be null', 'OperationError');
2058
+ }
2059
+ derivedBits = argon2DeriveBits(normalizedAlgorithm, baseKey, length);
1514
2060
  break;
1515
2061
  default:
1516
- throw new Error(`'subtle.deriveKey()' for ${algorithm.name} is not implemented.`);
2062
+ throw (0, _errors.lazyDOMException)(`'subtle.deriveKey()' for ${normalizedAlgorithm.name} is not implemented.`, 'NotSupportedError');
1517
2063
  }
1518
2064
 
1519
- // Step 2: Import as key
1520
- return this.importKey('raw', derivedBits, derivedKeyAlgorithm, extractable, keyUsages);
2065
+ // Step 2: Import as key. Use 'raw-secret' so derived material flows into
2066
+ // AEADs / KMAC correctly — they reject plain 'raw' (Node webcrypto.js:381-385).
2067
+ return this.importKey('raw-secret', derivedBits, derivedKeyAlgorithm, extractable, keyUsages);
1521
2068
  }
1522
2069
  async encrypt(algorithm, key, data) {
2070
+ requireArgs(arguments.length, 3, 'encrypt');
1523
2071
  const normalizedAlgorithm = normalizeAlgorithm(algorithm, 'encrypt');
1524
- return cipherOrWrap(CipherOrWrapMode.kWebCryptoCipherEncrypt, normalizedAlgorithm, key, (0, _conversion.bufferLikeToArrayBuffer)(data), 'encrypt');
2072
+ if (normalizedAlgorithm.name !== key.algorithm.name) {
2073
+ throw (0, _errors.lazyDOMException)('Key algorithm mismatch', 'InvalidAccessError');
2074
+ }
2075
+ if (!key.usages.includes('encrypt')) {
2076
+ throw (0, _errors.lazyDOMException)('Unable to use this key to encrypt', 'InvalidAccessError');
2077
+ }
2078
+ return cipherOrWrap(CipherOrWrapMode.kWebCryptoCipherEncrypt, normalizedAlgorithm, key, (0, _conversion.bufferLikeToArrayBuffer)(data));
1525
2079
  }
1526
2080
  async exportKey(format, key) {
1527
- if (!key.extractable) throw new Error('key is not extractable');
2081
+ requireArgs(arguments.length, 2, 'exportKey');
2082
+ if (!key.extractable) throw (0, _errors.lazyDOMException)('key is not extractable', 'InvalidAccessError');
1528
2083
  if (format === 'raw-seed') {
1529
- const pqcAlgos = ['ML-KEM-512', 'ML-KEM-768', 'ML-KEM-1024', 'ML-DSA-44', 'ML-DSA-65', 'ML-DSA-87'];
2084
+ const pqcAlgos = ['ML-KEM-512', 'ML-KEM-768', 'ML-KEM-1024', 'ML-DSA-44', 'ML-DSA-65', 'ML-DSA-87', ..._slhdsa.SLH_DSA_VARIANTS];
1530
2085
  if (!pqcAlgos.includes(key.algorithm.name)) {
1531
2086
  throw (0, _errors.lazyDOMException)('raw-seed export only supported for PQC keys', 'NotSupportedError');
1532
2087
  }
@@ -1535,9 +2090,6 @@ class Subtle {
1535
2090
  }
1536
2091
  return (0, _conversion.bufferLikeToArrayBuffer)(key.keyObject.handle.exportKey());
1537
2092
  }
1538
-
1539
- // Note: 'raw-seed' is handled above; do NOT normalize it here
1540
- if (format === 'raw-secret' || format === 'raw-public') format = 'raw';
1541
2093
  switch (format) {
1542
2094
  case 'spki':
1543
2095
  return await exportKeySpki(key);
@@ -1546,13 +2098,19 @@ class Subtle {
1546
2098
  case 'jwk':
1547
2099
  return exportKeyJWK(key);
1548
2100
  case 'raw':
1549
- return exportKeyRaw(key);
2101
+ case 'raw-secret':
2102
+ case 'raw-public':
2103
+ return exportKeyRaw(key, format);
1550
2104
  }
1551
2105
  }
1552
2106
  async wrapKey(format, key, wrappingKey, wrapAlgorithm) {
1553
- // Validate wrappingKey usage
2107
+ requireArgs(arguments.length, 4, 'wrapKey');
2108
+ const normalizedWrapAlgorithm = normalizeAlgorithm(wrapAlgorithm, 'wrapKey');
2109
+ if (normalizedWrapAlgorithm.name !== wrappingKey.algorithm.name) {
2110
+ throw (0, _errors.lazyDOMException)('Key algorithm mismatch', 'InvalidAccessError');
2111
+ }
1554
2112
  if (!wrappingKey.usages.includes('wrapKey')) {
1555
- throw (0, _errors.lazyDOMException)('wrappingKey does not have wrapKey usage', 'InvalidAccessError');
2113
+ throw (0, _errors.lazyDOMException)('Unable to use this key to wrapKey', 'InvalidAccessError');
1556
2114
  }
1557
2115
 
1558
2116
  // Step 1: Export the key
@@ -1565,7 +2123,7 @@ class Subtle {
1565
2123
  const buffer = _safeBuffer.Buffer.from(jwkString, 'utf8');
1566
2124
 
1567
2125
  // For AES-KW, pad to multiple of 8 bytes (accounting for null terminator)
1568
- if (wrapAlgorithm.name === 'AES-KW') {
2126
+ if (normalizedWrapAlgorithm.name === 'AES-KW') {
1569
2127
  const length = buffer.length;
1570
2128
  // Add 1 for null terminator, then pad to multiple of 8
1571
2129
  const paddedLength = Math.ceil((length + 1) / 8) * 8;
@@ -1582,16 +2140,20 @@ class Subtle {
1582
2140
  }
1583
2141
 
1584
2142
  // Step 3: Encrypt the exported key
1585
- return cipherOrWrap(CipherOrWrapMode.kWebCryptoCipherEncrypt, wrapAlgorithm, wrappingKey, keyData, 'wrapKey');
2143
+ return cipherOrWrap(CipherOrWrapMode.kWebCryptoCipherEncrypt, normalizedWrapAlgorithm, wrappingKey, keyData);
1586
2144
  }
1587
2145
  async unwrapKey(format, wrappedKey, unwrappingKey, unwrapAlgorithm, unwrappedKeyAlgorithm, extractable, keyUsages) {
1588
- // Validate unwrappingKey usage
2146
+ requireArgs(arguments.length, 7, 'unwrapKey');
2147
+ const normalizedUnwrapAlgorithm = normalizeAlgorithm(unwrapAlgorithm, 'unwrapKey');
2148
+ if (normalizedUnwrapAlgorithm.name !== unwrappingKey.algorithm.name) {
2149
+ throw (0, _errors.lazyDOMException)('Key algorithm mismatch', 'InvalidAccessError');
2150
+ }
1589
2151
  if (!unwrappingKey.usages.includes('unwrapKey')) {
1590
- throw (0, _errors.lazyDOMException)('unwrappingKey does not have unwrapKey usage', 'InvalidAccessError');
2152
+ throw (0, _errors.lazyDOMException)('Unable to use this key to unwrapKey', 'InvalidAccessError');
1591
2153
  }
1592
2154
 
1593
2155
  // Step 1: Decrypt the wrapped key
1594
- const decrypted = await cipherOrWrap(CipherOrWrapMode.kWebCryptoCipherDecrypt, unwrapAlgorithm, unwrappingKey, (0, _conversion.bufferLikeToArrayBuffer)(wrappedKey), 'unwrapKey');
2156
+ const decrypted = await cipherOrWrap(CipherOrWrapMode.kWebCryptoCipherDecrypt, normalizedUnwrapAlgorithm, unwrappingKey, (0, _conversion.bufferLikeToArrayBuffer)(wrappedKey));
1595
2157
 
1596
2158
  // Step 2: Convert to appropriate format
1597
2159
  let keyData;
@@ -1599,7 +2161,7 @@ class Subtle {
1599
2161
  const buffer = _safeBuffer.Buffer.from(decrypted);
1600
2162
  // For AES-KW, the data may be padded - find the null terminator
1601
2163
  let jwkString;
1602
- if (unwrapAlgorithm.name === 'AES-KW') {
2164
+ if (normalizedUnwrapAlgorithm.name === 'AES-KW') {
1603
2165
  // Find the null terminator (if present) to get the original string
1604
2166
  const nullIndex = buffer.indexOf(0);
1605
2167
  if (nullIndex !== -1) {
@@ -1620,6 +2182,7 @@ class Subtle {
1620
2182
  return this.importKey(format, keyData, unwrappedKeyAlgorithm, extractable, keyUsages);
1621
2183
  }
1622
2184
  async generateKey(algorithm, extractable, keyUsages) {
2185
+ requireArgs(arguments.length, 3, 'generateKey');
1623
2186
  algorithm = normalizeAlgorithm(algorithm, 'generateKey');
1624
2187
  let result;
1625
2188
  switch (algorithm.name) {
@@ -1681,6 +2244,21 @@ class Subtle {
1681
2244
  result = await (0, _mldsa.mldsa_generateKeyPairWebCrypto)(algorithm.name, extractable, keyUsages);
1682
2245
  checkCryptoKeyPairUsages(result);
1683
2246
  break;
2247
+ case 'SLH-DSA-SHA2-128s':
2248
+ case 'SLH-DSA-SHA2-128f':
2249
+ case 'SLH-DSA-SHA2-192s':
2250
+ case 'SLH-DSA-SHA2-192f':
2251
+ case 'SLH-DSA-SHA2-256s':
2252
+ case 'SLH-DSA-SHA2-256f':
2253
+ case 'SLH-DSA-SHAKE-128s':
2254
+ case 'SLH-DSA-SHAKE-128f':
2255
+ case 'SLH-DSA-SHAKE-192s':
2256
+ case 'SLH-DSA-SHAKE-192f':
2257
+ case 'SLH-DSA-SHAKE-256s':
2258
+ case 'SLH-DSA-SHAKE-256f':
2259
+ result = await (0, _slhdsa.slhdsa_generateKeyPairWebCrypto)(algorithm.name, extractable, keyUsages);
2260
+ checkCryptoKeyPairUsages(result);
2261
+ break;
1684
2262
  case 'X25519':
1685
2263
  // Fall through
1686
2264
  case 'X448':
@@ -1702,6 +2280,7 @@ class Subtle {
1702
2280
  return result;
1703
2281
  }
1704
2282
  async getPublicKey(key, keyUsages) {
2283
+ requireArgs(arguments.length, 2, 'getPublicKey');
1705
2284
  if (key.type === 'secret') {
1706
2285
  throw (0, _errors.lazyDOMException)('key must be a private key', 'NotSupportedError');
1707
2286
  }
@@ -1712,8 +2291,11 @@ class Subtle {
1712
2291
  return publicKeyObject.toCryptoKey(key.algorithm, true, keyUsages);
1713
2292
  }
1714
2293
  async importKey(format, data, algorithm, extractable, keyUsages) {
1715
- // Note: 'raw-seed' is NOT normalized — PQC import functions handle it directly
1716
- if (format === 'raw-secret' || format === 'raw-public') format = 'raw';
2294
+ requireArgs(arguments.length, 5, 'importKey');
2295
+ // Per-algorithm format handling. Some algorithms alias raw-secret/raw-public
2296
+ // to 'raw' (RSA, EC, Ed/X, HMAC, HKDF, PBKDF2); others demand the
2297
+ // disambiguated form (KMAC, AES-OCB, ChaCha20-Poly1305, Argon2, ML-DSA,
2298
+ // ML-KEM). 'raw-seed' is never normalized — PQC import handles it directly.
1717
2299
  const normalizedAlgorithm = normalizeAlgorithm(algorithm, 'importKey');
1718
2300
  let result;
1719
2301
  switch (normalizedAlgorithm.name) {
@@ -1722,14 +2304,17 @@ class Subtle {
1722
2304
  case 'RSA-PSS':
1723
2305
  // Fall through
1724
2306
  case 'RSA-OAEP':
1725
- result = rsaImportKey(format, data, normalizedAlgorithm, extractable, keyUsages);
2307
+ result = rsaImportKey(aliasKeyFormat(format), data, normalizedAlgorithm, extractable, keyUsages);
1726
2308
  break;
1727
2309
  case 'ECDSA':
1728
2310
  // Fall through
1729
2311
  case 'ECDH':
1730
- result = (0, _ec.ecImportKey)(format, data, normalizedAlgorithm, extractable, keyUsages);
2312
+ result = (0, _ec.ecImportKey)(aliasKeyFormat(format), data, normalizedAlgorithm, extractable, keyUsages);
1731
2313
  break;
1732
2314
  case 'HMAC':
2315
+ // No aliasing — Node routes HMAC straight into mac.js, which accepts
2316
+ // 'raw' / 'raw-secret' / 'jwk' and rejects everything else
2317
+ // (webcrypto.js:774-781, mac.js:136-174).
1733
2318
  result = await hmacImportKey(normalizedAlgorithm, format, data, extractable, keyUsages);
1734
2319
  break;
1735
2320
  case 'KMAC128':
@@ -1751,13 +2336,15 @@ class Subtle {
1751
2336
  result = await aesImportKey(normalizedAlgorithm, format, data, extractable, keyUsages);
1752
2337
  break;
1753
2338
  case 'PBKDF2':
2339
+ result = await pbkdf2ImportKey(normalizedAlgorithm, aliasKeyFormat(format), data, extractable, keyUsages);
2340
+ break;
1754
2341
  case 'Argon2d':
1755
2342
  case 'Argon2i':
1756
2343
  case 'Argon2id':
1757
- result = await importGenericSecretKey(normalizedAlgorithm, format, data, extractable, keyUsages);
2344
+ result = await argon2ImportKey(normalizedAlgorithm, format, data, extractable, keyUsages);
1758
2345
  break;
1759
2346
  case 'HKDF':
1760
- result = await hkdfImportKey(format, data, normalizedAlgorithm, extractable, keyUsages);
2347
+ result = await hkdfImportKey(aliasKeyFormat(format), data, normalizedAlgorithm, extractable, keyUsages);
1761
2348
  break;
1762
2349
  case 'X25519':
1763
2350
  // Fall through
@@ -1766,7 +2353,21 @@ class Subtle {
1766
2353
  case 'Ed25519':
1767
2354
  // Fall through
1768
2355
  case 'Ed448':
1769
- result = edImportKey(format, data, normalizedAlgorithm, extractable, keyUsages);
2356
+ result = edImportKey(aliasKeyFormat(format), data, normalizedAlgorithm, extractable, keyUsages);
2357
+ break;
2358
+ case 'SLH-DSA-SHA2-128s':
2359
+ case 'SLH-DSA-SHA2-128f':
2360
+ case 'SLH-DSA-SHA2-192s':
2361
+ case 'SLH-DSA-SHA2-192f':
2362
+ case 'SLH-DSA-SHA2-256s':
2363
+ case 'SLH-DSA-SHA2-256f':
2364
+ case 'SLH-DSA-SHAKE-128s':
2365
+ case 'SLH-DSA-SHAKE-128f':
2366
+ case 'SLH-DSA-SHAKE-192s':
2367
+ case 'SLH-DSA-SHAKE-192f':
2368
+ case 'SLH-DSA-SHAKE-256s':
2369
+ case 'SLH-DSA-SHAKE-256f':
2370
+ result = slhdsaImportKey(format, data, normalizedAlgorithm, extractable, keyUsages);
1770
2371
  break;
1771
2372
  case 'ML-DSA-44':
1772
2373
  // Fall through
@@ -1791,9 +2392,11 @@ class Subtle {
1791
2392
  return result;
1792
2393
  }
1793
2394
  async sign(algorithm, key, data) {
2395
+ requireArgs(arguments.length, 3, 'sign');
1794
2396
  return signVerify(normalizeAlgorithm(algorithm, 'sign'), key, data);
1795
2397
  }
1796
2398
  async verify(algorithm, key, signature, data) {
2399
+ requireArgs(arguments.length, 4, 'verify');
1797
2400
  return signVerify(normalizeAlgorithm(algorithm, 'verify'), key, data, signature);
1798
2401
  }
1799
2402
  _encapsulateCore(algorithm, key) {
@@ -1819,12 +2422,14 @@ class Subtle {
1819
2422
  return mlkem.decapsulateSync((0, _conversion.bufferLikeToArrayBuffer)(ciphertext));
1820
2423
  }
1821
2424
  async encapsulateBits(algorithm, key) {
2425
+ requireArgs(arguments.length, 2, 'encapsulateBits');
1822
2426
  if (!key.usages.includes('encapsulateBits')) {
1823
2427
  throw (0, _errors.lazyDOMException)('Key does not have encapsulateBits usage', 'InvalidAccessError');
1824
2428
  }
1825
2429
  return this._encapsulateCore(algorithm, key);
1826
2430
  }
1827
2431
  async encapsulateKey(algorithm, key, sharedKeyAlgorithm, extractable, usages) {
2432
+ requireArgs(arguments.length, 5, 'encapsulateKey');
1828
2433
  if (!key.usages.includes('encapsulateKey')) {
1829
2434
  throw (0, _errors.lazyDOMException)('Key does not have encapsulateKey usage', 'InvalidAccessError');
1830
2435
  }
@@ -1832,49 +2437,95 @@ class Subtle {
1832
2437
  sharedKey,
1833
2438
  ciphertext
1834
2439
  } = this._encapsulateCore(algorithm, key);
1835
- const importedKey = await this.importKey('raw', sharedKey, sharedKeyAlgorithm, extractable, usages);
2440
+ // Node imports the encapsulated shared bits as 'raw-secret'
2441
+ // (webcrypto.js:1370-1374) so AEADs / KMAC accept the result.
2442
+ const importedKey = await this.importKey('raw-secret', sharedKey, sharedKeyAlgorithm, extractable, usages);
1836
2443
  return {
1837
2444
  key: importedKey,
1838
2445
  ciphertext
1839
2446
  };
1840
2447
  }
1841
2448
  async decapsulateBits(algorithm, key, ciphertext) {
2449
+ requireArgs(arguments.length, 3, 'decapsulateBits');
1842
2450
  if (!key.usages.includes('decapsulateBits')) {
1843
2451
  throw (0, _errors.lazyDOMException)('Key does not have decapsulateBits usage', 'InvalidAccessError');
1844
2452
  }
1845
2453
  return this._decapsulateCore(algorithm, key, ciphertext);
1846
2454
  }
1847
2455
  async decapsulateKey(algorithm, key, ciphertext, sharedKeyAlgorithm, extractable, usages) {
2456
+ requireArgs(arguments.length, 6, 'decapsulateKey');
1848
2457
  if (!key.usages.includes('decapsulateKey')) {
1849
2458
  throw (0, _errors.lazyDOMException)('Key does not have decapsulateKey usage', 'InvalidAccessError');
1850
2459
  }
1851
2460
  const sharedKey = this._decapsulateCore(algorithm, key, ciphertext);
1852
- return this.importKey('raw', sharedKey, sharedKeyAlgorithm, extractable, usages);
2461
+ // Node imports the decapsulated shared bits as 'raw-secret'
2462
+ // (webcrypto.js:1490-1494).
2463
+ return this.importKey('raw-secret', sharedKey, sharedKeyAlgorithm, extractable, usages);
1853
2464
  }
1854
2465
  }
1855
2466
  exports.Subtle = Subtle;
1856
2467
  const subtle = exports.subtle = new Subtle();
2468
+
2469
+ // Returns the number of bits to derive for an `importKey` algorithm, mirroring
2470
+ // Node's webcrypto.js:269-306 `getKeyLength`. Returns null for KDF algorithms
2471
+ // (HKDF / PBKDF2 / Argon2) — those carry their full derived secret without a
2472
+ // fixed key length. Throws OperationError on invalid AES / HMAC inputs rather
2473
+ // than silently coercing to a default (Node commit 4cb1f284136 behavior).
1857
2474
  function getKeyLength(algorithm) {
1858
2475
  const name = algorithm.name;
2476
+ const length = algorithm.length;
1859
2477
  switch (name) {
1860
2478
  case 'AES-CTR':
1861
2479
  case 'AES-CBC':
1862
2480
  case 'AES-GCM':
1863
2481
  case 'AES-KW':
1864
2482
  case 'AES-OCB':
1865
- case 'ChaCha20-Poly1305':
1866
- return algorithm.length || 256;
2483
+ if (length !== 128 && length !== 192 && length !== 256) {
2484
+ throw (0, _errors.lazyDOMException)('Invalid key length', 'OperationError');
2485
+ }
2486
+ return length;
1867
2487
  case 'HMAC':
1868
2488
  {
1869
- const hmacAlg = algorithm;
1870
- return hmacAlg.length || 256;
2489
+ if (length === undefined) {
2490
+ return getHmacBlockSize(algorithm.hash?.name ?? algorithm.hash);
2491
+ }
2492
+ if (typeof length === 'number' && length !== 0) {
2493
+ return length;
2494
+ }
2495
+ throw (0, _errors.lazyDOMException)('Invalid key length', 'OperationError');
1871
2496
  }
1872
2497
  case 'KMAC128':
1873
- return algorithm.length || 128;
2498
+ return typeof length === 'number' ? length : 128;
1874
2499
  case 'KMAC256':
1875
- return algorithm.length || 256;
2500
+ return typeof length === 'number' ? length : 256;
2501
+ case 'ChaCha20-Poly1305':
2502
+ return 256;
2503
+ case 'HKDF':
2504
+ case 'PBKDF2':
2505
+ case 'Argon2d':
2506
+ case 'Argon2i':
2507
+ case 'Argon2id':
2508
+ return null;
1876
2509
  default:
1877
2510
  throw (0, _errors.lazyDOMException)(`Cannot determine key length for ${name}`, 'NotSupportedError');
1878
2511
  }
1879
2512
  }
2513
+ function getHmacBlockSize(name) {
2514
+ switch (name) {
2515
+ case 'SHA-1':
2516
+ case 'SHA-256':
2517
+ return 512;
2518
+ case 'SHA-384':
2519
+ case 'SHA-512':
2520
+ return 1024;
2521
+ case 'SHA3-256':
2522
+ case 'SHA3-384':
2523
+ case 'SHA3-512':
2524
+ // SHA-3 / HMAC interaction undefined — Node throws here too
2525
+ // (webcrypto-modern-algos issue #23).
2526
+ throw (0, _errors.lazyDOMException)('Explicit algorithm length member is required', 'NotSupportedError');
2527
+ default:
2528
+ throw (0, _errors.lazyDOMException)('Invalid key length', 'OperationError');
2529
+ }
2530
+ }
1880
2531
  //# sourceMappingURL=subtle.js.map