@blamejs/pki 0.8.30 → 0.8.48

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 +411 -0
  2. package/README.md +67 -16
  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 +28 -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 +939 -48
  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 +448 -2
  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 +267 -4
  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
@@ -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++) {
@@ -89,9 +89,14 @@ var _ENCRYPT_KEYS = intrinsic.assign(intrinsic.create(null), { password: 1, ciph
89
89
  var _MAC_KEYS = intrinsic.assign(intrinsic.create(null), { algorithm: 1, hash: 1, salt: 1, iterations: 1, keyLength: 1, password: 1 });
90
90
  var _MAC_KEYS_HMAC = intrinsic.assign(intrinsic.create(null), { algorithm: 1, hash: 1, salt: 1, iterations: 1, password: 1 });
91
91
  var _MAC_KEYS_PBMAC1 = intrinsic.assign(intrinsic.create(null), { algorithm: 1, hash: 1, salt: 1, iterations: 1, keyLength: 1, password: 1 });
92
+ /** @internal `allowUnauthenticatedRsa15` reaches `pki.cms.decrypt` through the options this verb already
93
+ * forwards. A public-key-privacy safe IS a CMS EnvelopedData, and the one `openssl pkcs12` or
94
+ * `openssl cms -encrypt` produces uses RSAES-PKCS1-v1_5 key transport over an AES-CBC content, which that
95
+ * verb refuses by default. An operator opening such a file needs the option, and naming it here is what
96
+ * lets them pass it rather than meeting an unknown-option refusal first. */
92
97
  var _OPEN_OPTS = intrinsic.assign(intrinsic.create(null), {
93
- allowUnauthenticated: 1, importAlgorithm: 1, keys: 1, maxIterations: 1, signerCerts: 1,
94
- recipientKey: 1, recipientCert: 1, recipientIndex: 1,
98
+ allowUnauthenticated: 1, allowUnauthenticatedRsa15: 1, importAlgorithm: 1, keys: 1, maxIterations: 1,
99
+ signerCerts: 1, recipientKey: 1, recipientCert: 1, recipientIndex: 1,
95
100
  });
96
101
  var _VERIFY_MAC_OPTS = intrinsic.assign(intrinsic.create(null), { maxIterations: 1 });
97
102
 
package/lib/pki-build.js CHANGED
@@ -29,6 +29,7 @@ var _stringify = intrinsic.stringify;
29
29
  var _mapIntrinsic = intrinsic.map;
30
30
  var _bufferConcatIntrinsic = intrinsic.bufferConcat;
31
31
  var OID_SKI = oid.byName("subjectKeyIdentifier");
32
+ var OID_SAPKI = oid.byName("subjectAltPublicKeyInfo");
32
33
 
33
34
  function _isAlphaCode(c) { return (c >= 65 && c <= 90) || (c >= 97 && c <= 122); }
34
35
  function _isDigitCode(c) { return c >= 48 && c <= 57; }
@@ -1375,15 +1376,15 @@ function makeBuilder(ctx) {
1375
1376
  });
1376
1377
  }
1377
1378
  var pub;
1378
- try { pub = nodeCrypto.createPublicKey({ key: spki, format: "der", type: "spki" }); }
1379
+ try { pub = guard.crypto.publicKey({ key: spki, format: "der", type: "spki" }); }
1379
1380
  catch (e) { throw E("bad-input", "the public key could not be imported for the signature self-check", e); }
1380
1381
  var s = scheme.sign, ok;
1381
1382
  try {
1382
- if (s.name === "ECDSA") ok = nodeCrypto.verify(scheme.digest, preimage, { key: pub, dsaEncoding: "der" }, sig);
1383
- else if (s.name === "RSA-PSS") ok = nodeCrypto.verify(scheme.digest, preimage, { key: pub, padding: nodeCrypto.constants.RSA_PKCS1_PSS_PADDING, saltLength: s.saltLength }, sig);
1384
- else if (s.name === "RSASSA-PKCS1-v1_5") ok = nodeCrypto.verify(scheme.digest, preimage, pub, sig);
1383
+ if (s.name === "ECDSA") ok = guard.crypto.verify(scheme.digest, preimage, { key: pub, dsaEncoding: "der" }, sig);
1384
+ else if (s.name === "RSA-PSS") ok = guard.crypto.verify(scheme.digest, preimage, { key: pub, padding: guard.crypto.padding.RSA_PKCS1_PSS, saltLength: s.saltLength }, sig);
1385
+ else if (s.name === "RSASSA-PKCS1-v1_5") ok = guard.crypto.verify(scheme.digest, preimage, pub, sig);
1385
1386
  // allow:eddsa-verify-without-loworder-gate -- a self-check that OUR just-produced signature verifies
1386
- else ok = nodeCrypto.verify(null, preimage, pub, sig);
1387
+ else ok = guard.crypto.verify(null, preimage, pub, sig);
1387
1388
  } catch (e) { throw E("bad-input", "the signature self-check could not run against the public key", e); }
1388
1389
  if (!ok) throw E("bad-input", "the signing key does not correspond to the public key -- the signature would not verify");
1389
1390
  }
@@ -1404,6 +1405,7 @@ function makeBuilder(ctx) {
1404
1405
  extMsCertificateTemplate: extMsCertificateTemplate, extMsCaVersion: extMsCaVersion,
1405
1406
  extMsPreviousCertHash: extMsPreviousCertHash, extMsEnrollCertType: extMsEnrollCertType,
1406
1407
  spkiKeyId: spkiKeyId, skiKeyId: skiKeyId, skiValueOf: skiValueOf,
1408
+ altPublicKeyValueOf: altPublicKeyValueOf,
1407
1409
  reqDer: reqDer, reqDenseArray: reqDenseArray, reqDerSequence: reqDerSequence, assertValidSpki: assertValidSpki, assertValidExtension: assertValidExtension,
1408
1410
  assertExtnValueIsDer: assertExtnValueIsDer,
1409
1411
  assertCertCriticality: assertCertCriticality,
@@ -1435,6 +1437,28 @@ function skiValueOf(parsedCert) {
1435
1437
  catch (_e) { return null; } // allow:swallow-unverified -- a malformed SKI is read as absent; a signer then derives the authorityKeyIdentifier keyIdentifier from the public key, and a verifier matches on nothing
1436
1438
  }
1437
1439
 
1440
+ /** @internal The alternative public key a parsed certificate PUBLISHES, from its
1441
+ * subjectAltPublicKeyInfo extension (ITU-T X.509 (2019) clause 9.8.2), or null when it carries none.
1442
+ *
1443
+ * A signer needs this to bind an alternative signature to the issuer the same way the native signature
1444
+ * is already bound: the native key is read out of the issuer certificate, while the alternative key is
1445
+ * handed in beside it, so without this the two can be different keys. Clause 9.8.4 has a relying party
1446
+ * check an alternative signature with the issuer's alternative public key, which is the one in the
1447
+ * issuer's own certificate, so a signature made with any other key verifies for nobody. */
1448
+ function altPublicKeyValueOf(parsedCert) {
1449
+ var exts = intrinsic.filter(parsedCert.extensions, function (e) { return e.oid === OID_SAPKI; });
1450
+ if (!exts.length) return null;
1451
+ /** @internal The extension's value IS a SubjectPublicKeyInfo, so unlike subjectKeyIdentifier there is
1452
+ * no inner OCTET STRING to read through: the bytes are the SPKI. They are decoded only to confirm the
1453
+ * shape, and the original bytes returned rather than a re-encoding, so the comparison a signer makes is
1454
+ * against what the certificate actually carries. */
1455
+ try {
1456
+ var node = asn1.decode(exts[0].value);
1457
+ if (!node.constructed || node.tagNumber !== asn1.TAGS.SEQUENCE) return null;
1458
+ return _bufferFrom(exts[0].value);
1459
+ } catch (_e) { return null; } // allow:swallow-unverified -- an unreadable extension publishes no key, and a signer then refuses rather than binding to bytes it could not decode
1460
+ }
1461
+
1438
1462
  /** @internal Whether two SubjectPublicKeyInfo encodings carry the same public key: byte-equal, or
1439
1463
  * equal under the runtime's key comparison once both import. */
1440
1464
  function samePublicKey(spkiA, spkiB) {
@@ -1443,8 +1467,8 @@ function samePublicKey(spkiA, spkiB) {
1443
1467
  var ka, kb;
1444
1468
  // allow:swallow-unverified an SPKI the key layer cannot import matches nothing, which is the
1445
1469
  try {
1446
- ka = nodeCrypto.createPublicKey({ key: a, format: "der", type: "spki" });
1447
- kb = nodeCrypto.createPublicKey({ key: bb, format: "der", type: "spki" });
1470
+ ka = guard.crypto.publicKey({ key: a, format: "der", type: "spki" });
1471
+ kb = guard.crypto.publicKey({ key: bb, format: "der", type: "spki" });
1448
1472
  } catch (_e) { return false; }
1449
1473
  /** @internal Two keys of different types are never the same key, and comparing them through
1450
1474
  * the runtime's equality leaves a fault queued in the crypto library that surfaces on the