@blamejs/pki 0.6.11 → 0.6.13

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,31 @@ 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.13 — 2026-09-01
8
+
9
+ pki.sigstore.verifyBundle now reports which transparency-log entry attested a bundle: the verdict carries `logIndex` and `logId` so a caller can locate the exact Rekor record it verified against.
10
+
11
+ ### Added
12
+
13
+ - pki.sigstore.verifyBundle's verdict carries `logIndex` (the attested Rekor entry's global log index) and `logId` (the log's key id, hex) beside `integratedTime`, so a caller can fetch or audit the exact transparency-log record the bundle was verified against instead of re-deriving it.
14
+
15
+ ## v0.6.12 — 2026-08-31
16
+
17
+ 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.
18
+
19
+ ### Added
20
+
21
+ - 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 the store's verified integrity: the password MAC for a password store, or the CMS signature for a public-key store). 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.
22
+ - pki.ocsp.verify and pki.path.verifyOcspResponse surface `revocationTime` (the revocation instant) beside `revocationReason` on a revoked verdict, for long-term validation.
23
+ - 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.
24
+ - 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.
25
+
26
+ ### Changed
27
+
28
+ - 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.
29
+ - 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.
30
+ - 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.
31
+
7
32
  ## v0.6.11 — 2026-08-31
8
33
 
9
34
  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.
@@ -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<boolean>
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. It fails closed to `false` on
652
- * any resolution, import, or verification fault; malformed input throws a typed `CrlError`.
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). Either
657
- * failing is `false`: a statement about the CRL, not about the caller's input, so trying each candidate
658
- * issuer in turn still works. A signature verifying says only that SOME key signed these bytes; without
659
- * those two questions a CRL minted under an end-entity certificate of the same CA verified as that CA's.
660
- * Handed a bare SPKI there is no certificate to carry either restriction, and the signature is all that
661
- * is checked. Currency and distribution-point scope remain `pki.path.crlChecker`.
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 ok = await pki.crl.verify(crlDer, { publicKey: signerSpki }); // true / false
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
- // Asked BEFORE the signature: a certificate that may not sign CRLs, or that is not this CRL's
679
- // issuer, is a caller-configuration answer that does not depend on the math, and spending a
680
- // verification on it would let the cheaper question be answered by the more expensive one.
681
- if (resolved.cert && !_issuerMaySign(parsed, resolved.cert)) return false;
682
- return crlVerify.verifyCrlSignature(parsed, resolved.spki);
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) { return Promise.resolve(Object.assign({}, base, 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
- return { verified: out.length > 0 && out.every(function (m) { return m.verified === true; }), messages: out };
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<boolean>
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
- * Resolves `true` for a valid signature from a trusted, in-window log; `false` on a cryptographic
831
- * mismatch (a verdict); throws a typed `CtError` on any structural / trust failure.
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 ok = await pki.ct.verifySctWithLogList(sctEntry, embeddedSct, logList);
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
- return verifySct(entry, sct, c.log.key); // the shipped crypto verdict (re-checks the logId binding)
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,
@@ -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
@@ -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
  });
@@ -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<boolean>
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. Returns `true` / `false` for the password match; throws `Pkcs12Error` on a MAC-less
796
- * or public-key-integrity store, or an unsupported MAC algorithm (never a falsy verdict standing in for an error).
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 ok = await pki.pkcs12.verifyMac(p12, 'changeit');
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
- return _verifyMacOfStore(_storeFromBytes(pfx), password, opts);
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 || "").toLowerCase();
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
- var out = { integrityMode: m.integrityMode, macVerified: macVerified, signers: signers, keys: [], certs: [], crls: [], secrets: [] };
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
@@ -411,7 +411,10 @@ function _verifyRekor(te, envelope, rekorKeys, leafDer) {
411
411
  if (!setOk) throw _err("sigstore/unattested-time", "the Rekor SET (the signed source of integratedTime) did not verify -- the log time is not attested and cannot date the Fulcio certificate");
412
412
  var t = Number(te.integratedTime);
413
413
  if (!Number.isFinite(t) || t < 0) throw _err("sigstore/bad-tlog-entry", "the transparency-log entry has a malformed integratedTime");
414
- return t;
414
+ // Surface which attested Rekor log entry this is: the global log index and the log's key id (hex), so a
415
+ // caller can locate the exact transparency-log record for an audit rather than re-deriving it.
416
+ var li = Number(te.logIndex);
417
+ return { integratedTime: t, logIndex: Number.isFinite(li) ? li : null, logId: te.logId && te.logId.keyId ? _b64(te.logId.keyId, "logId.keyId").toString("hex") : null };
415
418
  }
416
419
 
417
420
  // ---- Fulcio chain leg (RFC 5280 path validation, as-of the log time) ----------
@@ -647,8 +650,11 @@ function _statement(payload, payloadType, expectedPredicate) {
647
650
  * of the Rekor log time; the Rekor inclusion proof folded to a Rekor-signed root;
648
651
  * the log entry bound to this exact signature; and the in-toto SLSA statement.
649
652
  * Any leg failing throws a typed `sigstore/*` error. On success returns
650
- * `{ verified: true, payload, statement, subjects, predicateType, predicate,
651
- * identity, identityChecked, integratedTime }`. `payload` is the raw verified
653
+ * `{ valid: true, verified: true, payload, statement, subjects, predicateType, predicate,
654
+ * identity, identityChecked, predicateTypeChecked, integratedTime, logIndex, logId }` (`valid` is the
655
+ * canonical toolkit-wide verdict alias of `verified`, `predicateTypeChecked` says whether a pinned
656
+ * `opts.predicateType` was checked, and `logIndex` / `logId` identify the attested Rekor log entry).
657
+ * `payload` is the raw verified
652
658
  * envelope bytes (never a re-serialization), and the caller confirms a
653
659
  * `subjects[].digest` matches the published artifact.
654
660
  *
@@ -706,13 +712,14 @@ async function verifyBundle(bundle, opts) {
706
712
  // verifies (and binds this signature), rejecting only if none do.
707
713
  var tlogs = Array.isArray(vm.tlogEntries) ? vm.tlogEntries : [];
708
714
  if (!tlogs.length) throw _err("sigstore/bad-bundle", "a keyless bundle requires at least one transparency-log entry");
709
- var integratedTime = null, lastErr = null;
715
+ var rekor = null, lastErr = null;
710
716
  for (var ti = 0; ti < tlogs.length; ti++) {
711
717
  if (!tlogs[ti] || typeof tlogs[ti] !== "object") { lastErr = _err("sigstore/bad-bundle", "a transparency-log entry is not an object"); continue; }
712
- try { integratedTime = _verifyRekor(tlogs[ti], env, rekorKeys, leafDer); break; }
718
+ try { rekor = _verifyRekor(tlogs[ti], env, rekorKeys, leafDer); break; }
713
719
  catch (e) { lastErr = e; }
714
720
  }
715
- if (integratedTime === null) throw lastErr;
721
+ if (rekor === null) throw lastErr;
722
+ var integratedTime = rekor.integratedTime;
716
723
 
717
724
  // Leg 3 -- Fulcio chain, validated as of the (trusted) log time; then identity.
718
725
  // The instant comes through the time guard's intrinsic read. `getTime` is an ordinary method a
@@ -729,6 +736,7 @@ async function verifyBundle(bundle, opts) {
729
736
  var st = _statement(payload, env.payloadType, opts.predicateType);
730
737
 
731
738
  return {
739
+ valid: true, // canonical toolkit-wide verdict alias, beside the verb's own `verified`
732
740
  verified: true,
733
741
  payload: payload,
734
742
  statement: st,
@@ -739,7 +747,15 @@ async function verifyBundle(bundle, opts) {
739
747
  // Which identity fields were actually compared. `verified` says the artifact was signed and
740
748
  // logged; only these say a signer the caller named was the one who signed it.
741
749
  identityChecked: identityChecked,
750
+ // Whether the caller pinned a predicateType (opts.predicateType) that was checked. The verdict is
751
+ // only reached when a pinned predicate matched (a mismatch throws sigstore/predicate-mismatch), so
752
+ // a caller that omitted the pin gets `false` and knows the SLSA predicate went unverified.
753
+ predicateTypeChecked: opts.predicateType != null,
742
754
  integratedTime: integratedTime,
755
+ // The attested Rekor entry this verdict rests on: `logIndex` is its global log index and `logId` the
756
+ // log's key id (hex), so a caller can fetch or audit the exact transparency-log record.
757
+ logIndex: rekor.logIndex,
758
+ logId: rekor.logId,
743
759
  };
744
760
  }
745
761
 
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: true, fmt: fmt, attestationType: attestationType,
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/pki",
3
- "version": "0.6.11",
3
+ "version": "0.6.13",
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:bfbe087f-30ed-484a-ba11-142da30522a8",
5
+ "serialNumber": "urn:uuid:7a020e1d-6d6a-4d9e-9a8b-d17c7e4fa867",
6
6
  "version": 1,
7
7
  "metadata": {
8
- "timestamp": "2026-08-31T23:06:26.187Z",
8
+ "timestamp": "2026-09-01T02:27:31.392Z",
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.11",
22
+ "bom-ref": "@blamejs/pki@0.6.13",
23
23
  "type": "application",
24
24
  "name": "pki",
25
- "version": "0.6.11",
25
+ "version": "0.6.13",
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.11",
29
+ "purl": "pkg:npm/%40blamejs/pki@0.6.13",
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.11",
57
+ "ref": "@blamejs/pki@0.6.13",
58
58
  "dependsOn": []
59
59
  }
60
60
  ]