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