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