@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
@@ -72,6 +72,15 @@ var nodeDns = require("node:dns");
72
72
  var nodeUtil = require("node:util");
73
73
  var constants = require("./constants");
74
74
  var guard = require("./guard-all");
75
+ var intrinsic = require("./guard-intrinsic");
76
+ /** @internal `resolve` and `reject` build their promise from the RECEIVER, so reading the global
77
+ * binding at the call hands the construction to whatever that binding holds. A refusal returned as a
78
+ * rejected promise is a verdict: a replacement whose `reject` resolves lets a request the preparation
79
+ * step rejected proceed, and `peerChain`'s refusal to tunnel through a proxy is one of those. Taken at
80
+ * load. */
81
+ var _Promise = intrinsic.Promise;
82
+ var _promiseResolve = intrinsic.uncurry(intrinsic.promiseResolve);
83
+ var _promiseReject = intrinsic.uncurry(intrinsic.promiseReject);
75
84
  var frameworkError = require("./framework-error");
76
85
 
77
86
  var TransportError = frameworkError.TransportError;
@@ -124,6 +133,30 @@ function _tlsInfo(socket) {
124
133
  return { protocol: proto, cipher: cipher, peerCertificate: peer && peer.raw ? peer.raw : null, tlsUnique: tlsUnique, tlsExporter: tlsExporter };
125
134
  }
126
135
 
136
+ /** @internal The chain the TLS session resolved, leaf first, walked through the detailed peer
137
+ * certificate's `issuerCertificate` links. The walk is bounded twice over: the top certificate
138
+ * issues itself, so the identity comparison ends it, and a cap ends any cycle a runtime could
139
+ * report instead.
140
+ * IT IS NOT THE SAME AS WHAT THE ENDPOINT TRANSMITTED, which this comment used to claim. Node
141
+ * completes `issuerCertificate` from the trust store in scope, so an endpoint that sends its leaf
142
+ * alone still yields a chain whose upper entries came from the configured anchors rather than off
143
+ * the wire. Node exposes no API for the transmitted list, so the distinction cannot be recovered
144
+ * here and is stated instead of guessed at: filtering the entries that match an anchor would drop
145
+ * a root an endpoint really did send. */
146
+ function _peerChain(socket) {
147
+ if (!socket || typeof socket.getPeerCertificate !== "function") return [];
148
+ var out = [];
149
+ var seen = [];
150
+ var cur = socket.getPeerCertificate(true);
151
+ while (cur && Buffer.isBuffer(cur.raw) && out.length < constants.LIMITS.TLS_MAX_PEER_CHAIN) {
152
+ if (seen.indexOf(cur) !== -1) break;
153
+ seen.push(cur);
154
+ out.push(cur.raw);
155
+ cur = cur.issuerCertificate;
156
+ }
157
+ return out;
158
+ }
159
+
127
160
  function _pemifyAnchor(a) {
128
161
  if (typeof a === "string") return a;
129
162
  if (Buffer.isBuffer(a)) {
@@ -557,8 +590,8 @@ function httpsTransport(defaults) {
557
590
  request = request || {};
558
591
  var prep;
559
592
  try { prep = _prepare(request); }
560
- catch (e) { return Promise.reject(e); }
561
- return new Promise(function (resolve, reject) {
593
+ catch (e) { return _promiseReject(_Promise, e); }
594
+ return new _Promise(function (resolve, reject) {
562
595
  var settled = false;
563
596
  var req = null;
564
597
  var creq = null;
@@ -625,7 +658,7 @@ function httpsTransport(defaults) {
625
658
  function _writeBoundBody() {
626
659
  if (settled) return;
627
660
  var info = _tlsInfo(socket);
628
- Promise.resolve().then(function () { return prep.body(info); }).then(function (b) {
661
+ _promiseResolve(_Promise, undefined).then(function () { return prep.body(info); }).then(function (b) {
629
662
  if (settled) return;
630
663
  try {
631
664
  if (guard.bytes.isByteSource(b)) b = guard.bytes.source(b, E, C("bad-input"), "the channel-binding body");
@@ -745,7 +778,126 @@ function httpsTransport(defaults) {
745
778
  });
746
779
  };
747
780
  _transportFn.blocksPrivateAddresses = true;
781
+
782
+ /** @internal peerChain negotiates TLS and hands back the channel without sending a request. It
783
+ * prepares the connection through the SAME `_prepare` the request path uses, so the anchors, the
784
+ * SNI, the version floor, the identity hook, the private-address block and the URL scheme gate are
785
+ * one policy with two entry points rather than two policies. `tls.connect` takes `host` where
786
+ * `https.request` takes `hostname`, and ignores the method, path and headers `_prepare` also
787
+ * builds. A proxy is refused rather than bypassed: tunnelling belongs to the request path, and
788
+ * connecting directly while a proxy is configured would reach an origin the caller asked not to
789
+ * reach directly. */
790
+ _transportFn.peerChain = function peerChain(request) {
791
+ request = request || {};
792
+ var prep;
793
+ try { prep = _prepare({
794
+ url: request.url,
795
+ method: "GET",
796
+ tls: request.tls,
797
+ timeout: request.timeout,
798
+ blockPrivateAddresses: request.blockPrivateAddresses,
799
+ allowPlaintextHttp: false,
800
+ proxy: request.proxy,
801
+ }); }
802
+ catch (e) { return _promiseReject(_Promise, e); }
803
+ if (prep.proxy) {
804
+ return _promiseReject(_Promise, E(C("bad-proxy"),
805
+ "peerChain opens a direct connection and does not tunnel; fetch through a proxy with the request verb"));
806
+ }
807
+ var o = prep.options;
808
+ var connectOpts = {
809
+ host: o.hostname, port: o.port, minVersion: o.minVersion,
810
+ rejectUnauthorized: o.rejectUnauthorized,
811
+ };
812
+ if (o.servername !== undefined) connectOpts.servername = o.servername;
813
+ if (o.ca !== undefined) connectOpts.ca = o.ca;
814
+ if (o.cert !== undefined) connectOpts.cert = o.cert;
815
+ if (o.key !== undefined) connectOpts.key = o.key;
816
+ if (o.checkServerIdentity !== undefined) connectOpts.checkServerIdentity = o.checkServerIdentity;
817
+ if (o.lookup !== undefined) connectOpts.lookup = o.lookup;
818
+
819
+ return new _Promise(function (resolve, reject) {
820
+ var settled = false;
821
+ var socket;
822
+ var timer = null;
823
+ function done(fn, arg) {
824
+ if (settled) return;
825
+ settled = true;
826
+ if (timer !== null) { clearTimeout(timer); timer = null; }
827
+ try { if (socket) socket.destroy(); } catch (_de) { /* allow:swallow-unverified the socket is being abandoned either way */ }
828
+ fn(arg);
829
+ }
830
+ try { socket = nodeTls.connect(connectOpts); }
831
+ catch (e) {
832
+ reject(E(_classifyError(e, C), "the TLS connection failed: " + e.message, e));
833
+ return;
834
+ }
835
+ /** @internal The budget is an ABSOLUTE deadline, the same timer the request path above uses, and not
836
+ * `socket.setTimeout`: that one bounds INACTIVITY, so what it measures is the gap between bytes
837
+ * rather than the elapsed time a caller asked to be bounded by. A peer that keeps the socket busy
838
+ * without completing the handshake is the case the two differ on. The socket inactivity timeout is
839
+ * kept as well, since it ends a connection that goes silent sooner than the deadline would. */
840
+ timer = setTimeout(function () {
841
+ done(reject, E(C("timeout"), "the TLS handshake did not complete within " + prep.timeout + "ms"));
842
+ }, prep.timeout);
843
+ socket.setTimeout(prep.timeout, function () {
844
+ done(reject, E(C("timeout"), "the TLS handshake did not complete within " + prep.timeout + "ms"));
845
+ });
846
+ socket.once("secureConnect", function () {
847
+ var info = _tlsInfo(socket);
848
+ info.peerChain = _peerChain(socket);
849
+ done(resolve, info);
850
+ });
851
+ socket.once("error", function (e) {
852
+ var code = _classifyError(e, C);
853
+ done(reject, E(code || C("tls-failed"), "the TLS connection failed: " + e.message, e));
854
+ });
855
+ });
856
+ };
857
+
748
858
  return _transportFn;
749
859
  }
750
860
 
751
- module.exports = { https: httpsTransport, isBlockedIp: _isBlockedIp, _makeGuardedLookup: _makeGuardedLookup, MAX_TIMEOUT: MAX_TIMEOUT, snapshotProxy: snapshotProxy };
861
+ /**
862
+ * @primitive pki.transport.peerChain
863
+ * @signature pki.transport.peerChain(request, defaults?) -> Promise<channel>
864
+ * @since 0.8.32
865
+ * @status stable
866
+ * @spec RFC 8446, RFC 6125
867
+ * @defends server-impersonation (CWE-297), tls-downgrade (CWE-757), ssrf (CWE-918)
868
+ * @related pki.transport.https, pki.path.validate
869
+ *
870
+ * Negotiate TLS with an endpoint and return the channel it established, without sending a
871
+ * request: `{ protocol, cipher, peerCertificate, peerChain, tlsUnique, tlsExporter }`.
872
+ * `peerChain` is the chain the session resolved as DER, leaf first, walked up from the peer
873
+ * certificate and stopping at the one that issues itself. It is not a record of what crossed the
874
+ * wire: the runtime completes the chain from the trust anchors in scope, so an endpoint that sends
875
+ * only its leaf still yields the entries above it, and those came from the configured anchors.
876
+ * Treat the list as the chain this session built, and read `peerCertificate` for the one
877
+ * certificate the endpoint certainly sent. Nothing reaches the application behind the endpoint,
878
+ * which is the difference from making a request for the same purpose.
879
+ *
880
+ * The connection is prepared by the same code `pki.transport.https` prepares a request with,
881
+ * so the trust anchors, the SNI, the version floor, the `checkServerIdentity` hook, the
882
+ * private-address block and the https-only rule are one policy rather than a second copy of
883
+ * one. A request with neither an explicit anchor nor `useSystemStore` is refused
884
+ * (`transport/no-trust-anchors`) before any socket is opened, and an endpoint whose chain does
885
+ * not build to the configured anchors, or whose name does not match the URL, is refused
886
+ * (`transport/server-auth-failed`). A configured proxy is refused (`transport/bad-proxy`)
887
+ * rather than bypassed, since connecting directly would reach an origin the caller asked to
888
+ * reach through the proxy.
889
+ *
890
+ * **A chain this returns is not a validated one.** The handshake checked that it builds to a
891
+ * configured anchor and matches the name in the URL, and checked no revocation status and no
892
+ * policy. `pki.path.validate` is what validates a path.
893
+ *
894
+ * @example
895
+ * var channel = await pki.transport.peerChain(
896
+ * { url: "https://example.com/" }, { tls: { useSystemStore: true } });
897
+ * channel.peerChain.length; // the chain the session resolved
898
+ * pki.schema.x509.parse(channel.peerChain[0]).subject.dn; // the leaf's subject
899
+ * channel.peerCertificate; // the one certificate the endpoint sent
900
+ */
901
+ function peerChain(request, defaults) { return httpsTransport(defaults || {}).peerChain(request); }
902
+
903
+ module.exports = { https: httpsTransport, peerChain: peerChain, isBlockedIp: _isBlockedIp, _makeGuardedLookup: _makeGuardedLookup, MAX_TIMEOUT: MAX_TIMEOUT, snapshotProxy: snapshotProxy };
@@ -446,8 +446,8 @@ function _search(refs, entries, wildcardsAllowed) {
446
446
  pairs.tick();
447
447
  var got = _compare(refs[r], entries[e], wildcardsAllowed);
448
448
  if (got.disposition === "match") {
449
- return { matched: true, matchedReference: refs[r].input, matchedEntry: _entryLabel(entries[e]),
450
- ignored: ignored, reason: null };
449
+ return guard.verdict.of({ matched: true, matchedReference: refs[r].input, matchedEntry: _entryLabel(entries[e]),
450
+ ignored: ignored, reason: null });
451
451
  }
452
452
  if (got.disposition === "ignored") {
453
453
  var key = e + "|" + got.reason;
@@ -458,10 +458,10 @@ function _search(refs, entries, wildcardsAllowed) {
458
458
  }
459
459
  }
460
460
  }
461
- return { matched: false, matchedReference: null, matchedEntry: null, ignored: ignored,
461
+ return guard.verdict.of({ matched: false, matchedReference: null, matchedEntry: null, ignored: ignored,
462
462
  reason: entries.length === 0
463
463
  ? "the certificate presents no subjectAltName entry, and RFC 9525 sec. 2 forbids reading an identity from the subject"
464
- : "no presented identifier matched a reference identity" };
464
+ : "no presented identifier matched a reference identity" });
465
465
  }
466
466
 
467
467
  /**