@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.
- package/CHANGELOG.md +411 -0
- package/README.md +67 -16
- package/bin/pki.js +716 -30
- package/examples/issue-a-certificate.js +90 -0
- package/examples/post-quantum.js +66 -0
- package/examples/read-a-certificate.js +56 -0
- package/examples/revoke-and-check.js +86 -0
- package/examples/sign-and-verify-cms.js +63 -0
- package/index.js +18 -5
- package/lib/acme.js +223 -74
- package/lib/alt-sig.js +441 -0
- package/lib/asn1-der.js +48 -25
- package/lib/attrcert-sign.js +27 -19
- package/lib/cbor-det.js +24 -14
- package/lib/cmc-build.js +9 -2
- package/lib/cmc-verify.js +14 -3
- package/lib/cmp-build.js +17 -5
- package/lib/cmp-session.js +24 -11
- package/lib/cmp-verify.js +28 -19
- package/lib/cms-compress.js +3 -1
- package/lib/cms-decrypt.js +102 -7
- package/lib/cms-encrypt.js +59 -7
- package/lib/cms-sign.js +31 -9
- package/lib/cms-verify.js +22 -18
- package/lib/composite-kem.js +30 -18
- package/lib/composite-sig.js +29 -14
- package/lib/constants.js +28 -0
- package/lib/crl-sign.js +111 -19
- package/lib/crmf-sign.js +167 -15
- package/lib/csr-sign.js +117 -6
- package/lib/ct.js +939 -48
- package/lib/est.js +29 -16
- package/lib/framework-error.js +12 -0
- package/lib/guard-async.js +1 -1
- package/lib/guard-bytes.js +103 -20
- package/lib/guard-crypto.js +205 -3
- package/lib/guard-encoding.js +6 -1
- package/lib/guard-identifier.js +55 -8
- package/lib/guard-intrinsic.js +43 -0
- package/lib/guard-json.js +122 -9
- package/lib/guard-limits.js +12 -1
- package/lib/guard-list.js +63 -8
- package/lib/guard-name.js +167 -20
- package/lib/guard-range.js +43 -5
- package/lib/guard-text.js +41 -0
- package/lib/guard-verdict.js +39 -8
- package/lib/hpke.js +688 -42
- package/lib/http-transport.js +156 -4
- package/lib/identity-match.js +4 -4
- package/lib/inspect.js +448 -2
- package/lib/jose.js +93 -25
- package/lib/key.js +18 -13
- package/lib/lint.js +165 -1
- package/lib/merkle.js +233 -27
- package/lib/ocsp.js +27 -14
- package/lib/oid.js +14 -4
- package/lib/path-validate.js +143 -61
- package/lib/pkcs12-build.js +7 -2
- package/lib/pki-build.js +31 -7
- package/lib/possession.js +575 -0
- package/lib/related-cert.js +708 -0
- package/lib/revocation-fetch.js +6 -6
- package/lib/scep.js +15 -5
- package/lib/schema-c509.js +5 -2
- package/lib/schema-cmc.js +1 -1
- package/lib/schema-cmp.js +8 -8
- package/lib/schema-cms.js +2 -15
- package/lib/schema-csr.js +69 -0
- package/lib/schema-engine.js +10 -3
- package/lib/schema-pkix.js +267 -4
- package/lib/schema-tsp.js +7 -6
- package/lib/sign-scheme.js +22 -9
- package/lib/sigstore.js +684 -168
- package/lib/sleep.js +8 -1
- package/lib/smime.js +22 -1
- package/lib/tlog.js +1235 -0
- package/lib/tls-cert-compress.js +25 -8
- package/lib/tsp-sign.js +120 -35
- package/lib/tuf.js +1395 -0
- package/lib/validator-tpm.js +4 -1
- package/lib/verify-seam.js +7 -1
- package/lib/webauthn-mds.js +41 -4
- package/lib/webauthn.js +43 -25
- package/lib/webcrypto.js +48 -46
- package/lib/x509-sign.js +225 -19
- package/package.json +2 -1
- package/sbom.cdx.json +6 -6
package/lib/http-transport.js
CHANGED
|
@@ -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
|
|
561
|
-
return new
|
|
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
|
-
|
|
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
|
-
|
|
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 };
|
package/lib/identity-match.js
CHANGED
|
@@ -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
|
/**
|