@blamejs/pki 0.5.18 → 0.5.19

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 CHANGED
@@ -4,6 +4,25 @@ All notable changes to `@blamejs/pki` are documented here. The format
4
4
  follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this
5
5
  project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## v0.5.19 — 2026-08-20
8
+
9
+ The crypto engine and the JWS signer decide with operations captured at load, so code that runs after the module does cannot change what a permission check concludes.
10
+
11
+ ### Changed
12
+
13
+ - `pki.webcrypto` and `pki.jose` are held to the live-read rule the guard family already follows: the check over `lib/` that refuses reading an operation from the runtime at call time covers every module that takes the captures, and these two now do. Operator-visible behavior is unchanged except where a refusal that could be bypassed now holds.
14
+
15
+ ### Fixed
16
+
17
+ - The WebCrypto key-usage check holds against a replaced `Array.prototype.indexOf`. The membership test IS the permission decision, so it runs through the captured method: dispatched live, a key permitted only for `verify` passed the engine's gate for `sign` and failed later, in the platform's own crypto, with an untyped fault instead of `webcrypto/invalid-access`.
18
+ - A CryptoKey's usage list is a copy this engine took with a captured `slice`, so a replaced one cannot leave the caller holding the array the key is checked against for the rest of its life. The same applies to the partition that decides which usages each half of a generated pair is minted with.
19
+ - `pki.key.import`, `pki.key.generate` and `subtle.generateKey` refuse a non-array `usages` by name. It reached the key constructor, where `Array.prototype.slice` on a number yields an empty list, so the call minted a key permitted for nothing rather than naming the argument that was wrong. The asymmetric branch of `generateKey` partitions the usages before either half is constructed and `filter` treats a non-list the same way, so it is checked at that verb's own door and the whole pair is refused.
20
+ - The AES-GCM ciphertext and authentication tag are split with a captured `subarray`, so a replaced one cannot hand the tag check a different range of the same buffer.
21
+ - A JWK thumbprint (RFC 7638 sec. 3.3) is built with a captured serializer. The canonical JSON is the thumbprint's preimage, so a replaced `JSON.stringify` changes the identity every consumer compares keys by.
22
+ - The JOSE header checks -- own-name reads, the `crit` list walk, the embedded-JWK shape test -- and the protected-header serialization all run on captured operations, so a caller who reaches the header through an accessor cannot decide which of them apply.
23
+ - A digest runs under the algorithm the caller named. The hash name is case-folded to pick its registry row, and dispatched live a replaced `String.prototype.toUpperCase` answering `SHA-1` gave a caller who asked for SHA-256 a 20-byte digest. The same fold picks the HMAC block size and decides whether a key's algorithm matches the operation's.
24
+ - `getRandomValues` settles the kinds WebCrypto refuses from the value's internal slot rather than by `instanceof` against a replaceable global, so a real `Float32Array` cannot pass a check written to reject it by swapping the constructor the comparison names.
25
+
7
26
  ## v0.5.18 — 2026-08-20
8
27
 
9
28
  Every producing verb refuses an authoring field it does not read, so a misspelling is a refusal at the call instead of a signed artifact that quietly omits what was asked for.
@@ -84,6 +84,10 @@ var _jsonStringify = JSON.stringify;
84
84
  var _mathFloor = Math.floor;
85
85
  var _mathCeil = Math.ceil;
86
86
  var _mathMin = Math.min;
87
+ var _mathMax = Math.max;
88
+ // A numeric parse a bound is then compared against: one answering a larger value widens the bound
89
+ // the comparison was written to enforce.
90
+ var _parseInt = parseInt;
87
91
  var _promiseResolve = Promise.resolve;
88
92
  var _promiseReject = Promise.reject;
89
93
 
@@ -102,6 +106,19 @@ var _arrSort = uncurry(Array.prototype.sort);
102
106
  var _arrConcat = uncurry(Array.prototype.concat);
103
107
  var _arrSlice = uncurry(Array.prototype.slice);
104
108
  var _arrJoin = uncurry(Array.prototype.join);
109
+ var _arrPush = uncurry(Array.prototype.push);
110
+
111
+ // The string and byte-view operations a decision is then made on. A `toUpperCase` or `toLowerCase`
112
+ // replaced after load changes which registry row an algorithm name resolves to; a `subarray`
113
+ // returning a different window hands the next step bytes from somewhere else in the same buffer;
114
+ // and a radix conversion is how a key's own numbers are read back before they are compared.
115
+ var _strToUpperCase = uncurry(String.prototype.toUpperCase);
116
+ var _strToLowerCase = uncurry(String.prototype.toLowerCase);
117
+ var _strIndexOf = uncurry(String.prototype.indexOf);
118
+ var _taSubarray = uncurry(Uint8Array.prototype.subarray);
119
+ var _abSlice = uncurry(ArrayBuffer.prototype.slice);
120
+ var _numToString = uncurry(Number.prototype.toString);
121
+ var _bigIntToString = uncurry(BigInt.prototype.toString);
105
122
 
106
123
  // The collection operations, uncurried. A guard's cycle-safe chain walk is a `Set` it owns, and
107
124
  // `has` is what stops it: one answering `true` ends the walk at the first prototype, so a scan that
@@ -270,6 +287,16 @@ module.exports = {
270
287
  floor: _mathFloor,
271
288
  ceil: _mathCeil,
272
289
  min: _mathMin,
290
+ max: _mathMax,
291
+ parseInt: _parseInt,
292
+ push: _arrPush,
293
+ toUpperCase: _strToUpperCase,
294
+ toLowerCase: _strToLowerCase,
295
+ stringIndexOf: _strIndexOf,
296
+ subarray: _taSubarray,
297
+ arrayBufferSlice: _abSlice,
298
+ numberToString: _numToString,
299
+ bigIntToString: _bigIntToString,
273
300
  promiseResolve: _promiseResolve,
274
301
  promiseReject: _promiseReject,
275
302
  forEach: _arrForEach,
package/lib/jose.js CHANGED
@@ -42,6 +42,24 @@ var webcrypto = wcEngine.webcrypto;
42
42
  var frameworkError = require("./framework-error");
43
43
  var guard = require("./guard-all");
44
44
  var edwardsPoint = require("./edwards-point");
45
+ // What this module reads from the runtime while it is deciding something, taken at load. A JWS is
46
+ // signed and verified against a header the caller owns, and the reads below are the ones that decide
47
+ // what that header IS: whether a value is a list, whether a name is the caller's own, which
48
+ // conversion turns a claim into the string compared against a profile. A caller who receives control
49
+ // afterwards -- through an accessor on the header, or a `toJSON` while it is being serialized -- can
50
+ // replace any of them, and every check around them still runs and still passes. See guard-intrinsic.
51
+ var intrinsic = require("./guard-intrinsic");
52
+ var _isArray = intrinsic.isArray;
53
+ var _hasOwn = intrinsic.hasOwn;
54
+ var _keys = intrinsic.keys;
55
+ var _isBuffer = intrinsic.isBuffer;
56
+ var _bufferFrom = intrinsic.bufferFrom;
57
+ var _stringify = intrinsic.stringify;
58
+ var _ObjectProto = intrinsic.ObjectProto;
59
+ var _push = intrinsic.uncurry(Array.prototype.push);
60
+ var _join = intrinsic.join;
61
+ var _map = intrinsic.map;
62
+ var _bufToString = intrinsic.uncurry(Buffer.prototype.toString);
45
63
 
46
64
  var JoseError = frameworkError.JoseError;
47
65
  function E(code, message, cause) { return new JoseError(code, message, cause); }
@@ -65,11 +83,11 @@ var LIMITS = constants.LIMITS;
65
83
  * pki.jose.base64url.encode(Buffer.from([1, 2, 3])); // -> "AQID"
66
84
  */
67
85
  function b64uEncode(bytes) {
68
- if (!Buffer.isBuffer(bytes)) throw E("jose/bad-input", "base64url.encode requires a Buffer");
86
+ if (!_isBuffer(bytes)) throw E("jose/bad-input", "base64url.encode requires a Buffer");
69
87
  // Re-view through the byte guard: a detached-backed Buffer reads as zero-length,
70
88
  // so a bare .toString would silently encode "" -- fail closed instead.
71
89
  bytes = guard.bytes.view(bytes, JoseError, "jose/bad-input", "base64url.encode input");
72
- return bytes.toString("base64url");
90
+ return _bufToString(bytes, "base64url");
73
91
  }
74
92
 
75
93
  /**
@@ -182,19 +200,19 @@ var PROFILES = {
182
200
  // { alg, algRow, header }; throws a typed jose/* fault on any violation.
183
201
  function _checkHeader(header, profileName) {
184
202
  var profile = PROFILES[profileName];
185
- if (!profile) throw E("jose/bad-input", "unknown JWS profile " + JSON.stringify(profileName));
186
- if (!header || typeof header !== "object" || Array.isArray(header)) throw E("jose/bad-header", "the protected header must be a JSON object");
203
+ if (!profile) throw E("jose/bad-input", "unknown JWS profile " + _stringify(profileName));
204
+ if (!header || typeof header !== "object" || _isArray(header)) throw E("jose/bad-header", "the protected header must be a JSON object");
187
205
  // Every field is read as an OWN property. JSON.stringify (used by sign) omits
188
206
  // inherited members, so an alg/nonce/url living on the prototype (a polluted or
189
207
  // Object.create(...) header) would pass here yet serialize to a header MISSING
190
208
  // them -- so require own-ness, matching what the serialized JWS will actually carry.
191
- var own = function (k) { return Object.prototype.hasOwnProperty.call(header, k); };
209
+ var own = function (k) { return _hasOwn(header, k); };
192
210
  // b64 (RFC 7797) MUST NOT be present at all -- the unencoded-payload downgrade.
193
211
  if (own("b64")) throw E("jose/bad-jws", "the RFC 7797 b64 header parameter is forbidden");
194
212
  var alg = header.alg;
195
213
  if (!own("alg") || typeof alg !== "string") throw E("jose/bad-header", "the protected header must carry a string alg");
196
214
  var algRow = profile.algs[alg];
197
- if (!algRow) throw E("jose/bad-alg", "unsupported or forbidden algorithm " + JSON.stringify(alg) + " for this JWS profile");
215
+ if (!algRow) throw E("jose/bad-alg", "unsupported or forbidden algorithm " + _stringify(alg) + " for this JWS profile");
198
216
  if (profile.requireUrl && (!own("url") || typeof header.url !== "string" || header.url.length === 0)) throw E("jose/bad-header", "the protected header must carry a non-empty string url");
199
217
  // nonce discipline.
200
218
  var hasNonce = own("nonce");
@@ -219,20 +237,20 @@ function _checkHeader(header, profileName) {
219
237
  } else if (profile.keyId === "jwk") {
220
238
  if (!hasJwk || hasKid) throw E("jose/bad-header", "this JWS must carry an embedded jwk, not kid");
221
239
  }
222
- if (hasJwk && (!header.jwk || typeof header.jwk !== "object" || Array.isArray(header.jwk))) throw E("jose/bad-header", "jwk must be a JSON object");
240
+ if (hasJwk && (!header.jwk || typeof header.jwk !== "object" || _isArray(header.jwk))) throw E("jose/bad-header", "jwk must be a JSON object");
223
241
  // crit: protected-only, non-empty, no standard names, no duplicates, all understood.
224
- if (Object.prototype.hasOwnProperty.call(header, "crit")) {
242
+ if (_hasOwn(header, "crit")) {
225
243
  var crit = header.crit;
226
- if (!Array.isArray(crit) || crit.length === 0) throw E("jose/bad-crit", "crit must be a non-empty array");
244
+ if (!_isArray(crit) || crit.length === 0) throw E("jose/bad-crit", "crit must be a non-empty array");
227
245
  var seen = {};
228
246
  for (var c = 0; c < crit.length; c++) {
229
247
  var name = crit[c];
230
248
  if (typeof name !== "string") throw E("jose/bad-crit", "crit entries must be strings");
231
- if (Object.prototype.hasOwnProperty.call(seen, name)) throw E("jose/bad-crit", "duplicate crit entry " + JSON.stringify(name));
249
+ if (_hasOwn(seen, name)) throw E("jose/bad-crit", "duplicate crit entry " + _stringify(name));
232
250
  seen[name] = 1;
233
- if (UNDERSTOOD_HEADER[name]) throw E("jose/bad-crit", "crit must not name a standard header parameter " + JSON.stringify(name));
251
+ if (UNDERSTOOD_HEADER[name]) throw E("jose/bad-crit", "crit must not name a standard header parameter " + _stringify(name));
234
252
  // Anything else is an extension parameter this implementation does not process.
235
- throw E("jose/bad-crit", "unprocessed critical header parameter " + JSON.stringify(name));
253
+ throw E("jose/bad-crit", "unprocessed critical header parameter " + _stringify(name));
236
254
  }
237
255
  }
238
256
  return { alg: alg, algRow: algRow, header: header };
@@ -267,9 +285,9 @@ function _cryptoAlg(algRow, jwk, key) {
267
285
  // The key type the JWK MUST have for this alg (fail closed on confusion).
268
286
  function _assertKeyType(algRow, jwk) {
269
287
  if (!jwk || typeof jwk !== "object" || typeof jwk.kty !== "string") throw E("jose/bad-key", "a JWK object with a kty is required");
270
- if (jwk.kty !== algRow.kty) throw E("jose/bad-alg", "the key type " + JSON.stringify(jwk.kty) + " does not match the algorithm");
288
+ if (jwk.kty !== algRow.kty) throw E("jose/bad-alg", "the key type " + _stringify(jwk.kty) + " does not match the algorithm");
271
289
  if (algRow.kty === "EC" && jwk.crv !== algRow.crv) throw E("jose/bad-alg", "the EC curve does not match the algorithm");
272
- if (algRow.kty === "OKP" && jwk.crv !== "Ed25519" && jwk.crv !== "Ed448") throw E("jose/bad-alg", "unsupported OKP curve " + JSON.stringify(jwk.crv));
290
+ if (algRow.kty === "OKP" && jwk.crv !== "Ed25519" && jwk.crv !== "Ed448") throw E("jose/bad-alg", "unsupported OKP curve " + _stringify(jwk.crv));
273
291
  // node/OpenSSL import a low-order (e.g. identity) OKP point without complaint and it verifies a
274
292
  // forged signature; reject a non-full-order Edwards point through the shared gate, the same check
275
293
  // every EdDSA verify path in the toolkit applies.
@@ -278,7 +296,7 @@ function _assertKeyType(algRow, jwk) {
278
296
  }
279
297
  // An AKP (ML-DSA) JWK carries its parameter set in `alg` (RFC 9964); it MUST match
280
298
  // the algorithm's set, or an ML-DSA-44 key could embed an ML-DSA-65 public JWK.
281
- if (algRow.kty === "AKP" && jwk.alg !== algRow.subtle) throw E("jose/bad-alg", "the AKP parameter set " + JSON.stringify(jwk.alg) + " does not match the algorithm " + algRow.subtle);
299
+ if (algRow.kty === "AKP" && jwk.alg !== algRow.subtle) throw E("jose/bad-alg", "the AKP parameter set " + _stringify(jwk.alg) + " does not match the algorithm " + algRow.subtle);
282
300
  }
283
301
 
284
302
  // The exact signature byte length this alg + key must produce.
@@ -317,10 +335,10 @@ var PRIVATE_JWK_MEMBERS = ["d", "p", "q", "dp", "dq", "qi", "k", "priv"];
317
335
  * pki.jose.assertPublicJwk({ kty: "EC", crv: "P-256", x: "...", y: "..." });
318
336
  */
319
337
  function assertPublicJwk(jwk) {
320
- if (!jwk || typeof jwk !== "object" || Array.isArray(jwk)) throw E("jose/bad-key", "a JWK object is required");
338
+ if (!jwk || typeof jwk !== "object" || _isArray(jwk)) throw E("jose/bad-key", "a JWK object is required");
321
339
  for (var i = 0; i < PRIVATE_JWK_MEMBERS.length; i++) {
322
- if (Object.prototype.hasOwnProperty.call(jwk, PRIVATE_JWK_MEMBERS[i])) {
323
- throw E("jose/private-key-material", "a published JWK must be public-only; it carries the private member " + JSON.stringify(PRIVATE_JWK_MEMBERS[i]));
340
+ if (_hasOwn(jwk, PRIVATE_JWK_MEMBERS[i])) {
341
+ throw E("jose/private-key-material", "a published JWK must be public-only; it carries the private member " + _stringify(PRIVATE_JWK_MEMBERS[i]));
324
342
  }
325
343
  }
326
344
  return jwk;
@@ -374,9 +392,9 @@ var _VERIFY_KEYS = { key: 1, profile: 1 };
374
392
  async function verify(jws, opts) {
375
393
  opts = opts || {};
376
394
  guard.identifier.assertKnownKeys(opts, _VERIFY_KEYS, E, "jose/bad-input", "unknown pki.jose.verify option ");
377
- if (!jws || typeof jws !== "object" || Array.isArray(jws)) throw E("jose/bad-jws", "a flattened JWS object is required");
378
- if (Object.prototype.hasOwnProperty.call(jws, "signatures")) throw E("jose/bad-jws", "the multi-signature signatures member is forbidden");
379
- if (Object.prototype.hasOwnProperty.call(jws, "header")) throw E("jose/bad-jws", "the unprotected header member is forbidden");
395
+ if (!jws || typeof jws !== "object" || _isArray(jws)) throw E("jose/bad-jws", "a flattened JWS object is required");
396
+ if (_hasOwn(jws, "signatures")) throw E("jose/bad-jws", "the multi-signature signatures member is forbidden");
397
+ if (_hasOwn(jws, "header")) throw E("jose/bad-jws", "the unprotected header member is forbidden");
380
398
  if (typeof jws.protected !== "string" || typeof jws.signature !== "string") throw E("jose/bad-jws", "protected and signature must be base64url strings");
381
399
  if (typeof jws.payload !== "string") throw E("jose/bad-jws", "payload must be a base64url string (never detached)");
382
400
  var header = parseJson(b64uDecode(jws.protected));
@@ -393,7 +411,7 @@ async function verify(jws, opts) {
393
411
  // matters most -- a `key` that came back null from a lookup would verify against whatever the
394
412
  // message carried, and the verdict would report the embedded key as though none was named.
395
413
  // `undefined` alone means "not supplied", so spreading an options object stays safe.
396
- if (opts.key !== undefined && (typeof opts.key !== "object" || opts.key === null || Array.isArray(opts.key))) {
414
+ if (opts.key !== undefined && (typeof opts.key !== "object" || opts.key === null || _isArray(opts.key))) {
397
415
  throw E("jose/bad-key", "opts.key was supplied but is not a JWK object, so the key this message must be signed under cannot be established");
398
416
  }
399
417
  var jwk = header.jwk || opts.key;
@@ -419,7 +437,7 @@ async function verify(jws, opts) {
419
437
  var want = _expectedSigBytes(checked.algRow, jwk);
420
438
  if (want != null && sig.length !== want) throw E("jose/bad-signature", "the signature length " + sig.length + " is not the " + want + " bytes " + checked.alg + " requires");
421
439
  // Signing input from the RECEIVED base64url strings verbatim (never a re-serialize).
422
- var signingInput = Buffer.from(jws.protected + "." + jws.payload, "ascii");
440
+ var signingInput = _bufferFrom(jws.protected + "." + jws.payload, "ascii");
423
441
  var key;
424
442
  try { key = await webcrypto.subtle.importKey("jwk", jwk, _importParams(checked.algRow, jwk), false, ["verify"]); }
425
443
  catch (e) { throw E("jose/bad-key", "the JWK could not be imported for verification", e); }
@@ -483,23 +501,23 @@ async function sign(opts) {
483
501
  // those questions separately -- assertPublicJwk sees a public JWK while the bytes that ship carry
484
502
  // the private one, and the door sees a jwk-mode header while the signer sees a kid-mode one.
485
503
  var headerJson;
486
- try { headerJson = JSON.stringify(opts.protected); }
504
+ try { headerJson = _stringify(opts.protected); }
487
505
  catch (e) { throw E("jose/bad-header", "the protected header must be JSON-serializable", e); }
488
506
  // Read back through the same bounded reader a received JWS goes through, so a header this side
489
507
  // produces is held to the caps and the duplicate-member rule the other side enforces.
490
- var headerBytes = headerJson === undefined ? null : Buffer.from(headerJson, "utf8");
508
+ var headerBytes = headerJson === undefined ? null : _bufferFrom(headerJson, "utf8");
491
509
  var header = headerBytes === null ? opts.protected : parseJson(headerBytes);
492
510
  // Which of the two key sources is in force is known only once the header has been read, so the
493
511
  // narrower table runs here. It re-walks the bag, which also catches a name that code added.
494
512
  var embedsJwk = !!(header && typeof header === "object" && header.jwk);
495
513
  if (embedsJwk) {
496
514
  guard.identifier.assertKnownKeys(opts, _SIGN_KEYS_EMBEDDED_JWK, E, "jose/bad-input", function (k) {
497
- return "unknown pki.jose.sign option " + JSON.stringify(k) +
515
+ return "unknown pki.jose.sign option " + _stringify(k) +
498
516
  (k === "jwk" ? "; the protected header already embeds the public JWK" : "");
499
517
  });
500
518
  }
501
519
  var payload = opts.payload;
502
- if (!Buffer.isBuffer(payload)) throw E("jose/bad-input", "payload must be a Buffer (empty Buffer for POST-as-GET)");
520
+ if (!_isBuffer(payload)) throw E("jose/bad-input", "payload must be a Buffer (empty Buffer for POST-as-GET)");
503
521
  // Re-view before the length===0 fast-path: a DETACHED payload reads as zero-length
504
522
  // and would be silently signed as an empty POST-as-GET body -- fail closed instead.
505
523
  payload = guard.bytes.view(payload, JoseError, "jose/bad-input", "payload");
@@ -523,14 +541,14 @@ async function sign(opts) {
523
541
  if (header.jwk) { _assertKeyType(checked.algRow, header.jwk); assertPublicJwk(header.jwk); }
524
542
  var protectedB64 = b64uEncode(headerBytes); // the exact bytes every check above read
525
543
  var payloadB64 = payload.length === 0 ? "" : b64uEncode(payload);
526
- var signingInput = Buffer.from(protectedB64 + "." + payloadB64, "ascii");
544
+ var signingInput = _bufferFrom(protectedB64 + "." + payloadB64, "ascii");
527
545
  var jwk = header.jwk || opts.jwk || {};
528
546
  // opts.key is documented as a CryptoKey, so one created outside this engine signs here too: it
529
547
  // carries none of the material this engine signs with, and is re-imported through it rather than
530
548
  // reaching subtle.sign as a key it cannot read. A key whose material this process cannot reach at
531
549
  // all -- non-extractable, or held behind another implementation's interface -- is refused there.
532
550
  var signKey = await wcEngine.adoptKey(opts.key, null, ["sign"], E, "jose/bad-input");
533
- var sigBuf = Buffer.from(await webcrypto.subtle.sign(_cryptoAlg(checked.algRow, jwk, signKey), signKey, signingInput));
551
+ var sigBuf = _bufferFrom(await webcrypto.subtle.sign(_cryptoAlg(checked.algRow, jwk, signKey), signKey, signingInput));
534
552
  // Pin the produced signature length the same way verify does. A key whose curve /
535
553
  // hash / modulus does not match the header alg (a P-384 key under ES256, an
536
554
  // HS512 key under HS256) still signs, but emits the wrong length -- caught here
@@ -590,15 +608,17 @@ var THUMBPRINT_MEMBERS = {
590
608
  async function thumbprint(jwk) {
591
609
  if (!jwk || typeof jwk !== "object" || typeof jwk.kty !== "string") throw E("jose/bad-key", "a JWK object with a kty is required");
592
610
  var members = THUMBPRINT_MEMBERS[jwk.kty];
593
- if (!members) throw E("jose/bad-key", "unsupported JWK key type " + JSON.stringify(jwk.kty));
611
+ if (!members) throw E("jose/bad-key", "unsupported JWK key type " + _stringify(jwk.kty));
594
612
  var parts = [];
595
613
  for (var i = 0; i < members.length; i++) {
596
614
  var m = members[i];
597
- if (typeof jwk[m] !== "string") throw E("jose/bad-key", "the JWK is missing the required member " + JSON.stringify(m));
598
- parts.push(JSON.stringify(m) + ":" + JSON.stringify(jwk[m]));
615
+ if (typeof jwk[m] !== "string") throw E("jose/bad-key", "the JWK is missing the required member " + _stringify(m));
616
+ // The canonical form IS the thumbprint preimage (RFC 7638 sec. 3.3), so the serializer that
617
+ // builds it is the captured one: a replacement changes the identity every consumer compares.
618
+ _push(parts, _stringify(m) + ":" + _stringify(jwk[m]));
599
619
  }
600
- var canonical = "{" + parts.join(",") + "}";
601
- var digest = Buffer.from(await webcrypto.subtle.digest("SHA-256", Buffer.from(canonical, "utf8")));
620
+ var canonical = "{" + _join(parts, ",") + "}";
621
+ var digest = _bufferFrom(await webcrypto.subtle.digest("SHA-256", _bufferFrom(canonical, "utf8")));
602
622
  return b64uEncode(digest);
603
623
  }
604
624
 
@@ -630,7 +650,7 @@ async function thumbprint(jwk) {
630
650
  * pss.map(function (r) { return r.alg; }); // -> ["PS256", "PS384", "PS512"]
631
651
  */
632
652
  function sigAlgs() {
633
- return Object.keys(SIG_ALGS).map(function (alg) {
653
+ return _map(_keys(SIG_ALGS), function (alg) {
634
654
  var row = SIG_ALGS[alg];
635
655
  var out = { alg: alg, kty: row.kty };
636
656
  if (row.crv) out.crv = row.crv;
package/lib/key.js CHANGED
@@ -305,6 +305,10 @@ async function import_(input, opts) {
305
305
  }
306
306
  var algorithm = opts.algorithm != null ? opts.algorithm : _inferAlgorithm(detected);
307
307
  var isPublic = detected.format === "spki";
308
+ // Checked at this verb's own door rather than left for the engine to discover: a non-list reached
309
+ // the CryptoKey copy, where `Array.prototype.slice` on a number yields an empty list, so the key
310
+ // was minted permitted for NOTHING instead of the call naming the argument that was wrong.
311
+ if (opts.usages != null && !Array.isArray(opts.usages)) throw _err("key/bad-input", "opts.usages must be an array of key-usage strings");
308
312
  var usages = opts.usages || _importUsages(_algName(algorithm), isPublic);
309
313
  var extractable = opts.extractable != null ? opts.extractable : false;
310
314
  try {
@@ -344,6 +348,8 @@ async function generate(algorithm, opts) {
344
348
  guard.identifier.assertKnownKeys(opts, _GENERATE_OPTS, _err, "key/bad-input",
345
349
  "pki.key.generate has an unknown option (the WebCrypto spelling is `extractable`): ");
346
350
  var extractable = opts.extractable != null ? opts.extractable : true;
351
+ // The same door as import: a non-list is named here rather than becoming an empty usage set.
352
+ if (opts.usages != null && !Array.isArray(opts.usages)) throw _err("key/bad-input", "opts.usages must be an array of key-usage strings");
347
353
  var usages = opts.usages || _generateUsages(_algName(algorithm));
348
354
  var pair;
349
355
  try { pair = await subtle.generateKey(algorithm, extractable, usages); }
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.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
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 Buffer.from(buf).toString("base64url"); }
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 (Object.prototype.hasOwnProperty.call(alg, k)) out[k] = alg[k]; }
80
- out.name = alg.name.toUpperCase();
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
- var node = HASH_NODE[String(name).toUpperCase()];
123
- if (!node) throw new WebCryptoError("webcrypto/not-supported", (who || "operation") + ": unsupported hash " + JSON.stringify(name));
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[String(name).toUpperCase()];
142
- if (!blockBits) throw new WebCryptoError("webcrypto/not-supported", who + ": unsupported hash " + JSON.stringify(name));
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" || !isFinite(alg.length) || alg.length <= 0 || alg.length % 8 !== 0) {
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.toUpperCase()] = "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(Object.keys(ML_DSA_NODE), Object.keys(SLH_DSA_NODE))
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
- this.usages = usages ? usages.slice() : [];
210
- Object.defineProperty(this, "_handle", { value: handle, enumerable: false });
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
- if (key.usages.indexOf(usage) === -1) {
229
- throw new WebCryptoError("webcrypto/invalid-access", "key is not permitted for '" + usage + "' (usages: " + key.usages.join(",") + ")");
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 (String(keyName).toUpperCase() !== alg.name) {
243
- throw new WebCryptoError("webcrypto/invalid-access", who + ": algorithm " + JSON.stringify(alg.name) + " does not match the key's algorithm " + JSON.stringify(keyName));
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[String(name).toUpperCase()];
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
- var privUsages = usages.filter(function (u) { return u === "sign" || u === "deriveKey" || u === "deriveBits" || u === "decrypt" || u === "unwrapKey" || u === "decapsulateKey" || u === "decapsulateBits"; });
286
- var pubUsages = usages.filter(function (u) { return u === "verify" || u === "encrypt" || u === "wrapKey" || u === "encapsulateKey" || u === "encapsulateBits"; });
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 " + JSON.stringify(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.toLowerCase());
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 " + JSON.stringify(name));
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 = BigInt("0x" + b.toString("hex"));
427
+ var v = _BigInt("0x" + _bufToString(b, "hex"));
358
428
  if (v > 0xffffffffn) {
359
- throw new WebCryptoError("webcrypto/syntax", "publicExponent " + v.toString() + " exceeds the 2^32-1 bound");
429
+ throw new WebCryptoError("webcrypto/syntax", "publicExponent " + _bigIntToString(v) + " exceeds the 2^32-1 bound");
360
430
  }
361
- return Number(v);
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 " + JSON.stringify(name));
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 " + JSON.stringify(name));
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 " + JSON.stringify(name));
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 " + JSON.stringify(name));
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 " + JSON.stringify(name));
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 Buffer.concat([ct, cipher.getAuthTag()]);
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 " + JSON.stringify(name));
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 " + JSON.stringify(name));
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
- var ct = buf.subarray(0, buf.length - tagLen);
525
- var tag = buf.subarray(buf.length - tagLen);
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 " + JSON.stringify(name));
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" || !isFinite(length) || length <= 0 || length % 8 !== 0) {
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 = Math.max(1, (parseInt(process.env.UV_THREADPOOL_SIZE, 10) || 4) - 2);
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.subarray(0, length / 8));
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 || Buffer.alloc(0), "HKDF info"), length / 8);
678
- return derived instanceof ArrayBuffer ? derived : _toArrayBuffer(Buffer.from(derived));
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 || Buffer.alloc(0), "X963KDF SharedInfo"), length / 8);
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 " + JSON.stringify(name));
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 = Buffer.alloc(4); ctr.writeUInt32BE(counter, 0);
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.push(h); got += h.length; counter += 1;
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 = Buffer.concat(blocks);
727
- var exact = Buffer.alloc(lenBytes);
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 " + JSON.stringify(alg.name));
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 " + JSON.stringify(alg.name));
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.indexOf("AES") === 0) {
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 Uint8Array(raw), WebCryptoError, "webcrypto/operation", "the derived key material"); }
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 " + JSON.stringify(alg.name));
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 " + JSON.stringify(encapsulationKey.type));
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 " + JSON.stringify(alg.name));
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 " + JSON.stringify(decapsulationKey.type));
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") ? Buffer.from(JSON.stringify(exported)) : Buffer.from(exported);
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 " + JSON.stringify(alg.name));
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 " + JSON.stringify(format) + " is not AES-KW-wrappable");
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, Buffer.from("A6A6A6A6A6A6A6A6", "hex"));
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 " + JSON.stringify(alg.name));
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, Buffer.from("A6A6A6A6A6A6A6A6", "hex"));
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 = Buffer.from(await this.decrypt(unwrapAlgorithm, unwrapKeyClone, wrappedKey));
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.concat([usage]), key._handle);
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
- Object.keys(ML_KEM_INNER).forEach(function (o) { PKCS8_INNER_VALIDATORS[o] = function (inner) { _validateMlKemInner(inner, ML_KEM_INNER[o]); }; });
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" || Array.isArray(jwk)) {
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 = Object.prototype.hasOwnProperty.call(jwk, "d") ||
1113
- (jwk.kty === "AKP" && Object.prototype.hasOwnProperty.call(jwk, "priv"));
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 " + JSON.stringify(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.subarray(1, 1 + fb)), y: _bufToB64url(raw.subarray(1 + fb)) };
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 " + JSON.stringify(name));
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
- Object.keys(m).forEach(function (webName) { var t = {}; t[m[webName]] = 1; IMPORT_KEY_TYPE[webName] = t; });
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 " + JSON.stringify(alg.namedCurve) +
1173
- " does not match the imported key's curve " + JSON.stringify(actualCurve));
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 " + JSON.stringify(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 Buffer.concat([Buffer.from([0x04]), _b64urlToBuf(jwk.x), _b64urlToBuf(jwk.y)]);
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
- if (!ArrayBuffer.isView(typedArray) || typedArray instanceof Float32Array || typedArray instanceof Float64Array || typedArray instanceof DataView) {
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 = Object.assign(Object.create(null), { "private": "pkcs8", "public": "spki", "secret": "raw" });
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" && Array.isArray(x.usages);
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.resolve()
1424
+ return _resolve(Promise)
1350
1425
  .then(function () { return _crypto.subtle.exportKey(format, key); })
1351
- .then(function (b) { return Buffer.from(b); });
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.resolve().then(function () {
1438
+ return _resolve(Promise).then(function () {
1364
1439
  return format === "raw" ? handle.export() : handle.export({ format: "der", type: format });
1365
- }).then(function (b) { return Buffer.from(b); }, function (e) {
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.resolve()
1447
+ return _resolve(Promise)
1373
1448
  .then(function () { return nodeCrypto.webcrypto.subtle.exportKey(format, key); })
1374
- .then(function (b) { return Buffer.from(b); }, function (e) {
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.resolve(key);
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 (!Array.isArray(key.usages)) throw E(code, "a CryptoKey carrying its permitted usages is required");
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.indexOf(usages[i]) === -1) {
1393
- throw E(code, "the CryptoKey is not permitted for '" + usages[i] + "' (usages: " + key.usages.join(",") + ")");
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.resolve()
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/pki",
3
- "version": "0.5.18",
3
+ "version": "0.5.19",
4
4
  "description": "Pure-JavaScript PKI toolkit that owns its stack — X.509, ASN.1/DER, CMS, PQC-first.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "blamejs contributors",
package/sbom.cdx.json CHANGED
@@ -2,10 +2,10 @@
2
2
  "$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
3
3
  "bomFormat": "CycloneDX",
4
4
  "specVersion": "1.5",
5
- "serialNumber": "urn:uuid:123ddb5e-05ea-4e65-9d7d-7a667c46e3a3",
5
+ "serialNumber": "urn:uuid:d0064cbc-a37c-4d4f-ae67-0b9ba6887de2",
6
6
  "version": 1,
7
7
  "metadata": {
8
- "timestamp": "2026-08-20T20:31:47.390Z",
8
+ "timestamp": "2026-08-20T21:35:06.293Z",
9
9
  "lifecycles": [
10
10
  {
11
11
  "phase": "build"
@@ -19,14 +19,14 @@
19
19
  }
20
20
  ],
21
21
  "component": {
22
- "bom-ref": "@blamejs/pki@0.5.18",
22
+ "bom-ref": "@blamejs/pki@0.5.19",
23
23
  "type": "application",
24
24
  "name": "pki",
25
- "version": "0.5.18",
25
+ "version": "0.5.19",
26
26
  "scope": "required",
27
27
  "author": "blamejs contributors",
28
28
  "description": "Pure-JavaScript PKI toolkit that owns its stack — X.509, ASN.1/DER, CMS, PQC-first.",
29
- "purl": "pkg:npm/%40blamejs/pki@0.5.18",
29
+ "purl": "pkg:npm/%40blamejs/pki@0.5.19",
30
30
  "properties": [],
31
31
  "externalReferences": [
32
32
  {
@@ -54,7 +54,7 @@
54
54
  "components": [],
55
55
  "dependencies": [
56
56
  {
57
- "ref": "@blamejs/pki@0.5.18",
57
+ "ref": "@blamejs/pki@0.5.19",
58
58
  "dependsOn": []
59
59
  }
60
60
  ]