@blamejs/pki 0.9.4 → 0.9.6

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.
Files changed (47) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/README.md +7 -6
  3. package/bin/pki.js +110 -34
  4. package/lib/acme.js +91 -46
  5. package/lib/attrcert-sign.js +4 -3
  6. package/lib/cmp-build.js +16 -6
  7. package/lib/cmp-session.js +19 -3
  8. package/lib/cmp-verify.js +25 -2
  9. package/lib/cms-compress.js +3 -3
  10. package/lib/cms-digest.js +10 -5
  11. package/lib/cms-encrypt.js +6 -1
  12. package/lib/cms-sign.js +16 -0
  13. package/lib/cms-verify.js +32 -14
  14. package/lib/constants.js +1 -0
  15. package/lib/crl-sign.js +3 -3
  16. package/lib/csr-sign.js +6 -2
  17. package/lib/ct.js +16 -3
  18. package/lib/est.js +67 -31
  19. package/lib/guard-all.js +2 -0
  20. package/lib/guard-distrust.js +224 -0
  21. package/lib/guard-identifier.js +27 -1
  22. package/lib/guard-json.js +1 -1
  23. package/lib/http-transport.js +299 -36
  24. package/lib/jose.js +1 -1
  25. package/lib/lint.js +67 -7
  26. package/lib/ocsp-verify.js +9 -3
  27. package/lib/ocsp.js +16 -4
  28. package/lib/oid.js +4 -0
  29. package/lib/path-validate.js +119 -18
  30. package/lib/pkcs12-build.js +6 -1
  31. package/lib/pki-build.js +38 -6
  32. package/lib/possession.js +6 -2
  33. package/lib/related-cert.js +2 -2
  34. package/lib/revocation-fetch.js +46 -11
  35. package/lib/scep.js +39 -14
  36. package/lib/schema-pkix.js +3 -3
  37. package/lib/sigstore.js +11 -6
  38. package/lib/smime.js +32 -16
  39. package/lib/tls-cert-compress.js +6 -5
  40. package/lib/trust.js +121 -20
  41. package/lib/trustanchor-build.js +16 -1
  42. package/lib/tsp-sign.js +15 -2
  43. package/lib/webauthn-mds.js +29 -8
  44. package/lib/webauthn.js +46 -14
  45. package/lib/x509-sign.js +26 -10
  46. package/package.json +2 -2
  47. package/sbom.cdx.json +6 -6
package/lib/pki-build.js CHANGED
@@ -648,6 +648,36 @@ function makeBuilder(ctx) {
648
648
  if (bc.pathLen != null) pathLen(bc.pathLen);
649
649
  guard.identifier.assertKnownKeys(bc, BC_KEYS, E, "bad-input", "unknown basicConstraints field ");
650
650
  }
651
+
652
+ /** @internal The criticality a caller states for an extension, read as the boolean each flag
653
+ * documents and resolved to the default an omitted flag selects. RFC 5280 sec. 4.2 fixes
654
+ * keyUsage critical and leaves the other five non-critical, which is why the defaults differ.
655
+ * `basicConstraints` carries its own flag inside the value and is typed by `validateBcSpec`.
656
+ *
657
+ * The flags are RETURNED, so the emitters below read criticality from this record rather than
658
+ * from the caller's spec a second time. A flag read off the spec at the point of emission is a
659
+ * read of a value that was never typed: measured, `extendedKeyUsageCritical: "false"` emitted
660
+ * that extension critical, and an extension a relying party does not recognize as critical is
661
+ * one RFC 5280 sec. 6.1 requires it to reject. */
662
+ var CRITICALITY_FLAGS = [
663
+ ["keyUsageCritical", true],
664
+ ["extendedKeyUsageCritical", false],
665
+ ["certificatePoliciesCritical", false],
666
+ ["policyMappingsCritical", false],
667
+ ["qcStatementsCritical", false],
668
+ ["tlsFeatureCritical", false],
669
+ ];
670
+ function criticality(extSpec, label) {
671
+ var out = intrinsic.create(null);
672
+ var where = label || "extensions";
673
+ for (var i = 0; i < CRITICALITY_FLAGS.length; i++) {
674
+ var k = CRITICALITY_FLAGS[i][0];
675
+ out[k] = CRITICALITY_FLAGS[i][1]
676
+ ? guard.identifier.booleanOptionDefaultOn(extSpec[k], E, "bad-input", where + "." + k)
677
+ : guard.identifier.booleanOption(extSpec[k], E, "bad-input", where + "." + k);
678
+ }
679
+ return out;
680
+ }
651
681
  function extBasicConstraints(spec) {
652
682
  var children = [];
653
683
  if (spec.cA === true) _push(children,b.boolean(true));
@@ -1368,16 +1398,17 @@ function makeBuilder(ctx) {
1368
1398
  guard.identifier.assertKnownKeys(extSpec, REQ_EXT_KEYS, E, "bad-input", function (k) {
1369
1399
  return "unknown requested extension " + JSON.stringify(k) + "; pass a pre-encoded Extension DER via the array form for a custom extension";
1370
1400
  });
1401
+ var crit = criticality(extSpec, "requested extensions");
1371
1402
  var out = [];
1372
1403
  if (extSpec.subjectKeyIdentifier != null) {
1373
1404
  if (extSpec.subjectKeyIdentifier === true && spki == null) throw E("bad-input", "subjectKeyIdentifier auto-derive (true) requires the public key -- supply a Buffer key id, or include the public key");
1374
1405
  _push(out,ext(O("subjectKeyIdentifier"), false, extSki(skiKeyId(extSpec.subjectKeyIdentifier, spki))));
1375
1406
  }
1376
- if (extSpec.keyUsage != null) _push(out,ext(O("keyUsage"), extSpec.keyUsageCritical !== false, extKeyUsage(extSpec.keyUsage)));
1377
- if (extSpec.extendedKeyUsage != null) _push(out,ext(O("extKeyUsage"), !!extSpec.extendedKeyUsageCritical, extExtKeyUsage(extSpec.extendedKeyUsage)));
1407
+ if (extSpec.keyUsage != null) _push(out,ext(O("keyUsage"), crit.keyUsageCritical, extKeyUsage(extSpec.keyUsage)));
1408
+ if (extSpec.extendedKeyUsage != null) _push(out,ext(O("extKeyUsage"), crit.extendedKeyUsageCritical, extExtKeyUsage(extSpec.extendedKeyUsage)));
1378
1409
  if (extSpec.basicConstraints != null) { validateBcSpec(extSpec.basicConstraints); _push(out,ext(O("basicConstraints"), extSpec.basicConstraints.critical !== false, extBasicConstraints(extSpec.basicConstraints))); }
1379
1410
  if (extSpec.subjectAltName != null) _push(out,ext(O("subjectAltName"), false, extSan(extSpec.subjectAltName)));
1380
- if (extSpec.certificatePolicies != null) _push(out,ext(O("certificatePolicies"), !!extSpec.certificatePoliciesCritical, extCertPolicies(extSpec.certificatePolicies)));
1411
+ if (extSpec.certificatePolicies != null) _push(out,ext(O("certificatePolicies"), crit.certificatePoliciesCritical, extCertPolicies(extSpec.certificatePolicies)));
1381
1412
  /** @internal RFC 5280 sec. 4.2.1.10 fixes this extension critical, in a request as in the certificate. */
1382
1413
  if (extSpec.nameConstraints != null) guard.list.append(out, ext(O("nameConstraints"), true, extNameConstraints(extSpec.nameConstraints)));
1383
1414
  if (extSpec.authorityInfoAccess != null) guard.list.append(out, ext(O("authorityInfoAccess"), false, extAuthorityInfoAccess(extSpec.authorityInfoAccess)));
@@ -1385,14 +1416,14 @@ function makeBuilder(ctx) {
1385
1416
  if (extSpec.freshestCRL != null) guard.list.append(out, ext(O("freshestCRL"), false, extCrlDistributionPoints(extSpec.freshestCRL, "freshestCRL")));
1386
1417
  if (extSpec.policyConstraints != null) guard.list.append(out, ext(O("policyConstraints"), true, extPolicyConstraints(extSpec.policyConstraints)));
1387
1418
  if (extSpec.inhibitAnyPolicy != null) guard.list.append(out, ext(O("inhibitAnyPolicy"), true, extInhibitAnyPolicy(extSpec.inhibitAnyPolicy)));
1388
- if (extSpec.policyMappings != null) guard.list.append(out, ext(O("policyMappings"), !!extSpec.policyMappingsCritical, extPolicyMappings(extSpec.policyMappings)));
1419
+ if (extSpec.policyMappings != null) guard.list.append(out, ext(O("policyMappings"), crit.policyMappingsCritical, extPolicyMappings(extSpec.policyMappings)));
1389
1420
  if (extSpec.issuerAltName != null) guard.list.append(out, ext(O("issuerAltName"), false, encodeGeneralNames(reqDenseArray(extSpec.issuerAltName, "issuerAltName"))));
1390
- if (extSpec.qcStatements != null) guard.list.append(out, ext(O("qcStatements"), !!extSpec.qcStatementsCritical, extQcStatements(extSpec.qcStatements)));
1421
+ if (extSpec.qcStatements != null) guard.list.append(out, ext(O("qcStatements"), crit.qcStatementsCritical, extQcStatements(extSpec.qcStatements)));
1391
1422
  /** @internal RFC 7633 sec. 4.2.1 lets a request state the features the server supports, and
1392
1423
  * sec. 4.3.2 asks a server to generate it, so a feature declaration is the requester's wish and
1393
1424
  * belongs in this table. The criticality knob is here too, because a request carries the
1394
1425
  * Extension shape whole and the CA decides what it issues. */
1395
- if (extSpec.tlsFeature != null) guard.list.append(out, ext(O("tlsFeature"), !!extSpec.tlsFeatureCritical, extTlsFeature(extSpec.tlsFeature)));
1426
+ if (extSpec.tlsFeature != null) guard.list.append(out, ext(O("tlsFeature"), crit.tlsFeatureCritical, extTlsFeature(extSpec.tlsFeature)));
1396
1427
  if (extSpec.msCertificateTemplate != null) guard.list.append(out, ext(O("msCertificateTemplate"), false, extMsCertificateTemplate(extSpec.msCertificateTemplate)));
1397
1428
  if (extSpec.msEnrollCertType != null) guard.list.append(out, ext(O("msEnrollCertType"), false, extMsEnrollCertType(extSpec.msEnrollCertType)));
1398
1429
  if (extSpec.msApplicationPolicies != null) guard.list.append(out, ext(O("msApplicationPolicies"), false, extCertPolicies(extSpec.msApplicationPolicies)));
@@ -1457,6 +1488,7 @@ function makeBuilder(ctx) {
1457
1488
  encodeGeneralNames: encodeGeneralNames, serialInteger: serialInteger, timeDer: timeDer,
1458
1489
  requestedExtensions: requestedExtensions,
1459
1490
  extKeyUsage: extKeyUsage, extExtKeyUsage: extExtKeyUsage, validateBcSpec: validateBcSpec,
1491
+ criticality: criticality,
1460
1492
  extBasicConstraints: extBasicConstraints, pathLen: pathLen, extSki: extSki, extAki: extAki, AKI_KEYS: AKI_KEYS,
1461
1493
  extSan: extSan, extCertPolicies: extCertPolicies, ext: ext,
1462
1494
  extSubjectInfoAccess: extSubjectInfoAccess, extSubjectDirectoryAttributes: extSubjectDirectoryAttributes,
package/lib/possession.js CHANGED
@@ -118,7 +118,7 @@ var STATEMENT = schema.seq([
118
118
  });
119
119
 
120
120
  var KNOWN_VERIFY_OPTS = _assign(_create(null), {
121
- trustAnchors: 1, time: 1, signatureCertificate: 1, intermediates: 1,
121
+ trustAnchors: 1, time: 1, signatureCertificate: 1, intermediates: 1, distrustedKeys: 1,
122
122
  });
123
123
 
124
124
  /** @internal Whether a certificate is the one an IssuerAndSerialNumber names. Both halves are compared,
@@ -345,6 +345,7 @@ var _isSignatureOnlyKey = pkix.signatureOnlyKeyOid(NS);
345
345
  * that is present and is not a usable `Date` is `possession/bad-input`.
346
346
  * @opts signatureCertificate The signature certificate, for a statement that omits it; refused when it is not the one `signer` names.
347
347
  * @opts intermediates The rest of the signature certificate's path, when an intermediate CA issued it rather than an anchor directly. Ordered as `pki.path.validate` takes a path, from the certificate nearest an anchor down to the issuer of the signature certificate, which this verb appends. Omitted, the signature certificate is validated on its own, which succeeds only when an anchor issued it.
348
+ * @opts distrustedKeys SHA-256 digests of SubjectPublicKeyInfo values (64 hex characters or 32 bytes each). A listed key on the anchor, an intermediate or the signature certificate makes `pathValidated` and `valid` false. A malformed list is `possession/bad-input`.
348
349
  *
349
350
  * @example
350
351
  * var kem = await pki.key.generate({ name: "ML-KEM-768" });
@@ -382,6 +383,7 @@ function _verifyRequest(request, opts) {
382
383
  guard.identifier.assertKnownKeys(opts, KNOWN_VERIFY_OPTS, _err, "possession/bad-input",
383
384
  "pki.possession.verifyRequest has an unknown option: ");
384
385
  var o = guard.identifier.snapshotOptions(opts, KNOWN_VERIFY_OPTS);
386
+ var distrusted = guard.distrust.keys(o.distrustedKeys, _err, "possession/bad-input", "opts.distrustedKeys");
385
387
  if (o.trustAnchors == null || !_isArray(o.trustAnchors) || o.trustAnchors.length === 0) {
386
388
  throw _err("possession/bad-input",
387
389
  "opts.trustAnchors is required: RFC 9883 sec. 4 makes certification path validation of the signature certificate a MUST, and a verdict that skipped it would not answer the question asked");
@@ -522,8 +524,10 @@ function _verifyRequest(request, opts) {
522
524
  "authorize a certification request (RFC 5280 sec. 4.2.1.3)");
523
525
  }
524
526
 
527
+ var pathOpts = { trustAnchors: o.trustAnchors, time: at };
528
+ if (distrusted !== null) pathOpts.distrustedKeys = distrusted;
525
529
  return intrinsic.chain(
526
- _promiseResolve(_Promise, _validatePath(chain, { trustAnchors: o.trustAnchors, time: at })),
530
+ _promiseResolve(_Promise, _validatePath(chain, pathOpts)),
527
531
  function (pathResult) {
528
532
  var pathValidated = pathResult.valid === true;
529
533
  if (!pathValidated) _push(reasons, "the signature certificate's certification path is not valid, and RFC 9883 sec. 4 makes that a MUST");
@@ -208,7 +208,7 @@ var KNOWN_VERIFY_OPTS = _assign(_create(null), { digestAlgorithm: 1, signatureAl
208
208
  * an attacker's key verifies forever without it, and a verifier that cannot perform a MUST check must not
209
209
  * answer true. `at` is the instant judged against, for a caller reading a request recorded earlier. */
210
210
  function _assertFresh(requestTime, o) {
211
- if (o.maxAge === undefined) {
211
+ if (o.maxAge == null) {
212
212
  throw _err("relatedcert/no-freshness-policy",
213
213
  "pki.relatedCert.verifyRequest needs opts.maxAge, the seconds a requestTime may be old: RFC 9763 " +
214
214
  "sec. 3.2 requires the certification authority to check that it is sufficiently fresh and leaves " +
@@ -217,7 +217,7 @@ function _assertFresh(requestTime, o) {
217
217
  }
218
218
  var maxAge = guard.range.authoredInteger(o.maxAge, _err, "relatedcert/bad-input", "opts.maxAge");
219
219
  if (maxAge < 0n) throw _err("relatedcert/bad-input", "opts.maxAge must not be negative");
220
- var at = o.at === undefined ? new intrinsic.Date() : o.at;
220
+ var at = o.at == null ? new intrinsic.Date() : o.at;
221
221
  if (!guard.time.isDate(at)) throw _err("relatedcert/bad-input", "opts.at must be a Date");
222
222
  guard.time.assertValid(at, _err, "relatedcert/bad-input", "opts.at");
223
223
  var atMs = guard.time.instantOf(at);
@@ -398,7 +398,8 @@ async function _tryOcsp(state, cert, issuer, ctx, candidate) {
398
398
  hit = got.body;
399
399
  }
400
400
  var v = await state.verifyOcspResponse(hit, cert, issuerCert, ctx.time,
401
- { historicalMode: ctx.historicalMode === true, requestNonce: state.nonce ? _requestNonce(parsedReq) : undefined });
401
+ { historicalMode: ctx.historicalMode === true, requestNonce: state.nonce ? _requestNonce(parsedReq) : undefined,
402
+ distrustedKeys: state.distrusted });
402
403
  /** @internal `valid` on that verdict means the certificate is good, not that the response was
403
404
  * acceptable, so reading it as acceptance would turn a `revoked` answer into an undetermined
404
405
  * one that `opts.softFail` could then waive. Acceptance is the three conditions the gate
@@ -670,7 +671,9 @@ function _deltaUrlsFor(parsedCrl, state) {
670
671
  * of the same shape. A transport that does not declare that it blocks
671
672
  * private addresses is refused a hostname.
672
673
  * @opts allowPlaintextHttp Whether an `http` URL is fetched. Default `true`.
673
- * @opts useDeltas Whether a freshestCRL delta is fetched and merged. Default `true`.
674
+ * @opts useDeltas Whether a freshestCRL delta is fetched and merged. Default `true`. A
675
+ * value other than `true`, `false`, `null` or an absent option is refused
676
+ * `path/bad-input`.
674
677
  * @opts nonce Whether an OCSP request carries a 32-octet nonce. `true` or `false`,
675
678
  * and anything else is `path/bad-input` rather than the default, since a
676
679
  * control read as off is one an operator asked for and did not get.
@@ -693,6 +696,21 @@ function _deltaUrlsFor(parsedCrl, state) {
693
696
  * var checker = pki.path.fetchingChecker({ transport: pki.transport.https({}) });
694
697
  * console.log(typeof checker.check);
695
698
  */
699
+ /** @internal The TLS policy every fetch is sent with, copied once at the door, with its distrust list
700
+ * typed there. A fetch fault becomes an undetermined verdict, so a malformed list that reached the
701
+ * transport would be reported as a responder that did not answer. */
702
+ function _tlsSnapshot(tls) {
703
+ if (tls == null) return tls;
704
+ guard.identifier.assertKnownKeys(tls, httpTransport.TLS_KEYS, E, "path/bad-input", "fetchingChecker: opts.tls has an unknown option ");
705
+ /** @internal Each setting the transport reads is taken by value, whether the bag holds it or
706
+ * inherits it, which is how the transport itself reads the bag. */
707
+ var copy = guard.identifier.pickSettings(tls, intrinsic.keys(httpTransport.TLS_KEYS));
708
+ if (copy.distrustedKeys != null) {
709
+ copy.distrustedKeys = guard.distrust.keys(copy.distrustedKeys, E, "path/bad-input", "fetchingChecker: opts.tls.distrustedKeys");
710
+ }
711
+ return copy;
712
+ }
713
+
696
714
  function fetchingChecker(opts) {
697
715
  opts = guard.identifier.optionsObject(opts, E, "path/bad-input", "fetchingChecker: opts");
698
716
  guard.identifier.assertKnownKeys(opts, CHECKER_OPTS, E, "path/bad-input",
@@ -701,6 +719,7 @@ function fetchingChecker(opts) {
701
719
  * accessor twice, so the transport that passed the door is the transport that gets called. */
702
720
  var transport = guard.identifier.assertCallable(opts.transport, E, "path/bad-input",
703
721
  "fetchingChecker: opts.transport", TRANSPORT_SHAPE);
722
+ // allow:caller-option-presence-tested-against-undefined -- absence selects the permissive default, so a null must not reach it
704
723
  var allowPlaintextHttp = opts.allowPlaintextHttp === undefined ? true : opts.allowPlaintextHttp === true;
705
724
  var cfg = {
706
725
  transport: transport,
@@ -709,9 +728,13 @@ function fetchingChecker(opts) {
709
728
  * door is the value the rule is applied on. */
710
729
  net: intrinsic.freeze({ allowHttp: allowPlaintextHttp, guardsAddresses: _declaresAddressGuard(transport) }),
711
730
  allowPlaintextHttp: allowPlaintextHttp,
712
- useDeltas: !(opts.useDeltas === false),
731
+ /** @internal Read the way `nonce` on the next line has been read since it was written: the
732
+ * comparison this replaces answered for a value of any other type by fetching the deltas the
733
+ * caller declined. */
734
+ useDeltas: guard.identifier.booleanOptionDefaultOn(opts.useDeltas, E, "path/bad-input",
735
+ "fetchingChecker: opts.useDeltas"),
713
736
  nonce: _booleanOption(opts.nonce, "fetchingChecker: opts.nonce"),
714
- tls: opts.tls,
737
+ tls: _tlsSnapshot(opts.tls),
715
738
  maxFetches: guard.limits.cap(opts.maxFetches, "fetchingChecker: opts.maxFetches", LIMITS.REVOCATION_MAX_FETCHES,
716
739
  { E: E, code: "path/bad-input", min: 1, max: LIMITS.REVOCATION_MAX_FETCHES }),
717
740
  maxPerCert: guard.limits.cap(opts.maxPerCert, "fetchingChecker: opts.maxPerCert", LIMITS.REVOCATION_MAX_PER_CERT,
@@ -751,10 +774,17 @@ function fetchingChecker(opts) {
751
774
 
752
775
  return {
753
776
  check: async function (cert, issuer, ctx) {
777
+ /** @internal Typed before any location is decoded or fetched: a malformed list is the
778
+ * caller's fault and is thrown, where a fetch fault below becomes an undetermined verdict. */
779
+ var distrusted = guard.distrust.keys(ctx == null ? undefined : ctx.distrustedKeys, E, "path/bad-input",
780
+ "fetchingChecker: ctx.distrustedKeys");
781
+ if (guard.distrust.has(distrusted, issuer.workingPublicKey)) {
782
+ return _unknown("the issuer's key is listed in distrustedKeys, so nothing it signs is an answer", [], []);
783
+ }
754
784
  var notes = [], skips = [];
755
785
  var budget = budgetFor(ctx == null ? null : ctx.run);
756
786
  var state = intrinsic.assign(intrinsic.create(null), cfg, {
757
- notes: notes, cache: cache, crlCache: crlCache,
787
+ notes: notes, cache: cache, crlCache: crlCache, distrusted: distrusted,
758
788
  deadline: budget.deadline,
759
789
  fetches: budget.fetches,
760
790
  urls: new intrinsic.Set(),
@@ -781,7 +811,9 @@ function fetchingChecker(opts) {
781
811
  * number of retries. A responder that answers `revoked` or `good` ends it; anything else
782
812
  * falls through, because sec. 5 of RFC 6960 makes the CRL the fallback and never an
783
813
  * assumption of good standing. */
784
- var lastReason = null;
814
+ /** @internal Each source keeps its own last failure, so an undetermined verdict names the
815
+ * responder's fault and the CRL's. They usually have different causes. */
816
+ var ocspReason = null, crlReason = null;
785
817
  for (var a = 0; a <= cfg.retries && ocspCands.length > 0; a++) {
786
818
  for (var o = 0; o < ocspCands.length; o++) {
787
819
  if (!_takeUrl(state, ocspCands[o].url)) {
@@ -791,18 +823,21 @@ function fetchingChecker(opts) {
791
823
  try {
792
824
  var got = await _tryOcsp(state, cert, issuer, ctx, ocspCands[o]);
793
825
  if (got.status === "good" || got.status === "revoked") return _verdict(got, notes, skips);
794
- lastReason = got.reason + " reported " + got.status;
795
- } catch (e) { lastReason = _say(e); }
826
+ ocspReason = got.reason + " reported " + got.status;
827
+ } catch (e) { ocspReason = _say(e); }
796
828
  }
797
829
  }
798
830
  if (crlCands.length > 0) {
799
831
  try {
800
832
  var viaCrl = await _tryCrl(state, cert, issuer, ctx, crlCands, certDeltas);
801
833
  if (viaCrl.status === "good" || viaCrl.status === "revoked") return _verdict(viaCrl, notes, skips);
802
- lastReason = viaCrl.reason;
803
- } catch (e2) { lastReason = _say(e2); }
834
+ crlReason = viaCrl.reason;
835
+ } catch (e2) { crlReason = _say(e2); }
804
836
  }
805
- return _unknown(lastReason || "no revocation source answered", notes, skips);
837
+ var why = [];
838
+ if (ocspReason !== null) _append(why, "OCSP: " + ocspReason);
839
+ if (crlReason !== null) _append(why, "CRL: " + crlReason);
840
+ return _unknown(why.length ? intrinsic.join(why, "; ") : "no revocation source answered", notes, skips);
806
841
  },
807
842
  };
808
843
  }
package/lib/scep.js CHANGED
@@ -28,6 +28,12 @@
28
28
  * the message layer, transport-agnostic; a live SCEP CA is driven over
29
29
  * pki.transport separately.
30
30
  *
31
+ * Every verb that reaches a CA accepts the same transport bounds, listed on
32
+ * each of them. One of those bounds is a switch: `allowCrossOriginRedirect`
33
+ * opts in to following a cross-origin redirect on an unsafe method. It is a
34
+ * boolean, and a value other than `true`, `false`, `null` or an absent option
35
+ * is refused `scep/bad-input` before the first request goes out.
36
+ *
31
37
  * @card
32
38
  * Build and read SCEP (RFC 8894) messages: a PKCSReq, RenewalReq, CertPoll,
33
39
  * GetCert, or GetCRL request, or a CA-side CertRep response, wrapped in the
@@ -683,10 +689,25 @@ function _responseLen(body) {
683
689
  if (Buffer.isBuffer(body)) return guard.bytes.lengthOf(body);
684
690
  return guard.bytes.lengthOf(guard.bytes.source(body, ScepError, "scep/bad-response", "the response body"));
685
691
  }
686
- /** @internal The settings this client forwards to its transport. */
687
- var TLS_SETTINGS = ["anchors", "useSystemStore", "cert", "key", "minVersion", "servername", "checkServerIdentity"];
688
- function _tlsForRequest(opts) {
689
- return guard.identifier.pickSettings(opts.tls, TLS_SETTINGS);
692
+ /** @internal The settings this client forwards to its transport. The distrust list is typed here, and
693
+ * the frozen copy is the one every hop carries. A setting not on the list is refused. */
694
+ var TLS_SETTINGS = ["anchors", "useSystemStore", "cert", "key", "minVersion", "servername", "checkServerIdentity", "distrustedKeys"];
695
+ var TLS_KNOWN = intrinsic.freeze(guard.identifier.pickSettings(null, TLS_SETTINGS));
696
+ function _distrustOf(value) {
697
+ return guard.distrust.keys(value, _err, "scep/bad-input", "opts.tls.distrustedKeys");
698
+ }
699
+ function _tlsForRequest(tls) {
700
+ guard.identifier.assertKnownKeys(tls, TLS_KNOWN, _err, "scep/bad-input", "unknown opts.tls setting ");
701
+ var t = guard.identifier.pickSettings(tls, TLS_SETTINGS);
702
+ t.distrustedKeys = _distrustOf(t.distrustedKeys);
703
+ return t;
704
+ }
705
+ /** @internal A verb that reaches the transport only after an await copies the caller's tls at its door,
706
+ * with the distrust list typed there, so a later change to the caller's array does not reach the request.
707
+ * Each setting is read the way `_tlsForRequest` reads it, a prototype included. */
708
+ function _snapshotTls(tls) {
709
+ if (tls == null) return tls;
710
+ return _tlsForRequest(tls);
690
711
  }
691
712
  function _redirectTarget(current, location, method, allowCrossOrigin) {
692
713
  if (location == null || _trim(String(location)) === "") throw _err("scep/bad-response", "a redirect response carried no Location header");
@@ -730,8 +751,12 @@ function _pkiRequest(method, opUrl, reqMsg, clientOpts) {
730
751
  function _drive(method, url, body, headers, opts, transport, budgets) {
731
752
  var redirects = 0, curMethod = method, curBody = body, initialOrigin = _urlOrigin(url);
732
753
  var proxySnap = _snapshotProxy(opts.proxy);
733
- var tlsBase = _tlsForRequest(opts);
734
- var allowCrossOrigin = !!opts.allowCrossOriginRedirect;
754
+ var tlsBase = _tlsForRequest(opts.tls);
755
+ /** @internal Read as the boolean it documents, on the one path every network verb here takes and
756
+ * before the first request. Taken by truthiness, `allowCrossOriginRedirect: "false"` ALLOWED the
757
+ * cross-origin redirect on an unsafe method that the check below exists to refuse. */
758
+ var allowCrossOrigin = guard.identifier.booleanOption(opts.allowCrossOriginRedirect, _err,
759
+ "scep/bad-input", "opts.allowCrossOriginRedirect");
735
760
  function _hopTls(current) {
736
761
  var t = tlsBase;
737
762
  if (_urlOrigin(current) !== initialOrigin) {
@@ -858,7 +883,7 @@ function _assertStrongProfile(caps, profile) {
858
883
  * - `transport` -- an injected transport (defaults to `pki.transport.https`); the request is a GET with no body.
859
884
  * - `expectSCEPStandard` -- refuse (`scep/weak-profile`) if the response does not advertise `SCEPStandard`. Takes `true`, `false`, `null` or an absent option; any other value is refused `scep/bad-input`.
860
885
  * - `requireStrongProfile` -- refuse unless AES + SHA-256 + POSTPKIOperation are all advertised. Read the same way.
861
- * - `timeout` / `maxResponseBytes` / `maxRedirects` / `tls` / `allowCrossOriginRedirect` / `proxy` -- transport bounds (`proxy` reaches the CA through a forward HTTP proxy; see pki.transport).
886
+ * - `timeout` / `maxResponseBytes` / `maxRedirects` / `tls` / `allowCrossOriginRedirect` / `proxy` -- transport bounds (`proxy` reaches the CA through a forward HTTP proxy; see pki.transport). `tls.distrustedKeys` lists SHA-256 SubjectPublicKeyInfo digests (see pki.trust.spkiDigest); a server whose chain carries a listed key is refused with `scep/server-key-distrusted` on every hop, and a malformed list is `scep/bad-input`. A `tls` setting other than `anchors`, `useSystemStore`, `cert`, `key`, `minVersion`, `servername`, `checkServerIdentity` and `distrustedKeys` is refused `scep/bad-input`.
862
887
  * @example
863
888
  * // requires: scepTransport -- an injected transport (omit to use pki.transport.https)
864
889
  * var caps = await pki.scep.getCACaps("http://ca.example/scep", { transport: scepTransport });
@@ -942,7 +967,7 @@ function _normalizeFingerprint(fp, algName) {
942
967
  * @opts
943
968
  * - `expectedFingerprint` -- a hex string or Buffer; a returned certificate must hash to it, or the response is refused.
944
969
  * - `fingerprintAlgorithm` -- the hash for the fingerprint (default "sha256").
945
- * - `transport` / `timeout` / `maxResponseBytes` / `maxRedirects` / `tls` / `allowCrossOriginRedirect` / `proxy` -- transport bounds (`proxy` reaches the CA through a forward HTTP proxy; see pki.transport).
970
+ * - `transport` / `timeout` / `maxResponseBytes` / `maxRedirects` / `tls` / `allowCrossOriginRedirect` / `proxy` -- transport bounds (`proxy` reaches the CA through a forward HTTP proxy; see pki.transport). `tls.distrustedKeys` lists SHA-256 SubjectPublicKeyInfo digests (see pki.trust.spkiDigest); a server whose chain carries a listed key is refused with `scep/server-key-distrusted` on every hop, and a malformed list is `scep/bad-input`. A `tls` setting other than `anchors`, `useSystemStore`, `cert`, `key`, `minVersion`, `servername`, `checkServerIdentity` and `distrustedKeys` is refused `scep/bad-input`.
946
971
  * @example
947
972
  * // requires: scepCaCertTransport -- an injected transport returning a single CA certificate
948
973
  * var ca = await pki.scep.getCACert("http://ca.example/scep", { transport: scepCaCertTransport });
@@ -998,7 +1023,7 @@ async function getCACert(baseUrl, opts) {
998
1023
  *
999
1024
  * @opts
1000
1025
  * - `caCertificate` -- the current SCEP CA certificate DER; the response is authenticated against its key (required).
1001
- * - `transport` / `timeout` / `maxResponseBytes` / `maxRedirects` / `tls` / `allowCrossOriginRedirect` / `proxy` -- transport bounds (`proxy` reaches the CA through a forward HTTP proxy; see pki.transport).
1026
+ * - `transport` / `timeout` / `maxResponseBytes` / `maxRedirects` / `tls` / `allowCrossOriginRedirect` / `proxy` -- transport bounds (`proxy` reaches the CA through a forward HTTP proxy; see pki.transport). `tls.distrustedKeys` lists SHA-256 SubjectPublicKeyInfo digests (see pki.trust.spkiDigest); a server whose chain carries a listed key is refused with `scep/server-key-distrusted` on every hop, and a malformed list is `scep/bad-input`. A `tls` setting other than `anchors`, `useSystemStore`, `cert`, `key`, `minVersion`, `servername`, `checkServerIdentity` and `distrustedKeys` is refused `scep/bad-input`.
1002
1027
  * @example
1003
1028
  * // requires: caCertDer -- the current CA certificate (from getCACert), scepTransport
1004
1029
  * var next = await pki.scep.getNextCACert("http://ca.example/scep", { caCertificate: caCertDer, transport: scepTransport });
@@ -1104,7 +1129,7 @@ async function _pkiOperation(messageType, baseUrl, opts, verbName) {
1104
1129
  var recipientKey = _captureRecipientKey(opts.recipientKey);
1105
1130
  var signer = { cert: guard.bytes.snapshotSource(opts.signer.cert, ScepError, "scep/bad-input", "signer.cert"), key: opts.signer.key };
1106
1131
  var clientOpts = {
1107
- tls: opts.tls != null ? guard.identifier.ownOptions(opts.tls) : opts.tls,
1132
+ tls: _snapshotTls(opts.tls),
1108
1133
  maxRedirects: opts.maxRedirects,
1109
1134
  allowCrossOriginRedirect: opts.allowCrossOriginRedirect,
1110
1135
  timeout: opts.timeout,
@@ -1177,7 +1202,7 @@ async function _pkiOperation(messageType, baseUrl, opts, verbName) {
1177
1202
  * - `sleep` -- an injectable sleeper `(ms) -> Promise` for the Retry-After wait (default a real timer).
1178
1203
  * - `onRetryAfter` -- an optional observer `(delaySeconds) -> void` called before each poll sleep.
1179
1204
  * - `httpMethod` -- `"POST"` (default) or `"GET"`. RFC 8894 sec. 4.1 sends the PKIOperation message over POST; `"GET"` carries it base64-encoded in the URL `message` query for a legacy CA that lacks POST.
1180
- * - `transport` / `timeout` / `maxResponseBytes` / `maxRedirects` / `tls` / `allowCrossOriginRedirect` / `proxy` -- transport bounds (`proxy` reaches the CA through a forward HTTP proxy; see pki.transport).
1205
+ * - `transport` / `timeout` / `maxResponseBytes` / `maxRedirects` / `tls` / `allowCrossOriginRedirect` / `proxy` -- transport bounds (`proxy` reaches the CA through a forward HTTP proxy; see pki.transport). `tls.distrustedKeys` lists SHA-256 SubjectPublicKeyInfo digests (see pki.trust.spkiDigest); a server whose chain carries a listed key is refused with `scep/server-key-distrusted` on every hop, and a malformed list is `scep/bad-input`. A `tls` setting other than `anchors`, `useSystemStore`, `cert`, `key`, `minVersion`, `servername`, `checkServerIdentity` and `distrustedKeys` is refused `scep/bad-input`.
1181
1206
  * @example
1182
1207
  * // requires: scepCsr, caCertDer, clientCertDer, clientKeyPkcs8, scepTransport
1183
1208
  * var out = await pki.scep.enroll("http://ca.example/scep", { csr: scepCsr, caCert: caCertDer,
@@ -1255,7 +1280,7 @@ async function _queryOperation(messageType, baseUrl, opts, verbName) {
1255
1280
  var recipientKey = _captureRecipientKey(opts.recipientKey);
1256
1281
  var signer = { cert: guard.bytes.snapshotSource(opts.signer.cert, ScepError, "scep/bad-input", "signer.cert"), key: opts.signer.key };
1257
1282
  var want = _resolveQueryTarget(opts);
1258
- var clientOpts = { tls: opts.tls != null ? guard.identifier.ownOptions(opts.tls) : opts.tls, maxRedirects: opts.maxRedirects, allowCrossOriginRedirect: opts.allowCrossOriginRedirect, timeout: opts.timeout, maxResponseBytes: opts.maxResponseBytes, proxy: _snapshotProxy(opts.proxy) };
1283
+ var clientOpts = { tls: _snapshotTls(opts.tls), maxRedirects: opts.maxRedirects, allowCrossOriginRedirect: opts.allowCrossOriginRedirect, timeout: opts.timeout, maxResponseBytes: opts.maxResponseBytes, proxy: _snapshotProxy(opts.proxy) };
1259
1284
  if (checkedTransport !== undefined) clientOpts.transport = checkedTransport;
1260
1285
  var senderNonce = guard.crypto.randomBytes(16);
1261
1286
  var transactionId = opts.transactionId != null ? opts.transactionId : _defaultTransactionId();
@@ -1295,7 +1320,7 @@ async function _queryOperation(messageType, baseUrl, opts, verbName) {
1295
1320
  * - `responderCert` -- the certificate to authenticate the CA response signature against (defaults to `caCert`).
1296
1321
  * - `transactionId` -- a caller-unique PrintableString (defaults to 16 random bytes, hex-encoded).
1297
1322
  * - `httpMethod` -- `"POST"` (default) or `"GET"`. RFC 8894 sec. 4.1 sends the PKIOperation message over POST; `"GET"` carries it base64-encoded in the URL `message` query for a legacy CA that lacks POST.
1298
- * - `transport` / `timeout` / `maxResponseBytes` / `maxRedirects` / `tls` / `allowCrossOriginRedirect` / `proxy` -- transport bounds (`proxy` reaches the CA through a forward HTTP proxy; see pki.transport).
1323
+ * - `transport` / `timeout` / `maxResponseBytes` / `maxRedirects` / `tls` / `allowCrossOriginRedirect` / `proxy` -- transport bounds (`proxy` reaches the CA through a forward HTTP proxy; see pki.transport). `tls.distrustedKeys` lists SHA-256 SubjectPublicKeyInfo digests (see pki.trust.spkiDigest); a server whose chain carries a listed key is refused with `scep/server-key-distrusted` on every hop, and a malformed list is `scep/bad-input`. A `tls` setting other than `anchors`, `useSystemStore`, `cert`, `key`, `minVersion`, `servername`, `checkServerIdentity` and `distrustedKeys` is refused `scep/bad-input`.
1299
1324
  * @example
1300
1325
  * // requires: clientCertDer, caCertDer, clientKeyPkcs8, scepTransport
1301
1326
  * var out = await pki.scep.getCert("http://ca.example/scep", { certificate: clientCertDer, caCert: caCertDer,
@@ -1344,7 +1369,7 @@ async function getCert(baseUrl, opts) {
1344
1369
  * - `responderCert` -- the certificate to authenticate the CA response against (defaults to `caCert`).
1345
1370
  * - `transactionId` -- a caller-unique PrintableString (defaults to 16 random bytes, hex-encoded).
1346
1371
  * - `httpMethod` -- `"POST"` (default) or `"GET"`. RFC 8894 sec. 4.1 sends the PKIOperation message over POST; `"GET"` carries it base64-encoded in the URL `message` query for a legacy CA that lacks POST.
1347
- * - `transport` / `timeout` / `maxResponseBytes` / `maxRedirects` / `tls` / `allowCrossOriginRedirect` / `proxy` -- transport bounds (`proxy` reaches the CA through a forward HTTP proxy; see pki.transport).
1372
+ * - `transport` / `timeout` / `maxResponseBytes` / `maxRedirects` / `tls` / `allowCrossOriginRedirect` / `proxy` -- transport bounds (`proxy` reaches the CA through a forward HTTP proxy; see pki.transport). `tls.distrustedKeys` lists SHA-256 SubjectPublicKeyInfo digests (see pki.trust.spkiDigest); a server whose chain carries a listed key is refused with `scep/server-key-distrusted` on every hop, and a malformed list is `scep/bad-input`. A `tls` setting other than `anchors`, `useSystemStore`, `cert`, `key`, `minVersion`, `servername`, `checkServerIdentity` and `distrustedKeys` is refused `scep/bad-input`.
1348
1373
  * @example
1349
1374
  * // requires: clientCertDer, caCertDer, clientKeyPkcs8, scepTransport
1350
1375
  * var out = await pki.scep.getCrl("http://ca.example/scep", { certificate: clientCertDer, caCert: caCertDer,
@@ -1557,7 +1557,7 @@ function _extensionRow(ext, opts, index, state, decoded, code, profile) {
1557
1557
  row.value = ext.value;
1558
1558
  row.scope = opts.scope;
1559
1559
  row.index = index;
1560
- row.containerIndex = opts.containerIndex === undefined ? null : opts.containerIndex;
1560
+ row.containerIndex = opts.containerIndex == null ? null : opts.containerIndex;
1561
1561
  row.state = state;
1562
1562
  row.decoded = decoded;
1563
1563
  row.code = code;
@@ -1754,7 +1754,7 @@ function extensionDoor(spec) {
1754
1754
  }
1755
1755
 
1756
1756
  function decodeExtension(ext, callerOpts) {
1757
- var read = readOpts(callerOpts, "decodeExtension", spec.defaultScope === undefined);
1757
+ var read = readOpts(callerOpts, "decodeExtension", spec.defaultScope == null);
1758
1758
  var key = read.scope === undefined ? spec.defaultScope : read.scope;
1759
1759
  if (typeof key !== "string" || !_hasOwn(spec.scopes, key)) {
1760
1760
  throw E(badInput, "pki.schema." + spec.key + ".decodeExtension needs opts.scope naming which " +
@@ -2617,7 +2617,7 @@ function spki(ns, opts) {
2617
2617
 
2618
2618
  function attribute(ns, opts) {
2619
2619
  opts = opts || _create(null);
2620
- var minValues = opts.minValues === undefined ? 1 : opts.minValues;
2620
+ var minValues = opts.minValues == null ? 1 : opts.minValues;
2621
2621
  return schema.seq([
2622
2622
  schema.field("type", schema.oidLeaf()),
2623
2623
  schema.field("values", schema.setOf(schema.any(), { assert: "set", min: minValues, max: constants.LIMITS.ATTRIBUTE_MAX_VALUES,
package/lib/sigstore.js CHANGED
@@ -1244,7 +1244,7 @@ async function _verifyRekorEntry(te, rekorKeys, leafDer, contentType, arm, paylo
1244
1244
  * be checked. A token that does not verify is passed over rather than refused: it is a statement from
1245
1245
  * an authority this caller did not pin, which says nothing either way. With no token verifying, the
1246
1246
  * caller is left with no attested instant and the verdict says so. */
1247
- async function _timestampInstants(vm, sigBytes, tsaRoots) {
1247
+ async function _timestampInstants(vm, sigBytes, tsaRoots, distrusted) {
1248
1248
  if (!tsaRoots) return null;
1249
1249
  var anchors = _normTsaAnchors(tsaRoots);
1250
1250
  if (!anchors.length) {
@@ -1271,7 +1271,7 @@ async function _timestampInstants(vm, sigBytes, tsaRoots) {
1271
1271
  ? _b64(t.signedTimestamp, "rfc3161Timestamps[" + i + "].signedTimestamp") : null;
1272
1272
  if (der === null) continue;
1273
1273
  var res;
1274
- try { res = await _tspVerify(der, sigBytes, { trustAnchors: anchors }); }
1274
+ try { res = await _tspVerify(der, sigBytes, { trustAnchors: anchors, distrustedKeys: distrusted }); }
1275
1275
  catch (_e) { continue; } // allow:swallow-unverified a token this caller cannot check says nothing; the next is tried and a bundle with none verifying is refused for want of an instant
1276
1276
  if (!res || res.valid !== true || res.trusted !== true) continue;
1277
1277
  var ms = guard.time.instantOf(res.genTime);
@@ -1402,7 +1402,7 @@ function _ctLogFor(logs, logIdBytes, timeMs) {
1402
1402
  * here read the caller's array after a yield, so a caller could add an anchor while verification was
1403
1403
  * pending and decide which CA is trusted: an empty list that grows during the timestamp check would have
1404
1404
  * passed a chain the call was made without any anchor for. */
1405
- async function _verifyChain(leaf, chainDers, roots, timeMs) {
1405
+ async function _verifyChain(leaf, chainDers, roots, timeMs, distrusted) {
1406
1406
  var bundleLinks = _map(_arraySlice(chainDers, 1), function (d, i) { return _parseCert(d, "bundle intermediate [" + i + "]"); });
1407
1407
  var inWindow = _filter(roots, function (r) { return _inWindow(timeMs, r.validFor); });
1408
1408
  if (!inWindow.length) {
@@ -1449,6 +1449,7 @@ async function _verifyChain(leaf, chainDers, roots, timeMs) {
1449
1449
  time: new _Date(timeMs),
1450
1450
  historicalMode: true,
1451
1451
  requiredEku: ["codeSigning"],
1452
+ distrustedKeys: distrusted,
1452
1453
  });
1453
1454
  } catch (e) {
1454
1455
  if (e && e.code === "path/no-path") {
@@ -1557,7 +1558,7 @@ function _fulcioExtValue(ext, leafArc) {
1557
1558
 
1558
1559
  var IDENTITY_FIELDS = ["san", "issuer", "sourceRepositoryURI"];
1559
1560
  var IDENTITY_KEYS = _assign(_create(null), { san: 1, issuer: 1, sourceRepositoryURI: 1 });
1560
- var _VERIFY_BUNDLE_OPTS = _assign(_create(null), { fulcioRoots: 1, rekorKeys: 1, ctLogs: 1, identity: 1, predicateType: 1, time: 1, artifact: 1, tsaRoots: 1 });
1561
+ var _VERIFY_BUNDLE_OPTS = _assign(_create(null), { fulcioRoots: 1, rekorKeys: 1, ctLogs: 1, identity: 1, predicateType: 1, time: 1, artifact: 1, tsaRoots: 1, distrustedKeys: 1 });
1561
1562
 
1562
1563
  /** @internal The bytes a message signature covers, taken once. There is no shape here in which a
1563
1564
  * caller hands over a precomputed digest: the arm's own field comment forbids using one as a
@@ -1702,6 +1703,7 @@ function _statement(payload, payloadType, expectedPredicate) {
1702
1703
  * predicateType: string, // optional: require this in-toto predicateType (e.g. the SLSA URI); dsse_envelope bundles only
1703
1704
  * artifact: BufferSource, // the bytes a message_signature covers; required for that arm, refused for a dsse_envelope
1704
1705
  * tsaRoots: Array, // optional timestamp-authority anchors: a DER Buffer or { der } each; required to date a Rekor v2 bundle, which carries no signed entry timestamp
1706
+ * distrustedKeys: Array, // optional SHA-256 digests of SubjectPublicKeyInfo DER (64 hex characters or 32 bytes); a listed key fails the Fulcio chain, or the timestamp authority's chain so its token dates nothing
1705
1707
  * time: Date, // optional check-date override (default: the Rekor integratedTime, or an RFC 3161 token's genTime on a v2 entry)
1706
1708
  *
1707
1709
  * @example
@@ -1736,6 +1738,9 @@ async function verifyBundle(bundle, opts) {
1736
1738
  * verification is pending would decide a check the call was made without it. */
1737
1739
  var fulcioRoots = _normRoots(o.fulcioRoots || []);
1738
1740
  var ctLogs = o.ctLogs === undefined || o.ctLogs === null ? o.ctLogs : _normCtLogs(o.ctLogs);
1741
+ /** @internal Typed here, before the timestamp leg, because that leg passes over a token whose
1742
+ * verification throws, and a malformed list must be reported as the caller's input fault. */
1743
+ var distrusted = guard.distrust.keys(o.distrustedKeys, _err, "sigstore/bad-input", "opts.distrustedKeys");
1739
1744
 
1740
1745
  /** @internal Each option belongs to one arm, and one supplied for the other is refused rather than
1741
1746
  * passed over. An ignored option reads as a check that ran: opts.artifact on a DSSE bundle names
@@ -1796,7 +1801,7 @@ async function verifyBundle(bundle, opts) {
1796
1801
  * SIGNATURE bytes, which is what dates the ephemeral certificate that made them; a token over the
1797
1802
  * payload would date the thing signed rather than the signing. */
1798
1803
  var tsaMsList = null;
1799
- if (!haveCallerTime) tsaMsList = await _timestampInstants(vm, derSig, o.tsaRoots);
1804
+ if (!haveCallerTime) tsaMsList = await _timestampInstants(vm, derSig, o.tsaRoots, distrusted);
1800
1805
  var chainDers = _chainDers(vm);
1801
1806
  /** @internal A caller-pinned instant is the same for every candidate, so the chain it decides is
1802
1807
  * computed on the first candidate and reused rather than rebuilt per entry. Without one the instant
@@ -1873,7 +1878,7 @@ async function verifyBundle(bundle, opts) {
1873
1878
  "timestamp authority, or supply opts.time");
1874
1879
  }
1875
1880
  var candidateChain = pinnedChain !== null ? pinnedChain
1876
- : await _verifyChain(leaf, chainDers, fulcioRoots, candidateTime);
1881
+ : await _verifyChain(leaf, chainDers, fulcioRoots, candidateTime, distrusted);
1877
1882
  if (haveCallerTime) pinnedChain = candidateChain;
1878
1883
  var candidateScts = await _verifyEmbeddedScts(leaf, leafDer, candidateChain.issuer, ctLogs, candidateTime, candidateBound);
1879
1884
  rekor = candidate;