@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.
- package/CHANGELOG.md +9 -0
- package/README.md +1 -1
- package/lib/cms-sign.js +207 -86
- package/lib/cms-verify.js +360 -239
- package/lib/guard-bytes.js +32 -1
- package/lib/guard-intrinsic.js +13 -0
- package/lib/webcrypto.js +79 -0
- package/package.json +2 -2
- package/sbom.cdx.json +6 -6
package/lib/guard-bytes.js
CHANGED
|
@@ -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
|
});
|
package/lib/guard-intrinsic.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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:
|
|
5
|
+
"serialNumber": "urn:uuid:50c541ec-c574-4bac-aa78-36123ed457dc",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-08-
|
|
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.
|
|
22
|
+
"bom-ref": "@blamejs/pki@0.5.33",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "pki",
|
|
25
|
-
"version": "0.5.
|
|
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.
|
|
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.
|
|
57
|
+
"ref": "@blamejs/pki@0.5.33",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|