@blamejs/pki 0.5.32 → 0.5.33

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.
@@ -27,6 +27,8 @@ var _bufferFrom = intrinsic.bufferFrom;
27
27
  // question this guard asked on the caller's behalf. See guard-intrinsic for the whole set.
28
28
  var _isView = intrinsic.isView;
29
29
  var _isBuffer = intrinsic.isBuffer;
30
+ var _asyncIterator = intrinsic.asyncIterator;
31
+ var _NO_ARGS = intrinsic.freeze([]);
30
32
  var _create = intrinsic.create;
31
33
  var _getPrototypeOf = intrinsic.getPrototypeOf;
32
34
  var _getOwnPropertyDescriptor = intrinsic.getOwnPropertyDescriptor;
@@ -80,6 +82,35 @@ function isByteSource(x) {
80
82
  return _isBuffer(x) || _isView(x) || _isArrayBuffer(x);
81
83
  }
82
84
 
85
+ // asyncStreamOf(x) -> a LAZY single-use async iterable of x's byte chunks, or null when x is not a
86
+ // byte stream.
87
+ //
88
+ // A door offering both a byte source and a streamed content (an async iterable of byte chunks) asks
89
+ // this ONE module which it has, instead of re-inlining the `for await` protocol test and drifting on
90
+ // what counts. It reads x's Symbol.asyncIterator EXACTLY ONCE, captures that method, and returns a
91
+ // thin iterable that CALLS it only when the consumer drives the stream. Deferring the call matters
92
+ // twice: the underlying iterator is acquired once, in the digest engine, so reading the protocol
93
+ // property a second time downstream cannot fire a one-shot or stateful accessor and refuse a stream a
94
+ // direct `for await` accepts; AND a caller whose iterator factory opens a file or socket has that
95
+ // resource opened only on a path that will actually consume it, never on a pre-hash rejection (a
96
+ // missing detached option, an empty signer list, a message needing no streamed digest) that would
97
+ // otherwise acquire and leak it. A byte source is bytes, never a stream, EVEN when a caller attached a
98
+ // Symbol.asyncIterator to it: the byte-source classification wins and is checked FIRST, so a Buffer /
99
+ // typed array / DataView goes down the byte path every time and its own accessors are never invoked
100
+ // here. A callable carrying the method is a valid async iterable that `for await` consumes, so it too
101
+ // yields a stream.
102
+ // @enforced-by behavioral -- a lazy stream wrapper has no rename-proof code shape; the RED vectors (a
103
+ // stream produced where a byte source is not, a one-shot-accessor stream still consumed, and the
104
+ // factory NOT invoked until the stream is driven) are the guard.
105
+ function asyncStreamOf(x) {
106
+ if (isByteSource(x) || x == null || (typeof x !== "object" && typeof x !== "function")) return null;
107
+ var acquire = x[_asyncIterator];
108
+ if (typeof acquire !== "function") return null;
109
+ var stream = {};
110
+ stream[_asyncIterator] = function () { return _apply(acquire, x, _NO_ARGS); };
111
+ return stream;
112
+ }
113
+
83
114
  // Is this value a byte store, asked of the internal slot rather than the prototype?
84
115
  //
85
116
  // `v instanceof ArrayBuffer` answers what a value inherits from, which is a different question
@@ -1057,6 +1088,6 @@ function fixedCall(ErrorClass, code, args, body) {
1057
1088
  // call, so a writable export would let a caller replace the check rather than pass it.
1058
1089
  module.exports = intrinsic.freeze({
1059
1090
  view: view, source: source, snapshot: snapshot, snapshotSource: snapshotSource,
1060
- isByteSource: isByteSource, lengthOf: lengthOf, outputView: outputView,
1091
+ isByteSource: isByteSource, asyncStreamOf: asyncStreamOf, lengthOf: lengthOf, outputView: outputView,
1061
1092
  snapshotDeep: snapshotDeep, fixArguments: fixArguments, fixedCall: fixedCall,
1062
1093
  });
@@ -59,6 +59,10 @@ var _bufferCompare = Buffer.compare;
59
59
  // response is about this issuer, a responder ID says this key signed it. Dispatched live, one
60
60
  // answering true makes a response about a DIFFERENT certificate match the one being asked about.
61
61
  var _bufferEquals = uncurry(Buffer.prototype.equals);
62
+ // Buffer#toString, uncurried at load. Used to build dedup keys (a hex string) from bytes the toolkit
63
+ // owns; dispatched live, a replaced toString could make two distinct values key alike or one value key
64
+ // differently on two reads, so a dedup over untrusted-adjacent bytes would merge or split wrongly.
65
+ var _bufferToString = uncurry(Buffer.prototype.toString);
62
66
 
63
67
  // The rest of what a guard reads from the runtime while it is deciding something. These are
64
68
  // grouped here rather than captured file by file so the set is enumerable in one place and a guard
@@ -94,6 +98,13 @@ var _mathMax = Math.max;
94
98
  var _parseInt = parseInt;
95
99
  var _promiseResolve = Promise.resolve;
96
100
  var _promiseReject = Promise.reject;
101
+ // The well-known async-iteration symbol, captured at load. `for await` uses this symbol at the
102
+ // language level regardless of the global `Symbol` object, so a classifier that reads
103
+ // `Symbol.asyncIterator` live would disagree with the loop the moment a co-resident replaced the
104
+ // writable `Symbol` global: a real async iterable would be looked up under the wrong key, judged not
105
+ // iterable, and refused where the engine would have consumed it. Reading the captured symbol keeps
106
+ // the door's answer and the loop's behavior the same.
107
+ var _asyncIterator = Symbol.asyncIterator;
97
108
 
98
109
  // The array operations, uncurried at load. These run on arrays the TOOLKIT owns -- an `ownKeys`
99
110
  // result, a local accumulator -- so the receiver is never a caller's object; what is caller-
@@ -274,6 +285,7 @@ module.exports = Object.freeze({
274
285
  bufferFrom: _bufferFrom,
275
286
  bufferAlloc: _bufferAlloc,
276
287
  bufferConcat: _bufferConcat,
288
+ bufToString: _bufferToString,
277
289
  isBuffer: _bufferIsBuffer,
278
290
  byteLength: _bufferByteLength,
279
291
  compare: _bufferCompare,
@@ -309,6 +321,7 @@ module.exports = Object.freeze({
309
321
  bigIntToString: _bigIntToString,
310
322
  promiseResolve: _promiseResolve,
311
323
  promiseReject: _promiseReject,
324
+ asyncIterator: _asyncIterator,
312
325
  forEach: _arrForEach,
313
326
  map: _arrMap,
314
327
  filter: _arrFilter,
package/lib/webcrypto.js CHANGED
@@ -54,6 +54,14 @@ var _hasOwn = intrinsic.hasOwn;
54
54
  var _stringify = intrinsic.stringify;
55
55
  var _bufferFrom = intrinsic.bufferFrom;
56
56
  var _bufToString = intrinsic.uncurry(Buffer.prototype.toString);
57
+ // The node Hash instance methods, captured at load. digestStream feeds chunks across `await` points,
58
+ // and a co-resident that replaced Hash.prototype.update while the stream was suspended between chunks
59
+ // could drop or alter one -- making digest("ab") equal digest("a") -- and, because both streamed
60
+ // signing and verification trust this digest, forge a valid verdict over bytes the stream never
61
+ // yielded. Calling the captured update/digest keeps the hash bound to the bytes the stream saw. The
62
+ // one-shot digest needs no such capture: its single update and digest run with no suspension between.
63
+ var _hashUpdate = intrinsic.uncurry(nodeCrypto.Hash.prototype.update);
64
+ var _hashDigest = intrinsic.uncurry(nodeCrypto.Hash.prototype.digest);
57
65
  var _bufferConcat = intrinsic.bufferConcat;
58
66
  var _bufferAlloc = intrinsic.bufferAlloc;
59
67
  var _isBuffer = intrinsic.isBuffer;
@@ -76,6 +84,9 @@ var _Uint8Array = intrinsic.Uint8Array;
76
84
  var _assign = intrinsic.assign;
77
85
  var _create = intrinsic.create;
78
86
  var _isView = intrinsic.isView;
87
+ var _asyncIterator = intrinsic.asyncIterator;
88
+ var _apply = intrinsic.apply;
89
+ var _NO_ARGS = intrinsic.freeze([]);
79
90
  var _toUpperCase = intrinsic.toUpperCase;
80
91
  var _toLowerCase = intrinsic.toLowerCase;
81
92
  var _stringIndexOf = intrinsic.stringIndexOf;
@@ -334,6 +345,74 @@ SubtleCrypto.prototype.digest = async function digest(algorithm, data) {
334
345
  return _toArrayBuffer(h.digest());
335
346
  };
336
347
 
348
+ // Digest an async iterable of byte chunks under several algorithms in ONE pass, returning one
349
+ // ArrayBuffer per algorithm in order. Each result is byte-identical to digest(algorithms[i], <the
350
+ // concatenated chunks>): the same createHash + XOF handling, fed incrementally so an arbitrarily
351
+ // large payload is never held whole. The single pass is what lets a multi-signer detached CMS sign
352
+ // hash a single-use stream once for every distinct digest algorithm. Which algorithm each Hash runs
353
+ // is fixed up front through the captured name resolution (_normalizeAlg / _toUpperCase / _hashNode),
354
+ // exactly as the one-shot digest resolves it, so a replaced toUpperCase cannot swap SHA-256 for SHA-1
355
+ // mid-stream. A chunk that is not a BufferSource throws webcrypto/data, before it reaches a hash.
356
+ SubtleCrypto.prototype.digestStream = async function digestStream(algorithms, source) {
357
+ if (!_isArray(algorithms) || !algorithms.length) {
358
+ throw new WebCryptoError("webcrypto/syntax", "digestStream: algorithms must be a non-empty array of digest algorithms");
359
+ }
360
+ // Follow the async-iteration protocol exactly as `for await` does: read the source's
361
+ // Symbol.asyncIterator ONCE, call it to obtain the iterator, then capture THAT iterator's `next` with
362
+ // a single read and drive it manually. Driving `source` by its own `next` would diverge from
363
+ // `for await` whenever Symbol.asyncIterator returns a distinct iterator, and an inherited `next` (say
364
+ // from a polluted prototype) would misclassify an ordinary iterable; going through the method avoids
365
+ // both. Native `for await` caches `next` after one read, so capturing each method once and reusing it
366
+ // -- rather than re-reading to drive the loop -- also consumes a one-shot or stateful accessor a
367
+ // direct `for await` accepts.
368
+ // Build (and thereby validate) the hashes BEFORE acquiring the iterator: an unsupported algorithm
369
+ // must reject before the iterator factory runs, so a factory that opens a file or socket is never
370
+ // invoked -- and never leaked outside the cleanup guard -- on a rejection, keeping the lazy-stream
371
+ // guarantee that a pre-hash rejection acquires no resource.
372
+ // A plain index loop, not `_map`: Array.prototype.map SKIPS holes, so a sparse list such as
373
+ // `[, "SHA-256"]` would leave a hole in `hashes` (later read as `undefined` and updated as a raw
374
+ // TypeError) or, if fully sparse, yield sparse output. Indexing every slot instead makes a hole read
375
+ // as `undefined`, which `_normalizeAlg` rejects with a typed webcrypto/syntax before any chunk is read.
376
+ var hashes = [];
377
+ for (var _h = 0; _h < algorithms.length; _h++) {
378
+ var name = _normalizeAlg(algorithms[_h], "digestStream").name;
379
+ var xof = XOF_NODE[_toUpperCase(_String(name))];
380
+ hashes[_h] = xof
381
+ ? nodeCrypto.createHash(xof.node, { outputLength: xof.length })
382
+ : nodeCrypto.createHash(_hashNode(name, "digestStream"));
383
+ }
384
+ var acquire = source == null ? undefined : source[_asyncIterator];
385
+ if (typeof acquire !== "function") {
386
+ throw new WebCryptoError("webcrypto/syntax", "digestStream: source must be an async iterable of byte chunks");
387
+ }
388
+ var iterator = _apply(acquire, source, _NO_ARGS);
389
+ try {
390
+ // The `next` retrieval and its validation are INSIDE the guard: the factory above may have opened a
391
+ // resource, so a non-callable next (or a throwing next accessor) must still reach the cleanup below
392
+ // that calls the iterator's return(), rather than leak on a malformed iterator protocol.
393
+ var nextMethod = iterator == null ? undefined : iterator.next;
394
+ if (typeof nextMethod !== "function") {
395
+ throw new WebCryptoError("webcrypto/syntax", "digestStream: source must be an async iterable of byte chunks");
396
+ }
397
+ for (;;) {
398
+ var step = await _apply(nextMethod, iterator, _NO_ARGS);
399
+ if (step && step.done) break;
400
+ var buf = _toBuf(step == null ? undefined : step.value, "digestStream chunk");
401
+ for (var i = 0; i < hashes.length; i++) _hashUpdate(hashes[i], buf);
402
+ }
403
+ } catch (e) {
404
+ // Best-effort completion, as `for await` performs on an early exit or throw: let the source release.
405
+ // The retrieval of `return` is INSIDE the guard too, so a throwing `return` accessor cannot replace
406
+ // the original error -- the malformed-chunk verdict is what the caller must see, not a cleanup fault.
407
+ try {
408
+ var ret = iterator == null ? undefined : iterator.return;
409
+ if (typeof ret === "function") await _apply(ret, iterator, _NO_ARGS);
410
+ } catch (_r) { /* cleanup is best-effort */ }
411
+ throw e;
412
+ }
413
+ return _map(hashes, function (h) { return _toArrayBuffer(_hashDigest(h)); });
414
+ };
415
+
337
416
  SubtleCrypto.prototype.generateKey = async function generateKey(algorithm, extractable, keyUsages) {
338
417
  var alg = _normalizeAlg(algorithm, "generateKey");
339
418
  // Before the partition below, not only in the CryptoKey constructor: the asymmetric branch splits
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/pki",
3
- "version": "0.5.32",
3
+ "version": "0.5.33",
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",
@@ -83,6 +83,6 @@
83
83
  "devDependencies": {
84
84
  "c8": "12.0.0",
85
85
  "esbuild": "0.28.2",
86
- "eslint": "10.8.1"
86
+ "eslint": "10.9.1"
87
87
  }
88
88
  }
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:e0315ce7-f8af-453b-be94-cae61e3f47ea",
5
+ "serialNumber": "urn:uuid:50c541ec-c574-4bac-aa78-36123ed457dc",
6
6
  "version": 1,
7
7
  "metadata": {
8
- "timestamp": "2026-08-28T18:08:22.906Z",
8
+ "timestamp": "2026-08-29T08:57:14.236Z",
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.32",
22
+ "bom-ref": "@blamejs/pki@0.5.33",
23
23
  "type": "application",
24
24
  "name": "pki",
25
- "version": "0.5.32",
25
+ "version": "0.5.33",
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.32",
29
+ "purl": "pkg:npm/%40blamejs/pki@0.5.33",
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.32",
57
+ "ref": "@blamejs/pki@0.5.33",
58
58
  "dependsOn": []
59
59
  }
60
60
  ]