@blamejs/pki 0.8.31 → 0.8.49

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 (87) hide show
  1. package/CHANGELOG.md +401 -0
  2. package/README.md +66 -15
  3. package/bin/pki.js +716 -30
  4. package/examples/issue-a-certificate.js +90 -0
  5. package/examples/post-quantum.js +66 -0
  6. package/examples/read-a-certificate.js +56 -0
  7. package/examples/revoke-and-check.js +86 -0
  8. package/examples/sign-and-verify-cms.js +63 -0
  9. package/index.js +18 -5
  10. package/lib/acme.js +223 -74
  11. package/lib/alt-sig.js +441 -0
  12. package/lib/asn1-der.js +48 -25
  13. package/lib/attrcert-sign.js +27 -19
  14. package/lib/cbor-det.js +24 -14
  15. package/lib/cmc-build.js +9 -2
  16. package/lib/cmc-verify.js +14 -3
  17. package/lib/cmp-build.js +17 -5
  18. package/lib/cmp-session.js +24 -11
  19. package/lib/cmp-verify.js +28 -19
  20. package/lib/cms-compress.js +3 -1
  21. package/lib/cms-decrypt.js +102 -7
  22. package/lib/cms-encrypt.js +59 -7
  23. package/lib/cms-sign.js +31 -9
  24. package/lib/cms-verify.js +22 -18
  25. package/lib/composite-kem.js +30 -18
  26. package/lib/composite-sig.js +29 -14
  27. package/lib/constants.js +26 -0
  28. package/lib/crl-sign.js +111 -19
  29. package/lib/crmf-sign.js +167 -15
  30. package/lib/csr-sign.js +117 -6
  31. package/lib/ct.js +1008 -58
  32. package/lib/est.js +29 -16
  33. package/lib/framework-error.js +12 -0
  34. package/lib/guard-async.js +1 -1
  35. package/lib/guard-bytes.js +103 -20
  36. package/lib/guard-crypto.js +205 -3
  37. package/lib/guard-encoding.js +6 -1
  38. package/lib/guard-identifier.js +55 -8
  39. package/lib/guard-intrinsic.js +43 -0
  40. package/lib/guard-json.js +122 -9
  41. package/lib/guard-limits.js +12 -1
  42. package/lib/guard-list.js +63 -8
  43. package/lib/guard-name.js +167 -20
  44. package/lib/guard-range.js +43 -5
  45. package/lib/guard-text.js +41 -0
  46. package/lib/guard-verdict.js +39 -8
  47. package/lib/hpke.js +688 -42
  48. package/lib/http-transport.js +156 -4
  49. package/lib/identity-match.js +4 -4
  50. package/lib/inspect.js +33 -6
  51. package/lib/jose.js +93 -25
  52. package/lib/key.js +18 -13
  53. package/lib/lint.js +165 -1
  54. package/lib/merkle.js +233 -27
  55. package/lib/ocsp.js +27 -14
  56. package/lib/oid.js +14 -4
  57. package/lib/path-validate.js +143 -61
  58. package/lib/pkcs12-build.js +7 -2
  59. package/lib/pki-build.js +31 -7
  60. package/lib/possession.js +575 -0
  61. package/lib/related-cert.js +708 -0
  62. package/lib/revocation-fetch.js +6 -6
  63. package/lib/scep.js +15 -5
  64. package/lib/schema-c509.js +5 -2
  65. package/lib/schema-cmc.js +1 -1
  66. package/lib/schema-cmp.js +8 -8
  67. package/lib/schema-cms.js +2 -15
  68. package/lib/schema-csr.js +69 -0
  69. package/lib/schema-engine.js +10 -3
  70. package/lib/schema-pkix.js +228 -2
  71. package/lib/schema-tsp.js +7 -6
  72. package/lib/sign-scheme.js +22 -9
  73. package/lib/sigstore.js +684 -168
  74. package/lib/sleep.js +8 -1
  75. package/lib/smime.js +22 -1
  76. package/lib/tlog.js +1235 -0
  77. package/lib/tls-cert-compress.js +25 -8
  78. package/lib/tsp-sign.js +120 -35
  79. package/lib/tuf.js +1395 -0
  80. package/lib/validator-tpm.js +4 -1
  81. package/lib/verify-seam.js +7 -1
  82. package/lib/webauthn-mds.js +41 -4
  83. package/lib/webauthn.js +43 -25
  84. package/lib/webcrypto.js +48 -46
  85. package/lib/x509-sign.js +225 -19
  86. package/package.json +2 -1
  87. package/sbom.cdx.json +6 -6
package/lib/ocsp.js CHANGED
@@ -26,6 +26,19 @@
26
26
  var nodeCrypto = require("crypto");
27
27
  var asn1 = require("./asn1-der");
28
28
  var pkiBuild = require("./pki-build");
29
+ var _ocspIntrinsic = require("./guard-intrinsic");
30
+ /** @internal The promise a signature self-check is awaited through, built from the constructor captured
31
+ * at load. `Promise.resolve` builds from its RECEIVER, so reading the global binding at the call hands
32
+ * the construction to whatever that binding holds, and one settling without awaiting its argument runs
33
+ * the continuation before the check has finished. */
34
+ var _Promise = _ocspIntrinsic.Promise;
35
+ var _promiseResolve = _ocspIntrinsic.uncurry(_ocspIntrinsic.promiseResolve);
36
+ /** @internal A refusal returned as a REJECTED promise is a verdict too: read from the global binding, a
37
+ * replacement whose `reject` resolves instead turns each `ocsp/bad-input` below into a pass. */
38
+ var _promiseReject = _ocspIntrinsic.uncurry(_ocspIntrinsic.promiseReject);
39
+ /** @internal The CertID hashes bind a response to the certificate it answers for, and they are awaited
40
+ * as a pair, so a replaced `all` decides what that pair contains. */
41
+ var _promiseAll = _ocspIntrinsic.uncurry(_ocspIntrinsic.promiseAll);
29
42
  var oid = require("./oid");
30
43
  var x509 = require("./schema-x509");
31
44
  var ocspSchema = require("./schema-ocsp");
@@ -100,7 +113,7 @@ var REQUESTER_NONCE_FLOOR = 32, RESPONDER_NONCE_FLOOR = 1;
100
113
  function _buildCertID(cert, issuer, hashName) {
101
114
  var wc = HASH_WC[guard.text.keyOf(hashName)];
102
115
  if (!wc) throw _err("ocsp/bad-input", "unsupported CertID hashAlgorithm " + guard.text.showValue(hashName) + " (sha1 / sha256 / sha384 / sha512)");
103
- return Promise.all([
116
+ return _promiseAll(_Promise, [
104
117
  _digest(wc, issuer.subject.bytes),
105
118
  _digest(wc, _keyValue(issuer.subjectPublicKeyInfo.bytes)),
106
119
  ]).then(function (hashes) {
@@ -189,7 +202,7 @@ function _buildRequest(query, opts) {
189
202
  requestorNameDer = signerCert.subject.bytes;
190
203
  }
191
204
 
192
- return Promise.all(queries.map(function (q) {
205
+ return _promiseAll(_Promise, queries.map(function (q) {
193
206
  if (!q || typeof q !== "object") throw _err("ocsp/bad-input", "each query must be { cert, issuer }");
194
207
  guard.identifier.assertKnownKeys(q, _QUERY_KEYS, _err, "ocsp/bad-input", "unknown query field (a query is { cert, issuer, singleRequestExtensions? }): ");
195
208
  if (q.cert == null || q.issuer == null) throw _err("ocsp/bad-input", "each query must be { cert, issuer }");
@@ -227,7 +240,7 @@ function _buildRequest(query, opts) {
227
240
  return signScheme.signOverTbs(scheme, opts.signer.key, tbsRequest, _signE).then(function (sig) {
228
241
  /** @internal The request is held to the same proof a certificate is: the signature verifies
229
242
  * against the public key the certificate it carries declares. */
230
- return Promise.resolve(_b.assertSignatureVerifies(tbsRequest, sig, signerCert.subjectPublicKeyInfo.bytes, scheme)).then(function () {
243
+ return _promiseResolve(_Promise, _b.assertSignatureVerifies(tbsRequest, sig, signerCert.subjectPublicKeyInfo.bytes, scheme)).then(function () {
231
244
  var optionalSignature = b.sequence([scheme.sigAlgId, b.bitString(sig, 0), b.explicit(0, b.sequence([b.raw(signerCertDer)]))]);
232
245
  return _emitReq(b.sequence([tbsRequest, b.explicit(0, optionalSignature)]), opts);
233
246
  });
@@ -347,7 +360,7 @@ function _signResponse(responseData, responder, respCert, respCertDer, responder
347
360
  if (!responses.length) throw _err("ocsp/bad-input", "a response MUST include at least one SingleResponse (RFC 6960 sec. 4.2.1)");
348
361
 
349
362
  return _responderID(responseData.responderID, respCert).then(function (ridNode) {
350
- return Promise.all(responses.map(function (r) { return _buildSingleResponse(r, opts); })).then(function (built) {
363
+ return _promiseAll(_Promise, responses.map(function (r) { return _buildSingleResponse(r, opts); })).then(function (built) {
351
364
  var srNodes = built.map(function (x) { return x.node; });
352
365
  var anyNonIssued = built.some(function (x) { return x.nonIssued; });
353
366
  /** @internal Sec. 2.2 and 4.4.8: a "revoked" answer for a non-issued certificate MUST carry the
@@ -366,7 +379,7 @@ function _signResponse(responseData, responder, respCert, respCertDer, responder
366
379
  return signScheme.signOverTbs(scheme, responderKey, responseDataDer, _signE).then(function (sig) {
367
380
  /** @internal The response is held to the same proof a certificate is: the signature verifies
368
381
  * against the public key the responder certificate declares. */
369
- return Promise.resolve(_b.assertSignatureVerifies(responseDataDer, sig, respCert.subjectPublicKeyInfo.bytes, scheme)).then(function () {
382
+ return _promiseResolve(_Promise, _b.assertSignatureVerifies(responseDataDer, sig, respCert.subjectPublicKeyInfo.bytes, scheme)).then(function () {
370
383
  var basicChildren = [responseDataDer, scheme.sigAlgId, b.bitString(sig, 0)];
371
384
  if (opts.embedCert !== false) basicChildren.push(b.explicit(0, b.sequence([b.raw(respCertDer)])));
372
385
  var responseBytes = b.sequence([b.oid(OID_OCSP_BASIC), b.octetString(b.sequence(basicChildren))]);
@@ -406,19 +419,19 @@ function _snapshotSignerKey(key, owned) {
406
419
  }
407
420
 
408
421
  function _responderID(rid, respCert) {
409
- if (rid == null || rid === "byName") return Promise.resolve(b.explicit(1, b.raw(respCert.subject.bytes)));
422
+ if (rid == null || rid === "byName") return _promiseResolve(_Promise, b.explicit(1, b.raw(respCert.subject.bytes)));
410
423
  if (rid === "byKey") return _digest("SHA-1", _keyValue(respCert.subjectPublicKeyInfo.bytes)).then(function (kh) { return b.explicit(2, b.octetString(kh)); });
411
424
  throw _err("ocsp/bad-input", "responderID must be \"byName\" or \"byKey\"");
412
425
  }
413
426
  function _buildSingleResponse(r, opts) {
414
427
  r = r || {};
415
428
  var certIdP;
416
- if (typeof r !== "object") return Promise.reject(_err("ocsp/bad-input", "each response entry must be an object"));
429
+ if (typeof r !== "object") return _promiseReject(_Promise, _err("ocsp/bad-input", "each response entry must be an object"));
417
430
  try { guard.identifier.assertKnownKeys(r, _SINGLE_RESPONSE_KEYS, _err, "ocsp/bad-input", "unknown response entry field (accepted: cert, issuer, certID, hashAlgorithm, status, thisUpdate, nextUpdate, singleExtensions): "); }
418
- catch (e) { return Promise.reject(e); }
419
- if (r.certID != null) certIdP = Promise.resolve(b.raw(guard.bytes.view(r.certID, OcspError, "ocsp/bad-input", "a response entry's certID")));
431
+ catch (e) { return _promiseReject(_Promise, e); }
432
+ if (r.certID != null) certIdP = _promiseResolve(_Promise, b.raw(guard.bytes.view(r.certID, OcspError, "ocsp/bad-input", "a response entry's certID")));
420
433
  else if (r.cert != null && r.issuer != null) certIdP = _buildCertID(_certOf(r.cert, "a response certificate"), _certOf(r.issuer, "a response issuer"), r.hashAlgorithm || "sha1");
421
- else return Promise.reject(_err("ocsp/bad-input", "each response entry needs { certID } or { cert, issuer }"));
434
+ else return _promiseReject(_Promise, _err("ocsp/bad-input", "each response entry needs { certID } or { cert, issuer }"));
422
435
  /** @internal Every per-entry refusal stays INSIDE this chain. These entries are built through a
423
436
  * map into Promise.all, so a synchronous throw here would abort the map before Promise.all
424
437
  * attached handlers, and an earlier entry's rejection would be orphaned: the caller catches
@@ -653,15 +666,15 @@ function verify(response, opts) {
653
666
  opts = guard.identifier.optionsObject(opts, _err, "ocsp/bad-input", "pki.ocsp.verify options");
654
667
  guard.identifier.assertKnownKeys(opts, _VERIFY_OPTS, _err, "ocsp/bad-input",
655
668
  "pki.ocsp.verify has an unknown option (the request nonce to bind against is `requestNonce`): ");
656
- } catch (e) { return Promise.reject(e); }
657
- if (opts.cert == null || opts.issuer == null) return Promise.reject(_err("ocsp/bad-input", "verify requires opts.cert and opts.issuer"));
669
+ } catch (e) { return _promiseReject(_Promise, e); }
670
+ if (opts.cert == null || opts.issuer == null) return _promiseReject(_Promise, _err("ocsp/bad-input", "verify requires opts.cert and opts.issuer"));
658
671
  var parsed, cert, issuerCert, time;
659
672
  try {
660
673
  parsed = _responseFromBytes(response);
661
674
  cert = _certOf(opts.cert, "the target certificate");
662
675
  issuerCert = _certOf(opts.issuer, "the issuer certificate");
663
676
  time = opts.time == null ? new Date() : _asDate(opts.time);
664
- } catch (e) { return Promise.reject(e); }
677
+ } catch (e) { return _promiseReject(_Promise, e); }
665
678
  /** @internal The nonce rule is applied by the acceptance gate rather than restated here, so this
666
679
  * door and every other consumer of that gate answer alike. The bytes are snapshotted here so a
667
680
  * fault in them carries this door's own code; a value that is not bytes is passed through for the
@@ -670,7 +683,7 @@ function verify(response, opts) {
670
683
  var reqNonce = opts.requestNonce;
671
684
  if (guard.bytes.isByteSource(reqNonce)) {
672
685
  try { reqNonce = guard.bytes.snapshotSource(reqNonce, OcspError, "ocsp/bad-input", "opts.requestNonce"); }
673
- catch (e) { return Promise.reject(e); }
686
+ catch (e) { return _promiseReject(_Promise, e); }
674
687
  }
675
688
  return pathValidate.verifyOcspResponse(parsed, cert, issuerCert, time,
676
689
  { historicalMode: opts.historicalMode === true, requestNonce: reqNonce }).then(function (verdict) {
package/lib/oid.js CHANGED
@@ -65,11 +65,13 @@ var FAMILIES = Object.assign(Object.create(null), {
65
65
  policyConstraints: 36, extKeyUsage: 37, freshestCRL: 46,
66
66
  inhibitAnyPolicy: 54,
67
67
  anyPolicy: [32, 0], anyExtendedKeyUsage: [37, 0],
68
- targetInformation: 55, noRevAvail: 56 } },
68
+ targetInformation: 55, noRevAvail: 56,
69
+ subjectAltPublicKeyInfo: 72, altSignatureAlgorithm: 73, altSignatureValue: 74 } },
69
70
 
70
71
  pkixAccess: { base: [1, 3, 6, 1, 5, 5, 7, 1], of: {
71
72
  authorityInfoAccess: 1, acAuditIdentity: 4, aaControls: 6, acProxying: 10,
72
73
  acmeIdentifier: 31,
74
+ relatedCertificate: 36,
73
75
  subjectInfoAccess: 11,
74
76
  tlsFeature: 24,
75
77
  qcStatements: 3,
@@ -187,6 +189,12 @@ var FAMILIES = Object.assign(Object.create(null), {
187
189
 
188
190
  wisun: { base: [1, 3, 6, 1, 4, 1, 45605], of: { fanDevice: 1 } },
189
191
 
192
+ /** @internal RFC 9883 assigns its attribute on a private-enterprise arc rather than a PKIX one, and
193
+ * uses the SAME value as a PKCS#10 attribute and as a CRMF registration control, so the two names
194
+ * resolve to one dotted OID. `byName` is one-to-one in the other direction, so the attribute name is
195
+ * the one the registry returns for the dotted form. */
196
+ rfc9883: { base: [1, 3, 6, 1, 4, 1, 22112, 2], of: { statementOfPossession: 1 } },
197
+
190
198
  ct: { base: [1, 3, 6, 1, 4, 1, 11129, 2, 4], of: {
191
199
  signedCertificateTimestampList: 2, precertificatePoison: 3,
192
200
  precertificateSigningCert: 4, ocspSignedCertificateTimestampList: 5 } },
@@ -314,7 +322,9 @@ var FAMILIES = Object.assign(Object.create(null), {
314
322
  msgSigDigest: 5, contentIdentifier: 7, equivalentLabels: 9, contentReference: 10,
315
323
  signingCertificate: 12, timeStampToken: 14, decryptKeyID: 37, signingCertificateV2: 47,
316
324
  cmcUnsignedData: 34,
317
- asymmDecryptKeyID: 54, certificationRequestInfoTemplate: 61, extensionReqTemplate: 62 } },
325
+ binarySigningTime: 46,
326
+ asymmDecryptKeyID: 54, relatedCertRequest: 60,
327
+ certificationRequestInfoTemplate: 61, extensionReqTemplate: 62 } },
318
328
 
319
329
  ansiX962: { base: [1, 2, 840, 10045], of: {
320
330
  ecPublicKey: [2, 1], prime192v1: [3, 1, 1], prime256v1: [3, 1, 7],
@@ -443,8 +453,8 @@ function _assertEncodableArcs(arcs, who) {
443
453
  if (arcs.length < 2) {
444
454
  throw new OidError("oid/bad-input", who + ": an OID must have at least 2 arcs");
445
455
  }
446
- var root = typeof arcs[0] === "bigint" ? arcs[0] : BigInt(arcs[0]);
447
- var second = typeof arcs[1] === "bigint" ? arcs[1] : BigInt(arcs[1]);
456
+ var root = guard.range.authoredInteger(arcs[0], _oidError, "oid/bad-arc", who + ": the root arc");
457
+ var second = guard.range.authoredInteger(arcs[1], _oidError, "oid/bad-arc", who + ": the second arc");
448
458
  if (root > 2n) throw new OidError("oid/bad-arc", who + ": the root arc must be 0, 1, or 2 (X.660)");
449
459
  if (root < 2n && second > 39n) throw new OidError("oid/bad-arc", who + ": the second arc must be 0..39 under roots 0 and 1 (X.660)");
450
460
  }
@@ -47,15 +47,26 @@ var csrVerify = require("./csr-verify");
47
47
  var crmfVerify = require("./crmf-verify");
48
48
  var attrcertVerify = require("./attrcert-verify");
49
49
  var ocspRequestVerify = require("./ocsp-request-verify");
50
+ var relatedCert = require("./related-cert");
51
+ var altSig = require("./alt-sig");
52
+ var possession = require("./possession");
50
53
  var cmpVerify = require("./cmp-verify");
51
54
  var cmsVerify = require("./cms-verify");
52
55
  var cmpSession = require("./cmp-session");
53
56
  var guard = require("./guard-all");
54
57
  var intrinsic = require("./guard-intrinsic");
55
58
  var _charCodeAt = intrinsic.uncurry(String.prototype.charCodeAt);
59
+ var _pop = intrinsic.pop;
60
+ var _splice = intrinsic.splice;
56
61
  var _strSlice = intrinsic.uncurry(String.prototype.slice);
57
62
  var _strIndexOf = intrinsic.uncurry(String.prototype.indexOf);
58
63
  var _resolve = intrinsic.uncurry(intrinsic.promiseResolve);
64
+ var _reject = intrinsic.uncurry(intrinsic.promiseReject);
65
+ /** @internal The promise constructor, captured at load. `promiseResolve` builds its promise from the
66
+ * RECEIVER it is called on, so reading the global binding at the call hands that construction to
67
+ * whatever the binding holds: a descriptor failure answering `false` through a replaced constructor is
68
+ * not a refusal, and this module's verdicts are what a caller acts on. */
69
+ var _Promise = intrinsic.Promise;
59
70
  var constants = require("./constants");
60
71
  var validator = require("./validator-all");
61
72
  var cms = require("./schema-cms");
@@ -90,6 +101,11 @@ var EXT_DECODERS = pkix.certExtensionDecoders(NS).byOid;
90
101
  var ANCHOR_SPKI_SCHEMA = pkix.spki(NS);
91
102
 
92
103
  var subtle = webcrypto.webcrypto.subtle;
104
+ /** @internal The two SubtleCrypto operations a certificate signature verdict rests on, captured at
105
+ * load. The object was held and its METHODS fetched per call, which leaves them replaceable: a
106
+ * `verify` answering true accepts a certificate nothing in the path signed. */
107
+ var _subtleImportKey = intrinsic.uncurry(subtle.importKey);
108
+ var _subtleVerify = intrinsic.uncurry(subtle.verify);
93
109
 
94
110
  var OID = intrinsic.assign(intrinsic.create(null), {
95
111
  basicConstraints: oid.byName("basicConstraints"),
@@ -250,18 +266,18 @@ function assertKeyMatchesSigAlg(spkiBytes, sigOid, d) {
250
266
  function compositeKeyUsageCheck(cert) {
251
267
  var ku;
252
268
  try { ku = decodeExt(cert, OID.keyUsage); }
253
- catch (e) { return { ok: false, code: "path/composite-key-usage", error: e }; }
254
- if (!ku) return { ok: true };
269
+ catch (e) { return guard.verdict.of({ ok: false, code: "path/composite-key-usage", error: e }); }
270
+ if (!ku) return guard.verdict.of({ ok: true });
255
271
  var v = ku.value;
256
272
  if (v.keyEncipherment || v.dataEncipherment || v.keyAgreement || v.encipherOnly || v.decipherOnly) {
257
- return { ok: false, code: "path/composite-key-usage",
258
- error: E("path/composite-key-usage", "a composite ML-DSA key asserts a forbidden encryption/key-establishment keyUsage bit (draft-ietf-lamps-pq-composite-sigs sec. 5.2)") };
273
+ return guard.verdict.of({ ok: false, code: "path/composite-key-usage",
274
+ error: E("path/composite-key-usage", "a composite ML-DSA key asserts a forbidden encryption/key-establishment keyUsage bit (draft-ietf-lamps-pq-composite-sigs sec. 5.2)") });
259
275
  }
260
276
  if (!(v.digitalSignature || v.nonRepudiation || v.keyCertSign || v.cRLSign)) {
261
- return { ok: false, code: "path/composite-key-usage",
262
- error: E("path/composite-key-usage", "a composite ML-DSA key's keyUsage asserts no signature bit (draft-ietf-lamps-pq-composite-sigs sec. 5.2)") };
277
+ return guard.verdict.of({ ok: false, code: "path/composite-key-usage",
278
+ error: E("path/composite-key-usage", "a composite ML-DSA key's keyUsage asserts no signature bit (draft-ietf-lamps-pq-composite-sigs sec. 5.2)") });
263
279
  }
264
- return { ok: true };
280
+ return guard.verdict.of({ ok: true });
265
281
  }
266
282
 
267
283
  var STATEFUL_HBS_OIDS = intrinsic.create(null);
@@ -284,24 +300,24 @@ function _isCaCert(cert) {
284
300
  function statefulHbsKeyUsageCheck(cert, isCa) {
285
301
  var ku;
286
302
  try { ku = decodeExt(cert, OID.keyUsage); }
287
- catch (e) { return { ok: false, code: "path/hbs-key-usage", error: e }; }
288
- if (!ku) return { ok: true };
303
+ catch (e) { return guard.verdict.of({ ok: false, code: "path/hbs-key-usage", error: e }); }
304
+ if (!ku) return guard.verdict.of({ ok: true });
289
305
  var v = ku.value;
290
306
  var where = isCa ? "a certification authority" : "an end entity";
291
307
  var allowed = isCa ? "digitalSignature, nonRepudiation, keyCertSign or cRLSign"
292
308
  : "digitalSignature, nonRepudiation or cRLSign";
293
309
  var asserted = v.digitalSignature || v.nonRepudiation || v.cRLSign || v.keyCertSign;
294
310
  if (!asserted) {
295
- return { ok: false, code: "path/hbs-key-usage",
296
- error: E("path/hbs-key-usage", "a stateful hash-based key in " + where + " certificate must assert at least one of " + allowed + " (RFC 9802 sec. 6)") };
311
+ return guard.verdict.of({ ok: false, code: "path/hbs-key-usage",
312
+ error: E("path/hbs-key-usage", "a stateful hash-based key in " + where + " certificate must assert at least one of " + allowed + " (RFC 9802 sec. 6)") });
297
313
  }
298
314
  var forbidden = v.keyEncipherment || v.dataEncipherment || v.keyAgreement || v.encipherOnly ||
299
315
  v.decipherOnly || (!isCa && v.keyCertSign) || (v.reservedBitsSet === true);
300
316
  if (forbidden) {
301
- return { ok: false, code: "path/hbs-key-usage",
302
- error: E("path/hbs-key-usage", "a stateful hash-based key in " + where + " certificate must assert no keyUsage value outside " + allowed + " (RFC 9802 sec. 6)") };
317
+ return guard.verdict.of({ ok: false, code: "path/hbs-key-usage",
318
+ error: E("path/hbs-key-usage", "a stateful hash-based key in " + where + " certificate must assert no keyUsage value outside " + allowed + " (RFC 9802 sec. 6)") });
303
319
  }
304
- return { ok: true };
320
+ return guard.verdict.of({ ok: true });
305
321
  }
306
322
 
307
323
  var ML_KEM_OIDS = intrinsic.create(null);
@@ -310,23 +326,23 @@ var ML_KEM_OIDS = intrinsic.create(null);
310
326
  function kemKeyUsageCheck(cert) {
311
327
  var ku;
312
328
  try { ku = decodeExt(cert, OID.keyUsage); }
313
- catch (e) { return { ok: false, code: "path/kem-key-usage", error: e }; }
314
- if (!ku) return { ok: true };
329
+ catch (e) { return guard.verdict.of({ ok: false, code: "path/kem-key-usage", error: e }); }
330
+ if (!ku) return guard.verdict.of({ ok: true });
315
331
  var v = ku.value;
316
332
  var others = v.digitalSignature || v.nonRepudiation || v.dataEncipherment || v.keyAgreement ||
317
333
  v.keyCertSign || v.cRLSign || v.encipherOnly || v.decipherOnly || (v.reservedBitsSet === true);
318
334
  if (!v.keyEncipherment || others) {
319
- return { ok: false, code: "path/kem-key-usage",
320
- error: E("path/kem-key-usage", "an ML-KEM key's keyUsage must assert keyEncipherment as the only bit (RFC 9935 sec. 5)") };
335
+ return guard.verdict.of({ ok: false, code: "path/kem-key-usage",
336
+ error: E("path/kem-key-usage", "an ML-KEM key's keyUsage must assert keyEncipherment as the only bit (RFC 9935 sec. 5)") });
321
337
  }
322
- return { ok: true };
338
+ return guard.verdict.of({ ok: true });
323
339
  }
324
340
 
325
341
  function _importVerifyKey(spkiBytes, d) {
326
342
  try {
327
343
  if (d.eddsa) edwardsPoint.validateSpki(spkiBytes, d.eddsa, PathError, "path/bad-signature");
328
- } catch (e) { return Promise.reject(e); }
329
- return subtle.importKey("spki", spkiBytes, d.imp, false, ["verify"]);
344
+ } catch (e) { return _reject(_Promise, e); }
345
+ return _subtleImportKey(subtle, "spki", spkiBytes, d.imp, false, ["verify"]);
330
346
  }
331
347
 
332
348
  /** @internal The RFC 8554 public key the SubjectPublicKeyInfo carries, and the verdict on a signature over
@@ -397,23 +413,24 @@ function builtinVerify(state, cert) {
397
413
  try {
398
414
  d = resolveDescriptor(cert.signatureAlgorithm);
399
415
  assertKeyMatchesSigAlg(state.workingPublicKey, cert.signatureAlgorithm.oid, d);
400
- /** @internal Resolving reads `then` off the value, so a synchronous verdict is built with an own one:
401
- * an inherited accessor would otherwise run with the verdict as its receiver and hand the caller a
402
- * different object (see guard-verdict). */
403
- } catch (e) { return _resolve(Promise, guard.verdict.of({ ok: false, code: pathCode(e, "path/unsupported-algorithm"), error: e })); }
404
- if (!guard.crypto.isOctetAligned(cert.signatureValue)) return _resolve(Promise, guard.verdict.of({ ok: false, code: "path/bad-signature" }));
416
+ /** @internal Resolving reads `then` off the value, so every verdict here is built with an own one,
417
+ * on the asynchronous arms as much as the synchronous ones: an inherited accessor would otherwise run
418
+ * with the verdict as its receiver and hand the caller a different object (see guard-verdict). The
419
+ * resolution of the `.then` callbacks below is the same window as the `_resolve` calls here. */
420
+ } catch (e) { return _resolve(_Promise, guard.verdict.of({ ok: false, code: pathCode(e, "path/unsupported-algorithm"), error: e })); }
421
+ if (!guard.crypto.isOctetAligned(cert.signatureValue)) return _resolve(_Promise, guard.verdict.of({ ok: false, code: "path/bad-signature" }));
405
422
  if (d.composite) return compositeSig.compositeVerify(state.workingPublicKey, cert.signatureValue.bytes, cert.tbsBytes, d.composite, PathError, "path/unsupported-algorithm", "path/bad-signature");
406
- if (d.shbs) return _resolve(Promise, _shbsVerify(state.workingPublicKey, cert.signatureValue.bytes, cert.tbsBytes));
423
+ if (d.shbs) return _resolve(_Promise, _shbsVerify(state.workingPublicKey, cert.signatureValue.bytes, cert.tbsBytes));
407
424
  var key;
408
425
  return _importVerifyKey(state.workingPublicKey, d).then(function (k) {
409
426
  key = k;
410
427
  var sig = cert.signatureValue.bytes;
411
428
  if (d.ecdsa) sig = validator.sig.ecdsaDerToP1363(sig, key.algorithm.namedCurve, PathError, "path/bad-signature");
412
- return subtle.verify(d.verify, key, sig, cert.tbsBytes);
429
+ return _subtleVerify(subtle, d.verify, key, sig, cert.tbsBytes);
413
430
  }).then(function (ok) {
414
- return { ok: ok === true };
431
+ return guard.verdict.of({ ok: ok === true });
415
432
  }, function (e) {
416
- return { ok: false, code: pathCode(e, "path/bad-signature"), error: e };
433
+ return guard.verdict.of({ ok: false, code: pathCode(e, "path/bad-signature"), error: e });
417
434
  });
418
435
  }
419
436
 
@@ -596,8 +613,8 @@ function checkNameConstraints(state, cert) {
596
613
  var ex = state.excludedSubtrees[e];
597
614
  for (var i = 0; i < forms.length; i++) {
598
615
  var m = nameMatchesConstraint(forms[i].tag, forms[i].value, { tagNumber: ex.tag, value: ex.base }, true);
599
- if (m === true) return { ok: false, code: "path/name-constraint-excluded" };
600
- if (m === "unsupported") return { ok: false, code: "path/name-constraint-unsupported" };
616
+ if (m === true) return guard.verdict.of({ ok: false, code: "path/name-constraint-excluded" });
617
+ if (m === "unsupported") return guard.verdict.of({ ok: false, code: "path/name-constraint-unsupported" });
601
618
  }
602
619
  }
603
620
  for (var f = 0; f < forms.length; f++) {
@@ -611,10 +628,10 @@ function checkNameConstraints(state, cert) {
611
628
  if (r === true) permitted = true;
612
629
  else if (r === "unsupported") unsupported = true;
613
630
  });
614
- if (!permitted) return { ok: false, code: unsupported ? "path/name-constraint-unsupported" : "path/name-constraint-not-permitted" };
631
+ if (!permitted) return guard.verdict.of({ ok: false, code: unsupported ? "path/name-constraint-unsupported" : "path/name-constraint-not-permitted" });
615
632
  }
616
633
  }
617
- return { ok: true };
634
+ return guard.verdict.of({ ok: true });
618
635
  }
619
636
 
620
637
  function absorbNameConstraints(state, decoded) {
@@ -683,11 +700,11 @@ function addPolicyNode(state, depth, validPolicy, qualifiers, expectedPolicySet,
683
700
  function removePolicyNode(state, node) {
684
701
  node.parents.forEach(function (p) {
685
702
  var idx = p.children.indexOf(node);
686
- if (idx !== -1) p.children.splice(idx, 1);
703
+ if (idx !== -1) _splice(p.children, idx, 1);
687
704
  });
688
705
  node.children.forEach(function (c) {
689
706
  var idx = c.parents.indexOf(node);
690
- if (idx !== -1) c.parents.splice(idx, 1);
707
+ if (idx !== -1) _splice(c.parents, idx, 1);
691
708
  });
692
709
  node.parents = [];
693
710
  var layer = state.policyIndex[node.depth];
@@ -1156,7 +1173,9 @@ function validateCriticalExtensionStructure(cert) {
1156
1173
  * `crlChecker` gives the CRLReason integer and `revocationReason` the name `ocspChecker` gives it;
1157
1174
  * both are read so the row does not depend on which checker answered. */
1158
1175
  function _revocationRow(row, rv) {
1159
- if (rv == null) return row;
1176
+ /** @internal The row carries the sentinel from here rather than only from the walk that shields the
1177
+ * finished results, so a row returned to anything that awaits it is shielded on both arms. */
1178
+ if (rv == null) return guard.verdict.shield(row);
1160
1179
  var reason = rv.reason;
1161
1180
  if (typeof reason === "string" && reason.length > 0) row.reason = reason;
1162
1181
  var code = rv.revocationReason;
@@ -1164,7 +1183,7 @@ function _revocationRow(row, rv) {
1164
1183
  if (intrinsic.numberIsFinite(code)) row.revocationReason = code;
1165
1184
  var at = rv.revocationTime;
1166
1185
  if (guard.time.isDate(at)) row.revocationTime = new intrinsic.Date(guard.time.instantOf(at));
1167
- return row;
1186
+ return guard.verdict.shield(row);
1168
1187
  }
1169
1188
 
1170
1189
  /**
@@ -1333,12 +1352,26 @@ async function validate(path, opts) {
1333
1352
  * holds its state against this object; the object is unreachable once the validation returns, so
1334
1353
  * a checker stays reusable and nothing accumulates between runs. The anchor recursion carries the
1335
1354
  * same one, and a caller has no way to supply one, so two validations never share a budget. */
1336
- return _validate(path, checked, intrinsic.freeze(intrinsic.create(null)));
1355
+ /** @internal The verifier's `verify` is read ONCE here, in the door, and carried through every anchor
1356
+ * attempt. Read inside `_validate` it was read once per attempt, because a list of trust anchors
1357
+ * whose names all match the first certificate's issuer retries the walk per anchor: measured, an
1358
+ * accessor whose first answer refused both certificates and whose second accepted them returned
1359
+ * `valid: true` over two matching anchors. It is the function that answers whether each signature in
1360
+ * the path verifies, so one reading of it governs the whole call. */
1361
+ var suppliedVerifier = checked.verifier || null;
1362
+ var suppliedVerify = suppliedVerifier === null ? null : suppliedVerifier.verify;
1363
+ if (suppliedVerifier !== null && typeof suppliedVerify !== "function") {
1364
+ throw E("path/bad-input", "validate: opts.verifier must carry a verify(args) function");
1365
+ }
1366
+ var scope = intrinsic.freeze(intrinsic.assign(intrinsic.create(null), {
1367
+ verifier: suppliedVerifier, verify: suppliedVerify,
1368
+ }));
1369
+ return _validate(path, checked, intrinsic.freeze(intrinsic.create(null)), scope);
1337
1370
  }
1338
1371
 
1339
1372
  /** @internal The options are the ones the door above checked, and each anchor attempt is handed a
1340
1373
  * copy this module built, so the door runs once per call rather than once per anchor. */
1341
- async function _validate(path, opts, run) {
1374
+ async function _validate(path, opts, run, scope) {
1342
1375
  if (opts.ctPolicy != null && opts.ctLogList == null) throw E("path/bad-input", "validate: opts.ctPolicy requires opts.ctLogList (the trusted CT log-list to verify the certificate's SCTs against)");
1343
1376
  if (opts.ctLogList != null) {
1344
1377
  if (typeof opts.ctLogList !== "object" || opts.ctLogList.byLogId == null) throw E("path/bad-input", "validate: opts.ctLogList must be a pki.ct.parseLogList result");
@@ -1409,11 +1442,11 @@ async function _validate(path, opts, run) {
1409
1442
  for (var ai = 0; ai < tuples.length; ai++) {
1410
1443
  if (!dnEqual(tuples[ai].name.rdns, certs[0].issuer.rdns)) continue;
1411
1444
  anyMatched = true;
1412
- var oneRes = await _validate(path, singleOpts(tuples[ai]), run);
1445
+ var oneRes = await _validate(path, singleOpts(tuples[ai]), run, scope);
1413
1446
  if (oneRes && oneRes.valid) return oneRes;
1414
1447
  lastMatched = oneRes;
1415
1448
  }
1416
- return anyMatched ? lastMatched : _validate(path, singleOpts(tuples[0]), run);
1449
+ return anyMatched ? lastMatched : _validate(path, singleOpts(tuples[0]), run, scope);
1417
1450
  }
1418
1451
  var anchor = toAnchor(anchorList[0], "validate");
1419
1452
  guard.time.assertValid(opts.time, E, "path/bad-input", "validate: opts.time (the always-on validity-window check date)");
@@ -1438,7 +1471,11 @@ async function _validate(path, opts, run) {
1438
1471
  var state = initialize(certs, opts, seeds, anchor, taConstraints);
1439
1472
  state._n = n;
1440
1473
  state.requiredEku = requiredEku;
1441
- var verifier = opts.verifier || null;
1474
+ /** @internal The verifier comes from the per-call scope the door built, never from `opts`: the option
1475
+ * bag is rebuilt per anchor attempt and reading it again would ask the caller's object once per
1476
+ * attempt (see the note in `validate`). */
1477
+ var verifier = scope.verifier;
1478
+ var verifierVerify = scope.verify;
1442
1479
  var revocationChecker = opts.revocationChecker || null;
1443
1480
  var softFail = opts.softFail === true;
1444
1481
  var requireRevocation = opts.requireRevocation === true;
@@ -1458,18 +1495,18 @@ async function _validate(path, opts, run) {
1458
1495
  var sigRes;
1459
1496
  var weak = _weakIssuerKey(state);
1460
1497
  if (weak) {
1461
- sigRes = { ok: false, code: "path/weak-key", error: weak };
1498
+ sigRes = guard.verdict.of({ ok: false, code: "path/weak-key", error: weak });
1462
1499
  } else if (verifier) {
1463
1500
  var vv;
1464
1501
  try {
1465
- vv = await verifier.verify({
1502
+ vv = await intrinsic.apply(verifierVerify, verifier, [{
1466
1503
  cert: cert,
1467
1504
  workingPublicKey: state.workingPublicKey,
1468
1505
  workingPublicKeyAlgorithm: state.workingPublicKeyAlgorithm,
1469
1506
  workingPublicKeyParameters: state.workingPublicKeyParameters,
1470
- });
1507
+ }]);
1471
1508
  } catch (_e) { vv = false; }
1472
- sigRes = { ok: vv === true };
1509
+ sigRes = guard.verdict.of({ ok: vv === true });
1473
1510
  } else {
1474
1511
  sigRes = await builtinVerify(state, cert);
1475
1512
  }
@@ -1670,7 +1707,7 @@ async function _validate(path, opts, run) {
1670
1707
  nameConstraintsApplied: anchorNameConstraintsApplied,
1671
1708
  },
1672
1709
  path: certs,
1673
- results: state.results,
1710
+ results: shieldResults(state.results),
1674
1711
  workingPublicKey: state.workingPublicKey,
1675
1712
  workingPublicKeyAlgorithm: state.workingPublicKeyAlgorithm,
1676
1713
  workingPublicKeyParameters: state.workingPublicKeyParameters,
@@ -1679,6 +1716,22 @@ async function _validate(path, opts, run) {
1679
1716
  });
1680
1717
  }
1681
1718
 
1719
+ /**
1720
+ * @internal A per-certificate result and every check row inside it is a verdict a caller reads `ok`
1721
+ * off, and the rows are appended from some thirty places, two of them after the result itself is
1722
+ * recorded. The sentinel is applied once over the finished set rather than at each append, so a row
1723
+ * a check written later contributes carries it without being told to. `shield` adds the member in
1724
+ * place, which keeps the row identity the appends and the identity lookup above rely on.
1725
+ */
1726
+ function shieldResults(results) {
1727
+ for (var r = 0; r < results.length; r++) {
1728
+ var row = results[r];
1729
+ for (var c = 0; c < row.checks.length; c++) guard.verdict.shield(row.checks[c]);
1730
+ guard.verdict.shield(row);
1731
+ }
1732
+ return results;
1733
+ }
1734
+
1682
1735
  /**
1683
1736
  * @internal RFC 9618 sec. 5.5 rewrites RFC 5280 sec. 6.1.5 step (g) over the graph. The set is read
1684
1737
  * from the nodes whose only parent is the anyPolicy node, at ANY depth rather than at the deepest
@@ -1902,6 +1955,14 @@ function selectDelta(candidates) {
1902
1955
  * typeof checker.check; // "function"
1903
1956
  */
1904
1957
  function crlChecker(crls, opts) {
1958
+ /** @internal A LIST, held to being one before it is mapped. An omitted list is "nothing to check" and
1959
+ * stays accepted; anything else that is not an array fell through to `.map`, where an ArrayBuffer or a
1960
+ * plain object raised a bare TypeError naming an internal expression and a single CRL Buffer mapped
1961
+ * over its BYTES and reported the first byte as a malformed CRL. Passing one CRL where a list belongs
1962
+ * is the natural slip, and it is the one that read furthest before failing. */
1963
+ if (crls !== undefined && crls !== null && !intrinsic.isArray(crls)) {
1964
+ throw E("path/bad-input", "crlChecker: crls must be an array of CRLs");
1965
+ }
1905
1966
  var parsed = (crls || []).map(function (c, ci) {
1906
1967
  return guard.parsed.acceptDerived(c, "crl", crl.parse, E, "path/bad-input",
1907
1968
  "crlChecker: crls[" + ci + "]");
@@ -2101,15 +2162,15 @@ function _verifyWithSpki(sigAlg, rawSig, spkiBytes, tbsBytes) {
2101
2162
  try {
2102
2163
  d = resolveDescriptor(sigAlg);
2103
2164
  assertKeyMatchesSigAlg(spkiBytes, sigAlg.oid, d);
2104
- } catch (_e) { return Promise.resolve(false); }
2165
+ } catch (_e) { return _resolve(_Promise, false); }
2105
2166
  if (d.composite) return compositeSig.compositeVerify(spkiBytes, rawSig, tbsBytes, d.composite, PathError, "path/unsupported-algorithm", "path/bad-signature").then(function (r) { return r.ok === true; });
2106
2167
  /** @internal RFC 9802 sec. 6 names the CertificateList as well as the Certificate, so a revocation list
2107
2168
  * signed with a stateful hash-based key reaches the same engine the certificate path uses. */
2108
- if (d.shbs) return _resolve(Promise, _shbsVerify(spkiBytes, rawSig, tbsBytes).ok === true);
2169
+ if (d.shbs) return _resolve(_Promise, _shbsVerify(spkiBytes, rawSig, tbsBytes).ok === true);
2109
2170
  return _importVerifyKey(spkiBytes, d).then(function (key) {
2110
2171
  var sig = rawSig;
2111
2172
  if (d.ecdsa) sig = validator.sig.ecdsaDerToP1363(sig, key.algorithm.namedCurve, PathError, "path/bad-signature");
2112
- return subtle.verify(d.verify, key, sig, tbsBytes);
2173
+ return _subtleVerify(subtle, d.verify, key, sig, tbsBytes);
2113
2174
  }).then(function (ok) { return ok === true; }, function () { return false; });
2114
2175
  }
2115
2176
 
@@ -2123,6 +2184,21 @@ attrcertVerify.setEngine(_verifyWithSpki);
2123
2184
 
2124
2185
  ocspRequestVerify.setEngine(_verifyWithSpki);
2125
2186
 
2187
+ /** @internal RFC 9763 sec. 3.1's proof of possession is a signature by the key of a certificate the
2188
+ * request names, so it reaches the same engine the certificate path uses and the same binding of the
2189
+ * advertised algorithm to the key. */
2190
+ relatedCert.setEngine(_verifyWithSpki);
2191
+
2192
+ /** @internal An ITU-T X.509 (2019) clause 9.8 alternative signature is a signature over a certificate
2193
+ * or a CRL by a key in an extension, so it reaches the same engine and the same binding of the
2194
+ * advertised algorithm to the key. */
2195
+ altSig.setEngine(_verifyWithSpki);
2196
+
2197
+ /** @internal RFC 9883 sec. 4 makes certification path validation of the signature certificate a MUST
2198
+ * alongside the signature check, so the possession verb needs the validator as well as the engine and
2199
+ * runs the same `validate` a caller would call rather than holding a second copy of it. */
2200
+ possession.setEngine({ verifyWithSpki: _verifyWithSpki, validate: validate });
2201
+
2126
2202
 
2127
2203
  var ocspCore = ocspVerify.makeOcspVerify({
2128
2204
  verifyWithSpki: _verifyWithSpki,
@@ -2170,6 +2246,10 @@ cmsVerify.setEngine({ build: build, validate: validate, toAnchor: toAnchor,
2170
2246
  * typeof checker.check; // "function"
2171
2247
  */
2172
2248
  function ocspChecker(responses) {
2249
+ /** @internal A LIST, for the reason at `crlChecker`. */
2250
+ if (responses !== undefined && responses !== null && !intrinsic.isArray(responses)) {
2251
+ throw E("path/bad-input", "ocspChecker: responses must be an array of OCSP responses");
2252
+ }
2173
2253
  var parsed = (responses || []).map(function (r) { return _ocspFromBytes(r); });
2174
2254
  return {
2175
2255
  check: async function (cert, issuer, ctx) {
@@ -2288,7 +2368,7 @@ async function verifyOcspResponse(response, cert, issuerCert, time, opts) {
2288
2368
  parsedResponse = _ocspFromBytes(response);
2289
2369
  subject = coerceCert(cert, "the certificate");
2290
2370
  issuer = coerceCert(issuerCert, "the issuer certificate");
2291
- } catch (e) { return Promise.reject(e); }
2371
+ } catch (e) { return _reject(_Promise, e); }
2292
2372
  var v = await _verifyOcspParsed(parsedResponse, subject, issuer, time, checkedOpts);
2293
2373
  var out = guard.verdict.of(v);
2294
2374
  return guard.verdict.of(out, { valid: out.status === "good" });
@@ -2308,7 +2388,7 @@ function _ocspFromBytes(response) {
2308
2388
  function _verifyOcspParsed(parsedResponse, cert, issuerCert, time, opts) {
2309
2389
  opts = opts || {};
2310
2390
  if (!guard.time.isDate(time) || isNaN(guard.time.instantOf(time))) {
2311
- return Promise.reject(E("path/bad-input", "verifyOcspResponse: time must be a valid Date (the currency + responder-validity check date)"));
2391
+ return _reject(_Promise, E("path/bad-input", "verifyOcspResponse: time must be a valid Date (the currency + responder-validity check date)"));
2312
2392
  }
2313
2393
  /** @internal Snapshotted in the synchronous prologue: the bytes compared are the bytes the caller
2314
2394
  * passed, not what its BufferSource holds once the verify has awaited. A value that is not bytes
@@ -2327,9 +2407,9 @@ function _verifyOcspParsed(parsedResponse, cert, issuerCert, time, opts) {
2327
2407
  }
2328
2408
  var boundName;
2329
2409
  try { boundName = dnEqual(cert.issuer.rdns, issuerCert.subject.rdns); }
2330
- catch (e) { return Promise.reject(e); }
2410
+ catch (e) { return _reject(_Promise, e); }
2331
2411
  if (!boundName) {
2332
- return Promise.resolve(unbound("the supplied issuer certificate's subject does not match the target certificate's issuer"));
2412
+ return _resolve(_Promise, unbound("the supplied issuer certificate's subject does not match the target certificate's issuer"));
2333
2413
  }
2334
2414
  return builtinVerify({ workingPublicKey: issuerCert.subjectPublicKeyInfo.bytes }, cert).then(function (sig) {
2335
2415
  if (!sig.ok) return unbound("the target certificate's signature does not verify under the supplied issuer certificate's key");
@@ -2984,7 +3064,9 @@ function _bestByPolicy(accepted, uips) {
2984
3064
  var s = _policyScore(accepted[i].result, uips);
2985
3065
  if (s > bestScore) { best = accepted[i]; bestScore = s; }
2986
3066
  }
2987
- return best;
3067
+ /** @internal The candidate carries a verdict in its `result`, so the wrapper carries the sentinel
3068
+ * too: whichever of the two a caller awaits, the lookup ends on the object itself. */
3069
+ return guard.verdict.shield(best);
2988
3070
  }
2989
3071
 
2990
3072
  function _assertUserInitialPolicySet(uips, prefix) {
@@ -3015,7 +3097,7 @@ function _pushCandidates(frame, scored, stack, counter, down) {
3015
3097
  intrinsic.setAdd(childKeys, candKey);
3016
3098
  guard.list.append(stack, down
3017
3099
  ? { chain: intrinsic.concat(frame.chain, [cand]), hop: frame.hop + 1, keys: childKeys, anchor: frame.anchor }
3018
- : { chain: [cand].concat(frame.chain), hop: frame.hop + 1, keys: childKeys });
3100
+ : { chain: intrinsic.concat([cand], frame.chain), hop: frame.hop + 1, keys: childKeys });
3019
3101
  }
3020
3102
  return n;
3021
3103
  }
@@ -3216,7 +3298,7 @@ async function build(leaf, opts) {
3216
3298
  var poolCeiling = constants.LIMITS.PATH_BUILD_MAX_CANDIDATES;
3217
3299
  if (poolInput.length > poolCeiling) throw E("path/bad-input", "build: the candidate pool has " + poolInput.length + " certificates, exceeding the " + poolCeiling + " ceiling");
3218
3300
  var pool;
3219
- try { pool = poolInput.map(coerceCert); }
3301
+ try { pool = intrinsic.map(poolInput, coerceCert); }
3220
3302
  catch (e) { throw E("path/bad-input", "build: a candidate certificate did not parse", e); }
3221
3303
  var poolDerKeys = new Set();
3222
3304
  for (var pdk = 0; pdk < pool.length; pdk++) intrinsic.setAdd(poolDerKeys, certDerKey(pool[pdk]));
@@ -3280,7 +3362,7 @@ async function build(leaf, opts) {
3280
3362
  var BUILD_ONLY_OPT = { candidates: 1, intermediates: 1, trustAnchors: 1, maxDepth: 1, maxCandidatesConsidered: 1, validate: 1,
3281
3363
  fetchAia: 1, transport: 1, tls: 1, maxAiaFetches: 1, maxAiaPerCert: 1, aiaTimeout: 1, maxResponseBytes: 1 };
3282
3364
  var forwarded = Object.create(null);
3283
- Object.keys(_VALIDATE_OPTS).forEach(function (k) {
3365
+ intrinsic.forEach(intrinsic.keys(_VALIDATE_OPTS), function (k) {
3284
3366
  if (intrinsic.hasOwn(BUILD_ONLY_OPT, k)) return;
3285
3367
  if (k in opts) forwarded[k] = opts[k];
3286
3368
  });
@@ -3319,7 +3401,7 @@ async function build(leaf, opts) {
3319
3401
  var rstack = [];
3320
3402
  for (var rsi = 0; rsi < anchors.length; rsi++) guard.list.append(rstack, { chain: [], hop: 0, keys: new Set([identityKey(leafCert)]), anchor: anchors[rsi] });
3321
3403
  while (rstack.length) {
3322
- var rframe = rstack.pop();
3404
+ var rframe = _pop(rstack);
3323
3405
  var frontierCert = rframe.chain.length ? rframe.chain[rframe.chain.length - 1] : null;
3324
3406
  var frontierName = frontierCert ? frontierCert.subject.rdns : rframe.anchor.name.rdns;
3325
3407
  if (nameMatchSoft(leafCert.issuer.rdns, frontierName)) {
@@ -3384,7 +3466,7 @@ async function build(leaf, opts) {
3384
3466
  considered += _pushCandidates(fb, fbScored, stack, counter);
3385
3467
  continue;
3386
3468
  }
3387
- var frame = stack.pop();
3469
+ var frame = _pop(stack);
3388
3470
  var current = frame.chain[0];
3389
3471
 
3390
3472
  for (var ai = 0; ai < anchors.length; ai++) {