@blamejs/pki 0.7.1 → 0.7.3
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 +16 -0
- package/lib/cmp-verify.js +7 -3
- package/lib/pbes2.js +7 -3
- package/lib/pkcs12-build.js +18 -4
- package/lib/tsp-sign.js +6 -0
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,22 @@ 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.7.3 — 2026-09-10
|
|
8
|
+
|
|
9
|
+
A timestamp verification takes its options once, so every pass reads the same answer.
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- pki.tsp.verify reads each option once. A token is examined in more than one pass, and opts.certs was consulted again by each of them, so an option reached through an accessor could hand one set of certificates to the signature check and another to the chain the signer is traced through. No route was found that turns this into a wrong verdict, and a caller passing options as plain values was never affected; the change makes the verdict describe one set of inputs rather than relying on a caller object to answer consistently. It follows the same read-once shape applied to the PKCS#12 verbs in 0.7.2.
|
|
14
|
+
|
|
15
|
+
## v0.7.2 — 2026-09-10
|
|
16
|
+
|
|
17
|
+
A PKCS#12 iteration cap is read once, so it cannot be answered with a value that is not a number.
|
|
18
|
+
|
|
19
|
+
### Security
|
|
20
|
+
|
|
21
|
+
- pki.pkcs12.open and pki.pkcs12.verifyMac hold a store to opts.maxIterations however that option is supplied. The option is the caller's own bound on key-derivation work, and it was read once for each part of its shape check and again by every later pass that applies it: the message authentication code, and then each bag the store carries. An option reached through an accessor answers each read separately, so one that returned a valid integer to the shape check and a non-numeric value to a later comparison left the cap as NaN. No count is above NaN, so the comparison stopped refusing anything and a store demanding far more work than the caller allowed was accepted and its keys derived. A store carrying no message authentication code reaches this through its bags alone, so opening one with opts.allowUnauthenticated was affected even though the authenticated path was not consulted. Both verbs now take the whole options object once, at entry, and every pass reads that one copy, which closes the same door for every option either verb accepts rather than for this one alone. A caller passing options as plain values was never affected. The same read-once shape is applied to the PBES2 and CMP iteration caps, where no route was found that reaches them this way.
|
|
22
|
+
|
|
7
23
|
## v0.7.1 — 2026-09-10
|
|
8
24
|
|
|
9
25
|
A wildcard name in a certificate is held to an excluded name constraint it can reach.
|
package/lib/cmp-verify.js
CHANGED
|
@@ -535,11 +535,15 @@ async function _verifyMac(m, protectedPart, protectionAlg, protection, opts) {
|
|
|
535
535
|
|
|
536
536
|
function _capWork(iterationCount, salt, keyLength, prfHash, opts) {
|
|
537
537
|
var cap = PBMAC1_MAX_ITER;
|
|
538
|
-
|
|
539
|
-
|
|
538
|
+
/** @internal Read once: the shape check and the comparison must read the same answer, or an
|
|
539
|
+
* option reached through an accessor passes as a number and then answers the comparison with
|
|
540
|
+
* something that is not one, leaving the cap NaN and bounding nothing. */
|
|
541
|
+
var wantedIter = opts.maxIterations;
|
|
542
|
+
if (wantedIter != null) {
|
|
543
|
+
if (typeof wantedIter !== "number" || !_isFinite(wantedIter) || wantedIter < 1 || _floor(wantedIter) !== wantedIter) {
|
|
540
544
|
throw _err("cmp/bad-input", "opts.maxIterations must be a positive integer");
|
|
541
545
|
}
|
|
542
|
-
cap = _min(
|
|
546
|
+
cap = _min(wantedIter, cap);
|
|
543
547
|
}
|
|
544
548
|
if (iterationCount < PBMAC1_MIN_ITER) throw _err("cmp/bad-input", "the PBMAC1 iterationCount " + iterationCount + " is below the floor " + PBMAC1_MIN_ITER + " (RFC 8018 sec. 4.2)");
|
|
545
549
|
if (iterationCount > cap) throw _err("cmp/bad-input", "the PBMAC1 iterationCount " + iterationCount + " exceeds the cap " + cap);
|
package/lib/pbes2.js
CHANGED
|
@@ -74,9 +74,13 @@ function parsePbkdf2Params(paramsDer, opts, E, prefix, strictPrf) {
|
|
|
74
74
|
if (salt.length > C.LIMITS.PBKDF2_MAX_SALT) throw E(prefix + "/bad-input", "PBKDF2 salt exceeds the " + C.LIMITS.PBKDF2_MAX_SALT + "-octet cap");
|
|
75
75
|
var iterations = guard.range.positiveInt31(asn1.read.integer(kids[1]), E, prefix + "/bad-input", "PBKDF2 iterationCount");
|
|
76
76
|
var cap = C.LIMITS.PBKDF2_MAX_ITERATIONS;
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
77
|
+
/** @internal Read once: the shape check and the comparison must read the same answer, or an
|
|
78
|
+
* option reached through an accessor passes as a number and then answers the comparison with
|
|
79
|
+
* something that is not one, leaving the cap NaN and bounding nothing. */
|
|
80
|
+
var wantedIter = opts ? opts.maxIterations : null;
|
|
81
|
+
if (wantedIter != null) {
|
|
82
|
+
if (typeof wantedIter !== "number" || !isFinite(wantedIter) || wantedIter < 1 || Math.floor(wantedIter) !== wantedIter) throw E(prefix + "/bad-input", "maxIterations must be a positive integer");
|
|
83
|
+
cap = Math.min(wantedIter, cap);
|
|
80
84
|
}
|
|
81
85
|
if (iterations > cap) throw E(prefix + "/iteration-limit", "PBKDF2 iterationCount " + iterations + " exceeds the cap " + cap);
|
|
82
86
|
var prfNode = "sha1";
|
package/lib/pkcs12-build.js
CHANGED
|
@@ -552,6 +552,8 @@ async function _build(spec, opts) {
|
|
|
552
552
|
async function verifyMac(pfx, password, opts) {
|
|
553
553
|
if (opts != null && typeof opts === "object" && !Buffer.isBuffer(opts)) {
|
|
554
554
|
guard.identifier.assertKnownKeys(opts, _VERIFY_MAC_OPTS, _err, "pkcs12/bad-input", "pki.pkcs12.verifyMac has an unknown option ");
|
|
555
|
+
/** @internal Taken once here, for the reason the same call in `open` states. */
|
|
556
|
+
opts = guard.identifier.snapshotOptions(opts, _VERIFY_MAC_OPTS);
|
|
555
557
|
}
|
|
556
558
|
var m = _storeFromBytes(pfx);
|
|
557
559
|
var valid = await _verifyMacOfStore(m, password, opts);
|
|
@@ -593,11 +595,16 @@ async function _verifyMacOfStore(m, password, opts) {
|
|
|
593
595
|
return computed.length === expected.length && guard.crypto.constantTimeEqual(computed, expected);
|
|
594
596
|
}
|
|
595
597
|
|
|
598
|
+
/** @internal The caller's bound is read once and the shape check and the comparison both read that
|
|
599
|
+
* one answer. Read separately, an option reached through an accessor satisfies every check as a
|
|
600
|
+
* number and then answers the comparison with something that is not one, so `Math.min` yields NaN.
|
|
601
|
+
* Nothing compares above NaN, so the cap stopped bounding the work it exists to bound. */
|
|
596
602
|
function _capWork(iterations, salt, opts, keyLength, hardCap) {
|
|
597
603
|
var cap = hardCap;
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
604
|
+
var wanted = opts.maxIterations;
|
|
605
|
+
if (wanted != null) {
|
|
606
|
+
if (typeof wanted !== "number" || !isFinite(wanted) || wanted < 1 || Math.floor(wanted) !== wanted) throw _err("pkcs12/bad-input", "maxIterations must be a positive integer");
|
|
607
|
+
cap = Math.min(wanted, cap);
|
|
601
608
|
}
|
|
602
609
|
if (iterations > cap) throw _err("pkcs12/iteration-limit", "the MAC iteration count " + iterations + " exceeds the cap " + cap);
|
|
603
610
|
if (salt && salt.length > C.LIMITS.PBKDF2_MAX_SALT) throw _err("pkcs12/bad-input", "the MAC salt exceeds the " + C.LIMITS.PBKDF2_MAX_SALT + "-octet cap");
|
|
@@ -653,6 +660,12 @@ function _capWork(iterations, salt, opts, keyLength, hardCap) {
|
|
|
653
660
|
async function open(pfx, password, opts) {
|
|
654
661
|
opts = opts || {};
|
|
655
662
|
guard.identifier.assertKnownKeys(opts, _OPEN_OPTS, _err, "pkcs12/bad-input", "pki.pkcs12.open has an unknown option ");
|
|
663
|
+
/** @internal Every option is taken once, here, and everything below reads the snapshot. A store
|
|
664
|
+
* is opened in many passes, the MAC and then each bag, so an option left on the caller's object is
|
|
665
|
+
* read again by every one of them. An option reached through an accessor answers each read
|
|
666
|
+
* separately: one that returned a valid integer to this check and something that is not a number
|
|
667
|
+
* to a later bag left the work cap as NaN, and no count is above NaN. */
|
|
668
|
+
opts = guard.identifier.snapshotOptions(opts, _OPEN_OPTS);
|
|
656
669
|
if (opts.maxIterations != null &&(typeof opts.maxIterations !== "number" || !isFinite(opts.maxIterations) || opts.maxIterations < 1 || Math.floor(opts.maxIterations) !== opts.maxIterations)) {
|
|
657
670
|
throw _err("pkcs12/bad-input", "maxIterations must be a positive integer");
|
|
658
671
|
}
|
|
@@ -732,7 +745,8 @@ function _decryptLegacyPbe(ea, ct, password, opts, budget) {
|
|
|
732
745
|
if (e && e.isPkcs12Error) throw e;
|
|
733
746
|
throw _err("pkcs12/bad-algorithm-parameters", "malformed pkcs-12PbeParams (RFC 7292 App. C)", e);
|
|
734
747
|
}
|
|
735
|
-
var
|
|
748
|
+
var wantedLegacy = opts.maxIterations;
|
|
749
|
+
var cap = wantedLegacy != null ? Math.min(wantedLegacy, CLASSIC_MAC_MAX_ITERATIONS) : CLASSIC_MAC_MAX_ITERATIONS;
|
|
736
750
|
if (iterations > cap) throw _err("pkcs12/iteration-limit", "the legacy PBE iteration count " + iterations + " exceeds the cap " + cap);
|
|
737
751
|
if (salt.length > C.LIMITS.PBKDF2_MAX_SALT) throw _err("pkcs12/bad-input", "the legacy PBE salt exceeds the " + C.LIMITS.PBKDF2_MAX_SALT + "-octet cap");
|
|
738
752
|
var u = P12_KDF_UV[scheme.hash].u;
|
package/lib/tsp-sign.js
CHANGED
|
@@ -596,6 +596,12 @@ async function verify(token, data, opts) {
|
|
|
596
596
|
if (typeof opts !== "object" || Buffer.isBuffer(opts)) throw _err("tsp/bad-input", "pki.tsp.verify options must be an object");
|
|
597
597
|
guard.identifier.assertKnownKeys(opts, _VERIFY_OPTS, _err, "tsp/bad-input",
|
|
598
598
|
"pki.tsp.verify has an unknown option (the anchor option is `trustAnchors`) ");
|
|
599
|
+
/** @internal Every option is taken once, here, and each pass below reads that copy. A token is
|
|
600
|
+
* examined in several passes: the signature over it, then the chain from the signer to the
|
|
601
|
+
* caller's anchors. An option left on the caller's object is read again by each, and an option
|
|
602
|
+
* reached through an accessor answers each read separately, so the certificates that verified the
|
|
603
|
+
* signature need not be the certificates the chain was built from. */
|
|
604
|
+
opts = guard.identifier.snapshotOptions(opts, _VERIFY_OPTS);
|
|
599
605
|
if (opts.certs != null && (!Array.isArray(opts.certs) || !guard.list.allMatch(opts.certs, function (c) { return guard.bytes.isByteSource(c); }))) {
|
|
600
606
|
throw _err("tsp/bad-input", "pki.tsp.verify opts.certs must be an array of DER certificate Buffers");
|
|
601
607
|
}
|
package/package.json
CHANGED
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:7729b1e4-672e-4d2e-ac81-8d11135b58a9",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-09-
|
|
8
|
+
"timestamp": "2026-09-10T08:34:35.597Z",
|
|
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.7.
|
|
22
|
+
"bom-ref": "@blamejs/pki@0.7.3",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "pki",
|
|
25
|
-
"version": "0.7.
|
|
25
|
+
"version": "0.7.3",
|
|
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.7.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/pki@0.7.3",
|
|
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.7.
|
|
57
|
+
"ref": "@blamejs/pki@0.7.3",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|