@blamejs/pki 0.8.52 → 0.8.54

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.
@@ -945,16 +945,20 @@ function _flagSet(callerValue, anchorFlags, name) {
945
945
  return anchorFlags !== null && anchorFlags[name] === true;
946
946
  }
947
947
 
948
- function initialize(certs, params, seeds, anchor, ta) {
948
+ /** @internal `policySwitches` carries the three sec. 6.1.1 inputs as the door read and typed them.
949
+ * They are not read off `params` here: normalizing an anchor runs caller accessors for its name,
950
+ * key and algorithm, so an options object the caller still holds can carry a different value by
951
+ * the time this runs, and the state would be built from a value nothing typed. */
952
+ function initialize(certs, params, seeds, anchor, ta, policySwitches) {
949
953
  var n = certs.length;
950
- var state = _initialState(certs, params, seeds, anchor, n, ta);
954
+ var state = _initialState(certs, params, seeds, anchor, n, ta, policySwitches);
951
955
  /** @internal The root is the depth-zero anyPolicy node, and it is indexed like any other, so the
952
956
  * "is there a node at this depth for this policy" lookup answers for depth zero too. */
953
957
  policyLayer(state, 0)[OID.anyPolicy] = state.validPolicyGraph;
954
958
  return state;
955
959
  }
956
960
 
957
- function _initialState(certs, params, seeds, anchor, n, ta) {
961
+ function _initialState(certs, params, seeds, anchor, n, ta, policySwitches) {
958
962
  /** @internal The anchor's constraints are read HERE rather than merged into a copy of `params`,
959
963
  * because a copy answers only for the own properties the copy loop enumerated. A caller option
960
964
  * defined non-enumerably would be dropped by the copy and the anchor would then WIDEN the
@@ -969,9 +973,9 @@ function _initialState(certs, params, seeds, anchor, n, ta) {
969
973
  minRsaModulusBits: params.minRsaModulusBits !== undefined ? params.minRsaModulusBits : constants.LIMITS.RSA_MIN_MODULUS_BITS,
970
974
  permittedGenerations: intrinsic.filter(seeds.permittedGenerations, function (g) { return g.length > 0; }),
971
975
  excludedSubtrees: seeds.excluded,
972
- explicitPolicy: _flagSet(params.initialExplicitPolicy, ta.policyFlags, "requireExplicitPolicy") ? 0 : n + 1,
973
- inhibitAnyPolicy: _flagSet(params.initialAnyPolicyInhibit, ta.policyFlags, "inhibitAnyPolicy") ? 0 : n + 1,
974
- policyMapping: _flagSet(params.initialPolicyMappingInhibit, ta.policyFlags, "inhibitPolicyMapping") ? 0 : n + 1,
976
+ explicitPolicy: _flagSet(policySwitches.initialExplicitPolicy, ta.policyFlags, "requireExplicitPolicy") ? 0 : n + 1,
977
+ inhibitAnyPolicy: _flagSet(policySwitches.initialAnyPolicyInhibit, ta.policyFlags, "inhibitAnyPolicy") ? 0 : n + 1,
978
+ policyMapping: _flagSet(policySwitches.initialPolicyMappingInhibit, ta.policyFlags, "inhibitPolicyMapping") ? 0 : n + 1,
975
979
  workingPublicKeyAlgorithm: anchor.algorithm,
976
980
  workingPublicKey: anchor.publicKey,
977
981
  workingPublicKeyParameters: anchor.parameters || null,
@@ -1286,7 +1290,12 @@ function _revocationRow(row, rv) {
1286
1290
  * The value-carrying options (`time`, `maxPathCerts`,
1287
1291
  * `maxPolicyNodes`, `minRsaModulusBits`, the subtree seeds, `userInitialPolicySet`, `requiredEku`)
1288
1292
  * are validated at the entry point. A mis-shaped value throws `path/bad-input`,
1289
- * so it cannot silently go unapplied. Returns `{ valid, revocationChecked, anchorConstraints,
1293
+ * so it cannot silently go unapplied. `requireRevocation` makes the revocation
1294
+ * determination mandatory, failing the path with `path/revocation-undetermined`
1295
+ * where it could not be made. It and the three sec. 6.1.1 policy inputs
1296
+ * (`initialExplicitPolicy`, `initialAnyPolicyInhibit`,
1297
+ * `initialPolicyMappingInhibit`) each take `true`, `false`, `null` or an absent
1298
+ * option, and any other value throws `path/bad-input` for the same reason. Returns `{ valid, revocationChecked, anchorConstraints,
1290
1299
  * path, results, workingPublicKey, workingPublicKeyAlgorithm,
1291
1300
  * workingPublicKeyParameters, validPolicyGraph, userConstrainedPolicySet }` where `results[i].checks`
1292
1301
  * carries a per-check reason code (`path/*`) for every step. Pure and
@@ -1473,6 +1482,23 @@ async function _validate(path, opts, run, scope) {
1473
1482
  var certs = path.map(function (c, ci) { return coerceCert(c, "validate: path[" + ci + "]"); });
1474
1483
  var n = certs.length;
1475
1484
  if (n < 1) throw E("path/empty-path", "validate: the certification path is empty");
1485
+ /** @internal The four switches that turn a requirement on, each read as the boolean it documents
1486
+ * and read HERE, before any anchor is normalized. An anchor's own copies of the three policy
1487
+ * flags are typed where they are copied, and `_flagSet` compares the caller's value with the
1488
+ * anchor's, so typing one side and not the other would accept at the door what the comparison
1489
+ * refuses. Normalizing an anchor reads its name, key and algorithm, and those reads run caller
1490
+ * code when the anchor carries accessors: the typed values below are what every anchor attempt
1491
+ * is handed, so a requirement cannot be turned off between the attempts. */
1492
+ var policySwitches = {
1493
+ requireRevocation: guard.identifier.booleanOption(opts.requireRevocation, E,
1494
+ "path/bad-input", "validate: opts.requireRevocation"),
1495
+ initialExplicitPolicy: guard.identifier.booleanOption(opts.initialExplicitPolicy, E,
1496
+ "path/bad-input", "validate: opts.initialExplicitPolicy"),
1497
+ initialAnyPolicyInhibit: guard.identifier.booleanOption(opts.initialAnyPolicyInhibit, E,
1498
+ "path/bad-input", "validate: opts.initialAnyPolicyInhibit"),
1499
+ initialPolicyMappingInhibit: guard.identifier.booleanOption(opts.initialPolicyMappingInhibit, E,
1500
+ "path/bad-input", "validate: opts.initialPolicyMappingInhibit"),
1501
+ };
1476
1502
  /** @internal Captured before any anchor is normalized: an anchor field may be an accessor, and
1477
1503
  * that getter runs caller code which could otherwise clear these lists first. */
1478
1504
  var callerPermitted = checkedSubtreeSeeds(opts.initialPermittedSubtrees, "initialPermittedSubtrees");
@@ -1506,6 +1532,14 @@ async function _validate(path, opts, run, scope) {
1506
1532
  * question the caller asked. Copying `opts.identity` across would have each attempt read
1507
1533
  * the caller's own list again, after the awaits the earlier attempts spent. */
1508
1534
  if (identityRefs !== null) o.identity = identityRefs;
1535
+ /** @internal The four switches as this call typed them, carried into every attempt rather
1536
+ * than copied from an options object the caller still holds: the tuples above were built by
1537
+ * reading each anchor, which runs the caller's accessors, so a value copied here would be
1538
+ * whatever those left behind. */
1539
+ o.requireRevocation = policySwitches.requireRevocation;
1540
+ o.initialExplicitPolicy = policySwitches.initialExplicitPolicy;
1541
+ o.initialAnyPolicyInhibit = policySwitches.initialAnyPolicyInhibit;
1542
+ o.initialPolicyMappingInhibit = policySwitches.initialPolicyMappingInhibit;
1509
1543
  return o;
1510
1544
  };
1511
1545
  var lastMatched = null, anyMatched = false;
@@ -1538,7 +1572,7 @@ async function _validate(path, opts, run, scope) {
1538
1572
  var requiredEku = purposeOpts.requiredEku;
1539
1573
  var checkPurpose = purposeOpts.checkPurpose;
1540
1574
 
1541
- var state = initialize(certs, opts, seeds, anchor, taConstraints);
1575
+ var state = initialize(certs, opts, seeds, anchor, taConstraints, policySwitches);
1542
1576
  state._n = n;
1543
1577
  state.requiredEku = requiredEku;
1544
1578
  /** @internal The verifier comes from the per-call scope the door built, never from `opts`: the option
@@ -1548,7 +1582,7 @@ async function _validate(path, opts, run, scope) {
1548
1582
  var verifierVerify = scope.verify;
1549
1583
  var revocationChecker = opts.revocationChecker || null;
1550
1584
  var softFail = opts.softFail === true;
1551
- var requireRevocation = opts.requireRevocation === true;
1585
+ var requireRevocation = policySwitches.requireRevocation;
1552
1586
  var failed = false;
1553
1587
  var revocationRan = false, revocationWaived = false, revocationUndetermined = false;
1554
1588
  var prefixAuthentic = true;
@@ -3330,7 +3364,7 @@ async function _fetchAiaIssuers(current, aia) {
3330
3364
  * @opts direction Search direction: `"forward"` (default, from the leaf toward an anchor), `"reverse"` (from an anchor toward the leaf, RFC 4158 sec. 3.1, narrower when the anchor set is small and the leaf fans out), or `"auto"` (pick by first-hop fan-out). Reverse building is pool-only and does not combine with `fetchAia`. Every direction hands each assembled path to `validate`, so the direction changes only search order, never which paths are accepted.
3331
3365
  * @opts maxDepth Chain-length depth cap (default `C.LIMITS.PATH_BUILD_MAX_DEPTH`).
3332
3366
  * @opts maxCandidatesConsidered Total-work cap on candidate expansions (default `C.LIMITS.PATH_BUILD_MAX_CANDIDATES`).
3333
- * @opts validate `false` returns the ordered path without validating (pure-builder mode; default `true`).
3367
+ * @opts validate `false` returns the ordered path without validating (pure-builder mode; default `true`). It takes `true`, `false`, `null` or an absent option, and any other value is refused `path/bad-input` at the door rather than read as on.
3334
3368
  * @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`.
3335
3369
  * @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.
3336
3370
  * @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.
@@ -3338,7 +3372,7 @@ async function _fetchAiaIssuers(current, aia) {
3338
3372
  * @opts maxAiaPerCert Cap on caIssuers URLs tried per certificate (default `C.LIMITS.PATH_AIA_MAX_PER_CERT`).
3339
3373
  * @opts aiaTimeout Per-fetch timeout in ms, forwarded to the transport.
3340
3374
  * @opts maxResponseBytes Per-fetch response size cap, forwarded to the transport (tightenable downward only).
3341
- * @opts (validate options) Every `pki.path.validate` option (`requiredEku`, `revocationChecker`, `checkPurpose`, the initial policy inputs, ...) is forwarded unchanged. When `userInitialPolicySet` is supplied, build returns the accepted path whose user-constrained policy set best satisfies it (RFC 4158 sec. 4 forward policy chaining) rather than the first accepted path; `validate` still gates every path, so this changes only which valid path is chosen. The ranking explores more of the candidate graph than a first-accept build, so it is bounded by `maxCandidatesConsidered` like the rest of the search. Reaching that cap means stop looking, not nothing found: if a valid path was already accepted, build returns the best-policy one of those; it reaches `path/build-limit` only when the cap is hit before any valid path is accepted. An `anyPolicy` entry makes the set unconstrained, so ranking is skipped and the first accepted path is returned.
3375
+ * @opts (validate options) Every `pki.path.validate` option (`requiredEku`, `revocationChecker`, `checkPurpose`, the initial policy inputs, ...) is forwarded unchanged. `requireRevocation` and the three policy inputs are typed at this door as well as at `validate`'s, because a build reaches an AIA fetch before it reaches `validate` and a build that assembles no path never calls it. When `userInitialPolicySet` is supplied, build returns the accepted path whose user-constrained policy set best satisfies it (RFC 4158 sec. 4 forward policy chaining) rather than the first accepted path; `validate` still gates every path, so this changes only which valid path is chosen. The ranking explores more of the candidate graph than a first-accept build, so it is bounded by `maxCandidatesConsidered` like the rest of the search. Reaching that cap means stop looking, not nothing found: if a valid path was already accepted, build returns the best-policy one of those; it reaches `path/build-limit` only when the cap is hit before any valid path is accepted. An `anyPolicy` entry makes the set unconstrained, so ranking is skipped and the first accepted path is returned.
3342
3376
  * @example
3343
3377
  * var pair = await pki.key.generate("Ed25519");
3344
3378
  * var pemString = await pki.x509.sign({ subject: "Example Root", subjectPublicKey: await pki.key.export(pair.publicKey),
@@ -3372,6 +3406,38 @@ async function build(leaf, opts) {
3372
3406
  * second time and an accessor can answer differently on each read. */
3373
3407
  var checkedTransport = guard.identifier.assertCallableOption(opts, "transport", E, "path/bad-input",
3374
3408
  "build: opts.transport", "(request) -> Promise<{ status, headers, body }>");
3409
+ /** @internal The switches build forwards to `validate` are typed at THIS door too, because build
3410
+ * reaches the network before it reaches `validate`: with `fetchAia` on it fetches AIA issuers
3411
+ * while assembling a chain, and a build that assembles none returns `path/no-path` with no
3412
+ * `validate` call having read them. A door that only the successful route passes through is not
3413
+ * a door. */
3414
+ var FORWARDED_SWITCHES = ["requireRevocation", "initialExplicitPolicy", "initialAnyPolicyInhibit",
3415
+ "initialPolicyMappingInhibit"];
3416
+ var forwardedSwitches = intrinsic.create(null);
3417
+ intrinsic.forEach(FORWARDED_SWITCHES, function (k) {
3418
+ /** @internal Every one of the four is typed and recorded, including the ones the caller left
3419
+ * out. An absent switch recorded as `false` is the same question answered the same way, and
3420
+ * recording it is what stops a later read from finding one that was not there at this door:
3421
+ * reading each anchor runs the caller's accessors, and an accessor can add an option. */
3422
+ forwardedSwitches[k] = guard.identifier.booleanOption(opts[k], E, "path/bad-input", "build: opts." + k);
3423
+ });
3424
+ /** @internal `validate` defaults ON, so absence means on here and the off form is `false`. A
3425
+ * value outside that set is a caller asking for pure-builder mode in a spelling this verb does
3426
+ * not read, and running the validation they asked to skip is as silent as skipping it. */
3427
+ var doValidateOpt = (opts.validate === undefined || opts.validate === null) ? true
3428
+ : guard.identifier.booleanOption(opts.validate, E, "path/bad-input", "build: opts.validate");
3429
+ /** @internal Pure-builder mode runs no validation, so a requirement asked for alongside it is a
3430
+ * requirement nothing will apply. The combination is refused rather than answered with an
3431
+ * ordered path, the same way an asked-for check that its input cannot satisfy is refused. */
3432
+ if (!doValidateOpt) {
3433
+ intrinsic.forEach(FORWARDED_SWITCHES, function (k) {
3434
+ if (forwardedSwitches[k] === true) {
3435
+ throw E("path/bad-input", "build: opts." + k + " asks for a check that only validation " +
3436
+ "applies, and opts.validate is false, which returns the ordered path without validating; " +
3437
+ "drop one of the two");
3438
+ }
3439
+ });
3440
+ }
3375
3441
  var leafCert;
3376
3442
  try { leafCert = coerceCert(leaf); }
3377
3443
  catch (e) { throw E("path/bad-input", "build: the leaf certificate did not parse", e); }
@@ -3409,7 +3475,7 @@ async function build(leaf, opts) {
3409
3475
  // allow:guard-shape-reinlined -- opts.maxDepth here is the path-building chain depth (capped via guard.limits.cap against the PATH_BUILD_MAX_DEPTH ceiling), a different concept from the decode-recursion depth guard.limits.depthCap enforces
3410
3476
  var maxDepth = guard.limits.cap(opts.maxDepth, "build: opts.maxDepth", Math.min(constants.LIMITS.PATH_BUILD_MAX_DEPTH, depthCeiling), { E: E, code: "path/bad-input", min: 0, max: depthCeiling });
3411
3477
  var maxConsidered = guard.limits.cap(opts.maxCandidatesConsidered, "build: opts.maxCandidatesConsidered", poolCeiling, { E: E, code: "path/bad-input", min: 1 });
3412
- var doValidate = opts.validate !== false;
3478
+ var doValidate = doValidateOpt;
3413
3479
 
3414
3480
  var direction = opts.direction !== undefined ? opts.direction : "forward";
3415
3481
  if (direction !== "forward" && direction !== "reverse" && direction !== "auto") {
@@ -3448,6 +3514,10 @@ async function build(leaf, opts) {
3448
3514
  var forwarded = Object.create(null);
3449
3515
  intrinsic.forEach(intrinsic.keys(_VALIDATE_OPTS), function (k) {
3450
3516
  if (intrinsic.hasOwn(BUILD_ONLY_OPT, k)) return;
3517
+ /** @internal A switch this door typed is forwarded as the value it typed. Reading it off the
3518
+ * options object again would ask a second time, and normalizing an anchor between the two runs
3519
+ * caller accessors, so the value `validate` is handed need not be the one that passed here. */
3520
+ if (intrinsic.hasOwn(forwardedSwitches, k)) { forwarded[k] = forwardedSwitches[k]; return; }
3451
3521
  if (k in opts) forwarded[k] = opts[k];
3452
3522
  });
3453
3523
  if (buildPermRaw != null) forwarded.initialPermittedSubtrees = buildPermSeeds;
@@ -192,9 +192,7 @@ function _no(why) { return { url: null, why: why }; }
192
192
  * under a `=== true` test, and the requests would go out carrying no nonce with nothing said. An
193
193
  * absent value is the documented default; anything else is a configuration fault. */
194
194
  function _booleanOption(value, label) {
195
- if (value === undefined || value === null) return false;
196
- if (value === true || value === false) return value;
197
- throw E("path/bad-input", label + " must be true or false, got " + guard.text.showValue(value));
195
+ return guard.identifier.booleanOption(value, E, "path/bad-input", label);
198
196
  }
199
197
 
200
198
  /** @internal Whether this transport says it filters the address it resolved. A transport is a
package/lib/scep.js CHANGED
@@ -849,8 +849,8 @@ function _assertStrongProfile(caps, profile) {
849
849
  *
850
850
  * @opts
851
851
  * - `transport` -- an injected transport (defaults to `pki.transport.https`); the request is a GET with no body.
852
- * - `expectSCEPStandard` -- refuse (`scep/weak-profile`) if the response does not advertise `SCEPStandard`.
853
- * - `requireStrongProfile` -- refuse unless AES + SHA-256 + POSTPKIOperation are all advertised.
852
+ * - `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`.
853
+ * - `requireStrongProfile` -- refuse unless AES + SHA-256 + POSTPKIOperation are all advertised. Read the same way.
854
854
  * - `timeout` / `maxResponseBytes` / `maxRedirects` / `tls` / `allowCrossOriginRedirect` / `proxy` -- transport bounds (`proxy` reaches the CA through a forward HTTP proxy; see pki.transport).
855
855
  * @example
856
856
  * // requires: scepTransport -- an injected transport (omit to use pki.transport.https)
@@ -861,7 +861,12 @@ async function getCACaps(baseUrl, opts) {
861
861
  opts = guard.identifier.optionsObject(opts, _err, "scep/bad-input", "pki.scep.getCACaps options");
862
862
  guard.identifier.assertKnownKeys(opts, _CACAPS_KEYS, _err, "scep/bad-input", "pki.scep.getCACaps has an unknown option: ");
863
863
  var checkedTransport = _assertTransport(opts);
864
- var profile = { expectSCEPStandard: !!opts.expectSCEPStandard, requireStrongProfile: !!opts.requireStrongProfile };
864
+ var profile = {
865
+ expectSCEPStandard: guard.identifier.booleanOption(opts.expectSCEPStandard, _err,
866
+ "scep/bad-input", "pki.scep.getCACaps opts.expectSCEPStandard"),
867
+ requireStrongProfile: guard.identifier.booleanOption(opts.requireStrongProfile, _err,
868
+ "scep/bad-input", "pki.scep.getCACaps opts.requireStrongProfile"),
869
+ };
865
870
  var res = await _client("GET", _scepUrl(baseUrl, "GetCACaps"), null, {}, opts, checkedTransport);
866
871
  if (res.status === 200) {
867
872
  var caps = parseCapabilities(_requireOk(res, [CT_PLAIN], "GetCACaps"));
package/lib/schema-all.js CHANGED
@@ -368,7 +368,8 @@ module.exports = {
368
368
  },
369
369
  sec1: { parse: sec1.parse, encode: sec1.encode, pemDecode: sec1.pemDecode, pemEncode: sec1.pemEncode },
370
370
  pkcs12: { parse: pkcs12.parse, pemDecode: pkcs12.pemDecode, pemEncode: pkcs12.pemEncode },
371
- cms: { parse: cms.parse, pemDecode: cms.pemDecode, pemEncode: cms.pemEncode },
371
+ cms: { parse: cms.parse, parseAlgorithmProtection: cms.parseAlgorithmProtection,
372
+ pemDecode: cms.pemDecode, pemEncode: cms.pemEncode },
372
373
  ocsp: { parseRequest: ocsp.parseRequest, parseResponse: ocsp.parseResponse, pemDecode: ocsp.pemDecode, pemEncode: ocsp.pemEncode },
373
374
  tsp: { parse: tsp.parse, parseResponse: tsp.parseResponse, parseRequest: tsp.parseRequest, parseTstInfo: tsp.parseTstInfo, parseToken: tsp.parseToken, pemDecode: tsp.pemDecode, pemEncode: tsp.pemEncode },
374
375
  crmf: { parse: crmf.parse, pemDecode: crmf.pemDecode, pemEncode: crmf.pemEncode,