@blamejs/pki 0.5.7 → 0.5.9

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 (105) hide show
  1. package/CHANGELOG.md +418 -386
  2. package/MIGRATING.md +43 -0
  3. package/README.md +12 -12
  4. package/lib/acme.js +31 -31
  5. package/lib/asn1-der.js +10 -10
  6. package/lib/attrcert-sign.js +19 -19
  7. package/lib/byte-reader.js +6 -6
  8. package/lib/byte-writer.js +5 -5
  9. package/lib/cbor-det.js +27 -24
  10. package/lib/cmc-build.js +97 -92
  11. package/lib/cmc-verify.js +106 -30
  12. package/lib/cmp-build.js +25 -25
  13. package/lib/cmp-session.js +70 -70
  14. package/lib/cmp-verify.js +71 -57
  15. package/lib/cms-compress.js +7 -7
  16. package/lib/cms-decrypt.js +90 -72
  17. package/lib/cms-encrypt.js +32 -32
  18. package/lib/cms-sign.js +74 -55
  19. package/lib/cms-verify.js +97 -75
  20. package/lib/composite-sig.js +13 -13
  21. package/lib/constants.js +4 -4
  22. package/lib/crl-sign.js +22 -22
  23. package/lib/crl-verify.js +7 -6
  24. package/lib/crmf-sign.js +14 -14
  25. package/lib/csr-sign.js +8 -8
  26. package/lib/ct.js +37 -37
  27. package/lib/edwards-point.js +7 -7
  28. package/lib/est.js +98 -55
  29. package/lib/framework-error.js +5 -5
  30. package/lib/guard-all.js +5 -3
  31. package/lib/guard-async.js +4 -4
  32. package/lib/guard-bytes.js +79 -79
  33. package/lib/guard-compress.js +17 -17
  34. package/lib/guard-crypto.js +1 -1
  35. package/lib/guard-der.js +152 -0
  36. package/lib/guard-encoding.js +15 -15
  37. package/lib/guard-header.js +3 -3
  38. package/lib/guard-identifier.js +16 -16
  39. package/lib/guard-json.js +15 -15
  40. package/lib/guard-limits.js +7 -7
  41. package/lib/guard-name.js +81 -16
  42. package/lib/guard-parsed.js +80 -80
  43. package/lib/guard-range.js +19 -19
  44. package/lib/guard-secret.js +11 -10
  45. package/lib/guard-text.js +6 -6
  46. package/lib/guard-time.js +10 -10
  47. package/lib/hpke.js +18 -17
  48. package/lib/http-digest.js +35 -35
  49. package/lib/http-retry-after.js +13 -13
  50. package/lib/http-transport.js +20 -19
  51. package/lib/inspect.js +53 -53
  52. package/lib/ip-utils.js +2 -2
  53. package/lib/jose.js +13 -13
  54. package/lib/key.js +16 -16
  55. package/lib/lint.js +51 -51
  56. package/lib/merkle.js +51 -36
  57. package/lib/mime.js +18 -18
  58. package/lib/ocsp-verify.js +10 -10
  59. package/lib/ocsp.js +13 -13
  60. package/lib/oid.js +29 -29
  61. package/lib/path-validate.js +114 -113
  62. package/lib/pbes2.js +16 -16
  63. package/lib/pkcs12-build.js +53 -53
  64. package/lib/pki-build.js +48 -20
  65. package/lib/rc2.js +1 -1
  66. package/lib/rfc3339.js +5 -5
  67. package/lib/schema-all.js +31 -31
  68. package/lib/schema-attrcert.js +12 -12
  69. package/lib/schema-c509.js +149 -197
  70. package/lib/schema-cmc.js +58 -58
  71. package/lib/schema-cmp.js +43 -43
  72. package/lib/schema-cms.js +45 -45
  73. package/lib/schema-crl.js +7 -7
  74. package/lib/schema-crmf.js +28 -28
  75. package/lib/schema-csr.js +12 -12
  76. package/lib/schema-csrattrs.js +16 -16
  77. package/lib/schema-engine.js +18 -18
  78. package/lib/schema-ocsp.js +15 -15
  79. package/lib/schema-pkcs12.js +20 -20
  80. package/lib/schema-pkcs8.js +2 -2
  81. package/lib/schema-pkix.js +131 -126
  82. package/lib/schema-smime.js +19 -19
  83. package/lib/schema-tsp.js +12 -12
  84. package/lib/schema-x509.js +3 -3
  85. package/lib/shbs.js +18 -18
  86. package/lib/sign-scheme.js +13 -13
  87. package/lib/sigstore.js +10 -11
  88. package/lib/sleep.js +1 -1
  89. package/lib/smime.js +308 -96
  90. package/lib/tls-cert-compress.js +18 -18
  91. package/lib/trust.js +27 -27
  92. package/lib/tsp-sign.js +17 -17
  93. package/lib/validator-all.js +1 -1
  94. package/lib/validator-attcert.js +1 -1
  95. package/lib/validator-cose.js +43 -44
  96. package/lib/validator-keydesc.js +3 -3
  97. package/lib/validator-sig.js +13 -13
  98. package/lib/validator-tls.js +11 -11
  99. package/lib/validator-tpm.js +20 -19
  100. package/lib/webauthn-mds.js +66 -66
  101. package/lib/webauthn.js +33 -33
  102. package/lib/webcrypto.js +15 -15
  103. package/lib/x509-sign.js +13 -13
  104. package/package.json +3 -2
  105. package/sbom.cdx.json +6 -6
@@ -88,7 +88,7 @@ var OID = {
88
88
  caIssuers: oid.byName("caIssuers"),
89
89
  };
90
90
 
91
- // The set of extension OIDs the validator PROCESSES -- an unrecognized critical
91
+ // The set of extension OIDs the validator processes. An unrecognized critical
92
92
  // extension outside this set fails the path (6.1.4(o), 6.1.5(e)).
93
93
  // extendedKeyUsage is recognized: the critical form is legal (4.2.1.12) and
94
94
  // appears in the wild (RFC 6960 4.2.2.2 delegated OCSP responders); its
@@ -102,7 +102,7 @@ var PROCESSED_EXTENSIONS = {};
102
102
  // validateCriticalExtensionStructure via the registered decoder. freshestCRL
103
103
  // stays OUT: sec. 4.2.1.15 requires it non-critical and the validator does not
104
104
  // consult it (no delta merge), so a critical instance fails unrecognized.
105
- // qcStatements is deliberately NOT processed: a critical QC statement (QcLimitValue reliance limit,
105
+ // qcStatements is deliberately left unprocessed: a critical QC statement (QcLimitValue reliance limit,
106
106
  // QcType certificate purpose) asserts qualified-certificate semantics a relying party MUST enforce, and
107
107
  // this validator does not enforce them (nor expose a handler to). Marking it processed would let a caller
108
108
  // treat a certificate as valid outside its asserted critical QC constraints, so a critical qcStatements
@@ -113,7 +113,7 @@ var PROCESSED_EXTENSIONS = {};
113
113
  OID.extKeyUsage, OID.cRLDistributionPoints].
114
114
  forEach(function (o) { PROCESSED_EXTENSIONS[o] = true; });
115
115
  // Frozen after seeding: this exact object is both consulted by the critical-extension check here and
116
- // exported (for pki.lint to stay consistent). A caller must not be able to add an OID -- doing so
116
+ // exported (for pki.lint to stay consistent). A caller must not be able to add an OID, because that
117
117
  // would make an attacker's critical, decoder-less extension pass as "processed" and skip both the
118
118
  // unrecognized-critical check and structural validation. Freezing makes any such write a no-op.
119
119
  Object.freeze(PROCESSED_EXTENSIONS);
@@ -130,11 +130,11 @@ var SIG_ALGS = {};
130
130
  // NULL must be present -- RSASSA-PKCS1-v1_5, RFC 4055 sec. 5) or "absent" (parameters
131
131
  // must be omitted -- ECDSA/EdDSA/ML-DSA, RFC 5758/8410). A cert deviating from
132
132
  // its algorithm's required shape is malformed and rejected before verify.
133
- // `sameKeyOid` marks the one-shot families whose PUBLIC-KEY algorithm OID is the
134
- // SAME as the signature algorithm OID -- EdDSA, ML-DSA, SLH-DSA. For these, Node's
135
- // WebCrypto imports an SPKI of ANOTHER type under the requested name and verifies
136
- // with the real key (it does NOT reject a mismatched SPKI the way it does for
137
- // RSA/ECDSA), so the issuer-key <-> signature-algorithm consistency (RFC 9814 sec. 4)
133
+ // `sameKeyOid` marks the one-shot families whose public-key algorithm OID equals
134
+ // the signature algorithm OID: EdDSA, ML-DSA, SLH-DSA. For these, Node's
135
+ // WebCrypto imports an SPKI of another type under the requested name and verifies
136
+ // with the real key (unlike RSA/ECDSA, where a mismatched SPKI is refused at
137
+ // import), so the issuer-key <-> signature-algorithm consistency (RFC 9814 sec. 4)
138
138
  // must be checked structurally: the SPKI OID must equal the signature OID.
139
139
  function _sig(name, verify, imp, params, ecdsa, sameKeyOid) {
140
140
  var entry = { verify: verify, imp: imp, params: params };
@@ -223,9 +223,9 @@ function resolveRsaPss(paramsBytes) {
223
223
  // RSASSA-PSS-params ::= SEQUENCE { hashAlgorithm [0] DEFAULT sha1,
224
224
  // maskGenAlgorithm [1] DEFAULT mgf1SHA1, saltLength [2] INTEGER DEFAULT 20,
225
225
  // trailerField [3] DEFAULT 1 }. WebCrypto verifies with MGF1 keyed to the
226
- // SAME hash as the signature and trailerField 0xBC (1); any declared value
227
- // that deviates cannot be honored, so it is REJECTED rather than verified
228
- // under WebCrypto's defaults (a signatureAlgorithm bypass otherwise).
226
+ // same hash as the signature and trailerField 0xBC (1). A declared value
227
+ // that deviates cannot be honored, so it is rejected; verifying it under
228
+ // WebCrypto's defaults would be a signatureAlgorithm bypass.
229
229
  // RFC 4055 DEFAULTs are SHA-1 (hashAlgorithm and mgf1SHA1). Because SHA-1 is
230
230
  // rejected, an absent hashAlgorithm or maskGenAlgorithm would resolve to SHA-1
231
231
  // and must be REJECTED -- a supported PSS AlgorithmIdentifier must state both
@@ -244,8 +244,8 @@ function resolveRsaPss(paramsBytes) {
244
244
  if (f.tagNumber > 3 || f.tagNumber <= pssLastTag) throw E("path/unsupported-algorithm", "RSASSA-PSS-params has an unexpected, duplicate, or out-of-order field [" + f.tagNumber + "]");
245
245
  pssLastTag = f.tagNumber;
246
246
  // Every RSASSA-PSS-params field is an EXPLICIT [n] wrapper (constructed)
247
- // around EXACTLY ONE value (an AlgorithmIdentifier or an INTEGER); a
248
- // primitive/childless or multi-child context field is malformed -- reading
247
+ // around exactly one value (an AlgorithmIdentifier or an INTEGER); a
248
+ // primitive/childless or multi-child context field is malformed: reading
249
249
  // f.children[0] and ignoring the rest would accept non-DER parameters.
250
250
  if (!f.children || f.children.length !== 1) throw E("path/unsupported-algorithm", "malformed RSASSA-PSS parameter field [" + f.tagNumber + "] (an EXPLICIT wrapper carries exactly one value)");
251
251
  if (f.tagNumber === 0) {
@@ -305,7 +305,7 @@ function resolveDescriptor(sigAlg) {
305
305
  // RFC 9814 sec. 4 issuer-key <-> signature-algorithm consistency (algorithm-confusion
306
306
  // defense). For the one-shot families whose public key shares the signature OID
307
307
  // (EdDSA, ML-DSA, SLH-DSA), Node's WebCrypto imports an SPKI of a DIFFERENT type
308
- // under the requested name and verifies with the real key -- so an Ed25519-signed
308
+ // under the requested name and verifies with the real key, so an Ed25519-signed
309
309
  // certificate labelled SLH-DSA would otherwise validate. Enforce structurally:
310
310
  // the issuer SPKI's algorithm OID MUST equal the signature algorithm OID. (For
311
311
  // RSA/ECDSA -- different key vs signature OIDs -- WebCrypto's import already rejects
@@ -331,9 +331,9 @@ function assertKeyMatchesSigAlg(spkiBytes, sigOid, d) {
331
331
  // compositeKeyUsageCheck (below) enforces the sec. 5.2 signature-only keyUsage restriction.
332
332
 
333
333
  // draft-ietf-lamps-pq-composite-sigs sec. 5.2: a certificate whose SubjectPublicKeyInfo
334
- // carries a composite ML-DSA OID, IF it has a keyUsage extension, MUST assert at least
335
- // one signature bit (digitalSignature / nonRepudiation / keyCertSign / cRLSign) and MUST
336
- // NOT assert any encryption or key-establishment bit -- a composite ML-DSA key is a
334
+ // carries a composite ML-DSA OID, if it has a keyUsage extension, MUST assert at least
335
+ // one signature bit (digitalSignature / nonRepudiation / keyCertSign / cRLSign) and
336
+ // MUST NOT assert any encryption or key-establishment bit. A composite ML-DSA key is a
337
337
  // signature-only key (ML-DSA cannot encrypt or agree, so a "dual usage" key is forbidden
338
338
  // even when the traditional component could encrypt). The caller invokes this only for a
339
339
  // composite-keyed certificate; an absent keyUsage places no restriction (RFC 5280 4.2.1.3).
@@ -359,9 +359,9 @@ function compositeKeyUsageCheck(cert) {
359
359
  var ML_KEM_OIDS = {};
360
360
  ["id-ml-kem-512", "id-ml-kem-768", "id-ml-kem-1024"].forEach(function (n) { ML_KEM_OIDS[oid.byName(n)] = true; });
361
361
 
362
- // RFC 9935 sec. 5: a certificate whose SubjectPublicKeyInfo carries an id-ml-kem-* OID, IF it
363
- // has a keyUsage extension, MUST assert keyEncipherment as the ONLY key usage set -- an ML-KEM
364
- // key is a key-establishment-only key (it cannot sign or agree, so no other bit is legitimate,
362
+ // RFC 9935 sec. 5: a certificate whose SubjectPublicKeyInfo carries an id-ml-kem-* OID, if it
363
+ // has a keyUsage extension, MUST assert keyEncipherment as the only key usage set, since an
364
+ // ML-KEM key is a key-establishment-only key (it cannot sign or agree, so no other bit is legitimate,
365
365
  // and an unnamed/reserved bit set alongside keyEncipherment is equally forbidden). The caller
366
366
  // invokes this only for an ML-KEM-keyed certificate; an absent keyUsage places no restriction
367
367
  // (RFC 5280 sec. 4.2.1.3). This also makes an ML-KEM "CA" (keyCertSign) an explicit reject.
@@ -380,12 +380,13 @@ function kemKeyUsageCheck(cert) {
380
380
  return { ok: true };
381
381
  }
382
382
 
383
- // Import a descriptor's verification key, validating an EdDSA point FIRST: node/OpenSSL import a
384
- // low-order (e.g. identity or all-zeroes) Ed25519/Ed448 SPKI without complaint and such a key
385
- // verifies a forged signature. This is the ONE seam both the certificate path and the revocation
386
- // (CRL / OCSP-response) path import through, so neither can skip the point gate -- a low-order
387
- // issuer / responder key fails the caller closed (a rejected promise the caller maps to a bad
388
- // verdict), never verifying a forged chain or a forged revocation.
383
+ // Import a descriptor's verification key, validating an EdDSA point before the import runs:
384
+ // node/OpenSSL import a low-order (e.g. identity or all-zeroes) Ed25519/Ed448 SPKI without
385
+ // complaint and such a key verifies a forged signature. Both the certificate path and the
386
+ // revocation (CRL / OCSP-response) path import through this single seam, so neither can skip
387
+ // the point gate; a low-order issuer / responder key fails the caller closed (a rejected
388
+ // promise the caller maps to a bad verdict), never verifying a forged chain or a forged
389
+ // revocation.
389
390
  function _importVerifyKey(spkiBytes, d) {
390
391
  try {
391
392
  if (d.eddsa) edwardsPoint.validateSpki(spkiBytes, d.eddsa, PathError, "path/bad-signature");
@@ -474,7 +475,7 @@ function requireCriticalExt(ext, name, checks) {
474
475
  // Split an addr-spec into [localPart, host] at its single "@". Returns null when
475
476
  // there is no "@" (a bare host/domain constraint), or "ambiguous" when there are
476
477
  // multiple "@": a conformant certificate rfc822Name is a simple addr-spec with
477
- // EXACTLY ONE "@" (RFC 5280 sec. 4.2.1.6 deprecates the quoted local part, and an
478
+ // exactly one "@" (RFC 5280 sec. 4.2.1.6 deprecates the quoted local part, and an
478
479
  // addr-spec domain never contains "@"), so a multi-"@" mailbox like
479
480
  // "a@b"@example.com cannot be split reliably and must fail closed.
480
481
  function splitMailbox(addr) {
@@ -511,8 +512,8 @@ function emailMatch(constraint, mailbox) {
511
512
  }
512
513
 
513
514
  // Strip a single trailing dot (the absolute-FQDN root label) so "evil.com."
514
- // and "evil.com" compare equal -- otherwise a trailing-dot SAN would escape a
515
- // dNSName constraint.
515
+ // and "evil.com" compare equal. Without that, a trailing-dot SAN would escape
516
+ // a dNSName constraint.
516
517
  function stripTrailingDot(s) { return s.charAt(s.length - 1) === "." ? s.slice(0, -1) : s; }
517
518
 
518
519
  // Host-suffix match with the RFC 5280 sec. 4.2.1.10 leading-period rule shared by
@@ -527,7 +528,7 @@ function hostConstraintMatch(constraint, host) {
527
528
  // Is `host` a fully qualified domain name (as a URI host constraint requires,
528
529
  // RFC 5280 sec. 4.2.1.10)? A dotless single label (localhost), an IPv4/IPv6 literal,
529
530
  // or a value carrying non-hostname characters (a scheme "://", a path "/", a
530
- // port ":") is NOT a FQDN and cannot be matched against a domain-suffix
531
+ // port ":") is not a FQDN and cannot be matched against a domain-suffix
531
532
  // constraint. Only [A-Za-z0-9.-] with at least one dot qualifies.
532
533
  function isFqdnHost(host) {
533
534
  var h = stripTrailingDot(host);
@@ -538,12 +539,12 @@ function isFqdnHost(host) {
538
539
  }
539
540
 
540
541
  // A URI constraint applies to the host part: a leading "." matches subdomains
541
- // only; a bare host matches that host EXACTLY (not subdomains, per sec. 4.2.1.10).
542
- // BOTH sides must be a fully qualified domain name: a URI SAN with no host / an
543
- // IP literal, OR a malformed constraint that is not an FQDN (e.g. a full URI
544
- // "http://blocked.example" rather than "blocked.example"), cannot be evaluated
545
- // and returns "unsupported" so the caller fails closed rather than letting the
546
- // name silently escape a critical constraint by never matching.
542
+ // only; a bare host matches that host exactly (not subdomains, per sec. 4.2.1.10).
543
+ // Both sides must be a fully qualified domain name. A URI SAN with no host / an
544
+ // IP literal, or a malformed constraint that is not an FQDN (e.g. a full URI
545
+ // "http://blocked.example" where the required form is "blocked.example"), cannot
546
+ // be evaluated, so it returns "unsupported" and the caller fails closed; reporting
547
+ // it as a plain non-match would let the name silently escape a critical constraint.
547
548
  function uriMatch(constraint, uri) {
548
549
  var host = uriHost(uri);
549
550
  if (host === null) return "unsupported";
@@ -560,10 +561,10 @@ function uriHost(uri) {
560
561
  if (!m) return null;
561
562
  var authority = m[1];
562
563
  // authority = [ userinfo "@" ] host [ ":" port ]. RFC 3986 userinfo does not
563
- // contain a raw "@", so a conformant authority has AT MOST ONE "@". Multiple
564
- // "@" is ambiguous -- the host cannot be determined reliably -- so fail CLOSED
565
- // (null -> uriMatch returns "unsupported" -> the caller rejects the path)
566
- // rather than guess a host that could slip a URI name constraint.
564
+ // contain a raw "@", so a conformant authority has at most one "@". With more
565
+ // than one the host cannot be determined reliably, so this fails closed
566
+ // (null -> uriMatch returns "unsupported" -> the caller rejects the path);
567
+ // guessing a host could slip a URI name constraint.
567
568
  var firstAt = authority.indexOf("@");
568
569
  if (firstAt !== authority.lastIndexOf("@")) return null;
569
570
  if (firstAt !== -1) authority = authority.slice(firstAt + 1);
@@ -583,9 +584,9 @@ function ipMatch(constraint, addr) {
583
584
 
584
585
  // Does GeneralName-like {tagNumber, value} match a constraint base of the same
585
586
  // form? Returns true/false for a supported form, null when the forms differ
586
- // (not comparable), or the string "unsupported" when the forms are the SAME
587
- // but this validator does not implement that form's comparison -- the caller
588
- // must then fail closed rather than treat it as "no match".
587
+ // (not comparable), or the string "unsupported" when the forms match but this
588
+ // validator does not implement that form's comparison. The caller must then fail
589
+ // closed: an unenforceable constraint cannot be read as "no match".
589
590
  function nameMatchesConstraint(gnTag, gnValue, base) {
590
591
  if (base.tagNumber !== gnTag) return null; // different form -> not comparable
591
592
  switch (gnTag) {
@@ -616,8 +617,8 @@ function certNameForms(cert) {
616
617
  var san = decodeExt(cert, OID.subjectAltName);
617
618
  var hasRfc822San = false;
618
619
  if (san) {
619
- // Preserve EVERY SAN entry, including a form whose value the validator does
620
- // not decode (x400Address [3] / ediPartyName [5]) -- dropping it would let a
620
+ // Preserve every SAN entry, including a form whose value the validator does
621
+ // not decode (x400Address [3] / ediPartyName [5]); dropping one would let a
621
622
  // critical constraint of that same unsupported form pass unenforced. The
622
623
  // constraint check fails such a form closed (name-constraint-unsupported).
623
624
  san.value.names.forEach(function (nm) {
@@ -629,10 +630,10 @@ function certNameForms(cert) {
629
630
  });
630
631
  }
631
632
  // RFC 5280 sec. 4.2.1.10: the legacy emailAddress in the subject DN is checked as
632
- // an rfc822Name UNLESS the SAN already carries the email identity as an
633
- // rfc822Name entry. A SAN of a DIFFERENT form (e.g. dNSName only) does NOT
634
- // cover the email, so the subject-DN email must still be constrained -- else an
635
- // excluded/non-permitted mailbox would slip an rfc822Name constraint.
633
+ // an rfc822Name unless the SAN already carries the email identity as an
634
+ // rfc822Name entry. A SAN of a different form (e.g. dNSName only) does not
635
+ // cover the email, so the subject-DN email must still be constrained. Otherwise
636
+ // an excluded/non-permitted mailbox would slip an rfc822Name constraint.
636
637
  if (!hasRfc822San) {
637
638
  cert.subject.rdns.forEach(function (rdn) {
638
639
  rdn.forEach(function (atv) {
@@ -645,17 +646,17 @@ function certNameForms(cert) {
645
646
  }
646
647
 
647
648
  // Check a cert's own names against the accumulated constraints. `excluded` is
648
- // a UNION (any match rejects). `permitted` is the INTERSECTION of every
649
+ // a union (any match rejects). `permitted` is the intersection of every
649
650
  // absorbing cert's permittedSubtrees (6.1.4(g)): the permitted set is tracked
650
- // as one GENERATION per absorbing cert, and a name of form F must match a
651
- // subtree of form F in EVERY generation that constrains form F. A flat pool
652
- // would compute the UNION -- letting a subordinate CA BROADEN what its parent
651
+ // as one generation per absorbing cert, and a name of form F must match a
652
+ // subtree of form F in every generation that constrains form F. A flat pool
653
+ // would compute the union, letting a subordinate CA broaden what its parent
653
654
  // permitted (a name-constraint bypass).
654
655
  function checkNameConstraints(state, cert) {
655
656
  var forms = certNameForms(cert);
656
657
  // Excluded: any match -> reject. A constraint of the same form the validator
657
658
  // cannot compare ("unsupported") means a critical exclusion it cannot
658
- // enforce -> fail closed rather than treat it as "no match".
659
+ // enforce, so it fails closed on the same footing as a match.
659
660
  for (var e = 0; e < state.excludedSubtrees.length; e++) {
660
661
  var ex = state.excludedSubtrees[e];
661
662
  for (var i = 0; i < forms.length; i++) {
@@ -703,7 +704,7 @@ function absorbNameConstraints(state, decoded) {
703
704
  function rootNode() {
704
705
  return { depth: 0, validPolicy: OID.anyPolicy, qualifierSet: [], expectedPolicySet: [OID.anyPolicy], children: [], parent: null };
705
706
  }
706
- // A deep copy of the valid-policy tree WITHOUT the internal `parent` back-pointer,
707
+ // A deep copy of the valid-policy tree that omits the internal `parent` back-pointer,
707
708
  // so the structured verdict returned to callers is acyclic (JSON.stringify-safe).
708
709
  // The `parent` link is an implementation detail of the 6.1.3 processing, not part
709
710
  // of the RFC 5280 valid_policy_tree a consumer inspects.
@@ -778,7 +779,7 @@ function initialize(certs, params, seeds) {
778
779
  policyNodeCount: 1,
779
780
  maxPolicyNodes: params.maxPolicyNodes !== undefined ? params.maxPolicyNodes : constants.LIMITS.PATH_MAX_POLICY_NODES,
780
781
  // Each absorbing cert's permittedSubtrees is one generation; a name must be
781
- // admitted by EVERY generation (intersection). An initial seed is generation 0.
782
+ // admitted by every generation (intersection). An initial seed is generation 0.
782
783
  permittedGenerations: seeds.permitted.length ? [seeds.permitted] : [],
783
784
  excludedSubtrees: seeds.excluded,
784
785
  explicitPolicy: params.initialExplicitPolicy ? 0 : n + 1,
@@ -796,8 +797,8 @@ function initialize(certs, params, seeds) {
796
797
 
797
798
  // self-issued = subject DN equals issuer DN. dnEqual throws on a NUL/control
798
799
  // DN (CVE-2009-2408); a malformed-DN cert is never "self-issued" (and is failed
799
- // by the name-chaining check), so swallow the throw rather than reject the
800
- // whole validate() promise from these unwrapped call sites.
800
+ // by the name-chaining check), so the throw is swallowed here, keeping it from
801
+ // rejecting the whole validate() promise at these unwrapped call sites.
801
802
  function selfIssued(cert) {
802
803
  try { return dnEqual(cert.subject.rdns, cert.issuer.rdns); }
803
804
  catch (_e) { return false; }
@@ -812,8 +813,8 @@ function processPolicies(state, cert, i, checks) {
812
813
  var policies = cp.value;
813
814
  var depth = i - 1;
814
815
  // anyPolicy processing is active only while inhibit_anyPolicy > 0, or for a
815
- // self-issued non-final cert -- this gates ONLY the (d)(2) expansion of a
816
- // cert-asserted anyPolicy. 4.2.1.14 inhibition is implemented entirely by
816
+ // self-issued non-final cert. What the gate covers is the (d)(2) expansion of
817
+ // a cert-asserted anyPolicy and nothing more. 4.2.1.14 inhibition rests entirely on
817
818
  // that gate: a depth-(i-1) anyPolicy node created while processing was
818
819
  // active remains matchable in (d)(1)(ii).
819
820
  var anyPolicyActive = state.inhibitAnyPolicy > 0 || (i < state._n && selfIssued(cert));
@@ -829,7 +830,7 @@ function processPolicies(state, cert, i, checks) {
829
830
  }
830
831
  });
831
832
  if (!matched) {
832
- // 6.1.3(d)(1)(ii): no expected-policy match -- create the node from a
833
+ // 6.1.3(d)(1)(ii): with no expected-policy match, create the node from a
833
834
  // depth-(i-1) anyPolicy node. The RFC runs this step UNCONDITIONALLY
834
835
  // (no inhibit clause); gating it would false-reject a path whose
835
836
  // specific policy chains through a legitimately created anyPolicy node.
@@ -901,8 +902,8 @@ function updateWorkingKey(state, cert) {
901
902
  state.workingPublicKeyParameters = null;
902
903
  }
903
904
  // When this cert's SPKI omits its algorithm parameters but the working set
904
- // carries inherited ones, store a reconstructed SPKI so the next signature
905
- // verify can import a complete key rather than failing on the bare bytes.
905
+ // carries inherited ones, store a reconstructed SPKI: the next signature verify
906
+ // needs a complete key and would fail on the bare bytes.
906
907
  if (isNullOrAbsentParams(keyAlg.parameters) && state.workingPublicKeyParameters) {
907
908
  state.workingPublicKey = spliceSpkiParameters(cert.subjectPublicKeyInfo, keyAlg.oid, state.workingPublicKeyParameters);
908
909
  } else {
@@ -915,7 +916,7 @@ function updateWorkingKey(state, cert) {
915
916
  // carrying an extendedKeyUsage must assert every one (or anyExtendedKeyUsage).
916
917
  // Applied to the TARGET cert (its own purposes) AND to every intermediate CA
917
918
  // (EKU chaining: an EKU on a CA constrains the purposes below it), so marking
918
- // extKeyUsage a PROCESSED critical extension is sound -- the semantic gate runs
919
+ // extKeyUsage a processed critical extension is sound. The semantic gate runs
919
920
  // wherever the extension appears, never only on the leaf. A cert with no EKU is
920
921
  // unconstrained. Returns true if the cert FAILS the required-purpose check.
921
922
  function ekuPurposeFails(cert, requiredEku, checks) {
@@ -954,7 +955,7 @@ function prepareNext(state, cert, i, checks) {
954
955
  state.workingIssuerName = cert.subject;
955
956
  updateWorkingKey(state, cert);
956
957
 
957
- // (g) name constraints absorb (AFTER this cert's own names were checked).
958
+ // (g) name constraints absorb, once this cert's own names have been checked.
958
959
  var nc;
959
960
  try { nc = decodeExt(cert, OID.nameConstraints); }
960
961
  catch (e) { checks.push({ name: "nameConstraints", ok: false, code: pathCode(e, "path/bad-name-constraints") }); return { fatal: true, error: e }; }
@@ -986,7 +987,7 @@ function prepareNext(state, cert, i, checks) {
986
987
  if (iapCritErr) return { fatal: true, error: iapCritErr };
987
988
  if (iap && iap.value < state.inhibitAnyPolicy) state.inhibitAnyPolicy = iap.value;
988
989
 
989
- // (k) basicConstraints cA gate -- the single authoritative CA check.
990
+ // (k) basicConstraints cA gate, the single authoritative CA check.
990
991
  var bc;
991
992
  try { bc = decodeExt(cert, OID.basicConstraints); }
992
993
  catch (e) { checks.push({ name: "basicConstraints", ok: false, code: "path/bad-basic-constraints" }); return { fatal: true, error: e }; }
@@ -995,7 +996,7 @@ function prepareNext(state, cert, i, checks) {
995
996
  return { fatal: true, error: E("path/not-a-ca", "intermediate certificate is not a CA (basicConstraints cA is not TRUE, RFC 5280 6.1.4(k))") };
996
997
  }
997
998
  // 4.2.1.9: a CA certificate used to validate certificate signatures MUST mark
998
- // basicConstraints critical. A non-critical cA:TRUE is non-conforming -- a
999
+ // basicConstraints critical. A non-critical cA:TRUE is non-conforming. A
999
1000
  // relying party that skips non-critical extensions would not see the CA bit.
1000
1001
  var bcCritErr = requireCriticalExt(bc, "basicConstraints", checks);
1001
1002
  if (bcCritErr) return { fatal: true, error: bcCritErr };
@@ -1022,9 +1023,9 @@ function applyPolicyMappings(state, mappings, i) {
1022
1023
  if (state.policyMapping > 0) {
1023
1024
  // 6.1.4(b)(1): for each depth-i node whose valid_policy is an ID-P that
1024
1025
  // the extension maps, REPLACE its expected_policy_set with the SET of
1025
- // subjectDomainPolicy values mapped from that ID-P (not append -- retaining
1026
- // the pre-mapping policy would let a later cert satisfy the chain by
1027
- // asserting the mapped-away policy).
1026
+ // subjectDomainPolicy values mapped from that ID-P. Appending would retain
1027
+ // the pre-mapping policy, letting a later cert satisfy the chain by
1028
+ // asserting the mapped-away policy.
1028
1029
  var mappedFrom = {}; // issuerDomainPolicy -> [subjectDomainPolicy, ...]
1029
1030
  mappings.forEach(function (m) { (mappedFrom[m.issuerDomainPolicy] = mappedFrom[m.issuerDomainPolicy] || []).push(m.subjectDomainPolicy); });
1030
1031
  var depthI = leavesAt(state.validPolicyTree, depth);
@@ -1034,7 +1035,7 @@ function applyPolicyMappings(state, mappings, i) {
1034
1035
  if (idpNodes.length) {
1035
1036
  idpNodes.forEach(function (nd) { nd.expectedPolicySet = mappedFrom[idp].slice(); });
1036
1037
  } else {
1037
- // 6.1.4(b)(1): no depth-i ID-P node, but a depth-i anyPolicy node -- GENERATE
1038
+ // 6.1.4(b)(1): with no depth-i ID-P node but a depth-i anyPolicy node, generate
1038
1039
  // the missing ID-P node under the anyPolicy node's parent with the mapped
1039
1040
  // expected set (else an anyPolicy-only CA loses the mapping).
1040
1041
  anyNodes.forEach(function (anyNode) {
@@ -1045,7 +1046,7 @@ function applyPolicyMappings(state, mappings, i) {
1045
1046
  } else {
1046
1047
  // 6.1.4(b)(2), policy_mapping == 0: delete every depth-i node whose
1047
1048
  // valid_policy is a mapped ID-P, then prune. A prior mapping in the same
1048
- // extension may have already emptied the tree -- stop if it is gone.
1049
+ // extension may have already emptied the tree, so stop if it is gone.
1049
1050
  var mappedSet = {};
1050
1051
  mappings.forEach(function (m) { mappedSet[m.issuerDomainPolicy] = true; });
1051
1052
  // Coverage residual -- unreachable today: applyPolicyMappings is invoked once per
@@ -1067,14 +1068,14 @@ function applyPolicyMappings(state, mappings, i) {
1067
1068
  }
1068
1069
  }
1069
1070
 
1070
- // policyMappings is semantically processed ONLY in the prepare-for-next step
1071
+ // policyMappings is semantically processed only in the prepare-for-next step
1071
1072
  // (sec. 6.1.4(a),(b)), which does not run for the target certificate. It is also
1072
- // SHOULD-be-non-critical (sec. 4.2.1.5), so a CRITICAL policyMappings on the target
1073
- // is both anomalous and unprocessed -- it must fail closed (sec. 6.1.5(f)) rather
1074
- // than let, e.g., a mapping to/from anyPolicy slip past the sec. 6.1.4(a) rejection
1073
+ // SHOULD-be-non-critical (sec. 4.2.1.5), so a critical policyMappings on the target
1074
+ // is both anomalous and unprocessed, and must fail closed (sec. 6.1.5(f)); accepting
1075
+ // it would let, e.g., a mapping to/from anyPolicy slip past the sec. 6.1.4(a) rejection
1075
1076
  // the intermediate path applies. (nameConstraints / inhibitAnyPolicy are also
1076
1077
  // prepare-next-only but are MUST-be-critical CA extensions, so a critical one on
1077
- // a terminal CA cert is conforming and is NOT treated as unprocessed here.)
1078
+ // a terminal CA cert is conforming and is not treated as unprocessed here.)
1078
1079
  var TARGET_UNPROCESSED_IF_CRITICAL = {};
1079
1080
  TARGET_UNPROCESSED_IF_CRITICAL[OID.policyMappings] = true;
1080
1081
  Object.freeze(TARGET_UNPROCESSED_IF_CRITICAL);
@@ -1121,13 +1122,13 @@ function validateCriticalExtensionStructure(cert) {
1121
1122
  * `userInitialPolicySet`, and `initialPermittedSubtrees` /
1122
1123
  * `initialExcludedSubtrees` -- arrays of `{ tag, base }` where `tag` is the
1123
1124
  * GeneralName tag number and `base` that form's constraint value), an
1124
- * optional `requiredEku` (key purposes -- registered OID names or dotted OID
1125
- * strings -- the target's extendedKeyUsage must assert; an absent extension
1126
- * is unrestricted, RFC 5280 4.2.1.12), and an optional `revocationChecker`.
1127
- * The value-carrying options (`time`, `maxPathCerts`, `maxPolicyNodes`, the
1128
- * subtree seeds, `userInitialPolicySet`, `requiredEku`) are validated at the
1129
- * entry point -- a mis-shaped value throws `path/bad-input` rather than
1130
- * silently not applying. Returns `{ valid, revocationChecked, anchorConstraints,
1125
+ * optional `requiredEku` (key purposes the target's extendedKeyUsage must
1126
+ * assert, given as registered OID names or dotted OID strings; an absent
1127
+ * extension is unrestricted, RFC 5280 4.2.1.12), and an optional
1128
+ * `revocationChecker`. The value-carrying options (`time`, `maxPathCerts`,
1129
+ * `maxPolicyNodes`, the subtree seeds, `userInitialPolicySet`, `requiredEku`)
1130
+ * are validated at the entry point. A mis-shaped value throws `path/bad-input`,
1131
+ * so it cannot silently go unapplied. Returns `{ valid, revocationChecked, anchorConstraints,
1131
1132
  * path, results, workingPublicKey, workingPublicKeyAlgorithm,
1132
1133
  * workingPublicKeyParameters, validPolicyTree }` where `results[i].checks`
1133
1134
  * carries a per-check reason code (`path/*`) for every step. Pure and
@@ -1142,19 +1143,18 @@ function validateCriticalExtensionStructure(cert) {
1142
1143
  * one could not be answered at all and the path fails for it. The
1143
1144
  * per-certificate `revocation` check carries the `status` it was decided on and
1144
1145
  * marks a waiver, so "checked, good" is distinguishable from "could not check,
1145
- * and you waived it" -- which a stored verdict is re-read to settle. A checker
1146
- * that THROWS is a fault in the checker rather than a status it reported, so it
1147
- * fails the path as `path/revocation-checker-error` carrying the fault whatever
1148
- * `softFail` says -- `softFail` opts into an undetermined ANSWER, and the
1149
- * built-in checkers report one as `{ status: "unknown" }` rather than throwing.
1146
+ * and you waived it", a distinction a stored verdict is re-read to settle. A
1147
+ * throw from a checker is a fault in the checker: no status was reported, so the
1148
+ * path fails as `path/revocation-checker-error` carrying the fault whatever
1149
+ * `softFail` says. `softFail` opts into an undetermined answer; the built-in
1150
+ * checkers report one as `{ status: "unknown" }` and do not throw.
1150
1151
  *
1151
1152
  * `anchorConstraints` reports what the anchor's own trust metadata decided:
1152
1153
  * the `checkedPurpose` it was judged under, and whether the `distrustAfter`
1153
1154
  * date and the `purposes` delegator map each applied. That metadata is keyed BY
1154
1155
  * key purpose, so an anchor carrying it while `opts.checkPurpose` is absent is a
1155
- * configuration fault (`path/bad-input`) rather than a constraint that silently
1156
- * does nothing -- a root distrusted years ago must not quietly validate a
1157
- * current leaf.
1156
+ * configuration fault (`path/bad-input`). A silently inert constraint would let
1157
+ * a root distrusted years ago quietly validate a current leaf.
1158
1158
  *
1159
1159
  * @example
1160
1160
  * var pair = await pki.key.generate("Ed25519");
@@ -1838,7 +1838,7 @@ function selectDelta(candidates) {
1838
1838
  * reasons, so a CA that partitions by reason code is served. A DELTA CRL is
1839
1839
  * merged onto a complete CRL it may be combined with (sec. 5.2.4 / 6.3.3(c)):
1840
1840
  * the delta is searched first, the complete CRL only if the delta left the
1841
- * status unrevoked, and `removeFromCRL` then releases the certificate -- so a
1841
+ * status unrevoked, and `removeFromCRL` then releases the certificate. A
1842
1842
  * base+delta pair reports a real verdict where the base alone could not. A
1843
1843
  * delta that merges with nothing is still consulted for revocation and still
1844
1844
  * blocks "good": merging may turn undetermined into good or revoked, never a
@@ -2288,9 +2288,9 @@ cmsVerify.setEngine({ build: build, validate: validate, toAnchor: toAnchor,
2288
2288
  * Build an OCSP-backed `RevocationChecker` for `pki.path.validate`'s
2289
2289
  * `revocationChecker` option from a set of pre-fetched OCSP responses (DER/PEM
2290
2290
  * or already-parsed). For each certificate it locates a SingleResponse whose
2291
- * CertID binds this cert's serial to its issuer -- recomputing `issuerNameHash`
2292
- * and `issuerKeyHash` under the CertID's own hashAlgorithm (SHA-1 or SHA-2), so
2293
- * a response using either matches -- confirms the responder is authorized (the
2291
+ * CertID binds this cert's serial to its issuer (recomputing `issuerNameHash`
2292
+ * and `issuerKeyHash` under the CertID's own hashAlgorithm, SHA-1 or SHA-2, so
2293
+ * a response using either matches), confirms the responder is authorized (the
2294
2294
  * issuing CA directly, or a valid CA-issued delegate bearing both id-kp-OCSPSigning
2295
2295
  * and id-pkix-ocsp-nocheck), verifies the response signature over
2296
2296
  * `tbsResponseDataBytes`, checks currency
@@ -2368,10 +2368,10 @@ function ocspChecker(responses) {
2368
2368
  * @related pki.ocsp.verify, pki.path.ocspChecker
2369
2369
  *
2370
2370
  * Verify a single OCSP response for one certificate
2371
- * against its already-parsed issuer certificate at `time` -- the lower-level primitive
2372
- * `pki.ocsp.verify` composes after parsing its inputs (most callers want that
2373
- * ergonomic entry, which also handles DER/PEM decoding and request-nonce
2374
- * matching). It runs the EXACT SAME gates the path validator's `ocspChecker`
2371
+ * against its already-parsed issuer certificate at `time`. This is the lower-level
2372
+ * primitive `pki.ocsp.verify` composes after parsing its inputs (most callers want
2373
+ * that ergonomic entry, which also handles DER/PEM decoding and request-nonce
2374
+ * matching). It runs exactly the gates the path validator's `ocspChecker`
2375
2375
  * does: it locates the SingleResponse whose CertID binds this cert's serial to
2376
2376
  * its issuer (recomputing `issuerNameHash`/`issuerKeyHash` under the CertID's
2377
2377
  * own hashAlgorithm), confirms the responder is authorized (the issuing CA
@@ -2818,20 +2818,21 @@ async function _fetchAiaIssuers(current, aia) {
2818
2818
  *
2819
2819
  * Candidate issuers are matched by RFC 5280 7.1 name chaining, prioritized by the RFC 4158 3.5
2820
2820
  * heuristics (a subjectKeyIdentifier/authorityKeyIdentifier match, an anchor-adjacent issuer,
2821
- * CA + keyCertSign, validity at the check time -- all hints, never filters), and searched
2822
- * depth-first with backtracking: the first ordered path that `pki.path.validate` accepts wins. A
2823
- * name or key-identifier match is only an ordering hint; every accept flows through `validate`,
2824
- * so `build` never weakens or duplicates a 6.1 check. The search over the untrusted pool is
2825
- * bounded -- a depth cap on chain length, a total-work cap on candidate expansions, and a
2826
- * visited-set keyed on the (subject, subjectAltName, public key) tuple -- so a cross-certificate
2827
- * cycle or Bridge-CA fan-out terminates deterministically rather than growing without bound.
2821
+ * CA + keyCertSign, validity at the check time; each orders the search without excluding a
2822
+ * candidate), and searched depth-first with backtracking: the first ordered path that
2823
+ * `pki.path.validate` accepts wins. A name or key-identifier match is only an ordering hint;
2824
+ * every accept flows through `validate`, so `build` never weakens or duplicates a 6.1 check. The
2825
+ * search over the untrusted pool is bounded: a depth cap on chain length, a total-work cap on
2826
+ * candidate expansions, and a visited-set keyed on the (subject, subjectAltName, public key)
2827
+ * tuple. A cross-certificate cycle or Bridge-CA fan-out therefore terminates deterministically;
2828
+ * the search cannot grow without bound.
2828
2829
  *
2829
2830
  * `leaf` is a DER `Buffer`, a PEM string, or an already-parsed `pki.schema.x509` object. Returns
2830
2831
  * `{ valid, path, trustAnchor, result, candidatesConsidered }`, where `path` is the ordered array
2831
2832
  * `validate` consumes (anchor-proximal first, leaf last, the anchor excluded). Fail-closed: bad
2832
2833
  * options throw `path/bad-input`; no chain to any anchor throws `path/no-path`; chains that
2833
2834
  * assemble but none validate return `{ valid:false }` with the best failing `validate` result;
2834
- * the search bound throws `path/build-limit`. By default `build` is OFFLINE (zero network) -- supply
2835
+ * the search bound throws `path/build-limit`. By default `build` is OFFLINE (zero network), so supply
2835
2836
  * intermediates in `opts.candidates`. Set `opts.fetchAia: true` to opt in to fetching a MISSING intermediate
2836
2837
  * from a certificate's Authority Information Access `caIssuers` URL (RFC 5280 sec. 4.2.2.1) over
2837
2838
  * `pki.transport`: the fetch triggers only on a pool miss, every fetched certificate is UNTRUSTED pool material
@@ -2840,7 +2841,7 @@ async function _fetchAiaIssuers(current, aia) {
2840
2841
  * buildable path), a per-cert URL cap, a build-wide URL dedupe, a response size + certificate-count cap, and no
2841
2842
  * redirect following; every fetch fault is a silent skip. `aiaFetches` reports how many network GETs the build
2842
2843
  * performed (`0` when `fetchAia` is off). NOTE: with `opts.validate:false` (pure-builder mode) a fetched cert is
2843
- * returned unvalidated, identical to a static candidate -- the "flows through validate" guarantee needs validation on.
2844
+ * returned unvalidated, identical to a static candidate; the "flows through validate" guarantee needs validation on.
2844
2845
  *
2845
2846
  * @opts candidates The untrusted candidate CA pool (array of DER/PEM/parsed certs; alias `intermediates`).
2846
2847
  * @opts trustAnchors The trust store (non-empty array of `{ name, publicKey, algorithm }` tuples or self-signed root certificates).
@@ -2848,10 +2849,10 @@ async function _fetchAiaIssuers(current, aia) {
2848
2849
  * @opts maxDepth Chain-length depth cap (default `C.LIMITS.PATH_BUILD_MAX_DEPTH`).
2849
2850
  * @opts maxCandidatesConsidered Total-work cap on candidate expansions (default `C.LIMITS.PATH_BUILD_MAX_CANDIDATES`).
2850
2851
  * @opts validate `false` returns the ordered path without validating (pure-builder mode; default `true`).
2851
- * @opts fetchAia `true` opts in to AIA caIssuers network fetching of a missing intermediate (default `false` -- fully offline). Off unless set; when set, a fetch runs only on a pool miss for a non-anchor-adjacent cert, and (with validation on) every fetched cert still flows through `validate`.
2852
- * @opts transport The injectable transport seam (`fn(request) -> Promise<{ status, headers, body }>`); tests drive the fetch offline. With none, the default `pki.transport.https` is used, which fails closed unless `opts.tls` carries trust. SSRF: for a caIssuers URL with a DNS hostname, a custom transport is used ONLY if it declares `fn.blocksPrivateAddresses = true` -- vouching it refuses (and pins) a resolved private / loopback / link-local / special-use address, as the default transport does. Without that marker a DNS-name AIA URL is fail-closed (skipped) and only an IP-literal URL (validated up front) is fetched; set the marker on your transport when it filters resolved addresses.
2853
- * @opts tls The TLS trust for the AIA HTTPS host (`{ anchors, useSystemStore, ... }`) -- DISTINCT from `opts.trustAnchors` (the PKI trust store the path validates against). The default transport refuses an unpinned server.
2854
- * @opts maxAiaFetches Total AIA network GET budget across the whole build (default `C.LIMITS.PATH_AIA_MAX_FETCHES`); on reaching it the builder stops fetching (a silent cap), never a throw -- a fetch bound never denies a path the pool could build.
2852
+ * @opts fetchAia `true` opts in to AIA caIssuers network fetching of a missing intermediate (default `false`, fully offline). Off unless set; when set, a fetch runs only on a pool miss for a non-anchor-adjacent cert, and (with validation on) every fetched cert still flows through `validate`.
2853
+ * @opts transport The injectable transport seam (`fn(request) -> Promise<{ status, headers, body }>`); tests drive the fetch offline. With none, the default `pki.transport.https` is used, which fails closed unless `opts.tls` carries trust. SSRF: for a caIssuers URL with a DNS hostname, a custom transport is used only if it declares `fn.blocksPrivateAddresses = true`, which vouches that it refuses (and pins) a resolved private / loopback / link-local / special-use address, as the default transport does. Without that marker a DNS-name AIA URL is fail-closed (skipped) and only an IP-literal URL (validated up front) is fetched; set the marker on your transport when it filters resolved addresses.
2854
+ * @opts tls The TLS trust for the AIA HTTPS host (`{ anchors, useSystemStore, ... }`). This is distinct from `opts.trustAnchors`, the PKI trust store the path validates against. The default transport refuses an unpinned server.
2855
+ * @opts maxAiaFetches Total AIA network GET budget across the whole build (default `C.LIMITS.PATH_AIA_MAX_FETCHES`); on reaching it the builder stops fetching (a silent cap) and does not throw, so a fetch bound cannot deny a path the pool could build.
2855
2856
  * @opts maxAiaPerCert Cap on caIssuers URLs tried per certificate (default `C.LIMITS.PATH_AIA_MAX_PER_CERT`).
2856
2857
  * @opts aiaTimeout Per-fetch timeout in ms, forwarded to the transport.
2857
2858
  * @opts maxResponseBytes Per-fetch response size cap, forwarded to the transport (tightenable downward only).