@blamejs/pki 0.5.18 → 0.5.20
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/CHANGELOG.md +43 -0
- package/lib/cms-verify.js +18 -5
- package/lib/guard-all.js +2 -0
- package/lib/guard-intrinsic.js +27 -0
- package/lib/guard-list.js +85 -0
- package/lib/jose.js +55 -35
- package/lib/key.js +6 -0
- package/lib/ocsp-verify.js +9 -2
- package/lib/path-validate.js +53 -27
- package/lib/sign-scheme.js +16 -1
- package/lib/tsp-sign.js +12 -4
- package/lib/validator-attcert.js +10 -1
- package/lib/webcrypto.js +165 -90
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/webcrypto.js
CHANGED
|
@@ -42,6 +42,52 @@ var guard = require("./guard-all");
|
|
|
42
42
|
var constants = require("./constants");
|
|
43
43
|
var asn1 = require("./asn1-der");
|
|
44
44
|
var oid = require("./oid");
|
|
45
|
+
// What this engine reads from the runtime while it is deciding something, taken at load. The
|
|
46
|
+
// decisions here are the ones the whole toolkit signs and verifies behind: whether a value IS a
|
|
47
|
+
// CryptoKey, which algorithm a key names, how many octets a modulus has. A caller who receives
|
|
48
|
+
// control afterwards -- through an accessor on a key-like object, or while a verb is suspended on
|
|
49
|
+
// an await -- can replace any of them, and every check around them still runs and still passes.
|
|
50
|
+
// See guard-intrinsic for the whole captured set.
|
|
51
|
+
var intrinsic = require("./guard-intrinsic");
|
|
52
|
+
var _isArray = intrinsic.isArray;
|
|
53
|
+
var _hasOwn = intrinsic.hasOwn;
|
|
54
|
+
var _stringify = intrinsic.stringify;
|
|
55
|
+
var _bufferFrom = intrinsic.bufferFrom;
|
|
56
|
+
var _bufToString = intrinsic.uncurry(Buffer.prototype.toString);
|
|
57
|
+
var _bufferConcat = intrinsic.bufferConcat;
|
|
58
|
+
var _bufferAlloc = intrinsic.bufferAlloc;
|
|
59
|
+
var _isBuffer = intrinsic.isBuffer;
|
|
60
|
+
var _keys = intrinsic.keys;
|
|
61
|
+
var _map = intrinsic.map;
|
|
62
|
+
var _forEach = intrinsic.forEach;
|
|
63
|
+
var _indexOf = intrinsic.indexOf;
|
|
64
|
+
var _isInteger = intrinsic.isInteger;
|
|
65
|
+
var _join = intrinsic.join;
|
|
66
|
+
var _filter = intrinsic.filter;
|
|
67
|
+
var _arraySlice = intrinsic.arraySlice;
|
|
68
|
+
var _defineProperty = intrinsic.defineProperty;
|
|
69
|
+
var _max = intrinsic.max;
|
|
70
|
+
var _parseInt = intrinsic.parseInt;
|
|
71
|
+
var _isFinite = intrinsic.isFinite;
|
|
72
|
+
var _String = intrinsic.String;
|
|
73
|
+
var _Number = intrinsic.Number;
|
|
74
|
+
var _BigInt = intrinsic.BigInt;
|
|
75
|
+
var _Uint8Array = intrinsic.Uint8Array;
|
|
76
|
+
var _assign = intrinsic.assign;
|
|
77
|
+
var _create = intrinsic.create;
|
|
78
|
+
var _isView = intrinsic.isView;
|
|
79
|
+
var _toUpperCase = intrinsic.toUpperCase;
|
|
80
|
+
var _toLowerCase = intrinsic.toLowerCase;
|
|
81
|
+
var _stringIndexOf = intrinsic.stringIndexOf;
|
|
82
|
+
var _subarray = intrinsic.subarray;
|
|
83
|
+
var _arrayBufferSlice = intrinsic.arrayBufferSlice;
|
|
84
|
+
var _numberToString = intrinsic.numberToString;
|
|
85
|
+
var _bigIntToString = intrinsic.bigIntToString;
|
|
86
|
+
var _push = intrinsic.push;
|
|
87
|
+
var _concat = intrinsic.concat;
|
|
88
|
+
// Promise.resolve is a constructor method, so it keeps its receiver: `_resolve(Promise, x)`.
|
|
89
|
+
var _resolve = intrinsic.uncurry(intrinsic.promiseResolve);
|
|
90
|
+
var _types = intrinsic.types;
|
|
45
91
|
|
|
46
92
|
// Single-owner error class -- co-located with its module (framework-error
|
|
47
93
|
// stays the cross-module home; this is webcrypto-private). withCause: a
|
|
@@ -58,7 +104,7 @@ function _toBuf(data, who) {
|
|
|
58
104
|
}
|
|
59
105
|
|
|
60
106
|
function _toArrayBuffer(buf) {
|
|
61
|
-
return buf.buffer
|
|
107
|
+
return _arrayBufferSlice(buf.buffer, buf.byteOffset, buf.byteOffset + buf.byteLength);
|
|
62
108
|
}
|
|
63
109
|
|
|
64
110
|
function _wcErr(code, msg) { return new WebCryptoError(code, msg); }
|
|
@@ -68,7 +114,7 @@ function _wcErr(code, msg) { return new WebCryptoError(code, msg); }
|
|
|
68
114
|
// bogus 6-byte key). Node's own canonical JWK export (_rawPublic) round-trips
|
|
69
115
|
// cleanly, so the strictness is benign there and fail-closed on untrusted import.
|
|
70
116
|
function _b64urlToBuf(s, who) { return guard.encoding.base64url(s, null, _wcErr, "webcrypto/data", who || "base64url value"); }
|
|
71
|
-
function _bufToB64url(buf) { return
|
|
117
|
+
function _bufToB64url(buf) { return _bufToString(_bufferFrom(buf), "base64url"); }
|
|
72
118
|
|
|
73
119
|
function _normalizeAlg(algorithm, who) {
|
|
74
120
|
var alg = (typeof algorithm === "string") ? { name: algorithm } : algorithm;
|
|
@@ -76,8 +122,8 @@ function _normalizeAlg(algorithm, who) {
|
|
|
76
122
|
throw new WebCryptoError("webcrypto/syntax", (who || "operation") + ": algorithm must be a string or an object with a name");
|
|
77
123
|
}
|
|
78
124
|
var out = {};
|
|
79
|
-
for (var k in alg) { if (
|
|
80
|
-
out.name = alg.name
|
|
125
|
+
for (var k in alg) { if (_hasOwn(alg, k)) out[k] = alg[k]; }
|
|
126
|
+
out.name = _toUpperCase(alg.name);
|
|
81
127
|
return out;
|
|
82
128
|
}
|
|
83
129
|
|
|
@@ -119,8 +165,11 @@ var XOF_NODE = {
|
|
|
119
165
|
|
|
120
166
|
function _hashNode(h, who) {
|
|
121
167
|
var name = (typeof h === "string") ? h : (h && h.name);
|
|
122
|
-
|
|
123
|
-
|
|
168
|
+
// The case fold is what picks the registry row, so it runs through the captured method: dispatched
|
|
169
|
+
// live, a replaced `toUpperCase` answering "SHA-1" makes a caller who asked for SHA-256 receive a
|
|
170
|
+
// SHA-1 digest, and nothing between here and the hash says the algorithm changed.
|
|
171
|
+
var node = HASH_NODE[_toUpperCase(_String(name))];
|
|
172
|
+
if (!node) throw new WebCryptoError("webcrypto/not-supported", (who || "operation") + ": unsupported hash " + _stringify(name));
|
|
124
173
|
return node;
|
|
125
174
|
}
|
|
126
175
|
|
|
@@ -138,10 +187,10 @@ var HMAC_BLOCK_BITS = {
|
|
|
138
187
|
|
|
139
188
|
function _hmacLengthBits(alg, who) {
|
|
140
189
|
var name = (typeof alg.hash === "string") ? alg.hash : (alg.hash && alg.hash.name);
|
|
141
|
-
var blockBits = HMAC_BLOCK_BITS[
|
|
142
|
-
if (!blockBits) throw new WebCryptoError("webcrypto/not-supported", who + ": unsupported hash " +
|
|
190
|
+
var blockBits = HMAC_BLOCK_BITS[_toUpperCase(_String(name))];
|
|
191
|
+
if (!blockBits) throw new WebCryptoError("webcrypto/not-supported", who + ": unsupported hash " + _stringify(name));
|
|
143
192
|
if (alg.length == null) return blockBits;
|
|
144
|
-
if (typeof alg.length !== "number" || !
|
|
193
|
+
if (typeof alg.length !== "number" || !_isFinite(alg.length) || alg.length <= 0 || alg.length % 8 !== 0) {
|
|
145
194
|
throw new WebCryptoError("webcrypto/syntax", who + ": HMAC length must be a positive multiple of 8 bits");
|
|
146
195
|
}
|
|
147
196
|
return alg.length;
|
|
@@ -161,7 +210,7 @@ var ML_KEM_NODE = { "ML-KEM-512": "ml-kem-512", "ML-KEM-768": "ml-kem-768", "ML-
|
|
|
161
210
|
var SLH_DSA_NODE = {};
|
|
162
211
|
["sha2-128s", "sha2-128f", "sha2-192s", "sha2-192f", "sha2-256s", "sha2-256f",
|
|
163
212
|
"shake-128s", "shake-128f", "shake-192s", "shake-192f", "shake-256s", "shake-256f"
|
|
164
|
-
].forEach(function (s) { SLH_DSA_NODE["SLH-DSA-" + s
|
|
213
|
+
].forEach(function (s) { SLH_DSA_NODE["SLH-DSA-" + _toUpperCase(s)] = "slh-dsa-" + s; });
|
|
165
214
|
|
|
166
215
|
// The algorithm names each keyed operation recognizes. Membership is checked
|
|
167
216
|
// before the algorithm/key name binding so an unrecognized algorithm reports
|
|
@@ -169,7 +218,7 @@ var SLH_DSA_NODE = {};
|
|
|
169
218
|
// InvalidAccessError, matching the W3C error ordering.
|
|
170
219
|
var SIGN_VERIFY_NAMES = {};
|
|
171
220
|
["RSASSA-PKCS1-V1_5", "RSA-PSS", "ECDSA", "ED25519", "ED448", "HMAC"]
|
|
172
|
-
.concat(
|
|
221
|
+
.concat(_keys(ML_DSA_NODE), _keys(SLH_DSA_NODE))
|
|
173
222
|
.forEach(function (n) { SIGN_VERIFY_NAMES[n] = true; });
|
|
174
223
|
var ENCRYPT_DECRYPT_NAMES = { "RSA-OAEP": true, "AES-GCM": true, "AES-CBC": true, "AES-CTR": true };
|
|
175
224
|
var DERIVE_NAMES = { "ECDH": true, "X25519": true, "X448": true, "HKDF": true, "PBKDF2": true, "X963KDF": true };
|
|
@@ -206,8 +255,16 @@ function CryptoKey(type, extractable, algorithm, usages, handle) {
|
|
|
206
255
|
this.type = type;
|
|
207
256
|
this.extractable = !!extractable;
|
|
208
257
|
this.algorithm = algorithm;
|
|
209
|
-
|
|
210
|
-
|
|
258
|
+
// The copy is what the key will be asked about for the rest of its life, so the method that makes
|
|
259
|
+
// it is the captured one: a replaced `slice` hands back a list the caller still controls. The
|
|
260
|
+
// list-ness is checked rather than left to the copy to discover -- `Array.prototype.slice` on a
|
|
261
|
+
// number yields an empty list, so a non-list would have minted a key permitted for NOTHING
|
|
262
|
+
// instead of naming the bad argument.
|
|
263
|
+
if (usages != null && !_isArray(usages)) {
|
|
264
|
+
throw new WebCryptoError("webcrypto/syntax", "usages must be an array of key-usage strings");
|
|
265
|
+
}
|
|
266
|
+
this.usages = usages ? _arraySlice(usages) : [];
|
|
267
|
+
_defineProperty(this, "_handle", { value: handle, enumerable: false });
|
|
211
268
|
}
|
|
212
269
|
|
|
213
270
|
// This engine reads key material through its own CryptoKey handle, which a key minted by another
|
|
@@ -225,8 +282,11 @@ function _requireOwnKey(key, who) {
|
|
|
225
282
|
|
|
226
283
|
function _requireUsage(key, usage) {
|
|
227
284
|
_requireOwnKey(key, usage);
|
|
228
|
-
|
|
229
|
-
|
|
285
|
+
// The membership test IS the permission check, so it runs through the captured method: dispatched
|
|
286
|
+
// live, an `Array.prototype.indexOf` replaced after load reports every usage present and a key
|
|
287
|
+
// minted to verify signs instead, with this refusal never raised.
|
|
288
|
+
if (_indexOf(key.usages, usage) === -1) {
|
|
289
|
+
throw new WebCryptoError("webcrypto/invalid-access", "key is not permitted for '" + usage + "' (usages: " + _join(key.usages, ",") + ")");
|
|
230
290
|
}
|
|
231
291
|
}
|
|
232
292
|
|
|
@@ -239,8 +299,8 @@ function _requireUsage(key, usage) {
|
|
|
239
299
|
// be satisfied by a key of algorithm Y (algorithm confusion).
|
|
240
300
|
function _requireAlgMatch(alg, key, who) {
|
|
241
301
|
var keyName = key && key.algorithm && key.algorithm.name;
|
|
242
|
-
if (
|
|
243
|
-
throw new WebCryptoError("webcrypto/invalid-access", who + ": algorithm " +
|
|
302
|
+
if (_toUpperCase(_String(keyName)) !== alg.name) {
|
|
303
|
+
throw new WebCryptoError("webcrypto/invalid-access", who + ": algorithm " + _stringify(alg.name) + " does not match the key's algorithm " + _stringify(keyName));
|
|
244
304
|
}
|
|
245
305
|
}
|
|
246
306
|
|
|
@@ -250,7 +310,7 @@ function SubtleCrypto() {}
|
|
|
250
310
|
|
|
251
311
|
SubtleCrypto.prototype.digest = async function digest(algorithm, data) {
|
|
252
312
|
var name = _normalizeAlg(algorithm, "digest").name;
|
|
253
|
-
var xof = XOF_NODE[
|
|
313
|
+
var xof = XOF_NODE[_toUpperCase(_String(name))];
|
|
254
314
|
// An XOF is always constructed with its output length stated. Omitting it
|
|
255
315
|
// makes node emit a deprecation warning and fall back to a 32-byte squeeze,
|
|
256
316
|
// which for SHAKE256 would silently produce half the required digest.
|
|
@@ -263,6 +323,14 @@ SubtleCrypto.prototype.digest = async function digest(algorithm, data) {
|
|
|
263
323
|
|
|
264
324
|
SubtleCrypto.prototype.generateKey = async function generateKey(algorithm, extractable, keyUsages) {
|
|
265
325
|
var alg = _normalizeAlg(algorithm, "generateKey");
|
|
326
|
+
// Before the partition below, not only in the CryptoKey constructor: the asymmetric branch splits
|
|
327
|
+
// the usages first, and `Array.prototype.filter` treats a non-list as array-like and yields `[]`,
|
|
328
|
+
// so both halves reached the constructor already valid and the call minted an unusable PAIR
|
|
329
|
+
// instead of naming the argument. The symmetric branches hand `usages` straight to the
|
|
330
|
+
// constructor, which is why its own door caught those and not these.
|
|
331
|
+
if (keyUsages != null && !_isArray(keyUsages)) {
|
|
332
|
+
throw new WebCryptoError("webcrypto/syntax", "generateKey: keyUsages must be an array of key-usage strings");
|
|
333
|
+
}
|
|
266
334
|
var usages = keyUsages || [];
|
|
267
335
|
var name = alg.name;
|
|
268
336
|
|
|
@@ -282,8 +350,10 @@ SubtleCrypto.prototype.generateKey = async function generateKey(algorithm, extra
|
|
|
282
350
|
// Asymmetric key pairs.
|
|
283
351
|
var pair = _generateKeyPair(alg);
|
|
284
352
|
var pubAlg = pair.algorithm;
|
|
285
|
-
|
|
286
|
-
|
|
353
|
+
// Which usages each half of the pair is minted with -- the permission set every later operation
|
|
354
|
+
// is checked against -- so the partition runs through the captured method.
|
|
355
|
+
var privUsages = _filter(usages, function (u) { return u === "sign" || u === "deriveKey" || u === "deriveBits" || u === "decrypt" || u === "unwrapKey" || u === "decapsulateKey" || u === "decapsulateBits"; });
|
|
356
|
+
var pubUsages = _filter(usages, function (u) { return u === "verify" || u === "encrypt" || u === "wrapKey" || u === "encapsulateKey" || u === "encapsulateBits"; });
|
|
287
357
|
return {
|
|
288
358
|
privateKey: new CryptoKey("private", extractable, pubAlg, privUsages, pair.privateKey),
|
|
289
359
|
publicKey: new CryptoKey("public", true, pubAlg, pubUsages, pair.publicKey),
|
|
@@ -304,11 +374,11 @@ function _generateKeyPair(alg) {
|
|
|
304
374
|
algorithm = { name: _stdName(name), modulusLength: alg.modulusLength || 2048, publicExponent: alg.publicExponent, hash: rsaHash };
|
|
305
375
|
} else if (name === "ECDSA" || name === "ECDH") {
|
|
306
376
|
var curve = alg.namedCurve;
|
|
307
|
-
if (!CURVE_NODE[curve]) throw new WebCryptoError("webcrypto/not-supported", name + ": unsupported curve " +
|
|
377
|
+
if (!CURVE_NODE[curve]) throw new WebCryptoError("webcrypto/not-supported", name + ": unsupported curve " + _stringify(curve));
|
|
308
378
|
kp = nodeCrypto.generateKeyPairSync("ec", { namedCurve: CURVE_NODE[curve] });
|
|
309
379
|
algorithm = { name: name, namedCurve: curve };
|
|
310
380
|
} else if (name === "ED25519" || name === "ED448" || name === "X25519" || name === "X448") {
|
|
311
|
-
kp = nodeCrypto.generateKeyPairSync(name
|
|
381
|
+
kp = nodeCrypto.generateKeyPairSync(_toLowerCase(name));
|
|
312
382
|
algorithm = { name: _stdName(name) };
|
|
313
383
|
} else if (ML_DSA_NODE[name]) {
|
|
314
384
|
kp = nodeCrypto.generateKeyPairSync(ML_DSA_NODE[name]);
|
|
@@ -320,7 +390,7 @@ function _generateKeyPair(alg) {
|
|
|
320
390
|
kp = nodeCrypto.generateKeyPairSync(SLH_DSA_NODE[name]);
|
|
321
391
|
algorithm = { name: name };
|
|
322
392
|
} else {
|
|
323
|
-
throw new WebCryptoError("webcrypto/not-supported", "generateKey: unsupported algorithm " +
|
|
393
|
+
throw new WebCryptoError("webcrypto/not-supported", "generateKey: unsupported algorithm " + _stringify(name));
|
|
324
394
|
}
|
|
325
395
|
return { publicKey: kp.publicKey, privateKey: kp.privateKey, algorithm: algorithm };
|
|
326
396
|
}
|
|
@@ -354,11 +424,11 @@ function _bufToBigIntNum(exp) {
|
|
|
354
424
|
if (b.length === 0) {
|
|
355
425
|
throw new WebCryptoError("webcrypto/syntax", "publicExponent must be a non-empty BigInteger octet string");
|
|
356
426
|
}
|
|
357
|
-
var v =
|
|
427
|
+
var v = _BigInt("0x" + _bufToString(b, "hex"));
|
|
358
428
|
if (v > 0xffffffffn) {
|
|
359
|
-
throw new WebCryptoError("webcrypto/syntax", "publicExponent " + v
|
|
429
|
+
throw new WebCryptoError("webcrypto/syntax", "publicExponent " + _bigIntToString(v) + " exceeds the 2^32-1 bound");
|
|
360
430
|
}
|
|
361
|
-
return
|
|
431
|
+
return _Number(v);
|
|
362
432
|
}
|
|
363
433
|
|
|
364
434
|
SubtleCrypto.prototype.sign = async function sign(algorithm, key, data) {
|
|
@@ -366,7 +436,7 @@ SubtleCrypto.prototype.sign = async function sign(algorithm, key, data) {
|
|
|
366
436
|
_requireUsage(key, "sign");
|
|
367
437
|
var buf = _toBuf(data, "sign");
|
|
368
438
|
var name = alg.name;
|
|
369
|
-
if (!SIGN_VERIFY_NAMES[name]) throw new WebCryptoError("webcrypto/not-supported", "sign: unsupported algorithm " +
|
|
439
|
+
if (!SIGN_VERIFY_NAMES[name]) throw new WebCryptoError("webcrypto/not-supported", "sign: unsupported algorithm " + _stringify(name));
|
|
370
440
|
_requireAlgMatch(alg, key, "sign");
|
|
371
441
|
if (name === "RSASSA-PKCS1-V1_5") {
|
|
372
442
|
return _toArrayBuffer(nodeCrypto.sign(_hashNode(key.algorithm.hash, "sign"), buf, key._handle));
|
|
@@ -394,7 +464,7 @@ SubtleCrypto.prototype.sign = async function sign(algorithm, key, data) {
|
|
|
394
464
|
hm.update(buf);
|
|
395
465
|
return _toArrayBuffer(hm.digest());
|
|
396
466
|
}
|
|
397
|
-
throw new WebCryptoError("webcrypto/not-supported", "sign: unsupported algorithm " +
|
|
467
|
+
throw new WebCryptoError("webcrypto/not-supported", "sign: unsupported algorithm " + _stringify(name));
|
|
398
468
|
};
|
|
399
469
|
|
|
400
470
|
SubtleCrypto.prototype.verify = async function verify(algorithm, key, signature, data) {
|
|
@@ -403,7 +473,7 @@ SubtleCrypto.prototype.verify = async function verify(algorithm, key, signature,
|
|
|
403
473
|
var sig = _toBuf(signature, "verify");
|
|
404
474
|
var buf = _toBuf(data, "verify");
|
|
405
475
|
var name = alg.name;
|
|
406
|
-
if (!SIGN_VERIFY_NAMES[name]) throw new WebCryptoError("webcrypto/not-supported", "verify: unsupported algorithm " +
|
|
476
|
+
if (!SIGN_VERIFY_NAMES[name]) throw new WebCryptoError("webcrypto/not-supported", "verify: unsupported algorithm " + _stringify(name));
|
|
407
477
|
_requireAlgMatch(alg, key, "verify");
|
|
408
478
|
if (name === "RSASSA-PKCS1-V1_5") {
|
|
409
479
|
return nodeCrypto.verify(_hashNode(key.algorithm.hash, "verify"), buf, key._handle, sig);
|
|
@@ -437,7 +507,7 @@ SubtleCrypto.prototype.verify = async function verify(algorithm, key, signature,
|
|
|
437
507
|
var digest = hm.digest();
|
|
438
508
|
return guard.crypto.constantTimeEqual(digest, sig);
|
|
439
509
|
}
|
|
440
|
-
throw new WebCryptoError("webcrypto/not-supported", "verify: unsupported algorithm " +
|
|
510
|
+
throw new WebCryptoError("webcrypto/not-supported", "verify: unsupported algorithm " + _stringify(name));
|
|
441
511
|
};
|
|
442
512
|
|
|
443
513
|
// Run a node:crypto AES cipher sequence, converting any raw node fault -- a bad IV length,
|
|
@@ -455,7 +525,7 @@ SubtleCrypto.prototype.encrypt = async function encrypt(algorithm, key, data) {
|
|
|
455
525
|
_requireUsage(key, "encrypt");
|
|
456
526
|
var buf = _toBuf(data, "encrypt");
|
|
457
527
|
var name = alg.name;
|
|
458
|
-
if (!ENCRYPT_DECRYPT_NAMES[name]) throw new WebCryptoError("webcrypto/not-supported", "encrypt: unsupported algorithm " +
|
|
528
|
+
if (!ENCRYPT_DECRYPT_NAMES[name]) throw new WebCryptoError("webcrypto/not-supported", "encrypt: unsupported algorithm " + _stringify(name));
|
|
459
529
|
_requireAlgMatch(alg, key, "encrypt");
|
|
460
530
|
if (name === "RSA-OAEP") {
|
|
461
531
|
return _toArrayBuffer(nodeCrypto.publicEncrypt({
|
|
@@ -472,7 +542,7 @@ SubtleCrypto.prototype.encrypt = async function encrypt(algorithm, key, data) {
|
|
|
472
542
|
var cipher = nodeCrypto.createCipheriv("aes-" + key.algorithm.length + "-gcm", kb, iv, { authTagLength: (alg.tagLength || 128) / 8 });
|
|
473
543
|
if (aad) cipher.setAAD(aad);
|
|
474
544
|
var ct = guard.secret.cipherFinish(cipher, buf, WebCryptoError, "webcrypto/operation", "an AES-GCM encrypt intermediate");
|
|
475
|
-
return
|
|
545
|
+
return _bufferConcat([ct, cipher.getAuthTag()]);
|
|
476
546
|
});
|
|
477
547
|
}, "encrypt"));
|
|
478
548
|
}
|
|
@@ -495,7 +565,7 @@ SubtleCrypto.prototype.encrypt = async function encrypt(algorithm, key, data) {
|
|
|
495
565
|
});
|
|
496
566
|
}, "encrypt"));
|
|
497
567
|
}
|
|
498
|
-
throw new WebCryptoError("webcrypto/not-supported", "encrypt: unsupported algorithm " +
|
|
568
|
+
throw new WebCryptoError("webcrypto/not-supported", "encrypt: unsupported algorithm " + _stringify(name));
|
|
499
569
|
};
|
|
500
570
|
|
|
501
571
|
SubtleCrypto.prototype.decrypt = async function decrypt(algorithm, key, data) {
|
|
@@ -503,7 +573,7 @@ SubtleCrypto.prototype.decrypt = async function decrypt(algorithm, key, data) {
|
|
|
503
573
|
_requireUsage(key, "decrypt");
|
|
504
574
|
var buf = _toBuf(data, "decrypt");
|
|
505
575
|
var name = alg.name;
|
|
506
|
-
if (!ENCRYPT_DECRYPT_NAMES[name]) throw new WebCryptoError("webcrypto/not-supported", "decrypt: unsupported algorithm " +
|
|
576
|
+
if (!ENCRYPT_DECRYPT_NAMES[name]) throw new WebCryptoError("webcrypto/not-supported", "decrypt: unsupported algorithm " + _stringify(name));
|
|
507
577
|
_requireAlgMatch(alg, key, "decrypt");
|
|
508
578
|
if (name === "RSA-OAEP") {
|
|
509
579
|
// For a CMS key-transport recipient this plaintext IS the recovered content-encryption key, and
|
|
@@ -521,8 +591,10 @@ SubtleCrypto.prototype.decrypt = async function decrypt(algorithm, key, data) {
|
|
|
521
591
|
var tagLen = (alg.tagLength || 128) / 8;
|
|
522
592
|
var iv = _toBuf(alg.iv, "AES-GCM iv");
|
|
523
593
|
if (buf.length < tagLen) throw new WebCryptoError("webcrypto/operation", "decrypt: AES-GCM ciphertext is shorter than its authentication tag");
|
|
524
|
-
|
|
525
|
-
|
|
594
|
+
// The split between ciphertext and tag decides which bytes are authenticated, so the window it
|
|
595
|
+
// is taken with is the captured one: a replaced `subarray` hands the tag check other bytes.
|
|
596
|
+
var ct = _subarray(buf, 0, buf.length - tagLen);
|
|
597
|
+
var tag = _subarray(buf, buf.length - tagLen);
|
|
526
598
|
var gcmAad = alg.additionalData ? _toBuf(alg.additionalData, "AES-GCM aad") : null;
|
|
527
599
|
return _toArrayBuffer(_runCipher(function () {
|
|
528
600
|
return _withSecretBytes(key, function (kb) {
|
|
@@ -554,7 +626,7 @@ SubtleCrypto.prototype.decrypt = async function decrypt(algorithm, key, data) {
|
|
|
554
626
|
});
|
|
555
627
|
}, "decrypt"));
|
|
556
628
|
}
|
|
557
|
-
throw new WebCryptoError("webcrypto/not-supported", "decrypt: unsupported algorithm " +
|
|
629
|
+
throw new WebCryptoError("webcrypto/not-supported", "decrypt: unsupported algorithm " + _stringify(name));
|
|
558
630
|
};
|
|
559
631
|
|
|
560
632
|
// _withSecretBytes(key, fn) -- export a secret key's raw material, hand it to fn, and wipe
|
|
@@ -607,7 +679,7 @@ function _requireCtrLength128(alg) {
|
|
|
607
679
|
// the `length / 8` narrowing in the per-algorithm branches below would
|
|
608
680
|
// otherwise silently truncate a fractional byte count.
|
|
609
681
|
function _requireDeriveLength(length, who) {
|
|
610
|
-
if (typeof length !== "number" || !
|
|
682
|
+
if (typeof length !== "number" || !_isFinite(length) || length <= 0 || length % 8 !== 0) {
|
|
611
683
|
throw new WebCryptoError("webcrypto/operation", who + ": length must be a positive multiple of 8 bits");
|
|
612
684
|
}
|
|
613
685
|
}
|
|
@@ -622,7 +694,7 @@ function _requireDeriveLength(length, who) {
|
|
|
622
694
|
// Concurrency is CAPPED so many high-iteration jobs cannot monopolize the whole worker pool and starve
|
|
623
695
|
// unrelated DNS / filesystem / crypto work: at least two pool threads are left free (default pool is four),
|
|
624
696
|
// and derivations beyond the cap queue and run as slots free. The cap tracks UV_THREADPOOL_SIZE when raised.
|
|
625
|
-
var _PBKDF2_MAX_CONCURRENT =
|
|
697
|
+
var _PBKDF2_MAX_CONCURRENT = _max(1, (_parseInt(process.env.UV_THREADPOOL_SIZE, 10) || 4) - 2);
|
|
626
698
|
var _pbkdf2InFlight = 0;
|
|
627
699
|
var _pbkdf2Waiters = [];
|
|
628
700
|
function _pbkdf2Async(pw, salt, iterations, keylen, digest) {
|
|
@@ -663,7 +735,7 @@ async function _deriveBitsRaw(alg, key, length) {
|
|
|
663
735
|
if (length / 8 > secret.length) {
|
|
664
736
|
throw new WebCryptoError("webcrypto/operation", name + ": requested " + length + " bits but the shared secret has " + (secret.length * 8));
|
|
665
737
|
}
|
|
666
|
-
return _toArrayBuffer(secret
|
|
738
|
+
return _toArrayBuffer(_subarray(secret, 0, length / 8));
|
|
667
739
|
} finally {
|
|
668
740
|
guard.secret.zeroize(secret, WebCryptoError, "webcrypto/operation", "the raw key-agreement shared secret");
|
|
669
741
|
}
|
|
@@ -674,8 +746,8 @@ async function _deriveBitsRaw(alg, key, length) {
|
|
|
674
746
|
// itself. A controllable allocation, not one of the runtime-internal copies the best-effort
|
|
675
747
|
// caveat covers, so it is wiped once the derivation has consumed it.
|
|
676
748
|
return _withSecretBytes(key, function (ikm) {
|
|
677
|
-
var derived = nodeCrypto.hkdfSync(_hashNode(alg.hash, "HKDF"), ikm, _toBuf(alg.salt, "HKDF salt"), _toBuf(alg.info ||
|
|
678
|
-
return derived instanceof ArrayBuffer ? derived : _toArrayBuffer(
|
|
749
|
+
var derived = nodeCrypto.hkdfSync(_hashNode(alg.hash, "HKDF"), ikm, _toBuf(alg.salt, "HKDF salt"), _toBuf(alg.info || _bufferAlloc(0), "HKDF info"), length / 8);
|
|
750
|
+
return derived instanceof ArrayBuffer ? derived : _toArrayBuffer(_bufferFrom(derived));
|
|
679
751
|
});
|
|
680
752
|
}
|
|
681
753
|
if (name === "PBKDF2") {
|
|
@@ -700,13 +772,13 @@ async function _deriveBitsRaw(alg, key, length) {
|
|
|
700
772
|
// The base key here HOLDS the ECDH shared secret Z of an RFC 5753 kari, so this export is
|
|
701
773
|
// exactly as sensitive as the raw agreement secret wiped above.
|
|
702
774
|
return _withSecretBytes(key, function (z) {
|
|
703
|
-
var derived = _x963Kdf(_hashNode(alg.hash, "X963KDF"), z, _toBuf(alg.info ||
|
|
775
|
+
var derived = _x963Kdf(_hashNode(alg.hash, "X963KDF"), z, _toBuf(alg.info || _bufferAlloc(0), "X963KDF SharedInfo"), length / 8);
|
|
704
776
|
// Same as PBKDF2 above: the copy is what the caller receives, so the derived buffer is cleared.
|
|
705
777
|
try { return _toArrayBuffer(derived); }
|
|
706
778
|
finally { guard.secret.zeroize(derived, WebCryptoError, "webcrypto/operation", "the X9.63 KDF output"); }
|
|
707
779
|
});
|
|
708
780
|
}
|
|
709
|
-
throw new WebCryptoError("webcrypto/not-supported", "deriveBits: unsupported algorithm " +
|
|
781
|
+
throw new WebCryptoError("webcrypto/not-supported", "deriveBits: unsupported algorithm " + _stringify(name));
|
|
710
782
|
}
|
|
711
783
|
|
|
712
784
|
// The X9.63 single-step KDF counter loop. Bounded by the 2^32 counter range; every input is a
|
|
@@ -715,16 +787,16 @@ function _x963Kdf(hashNode, z, sharedInfo, lenBytes) {
|
|
|
715
787
|
var blocks = [], counter = 1, got = 0;
|
|
716
788
|
while (got < lenBytes) {
|
|
717
789
|
if (counter > 0xffffffff) throw new WebCryptoError("webcrypto/operation", "X963KDF: requested output exceeds the counter range");
|
|
718
|
-
var ctr =
|
|
790
|
+
var ctr = _bufferAlloc(4); ctr.writeUInt32BE(counter, 0);
|
|
719
791
|
var h = nodeCrypto.createHash(hashNode).update(z).update(ctr).update(sharedInfo).digest();
|
|
720
|
-
blocks
|
|
792
|
+
_push(blocks, h); got += h.length; counter += 1;
|
|
721
793
|
}
|
|
722
794
|
// Return an exact-sized buffer the caller wholly owns, not a VIEW over the joined blocks: a
|
|
723
795
|
// caller clearing what it received would otherwise leave the unused tail of the final digest
|
|
724
796
|
// block -- derived from the shared secret -- readable behind it. The accumulator and the
|
|
725
797
|
// per-block digests are cleared here, where they were allocated.
|
|
726
|
-
var joined =
|
|
727
|
-
var exact =
|
|
798
|
+
var joined = _bufferConcat(blocks);
|
|
799
|
+
var exact = _bufferAlloc(lenBytes);
|
|
728
800
|
joined.copy(exact, 0, 0, lenBytes);
|
|
729
801
|
guard.secret.zeroize(joined, WebCryptoError, "webcrypto/operation", "the KDF accumulator");
|
|
730
802
|
for (var bi = 0; bi < blocks.length; bi++) guard.secret.zeroize(blocks[bi], WebCryptoError, "webcrypto/operation", "a KDF block");
|
|
@@ -734,7 +806,7 @@ function _x963Kdf(hashNode, z, sharedInfo, lenBytes) {
|
|
|
734
806
|
SubtleCrypto.prototype.deriveBits = async function deriveBits(algorithm, key, length) {
|
|
735
807
|
var alg = _normalizeAlg(algorithm, "deriveBits");
|
|
736
808
|
_requireUsage(key, "deriveBits");
|
|
737
|
-
if (!DERIVE_NAMES[alg.name]) throw new WebCryptoError("webcrypto/not-supported", "deriveBits: unsupported algorithm " +
|
|
809
|
+
if (!DERIVE_NAMES[alg.name]) throw new WebCryptoError("webcrypto/not-supported", "deriveBits: unsupported algorithm " + _stringify(alg.name));
|
|
738
810
|
_requireAlgMatch(alg, key, "deriveBits");
|
|
739
811
|
return _deriveBitsRaw(alg, key, length);
|
|
740
812
|
};
|
|
@@ -746,11 +818,11 @@ SubtleCrypto.prototype.deriveKey = async function deriveKey(algorithm, baseKey,
|
|
|
746
818
|
// here and route the raw derivation past deriveBits' own usage gate.
|
|
747
819
|
_requireUsage(baseKey, "deriveKey");
|
|
748
820
|
var alg = _normalizeAlg(algorithm, "deriveKey");
|
|
749
|
-
if (!DERIVE_NAMES[alg.name]) throw new WebCryptoError("webcrypto/not-supported", "deriveKey: unsupported algorithm " +
|
|
821
|
+
if (!DERIVE_NAMES[alg.name]) throw new WebCryptoError("webcrypto/not-supported", "deriveKey: unsupported algorithm " + _stringify(alg.name));
|
|
750
822
|
_requireAlgMatch(alg, baseKey, "deriveKey");
|
|
751
823
|
var dk = _normalizeAlg(derivedKeyType, "deriveKey");
|
|
752
824
|
var bits;
|
|
753
|
-
if (dk.name
|
|
825
|
+
if (_stringIndexOf(dk.name, "AES") === 0) {
|
|
754
826
|
// W3C get-key-length for AES: the derivedKeyType MUST carry a length of
|
|
755
827
|
// 128/192/256. Without this check the derived key would silently take
|
|
756
828
|
// the size of the raw agreement/KDF output (e.g. a 384-bit "AES" key
|
|
@@ -775,7 +847,7 @@ SubtleCrypto.prototype.deriveKey = async function deriveKey(algorithm, baseKey,
|
|
|
775
847
|
// failing import too (a bad derivedKeyType is caller-controlled).
|
|
776
848
|
var raw = await _deriveBitsRaw(alg, baseKey, bits);
|
|
777
849
|
try { return await this.importKey("raw", raw, dk, extractable, keyUsages); }
|
|
778
|
-
finally { guard.secret.zeroize(new
|
|
850
|
+
finally { guard.secret.zeroize(new _Uint8Array(raw), WebCryptoError, "webcrypto/operation", "the derived key material"); }
|
|
779
851
|
};
|
|
780
852
|
|
|
781
853
|
// ML-KEM (FIPS 203) key encapsulation over Node's crypto.encapsulate/decapsulate.
|
|
@@ -791,9 +863,9 @@ SubtleCrypto.prototype.deriveKey = async function deriveKey(algorithm, baseKey,
|
|
|
791
863
|
SubtleCrypto.prototype.encapsulateBits = async function encapsulateBits(algorithm, encapsulationKey) {
|
|
792
864
|
var alg = _normalizeAlg(algorithm, "encapsulateBits");
|
|
793
865
|
_requireUsage(encapsulationKey, "encapsulateBits");
|
|
794
|
-
if (!ML_KEM_NODE[alg.name]) throw new WebCryptoError("webcrypto/not-supported", "encapsulateBits: unsupported algorithm " +
|
|
866
|
+
if (!ML_KEM_NODE[alg.name]) throw new WebCryptoError("webcrypto/not-supported", "encapsulateBits: unsupported algorithm " + _stringify(alg.name));
|
|
795
867
|
_requireAlgMatch(alg, encapsulationKey, "encapsulateBits");
|
|
796
|
-
if (encapsulationKey.type !== "public") throw new WebCryptoError("webcrypto/invalid-access", "encapsulateBits requires a public (encapsulation) key, got " +
|
|
868
|
+
if (encapsulationKey.type !== "public") throw new WebCryptoError("webcrypto/invalid-access", "encapsulateBits requires a public (encapsulation) key, got " + _stringify(encapsulationKey.type));
|
|
797
869
|
var r;
|
|
798
870
|
try { r = nodeCrypto.encapsulate(encapsulationKey._handle); }
|
|
799
871
|
catch (e) { throw new WebCryptoError("webcrypto/operation", "encapsulateBits: ML-KEM encapsulation failed", e); }
|
|
@@ -814,9 +886,9 @@ SubtleCrypto.prototype.encapsulateBits = async function encapsulateBits(algorith
|
|
|
814
886
|
SubtleCrypto.prototype.decapsulateBits = async function decapsulateBits(algorithm, decapsulationKey, ciphertext) {
|
|
815
887
|
var alg = _normalizeAlg(algorithm, "decapsulateBits");
|
|
816
888
|
_requireUsage(decapsulationKey, "decapsulateBits");
|
|
817
|
-
if (!ML_KEM_NODE[alg.name]) throw new WebCryptoError("webcrypto/not-supported", "decapsulateBits: unsupported algorithm " +
|
|
889
|
+
if (!ML_KEM_NODE[alg.name]) throw new WebCryptoError("webcrypto/not-supported", "decapsulateBits: unsupported algorithm " + _stringify(alg.name));
|
|
818
890
|
_requireAlgMatch(alg, decapsulationKey, "decapsulateBits");
|
|
819
|
-
if (decapsulationKey.type !== "private") throw new WebCryptoError("webcrypto/invalid-access", "decapsulateBits requires a private (decapsulation) key, got " +
|
|
891
|
+
if (decapsulationKey.type !== "private") throw new WebCryptoError("webcrypto/invalid-access", "decapsulateBits requires a private (decapsulation) key, got " + _stringify(decapsulationKey.type));
|
|
820
892
|
var ct = _toBuf(ciphertext, "decapsulateBits ciphertext");
|
|
821
893
|
// FIPS 203 sec. 7.3 makes the ciphertext-length check the ONE per-execution input check a
|
|
822
894
|
// decapsulating party owes, so it belongs here, at the engine boundary, rather than only in the
|
|
@@ -858,25 +930,25 @@ SubtleCrypto.prototype.wrapKey = async function wrapKey(format, key, wrappingKey
|
|
|
858
930
|
// exists to protect. It is this function's allocation and is cleared once the wrap has consumed
|
|
859
931
|
// it, on the delegated branch as well as the AES-KW one.
|
|
860
932
|
var exported = await this.exportKey(format, key);
|
|
861
|
-
var bytes = (format === "jwk") ?
|
|
933
|
+
var bytes = (format === "jwk") ? _bufferFrom(_stringify(exported)) : _bufferFrom(exported);
|
|
862
934
|
try {
|
|
863
935
|
var alg = _normalizeAlg(wrapAlgorithm, "wrapKey");
|
|
864
936
|
_requireUsage(wrappingKey, "wrapKey");
|
|
865
|
-
if (alg.name !== "AES-KW" && !ENCRYPT_DECRYPT_NAMES[alg.name]) throw new WebCryptoError("webcrypto/not-supported", "wrapKey: unsupported algorithm " +
|
|
937
|
+
if (alg.name !== "AES-KW" && !ENCRYPT_DECRYPT_NAMES[alg.name]) throw new WebCryptoError("webcrypto/not-supported", "wrapKey: unsupported algorithm " + _stringify(alg.name));
|
|
866
938
|
_requireAlgMatch(alg, wrappingKey, "wrapKey");
|
|
867
939
|
if (alg.name === "AES-KW") {
|
|
868
940
|
// AES-KW wraps 64-bit blocks (RFC 3394): the serialized key MUST be a multiple of 8
|
|
869
941
|
// bytes and at least 16. A "jwk" serialization (arbitrary-length JSON) or any other
|
|
870
942
|
// non-conforming length is an OperationError -- a typed verdict, never a raw node fault.
|
|
871
943
|
if (bytes.length < 16 || bytes.length % 8 !== 0) {
|
|
872
|
-
throw new WebCryptoError("webcrypto/operation", "wrapKey: AES-KW requires the serialized key be a multiple of 8 bytes (>= 16); got " + bytes.length + " -- format " +
|
|
944
|
+
throw new WebCryptoError("webcrypto/operation", "wrapKey: AES-KW requires the serialized key be a multiple of 8 bytes (>= 16); got " + bytes.length + " -- format " + _stringify(format) + " is not AES-KW-wrappable");
|
|
873
945
|
}
|
|
874
946
|
// The mirror of unwrapKey below: in a KEM flow this export is the SENDER's copy of the same
|
|
875
947
|
// key-encryption key, so leaving it unwiped would keep a full copy of the KEK alive for the
|
|
876
948
|
// process lifetime and make the wipes the CMS layer performs pointless in the encrypt direction.
|
|
877
949
|
try {
|
|
878
950
|
return _withSecretBytes(wrappingKey, function (wkBytes) {
|
|
879
|
-
var c = nodeCrypto.createCipheriv("aes" + wrappingKey.algorithm.length + "-wrap", wkBytes,
|
|
951
|
+
var c = nodeCrypto.createCipheriv("aes" + wrappingKey.algorithm.length + "-wrap", wkBytes, _bufferFrom("A6A6A6A6A6A6A6A6", "hex"));
|
|
880
952
|
return _toArrayBuffer(guard.secret.cipherFinish(c, bytes, WebCryptoError, "webcrypto/operation", "an AES-KW wrap intermediate"));
|
|
881
953
|
});
|
|
882
954
|
} catch (e) { throw new WebCryptoError("webcrypto/operation", "wrapKey: AES-KW key wrap failed", e); }
|
|
@@ -902,7 +974,7 @@ SubtleCrypto.prototype.unwrapKey = async function unwrapKey(format, wrappedKey,
|
|
|
902
974
|
// never verify the caller was actually permitted to unwrap (mirrors
|
|
903
975
|
// wrapKey, which checks before both of its paths).
|
|
904
976
|
_requireUsage(unwrappingKey, "unwrapKey");
|
|
905
|
-
if (alg.name !== "AES-KW" && !ENCRYPT_DECRYPT_NAMES[alg.name]) throw new WebCryptoError("webcrypto/not-supported", "unwrapKey: unsupported algorithm " +
|
|
977
|
+
if (alg.name !== "AES-KW" && !ENCRYPT_DECRYPT_NAMES[alg.name]) throw new WebCryptoError("webcrypto/not-supported", "unwrapKey: unsupported algorithm " + _stringify(alg.name));
|
|
906
978
|
_requireAlgMatch(alg, unwrappingKey, "unwrapKey");
|
|
907
979
|
var bytes;
|
|
908
980
|
if (alg.name === "AES-KW") {
|
|
@@ -921,13 +993,13 @@ SubtleCrypto.prototype.unwrapKey = async function unwrapKey(format, wrappedKey,
|
|
|
921
993
|
// would also break the fuzz-harness contract.
|
|
922
994
|
try {
|
|
923
995
|
bytes = _withSecretBytes(unwrappingKey, function (kwBytes) {
|
|
924
|
-
var d = nodeCrypto.createDecipheriv("aes" + unwrappingKey.algorithm.length + "-wrap", kwBytes,
|
|
996
|
+
var d = nodeCrypto.createDecipheriv("aes" + unwrappingKey.algorithm.length + "-wrap", kwBytes, _bufferFrom("A6A6A6A6A6A6A6A6", "hex"));
|
|
925
997
|
return guard.secret.cipherFinish(d, wrapped, WebCryptoError, "webcrypto/operation", "the unwrapped key material");
|
|
926
998
|
});
|
|
927
999
|
} catch (e) { throw new WebCryptoError("webcrypto/operation", "unwrapKey: AES-KW key unwrap failed (integrity or length)", e); }
|
|
928
1000
|
} else {
|
|
929
1001
|
var unwrapKeyClone = _cloneWithUsage(unwrappingKey, "decrypt");
|
|
930
|
-
bytes =
|
|
1002
|
+
bytes = _bufferFrom(await this.decrypt(unwrapAlgorithm, unwrapKeyClone, wrappedKey));
|
|
931
1003
|
}
|
|
932
1004
|
var keyData;
|
|
933
1005
|
if (format === "jwk") {
|
|
@@ -955,7 +1027,7 @@ SubtleCrypto.prototype.unwrapKey = async function unwrapKey(format, wrappedKey,
|
|
|
955
1027
|
};
|
|
956
1028
|
|
|
957
1029
|
function _cloneWithUsage(key, usage) {
|
|
958
|
-
var k = new CryptoKey(key.type, key.extractable, key.algorithm, key.usages
|
|
1030
|
+
var k = new CryptoKey(key.type, key.extractable, key.algorithm, _concat(key.usages, [usage]), key._handle);
|
|
959
1031
|
return k;
|
|
960
1032
|
}
|
|
961
1033
|
|
|
@@ -1034,7 +1106,7 @@ function _validateMlKemInner(innerBytes, sizes) {
|
|
|
1034
1106
|
// per-algorithm structural check lives here, at the one import boundary. Only ML-KEM needs it
|
|
1035
1107
|
// today: the engine ACCEPTS the OpenSSL-legacy bare-seed layout RFC 9935 sec. 6 forbids.
|
|
1036
1108
|
var PKCS8_INNER_VALIDATORS = {};
|
|
1037
|
-
|
|
1109
|
+
_forEach(_keys(ML_KEM_INNER), function (o) { PKCS8_INNER_VALIDATORS[o] = function (inner) { _validateMlKemInner(inner, ML_KEM_INNER[o]); }; });
|
|
1038
1110
|
|
|
1039
1111
|
function _preValidatePkcs8(p8, name) {
|
|
1040
1112
|
if (!ML_KEM_NODE[name]) return; // only the ML-KEM CHOICE carries the bare-seed hazard
|
|
@@ -1090,7 +1162,7 @@ SubtleCrypto.prototype.importKey = async function importKey(format, keyData, alg
|
|
|
1090
1162
|
// A JWK is a JSON object; null / undefined / an array / a primitive (incl. the JSON token
|
|
1091
1163
|
// `null` from an unwrapKey over non-authenticating ciphertext) fails closed as a typed
|
|
1092
1164
|
// DataError, never a raw property-access TypeError.
|
|
1093
|
-
if (jwk === null || typeof jwk !== "object" ||
|
|
1165
|
+
if (jwk === null || typeof jwk !== "object" || _isArray(jwk)) {
|
|
1094
1166
|
throw new WebCryptoError("webcrypto/data", "importKey jwk: keyData must be a JsonWebKey object");
|
|
1095
1167
|
}
|
|
1096
1168
|
if (jwk.kty === "oct") {
|
|
@@ -1109,12 +1181,12 @@ SubtleCrypto.prototype.importKey = async function importKey(format, keyData, alg
|
|
|
1109
1181
|
// unrecognized member to be ignored, so a member of that name on an EC or OKP JWK is an
|
|
1110
1182
|
// extension this implementation has no meaning for -- reading it as private material there
|
|
1111
1183
|
// would turn a valid public-key import into a failure.
|
|
1112
|
-
var isPrivate =
|
|
1113
|
-
(jwk.kty === "AKP" &&
|
|
1184
|
+
var isPrivate = _hasOwn(jwk, "d") ||
|
|
1185
|
+
(jwk.kty === "AKP" && _hasOwn(jwk, "priv"));
|
|
1114
1186
|
var ko = _nodeKey(function () { return isPrivate ? nodeCrypto.createPrivateKey({ key: jwk, format: "jwk" }) : nodeCrypto.createPublicKey({ key: jwk, format: "jwk" }); }, "importKey jwk");
|
|
1115
1187
|
return new CryptoKey(isPrivate ? "private" : "public", isPrivate ? extractable : true, _algFromImport(name, alg, ko), usages, ko);
|
|
1116
1188
|
}
|
|
1117
|
-
throw new WebCryptoError("webcrypto/not-supported", "importKey: unsupported format " +
|
|
1189
|
+
throw new WebCryptoError("webcrypto/not-supported", "importKey: unsupported format " + _stringify(format));
|
|
1118
1190
|
};
|
|
1119
1191
|
|
|
1120
1192
|
function _importRawPublic(name, alg, raw, extractable, usages) {
|
|
@@ -1128,11 +1200,11 @@ function _importRawPublic(name, alg, raw, extractable, usages) {
|
|
|
1128
1200
|
if (name === "ECDSA" || name === "ECDH") {
|
|
1129
1201
|
var fb = CURVE_FIELD_BYTES[alg.namedCurve];
|
|
1130
1202
|
if (!fb || raw[0] !== 0x04 || raw.length !== 1 + 2 * fb) throw new WebCryptoError("webcrypto/data", "importKey raw EC: expected an uncompressed point for " + alg.namedCurve);
|
|
1131
|
-
var ecjwk = { kty: "EC", crv: alg.namedCurve, x: _bufToB64url(raw
|
|
1203
|
+
var ecjwk = { kty: "EC", crv: alg.namedCurve, x: _bufToB64url(_subarray(raw, 1, 1 + fb)), y: _bufToB64url(_subarray(raw, 1 + fb)) };
|
|
1132
1204
|
var eck = nodeCrypto.createPublicKey({ key: ecjwk, format: "jwk" });
|
|
1133
1205
|
return new CryptoKey("public", true, { name: name, namedCurve: alg.namedCurve }, usages, eck);
|
|
1134
1206
|
}
|
|
1135
|
-
throw new WebCryptoError("webcrypto/not-supported", "importKey raw: unsupported public-key algorithm " +
|
|
1207
|
+
throw new WebCryptoError("webcrypto/not-supported", "importKey raw: unsupported public-key algorithm " + _stringify(name));
|
|
1136
1208
|
}
|
|
1137
1209
|
|
|
1138
1210
|
// The node asymmetricKeyType(s) each imported asymmetric algorithm requires. The key TYPE
|
|
@@ -1149,7 +1221,7 @@ var IMPORT_KEY_TYPE = {
|
|
|
1149
1221
|
// algorithm name maps to exactly its node asymmetricKeyType, so an RSA (or any) key imported
|
|
1150
1222
|
// under an ML-DSA / ML-KEM / SLH-DSA name is a DataError, not a mislabeled PQC CryptoKey.
|
|
1151
1223
|
[ML_DSA_NODE, ML_KEM_NODE, SLH_DSA_NODE].forEach(function (m) {
|
|
1152
|
-
|
|
1224
|
+
_forEach(_keys(m), function (webName) { var t = {}; t[m[webName]] = 1; IMPORT_KEY_TYPE[webName] = t; });
|
|
1153
1225
|
});
|
|
1154
1226
|
function _algFromImport(name, alg, keyObject) {
|
|
1155
1227
|
var wantType = IMPORT_KEY_TYPE[name];
|
|
@@ -1169,8 +1241,8 @@ function _algFromImport(name, alg, keyObject) {
|
|
|
1169
1241
|
throw new WebCryptoError("webcrypto/not-supported", name + ": imported key uses an unsupported EC curve");
|
|
1170
1242
|
}
|
|
1171
1243
|
if (alg.namedCurve && alg.namedCurve !== actualCurve) {
|
|
1172
|
-
throw new WebCryptoError("webcrypto/data", name + ": importKey namedCurve " +
|
|
1173
|
-
" does not match the imported key's curve " +
|
|
1244
|
+
throw new WebCryptoError("webcrypto/data", name + ": importKey namedCurve " + _stringify(alg.namedCurve) +
|
|
1245
|
+
" does not match the imported key's curve " + _stringify(actualCurve));
|
|
1174
1246
|
}
|
|
1175
1247
|
return { name: name, namedCurve: actualCurve };
|
|
1176
1248
|
}
|
|
@@ -1243,13 +1315,13 @@ SubtleCrypto.prototype.exportKey = async function exportKey(format, key) {
|
|
|
1243
1315
|
}
|
|
1244
1316
|
return _toArrayBuffer(_rawPublic(key));
|
|
1245
1317
|
}
|
|
1246
|
-
throw new WebCryptoError("webcrypto/not-supported", "exportKey: unsupported format " +
|
|
1318
|
+
throw new WebCryptoError("webcrypto/not-supported", "exportKey: unsupported format " + _stringify(format));
|
|
1247
1319
|
};
|
|
1248
1320
|
|
|
1249
1321
|
function _rawPublic(key) {
|
|
1250
1322
|
var jwk = key._handle.export({ format: "jwk" });
|
|
1251
1323
|
if (jwk.kty === "OKP") return _b64urlToBuf(jwk.x);
|
|
1252
|
-
if (jwk.kty === "EC") return
|
|
1324
|
+
if (jwk.kty === "EC") return _bufferConcat([_bufferFrom([0x04]), _b64urlToBuf(jwk.x), _b64urlToBuf(jwk.y)]);
|
|
1253
1325
|
throw new WebCryptoError("webcrypto/not-supported", "exportKey raw: unsupported key type " + jwk.kty);
|
|
1254
1326
|
}
|
|
1255
1327
|
|
|
@@ -1275,7 +1347,10 @@ function Crypto() {
|
|
|
1275
1347
|
}
|
|
1276
1348
|
|
|
1277
1349
|
Crypto.prototype.getRandomValues = function getRandomValues(typedArray) {
|
|
1278
|
-
|
|
1350
|
+
// The kinds W3C WebCrypto refuses here are settled from the value's internal slot, not from an
|
|
1351
|
+
// `instanceof` against a replaceable global: replace `Float32Array` after load and the comparison
|
|
1352
|
+
// is against a different constructor, so a real Float32Array passes a check written to reject it.
|
|
1353
|
+
if (!_isView(typedArray) || _types.isFloat32Array(typedArray) || _types.isFloat64Array(typedArray) || _types.isDataView(typedArray)) {
|
|
1279
1354
|
throw new WebCryptoError("webcrypto/data", "getRandomValues: expected an integer TypedArray");
|
|
1280
1355
|
}
|
|
1281
1356
|
// Re-view the (already integer-TypedArray-validated) output buffer through the OUTPUT door --
|
|
@@ -1327,14 +1402,14 @@ function decompressEcPoint(sec1Compressed, nodeCurve, E, code) {
|
|
|
1327
1402
|
var _crypto = new Crypto();
|
|
1328
1403
|
// Null-prototype: is caller-supplied, and an inherited member ("constructor", "toString")
|
|
1329
1404
|
// would otherwise resolve to a truthy non-format and slip past the check below.
|
|
1330
|
-
var _EXPORT_FORMAT =
|
|
1405
|
+
var _EXPORT_FORMAT = _assign(_create(null), { "private": "pkcs8", "public": "spki", "secret": "raw" });
|
|
1331
1406
|
|
|
1332
1407
|
// @internal
|
|
1333
1408
|
// True for any object shaped like a WebCrypto CryptoKey, whichever implementation minted it.
|
|
1334
1409
|
function isCryptoKeyLike(x) {
|
|
1335
1410
|
return !!x && typeof x === "object" && typeof x.type === "string" &&
|
|
1336
1411
|
typeof x.extractable === "boolean" && !!x.algorithm && typeof x.algorithm === "object" &&
|
|
1337
|
-
typeof x.algorithm.name === "string" &&
|
|
1412
|
+
typeof x.algorithm.name === "string" && _isArray(x.usages);
|
|
1338
1413
|
}
|
|
1339
1414
|
|
|
1340
1415
|
// @internal
|
|
@@ -1346,9 +1421,9 @@ function exportAnyKey(key, E, code) {
|
|
|
1346
1421
|
var format = _EXPORT_FORMAT[key && key.type];
|
|
1347
1422
|
if (!format) throw E(code, "a CryptoKey with a private, public, or secret type is required");
|
|
1348
1423
|
if (key instanceof CryptoKey) {
|
|
1349
|
-
return Promise
|
|
1424
|
+
return _resolve(Promise)
|
|
1350
1425
|
.then(function () { return _crypto.subtle.exportKey(format, key); })
|
|
1351
|
-
.then(function (b) { return
|
|
1426
|
+
.then(function (b) { return _bufferFrom(b); });
|
|
1352
1427
|
}
|
|
1353
1428
|
if (!key.extractable) {
|
|
1354
1429
|
throw E(code, "the CryptoKey comes from a different WebCrypto implementation and is not extractable, so its key material cannot be reached; import it through pki.webcrypto.subtle, or pass the key as DER");
|
|
@@ -1360,18 +1435,18 @@ function exportAnyKey(key, E, code) {
|
|
|
1360
1435
|
// key's own extractable promise is this branch's responsibility, not the crypto library's.
|
|
1361
1436
|
var handle = key._handle;
|
|
1362
1437
|
if (handle instanceof nodeCrypto.KeyObject && handle.type === key.type) {
|
|
1363
|
-
return Promise
|
|
1438
|
+
return _resolve(Promise).then(function () {
|
|
1364
1439
|
return format === "raw" ? handle.export() : handle.export({ format: "der", type: format });
|
|
1365
|
-
}).then(function (b) { return
|
|
1440
|
+
}).then(function (b) { return _bufferFrom(b); }, function (e) {
|
|
1366
1441
|
throw E(code, "the CryptoKey comes from a different copy of this WebCrypto engine and its key material could not be read; import it through pki.webcrypto.subtle, or pass the key as DER", e);
|
|
1367
1442
|
});
|
|
1368
1443
|
}
|
|
1369
1444
|
// Otherwise the only remaining implementation whose keys are reachable from this process is the
|
|
1370
1445
|
// platform's own. A third party's -- a browser's, a userland polyfill's -- keeps its material
|
|
1371
1446
|
// behind its own SubtleCrypto, which nothing here holds a reference to, so it is refused below.
|
|
1372
|
-
return Promise
|
|
1447
|
+
return _resolve(Promise)
|
|
1373
1448
|
.then(function () { return nodeCrypto.webcrypto.subtle.exportKey(format, key); })
|
|
1374
|
-
.then(function (b) { return
|
|
1449
|
+
.then(function (b) { return _bufferFrom(b); }, function (e) {
|
|
1375
1450
|
throw E(code, "the CryptoKey comes from a different WebCrypto implementation and could not be exported for re-import; import it through pki.webcrypto.subtle, or pass the key as DER", e);
|
|
1376
1451
|
});
|
|
1377
1452
|
}
|
|
@@ -1381,19 +1456,19 @@ function exportAnyKey(key, E, code) {
|
|
|
1381
1456
|
// key of its own keeps working -- and a foreign one is re-imported. `importParams` may be null, in
|
|
1382
1457
|
// which case the key's own `algorithm` is used, which is what the key actually is.
|
|
1383
1458
|
function adoptKey(key, importParams, usages, E, code) {
|
|
1384
|
-
if (key instanceof CryptoKey) return Promise
|
|
1459
|
+
if (key instanceof CryptoKey) return _resolve(Promise, key);
|
|
1385
1460
|
// A key's usages are a capability restriction it carries, and re-importing it is the one moment
|
|
1386
1461
|
// that restriction could be widened -- the new key is created with the usages the CALLER wants,
|
|
1387
1462
|
// not the ones the original was created with. Require every one of them up front, so an adopted
|
|
1388
1463
|
// key can do no more than it could where it came from, and a verify-only key is refused here
|
|
1389
1464
|
// exactly as this engine's own verify-only key is refused by _requireUsage.
|
|
1390
|
-
if (!
|
|
1465
|
+
if (!_isArray(key.usages)) throw E(code, "a CryptoKey carrying its permitted usages is required");
|
|
1391
1466
|
for (var i = 0; i < usages.length; i++) {
|
|
1392
|
-
if (key.usages
|
|
1393
|
-
throw E(code, "the CryptoKey is not permitted for '" + usages[i] + "' (usages: " + key.usages
|
|
1467
|
+
if (_indexOf(key.usages, usages[i]) === -1) {
|
|
1468
|
+
throw E(code, "the CryptoKey is not permitted for '" + usages[i] + "' (usages: " + _join(key.usages, ",") + ")");
|
|
1394
1469
|
}
|
|
1395
1470
|
}
|
|
1396
|
-
return Promise
|
|
1471
|
+
return _resolve(Promise)
|
|
1397
1472
|
.then(function () { return exportAnyKey(key, E, code); })
|
|
1398
1473
|
.then(function (der) {
|
|
1399
1474
|
return _crypto.subtle.importKey(_EXPORT_FORMAT[key.type], der, importParams || key.algorithm, false, usages);
|