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
package/src/subtle.ts CHANGED
@@ -18,7 +18,8 @@ import type {
18
18
  RsaOaepParams,
19
19
  ChaCha20Poly1305Params,
20
20
  } from './utils';
21
- import { KFormatType, KeyEncoding, KeyType } from './utils';
21
+ import { KFormatType, KeyEncoding, KeyType, kNamedCurveAliases } from './utils';
22
+ import { Buffer } from '@craftzdog/react-native-buffer';
22
23
  import {
23
24
  CryptoKey,
24
25
  KeyObject,
@@ -31,7 +32,10 @@ import { bufferLikeToArrayBuffer } from './utils/conversion';
31
32
  import { argon2Sync } from './argon2';
32
33
  import { lazyDOMException } from './utils/errors';
33
34
  import { normalizeHashName, HashContext } from './utils/hashnames';
34
- import { validateMaxBufferLength } from './utils/validation';
35
+ import {
36
+ validateJwkStructure,
37
+ validateMaxBufferLength,
38
+ } from './utils/validation';
35
39
  import { asyncDigest } from './hash';
36
40
  import { createSecretKey, createPublicKey } from './keys';
37
41
  import { NitroModules } from 'react-native-nitro-modules';
@@ -58,6 +62,11 @@ import {
58
62
  Ed,
59
63
  } from './ed';
60
64
  import { mldsa_generateKeyPairWebCrypto, type MlDsaVariant } from './mldsa';
65
+ import {
66
+ slhdsa_generateKeyPairWebCrypto,
67
+ SLH_DSA_VARIANTS,
68
+ type SlhDsaVariant,
69
+ } from './slhdsa';
61
70
  import {
62
71
  mlkem_generateKeyPairWebCrypto,
63
72
  type MlKemVariant,
@@ -83,6 +92,18 @@ function hasAnyNotIn(usages: KeyUsage[], allowed: KeyUsage[]): boolean {
83
92
  return usages.some(usage => !allowed.includes(usage));
84
93
  }
85
94
 
95
+ // Mirrors webidl.requiredArguments. Node throws TypeError when a SubtleCrypto
96
+ // method is called with fewer than the spec-required number of arguments
97
+ // (webcrypto.js:866 etc.); we relied on TypeScript types alone, which apps
98
+ // catching `instanceof TypeError` could not see at runtime.
99
+ function requireArgs(actual: number, required: number, method: string): void {
100
+ if (actual < required) {
101
+ throw new TypeError(
102
+ `Failed to execute '${method}' on 'SubtleCrypto': ${required} arguments required, but only ${actual} present.`,
103
+ );
104
+ }
105
+ }
106
+
86
107
  // WebCrypto §18.4.4: algorithm name lookup is case-insensitive, but the
87
108
  // canonical mixed-case form is preserved in the resulting `name` field
88
109
  // (e.g. "aes-gcm" → "AES-GCM"). This map is built lazily on first call so
@@ -124,37 +145,6 @@ function normalizeAlgorithm(
124
145
  return algorithm as SubtleAlgorithm;
125
146
  }
126
147
 
127
- // WebCrypto §25.7.6 (JWK import): if the JWK's `ext` member is present and
128
- // false, the requested `extractable` parameter must also be false. If the
129
- // JWK's `key_ops` member is present, every requested usage must appear in
130
- // it. We centralize the check here so every importKey path that accepts
131
- // `format === 'jwk'` can reuse it.
132
- function validateJwkExtAndKeyOps(
133
- jwk: JWK,
134
- extractable: boolean,
135
- keyUsages: KeyUsage[],
136
- ): void {
137
- if (jwk.ext === false && extractable) {
138
- throw lazyDOMException(
139
- 'JWK "ext" is false but extractable was requested',
140
- 'DataError',
141
- );
142
- }
143
- if (jwk.key_ops !== undefined) {
144
- if (!Array.isArray(jwk.key_ops)) {
145
- throw lazyDOMException('JWK "key_ops" must be an array', 'DataError');
146
- }
147
- for (const usage of keyUsages) {
148
- if (!jwk.key_ops.includes(usage)) {
149
- throw lazyDOMException(
150
- `JWK "key_ops" does not include requested usage "${usage}"`,
151
- 'DataError',
152
- );
153
- }
154
- }
155
- }
156
- }
157
-
158
148
  function getAlgorithmName(name: string, length: number): string {
159
149
  switch (name) {
160
150
  case 'AES-CBC':
@@ -174,15 +164,70 @@ function getAlgorithmName(name: string, length: number): string {
174
164
  }
175
165
  }
176
166
 
177
- // Placeholder implementations for missing functions
167
+ // Mirrors Node's aliasKeyFormat (lib/internal/crypto/webcrypto.js): for
168
+ // algorithms whose import/export accepts both 'raw' and the disambiguated
169
+ // 'raw-secret' / 'raw-public', collapse the latter to 'raw'. Used per-algorithm
170
+ // — algorithms that demand the disambiguated form (KMAC, AES-OCB,
171
+ // ChaCha20-Poly1305, Argon2*, ML-DSA, ML-KEM) MUST NOT alias.
172
+ function aliasKeyFormat(format: ImportFormat): ImportFormat {
173
+ if (format === 'raw-secret' || format === 'raw-public') return 'raw';
174
+ return format;
175
+ }
176
+
177
+ const kUncompressedSpkiLength: Record<string, number> = {
178
+ 'P-256': 91,
179
+ 'P-384': 120,
180
+ 'P-521': 158,
181
+ };
182
+
178
183
  function ecExportKey(key: CryptoKey, format: KWebCryptoKeyFormat): ArrayBuffer {
179
184
  const keyObject = key.keyObject;
180
185
 
181
186
  if (format === KWebCryptoKeyFormat.kWebCryptoKeyFormatRaw) {
182
187
  return bufferLikeToArrayBuffer(keyObject.handle.exportKey());
183
188
  } else if (format === KWebCryptoKeyFormat.kWebCryptoKeyFormatSPKI) {
184
- const exported = keyObject.export({ format: 'der', type: 'spki' });
185
- return bufferLikeToArrayBuffer(exported);
189
+ const exported = bufferLikeToArrayBuffer(
190
+ keyObject.export({ format: 'der', type: 'spki' }),
191
+ );
192
+
193
+ // WebCrypto requires uncompressed point format for SPKI exports.
194
+ // If the key was imported in compressed form, re-export as uncompressed
195
+ // by reconstructing the point from the JWK x,y coordinates and
196
+ // round-tripping through initECRaw.
197
+ const namedCurve = key.algorithm.namedCurve;
198
+ const expected =
199
+ namedCurve === undefined
200
+ ? undefined
201
+ : kUncompressedSpkiLength[namedCurve];
202
+ if (expected !== undefined && exported.byteLength !== expected) {
203
+ const jwk = keyObject.handle.exportJwk({}, false);
204
+ if (!jwk.x || !jwk.y) {
205
+ throw lazyDOMException(
206
+ 'Failed to re-export EC public key as uncompressed SPKI',
207
+ 'OperationError',
208
+ );
209
+ }
210
+ const x = Buffer.from(jwk.x, 'base64url');
211
+ const y = Buffer.from(jwk.y, 'base64url');
212
+ const raw = new Uint8Array(1 + x.length + y.length);
213
+ raw[0] = 0x04;
214
+ raw.set(x, 1);
215
+ raw.set(y, 1 + x.length);
216
+ const tmp =
217
+ NitroModules.createHybridObject<KeyObjectHandle>('KeyObjectHandle');
218
+ const curveAlias =
219
+ kNamedCurveAliases[namedCurve as keyof typeof kNamedCurveAliases];
220
+ if (!tmp.initECRaw(curveAlias, raw.buffer as ArrayBuffer)) {
221
+ throw lazyDOMException(
222
+ 'Failed to re-export EC public key as uncompressed SPKI',
223
+ 'OperationError',
224
+ );
225
+ }
226
+ return bufferLikeToArrayBuffer(
227
+ tmp.exportKey(KFormatType.DER, KeyEncoding.SPKI),
228
+ );
229
+ }
230
+ return exported;
186
231
  } else if (format === KWebCryptoKeyFormat.kWebCryptoKeyFormatPKCS8) {
187
232
  const exported = keyObject.export({ format: 'der', type: 'pkcs8' });
188
233
  return bufferLikeToArrayBuffer(exported);
@@ -811,13 +856,22 @@ function kmacSignVerify(
811
856
  ): ArrayBuffer | boolean {
812
857
  const { name } = algorithm;
813
858
 
814
- const defaultLength = name === 'KMAC128' ? 256 : 512;
815
- const outputLengthBits = algorithm.length ?? defaultLength;
859
+ // KmacParams.outputLength is required per
860
+ // https://wicg.github.io/webcrypto-modern-algos/#KmacParams-dictionary
861
+ // and the rename from `length` (commit ab8dc2b84c2). Mirror Node's
862
+ // mac.js:213-223 by reading `outputLength` (in bits).
863
+ if (typeof algorithm.outputLength !== 'number') {
864
+ throw lazyDOMException(
865
+ `${name}Params.outputLength is required`,
866
+ 'OperationError',
867
+ );
868
+ }
869
+ const outputLengthBits = algorithm.outputLength;
816
870
 
817
871
  if (outputLengthBits % 8 !== 0) {
818
872
  throw lazyDOMException(
819
- 'KMAC output length must be a multiple of 8',
820
- 'OperationError',
873
+ `Unsupported ${name}Params outputLength`,
874
+ 'NotSupportedError',
821
875
  );
822
876
  }
823
877
 
@@ -862,13 +916,6 @@ async function kmacImportKey(
862
916
  ): Promise<CryptoKey> {
863
917
  const { name } = algorithm;
864
918
 
865
- if (hasAnyNotIn(keyUsages, ['sign', 'verify'])) {
866
- throw lazyDOMException(
867
- `Unsupported key usage for ${name} key`,
868
- 'SyntaxError',
869
- );
870
- }
871
-
872
919
  let keyObject: KeyObject;
873
920
 
874
921
  if (format === 'jwk') {
@@ -877,31 +924,51 @@ async function kmacImportKey(
877
924
  if (!jwk || typeof jwk !== 'object') {
878
925
  throw lazyDOMException('Invalid keyData', 'DataError');
879
926
  }
880
-
881
- validateJwkExtAndKeyOps(jwk, extractable, keyUsages);
882
-
883
927
  if (jwk.kty !== 'oct') {
884
- throw lazyDOMException('Invalid JWK format for KMAC key', 'DataError');
928
+ throw lazyDOMException('Invalid JWK "kty" Parameter', 'DataError');
885
929
  }
930
+ validateJwkStructure(jwk, extractable, keyUsages, 'sig');
886
931
 
887
932
  const expectedAlg = name === 'KMAC128' ? 'K128' : 'K256';
888
933
  if (jwk.alg !== undefined && jwk.alg !== expectedAlg) {
889
934
  throw lazyDOMException(
890
- 'JWK "alg" does not match the requested algorithm',
935
+ 'JWK "alg" Parameter and algorithm name mismatch',
891
936
  'DataError',
892
937
  );
893
938
  }
894
939
 
940
+ if (hasAnyNotIn(keyUsages, ['sign', 'verify'])) {
941
+ throw lazyDOMException(
942
+ `Unsupported key usage for ${name} key`,
943
+ 'SyntaxError',
944
+ );
945
+ }
946
+
895
947
  const handle =
896
948
  NitroModules.createHybridObject<KeyObjectHandle>('KeyObjectHandle');
897
- const keyType = handle.initJwk(jwk, undefined);
898
-
949
+ let keyType: KeyType | undefined;
950
+ try {
951
+ keyType = handle.initJwk(jwk, undefined);
952
+ } catch (err) {
953
+ throw lazyDOMException('Invalid keyData', {
954
+ name: 'DataError',
955
+ cause: err,
956
+ });
957
+ }
899
958
  if (keyType === undefined || keyType !== 0) {
900
- throw lazyDOMException('Failed to import KMAC JWK', 'DataError');
959
+ throw lazyDOMException('Invalid keyData', 'DataError');
901
960
  }
902
961
 
903
962
  keyObject = new SecretKeyObject(handle);
904
- } else if (format === 'raw' || format === 'raw-secret') {
963
+ } else if (format === 'raw-secret') {
964
+ // KMAC accepts only the disambiguated 'raw-secret' form (Node mac.js:141-145
965
+ // returns undefined for plain 'raw' when not HMAC).
966
+ if (hasAnyNotIn(keyUsages, ['sign', 'verify'])) {
967
+ throw lazyDOMException(
968
+ `Unsupported key usage for ${name} key`,
969
+ 'SyntaxError',
970
+ );
971
+ }
905
972
  keyObject = createSecretKey(data as BinaryLike);
906
973
  } else {
907
974
  throw lazyDOMException(
@@ -938,7 +1005,6 @@ function rsaImportKey(
938
1005
  ): CryptoKey {
939
1006
  const { name } = algorithm;
940
1007
 
941
- // Validate usages
942
1008
  let checkSet: KeyUsage[];
943
1009
  switch (name) {
944
1010
  case 'RSASSA-PKCS1-v1_5':
@@ -951,40 +1017,75 @@ function rsaImportKey(
951
1017
  default:
952
1018
  throw new Error(`Unsupported RSA algorithm: ${name}`);
953
1019
  }
954
-
955
- if (hasAnyNotIn(keyUsages, checkSet)) {
956
- throw new Error(`Unsupported key usage for ${name}`);
957
- }
1020
+ const checkUsages = (): void => {
1021
+ if (hasAnyNotIn(keyUsages, checkSet)) {
1022
+ throw lazyDOMException(
1023
+ `Unsupported key usage for ${name} key`,
1024
+ 'SyntaxError',
1025
+ );
1026
+ }
1027
+ };
958
1028
 
959
1029
  let keyObject: KeyObject;
960
1030
 
961
1031
  if (format === 'jwk') {
962
1032
  const jwk = data as JWK;
963
1033
 
964
- // Validate JWK
1034
+ if (!jwk || typeof jwk !== 'object') {
1035
+ throw lazyDOMException('Invalid keyData', 'DataError');
1036
+ }
965
1037
  if (jwk.kty !== 'RSA') {
966
- throw new Error('Invalid JWK format for RSA key');
1038
+ throw lazyDOMException('Invalid JWK "kty" Parameter', 'DataError');
1039
+ }
1040
+ const expectedUse = name === 'RSA-OAEP' ? 'enc' : 'sig';
1041
+ validateJwkStructure(jwk, extractable, keyUsages, expectedUse);
1042
+ checkUsages();
1043
+
1044
+ if (jwk.alg !== undefined) {
1045
+ let jwkContext: HashContext;
1046
+ switch (name) {
1047
+ case 'RSASSA-PKCS1-v1_5':
1048
+ jwkContext = HashContext.JwkRsa;
1049
+ break;
1050
+ case 'RSA-PSS':
1051
+ jwkContext = HashContext.JwkRsaPss;
1052
+ break;
1053
+ default:
1054
+ jwkContext = HashContext.JwkRsaOaep;
1055
+ }
1056
+ const expectedAlg = normalizeHashName(algorithm.hash, jwkContext);
1057
+ if (jwk.alg !== expectedAlg) {
1058
+ throw lazyDOMException(
1059
+ 'JWK "alg" does not match the requested algorithm',
1060
+ 'DataError',
1061
+ );
1062
+ }
967
1063
  }
968
-
969
- validateJwkExtAndKeyOps(jwk, extractable, keyUsages);
970
1064
 
971
1065
  const handle =
972
1066
  NitroModules.createHybridObject<KeyObjectHandle>('KeyObjectHandle');
973
- const keyType = handle.initJwk(jwk, undefined);
974
-
1067
+ let keyType: KeyType | undefined;
1068
+ try {
1069
+ keyType = handle.initJwk(jwk, undefined);
1070
+ } catch (err) {
1071
+ throw lazyDOMException('Invalid keyData', {
1072
+ name: 'DataError',
1073
+ cause: err,
1074
+ });
1075
+ }
975
1076
  if (keyType === undefined) {
976
- throw new Error('Failed to import RSA JWK');
1077
+ throw lazyDOMException('Invalid keyData', 'DataError');
977
1078
  }
978
1079
 
979
- // Create the appropriate KeyObject based on type
980
- if (keyType === 1) {
1080
+ if (keyType === KeyType.PUBLIC) {
981
1081
  keyObject = new PublicKeyObject(handle);
982
- } else if (keyType === 2) {
1082
+ } else if (keyType === KeyType.PRIVATE) {
983
1083
  keyObject = new PrivateKeyObject(handle);
984
1084
  } else {
985
- throw new Error('Unexpected key type from RSA JWK import');
1085
+ throw lazyDOMException('Invalid keyData', 'DataError');
986
1086
  }
987
1087
  } else if (format === 'spki') {
1088
+ checkUsages();
988
1089
  const keyData = bufferLikeToArrayBuffer(data as BufferLike);
989
1090
  keyObject = KeyObject.createKeyObject(
990
1091
  'public',
@@ -993,6 +1094,7 @@ function rsaImportKey(
993
1094
  KeyEncoding.SPKI,
994
1095
  );
995
1096
  } else if (format === 'pkcs8') {
1097
+ checkUsages();
996
1098
  const keyData = bufferLikeToArrayBuffer(data as BufferLike);
997
1099
  keyObject = KeyObject.createKeyObject(
998
1100
  'private',
@@ -1001,7 +1103,10 @@ function rsaImportKey(
1001
1103
  KeyEncoding.PKCS8,
1002
1104
  );
1003
1105
  } else {
1004
- throw new Error(`Unsupported format for RSA import: ${format}`);
1106
+ throw lazyDOMException(
1107
+ `Unsupported format for ${name} import: ${format}`,
1108
+ 'NotSupportedError',
1109
+ );
1005
1110
  }
1006
1111
 
1007
1112
  // Get the modulus length from the key and add it to the algorithm
@@ -1043,33 +1148,30 @@ async function hmacImportKey(
1043
1148
  extractable: boolean,
1044
1149
  keyUsages: KeyUsage[],
1045
1150
  ): Promise<CryptoKey> {
1046
- // Validate usages
1047
- if (hasAnyNotIn(keyUsages, ['sign', 'verify'])) {
1048
- throw new Error('Unsupported key usage for an HMAC key');
1049
- }
1151
+ const checkUsages = (): void => {
1152
+ if (hasAnyNotIn(keyUsages, ['sign', 'verify'])) {
1153
+ throw new Error('Unsupported key usage for an HMAC key');
1154
+ }
1155
+ };
1050
1156
 
1051
1157
  let keyObject: KeyObject;
1052
1158
 
1053
1159
  if (format === 'jwk') {
1054
1160
  const jwk = data as JWK;
1055
1161
 
1056
- // Validate JWK
1057
1162
  if (!jwk || typeof jwk !== 'object') {
1058
1163
  throw new Error('Invalid keyData');
1059
1164
  }
1060
-
1061
- validateJwkExtAndKeyOps(jwk, extractable, keyUsages);
1062
-
1063
1165
  if (jwk.kty !== 'oct') {
1064
1166
  throw new Error('Invalid JWK format for HMAC key');
1065
1167
  }
1168
+ validateJwkStructure(jwk, extractable, keyUsages, 'sig');
1169
+ checkUsages();
1066
1170
 
1067
- // Validate key length if specified
1068
1171
  if (algorithm.length !== undefined) {
1069
1172
  if (!jwk.k) {
1070
1173
  throw new Error('JWK missing key data');
1071
1174
  }
1072
- // Decode to check length
1073
1175
  const decoded = SBuffer.from(jwk.k, 'base64');
1074
1176
  const keyBitLength = decoded.length * 8;
1075
1177
  if (algorithm.length === 0) {
@@ -1082,17 +1184,29 @@ async function hmacImportKey(
1082
1184
 
1083
1185
  const handle =
1084
1186
  NitroModules.createHybridObject<KeyObjectHandle>('KeyObjectHandle');
1085
- const keyType = handle.initJwk(jwk, undefined);
1086
-
1187
+ let keyType: KeyType | undefined;
1188
+ try {
1189
+ keyType = handle.initJwk(jwk, undefined);
1190
+ } catch (err) {
1191
+ throw lazyDOMException('Invalid keyData', {
1192
+ name: 'DataError',
1193
+ cause: err,
1194
+ });
1195
+ }
1087
1196
  if (keyType === undefined || keyType !== 0) {
1088
- throw new Error('Failed to import HMAC JWK');
1197
+ throw lazyDOMException('Invalid keyData', 'DataError');
1089
1198
  }
1090
1199
 
1091
1200
  keyObject = new SecretKeyObject(handle);
1092
- } else if (format === 'raw') {
1201
+ } else if (format === 'raw' || format === 'raw-secret') {
1202
+ // HMAC accepts both 'raw' and 'raw-secret' (Node mac.js:141-145).
1203
+ checkUsages();
1093
1204
  keyObject = createSecretKey(data as BinaryLike);
1094
1205
  } else {
1095
- throw new Error(`Unable to import HMAC key with format ${format}`);
1206
+ throw lazyDOMException(
1207
+ `Unable to import HMAC key with format ${format}`,
1208
+ 'NotSupportedError',
1209
+ );
1096
1210
  }
1097
1211
 
1098
1212
  // Normalize hash to { name: string } format per WebCrypto spec
@@ -1115,16 +1229,24 @@ async function aesImportKey(
1115
1229
  ): Promise<CryptoKey> {
1116
1230
  const { name, length } = algorithm;
1117
1231
 
1118
- // Validate usages
1119
1232
  const validUsages: KeyUsage[] = [
1120
1233
  'encrypt',
1121
1234
  'decrypt',
1122
1235
  'wrapKey',
1123
1236
  'unwrapKey',
1124
1237
  ];
1125
- if (hasAnyNotIn(keyUsages, validUsages)) {
1126
- throw new Error(`Unsupported key usage for ${name}`);
1127
- }
1238
+ const checkUsages = (): void => {
1239
+ if (hasAnyNotIn(keyUsages, validUsages)) {
1240
+ throw new Error(`Unsupported key usage for ${name}`);
1241
+ }
1242
+ };
1243
+
1244
+ // AES-OCB and ChaCha20-Poly1305 require the disambiguated 'raw-secret' form
1245
+ // and reject 'raw' (Node aes.js:243-249, chacha20_poly1305.js:104-134).
1246
+ // Other AES variants accept both 'raw' and 'raw-secret'.
1247
+ const requiresRawSecret = name === 'AES-OCB' || name === 'ChaCha20-Poly1305';
1248
+ const acceptsRaw =
1249
+ format === 'raw-secret' || (format === 'raw' && !requiresRawSecret);
1128
1250
 
1129
1251
  let keyObject: KeyObject;
1130
1252
  let actualLength: number;
@@ -1132,38 +1254,53 @@ async function aesImportKey(
1132
1254
  if (format === 'jwk') {
1133
1255
  const jwk = data as JWK;
1134
1256
 
1135
- // Validate JWK
1136
1257
  if (jwk.kty !== 'oct') {
1137
1258
  throw new Error('Invalid JWK format for AES key');
1138
1259
  }
1139
-
1140
- validateJwkExtAndKeyOps(jwk, extractable, keyUsages);
1260
+ validateJwkStructure(jwk, extractable, keyUsages, 'enc');
1261
+ checkUsages();
1141
1262
 
1142
1263
  const handle =
1143
1264
  NitroModules.createHybridObject<KeyObjectHandle>('KeyObjectHandle');
1144
- const keyType = handle.initJwk(jwk, undefined);
1145
-
1265
+ let keyType: KeyType | undefined;
1266
+ try {
1267
+ keyType = handle.initJwk(jwk, undefined);
1268
+ } catch (err) {
1269
+ throw lazyDOMException('Invalid keyData', {
1270
+ name: 'DataError',
1271
+ cause: err,
1272
+ });
1273
+ }
1146
1274
  if (keyType === undefined || keyType !== 0) {
1147
- throw new Error('Failed to import AES JWK');
1275
+ throw lazyDOMException('Invalid keyData', 'DataError');
1148
1276
  }
1149
1277
 
1150
1278
  keyObject = new SecretKeyObject(handle);
1151
1279
 
1152
- // Get actual key length from imported key
1153
1280
  const exported = keyObject.export();
1154
1281
  actualLength = exported.byteLength * 8;
1155
- } else if (format === 'raw') {
1282
+ } else if (acceptsRaw) {
1283
+ checkUsages();
1156
1284
  const keyData = bufferLikeToArrayBuffer(data as BufferLike);
1157
1285
  actualLength = keyData.byteLength * 8;
1158
1286
 
1159
- // Validate key length
1160
- if (![128, 192, 256].includes(actualLength)) {
1287
+ if (name === 'ChaCha20-Poly1305') {
1288
+ if (actualLength !== 256) {
1289
+ throw lazyDOMException(
1290
+ 'Invalid ChaCha20-Poly1305 key length',
1291
+ 'DataError',
1292
+ );
1293
+ }
1294
+ } else if (![128, 192, 256].includes(actualLength)) {
1161
1295
  throw new Error('Invalid AES key length');
1162
1296
  }
1163
1297
 
1164
1298
  keyObject = createSecretKey(keyData);
1165
1299
  } else {
1166
- throw new Error(`Unsupported format for AES import: ${format}`);
1300
+ throw lazyDOMException(
1301
+ `Unable to import ${name} key with format ${format}`,
1302
+ 'NotSupportedError',
1303
+ );
1167
1304
  }
1168
1305
 
1169
1306
  // Validate length if specified
@@ -1190,23 +1327,23 @@ function edImportKey(
1190
1327
  ): CryptoKey {
1191
1328
  const { name } = algorithm;
1192
1329
 
1193
- // Validate usages
1194
1330
  const isX = name === 'X25519' || name === 'X448';
1195
1331
  const allowedUsages: KeyUsage[] = isX
1196
1332
  ? ['deriveKey', 'deriveBits']
1197
1333
  : ['sign', 'verify'];
1198
-
1199
- if (hasAnyNotIn(keyUsages, allowedUsages)) {
1200
- throw lazyDOMException(
1201
- `Unsupported key usage for ${name} key`,
1202
- 'SyntaxError',
1203
- );
1204
- }
1334
+ const checkUsages = (): void => {
1335
+ if (hasAnyNotIn(keyUsages, allowedUsages)) {
1336
+ throw lazyDOMException(
1337
+ `Unsupported key usage for ${name} key`,
1338
+ 'SyntaxError',
1339
+ );
1340
+ }
1341
+ };
1205
1342
 
1206
1343
  let keyObject: KeyObject;
1207
1344
 
1208
1345
  if (format === 'spki') {
1209
- // Import public key
1346
+ checkUsages();
1210
1347
  const keyData = bufferLikeToArrayBuffer(data as BufferLike);
1211
1348
  keyObject = KeyObject.createKeyObject(
1212
1349
  'public',
@@ -1215,7 +1352,7 @@ function edImportKey(
1215
1352
  KeyEncoding.SPKI,
1216
1353
  );
1217
1354
  } else if (format === 'pkcs8') {
1218
- // Import private key
1355
+ checkUsages();
1219
1356
  const keyData = bufferLikeToArrayBuffer(data as BufferLike);
1220
1357
  keyObject = KeyObject.createKeyObject(
1221
1358
  'private',
@@ -1224,20 +1361,51 @@ function edImportKey(
1224
1361
  KeyEncoding.PKCS8,
1225
1362
  );
1226
1363
  } else if (format === 'raw') {
1227
- // Raw format - public key only for Ed keys
1364
+ checkUsages();
1228
1365
  const keyData = bufferLikeToArrayBuffer(data as BufferLike);
1229
1366
  const handle =
1230
1367
  NitroModules.createHybridObject<KeyObjectHandle>('KeyObjectHandle');
1231
- // For raw Ed keys, we need to create them differently
1232
- // Raw public keys are just the key bytes
1233
- handle.init(1, keyData); // 1 = public key type
1368
+ handle.init(1, keyData);
1234
1369
  keyObject = new PublicKeyObject(handle);
1235
1370
  } else if (format === 'jwk') {
1236
1371
  const jwkData = data as JWK;
1237
- validateJwkExtAndKeyOps(jwkData, extractable, keyUsages);
1372
+ if (!jwkData || typeof jwkData !== 'object') {
1373
+ throw lazyDOMException('Invalid keyData', 'DataError');
1374
+ }
1375
+ if (jwkData.kty !== 'OKP') {
1376
+ throw lazyDOMException('Invalid JWK "kty" Parameter', 'DataError');
1377
+ }
1378
+ const expectedUse = isX ? 'enc' : 'sig';
1379
+ validateJwkStructure(jwkData, extractable, keyUsages, expectedUse);
1380
+
1381
+ if (jwkData.crv !== name) {
1382
+ throw lazyDOMException(
1383
+ 'JWK "crv" Parameter and algorithm name mismatch',
1384
+ 'DataError',
1385
+ );
1386
+ }
1387
+
1388
+ if (!isX && jwkData.alg !== undefined) {
1389
+ if (jwkData.alg !== name && jwkData.alg !== 'EdDSA') {
1390
+ throw lazyDOMException(
1391
+ 'JWK "alg" does not match the requested algorithm',
1392
+ 'DataError',
1393
+ );
1394
+ }
1395
+ }
1396
+
1397
+ checkUsages();
1238
1398
  const handle =
1239
1399
  NitroModules.createHybridObject<KeyObjectHandle>('KeyObjectHandle');
1240
- const keyType = handle.initJwk(jwkData);
1400
+ let keyType: KeyType | undefined;
1401
+ try {
1402
+ keyType = handle.initJwk(jwkData);
1403
+ } catch (err) {
1404
+ throw lazyDOMException('Invalid JWK data', {
1405
+ name: 'DataError',
1406
+ cause: err,
1407
+ });
1408
+ }
1241
1409
  if (keyType === undefined) {
1242
1410
  throw lazyDOMException('Invalid JWK data', 'DataError');
1243
1411
  }
@@ -1256,42 +1424,133 @@ function edImportKey(
1256
1424
  return new CryptoKey(keyObject, { name }, keyUsages, extractable);
1257
1425
  }
1258
1426
 
1427
+ // Lengths (in bytes) of seedless ML-DSA / ML-KEM PKCS#8 encodings. A PKCS#8
1428
+ // blob of exactly this length contains only the expanded private key with no
1429
+ // seed; Node rejects these to keep cross-implementation interop intact.
1430
+ // Refs: node lib/internal/crypto/ml_dsa.js (mlDsaImportKey, pkcs8 case)
1431
+ // node lib/internal/crypto/ml_kem.js (mlKemImportKey, pkcs8 case)
1432
+ export const PQC_SEEDLESS_PKCS8_LENGTHS: Readonly<Record<string, number>> = {
1433
+ 'ML-DSA-44': 2588,
1434
+ 'ML-DSA-65': 4060,
1435
+ 'ML-DSA-87': 4924,
1436
+ 'ML-KEM-512': 1660,
1437
+ 'ML-KEM-768': 2428,
1438
+ 'ML-KEM-1024': 3196,
1439
+ };
1440
+
1441
+ // Map from PQC algorithm name to display family. Used to render the
1442
+ // import-rejection error message in the same form Node emits.
1443
+ const PQC_FAMILY: Readonly<Record<string, 'ML-DSA' | 'ML-KEM' | 'SLH-DSA'>> = {
1444
+ 'ML-DSA-44': 'ML-DSA',
1445
+ 'ML-DSA-65': 'ML-DSA',
1446
+ 'ML-DSA-87': 'ML-DSA',
1447
+ 'ML-KEM-512': 'ML-KEM',
1448
+ 'ML-KEM-768': 'ML-KEM',
1449
+ 'ML-KEM-1024': 'ML-KEM',
1450
+ 'SLH-DSA-SHA2-128s': 'SLH-DSA',
1451
+ 'SLH-DSA-SHA2-128f': 'SLH-DSA',
1452
+ 'SLH-DSA-SHA2-192s': 'SLH-DSA',
1453
+ 'SLH-DSA-SHA2-192f': 'SLH-DSA',
1454
+ 'SLH-DSA-SHA2-256s': 'SLH-DSA',
1455
+ 'SLH-DSA-SHA2-256f': 'SLH-DSA',
1456
+ 'SLH-DSA-SHAKE-128s': 'SLH-DSA',
1457
+ 'SLH-DSA-SHAKE-128f': 'SLH-DSA',
1458
+ 'SLH-DSA-SHAKE-192s': 'SLH-DSA',
1459
+ 'SLH-DSA-SHAKE-192f': 'SLH-DSA',
1460
+ 'SLH-DSA-SHAKE-256s': 'SLH-DSA',
1461
+ 'SLH-DSA-SHAKE-256f': 'SLH-DSA',
1462
+ };
1463
+
1259
1464
  function pqcImportKeyObject(
1260
1465
  format: ImportFormat,
1261
- data: BufferLike,
1466
+ data: BufferLike | JWK,
1262
1467
  name: string,
1263
- ): KeyObject {
1468
+ ): { keyObject: KeyObject; isPublic: boolean } {
1264
1469
  if (format === 'spki') {
1265
- return KeyObject.createKeyObject(
1266
- 'public',
1267
- bufferLikeToArrayBuffer(data),
1268
- KFormatType.DER,
1269
- KeyEncoding.SPKI,
1270
- );
1470
+ return {
1471
+ keyObject: KeyObject.createKeyObject(
1472
+ 'public',
1473
+ bufferLikeToArrayBuffer(data as BufferLike),
1474
+ KFormatType.DER,
1475
+ KeyEncoding.SPKI,
1476
+ ),
1477
+ isPublic: true,
1478
+ };
1271
1479
  } else if (format === 'pkcs8') {
1272
- return KeyObject.createKeyObject(
1273
- 'private',
1274
- bufferLikeToArrayBuffer(data),
1275
- KFormatType.DER,
1276
- KeyEncoding.PKCS8,
1277
- );
1278
- } else if (format === 'raw') {
1480
+ const ab = bufferLikeToArrayBuffer(data as BufferLike);
1481
+ const family = PQC_FAMILY[name];
1482
+ if (
1483
+ family !== undefined &&
1484
+ ab.byteLength === PQC_SEEDLESS_PKCS8_LENGTHS[name]
1485
+ ) {
1486
+ throw lazyDOMException(
1487
+ `Importing an ${family} PKCS#8 key without a seed is not supported`,
1488
+ 'NotSupportedError',
1489
+ );
1490
+ }
1491
+ return {
1492
+ keyObject: KeyObject.createKeyObject(
1493
+ 'private',
1494
+ ab,
1495
+ KFormatType.DER,
1496
+ KeyEncoding.PKCS8,
1497
+ ),
1498
+ isPublic: false,
1499
+ };
1500
+ } else if (format === 'raw-public') {
1501
+ // ML-DSA / ML-KEM reject plain 'raw' — only 'raw-public' is accepted for
1502
+ // public-key import (Node webcrypto.js:493-499, 506-511).
1279
1503
  const handle =
1280
1504
  NitroModules.createHybridObject<KeyObjectHandle>('KeyObjectHandle');
1281
- if (!handle.initPqcRaw(name, bufferLikeToArrayBuffer(data), true)) {
1505
+ if (
1506
+ !handle.initPqcRaw(
1507
+ name,
1508
+ bufferLikeToArrayBuffer(data as BufferLike),
1509
+ true,
1510
+ )
1511
+ ) {
1282
1512
  throw lazyDOMException(
1283
1513
  `Failed to import ${name} raw public key`,
1284
1514
  'DataError',
1285
1515
  );
1286
1516
  }
1287
- return new PublicKeyObject(handle);
1517
+ return { keyObject: new PublicKeyObject(handle), isPublic: true };
1288
1518
  } else if (format === 'raw-seed') {
1289
1519
  const handle =
1290
1520
  NitroModules.createHybridObject<KeyObjectHandle>('KeyObjectHandle');
1291
- if (!handle.initPqcRaw(name, bufferLikeToArrayBuffer(data), false)) {
1521
+ if (
1522
+ !handle.initPqcRaw(
1523
+ name,
1524
+ bufferLikeToArrayBuffer(data as BufferLike),
1525
+ false,
1526
+ )
1527
+ ) {
1292
1528
  throw lazyDOMException(`Failed to import ${name} raw seed`, 'DataError');
1293
1529
  }
1294
- return new PrivateKeyObject(handle);
1530
+ return { keyObject: new PrivateKeyObject(handle), isPublic: false };
1531
+ } else if (format === 'jwk') {
1532
+ const jwkData = data as JWK;
1533
+ const isPublic = jwkData.priv === undefined;
1534
+ const handle =
1535
+ NitroModules.createHybridObject<KeyObjectHandle>('KeyObjectHandle');
1536
+ let keyType: KeyType | undefined;
1537
+ try {
1538
+ keyType = handle.initJwk(jwkData);
1539
+ } catch (err) {
1540
+ throw lazyDOMException('Invalid JWK data', {
1541
+ name: 'DataError',
1542
+ cause: err,
1543
+ });
1544
+ }
1545
+ if (keyType === undefined) {
1546
+ throw lazyDOMException('Invalid JWK data', 'DataError');
1547
+ }
1548
+ return {
1549
+ keyObject: isPublic
1550
+ ? new PublicKeyObject(handle)
1551
+ : new PrivateKeyObject(handle),
1552
+ isPublic,
1553
+ };
1295
1554
  }
1296
1555
  throw lazyDOMException(
1297
1556
  `Unsupported format for ${name} import: ${format}`,
@@ -1299,39 +1558,109 @@ function pqcImportKeyObject(
1299
1558
  );
1300
1559
  }
1301
1560
 
1561
+ // Per WebCrypto AKP JWK rules, public-vs-private is determined by the presence
1562
+ // of `priv`. For binary formats it follows from the format itself.
1563
+ function pqcIsPublicImport(
1564
+ format: ImportFormat,
1565
+ data: BufferLike | JWK,
1566
+ ): boolean {
1567
+ if (format === 'jwk') {
1568
+ return (
1569
+ typeof data === 'object' &&
1570
+ data !== null &&
1571
+ (data as JWK).priv === undefined
1572
+ );
1573
+ }
1574
+ return format === 'spki' || format === 'raw-public';
1575
+ }
1576
+
1577
+ function validatePqcJwk(
1578
+ data: BufferLike | JWK,
1579
+ name: string,
1580
+ extractable: boolean,
1581
+ keyUsages: KeyUsage[],
1582
+ expectedUse: 'sig' | 'enc',
1583
+ ): void {
1584
+ if (typeof data !== 'object' || data === null) {
1585
+ throw lazyDOMException('Invalid keyData', 'DataError');
1586
+ }
1587
+ const jwk = data as JWK;
1588
+ if (jwk.kty !== 'AKP') {
1589
+ throw lazyDOMException('Invalid JWK "kty" Parameter', 'DataError');
1590
+ }
1591
+ validateJwkStructure(jwk, extractable, keyUsages, expectedUse);
1592
+ if (jwk.alg !== name) {
1593
+ throw lazyDOMException(
1594
+ 'JWK "alg" Parameter and algorithm name mismatch',
1595
+ 'DataError',
1596
+ );
1597
+ }
1598
+ }
1599
+
1600
+ // Validates that `format` is one of the formats PQC algorithms accept; rejects
1601
+ // plain 'raw' early so the format error wins over usage-based errors.
1602
+ function validatePqcFormat(format: ImportFormat, name: string): void {
1603
+ if (
1604
+ format !== 'spki' &&
1605
+ format !== 'pkcs8' &&
1606
+ format !== 'raw-public' &&
1607
+ format !== 'raw-seed' &&
1608
+ format !== 'jwk'
1609
+ ) {
1610
+ throw lazyDOMException(
1611
+ `Unsupported format for ${name} import: ${format}`,
1612
+ 'NotSupportedError',
1613
+ );
1614
+ }
1615
+ }
1616
+
1302
1617
  function mldsaImportKey(
1303
1618
  format: ImportFormat,
1304
- data: BufferLike,
1619
+ data: BufferLike | JWK,
1305
1620
  algorithm: SubtleAlgorithm,
1306
1621
  extractable: boolean,
1307
1622
  keyUsages: KeyUsage[],
1308
1623
  ): CryptoKey {
1309
1624
  const { name } = algorithm;
1310
- const isPublicFormat = format === 'spki' || format === 'raw';
1311
- if (hasAnyNotIn(keyUsages, isPublicFormat ? ['verify'] : ['sign'])) {
1625
+ validatePqcFormat(format, name);
1626
+ if (format === 'jwk') {
1627
+ validatePqcJwk(data, name, extractable, keyUsages, 'sig');
1628
+ }
1629
+ const isPublic = pqcIsPublicImport(format, data);
1630
+ if (hasAnyNotIn(keyUsages, isPublic ? ['verify'] : ['sign'])) {
1312
1631
  throw lazyDOMException(
1313
1632
  `Unsupported key usage for ${name} key`,
1314
1633
  'SyntaxError',
1315
1634
  );
1316
1635
  }
1317
- return new CryptoKey(
1318
- pqcImportKeyObject(format, data, name),
1319
- { name },
1320
- keyUsages,
1321
- extractable,
1322
- );
1636
+ const { keyObject } = pqcImportKeyObject(format, data, name);
1637
+ return new CryptoKey(keyObject, { name }, keyUsages, extractable);
1638
+ }
1639
+
1640
+ function slhdsaImportKey(
1641
+ format: ImportFormat,
1642
+ data: BufferLike | JWK,
1643
+ algorithm: SubtleAlgorithm,
1644
+ extractable: boolean,
1645
+ keyUsages: KeyUsage[],
1646
+ ): CryptoKey {
1647
+ return mldsaImportKey(format, data, algorithm, extractable, keyUsages);
1323
1648
  }
1324
1649
 
1325
1650
  function mlkemImportKey(
1326
1651
  format: ImportFormat,
1327
- data: BufferLike,
1652
+ data: BufferLike | JWK,
1328
1653
  algorithm: SubtleAlgorithm,
1329
1654
  extractable: boolean,
1330
1655
  keyUsages: KeyUsage[],
1331
1656
  ): CryptoKey {
1332
1657
  const { name } = algorithm;
1333
- const isPublicFormat = format === 'spki' || format === 'raw';
1334
- const allowedUsages: KeyUsage[] = isPublicFormat
1658
+ validatePqcFormat(format, name);
1659
+ if (format === 'jwk') {
1660
+ validatePqcJwk(data, name, extractable, keyUsages, 'enc');
1661
+ }
1662
+ const isPublic = pqcIsPublicImport(format, data);
1663
+ const allowedUsages: KeyUsage[] = isPublic
1335
1664
  ? ['encapsulateBits', 'encapsulateKey']
1336
1665
  : ['decapsulateBits', 'decapsulateKey'];
1337
1666
  if (hasAnyNotIn(keyUsages, allowedUsages)) {
@@ -1340,12 +1669,8 @@ function mlkemImportKey(
1340
1669
  'SyntaxError',
1341
1670
  );
1342
1671
  }
1343
- return new CryptoKey(
1344
- pqcImportKeyObject(format, data, name),
1345
- { name },
1346
- keyUsages,
1347
- extractable,
1348
- );
1672
+ const { keyObject } = pqcImportKeyObject(format, data, name);
1673
+ return new CryptoKey(keyObject, { name }, keyUsages, extractable);
1349
1674
  }
1350
1675
 
1351
1676
  const exportKeySpki = async (
@@ -1387,8 +1712,21 @@ const exportKeySpki = async (
1387
1712
  case 'ML-DSA-65':
1388
1713
  // Fall through
1389
1714
  case 'ML-DSA-87':
1715
+ // Fall through
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':
1390
1728
  if (key.type === 'public') {
1391
- // Export ML-DSA key in SPKI DER format
1729
+ // Export ML-DSA / SLH-DSA key in SPKI DER format
1392
1730
  return bufferLikeToArrayBuffer(
1393
1731
  key.keyObject.handle.exportKey(KFormatType.DER, KeyEncoding.SPKI),
1394
1732
  );
@@ -1451,18 +1789,40 @@ const exportKeyPkcs8 = async (
1451
1789
  case 'ML-DSA-65':
1452
1790
  // Fall through
1453
1791
  case 'ML-DSA-87':
1454
- if (key.type === 'private') {
1455
- // Export ML-DSA key in PKCS8 DER format
1456
- return bufferLikeToArrayBuffer(
1457
- key.keyObject.handle.exportKey(KFormatType.DER, KeyEncoding.PKCS8),
1458
- );
1459
- }
1460
- break;
1792
+ // Fall through
1461
1793
  case 'ML-KEM-512':
1462
1794
  // Fall through
1463
1795
  case 'ML-KEM-768':
1464
1796
  // Fall through
1465
1797
  case 'ML-KEM-1024':
1798
+ if (key.type === 'private') {
1799
+ const ab = bufferLikeToArrayBuffer(
1800
+ key.keyObject.handle.exportKey(KFormatType.DER, KeyEncoding.PKCS8),
1801
+ );
1802
+ // 22 bytes of PKCS#8 ASN.1 + seed (32 ML-DSA, 64 ML-KEM). Guards
1803
+ // against a seedless KeyObject that was wrapped via toCryptoKey.
1804
+ const expected = key.algorithm.name.startsWith('ML-DSA') ? 54 : 86;
1805
+ if (ab.byteLength !== expected) {
1806
+ throw lazyDOMException(
1807
+ 'The operation failed for an operation-specific reason',
1808
+ 'OperationError',
1809
+ );
1810
+ }
1811
+ return ab;
1812
+ }
1813
+ break;
1814
+ case 'SLH-DSA-SHA2-128s':
1815
+ case 'SLH-DSA-SHA2-128f':
1816
+ case 'SLH-DSA-SHA2-192s':
1817
+ case 'SLH-DSA-SHA2-192f':
1818
+ case 'SLH-DSA-SHA2-256s':
1819
+ case 'SLH-DSA-SHA2-256f':
1820
+ case 'SLH-DSA-SHAKE-128s':
1821
+ case 'SLH-DSA-SHAKE-128f':
1822
+ case 'SLH-DSA-SHAKE-192s':
1823
+ case 'SLH-DSA-SHAKE-192f':
1824
+ case 'SLH-DSA-SHAKE-256s':
1825
+ case 'SLH-DSA-SHAKE-256f':
1466
1826
  if (key.type === 'private') {
1467
1827
  return bufferLikeToArrayBuffer(
1468
1828
  key.keyObject.handle.exportKey(KFormatType.DER, KeyEncoding.PKCS8),
@@ -1476,79 +1836,101 @@ const exportKeyPkcs8 = async (
1476
1836
  );
1477
1837
  };
1478
1838
 
1479
- const exportKeyRaw = (key: CryptoKey): ArrayBuffer | unknown => {
1480
- switch (key.algorithm.name) {
1839
+ // Mirrors Node's export key matrix (lib/internal/crypto/webcrypto.js
1840
+ // exportKeyRawSecret / exportKeyRawPublic, lines 472-563):
1841
+ //
1842
+ // raw — AES-CTR/CBC/GCM/KW + HMAC (secret); ECDSA/ECDH/Ed/X (public)
1843
+ // raw-secret — AES-CTR/CBC/GCM/KW + HMAC + AES-OCB + KMAC + ChaCha20-Poly1305
1844
+ // raw-public — ECDSA/ECDH + Ed/X + ML-DSA + ML-KEM (public)
1845
+ const exportKeyRaw = (
1846
+ key: CryptoKey,
1847
+ format: 'raw' | 'raw-secret' | 'raw-public',
1848
+ ): ArrayBuffer => {
1849
+ const name = key.algorithm.name;
1850
+ const isPublic = key.type === 'public';
1851
+ const isSecret = key.type === 'secret';
1852
+
1853
+ const exportSecret = (): ArrayBuffer => {
1854
+ const exported = key.keyObject.export();
1855
+ return exported.buffer.slice(
1856
+ exported.byteOffset,
1857
+ exported.byteOffset + exported.byteLength,
1858
+ ) as ArrayBuffer;
1859
+ };
1860
+ const exportRawPublic = (): ArrayBuffer =>
1861
+ bufferLikeToArrayBuffer(key.keyObject.handle.exportKey());
1862
+
1863
+ const fail = (): never => {
1864
+ throw lazyDOMException(
1865
+ `Unable to export ${name} ${key.type} key using ${format} format`,
1866
+ 'NotSupportedError',
1867
+ );
1868
+ };
1869
+
1870
+ // Symmetric: AES-CTR/CBC/GCM/KW and HMAC accept both 'raw' and 'raw-secret';
1871
+ // AES-OCB / KMAC* / ChaCha20-Poly1305 only 'raw-secret'.
1872
+ switch (name) {
1873
+ case 'AES-CTR':
1874
+ case 'AES-CBC':
1875
+ case 'AES-GCM':
1876
+ case 'AES-KW':
1877
+ case 'HMAC':
1878
+ if (!isSecret) return fail();
1879
+ if (format === 'raw' || format === 'raw-secret') return exportSecret();
1880
+ return fail();
1881
+ case 'AES-OCB':
1882
+ case 'KMAC128':
1883
+ case 'KMAC256':
1884
+ case 'ChaCha20-Poly1305':
1885
+ if (!isSecret) return fail();
1886
+ if (format === 'raw-secret') return exportSecret();
1887
+ return fail();
1481
1888
  case 'ECDSA':
1482
- // Fall through
1483
1889
  case 'ECDH':
1484
- if (key.type === 'public') {
1890
+ if (!isPublic) return fail();
1891
+ if (format === 'raw' || format === 'raw-public') {
1485
1892
  return ecExportKey(key, KWebCryptoKeyFormat.kWebCryptoKeyFormatRaw);
1486
1893
  }
1487
- break;
1894
+ return fail();
1488
1895
  case 'Ed25519':
1489
- // Fall through
1490
1896
  case 'Ed448':
1491
- // Fall through
1492
1897
  case 'X25519':
1493
- // Fall through
1494
1898
  case 'X448':
1495
- if (key.type === 'public') {
1496
- const exported = key.keyObject.handle.exportKey();
1497
- return bufferLikeToArrayBuffer(exported);
1498
- }
1499
- break;
1500
- case 'ML-KEM-512':
1501
- // Fall through
1502
- case 'ML-KEM-768':
1503
- // Fall through
1504
- case 'ML-KEM-1024':
1505
- // Fall through
1899
+ if (!isPublic) return fail();
1900
+ if (format === 'raw' || format === 'raw-public') return exportRawPublic();
1901
+ return fail();
1506
1902
  case 'ML-DSA-44':
1507
- // Fall through
1508
1903
  case 'ML-DSA-65':
1509
- // Fall through
1510
- case 'ML-DSA-87':
1511
- if (key.type === 'public') {
1512
- const exported = key.keyObject.handle.exportKey();
1513
- return bufferLikeToArrayBuffer(exported);
1514
- }
1515
- break;
1516
- case 'AES-CTR':
1517
- // Fall through
1518
- case 'AES-CBC':
1519
- // Fall through
1520
- case 'AES-GCM':
1521
- // Fall through
1522
- case 'AES-KW':
1523
- // Fall through
1524
- case 'AES-OCB':
1525
- // Fall through
1526
- case 'ChaCha20-Poly1305':
1527
- // Fall through
1528
- case 'HMAC':
1529
- // Fall through
1530
- case 'KMAC128':
1531
- // Fall through
1532
- case 'KMAC256': {
1533
- const exported = key.keyObject.export();
1534
- // Convert Buffer to ArrayBuffer
1535
- return exported.buffer.slice(
1536
- exported.byteOffset,
1537
- exported.byteOffset + exported.byteLength,
1538
- );
1539
- }
1540
- }
1541
-
1542
- throw lazyDOMException(
1543
- `Unable to export a raw ${key.algorithm.name} ${key.type} key`,
1544
- 'InvalidAccessError',
1545
- );
1904
+ case 'ML-DSA-87':
1905
+ case 'ML-KEM-512':
1906
+ case 'ML-KEM-768':
1907
+ case 'ML-KEM-1024':
1908
+ case 'SLH-DSA-SHA2-128s':
1909
+ case 'SLH-DSA-SHA2-128f':
1910
+ case 'SLH-DSA-SHA2-192s':
1911
+ case 'SLH-DSA-SHA2-192f':
1912
+ case 'SLH-DSA-SHA2-256s':
1913
+ case 'SLH-DSA-SHA2-256f':
1914
+ case 'SLH-DSA-SHAKE-128s':
1915
+ case 'SLH-DSA-SHAKE-128f':
1916
+ case 'SLH-DSA-SHAKE-192s':
1917
+ case 'SLH-DSA-SHAKE-192f':
1918
+ case 'SLH-DSA-SHAKE-256s':
1919
+ case 'SLH-DSA-SHAKE-256f':
1920
+ // ML-DSA / ML-KEM / SLH-DSA keys do not recognize plain 'raw' (Node
1921
+ // webcrypto.js lines 488-510).
1922
+ if (!isPublic) return fail();
1923
+ if (format === 'raw-public') return exportRawPublic();
1924
+ return fail();
1925
+ }
1926
+
1927
+ return fail();
1546
1928
  };
1547
1929
 
1548
1930
  const exportKeyJWK = (key: CryptoKey): ArrayBuffer | unknown => {
1549
1931
  const jwk = key.keyObject.handle.exportJwk(
1550
1932
  {
1551
- key_ops: key.usages,
1933
+ key_ops: [...key.usages],
1552
1934
  ext: key.extractable,
1553
1935
  },
1554
1936
  true,
@@ -1585,6 +1967,31 @@ const exportKeyJWK = (key: CryptoKey): ArrayBuffer | unknown => {
1585
1967
  // Fall through
1586
1968
  case 'X448':
1587
1969
  return jwk;
1970
+ case 'ML-DSA-44':
1971
+ // Fall through
1972
+ case 'ML-DSA-65':
1973
+ // Fall through
1974
+ case 'ML-DSA-87':
1975
+ // Fall through
1976
+ case 'ML-KEM-512':
1977
+ // Fall through
1978
+ case 'ML-KEM-768':
1979
+ // Fall through
1980
+ case 'ML-KEM-1024':
1981
+ // Fall through
1982
+ case 'SLH-DSA-SHA2-128s':
1983
+ case 'SLH-DSA-SHA2-128f':
1984
+ case 'SLH-DSA-SHA2-192s':
1985
+ case 'SLH-DSA-SHA2-192f':
1986
+ case 'SLH-DSA-SHA2-256s':
1987
+ case 'SLH-DSA-SHA2-256f':
1988
+ case 'SLH-DSA-SHAKE-128s':
1989
+ case 'SLH-DSA-SHAKE-128f':
1990
+ case 'SLH-DSA-SHAKE-192s':
1991
+ case 'SLH-DSA-SHAKE-192f':
1992
+ case 'SLH-DSA-SHAKE-256s':
1993
+ case 'SLH-DSA-SHAKE-256f':
1994
+ return jwk;
1588
1995
  case 'AES-CTR':
1589
1996
  // Fall through
1590
1997
  case 'AES-CBC':
@@ -1614,7 +2021,11 @@ const exportKeyJWK = (key: CryptoKey): ArrayBuffer | unknown => {
1614
2021
  );
1615
2022
  };
1616
2023
 
1617
- const importGenericSecretKey = async (
2024
+ // PBKDF2 import. Mirrors Node's importGenericSecretKey ordering
2025
+ // (keys.js:945-971): extractable → usage → format → length. Callers pre-alias
2026
+ // 'raw-secret' / 'raw-public' to 'raw' via aliasKeyFormat
2027
+ // (webcrypto.js:798-808).
2028
+ const pbkdf2ImportKey = async (
1618
2029
  { name, length }: SubtleAlgorithm,
1619
2030
  format: ImportFormat,
1620
2031
  keyData: BufferLike | BinaryLike,
@@ -1622,33 +2033,70 @@ const importGenericSecretKey = async (
1622
2033
  keyUsages: KeyUsage[],
1623
2034
  ): Promise<CryptoKey> => {
1624
2035
  if (extractable) {
1625
- throw new Error(`${name} keys are not extractable`);
2036
+ throw lazyDOMException(`${name} keys are not extractable`, 'SyntaxError');
1626
2037
  }
1627
2038
  if (hasAnyNotIn(keyUsages, ['deriveKey', 'deriveBits'])) {
1628
- throw new Error(`Unsupported key usage for a ${name} key`);
2039
+ throw lazyDOMException(
2040
+ `Unsupported key usage for a ${name} key`,
2041
+ 'SyntaxError',
2042
+ );
2043
+ }
2044
+ if (format !== 'raw') {
2045
+ throw lazyDOMException(
2046
+ `Unable to import ${name} key with format ${format}`,
2047
+ 'NotSupportedError',
2048
+ );
1629
2049
  }
1630
2050
 
1631
- switch (format) {
1632
- case 'raw': {
1633
- if (hasAnyNotIn(keyUsages, ['deriveKey', 'deriveBits'])) {
1634
- throw new Error(`Unsupported key usage for a ${name} key`);
1635
- }
2051
+ const checkLength =
2052
+ typeof keyData === 'string' || SBuffer.isBuffer(keyData)
2053
+ ? keyData.length * 8
2054
+ : keyData.byteLength * 8;
2055
+ if (length !== undefined && length !== checkLength) {
2056
+ throw lazyDOMException('Invalid key length', 'DataError');
2057
+ }
1636
2058
 
1637
- const checkLength =
1638
- typeof keyData === 'string' || SBuffer.isBuffer(keyData)
1639
- ? keyData.length * 8
1640
- : keyData.byteLength * 8;
2059
+ const keyObject = createSecretKey(keyData as BinaryLike);
2060
+ return new CryptoKey(keyObject, { name }, keyUsages, false);
2061
+ };
1641
2062
 
1642
- if (length !== undefined && length !== checkLength) {
1643
- throw new Error('Invalid key length');
1644
- }
2063
+ // Argon2 import. Node gates the format at the dispatcher level — only
2064
+ // 'raw-secret' enters importGenericSecretKey (webcrypto.js:813-822). To match
2065
+ // that, format is the first check here; remaining ordering matches Node's
2066
+ // importGenericSecretKey.
2067
+ const argon2ImportKey = async (
2068
+ { name, length }: SubtleAlgorithm,
2069
+ format: ImportFormat,
2070
+ keyData: BufferLike | BinaryLike,
2071
+ extractable: boolean,
2072
+ keyUsages: KeyUsage[],
2073
+ ): Promise<CryptoKey> => {
2074
+ if (format !== 'raw-secret') {
2075
+ throw lazyDOMException(
2076
+ `Unable to import ${name} key with format ${format}`,
2077
+ 'NotSupportedError',
2078
+ );
2079
+ }
2080
+ if (extractable) {
2081
+ throw lazyDOMException(`${name} keys are not extractable`, 'SyntaxError');
2082
+ }
2083
+ if (hasAnyNotIn(keyUsages, ['deriveKey', 'deriveBits'])) {
2084
+ throw lazyDOMException(
2085
+ `Unsupported key usage for a ${name} key`,
2086
+ 'SyntaxError',
2087
+ );
2088
+ }
1645
2089
 
1646
- const keyObject = createSecretKey(keyData as BinaryLike);
1647
- return new CryptoKey(keyObject, { name }, keyUsages, false);
1648
- }
2090
+ const checkLength =
2091
+ typeof keyData === 'string' || SBuffer.isBuffer(keyData)
2092
+ ? keyData.length * 8
2093
+ : keyData.byteLength * 8;
2094
+ if (length !== undefined && length !== checkLength) {
2095
+ throw lazyDOMException('Invalid key length', 'DataError');
1649
2096
  }
1650
2097
 
1651
- throw new Error(`Unable to import ${name} key with format ${format}`);
2098
+ const keyObject = createSecretKey(keyData as BinaryLike);
2099
+ return new CryptoKey(keyObject, { name }, keyUsages, false);
1652
2100
  };
1653
2101
 
1654
2102
  const hkdfImportKey = async (
@@ -1898,7 +2346,11 @@ const signVerify = (
1898
2346
  const usage: Operation = signature === undefined ? 'sign' : 'verify';
1899
2347
  algorithm = normalizeAlgorithm(algorithm, usage);
1900
2348
 
1901
- if (!key.usages.includes(usage) || algorithm.name !== key.algorithm.name) {
2349
+ if (algorithm.name !== key.algorithm.name) {
2350
+ throw lazyDOMException('Key algorithm mismatch', 'InvalidAccessError');
2351
+ }
2352
+
2353
+ if (!key.usages.includes(usage)) {
1902
2354
  throw lazyDOMException(
1903
2355
  `Unable to use this key to ${usage}`,
1904
2356
  'InvalidAccessError',
@@ -1920,6 +2372,18 @@ const signVerify = (
1920
2372
  case 'ML-DSA-44':
1921
2373
  case 'ML-DSA-65':
1922
2374
  case 'ML-DSA-87':
2375
+ case 'SLH-DSA-SHA2-128s':
2376
+ case 'SLH-DSA-SHA2-128f':
2377
+ case 'SLH-DSA-SHA2-192s':
2378
+ case 'SLH-DSA-SHA2-192f':
2379
+ case 'SLH-DSA-SHA2-256s':
2380
+ case 'SLH-DSA-SHA2-256f':
2381
+ case 'SLH-DSA-SHAKE-128s':
2382
+ case 'SLH-DSA-SHAKE-128f':
2383
+ case 'SLH-DSA-SHAKE-192s':
2384
+ case 'SLH-DSA-SHAKE-192f':
2385
+ case 'SLH-DSA-SHAKE-256s':
2386
+ case 'SLH-DSA-SHAKE-256f':
1923
2387
  return mldsaSignVerify(key, data, signature);
1924
2388
  case 'KMAC128':
1925
2389
  case 'KMAC256':
@@ -1931,23 +2395,16 @@ const signVerify = (
1931
2395
  );
1932
2396
  };
1933
2397
 
2398
+ // Algorithm-mismatch and usage checks live at the public-method call sites
2399
+ // (encrypt / decrypt / wrapKey / unwrapKey) so spec-mandated message and
2400
+ // ordering — algorithm-mismatch first, then usage — is preserved
2401
+ // (Node webcrypto.js, commit 4cb1f284136).
1934
2402
  const cipherOrWrap = async (
1935
2403
  mode: CipherOrWrapMode,
1936
2404
  algorithm: EncryptDecryptParams,
1937
2405
  key: CryptoKey,
1938
2406
  data: ArrayBuffer,
1939
- op: Operation,
1940
2407
  ): Promise<ArrayBuffer> => {
1941
- if (
1942
- key.algorithm.name !== algorithm.name ||
1943
- !key.usages.includes(op as KeyUsage)
1944
- ) {
1945
- throw lazyDOMException(
1946
- 'The requested operation is not valid for the provided key',
1947
- 'InvalidAccessError',
1948
- );
1949
- }
1950
-
1951
2408
  validateMaxBufferLength(data, 'data');
1952
2409
 
1953
2410
  switch (algorithm.name) {
@@ -2002,6 +2459,7 @@ const SUPPORTED_ALGORITHMS: Record<string, Set<string>> = {
2002
2459
  'ML-DSA-44',
2003
2460
  'ML-DSA-65',
2004
2461
  'ML-DSA-87',
2462
+ ...SLH_DSA_VARIANTS,
2005
2463
  ]),
2006
2464
  verify: new Set([
2007
2465
  'RSASSA-PKCS1-v1_5',
@@ -2015,6 +2473,7 @@ const SUPPORTED_ALGORITHMS: Record<string, Set<string>> = {
2015
2473
  'ML-DSA-44',
2016
2474
  'ML-DSA-65',
2017
2475
  'ML-DSA-87',
2476
+ ...SLH_DSA_VARIANTS,
2018
2477
  ]),
2019
2478
  digest: new Set([
2020
2479
  'SHA-1',
@@ -2026,6 +2485,10 @@ const SUPPORTED_ALGORITHMS: Record<string, Set<string>> = {
2026
2485
  'SHA3-512',
2027
2486
  'cSHAKE128',
2028
2487
  'cSHAKE256',
2488
+ 'TurboSHAKE128',
2489
+ 'TurboSHAKE256',
2490
+ 'KT128',
2491
+ 'KT256',
2029
2492
  ]),
2030
2493
  generateKey: new Set([
2031
2494
  'RSASSA-PKCS1-v1_5',
@@ -2052,6 +2515,7 @@ const SUPPORTED_ALGORITHMS: Record<string, Set<string>> = {
2052
2515
  'ML-KEM-512',
2053
2516
  'ML-KEM-768',
2054
2517
  'ML-KEM-1024',
2518
+ ...SLH_DSA_VARIANTS,
2055
2519
  ]),
2056
2520
  importKey: new Set([
2057
2521
  'RSASSA-PKCS1-v1_5',
@@ -2083,6 +2547,7 @@ const SUPPORTED_ALGORITHMS: Record<string, Set<string>> = {
2083
2547
  'ML-KEM-512',
2084
2548
  'ML-KEM-768',
2085
2549
  'ML-KEM-1024',
2550
+ ...SLH_DSA_VARIANTS,
2086
2551
  ]),
2087
2552
  exportKey: new Set([
2088
2553
  'RSASSA-PKCS1-v1_5',
@@ -2109,6 +2574,7 @@ const SUPPORTED_ALGORITHMS: Record<string, Set<string>> = {
2109
2574
  'ML-KEM-512',
2110
2575
  'ML-KEM-768',
2111
2576
  'ML-KEM-1024',
2577
+ ...SLH_DSA_VARIANTS,
2112
2578
  ]),
2113
2579
  deriveBits: new Set([
2114
2580
  'PBKDF2',
@@ -2144,7 +2610,7 @@ const SUPPORTED_ALGORITHMS: Record<string, Set<string>> = {
2144
2610
  decapsulateKey: new Set(['ML-KEM-512', 'ML-KEM-768', 'ML-KEM-1024']),
2145
2611
  };
2146
2612
 
2147
- const ASYMMETRIC_ALGORITHMS = new Set([
2613
+ const ASYMMETRIC_ALGORITHMS = new Set<string>([
2148
2614
  'RSASSA-PKCS1-v1_5',
2149
2615
  'RSA-PSS',
2150
2616
  'RSA-OAEP',
@@ -2160,52 +2626,230 @@ const ASYMMETRIC_ALGORITHMS = new Set([
2160
2626
  'ML-KEM-512',
2161
2627
  'ML-KEM-768',
2162
2628
  'ML-KEM-1024',
2629
+ ...SLH_DSA_VARIANTS,
2163
2630
  ]);
2164
2631
 
2165
- export class Subtle {
2166
- static supports(
2167
- operation: string,
2168
- algorithm: SubtleAlgorithm | AnyAlgorithm,
2169
- _lengthOrAdditionalAlgorithm?: unknown,
2170
- ): boolean {
2171
- let normalizedAlgorithm: SubtleAlgorithm;
2172
- try {
2173
- normalizedAlgorithm = normalizeAlgorithm(
2174
- algorithm,
2175
- (operation === 'getPublicKey' ? 'exportKey' : operation) as Operation,
2176
- );
2177
- } catch {
2632
+ // Per-algorithm validators for deriveBits (mirrors Node's hkdf.js:141-149,
2633
+ // pbkdf2.js:96-105, argon2.js:194-209). Used by Subtle.supports to reject
2634
+ // length values that the actual deriveBits implementation would reject.
2635
+ function validateKdfDeriveBitsLength(
2636
+ length: number | null | undefined,
2637
+ algName: string,
2638
+ ): void {
2639
+ if (length === null || length === undefined) {
2640
+ throw lazyDOMException('length cannot be null', 'OperationError');
2641
+ }
2642
+ if (length % 8) {
2643
+ throw lazyDOMException('length must be a multiple of 8', 'OperationError');
2644
+ }
2645
+ if (algName.startsWith('Argon2') && length < 32) {
2646
+ throw lazyDOMException('length must be >= 32', 'OperationError');
2647
+ }
2648
+ }
2649
+
2650
+ // Mirrors Node's webcrypto.js:1652-1731 `check`. Normalizes the algorithm,
2651
+ // looks it up in SUPPORTED_ALGORITHMS, and runs per-algorithm validation
2652
+ // (deriveBits length validators, HMAC+SHA3 generateKey rejection).
2653
+ // `op` is the operation key in SUPPORTED_ALGORITHMS — wrapKey/unwrapKey fall
2654
+ // back to encrypt/decrypt to mirror Node's normalize fallback.
2655
+ function supportsCheck(
2656
+ op: string,
2657
+ alg: SubtleAlgorithm | AnyAlgorithm,
2658
+ length?: number | null,
2659
+ ): boolean {
2660
+ let normalizedAlgorithm: SubtleAlgorithm;
2661
+ try {
2662
+ normalizedAlgorithm = normalizeAlgorithm(alg, op as Operation);
2663
+ } catch {
2664
+ if (op === 'wrapKey') return supportsCheck('encrypt', alg);
2665
+ if (op === 'unwrapKey') return supportsCheck('decrypt', alg);
2666
+ return false;
2667
+ }
2668
+
2669
+ const supported = SUPPORTED_ALGORITHMS[op];
2670
+ if (!supported || !supported.has(normalizedAlgorithm.name)) {
2671
+ if (op === 'wrapKey') return supportsCheck('encrypt', alg);
2672
+ if (op === 'unwrapKey') return supportsCheck('decrypt', alg);
2673
+ return false;
2674
+ }
2675
+
2676
+ if (op === 'deriveBits') {
2677
+ const name = normalizedAlgorithm.name;
2678
+ if (name === 'HKDF' || name === 'PBKDF2' || name.startsWith('Argon2')) {
2679
+ try {
2680
+ validateKdfDeriveBitsLength(length, name);
2681
+ } catch {
2682
+ return false;
2683
+ }
2684
+ }
2685
+ }
2686
+
2687
+ if (op === 'generateKey' && normalizedAlgorithm.name === 'HMAC') {
2688
+ const hashName =
2689
+ typeof normalizedAlgorithm.hash === 'string'
2690
+ ? normalizedAlgorithm.hash
2691
+ : (normalizedAlgorithm.hash as { name?: string } | undefined)?.name;
2692
+ if (
2693
+ normalizedAlgorithm.length === undefined &&
2694
+ typeof hashName === 'string' &&
2695
+ hashName.startsWith('SHA3-')
2696
+ ) {
2697
+ return false;
2698
+ }
2699
+ }
2700
+
2701
+ return true;
2702
+ }
2703
+
2704
+ function supportsImpl(
2705
+ operation: string,
2706
+ algorithm: SubtleAlgorithm | AnyAlgorithm,
2707
+ lengthOrAdditionalAlgorithm: unknown,
2708
+ ): boolean {
2709
+ switch (operation) {
2710
+ case 'decapsulateBits':
2711
+ case 'decapsulateKey':
2712
+ case 'decrypt':
2713
+ case 'deriveBits':
2714
+ case 'deriveKey':
2715
+ case 'digest':
2716
+ case 'encapsulateBits':
2717
+ case 'encapsulateKey':
2718
+ case 'encrypt':
2719
+ case 'exportKey':
2720
+ case 'generateKey':
2721
+ case 'getPublicKey':
2722
+ case 'importKey':
2723
+ case 'sign':
2724
+ case 'unwrapKey':
2725
+ case 'verify':
2726
+ case 'wrapKey':
2727
+ break;
2728
+ default:
2178
2729
  return false;
2730
+ }
2731
+
2732
+ let length: number | null | undefined;
2733
+
2734
+ if (operation === 'deriveKey') {
2735
+ // deriveKey decomposes to importKey of derived alg + deriveBits with that
2736
+ // alg's key length. Node webcrypto.js:1547-1563.
2737
+ if (lengthOrAdditionalAlgorithm != null) {
2738
+ const additional = lengthOrAdditionalAlgorithm as
2739
+ | SubtleAlgorithm
2740
+ | AnyAlgorithm;
2741
+ if (!supportsCheck('importKey', additional)) return false;
2742
+ try {
2743
+ length = getKeyLength(normalizeAlgorithm(additional, 'get key length'));
2744
+ } catch {
2745
+ return false;
2746
+ }
2747
+ return supportsCheck('deriveBits', algorithm, length);
2748
+ }
2749
+ // No additional algorithm given — only check the deriveBits side.
2750
+ return supportsCheck('deriveBits', algorithm);
2751
+ }
2752
+
2753
+ if (operation === 'wrapKey') {
2754
+ // wrapKey decomposes to encrypt of wrapping alg + exportKey of wrapped alg.
2755
+ // Node webcrypto.js:1564-1572.
2756
+ if (lengthOrAdditionalAlgorithm != null) {
2757
+ const additional = lengthOrAdditionalAlgorithm as
2758
+ | SubtleAlgorithm
2759
+ | AnyAlgorithm;
2760
+ if (!supportsCheck('exportKey', additional)) return false;
2179
2761
  }
2762
+ return supportsCheck('wrapKey', algorithm);
2763
+ }
2180
2764
 
2181
- const name = normalizedAlgorithm.name;
2765
+ if (operation === 'unwrapKey') {
2766
+ // unwrapKey decomposes to decrypt of wrapping alg + importKey of wrapped
2767
+ // alg. Node webcrypto.js:1573-1581.
2768
+ if (lengthOrAdditionalAlgorithm != null) {
2769
+ const additional = lengthOrAdditionalAlgorithm as
2770
+ | SubtleAlgorithm
2771
+ | AnyAlgorithm;
2772
+ if (!supportsCheck('importKey', additional)) return false;
2773
+ }
2774
+ return supportsCheck('unwrapKey', algorithm);
2775
+ }
2182
2776
 
2183
- if (operation === 'getPublicKey') {
2184
- return ASYMMETRIC_ALGORITHMS.has(name);
2777
+ if (operation === 'deriveBits') {
2778
+ if (lengthOrAdditionalAlgorithm == null) {
2779
+ length = null;
2780
+ } else if (typeof lengthOrAdditionalAlgorithm === 'number') {
2781
+ length = lengthOrAdditionalAlgorithm;
2782
+ } else {
2783
+ return false;
2185
2784
  }
2785
+ return supportsCheck('deriveBits', algorithm, length);
2786
+ }
2186
2787
 
2187
- if (operation === 'deriveKey') {
2188
- // deriveKey decomposes to deriveBits + importKey of additional algorithm
2189
- if (!SUPPORTED_ALGORITHMS.deriveBits?.has(name)) return false;
2190
- if (_lengthOrAdditionalAlgorithm != null) {
2191
- try {
2192
- const additionalAlg = normalizeAlgorithm(
2193
- _lengthOrAdditionalAlgorithm as SubtleAlgorithm | AnyAlgorithm,
2194
- 'importKey',
2195
- );
2196
- return (
2197
- SUPPORTED_ALGORITHMS.importKey?.has(additionalAlg.name) ?? false
2198
- );
2199
- } catch {
2200
- return false;
2201
- }
2788
+ if (operation === 'getPublicKey') {
2789
+ let normalized: SubtleAlgorithm;
2790
+ try {
2791
+ normalized = normalizeAlgorithm(algorithm, 'exportKey');
2792
+ } catch {
2793
+ return false;
2794
+ }
2795
+ return ASYMMETRIC_ALGORITHMS.has(normalized.name);
2796
+ }
2797
+
2798
+ if (operation === 'encapsulateKey' || operation === 'decapsulateKey') {
2799
+ // sharedKeyAlgorithm must support importKey, with HMAC/KMAC limited to
2800
+ // length === undefined or 256 (Node webcrypto.js:1610-1645).
2801
+ const additional = lengthOrAdditionalAlgorithm as
2802
+ | SubtleAlgorithm
2803
+ | AnyAlgorithm;
2804
+ let normalizedAdd: SubtleAlgorithm;
2805
+ try {
2806
+ normalizedAdd = normalizeAlgorithm(additional, 'importKey');
2807
+ } catch {
2808
+ return false;
2809
+ }
2810
+ switch (normalizedAdd.name) {
2811
+ case 'AES-OCB':
2812
+ case 'AES-KW':
2813
+ case 'AES-GCM':
2814
+ case 'AES-CTR':
2815
+ case 'AES-CBC':
2816
+ case 'ChaCha20-Poly1305':
2817
+ case 'HKDF':
2818
+ case 'PBKDF2':
2819
+ case 'Argon2i':
2820
+ case 'Argon2d':
2821
+ case 'Argon2id':
2822
+ break;
2823
+ case 'HMAC':
2824
+ case 'KMAC128':
2825
+ case 'KMAC256': {
2826
+ const addLen = normalizedAdd.length;
2827
+ if (addLen !== undefined && addLen !== 256) return false;
2828
+ break;
2202
2829
  }
2203
- return true;
2830
+ default:
2831
+ return false;
2204
2832
  }
2833
+ return supportsCheck(operation, algorithm);
2834
+ }
2835
+
2836
+ return supportsCheck(operation, algorithm);
2837
+ }
2205
2838
 
2206
- const supported = SUPPORTED_ALGORITHMS[operation];
2207
- if (!supported) return false;
2208
- return supported.has(name);
2839
+ export class Subtle {
2840
+ // Spec-compliant capability detector. Mirrors Node's webcrypto.js:1506-1649
2841
+ // `SubtleCrypto.supports`, including:
2842
+ // • composed-operation decomposition (deriveKey, wrapKey, unwrapKey,
2843
+ // encapsulateKey, decapsulateKey, getPublicKey)
2844
+ // • per-algorithm length validators for deriveBits (HKDF/PBKDF2/Argon2)
2845
+ // • HMAC + SHA3 generateKey with no length → false
2846
+ // Static-only per the WICG spec.
2847
+ static supports(
2848
+ operation: string,
2849
+ algorithm: SubtleAlgorithm | AnyAlgorithm,
2850
+ lengthOrAdditionalAlgorithm: unknown = null,
2851
+ ): boolean {
2852
+ return supportsImpl(operation, algorithm, lengthOrAdditionalAlgorithm);
2209
2853
  }
2210
2854
 
2211
2855
  async decrypt(
@@ -2213,13 +2857,25 @@ export class Subtle {
2213
2857
  key: CryptoKey,
2214
2858
  data: BufferLike,
2215
2859
  ): Promise<ArrayBuffer> {
2216
- const normalizedAlgorithm = normalizeAlgorithm(algorithm, 'decrypt');
2860
+ requireArgs(arguments.length, 3, 'decrypt');
2861
+ const normalizedAlgorithm = normalizeAlgorithm(
2862
+ algorithm,
2863
+ 'decrypt',
2864
+ ) as EncryptDecryptParams;
2865
+ if (normalizedAlgorithm.name !== key.algorithm.name) {
2866
+ throw lazyDOMException('Key algorithm mismatch', 'InvalidAccessError');
2867
+ }
2868
+ if (!key.usages.includes('decrypt')) {
2869
+ throw lazyDOMException(
2870
+ 'Unable to use this key to decrypt',
2871
+ 'InvalidAccessError',
2872
+ );
2873
+ }
2217
2874
  return cipherOrWrap(
2218
2875
  CipherOrWrapMode.kWebCryptoCipherDecrypt,
2219
- normalizedAlgorithm as EncryptDecryptParams,
2876
+ normalizedAlgorithm,
2220
2877
  key,
2221
2878
  bufferLikeToArrayBuffer(data),
2222
- 'decrypt',
2223
2879
  );
2224
2880
  }
2225
2881
 
@@ -2227,6 +2883,7 @@ export class Subtle {
2227
2883
  algorithm: SubtleAlgorithm | AnyAlgorithm,
2228
2884
  data: BufferLike,
2229
2885
  ): Promise<ArrayBuffer> {
2886
+ requireArgs(arguments.length, 2, 'digest');
2230
2887
  const normalizedAlgorithm = normalizeAlgorithm(
2231
2888
  algorithm,
2232
2889
  'digest' as Operation,
@@ -2237,43 +2894,56 @@ export class Subtle {
2237
2894
  async deriveBits(
2238
2895
  algorithm: SubtleAlgorithm,
2239
2896
  baseKey: CryptoKey,
2240
- length: number,
2897
+ length: number | null = null,
2241
2898
  ): Promise<ArrayBuffer> {
2899
+ requireArgs(arguments.length, 2, 'deriveBits');
2900
+ const normalizedAlgorithm = normalizeAlgorithm(algorithm, 'deriveBits');
2242
2901
  // WebCrypto §SubtleCrypto.deriveBits step 11: throw InvalidAccessError
2243
2902
  // unless `baseKey.[[usages]]` contains "deriveBits" specifically. The
2244
2903
  // previous `deriveBits || deriveKey` accept-either branch silently
2245
2904
  // promoted deriveKey-only keys into deriveBits use, contradicting the
2246
2905
  // spec usage gate.
2247
- if (!baseKey.keyUsages.includes('deriveBits')) {
2906
+ if (!baseKey.usages.includes('deriveBits')) {
2248
2907
  throw lazyDOMException(
2249
2908
  'baseKey does not have deriveBits usage',
2250
2909
  'InvalidAccessError',
2251
2910
  );
2252
2911
  }
2253
- if (baseKey.algorithm.name !== algorithm.name)
2254
- throw new Error('Key algorithm mismatch');
2255
- switch (algorithm.name) {
2912
+ if (baseKey.algorithm.name !== normalizedAlgorithm.name) {
2913
+ throw lazyDOMException('Key algorithm mismatch', 'InvalidAccessError');
2914
+ }
2915
+ switch (normalizedAlgorithm.name) {
2256
2916
  case 'PBKDF2':
2257
- return pbkdf2DeriveBits(algorithm, baseKey, length);
2917
+ if (length === null) {
2918
+ throw lazyDOMException('length cannot be null', 'OperationError');
2919
+ }
2920
+ return pbkdf2DeriveBits(normalizedAlgorithm, baseKey, length);
2258
2921
  case 'X25519':
2259
2922
  // Fall through
2260
2923
  case 'X448':
2261
- return xDeriveBits(algorithm, baseKey, length);
2924
+ return xDeriveBits(normalizedAlgorithm, baseKey, length);
2262
2925
  case 'ECDH':
2263
- return ecDeriveBits(algorithm, baseKey, length);
2926
+ return ecDeriveBits(normalizedAlgorithm, baseKey, length);
2264
2927
  case 'HKDF':
2928
+ if (length === null) {
2929
+ throw lazyDOMException('length cannot be null', 'OperationError');
2930
+ }
2265
2931
  return hkdfDeriveBits(
2266
- algorithm as unknown as HkdfAlgorithm,
2932
+ normalizedAlgorithm as unknown as HkdfAlgorithm,
2267
2933
  baseKey,
2268
2934
  length,
2269
2935
  );
2270
2936
  case 'Argon2d':
2271
2937
  case 'Argon2i':
2272
2938
  case 'Argon2id':
2273
- return argon2DeriveBits(algorithm, baseKey, length);
2939
+ if (length === null) {
2940
+ throw lazyDOMException('length cannot be null', 'OperationError');
2941
+ }
2942
+ return argon2DeriveBits(normalizedAlgorithm, baseKey, length);
2274
2943
  }
2275
- throw new Error(
2276
- `'subtle.deriveBits()' for ${algorithm.name} is not implemented.`,
2944
+ throw lazyDOMException(
2945
+ `'subtle.deriveBits()' for ${normalizedAlgorithm.name} is not implemented.`,
2946
+ 'NotSupportedError',
2277
2947
  );
2278
2948
  }
2279
2949
 
@@ -2284,40 +2954,55 @@ export class Subtle {
2284
2954
  extractable: boolean,
2285
2955
  keyUsages: KeyUsage[],
2286
2956
  ): Promise<CryptoKey> {
2957
+ requireArgs(arguments.length, 5, 'deriveKey');
2958
+ const normalizedAlgorithm = normalizeAlgorithm(algorithm, 'deriveBits');
2959
+ const normalizedDerivedKeyAlgorithm = normalizeAlgorithm(
2960
+ derivedKeyAlgorithm,
2961
+ 'importKey',
2962
+ );
2963
+
2287
2964
  // Validate baseKey usage
2288
- if (
2289
- !baseKey.usages.includes('deriveKey') &&
2290
- !baseKey.usages.includes('deriveBits')
2291
- ) {
2965
+ if (!baseKey.usages.includes('deriveKey')) {
2292
2966
  throw lazyDOMException(
2293
- 'baseKey does not have deriveKey or deriveBits usage',
2967
+ 'baseKey does not have deriveKey usage',
2294
2968
  'InvalidAccessError',
2295
2969
  );
2296
2970
  }
2297
2971
 
2298
- // Calculate required key length
2299
- const length = getKeyLength(derivedKeyAlgorithm);
2972
+ if (baseKey.algorithm.name !== normalizedAlgorithm.name) {
2973
+ throw lazyDOMException('Key algorithm mismatch', 'InvalidAccessError');
2974
+ }
2975
+
2976
+ // Calculate required key length (may be null for KDF-derived material).
2977
+ const length = getKeyLength(normalizedDerivedKeyAlgorithm);
2300
2978
 
2301
2979
  // Step 1: Derive bits
2302
2980
  let derivedBits: ArrayBuffer;
2303
- if (baseKey.algorithm.name !== algorithm.name)
2304
- throw new Error('Key algorithm mismatch');
2305
-
2306
- switch (algorithm.name) {
2981
+ switch (normalizedAlgorithm.name) {
2307
2982
  case 'PBKDF2':
2308
- derivedBits = await pbkdf2DeriveBits(algorithm, baseKey, length);
2983
+ if (length === null) {
2984
+ throw lazyDOMException('length cannot be null', 'OperationError');
2985
+ }
2986
+ derivedBits = await pbkdf2DeriveBits(
2987
+ normalizedAlgorithm,
2988
+ baseKey,
2989
+ length,
2990
+ );
2309
2991
  break;
2310
2992
  case 'X25519':
2311
2993
  // Fall through
2312
2994
  case 'X448':
2313
- derivedBits = await xDeriveBits(algorithm, baseKey, length);
2995
+ derivedBits = await xDeriveBits(normalizedAlgorithm, baseKey, length);
2314
2996
  break;
2315
2997
  case 'ECDH':
2316
- derivedBits = await ecDeriveBits(algorithm, baseKey, length);
2998
+ derivedBits = await ecDeriveBits(normalizedAlgorithm, baseKey, length);
2317
2999
  break;
2318
3000
  case 'HKDF':
3001
+ if (length === null) {
3002
+ throw lazyDOMException('length cannot be null', 'OperationError');
3003
+ }
2319
3004
  derivedBits = hkdfDeriveBits(
2320
- algorithm as unknown as HkdfAlgorithm,
3005
+ normalizedAlgorithm as unknown as HkdfAlgorithm,
2321
3006
  baseKey,
2322
3007
  length,
2323
3008
  );
@@ -2325,17 +3010,22 @@ export class Subtle {
2325
3010
  case 'Argon2d':
2326
3011
  case 'Argon2i':
2327
3012
  case 'Argon2id':
2328
- derivedBits = argon2DeriveBits(algorithm, baseKey, length);
3013
+ if (length === null) {
3014
+ throw lazyDOMException('length cannot be null', 'OperationError');
3015
+ }
3016
+ derivedBits = argon2DeriveBits(normalizedAlgorithm, baseKey, length);
2329
3017
  break;
2330
3018
  default:
2331
- throw new Error(
2332
- `'subtle.deriveKey()' for ${algorithm.name} is not implemented.`,
3019
+ throw lazyDOMException(
3020
+ `'subtle.deriveKey()' for ${normalizedAlgorithm.name} is not implemented.`,
3021
+ 'NotSupportedError',
2333
3022
  );
2334
3023
  }
2335
3024
 
2336
- // Step 2: Import as key
3025
+ // Step 2: Import as key. Use 'raw-secret' so derived material flows into
3026
+ // AEADs / KMAC correctly — they reject plain 'raw' (Node webcrypto.js:381-385).
2337
3027
  return this.importKey(
2338
- 'raw',
3028
+ 'raw-secret',
2339
3029
  derivedBits,
2340
3030
  derivedKeyAlgorithm,
2341
3031
  extractable,
@@ -2348,13 +3038,25 @@ export class Subtle {
2348
3038
  key: CryptoKey,
2349
3039
  data: BufferLike,
2350
3040
  ): Promise<ArrayBuffer> {
2351
- const normalizedAlgorithm = normalizeAlgorithm(algorithm, 'encrypt');
3041
+ requireArgs(arguments.length, 3, 'encrypt');
3042
+ const normalizedAlgorithm = normalizeAlgorithm(
3043
+ algorithm,
3044
+ 'encrypt',
3045
+ ) as EncryptDecryptParams;
3046
+ if (normalizedAlgorithm.name !== key.algorithm.name) {
3047
+ throw lazyDOMException('Key algorithm mismatch', 'InvalidAccessError');
3048
+ }
3049
+ if (!key.usages.includes('encrypt')) {
3050
+ throw lazyDOMException(
3051
+ 'Unable to use this key to encrypt',
3052
+ 'InvalidAccessError',
3053
+ );
3054
+ }
2352
3055
  return cipherOrWrap(
2353
3056
  CipherOrWrapMode.kWebCryptoCipherEncrypt,
2354
- normalizedAlgorithm as EncryptDecryptParams,
3057
+ normalizedAlgorithm,
2355
3058
  key,
2356
3059
  bufferLikeToArrayBuffer(data),
2357
- 'encrypt',
2358
3060
  );
2359
3061
  }
2360
3062
 
@@ -2362,16 +3064,19 @@ export class Subtle {
2362
3064
  format: ImportFormat,
2363
3065
  key: CryptoKey,
2364
3066
  ): Promise<ArrayBuffer | JWK> {
2365
- if (!key.extractable) throw new Error('key is not extractable');
3067
+ requireArgs(arguments.length, 2, 'exportKey');
3068
+ if (!key.extractable)
3069
+ throw lazyDOMException('key is not extractable', 'InvalidAccessError');
2366
3070
 
2367
3071
  if (format === 'raw-seed') {
2368
- const pqcAlgos = [
3072
+ const pqcAlgos: string[] = [
2369
3073
  'ML-KEM-512',
2370
3074
  'ML-KEM-768',
2371
3075
  'ML-KEM-1024',
2372
3076
  'ML-DSA-44',
2373
3077
  'ML-DSA-65',
2374
3078
  'ML-DSA-87',
3079
+ ...SLH_DSA_VARIANTS,
2375
3080
  ];
2376
3081
  if (!pqcAlgos.includes(key.algorithm.name)) {
2377
3082
  throw lazyDOMException(
@@ -2388,9 +3093,6 @@ export class Subtle {
2388
3093
  return bufferLikeToArrayBuffer(key.keyObject.handle.exportKey());
2389
3094
  }
2390
3095
 
2391
- // Note: 'raw-seed' is handled above; do NOT normalize it here
2392
- if (format === 'raw-secret' || format === 'raw-public') format = 'raw';
2393
-
2394
3096
  switch (format) {
2395
3097
  case 'spki':
2396
3098
  return (await exportKeySpki(key)) as ArrayBuffer;
@@ -2399,7 +3101,9 @@ export class Subtle {
2399
3101
  case 'jwk':
2400
3102
  return exportKeyJWK(key) as JWK;
2401
3103
  case 'raw':
2402
- return exportKeyRaw(key) as ArrayBuffer;
3104
+ case 'raw-secret':
3105
+ case 'raw-public':
3106
+ return exportKeyRaw(key, format) as ArrayBuffer;
2403
3107
  }
2404
3108
  }
2405
3109
 
@@ -2409,10 +3113,18 @@ export class Subtle {
2409
3113
  wrappingKey: CryptoKey,
2410
3114
  wrapAlgorithm: EncryptDecryptParams,
2411
3115
  ): Promise<ArrayBuffer> {
2412
- // Validate wrappingKey usage
3116
+ requireArgs(arguments.length, 4, 'wrapKey');
3117
+ const normalizedWrapAlgorithm = normalizeAlgorithm(
3118
+ wrapAlgorithm,
3119
+ 'wrapKey',
3120
+ ) as EncryptDecryptParams;
3121
+
3122
+ if (normalizedWrapAlgorithm.name !== wrappingKey.algorithm.name) {
3123
+ throw lazyDOMException('Key algorithm mismatch', 'InvalidAccessError');
3124
+ }
2413
3125
  if (!wrappingKey.usages.includes('wrapKey')) {
2414
3126
  throw lazyDOMException(
2415
- 'wrappingKey does not have wrapKey usage',
3127
+ 'Unable to use this key to wrapKey',
2416
3128
  'InvalidAccessError',
2417
3129
  );
2418
3130
  }
@@ -2427,7 +3139,7 @@ export class Subtle {
2427
3139
  const buffer = SBuffer.from(jwkString, 'utf8');
2428
3140
 
2429
3141
  // For AES-KW, pad to multiple of 8 bytes (accounting for null terminator)
2430
- if (wrapAlgorithm.name === 'AES-KW') {
3142
+ if (normalizedWrapAlgorithm.name === 'AES-KW') {
2431
3143
  const length = buffer.length;
2432
3144
  // Add 1 for null terminator, then pad to multiple of 8
2433
3145
  const paddedLength = Math.ceil((length + 1) / 8) * 8;
@@ -2446,10 +3158,9 @@ export class Subtle {
2446
3158
  // Step 3: Encrypt the exported key
2447
3159
  return cipherOrWrap(
2448
3160
  CipherOrWrapMode.kWebCryptoCipherEncrypt,
2449
- wrapAlgorithm,
3161
+ normalizedWrapAlgorithm,
2450
3162
  wrappingKey,
2451
3163
  keyData,
2452
- 'wrapKey',
2453
3164
  );
2454
3165
  }
2455
3166
 
@@ -2462,10 +3173,18 @@ export class Subtle {
2462
3173
  extractable: boolean,
2463
3174
  keyUsages: KeyUsage[],
2464
3175
  ): Promise<CryptoKey> {
2465
- // Validate unwrappingKey usage
3176
+ requireArgs(arguments.length, 7, 'unwrapKey');
3177
+ const normalizedUnwrapAlgorithm = normalizeAlgorithm(
3178
+ unwrapAlgorithm,
3179
+ 'unwrapKey',
3180
+ ) as EncryptDecryptParams;
3181
+
3182
+ if (normalizedUnwrapAlgorithm.name !== unwrappingKey.algorithm.name) {
3183
+ throw lazyDOMException('Key algorithm mismatch', 'InvalidAccessError');
3184
+ }
2466
3185
  if (!unwrappingKey.usages.includes('unwrapKey')) {
2467
3186
  throw lazyDOMException(
2468
- 'unwrappingKey does not have unwrapKey usage',
3187
+ 'Unable to use this key to unwrapKey',
2469
3188
  'InvalidAccessError',
2470
3189
  );
2471
3190
  }
@@ -2473,10 +3192,9 @@ export class Subtle {
2473
3192
  // Step 1: Decrypt the wrapped key
2474
3193
  const decrypted = await cipherOrWrap(
2475
3194
  CipherOrWrapMode.kWebCryptoCipherDecrypt,
2476
- unwrapAlgorithm,
3195
+ normalizedUnwrapAlgorithm,
2477
3196
  unwrappingKey,
2478
3197
  bufferLikeToArrayBuffer(wrappedKey),
2479
- 'unwrapKey',
2480
3198
  );
2481
3199
 
2482
3200
  // Step 2: Convert to appropriate format
@@ -2485,7 +3203,7 @@ export class Subtle {
2485
3203
  const buffer = SBuffer.from(decrypted);
2486
3204
  // For AES-KW, the data may be padded - find the null terminator
2487
3205
  let jwkString: string;
2488
- if (unwrapAlgorithm.name === 'AES-KW') {
3206
+ if (normalizedUnwrapAlgorithm.name === 'AES-KW') {
2489
3207
  // Find the null terminator (if present) to get the original string
2490
3208
  const nullIndex = buffer.indexOf(0);
2491
3209
  if (nullIndex !== -1) {
@@ -2517,6 +3235,7 @@ export class Subtle {
2517
3235
  extractable: boolean,
2518
3236
  keyUsages: KeyUsage[],
2519
3237
  ): Promise<CryptoKey | CryptoKeyPair> {
3238
+ requireArgs(arguments.length, 3, 'generateKey');
2520
3239
  algorithm = normalizeAlgorithm(algorithm, 'generateKey');
2521
3240
  let result: CryptoKey | CryptoKeyPair;
2522
3241
  switch (algorithm.name) {
@@ -2603,6 +3322,25 @@ export class Subtle {
2603
3322
  );
2604
3323
  checkCryptoKeyPairUsages(result as CryptoKeyPair);
2605
3324
  break;
3325
+ case 'SLH-DSA-SHA2-128s':
3326
+ case 'SLH-DSA-SHA2-128f':
3327
+ case 'SLH-DSA-SHA2-192s':
3328
+ case 'SLH-DSA-SHA2-192f':
3329
+ case 'SLH-DSA-SHA2-256s':
3330
+ case 'SLH-DSA-SHA2-256f':
3331
+ case 'SLH-DSA-SHAKE-128s':
3332
+ case 'SLH-DSA-SHAKE-128f':
3333
+ case 'SLH-DSA-SHAKE-192s':
3334
+ case 'SLH-DSA-SHAKE-192f':
3335
+ case 'SLH-DSA-SHAKE-256s':
3336
+ case 'SLH-DSA-SHAKE-256f':
3337
+ result = await slhdsa_generateKeyPairWebCrypto(
3338
+ algorithm.name as SlhDsaVariant,
3339
+ extractable,
3340
+ keyUsages,
3341
+ );
3342
+ checkCryptoKeyPairUsages(result as CryptoKeyPair);
3343
+ break;
2606
3344
  case 'X25519':
2607
3345
  // Fall through
2608
3346
  case 'X448':
@@ -2639,6 +3377,7 @@ export class Subtle {
2639
3377
  key: CryptoKey,
2640
3378
  keyUsages: KeyUsage[],
2641
3379
  ): Promise<CryptoKey> {
3380
+ requireArgs(arguments.length, 2, 'getPublicKey');
2642
3381
  if (key.type === 'secret') {
2643
3382
  throw lazyDOMException('key must be a private key', 'NotSupportedError');
2644
3383
  }
@@ -2657,8 +3396,11 @@ export class Subtle {
2657
3396
  extractable: boolean,
2658
3397
  keyUsages: KeyUsage[],
2659
3398
  ): Promise<CryptoKey> {
2660
- // Note: 'raw-seed' is NOT normalized — PQC import functions handle it directly
2661
- if (format === 'raw-secret' || format === 'raw-public') format = 'raw';
3399
+ requireArgs(arguments.length, 5, 'importKey');
3400
+ // Per-algorithm format handling. Some algorithms alias raw-secret/raw-public
3401
+ // to 'raw' (RSA, EC, Ed/X, HMAC, HKDF, PBKDF2); others demand the
3402
+ // disambiguated form (KMAC, AES-OCB, ChaCha20-Poly1305, Argon2, ML-DSA,
3403
+ // ML-KEM). 'raw-seed' is never normalized — PQC import handles it directly.
2662
3404
  const normalizedAlgorithm = normalizeAlgorithm(algorithm, 'importKey');
2663
3405
  let result: CryptoKey;
2664
3406
  switch (normalizedAlgorithm.name) {
@@ -2668,7 +3410,7 @@ export class Subtle {
2668
3410
  // Fall through
2669
3411
  case 'RSA-OAEP':
2670
3412
  result = rsaImportKey(
2671
- format,
3413
+ aliasKeyFormat(format),
2672
3414
  data as BufferLike | JWK,
2673
3415
  normalizedAlgorithm,
2674
3416
  extractable,
@@ -2679,7 +3421,7 @@ export class Subtle {
2679
3421
  // Fall through
2680
3422
  case 'ECDH':
2681
3423
  result = ecImportKey(
2682
- format,
3424
+ aliasKeyFormat(format),
2683
3425
  data,
2684
3426
  normalizedAlgorithm,
2685
3427
  extractable,
@@ -2687,6 +3429,9 @@ export class Subtle {
2687
3429
  );
2688
3430
  break;
2689
3431
  case 'HMAC':
3432
+ // No aliasing — Node routes HMAC straight into mac.js, which accepts
3433
+ // 'raw' / 'raw-secret' / 'jwk' and rejects everything else
3434
+ // (webcrypto.js:774-781, mac.js:136-174).
2690
3435
  result = await hmacImportKey(
2691
3436
  normalizedAlgorithm,
2692
3437
  format,
@@ -2726,10 +3471,18 @@ export class Subtle {
2726
3471
  );
2727
3472
  break;
2728
3473
  case 'PBKDF2':
3474
+ result = await pbkdf2ImportKey(
3475
+ normalizedAlgorithm,
3476
+ aliasKeyFormat(format),
3477
+ data as BufferLike | BinaryLike,
3478
+ extractable,
3479
+ keyUsages,
3480
+ );
3481
+ break;
2729
3482
  case 'Argon2d':
2730
3483
  case 'Argon2i':
2731
3484
  case 'Argon2id':
2732
- result = await importGenericSecretKey(
3485
+ result = await argon2ImportKey(
2733
3486
  normalizedAlgorithm,
2734
3487
  format,
2735
3488
  data as BufferLike | BinaryLike,
@@ -2739,7 +3492,7 @@ export class Subtle {
2739
3492
  break;
2740
3493
  case 'HKDF':
2741
3494
  result = await hkdfImportKey(
2742
- format,
3495
+ aliasKeyFormat(format),
2743
3496
  data as BufferLike | BinaryLike,
2744
3497
  normalizedAlgorithm,
2745
3498
  extractable,
@@ -2754,6 +3507,26 @@ export class Subtle {
2754
3507
  // Fall through
2755
3508
  case 'Ed448':
2756
3509
  result = edImportKey(
3510
+ aliasKeyFormat(format),
3511
+ data as BufferLike | JWK,
3512
+ normalizedAlgorithm,
3513
+ extractable,
3514
+ keyUsages,
3515
+ );
3516
+ break;
3517
+ case 'SLH-DSA-SHA2-128s':
3518
+ case 'SLH-DSA-SHA2-128f':
3519
+ case 'SLH-DSA-SHA2-192s':
3520
+ case 'SLH-DSA-SHA2-192f':
3521
+ case 'SLH-DSA-SHA2-256s':
3522
+ case 'SLH-DSA-SHA2-256f':
3523
+ case 'SLH-DSA-SHAKE-128s':
3524
+ case 'SLH-DSA-SHAKE-128f':
3525
+ case 'SLH-DSA-SHAKE-192s':
3526
+ case 'SLH-DSA-SHAKE-192f':
3527
+ case 'SLH-DSA-SHAKE-256s':
3528
+ case 'SLH-DSA-SHAKE-256f':
3529
+ result = slhdsaImportKey(
2757
3530
  format,
2758
3531
  data as BufferLike | JWK,
2759
3532
  normalizedAlgorithm,
@@ -2768,7 +3541,7 @@ export class Subtle {
2768
3541
  case 'ML-DSA-87':
2769
3542
  result = mldsaImportKey(
2770
3543
  format,
2771
- data as BufferLike,
3544
+ data as BufferLike | JWK,
2772
3545
  normalizedAlgorithm,
2773
3546
  extractable,
2774
3547
  keyUsages,
@@ -2781,7 +3554,7 @@ export class Subtle {
2781
3554
  case 'ML-KEM-1024':
2782
3555
  result = mlkemImportKey(
2783
3556
  format,
2784
- data as BufferLike,
3557
+ data as BufferLike | JWK,
2785
3558
  normalizedAlgorithm,
2786
3559
  extractable,
2787
3560
  keyUsages,
@@ -2810,6 +3583,7 @@ export class Subtle {
2810
3583
  key: CryptoKey,
2811
3584
  data: BufferLike,
2812
3585
  ): Promise<ArrayBuffer> {
3586
+ requireArgs(arguments.length, 3, 'sign');
2813
3587
  return signVerify(
2814
3588
  normalizeAlgorithm(algorithm, 'sign'),
2815
3589
  key,
@@ -2823,6 +3597,7 @@ export class Subtle {
2823
3597
  signature: BufferLike,
2824
3598
  data: BufferLike,
2825
3599
  ): Promise<boolean> {
3600
+ requireArgs(arguments.length, 4, 'verify');
2826
3601
  return signVerify(
2827
3602
  normalizeAlgorithm(algorithm, 'verify'),
2828
3603
  key,
@@ -2894,6 +3669,7 @@ export class Subtle {
2894
3669
  algorithm: SubtleAlgorithm,
2895
3670
  key: CryptoKey,
2896
3671
  ): Promise<EncapsulateResult> {
3672
+ requireArgs(arguments.length, 2, 'encapsulateBits');
2897
3673
  if (!key.usages.includes('encapsulateBits')) {
2898
3674
  throw lazyDOMException(
2899
3675
  'Key does not have encapsulateBits usage',
@@ -2911,6 +3687,7 @@ export class Subtle {
2911
3687
  extractable: boolean,
2912
3688
  usages: KeyUsage[],
2913
3689
  ): Promise<{ key: CryptoKey; ciphertext: ArrayBuffer }> {
3690
+ requireArgs(arguments.length, 5, 'encapsulateKey');
2914
3691
  if (!key.usages.includes('encapsulateKey')) {
2915
3692
  throw lazyDOMException(
2916
3693
  'Key does not have encapsulateKey usage',
@@ -2919,8 +3696,10 @@ export class Subtle {
2919
3696
  }
2920
3697
 
2921
3698
  const { sharedKey, ciphertext } = this._encapsulateCore(algorithm, key);
3699
+ // Node imports the encapsulated shared bits as 'raw-secret'
3700
+ // (webcrypto.js:1370-1374) so AEADs / KMAC accept the result.
2922
3701
  const importedKey = await this.importKey(
2923
- 'raw',
3702
+ 'raw-secret',
2924
3703
  sharedKey,
2925
3704
  sharedKeyAlgorithm,
2926
3705
  extractable,
@@ -2935,6 +3714,7 @@ export class Subtle {
2935
3714
  key: CryptoKey,
2936
3715
  ciphertext: BufferLike,
2937
3716
  ): Promise<ArrayBuffer> {
3717
+ requireArgs(arguments.length, 3, 'decapsulateBits');
2938
3718
  if (!key.usages.includes('decapsulateBits')) {
2939
3719
  throw lazyDOMException(
2940
3720
  'Key does not have decapsulateBits usage',
@@ -2953,6 +3733,7 @@ export class Subtle {
2953
3733
  extractable: boolean,
2954
3734
  usages: KeyUsage[],
2955
3735
  ): Promise<CryptoKey> {
3736
+ requireArgs(arguments.length, 6, 'decapsulateKey');
2956
3737
  if (!key.usages.includes('decapsulateKey')) {
2957
3738
  throw lazyDOMException(
2958
3739
  'Key does not have decapsulateKey usage',
@@ -2961,8 +3742,10 @@ export class Subtle {
2961
3742
  }
2962
3743
 
2963
3744
  const sharedKey = this._decapsulateCore(algorithm, key, ciphertext);
3745
+ // Node imports the decapsulated shared bits as 'raw-secret'
3746
+ // (webcrypto.js:1490-1494).
2964
3747
  return this.importKey(
2965
- 'raw',
3748
+ 'raw-secret',
2966
3749
  sharedKey,
2967
3750
  sharedKeyAlgorithm,
2968
3751
  extractable,
@@ -2973,8 +3756,14 @@ export class Subtle {
2973
3756
 
2974
3757
  export const subtle = new Subtle();
2975
3758
 
2976
- function getKeyLength(algorithm: SubtleAlgorithm): number {
3759
+ // Returns the number of bits to derive for an `importKey` algorithm, mirroring
3760
+ // Node's webcrypto.js:269-306 `getKeyLength`. Returns null for KDF algorithms
3761
+ // (HKDF / PBKDF2 / Argon2) — those carry their full derived secret without a
3762
+ // fixed key length. Throws OperationError on invalid AES / HMAC inputs rather
3763
+ // than silently coercing to a default (Node commit 4cb1f284136 behavior).
3764
+ function getKeyLength(algorithm: SubtleAlgorithm): number | null {
2977
3765
  const name = algorithm.name;
3766
+ const length = (algorithm as { length?: number }).length;
2978
3767
 
2979
3768
  switch (name) {
2980
3769
  case 'AES-CTR':
@@ -2982,18 +3771,38 @@ function getKeyLength(algorithm: SubtleAlgorithm): number {
2982
3771
  case 'AES-GCM':
2983
3772
  case 'AES-KW':
2984
3773
  case 'AES-OCB':
2985
- case 'ChaCha20-Poly1305':
2986
- return (algorithm as AesKeyGenParams).length || 256;
3774
+ if (length !== 128 && length !== 192 && length !== 256) {
3775
+ throw lazyDOMException('Invalid key length', 'OperationError');
3776
+ }
3777
+ return length;
2987
3778
 
2988
3779
  case 'HMAC': {
2989
- const hmacAlg = algorithm as { length?: number };
2990
- return hmacAlg.length || 256;
3780
+ if (length === undefined) {
3781
+ return getHmacBlockSize(
3782
+ (algorithm.hash as { name?: string } | undefined)?.name ??
3783
+ (algorithm.hash as string | undefined),
3784
+ );
3785
+ }
3786
+ if (typeof length === 'number' && length !== 0) {
3787
+ return length;
3788
+ }
3789
+ throw lazyDOMException('Invalid key length', 'OperationError');
2991
3790
  }
2992
3791
 
2993
3792
  case 'KMAC128':
2994
- return algorithm.length || 128;
3793
+ return typeof length === 'number' ? length : 128;
2995
3794
  case 'KMAC256':
2996
- return algorithm.length || 256;
3795
+ return typeof length === 'number' ? length : 256;
3796
+
3797
+ case 'ChaCha20-Poly1305':
3798
+ return 256;
3799
+
3800
+ case 'HKDF':
3801
+ case 'PBKDF2':
3802
+ case 'Argon2d':
3803
+ case 'Argon2i':
3804
+ case 'Argon2id':
3805
+ return null;
2997
3806
 
2998
3807
  default:
2999
3808
  throw lazyDOMException(
@@ -3002,3 +3811,25 @@ function getKeyLength(algorithm: SubtleAlgorithm): number {
3002
3811
  );
3003
3812
  }
3004
3813
  }
3814
+
3815
+ function getHmacBlockSize(name: string | undefined): number {
3816
+ switch (name) {
3817
+ case 'SHA-1':
3818
+ case 'SHA-256':
3819
+ return 512;
3820
+ case 'SHA-384':
3821
+ case 'SHA-512':
3822
+ return 1024;
3823
+ case 'SHA3-256':
3824
+ case 'SHA3-384':
3825
+ case 'SHA3-512':
3826
+ // SHA-3 / HMAC interaction undefined — Node throws here too
3827
+ // (webcrypto-modern-algos issue #23).
3828
+ throw lazyDOMException(
3829
+ 'Explicit algorithm length member is required',
3830
+ 'NotSupportedError',
3831
+ );
3832
+ default:
3833
+ throw lazyDOMException('Invalid key length', 'OperationError');
3834
+ }
3835
+ }