@blamejs/pki 0.6.11 → 0.6.12
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 +17 -0
- package/MIGRATING.md +25 -0
- package/lib/attrcert-sign.js +3 -1
- package/lib/cms-verify.js +3 -0
- package/lib/crl-sign.js +29 -15
- package/lib/crmf-sign.js +5 -4
- package/lib/csr-sign.js +2 -1
- package/lib/ct.js +19 -5
- package/lib/ocsp-verify.js +1 -1
- package/lib/ocsp.js +3 -2
- package/lib/path-validate.js +3 -3
- package/lib/pkcs12-build.js +25 -7
- package/lib/sigstore.js +9 -2
- package/lib/webauthn.js +7 -3
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,23 @@ 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.6.12 — 2026-09-01
|
|
8
|
+
|
|
9
|
+
Three verify verbs that returned a bare boolean now return a verdict object naming the checks they had hidden, a canonical `valid` field is present on every object verify verdict so `if (res.valid)` reads the same everywhere, and revocation, digest, and provenance data omitted before is surfaced.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Every object verify verdict now carries a canonical `valid` boolean beside its existing terminal, so `if (res.valid)` reads the verdict the same way across the toolkit: pki.sigstore.verifyBundle, pki.csr.verify, pki.crmf.verifyPop (top and per message), pki.attrcert.verify, pki.webauthn.verify, pki.webauthn.verifyAssertion, pki.ocsp.verifyRequest (`valid` is `signed && signatureValid`), pki.ct.verifySctList (`valid` is `policyOk`), and pki.pkcs12.open (`valid` is `macVerified`). The existing fields (`verified`, `policyOk`, `macVerified`, and the rest) are unchanged. The multi-state string verdicts (pki.ocsp.verify `status`, pki.scep `status`) keep their terminal and do not gain a `valid` alias, since a boolean cannot carry their states.
|
|
14
|
+
- pki.ocsp.verify and pki.path.verifyOcspResponse surface `revocationTime` (the revocation instant) beside `revocationReason` on a revoked verdict, for long-term validation.
|
|
15
|
+
- pki.cms.verify's primary signer node carries `digestAlgorithm`, the digest the countersignature node already reported, so a caller reading a signer's verdict learns it without a second parse.
|
|
16
|
+
- pki.sigstore.verifyBundle carries `predicateTypeChecked`, so a caller that did not pin `opts.predicateType` learns the in-toto predicate went unverified rather than assuming it was checked.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- pki.crl.verify returns `{ valid, issuerMaySign, signatureValid, issuer, code?, reason? }` instead of a bare boolean. The boolean hid whether the signing certificate was this CRL's issuer and asserted `cRLSign` in its keyUsage, so a CRL minted under an end-entity certificate of the same CA verified as that CA's own. `valid` is `issuerMaySign && signatureValid`; the signature is now checked even when the issuer may not sign, so `signatureValid` is always reported. To upgrade, read `res.valid` where you read the boolean. This is a silent break for an unmigrated `if (await pki.crl.verify(...))`, which now reads an object as always-true.
|
|
21
|
+
- pki.pkcs12.verifyMac returns `{ valid, macAlgorithm, macAlgorithmName, iterationCount }` instead of a bare boolean, so a caller can reject a legacy SHA-1 integrity MAC by inspecting `macAlgorithmName` rather than accepting any authenticated store. To upgrade, read `res.valid`. Silent break for an unmigrated boolean test.
|
|
22
|
+
- pki.ct.verifySctWithLogList returns `{ valid, logId, logIdHex, operator, logState, timestamp }` instead of a bare boolean, surfacing which trusted log accepted the SCT and when for a policy decision. To upgrade, read `res.valid`. Silent break for an unmigrated boolean test.
|
|
23
|
+
|
|
7
24
|
## v0.6.11 — 2026-08-31
|
|
8
25
|
|
|
9
26
|
pki.path.validate and pki.tsp.verify now take the trust store under the option name trustAnchors, the spelling the other verify verbs already use, accepting one anchor or an array; and pki.acme.client gains opts.resignKeys, which re-signs a request under an alternative account-key algorithm when a CA reports badSignatureAlgorithm.
|
package/MIGRATING.md
CHANGED
|
@@ -14,6 +14,31 @@ The toolkit has no `deprecate()`-marked surface awaiting removal.
|
|
|
14
14
|
|
|
15
15
|
Listed newest-first.
|
|
16
16
|
|
|
17
|
+
### v0.6.12 — `pki.crl.verify, pki.pkcs12.verifyMac, and pki.ct.verifySctWithLogList`
|
|
18
|
+
|
|
19
|
+
Each returned a bare boolean and now returns a verdict object; read `res.valid` where you read the boolean.
|
|
20
|
+
|
|
21
|
+
A bare boolean hides which checks ran. Each of these three verbs now returns an object whose
|
|
22
|
+
`valid` field is the boolean it used to return, alongside the checks and data it had dropped:
|
|
23
|
+
|
|
24
|
+
- `pki.crl.verify` -> `{ valid, issuerMaySign, signatureValid, issuer, code?, reason? }`
|
|
25
|
+
- `pki.pkcs12.verifyMac` -> `{ valid, macAlgorithm, macAlgorithmName, iterationCount }`
|
|
26
|
+
- `pki.ct.verifySctWithLogList` -> `{ valid, logId, logIdHex, operator, logState, timestamp }`
|
|
27
|
+
|
|
28
|
+
```js
|
|
29
|
+
if (await pki.crl.verify(crl, { cert })) // used to be the verdict; now an always-true object
|
|
30
|
+
|
|
31
|
+
var res = await pki.crl.verify(crl, { cert });
|
|
32
|
+
if (res.valid) { /* ... */ }
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
This is a SILENT break: the old return was a boolean, so an unmigrated `if (await verify(...))`
|
|
36
|
+
reads the new object as truthy and accepts what it used to reject. Switch every such test to
|
|
37
|
+
`res.valid`. The new fields let a caller act on the detail the boolean hid: `pki.crl.verify`
|
|
38
|
+
reports whether the certificate was allowed to sign the CRL (`issuerMaySign`) separately from the
|
|
39
|
+
signature (`signatureValid`); `pki.pkcs12.verifyMac` names the integrity algorithm so a legacy
|
|
40
|
+
SHA-1 MAC can be refused; `pki.ct.verifySctWithLogList` carries the resolved trusted-log record.
|
|
41
|
+
|
|
17
42
|
### v0.6.11 — `pki.path.validate(path, opts) and pki.tsp.verify(token, data, opts)`
|
|
18
43
|
|
|
19
44
|
The trust anchor option is now `trustAnchors` (a single anchor or an array); the former singular `trustAnchor` is removed and refused by name.
|
package/lib/attrcert-sign.js
CHANGED
|
@@ -776,7 +776,7 @@ function _unsupportedCritical(ac) {
|
|
|
776
776
|
|
|
777
777
|
/**
|
|
778
778
|
* @primitive pki.attrcert.verify
|
|
779
|
-
* @signature pki.attrcert.verify(ac, issuer, opts) -> Promise<{ verified, signatureValid, validityChecked, targetingChecked, revocationChecked, noRevAvail, holderBindingChecked, issuerPathChecked, holder, issuer, attributes, extensions, notBefore, notAfter, serialNumberHex, reason }>
|
|
779
|
+
* @signature pki.attrcert.verify(ac, issuer, opts) -> Promise<{ valid, verified, signatureValid, validityChecked, targetingChecked, revocationChecked, noRevAvail, holderBindingChecked, issuerPathChecked, holder, issuer, attributes, extensions, notBefore, notAfter, serialNumberHex, reason }>
|
|
780
780
|
* @since 0.5.15
|
|
781
781
|
* @status stable
|
|
782
782
|
* @spec RFC 5755 sec. 5
|
|
@@ -949,6 +949,7 @@ function _verify(ac, issuer, opts) {
|
|
|
949
949
|
noRevAvail: false,
|
|
950
950
|
holderBindingChecked: false, // item 1 needs the holder's certificate, which this verb is not given
|
|
951
951
|
issuerPathChecked: false, // items 2 and 3 need the issuer's chain, likewise
|
|
952
|
+
valid: false, // canonical toolkit-wide verdict alias, tracks `verified`
|
|
952
953
|
verified: false,
|
|
953
954
|
reason: undefined,
|
|
954
955
|
};
|
|
@@ -1028,6 +1029,7 @@ function _verify(ac, issuer, opts) {
|
|
|
1028
1029
|
}
|
|
1029
1030
|
|
|
1030
1031
|
result.verified = true;
|
|
1032
|
+
result.valid = true;
|
|
1031
1033
|
return result;
|
|
1032
1034
|
});
|
|
1033
1035
|
}
|
package/lib/cms-verify.js
CHANGED
|
@@ -873,6 +873,9 @@ async function _verify(input, opts) {
|
|
|
873
873
|
verdict.signedAttributesPresent = !!siOne.signedAttrsBytes;
|
|
874
874
|
// The authenticated attributes themselves, bound to this signer's verdict (read only when ok).
|
|
875
875
|
verdict.signedAttributes = _surfaceSignedAttrs(siOne);
|
|
876
|
+
// The SignerInfo digestAlgorithm on the primary signer node (the countersignature node already
|
|
877
|
+
// carries it), so a caller reading a signer's verdict learns the digest without re-parsing.
|
|
878
|
+
verdict.digestAlgorithm = siOne.digestAlgorithm.name;
|
|
876
879
|
intrinsic.push(signers, verdict);
|
|
877
880
|
}
|
|
878
881
|
// The content type travels with the verdict. An operator applying a policy of their own -- "I only
|
package/lib/crl-sign.js
CHANGED
|
@@ -637,7 +637,7 @@ function _issuerMaySign(parsed, cert) {
|
|
|
637
637
|
|
|
638
638
|
/**
|
|
639
639
|
* @primitive pki.crl.verify
|
|
640
|
-
* @signature pki.crl.verify(crl, issuer) -> Promise<
|
|
640
|
+
* @signature pki.crl.verify(crl, issuer) -> Promise<{ valid, issuerMaySign, signatureValid, issuer, code?, reason? }>
|
|
641
641
|
* @since 0.3.9
|
|
642
642
|
* @status stable
|
|
643
643
|
* @spec RFC 5280 sec. 5.1.1.3, RFC 9814
|
|
@@ -648,17 +648,19 @@ function _issuerMaySign(parsed, cert) {
|
|
|
648
648
|
* is a DER `Buffer`, a PEM string, or a parsed CRL; `issuer` is `{ cert }` (DER/PEM/parsed), `{ publicKey }`
|
|
649
649
|
* (SPKI DER), or a raw SPKI `Buffer`. Verification composes the one path-validation signature engine
|
|
650
650
|
* `pki.path.crlChecker` uses -- the same algorithm-confusion (RFC 9814 sec. 4 key-OID == sig-OID) and
|
|
651
|
-
* EdDSA low-order-point gates -- so there is no second, weaker CRL verifier.
|
|
652
|
-
* any
|
|
651
|
+
* EdDSA low-order-point gates -- so there is no second, weaker CRL verifier. The verdict's `valid` is
|
|
652
|
+
* `false` on any verification fault, and `signatureValid` reports the signature check on its own; malformed
|
|
653
|
+
* input throws a typed `CrlError`.
|
|
653
654
|
*
|
|
654
655
|
* Given a certificate in place of a bare key, it also asks what only a certificate can answer: that
|
|
655
656
|
* the certificate is the issuer this CRL names, and that its keyUsage, when it carries one, asserts
|
|
656
|
-
* `cRLSign` (RFC 5280 sec. 4.2.1.3, the same rule this module's signing side already enforces).
|
|
657
|
-
*
|
|
658
|
-
*
|
|
659
|
-
*
|
|
660
|
-
*
|
|
661
|
-
* is checked.
|
|
657
|
+
* `cRLSign` (RFC 5280 sec. 4.2.1.3, the same rule this module's signing side already enforces). Those two
|
|
658
|
+
* are `issuerMaySign`, reported beside `signatureValid` so a caller sees which held: `valid` is their
|
|
659
|
+
* conjunction. A signature verifying says only that SOME key signed these bytes, so a bare `valid` boolean
|
|
660
|
+
* would let a CRL minted under an end-entity certificate of the same CA read as that CA's own. Handed a bare
|
|
661
|
+
* SPKI there is no certificate to carry either restriction, so `issuerMaySign` is `true` and only the
|
|
662
|
+
* signature is checked. `issuer` is the CRL's issuer name; on a failure `code` / `reason` name which check
|
|
663
|
+
* failed. Currency and distribution-point scope remain `pki.path.crlChecker`.
|
|
662
664
|
*
|
|
663
665
|
* @example
|
|
664
666
|
* var pair = await pki.key.generate("Ed25519");
|
|
@@ -669,17 +671,29 @@ function _issuerMaySign(parsed, cert) {
|
|
|
669
671
|
* extensions: { basicConstraints: { cA: true }, keyUsage: ["keyCertSign", "cRLSign"] } }, { key: signerKeyPkcs8 });
|
|
670
672
|
* var crlDer = await pki.crl.sign({ thisUpdate: new Date("2026-01-01T00:00:00Z"), crlNumber: 1n, revoked: [] },
|
|
671
673
|
* { cert: signerCertDer, key: signerKeyPkcs8 });
|
|
672
|
-
* var
|
|
674
|
+
* var res = await pki.crl.verify(crlDer, { publicKey: signerSpki }); // { valid, signatureValid, issuerMaySign, issuer }
|
|
673
675
|
*/
|
|
674
676
|
function verify(crl, issuer) { return guard.async.deferred(function () { return _verify(crl, issuer); }); }
|
|
675
677
|
function _verify(crl, issuer) {
|
|
676
678
|
var parsed = _coerceCrl(crl);
|
|
677
679
|
var resolved = _resolveIssuer(issuer);
|
|
678
|
-
//
|
|
679
|
-
//
|
|
680
|
-
//
|
|
681
|
-
|
|
682
|
-
|
|
680
|
+
// A bare SPKI carries no certificate, so the two cert-only questions cannot be asked and only the
|
|
681
|
+
// signature is checked (issuerMaySign is then reported true). Both checks are surfaced on the verdict:
|
|
682
|
+
// a bare `true` used to hide whether the signer was ALLOWED to sign this CRL, so `if (verify(...))`
|
|
683
|
+
// accepted a CRL minted under an end-entity certificate of the same CA as that CA's own.
|
|
684
|
+
var issuerMaySign = resolved.cert ? _issuerMaySign(parsed, resolved.cert) : true;
|
|
685
|
+
return crlVerify.verifyCrlSignature(parsed, resolved.spki).then(function (sigOk) {
|
|
686
|
+
var signatureValid = sigOk === true;
|
|
687
|
+
var valid = issuerMaySign && signatureValid;
|
|
688
|
+
var out = { valid: valid, issuerMaySign: issuerMaySign, signatureValid: signatureValid, issuer: parsed.issuer };
|
|
689
|
+
if (!valid) {
|
|
690
|
+
out.code = !issuerMaySign ? "crl/issuer-may-not-sign" : "crl/bad-signature";
|
|
691
|
+
out.reason = !issuerMaySign
|
|
692
|
+
? "the issuer certificate is not this CRL's issuer, or its keyUsage does not assert cRLSign (RFC 5280 sec. 4.2.1.3)"
|
|
693
|
+
: "the CRL signature did not verify under the issuer key";
|
|
694
|
+
}
|
|
695
|
+
return out;
|
|
696
|
+
});
|
|
683
697
|
}
|
|
684
698
|
|
|
685
699
|
function _serialHexOf(serial) {
|
package/lib/crmf-sign.js
CHANGED
|
@@ -632,7 +632,7 @@ function _verifyOne(msg) {
|
|
|
632
632
|
subjectBound: false,
|
|
633
633
|
publicKey: (t && t.publicKey && t.publicKey.bytes) || null,
|
|
634
634
|
};
|
|
635
|
-
function settle(extra) {
|
|
635
|
+
function settle(extra) { var v = Object.assign({}, base, extra); v.valid = v.verified === true; return Promise.resolve(v); } // `valid` is the canonical toolkit-wide alias of this message's `verified`
|
|
636
636
|
|
|
637
637
|
var popo = msg.popo;
|
|
638
638
|
if (popo == null) {
|
|
@@ -679,7 +679,7 @@ function _verifyOne(msg) {
|
|
|
679
679
|
var bound = !pin;
|
|
680
680
|
return crmfVerify.verifyPopSignature(popo, spki, preimage).then(function (ok) {
|
|
681
681
|
return Object.assign({}, base, {
|
|
682
|
-
verified: ok === true, method: "signature", cryptographicallyVerified: ok === true,
|
|
682
|
+
valid: ok === true, verified: ok === true, method: "signature", cryptographicallyVerified: ok === true,
|
|
683
683
|
subject: bound ? base.subject : null,
|
|
684
684
|
subjectBound: bound,
|
|
685
685
|
publicKey: spki || null,
|
|
@@ -690,7 +690,7 @@ function _verifyOne(msg) {
|
|
|
690
690
|
|
|
691
691
|
/**
|
|
692
692
|
* @primitive pki.crmf.verifyPop
|
|
693
|
-
* @signature pki.crmf.verifyPop(messages) -> Promise<{ verified, messages: [{ verified, method, cryptographicallyVerified, certReqId, subject, subjectBound, publicKey, reason }] }>
|
|
693
|
+
* @signature pki.crmf.verifyPop(messages) -> Promise<{ valid, verified, messages: [{ valid, verified, method, cryptographicallyVerified, certReqId, subject, subjectBound, publicKey, reason }] }>
|
|
694
694
|
* @since 0.5.14
|
|
695
695
|
* @status stable
|
|
696
696
|
* @spec RFC 4211 sec. 4.1
|
|
@@ -736,7 +736,8 @@ function verifyPop(messages) { return guard.async.deferred(function () { return
|
|
|
736
736
|
function _verifyPop(messages) {
|
|
737
737
|
var parsed = _coerceMessages(messages);
|
|
738
738
|
return Promise.all(parsed.messages.map(_verifyOne)).then(function (out) {
|
|
739
|
-
|
|
739
|
+
var allVerified = out.length > 0 && out.every(function (m) { return m.verified === true; });
|
|
740
|
+
return { valid: allVerified, verified: allVerified, messages: out };
|
|
740
741
|
});
|
|
741
742
|
}
|
|
742
743
|
|
package/lib/csr-sign.js
CHANGED
|
@@ -181,7 +181,7 @@ function _coerceCsr(request) {
|
|
|
181
181
|
|
|
182
182
|
/**
|
|
183
183
|
* @primitive pki.csr.verify
|
|
184
|
-
* @signature pki.csr.verify(request) -> Promise<{ verified, subject, subjectPublicKeyInfo, attributes, certificationRequestInfoBytes }>
|
|
184
|
+
* @signature pki.csr.verify(request) -> Promise<{ valid, verified, subject, subjectPublicKeyInfo, attributes, certificationRequestInfoBytes }>
|
|
185
185
|
* @since 0.5.13
|
|
186
186
|
* @status stable
|
|
187
187
|
* @spec RFC 2986 sec. 4.2
|
|
@@ -237,6 +237,7 @@ function _verify(request) {
|
|
|
237
237
|
var parsed = _coerceCsr(request);
|
|
238
238
|
return Promise.resolve(csrVerify.verifyCsrSignature(parsed)).then(function (ok) {
|
|
239
239
|
return {
|
|
240
|
+
valid: ok === true, // canonical toolkit-wide verdict alias, beside the verb's own `verified`
|
|
240
241
|
verified: ok === true,
|
|
241
242
|
subject: parsed.subject,
|
|
242
243
|
subjectPublicKeyInfo: parsed.subjectPublicKeyInfo,
|
package/lib/ct.js
CHANGED
|
@@ -813,7 +813,7 @@ function _resolveNotAfter(entry, opts) {
|
|
|
813
813
|
|
|
814
814
|
/**
|
|
815
815
|
* @primitive pki.ct.verifySctWithLogList
|
|
816
|
-
* @signature pki.ct.verifySctWithLogList(entry, sct, logList, opts?) -> Promise<
|
|
816
|
+
* @signature pki.ct.verifySctWithLogList(entry, sct, logList, opts?) -> Promise<{ valid, logId, logIdHex, operator, logState, timestamp }>
|
|
817
817
|
* @since 0.2.28
|
|
818
818
|
* @status stable
|
|
819
819
|
* @spec RFC 6962
|
|
@@ -827,8 +827,10 @@ function _resolveNotAfter(entry, opts) {
|
|
|
827
827
|
* `opts.certNotAfter`, must fall in `[start_inclusive, end_exclusive)`, and a windowed log with no
|
|
828
828
|
* resolvable notAfter is `ct/temporal-interval`, never silently skipped). Then the crypto is delegated to
|
|
829
829
|
* the shipped `verifySct(entry, sct, log.key)` (which independently re-checks `logId == SHA-256(key)`).
|
|
830
|
-
*
|
|
831
|
-
* mismatch
|
|
830
|
+
* The verdict's `valid` is `true` for a valid signature from a trusted, in-window log and `false` on a
|
|
831
|
+
* cryptographic mismatch; `logId` / `logIdHex` / `operator` / `logState` / `timestamp` carry the resolved
|
|
832
|
+
* log record so a caller can record which trusted log accepted the SCT and when, for a policy decision,
|
|
833
|
+
* without re-deriving them. It throws a typed `CtError` on any structural / trust failure.
|
|
832
834
|
*
|
|
833
835
|
* @opts certNotAfter A `Date` -- the covered certificate's notAfter for the temporal-interval gate (required for a precert entry).
|
|
834
836
|
* @example
|
|
@@ -846,7 +848,7 @@ function _resolveNotAfter(entry, opts) {
|
|
|
846
848
|
* description: "Example Log", log_id: logId.toString("base64"), key: logSpki.toString("base64"),
|
|
847
849
|
* url: "https://ct.example/log/", mmd: 86400,
|
|
848
850
|
* state: { usable: { timestamp: "2026-01-01T00:00:00Z" } } }] }] }), "utf8"));
|
|
849
|
-
* var
|
|
851
|
+
* var res = await pki.ct.verifySctWithLogList(sctEntry, embeddedSct, logList); // { valid, logId, logIdHex, operator, logState, timestamp }
|
|
850
852
|
*/
|
|
851
853
|
// The pure, NON-THROWING log resolution + state + temporal classifier shared by verifySctWithLogList
|
|
852
854
|
// (which throws the mapped code on a non-ok status) and verifySctList (which records the code into a
|
|
@@ -887,7 +889,18 @@ async function verifySctWithLogList(entry, sct, logList, opts) {
|
|
|
887
889
|
opts = opts || {};
|
|
888
890
|
var c = _classifyLog(sct, logList, entry, opts);
|
|
889
891
|
if (c.status !== "ok") throw _ctErr(c.code, c.message);
|
|
890
|
-
|
|
892
|
+
// The crypto verdict alone hid WHICH trusted log accepted the SCT and when. Surface the resolved log
|
|
893
|
+
// record so a caller can record the operator / log state / timestamp for a policy decision instead of
|
|
894
|
+
// re-deriving them, rather than reading a bare boolean that drops the whole record it just resolved.
|
|
895
|
+
var valid = await verifySct(entry, sct, c.log.key); // re-checks the logId binding
|
|
896
|
+
return {
|
|
897
|
+
valid: valid,
|
|
898
|
+
logId: c.log.logId,
|
|
899
|
+
logIdHex: c.log.logIdHex,
|
|
900
|
+
operator: c.log.operator,
|
|
901
|
+
logState: c.log.state.name,
|
|
902
|
+
timestamp: sct.timestamp,
|
|
903
|
+
};
|
|
891
904
|
}
|
|
892
905
|
|
|
893
906
|
// Options verifySctList reads. All optional; the defaults are the RFC 6962 sec. 3.3 floor (>= 1).
|
|
@@ -1030,6 +1043,7 @@ async function verifySctList(entry, list, logList, opts) {
|
|
|
1030
1043
|
var operatorCount = operators.length;
|
|
1031
1044
|
var policyOk = validScts >= minScts && operatorCount >= minOperators;
|
|
1032
1045
|
return {
|
|
1046
|
+
valid: policyOk, // canonical toolkit-wide verdict alias, beside this verb's `policyOk`
|
|
1033
1047
|
policyOk: policyOk, totalScts: scts.length + unknownScts, validScts: validScts, unknownScts: unknownScts,
|
|
1034
1048
|
operatorCount: operatorCount, operators: operators, required: { minScts: minScts, minOperators: minOperators },
|
|
1035
1049
|
results: results,
|
package/lib/ocsp-verify.js
CHANGED
|
@@ -227,7 +227,7 @@ function makeOcspVerify(deps) {
|
|
|
227
227
|
// allow:nan-date-comparison-unguarded -- revocationTime is codec-parsed (NaN-rejected); a NaN check time makes this FAIL CLOSED (sawGood stays unset -> not treated good), and `time` is validated at the pki.ocsp.verify / path.verifyOcspResponse entry points.
|
|
228
228
|
if (historical && guard.time.isDate(st.revocationTime) &&
|
|
229
229
|
guard.time.instantOf(st.revocationTime) > guard.time.instantOf(time)) { out.sawGood = true; }
|
|
230
|
-
else if (!out.revoked) { out.revoked = { revocationReason: st.revocationReason || null, reason: "certificate reported revoked by an authorized OCSP responder" + (st.revocationReason ? " (" + st.revocationReason + ")" : "") }; }
|
|
230
|
+
else if (!out.revoked) { out.revoked = { revocationReason: st.revocationReason || null, revocationTime: guard.time.isDate(st.revocationTime) ? st.revocationTime : null, reason: "certificate reported revoked by an authorized OCSP responder" + (st.revocationReason ? " (" + st.revocationReason + ")" : "") }; }
|
|
231
231
|
} else if (st.type === "good") { out.sawGood = true; }
|
|
232
232
|
else { out.sawUnknownStatus = true; }
|
|
233
233
|
}
|
package/lib/ocsp.js
CHANGED
|
@@ -462,7 +462,7 @@ function buildErrorResponse(status) {
|
|
|
462
462
|
|
|
463
463
|
/**
|
|
464
464
|
* @primitive pki.ocsp.verify
|
|
465
|
-
* @signature pki.ocsp.verify(response, opts) -> Promise<{ status, responderAuthorized, signatureValid, thisUpdate, nextUpdate, revocationReason?, nonceMatched?, reason }>
|
|
465
|
+
* @signature pki.ocsp.verify(response, opts) -> Promise<{ status, responderAuthorized, signatureValid, thisUpdate, nextUpdate, revocationReason?, revocationTime?, nonceMatched?, reason }>
|
|
466
466
|
* @since 0.2.22
|
|
467
467
|
* @status stable
|
|
468
468
|
* @spec RFC 6960, RFC 9654, RFC 5019
|
|
@@ -593,6 +593,7 @@ function _requestFromBytes(request) {
|
|
|
593
593
|
}
|
|
594
594
|
function _reqVerdict(parsed, extra) {
|
|
595
595
|
return {
|
|
596
|
+
valid: extra.signed === true && extra.signatureValid === true, // canonical alias: the request was signed AND the signature verified
|
|
596
597
|
signed: extra.signed, signatureValid: extra.signatureValid,
|
|
597
598
|
signerCert: extra.signerCert != null ? extra.signerCert : null,
|
|
598
599
|
signerCerts: extra.signerCerts != null ? extra.signerCerts : [],
|
|
@@ -654,7 +655,7 @@ function _signerVerdict(parsed, found, failReason) {
|
|
|
654
655
|
|
|
655
656
|
/**
|
|
656
657
|
* @primitive pki.ocsp.verifyRequest
|
|
657
|
-
* @signature pki.ocsp.verifyRequest(request, opts?) -> Promise<{ signed, signatureValid, signerCert, signerCerts, certs, signerSubject, requestorName, requestList, requestExtensions, version, reason }>
|
|
658
|
+
* @signature pki.ocsp.verifyRequest(request, opts?) -> Promise<{ valid, signed, signatureValid, signerCert, signerCerts, certs, signerSubject, requestorName, requestList, requestExtensions, version, reason }>
|
|
658
659
|
* @since 0.6.0
|
|
659
660
|
* @status stable
|
|
660
661
|
* @spec RFC 6960
|
package/lib/path-validate.js
CHANGED
|
@@ -2583,7 +2583,7 @@ function ocspChecker(responses) {
|
|
|
2583
2583
|
for (var k = 0; k < parsed.length; k++) {
|
|
2584
2584
|
var v = await ocspCore.evaluateResponse(parsed[k], cert, issuer, issuerKeyBits, issuerNameCandidates, time, historical);
|
|
2585
2585
|
if (v.revoked && !revokedResult) {
|
|
2586
|
-
revokedResult = { status: "revoked", revocationReason: v.revoked.revocationReason, reason: v.revoked.reason };
|
|
2586
|
+
revokedResult = { status: "revoked", revocationReason: v.revoked.revocationReason, revocationTime: v.revoked.revocationTime, reason: v.revoked.reason };
|
|
2587
2587
|
}
|
|
2588
2588
|
if (v.sawGood) sawGood = true;
|
|
2589
2589
|
if (v.sawUnknownStatus) sawUnknownStatus = true;
|
|
@@ -2602,7 +2602,7 @@ function ocspChecker(responses) {
|
|
|
2602
2602
|
|
|
2603
2603
|
/**
|
|
2604
2604
|
* @primitive pki.path.verifyOcspResponse
|
|
2605
|
-
* @signature pki.path.verifyOcspResponse(response, cert, issuerCert, time, opts?) -> Promise<{ status, responderAuthorized, signatureValid, matched, thisUpdate, nextUpdate, revocationReason?, reason }>
|
|
2605
|
+
* @signature pki.path.verifyOcspResponse(response, cert, issuerCert, time, opts?) -> Promise<{ status, responderAuthorized, signatureValid, matched, thisUpdate, nextUpdate, revocationReason?, revocationTime?, reason }>
|
|
2606
2606
|
* @since 0.2.22
|
|
2607
2607
|
* @status stable
|
|
2608
2608
|
* @spec RFC 6960
|
|
@@ -2728,7 +2728,7 @@ function _verifyOcspParsed(parsedResponse, cert, issuerCert, time, opts) {
|
|
|
2728
2728
|
try { issuerKeyBits = ocspVerify.ocspKeyValue(issuerCert.subjectPublicKeyInfo.bytes); }
|
|
2729
2729
|
catch (_e) { return unbound("the issuer public key could not be read to recompute the OCSP CertID"); }
|
|
2730
2730
|
return ocspCore.evaluateResponse(parsedResponse, cert, issuerCtx, issuerKeyBits, issuerNameCandidates, time, opts.historicalMode === true).then(function (v) {
|
|
2731
|
-
if (v.revoked) return { status: "revoked", responderAuthorized: true, signatureValid: true, matched: true, thisUpdate: v.thisUpdate, nextUpdate: v.nextUpdate, revocationReason: v.revoked.revocationReason, reason: v.revoked.reason };
|
|
2731
|
+
if (v.revoked) return { status: "revoked", responderAuthorized: true, signatureValid: true, matched: true, thisUpdate: v.thisUpdate, nextUpdate: v.nextUpdate, revocationReason: v.revoked.revocationReason, revocationTime: v.revoked.revocationTime, reason: v.revoked.reason };
|
|
2732
2732
|
if (v.sawGood) return { status: "good", responderAuthorized: true, signatureValid: true, matched: true, thisUpdate: v.thisUpdate, nextUpdate: v.nextUpdate, reason: "good" };
|
|
2733
2733
|
return { status: "unknown", responderAuthorized: v.responderAuthorized === true, signatureValid: v.signatureValid === true, matched: v.matched === true, thisUpdate: v.thisUpdate, nextUpdate: v.nextUpdate, reason: v.reason };
|
|
2734
2734
|
});
|
package/lib/pkcs12-build.js
CHANGED
|
@@ -50,6 +50,7 @@ var pkix = require("./schema-pkix");
|
|
|
50
50
|
var pkiBuild = require("./pki-build"); // reqDenseArray: reject a sparse/nullish caller array as a typed pkcs12/bad-input
|
|
51
51
|
var guard = require("./guard-all");
|
|
52
52
|
var intrinsic = require("./guard-intrinsic");
|
|
53
|
+
var _toLower = intrinsic.uncurry(String.prototype.toLowerCase); // bound at load so a replaced String.prototype.toLowerCase cannot steer a hash-name comparison
|
|
53
54
|
var frameworkError = require("./framework-error");
|
|
54
55
|
var C = require("./constants");
|
|
55
56
|
|
|
@@ -779,7 +780,7 @@ async function _build(spec, opts) {
|
|
|
779
780
|
|
|
780
781
|
/**
|
|
781
782
|
* @primitive pki.pkcs12.verifyMac
|
|
782
|
-
* @signature pki.pkcs12.verifyMac(pfx, password, opts?) -> Promise<
|
|
783
|
+
* @signature pki.pkcs12.verifyMac(pfx, password, opts?) -> Promise<{ valid, macAlgorithm, macAlgorithmName, iterationCount }>
|
|
783
784
|
* @since 0.3.11
|
|
784
785
|
* @status stable
|
|
785
786
|
* @spec RFC 7292 sec. 5.1, RFC 9579
|
|
@@ -792,8 +793,11 @@ async function _build(spec, opts) {
|
|
|
792
793
|
* came from one place. `Object.assign`, spread and a JSON round-trip drop the mark. The password is BMPString+NULL encoded (RFC 7292 App. B.1), the MAC is
|
|
793
794
|
* recomputed over the store's exact AuthenticatedSafe byte range (`macedBytes`) using the store's own MAC
|
|
794
795
|
* parameters -- the classic Appendix B (ID=3) HMAC or the RFC 9579 PBMAC1 -- and constant-time-compared to
|
|
795
|
-
* the stored MAC value.
|
|
796
|
-
*
|
|
796
|
+
* the stored MAC value. The verdict's `valid` is the password match, and `macAlgorithm` (`hmac` or `pbmac1`),
|
|
797
|
+
* `macAlgorithmName` (the digest / scheme, e.g. `sha1`, `sha256`), and `iterationCount` name the integrity
|
|
798
|
+
* algorithm the store used, so a caller can reject a legacy SHA-1 MAC by inspecting them rather than
|
|
799
|
+
* accepting any `valid` store. Throws `Pkcs12Error` on a MAC-less or public-key-integrity store, or an
|
|
800
|
+
* unsupported MAC algorithm (never a falsy verdict standing in for an error).
|
|
797
801
|
*
|
|
798
802
|
* @example
|
|
799
803
|
* var pair = await pki.key.generate("Ed25519");
|
|
@@ -801,13 +805,24 @@ async function _build(spec, opts) {
|
|
|
801
805
|
* var signerCertDer = await pki.x509.sign({ subject: "Signer", subjectPublicKey: await pki.key.export(pair.publicKey),
|
|
802
806
|
* notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") }, { key: signerKeyPkcs8 });
|
|
803
807
|
* var p12 = await pki.pkcs12.build({ key: signerKeyPkcs8, cert: signerCertDer }, { password: 'changeit' });
|
|
804
|
-
* var
|
|
808
|
+
* var res = await pki.pkcs12.verifyMac(p12, 'changeit'); // { valid, macAlgorithm, macAlgorithmName, iterationCount }
|
|
805
809
|
*/
|
|
806
810
|
async function verifyMac(pfx, password, opts) {
|
|
807
811
|
if (opts != null && typeof opts === "object" && !Buffer.isBuffer(opts)) {
|
|
808
812
|
guard.identifier.assertKnownKeys(opts, _VERIFY_MAC_OPTS, _err, "pkcs12/bad-input", "pki.pkcs12.verifyMac has an unknown option ");
|
|
809
813
|
}
|
|
810
|
-
|
|
814
|
+
var m = _storeFromBytes(pfx);
|
|
815
|
+
// _verifyMacOfStore throws pkcs12/bad-input for a store carrying no password MAC, so m.mac is present
|
|
816
|
+
// once it returns. Surface the MAC algorithm the store used so a caller can reject an integrity
|
|
817
|
+
// algorithm it does not accept (e.g. a legacy SHA-1 classic MAC), instead of a bare boolean that hides it.
|
|
818
|
+
var valid = await _verifyMacOfStore(m, password, opts);
|
|
819
|
+
var isHmac = m.mac.kind === "hmac";
|
|
820
|
+
return {
|
|
821
|
+
valid: valid,
|
|
822
|
+
macAlgorithm: m.mac.kind,
|
|
823
|
+
macAlgorithmName: isHmac ? _toLower(m.mac.hashName || "") : m.mac.pbmac1.schemeName,
|
|
824
|
+
iterationCount: isHmac ? m.mac.iterations : m.mac.pbmac1.kdf.iterationCount,
|
|
825
|
+
};
|
|
811
826
|
}
|
|
812
827
|
|
|
813
828
|
// The MAC computation over a store this module has ALREADY parsed from bytes. Separate from the
|
|
@@ -819,7 +834,7 @@ async function _verifyMacOfStore(m, password, opts) {
|
|
|
819
834
|
var expected = m.mac.macValue;
|
|
820
835
|
var computed;
|
|
821
836
|
if (m.mac.kind === "hmac") {
|
|
822
|
-
var node = (m.mac.hashName || "")
|
|
837
|
+
var node = _toLower(m.mac.hashName || "");
|
|
823
838
|
if (!P12_KDF_UV[node]) throw _err("pkcs12/unsupported-algorithm", "unsupported classic MAC hash " + m.mac.hashName);
|
|
824
839
|
// The iteration count and salt are attacker-controlled work factors: bound them BEFORE the KDF runs.
|
|
825
840
|
_capWork(m.mac.iterations, m.mac.macSalt, opts, undefined, CLASSIC_MAC_MAX_ITERATIONS);
|
|
@@ -937,7 +952,10 @@ async function open(pfx, password, opts) {
|
|
|
937
952
|
// PBES2 uses UTF-8 (the pinned interop convention), a legacy App. C scheme uses the BMPString+NULL form (the
|
|
938
953
|
// App. B.1 encoding the classic MAC also uses) -- both distinct from the public-key integrity keypair. A
|
|
939
954
|
// wrong bag password fails at the first encrypted bag as the uniform pkcs12/decrypt-failed.
|
|
940
|
-
|
|
955
|
+
// `valid` is the integrity verdict: open THROWS on an integrity failure for both a password MAC and a
|
|
956
|
+
// public-key CMS signature, so a returned store with either mode is verified; only an allowUnauthenticated
|
|
957
|
+
// (MAC-less) store reaches here unverified. macVerified alone would read false for a valid public-key store.
|
|
958
|
+
var out = { valid: macVerified || m.integrityMode === "public-key", integrityMode: m.integrityMode, macVerified: macVerified, signers: signers, keys: [], certs: [], crls: [], secrets: [] };
|
|
941
959
|
var i;
|
|
942
960
|
var kdfBudget = { rounds: KDF_MAX_ROUNDS }; // aggregate KDF work budget for this whole open(), both schemes
|
|
943
961
|
for (i = 0; i < m.safeBags.length; i++) _openBag(m.safeBags[i], password, opts, out, 0, kdfBudget);
|
package/lib/sigstore.js
CHANGED
|
@@ -647,8 +647,10 @@ function _statement(payload, payloadType, expectedPredicate) {
|
|
|
647
647
|
* of the Rekor log time; the Rekor inclusion proof folded to a Rekor-signed root;
|
|
648
648
|
* the log entry bound to this exact signature; and the in-toto SLSA statement.
|
|
649
649
|
* Any leg failing throws a typed `sigstore/*` error. On success returns
|
|
650
|
-
* `{ verified: true, payload, statement, subjects, predicateType, predicate,
|
|
651
|
-
* identity, identityChecked, integratedTime }
|
|
650
|
+
* `{ valid: true, verified: true, payload, statement, subjects, predicateType, predicate,
|
|
651
|
+
* identity, identityChecked, predicateTypeChecked, integratedTime }` (`valid` is the canonical
|
|
652
|
+
* toolkit-wide verdict alias of `verified`, and `predicateTypeChecked` says whether a pinned
|
|
653
|
+
* `opts.predicateType` was checked). `payload` is the raw verified
|
|
652
654
|
* envelope bytes (never a re-serialization), and the caller confirms a
|
|
653
655
|
* `subjects[].digest` matches the published artifact.
|
|
654
656
|
*
|
|
@@ -729,6 +731,7 @@ async function verifyBundle(bundle, opts) {
|
|
|
729
731
|
var st = _statement(payload, env.payloadType, opts.predicateType);
|
|
730
732
|
|
|
731
733
|
return {
|
|
734
|
+
valid: true, // canonical toolkit-wide verdict alias, beside the verb's own `verified`
|
|
732
735
|
verified: true,
|
|
733
736
|
payload: payload,
|
|
734
737
|
statement: st,
|
|
@@ -739,6 +742,10 @@ async function verifyBundle(bundle, opts) {
|
|
|
739
742
|
// Which identity fields were actually compared. `verified` says the artifact was signed and
|
|
740
743
|
// logged; only these say a signer the caller named was the one who signed it.
|
|
741
744
|
identityChecked: identityChecked,
|
|
745
|
+
// Whether the caller pinned a predicateType (opts.predicateType) that was checked. The verdict is
|
|
746
|
+
// only reached when a pinned predicate matched (a mismatch throws sigstore/predicate-mismatch), so
|
|
747
|
+
// a caller that omitted the pin gets `false` and knows the SLSA predicate went unverified.
|
|
748
|
+
predicateTypeChecked: opts.predicateType != null,
|
|
742
749
|
integratedTime: integratedTime,
|
|
743
750
|
};
|
|
744
751
|
}
|
package/lib/webauthn.js
CHANGED
|
@@ -1217,7 +1217,10 @@ function _safetyNetChainTrusted(chain, roots, time) {
|
|
|
1217
1217
|
// had already decoded -- and a login cannot be verified without them.
|
|
1218
1218
|
function _result(fmt, attestationType, chain, att) {
|
|
1219
1219
|
return {
|
|
1220
|
-
attestationVerified
|
|
1220
|
+
// `valid` / `attestationVerified` say the attestation format was validly processed, which the `none`
|
|
1221
|
+
// format (WebAuthn 8.7, no attestation) satisfies too. The ASSURANCE level is `attestationType`
|
|
1222
|
+
// (`None` for none): a relying party that requires cryptographic attestation gates on it, not on `valid`.
|
|
1223
|
+
valid: true, attestationVerified: true, fmt: fmt, attestationType: attestationType,
|
|
1221
1224
|
trustPath: (chain || []).slice().reverse(),
|
|
1222
1225
|
aaguid: att.authData.aaguid,
|
|
1223
1226
|
credentialId: att.authData.credentialId,
|
|
@@ -1274,7 +1277,7 @@ function _checkAndroidKeyDescription(cert, clientDataHash) {
|
|
|
1274
1277
|
|
|
1275
1278
|
/**
|
|
1276
1279
|
* @primitive pki.webauthn.verify
|
|
1277
|
-
* @signature pki.webauthn.verify(attestationObject, clientDataHash?, opts?) -> Promise<{ attestationVerified, fmt, attestationType, trustPath, anchoredTo, aaguid, credentialId, credentialPublicKey, credentialPublicKeyBytes, signCount, flags, rpIdHash, extensions, bindingChecked, clientData }>
|
|
1280
|
+
* @signature pki.webauthn.verify(attestationObject, clientDataHash?, opts?) -> Promise<{ valid, attestationVerified, fmt, attestationType, trustPath, anchoredTo, aaguid, credentialId, credentialPublicKey, credentialPublicKeyBytes, signCount, flags, rpIdHash, extensions, bindingChecked, clientData }>
|
|
1278
1281
|
* @since 0.2.5
|
|
1279
1282
|
* @status stable
|
|
1280
1283
|
* @spec W3C WebAuthn Level 3 sec. 8 / sec. 7.1
|
|
@@ -2162,7 +2165,7 @@ var _ASSERT_OPTS = Object.assign(Object.create(null), {
|
|
|
2162
2165
|
|
|
2163
2166
|
/**
|
|
2164
2167
|
* @primitive pki.webauthn.verifyAssertion
|
|
2165
|
-
* @signature pki.webauthn.verifyAssertion(input) -> Promise<{ signatureVerified, signCount, signCountChecked, flags, rpIdHash, extensions, bindingChecked, clientData }>
|
|
2168
|
+
* @signature pki.webauthn.verifyAssertion(input) -> Promise<{ valid, signatureVerified, signCount, signCountChecked, flags, rpIdHash, extensions, bindingChecked, clientData }>
|
|
2166
2169
|
* @since 0.5.0
|
|
2167
2170
|
* @status stable
|
|
2168
2171
|
* @spec W3C WebAuthn Level 3 sec. 7.2
|
|
@@ -2370,6 +2373,7 @@ function verifyAssertion(input) {
|
|
|
2370
2373
|
}
|
|
2371
2374
|
}
|
|
2372
2375
|
return {
|
|
2376
|
+
valid: true, // canonical toolkit-wide verdict alias, beside the verb's own `signatureVerified`
|
|
2373
2377
|
signatureVerified: true,
|
|
2374
2378
|
signCount: authData.signCount,
|
|
2375
2379
|
signCountChecked: signCountChecked,
|
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:abae2085-b406-4ed9-924e-bb379912b38a",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-
|
|
8
|
+
"timestamp": "2026-09-01T01:44:33.946Z",
|
|
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.6.
|
|
22
|
+
"bom-ref": "@blamejs/pki@0.6.12",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "pki",
|
|
25
|
-
"version": "0.6.
|
|
25
|
+
"version": "0.6.12",
|
|
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.6.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/pki@0.6.12",
|
|
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.6.
|
|
57
|
+
"ref": "@blamejs/pki@0.6.12",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|