@blamejs/pki 0.6.14 → 0.6.16
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 +27 -1
- package/lib/acme.js +59 -815
- package/lib/asn1-der.js +7 -245
- package/lib/attrcert-sign.js +12 -260
- package/lib/attrcert-verify.js +1 -7
- package/lib/byte-reader.js +0 -8
- package/lib/byte-writer.js +0 -11
- package/lib/cbor-det.js +0 -54
- package/lib/cmc-build.js +15 -357
- package/lib/cmc-verify.js +6 -239
- package/lib/cmp-build.js +41 -317
- package/lib/cmp-session.js +73 -760
- package/lib/cmp-verify.js +32 -488
- package/lib/cms-compress.js +3 -32
- package/lib/cms-decrypt.js +29 -294
- package/lib/cms-encrypt.js +14 -234
- package/lib/cms-sign.js +29 -324
- package/lib/cms-verify.js +9 -500
- package/lib/composite-kem.js +2 -102
- package/lib/composite-sig.js +1 -77
- package/lib/constants.js +0 -249
- package/lib/crl-sign.js +9 -237
- package/lib/crl-verify.js +1 -10
- package/lib/crmf-sign.js +19 -198
- package/lib/crmf-verify.js +1 -12
- package/lib/csr-sign.js +3 -34
- package/lib/csr-verify.js +1 -12
- package/lib/ct.js +28 -253
- package/lib/edwards-point.js +10 -44
- package/lib/est.js +28 -764
- package/lib/framework-error.js +4 -182
- package/lib/guard-all.js +1 -58
- package/lib/guard-async.js +1 -36
- package/lib/guard-bytes.js +6 -650
- package/lib/guard-compress.js +5 -102
- package/lib/guard-crypto.js +1 -40
- package/lib/guard-der.js +2 -93
- package/lib/guard-encoding.js +2 -60
- package/lib/guard-header.js +1 -36
- package/lib/guard-identifier.js +7 -531
- package/lib/guard-intrinsic.js +1 -150
- package/lib/guard-json.js +10 -73
- package/lib/guard-limits.js +1 -77
- package/lib/guard-list.js +1 -49
- package/lib/guard-name.js +3 -209
- package/lib/guard-parsed.js +7 -335
- package/lib/guard-range.js +1 -71
- package/lib/guard-secret.js +1 -102
- package/lib/guard-text.js +1 -64
- package/lib/guard-time.js +1 -97
- package/lib/hpke.js +3 -105
- package/lib/http-digest.js +14 -142
- package/lib/http-retry-after.js +1 -47
- package/lib/http-transport.js +21 -131
- package/lib/inspect.js +30 -284
- package/lib/ip-utils.js +6 -47
- package/lib/jose.js +7 -142
- package/lib/key.js +2 -67
- package/lib/lint.js +9 -194
- package/lib/merkle.js +1 -27
- package/lib/mime.js +9 -82
- package/lib/ocsp-request-verify.js +1 -14
- package/lib/ocsp-verify.js +1 -82
- package/lib/ocsp.js +11 -169
- package/lib/oid.js +5 -337
- package/lib/path-validate.js +103 -1492
- package/lib/pbes2.js +2 -89
- package/lib/pkcs12-build.js +31 -379
- package/lib/pki-build.js +64 -290
- package/lib/rc2.js +2 -18
- package/lib/rfc3339.js +4 -27
- package/lib/scep.js +140 -286
- package/lib/schema-all.js +0 -122
- package/lib/schema-attrcert.js +4 -146
- package/lib/schema-c509.js +153 -802
- package/lib/schema-cmc.js +5 -220
- package/lib/schema-cmp.js +2 -223
- package/lib/schema-cms.js +13 -427
- package/lib/schema-crl.js +5 -68
- package/lib/schema-crmf.js +6 -167
- package/lib/schema-csr.js +1 -54
- package/lib/schema-csrattrs.js +2 -87
- package/lib/schema-engine.js +0 -152
- package/lib/schema-ocsp.js +0 -117
- package/lib/schema-pkcs12.js +2 -93
- package/lib/schema-pkcs8.js +3 -35
- package/lib/schema-pkix.js +24 -655
- package/lib/schema-smime.js +0 -54
- package/lib/schema-tsp.js +3 -99
- package/lib/schema-x509.js +4 -63
- package/lib/shbs.js +11 -95
- package/lib/sign-scheme.js +2 -97
- package/lib/sigstore.js +9 -169
- package/lib/sleep.js +2 -8
- package/lib/smime.js +15 -408
- package/lib/tls-cert-compress.js +0 -62
- package/lib/trust.js +19 -185
- package/lib/tsp-sign.js +16 -270
- package/lib/validator-all.js +1 -30
- package/lib/validator-attcert.js +2 -46
- package/lib/validator-cose.js +2 -153
- package/lib/validator-keydesc.js +1 -37
- package/lib/validator-sig.js +1 -46
- package/lib/validator-tls.js +1 -88
- package/lib/validator-tpm.js +19 -122
- package/lib/verify-seam.js +1 -24
- package/lib/webauthn-mds.js +3 -400
- package/lib/webauthn.js +12 -833
- package/lib/webcrypto.js +5 -485
- package/lib/x509-sign.js +4 -108
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/http-transport.js
CHANGED
|
@@ -58,37 +58,22 @@ var DEFAULT_TIMEOUT = constants.TIME.seconds(30);
|
|
|
58
58
|
var MAX_TIMEOUT = constants.TIME.seconds(600);
|
|
59
59
|
var DEFAULT_MIN_VERSION = "TLSv1.2";
|
|
60
60
|
|
|
61
|
-
// A config-time budget: cap() with the caller's typed factory + a >= 1 floor and an
|
|
62
|
-
// upper bound (maxResponseBytes may only be tightened DOWNWARD from the default). A
|
|
63
|
-
// NaN / negative / over-max value is a typed reject, never a silently-disabled bound.
|
|
64
61
|
function _budget(value, key, dflt, max, E, code) {
|
|
65
62
|
return guard.limits.cap(value, key, dflt, { E: E, code: code, min: 1, max: max, label: key });
|
|
66
63
|
}
|
|
67
64
|
|
|
68
|
-
// Node's `ca` option expects PEM. A raw DER certificate Buffer, the toolkit's native trust-anchor
|
|
69
|
-
// form (e.g. what pki.est.cacerts / parseCertsOnly return), is silently ignored there, so the
|
|
70
|
-
// connection would fail server authentication despite a valid anchor. Wrap a DER anchor as PEM
|
|
71
|
-
// (a Buffer already carrying PEM text, or a PEM string, passes through unchanged).
|
|
72
|
-
// The JS regex \s code points representable in a latin1 string (0..255): TAB LF VT FF CR SP NBSP.
|
|
73
|
-
// Built at load so the source stays pure ASCII. The armor scan runs over a latin1 prefix, where these
|
|
74
|
-
// are the only \s characters that can appear, so this is exactly /^\s*/ for that input.
|
|
75
65
|
var _LATIN1_WS = String.fromCharCode(0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x20, 0xa0);
|
|
76
66
|
|
|
77
|
-
// PEM armor at the START after optional whitespace, decided by character scan (no regex): skip the
|
|
78
|
-
// leading \s run, then require "-----BEGIN ".
|
|
79
67
|
function _looksLikePemArmor(s) {
|
|
80
68
|
var i = 0;
|
|
81
69
|
while (i < s.length && _LATIN1_WS.indexOf(s.charAt(i)) !== -1) i += 1;
|
|
82
70
|
return s.slice(i, i + 11) === "-----BEGIN ";
|
|
83
71
|
}
|
|
84
|
-
// Base64 wrapped to <=64-column lines, each terminated by LF (matching /.{1,64}/g -> "$&\n", which
|
|
85
|
-
// keeps a trailing LF on the final line).
|
|
86
72
|
function _wrap64Lines(s) {
|
|
87
73
|
var out = "";
|
|
88
74
|
for (var i = 0; i < s.length; i += 64) out += s.slice(i, i + 64) + "\n";
|
|
89
75
|
return out;
|
|
90
76
|
}
|
|
91
|
-
// The surrounding "[" "]" of an IPv6 literal removed, else the string unchanged (matching /^\[(.*)\]$/).
|
|
92
77
|
function _stripIpv6Brackets(s) {
|
|
93
78
|
return (s.length >= 2 && s.charAt(0) === "[" && s.charAt(s.length - 1) === "]") ? s.slice(1, s.length - 1) : s;
|
|
94
79
|
}
|
|
@@ -96,9 +81,6 @@ function _stripIpv6Brackets(s) {
|
|
|
96
81
|
function _pemifyAnchor(a) {
|
|
97
82
|
if (typeof a === "string") return a;
|
|
98
83
|
if (Buffer.isBuffer(a)) {
|
|
99
|
-
// Detect PEM by its armor at the START (after optional whitespace) only: a DER certificate begins
|
|
100
|
-
// with the SEQUENCE tag 0x30 and may itself contain the bytes "-----BEGIN" in a subject / extension
|
|
101
|
-
// value, so an anywhere-search would leave such a DER anchor unwrapped and unusable.
|
|
102
84
|
if (_looksLikePemArmor(a.toString("latin1", 0, 64))) return a;
|
|
103
85
|
var b64 = _wrap64Lines(a.toString("base64"));
|
|
104
86
|
return "-----BEGIN CERTIFICATE-----\n" + b64 + "-----END CERTIFICATE-----\n";
|
|
@@ -110,10 +92,6 @@ function _pemifyAnchors(anchors) {
|
|
|
110
92
|
return list;
|
|
111
93
|
}
|
|
112
94
|
|
|
113
|
-
// The OS system certificate store plus node's bundled roots (cached; the read is not free), for a
|
|
114
|
-
// tls.useSystemStore opt-in. Node's default (no `ca`) is just the bundled Mozilla set, so a server
|
|
115
|
-
// chaining to an OS-installed enterprise CA would fail without this. On a node too old for
|
|
116
|
-
// tls.getCACertificates the cache stays empty and the caller falls back to node's default store.
|
|
117
95
|
var _systemCaCache = null;
|
|
118
96
|
function _systemCa() {
|
|
119
97
|
if (_systemCaCache !== null) return _systemCaCache;
|
|
@@ -121,73 +99,54 @@ function _systemCa() {
|
|
|
121
99
|
if (typeof nodeTls.getCACertificates === "function") {
|
|
122
100
|
["system", "bundled"].forEach(function (t) {
|
|
123
101
|
// allow:swallow-unverified a store TYPE unsupported on this node is skipped; the other type
|
|
124
|
-
|
|
125
|
-
try { var c = nodeTls.getCACertificates(t); if (Array.isArray(c)) out = out.concat(c); } catch (_e) { /* unsupported type */ }
|
|
102
|
+
try { var c = nodeTls.getCACertificates(t); if (Array.isArray(c)) out = out.concat(c); } catch (_e) { }
|
|
126
103
|
});
|
|
127
104
|
}
|
|
128
105
|
_systemCaCache = out;
|
|
129
106
|
return out;
|
|
130
107
|
}
|
|
131
108
|
|
|
132
|
-
// Classify a bare IP string (no brackets) as a private / loopback / link-local / reserved destination an
|
|
133
|
-
// untrusted URL must not reach: RFC 1918 + loopback + this-network + multicast/reserved + 169.254 (cloud
|
|
134
|
-
// metadata) + CGNAT for IPv4, and loopback / unspecified / IPv4-mapped / ULA (fc00::/7) + link-local (fe80::/10)
|
|
135
|
-
// for IPv6. This is the same range set the AIA literal pre-check applies (pki.path.build reuses it), enforced
|
|
136
|
-
// here at DNS-resolution time so a hostname pointing at an internal address is caught too. A malformed IP fails
|
|
137
|
-
// closed (net.isIP === 0 -> not a v4/v6 arm -> the caller treats a non-IP as un-judgeable, never as public).
|
|
138
109
|
function _isBlockedIp(ip) {
|
|
139
110
|
var fam = nodeNet.isIP(ip);
|
|
140
111
|
if (fam === 4) {
|
|
141
|
-
// net.isIP === 4 guarantees exactly four octets 0..255. Block the complete IANA special-purpose /
|
|
142
|
-
// non-global set (RFC 6890) so an untrusted destination can reach only globally-routable public space.
|
|
143
112
|
var o = ip.split("."), a = +o[0], b = +o[1], c = +o[2];
|
|
144
|
-
return a === 0 || a === 10 || a === 127 || a >= 224 ||
|
|
145
|
-
(a === 100 && b >= 64 && b <= 127) ||
|
|
146
|
-
(a === 169 && b === 254) ||
|
|
147
|
-
(a === 172 && b >= 16 && b <= 31) ||
|
|
148
|
-
(a === 192 && b === 168) ||
|
|
149
|
-
(a === 192 && b === 0 && (c === 0 || c === 2)) ||
|
|
150
|
-
(a === 192 && b === 88 && c === 99) ||
|
|
151
|
-
(a === 198 && (b === 18 || b === 19)) ||
|
|
152
|
-
(a === 198 && b === 51 && c === 100) ||
|
|
153
|
-
(a === 203 && b === 0 && c === 113);
|
|
113
|
+
return a === 0 || a === 10 || a === 127 || a >= 224 ||
|
|
114
|
+
(a === 100 && b >= 64 && b <= 127) ||
|
|
115
|
+
(a === 169 && b === 254) ||
|
|
116
|
+
(a === 172 && b >= 16 && b <= 31) ||
|
|
117
|
+
(a === 192 && b === 168) ||
|
|
118
|
+
(a === 192 && b === 0 && (c === 0 || c === 2)) ||
|
|
119
|
+
(a === 192 && b === 88 && c === 99) ||
|
|
120
|
+
(a === 198 && (b === 18 || b === 19)) ||
|
|
121
|
+
(a === 198 && b === 51 && c === 100) ||
|
|
122
|
+
(a === 203 && b === 0 && c === 113);
|
|
154
123
|
}
|
|
155
124
|
if (fam === 6) {
|
|
156
125
|
var l = ip.toLowerCase();
|
|
157
|
-
if (l.indexOf("::ffff:") === 0) return true;
|
|
126
|
+
if (l.indexOf("::ffff:") === 0) return true;
|
|
158
127
|
var parts = l.split(":");
|
|
159
|
-
var h = parseInt(parts[0], 16);
|
|
160
|
-
if (!(h >= 0x2000 && h <= 0x3fff)) return true;
|
|
161
|
-
// Within 2000::/3, carve out the non-globally-routable special-purpose sub-ranges (IANA IPv6 Special-Purpose
|
|
162
|
-
// Address Registry) an attacker could route to an internal service: a first-hextet allow of the whole block
|
|
163
|
-
// would admit them. The second hextet is 0 when "::" compresses it (parts[1] empty).
|
|
128
|
+
var h = parseInt(parts[0], 16);
|
|
129
|
+
if (!(h >= 0x2000 && h <= 0x3fff)) return true;
|
|
164
130
|
var h2 = parts[1] ? parseInt(parts[1], 16) : 0;
|
|
165
|
-
if (h === 0x2002) return true;
|
|
166
|
-
if (h === 0x2001 && h2 < 0x0200) return true;
|
|
167
|
-
if (h === 0x2001 && h2 === 0x0db8) return true;
|
|
168
|
-
if (h === 0x3fff && h2 < 0x1000) return true;
|
|
131
|
+
if (h === 0x2002) return true;
|
|
132
|
+
if (h === 0x2001 && h2 < 0x0200) return true;
|
|
133
|
+
if (h === 0x2001 && h2 === 0x0db8) return true;
|
|
134
|
+
if (h === 0x3fff && h2 < 0x1000) return true;
|
|
169
135
|
return false;
|
|
170
136
|
}
|
|
171
137
|
return false;
|
|
172
138
|
}
|
|
173
139
|
|
|
174
|
-
// A DNS-rebinding-safe SSRF filter, installed as node's `lookup` only when a request opts into
|
|
175
|
-
// blockPrivateAddresses (the AIA fetch of an untrusted-cert URL). node connects to exactly the address this
|
|
176
|
-
// returns, with no second resolution, so checking the resolved address here pins it and closes the
|
|
177
|
-
// resolve/connect TOCTOU. Any private, loopback or link-local result fails the lookup, which surfaces as
|
|
178
|
-
// transport/blocked-address.
|
|
179
140
|
function _blockedAddrErr(hostname, address) {
|
|
180
141
|
var e = new Error("refusing to connect to " + hostname + " -> " + address + " (private / loopback / link-local address blocked)");
|
|
181
142
|
e.pkiBlockedAddress = true;
|
|
182
143
|
return e;
|
|
183
144
|
}
|
|
184
|
-
// Built over an injectable resolver (defaults to nodeDns.lookup) so every branch (a resolve error, the
|
|
185
|
-
// options.all array shape, a blocked result, a permitted result) is unit-testable without a live DNS.
|
|
186
145
|
function _makeGuardedLookup(lookupFn) {
|
|
187
146
|
return function guardedLookup(hostname, options, callback) {
|
|
188
147
|
lookupFn(hostname, options || {}, function (err, address, family) {
|
|
189
148
|
if (err) return callback(err);
|
|
190
|
-
if (Array.isArray(address)) {
|
|
149
|
+
if (Array.isArray(address)) {
|
|
191
150
|
for (var i = 0; i < address.length; i++) if (_isBlockedIp(address[i].address)) return callback(_blockedAddrErr(hostname, address[i].address));
|
|
192
151
|
return callback(null, address);
|
|
193
152
|
}
|
|
@@ -198,12 +157,6 @@ function _makeGuardedLookup(lookupFn) {
|
|
|
198
157
|
}
|
|
199
158
|
var _guardedLookup = _makeGuardedLookup(nodeDns.lookup);
|
|
200
159
|
|
|
201
|
-
// Classify a node request/TLS error into the transport's fail-closed verdict: a
|
|
202
|
-
// blocked-address lookup rejection; a protocol-version mismatch is the TLS floor; a
|
|
203
|
-
// certificate / identity / handshake failure is a server-authentication failure;
|
|
204
|
-
// anything else is a generic transport error. Every arm threads the raw fault as
|
|
205
|
-
// `.cause`, so the diagnostic survives.
|
|
206
|
-
// SELF.?SIGNED : "self", an optional single non-line-terminator character, then "signed" (in a lowercased string).
|
|
207
160
|
function _hasSelfSigned(l) {
|
|
208
161
|
for (var i = 0; i + 4 <= l.length; i++) {
|
|
209
162
|
if (l.charCodeAt(i) === 0x73 && l.charCodeAt(i + 1) === 0x65 && l.charCodeAt(i + 2) === 0x6c && l.charCodeAt(i + 3) === 0x66) {
|
|
@@ -214,7 +167,6 @@ function _hasSelfSigned(l) {
|
|
|
214
167
|
}
|
|
215
168
|
return false;
|
|
216
169
|
}
|
|
217
|
-
// \bWORD\b : `word` (lowercase) bounded on both sides by a non-word character ([a-z0-9_]) or a string edge.
|
|
218
170
|
function _hasWordBounded(l, word) {
|
|
219
171
|
var at = l.indexOf(word);
|
|
220
172
|
while (at !== -1) {
|
|
@@ -231,10 +183,8 @@ function _classifyError(e, C) {
|
|
|
231
183
|
if (e && e.pkiBlockedAddress) return C("blocked-address");
|
|
232
184
|
var s = String((e && e.code) || "") + " " + String((e && e.message) || "");
|
|
233
185
|
var l = s.toLowerCase();
|
|
234
|
-
// /PROTOCOL_VERSION|UNSUPPORTED_PROTOCOL|VERSION_TOO_LOW|WRONG_VERSION|NO_PROTOCOLS_AVAILABLE|INAPPROPRIATE_FALLBACK/i
|
|
235
186
|
if (l.indexOf("protocol_version") !== -1 || l.indexOf("unsupported_protocol") !== -1 || l.indexOf("version_too_low") !== -1 ||
|
|
236
187
|
l.indexOf("wrong_version") !== -1 || l.indexOf("no_protocols_available") !== -1 || l.indexOf("inappropriate_fallback") !== -1) return C("tls-floor");
|
|
237
|
-
// /CERT|SELF.?SIGNED|VERIFY|ALTNAME|HOSTNAME|DEPTH_ZERO|LOCAL_ISSUER|HANDSHAKE|\bSSL\b|\bTLS\b/i
|
|
238
188
|
if (l.indexOf("cert") !== -1 || l.indexOf("verify") !== -1 || l.indexOf("altname") !== -1 || l.indexOf("hostname") !== -1 ||
|
|
239
189
|
l.indexOf("depth_zero") !== -1 || l.indexOf("local_issuer") !== -1 || l.indexOf("handshake") !== -1 ||
|
|
240
190
|
_hasSelfSigned(l) || _hasWordBounded(l, "ssl") || _hasWordBounded(l, "tls")) return C("server-auth-failed");
|
|
@@ -285,9 +235,6 @@ function httpsTransport(defaults) {
|
|
|
285
235
|
function C(name) { return prefix + "/" + name; }
|
|
286
236
|
var tlsDefaults = defaults.tls || {};
|
|
287
237
|
|
|
288
|
-
// Synchronous config validation + node-options build. Throws the typed verdict on any
|
|
289
|
-
// fault (a non-https URL, no trust anchor, a bad budget, a sub-floor minVersion) so the
|
|
290
|
-
// async executor below never has to `return reject(...)`. rejectUnauthorized is forced on.
|
|
291
238
|
function _prepare(request) {
|
|
292
239
|
var url;
|
|
293
240
|
try { url = new URL(String(request.url)); }
|
|
@@ -296,9 +243,6 @@ function httpsTransport(defaults) {
|
|
|
296
243
|
|
|
297
244
|
var reqTls = request.tls || {};
|
|
298
245
|
var anchors = reqTls.anchors !== undefined ? reqTls.anchors : tlsDefaults.anchors;
|
|
299
|
-
// System-store trust requires a STRICT boolean true; a non-boolean (a "false" / "true" string from
|
|
300
|
-
// JSON/env, a truthy object) is treated as absent, so a malformed config fails closed rather than
|
|
301
|
-
// silently trusting node's bundled roots.
|
|
302
246
|
var useSystem = (reqTls.useSystemStore !== undefined ? reqTls.useSystemStore : tlsDefaults.useSystemStore) === true;
|
|
303
247
|
var hasAnchors = anchors !== undefined && anchors !== null && !(Array.isArray(anchors) && anchors.length === 0);
|
|
304
248
|
if (!hasAnchors && !useSystem) throw E(C("no-trust-anchors"), "no explicit trust anchor and useSystemStore not set -- refusing an unpinned server (RFC 7030 sec. 3.6)");
|
|
@@ -312,21 +256,12 @@ function httpsTransport(defaults) {
|
|
|
312
256
|
var body = request.body;
|
|
313
257
|
if (guard.bytes.isByteSource(body)) body = guard.bytes.source(body, E, C("bad-input"), "the request body");
|
|
314
258
|
|
|
315
|
-
// Frame a body-bearing request with a fixed Content-Length computed from the EXACT body, so it is sent
|
|
316
|
-
// length-delimited rather than Transfer-Encoding: chunked (node's default when a body is written with no
|
|
317
|
-
// Content-Length -- which strict enrollment / CMP appliances reject). A caller-supplied content-length
|
|
318
|
-
// (any case) is dropped: the transport owns request framing and never trusts a possibly-mismatched value.
|
|
319
259
|
var reqHeaders = Object.assign({}, request.headers || {});
|
|
320
260
|
if (body != null && body !== "") {
|
|
321
|
-
// Drop BOTH caller-supplied framing headers before installing the fixed length: node rejects a request
|
|
322
|
-
// carrying Content-Length AND Transfer-Encoding together (HPE_INVALID_CONTENT_LENGTH), and the transport
|
|
323
|
-
// owns framing -- it never honors a caller's chunked directive or a mismatched length.
|
|
324
261
|
Object.keys(reqHeaders).forEach(function (k) { var lk = k.toLowerCase(); if (lk === "content-length" || lk === "transfer-encoding") delete reqHeaders[k]; });
|
|
325
262
|
reqHeaders["Content-Length"] = Buffer.byteLength(body);
|
|
326
263
|
}
|
|
327
264
|
|
|
328
|
-
// URL.hostname keeps the square brackets on an IPv6 literal ([2001:db8::1]); node's `hostname`
|
|
329
|
-
// option and its IP-detection expect the bare address, so strip the brackets once here.
|
|
330
265
|
var host = _stripIpv6Brackets(url.hostname);
|
|
331
266
|
var options = {
|
|
332
267
|
method: request.method || "GET",
|
|
@@ -335,42 +270,21 @@ function httpsTransport(defaults) {
|
|
|
335
270
|
path: url.pathname + url.search,
|
|
336
271
|
headers: reqHeaders,
|
|
337
272
|
minVersion: minVersion,
|
|
338
|
-
rejectUnauthorized: true,
|
|
339
|
-
// Opt out of connection pooling: node's keep-alive agent keys the socket pool on host + TLS
|
|
340
|
-
// options but NOT on checkServerIdentity, so a reused socket would skip a per-request identity /
|
|
341
|
-
// pinning callback. A fresh connection per request guarantees the identity check always runs.
|
|
273
|
+
rejectUnauthorized: true,
|
|
342
274
|
agent: false,
|
|
343
275
|
};
|
|
344
|
-
// Every TLS field falls back to the factory defaults (as anchors / minVersion already do), so a
|
|
345
|
-
// reusable transport built with pki.transport.https({ tls: { cert, key, servername } }) applies
|
|
346
|
-
// them to a request that carries no per-request tls (mTLS enrollment, a configured SNI).
|
|
347
276
|
var cert = reqTls.cert !== undefined ? reqTls.cert : tlsDefaults.cert;
|
|
348
277
|
var key = reqTls.key !== undefined ? reqTls.key : tlsDefaults.key;
|
|
349
278
|
var callerCsi = typeof reqTls.checkServerIdentity === "function" ? reqTls.checkServerIdentity
|
|
350
279
|
: (typeof tlsDefaults.checkServerIdentity === "function" ? tlsDefaults.checkServerIdentity : null);
|
|
351
|
-
// SNI (and RFC 6125 identity) uses a hostname; node forbids a servername that is an IP
|
|
352
|
-
// literal, so it is omitted for an IP host (node then matches the IP against the cert's
|
|
353
|
-
// IP SANs). A caller connecting by hostname gets SNI + name verification by default.
|
|
354
|
-
// The per-request servername falls back to the factory default only when it is ABSENT
|
|
355
|
-
// (as cert/key do); an explicit value, INCLUDING null, overrides the default, so a caller
|
|
356
|
-
// that sets servername to null on a cross-origin hop suppresses a configured default SNI
|
|
357
|
-
// rather than falsy-coalescing back to it.
|
|
358
280
|
var sni = (reqTls.servername !== undefined ? reqTls.servername : tlsDefaults.servername) || host;
|
|
359
281
|
if (sni && !nodeNet.isIP(sni)) options.servername = sni;
|
|
360
|
-
// Build node's `ca`: the explicit anchors (DER wrapped as PEM) and, when useSystemStore is opted
|
|
361
|
-
// in, the OS system store + bundled roots. Node's `ca` REPLACES the default set, so both sources
|
|
362
|
-
// are merged here. Left unset only when no anchors and (on an older node) the system store is
|
|
363
|
-
// empty -- then node's default bundled store applies (the documented fallback).
|
|
364
282
|
var caList = [];
|
|
365
283
|
if (hasAnchors) caList = caList.concat(_pemifyAnchors(anchors));
|
|
366
284
|
if (useSystem) caList = caList.concat(_systemCa());
|
|
367
285
|
if (caList.length) options.ca = caList;
|
|
368
286
|
if (cert) options.cert = cert;
|
|
369
287
|
if (key) options.key = key;
|
|
370
|
-
// A caller checkServerIdentity may only TIGHTEN, never REPLACE, name verification: node uses a
|
|
371
|
-
// supplied callback in place of its default RFC 6125 check, so a hook that returns undefined
|
|
372
|
-
// (pinning/metrics only) would silently disable hostname verification. Run node's default first
|
|
373
|
-
// and reject on its verdict; the caller's hook adds checks only after the default has passed.
|
|
374
288
|
if (callerCsi) {
|
|
375
289
|
options.checkServerIdentity = function (host, cert2) {
|
|
376
290
|
var baseErr = nodeTls.checkServerIdentity(host, cert2);
|
|
@@ -378,15 +292,8 @@ function httpsTransport(defaults) {
|
|
|
378
292
|
return callerCsi(host, cert2);
|
|
379
293
|
};
|
|
380
294
|
}
|
|
381
|
-
// SSRF at resolution time: when a request opts in (the AIA fetch of an untrusted-cert URL), install a
|
|
382
|
-
// lookup that refuses -- and pins -- a private / loopback / link-local resolved address, so a hostname
|
|
383
|
-
// pointing at an internal service is blocked even though the literal-address check saw only a DNS name.
|
|
384
|
-
// A STRICT boolean true (a truthy string/object is treated as absent) keeps a malformed config fail-open-free.
|
|
385
295
|
var blockPrivate = (request.blockPrivateAddresses !== undefined ? request.blockPrivateAddresses : defaults.blockPrivateAddresses) === true;
|
|
386
296
|
if (blockPrivate) {
|
|
387
|
-
// Node does NOT invoke a custom `lookup` for an IP-LITERAL host (there is nothing to resolve), so the
|
|
388
|
-
// resolver alone would let a literal private / loopback / link-local destination through. Reject a blocked
|
|
389
|
-
// literal here, before installing the resolver -- so the option blocks a literal AND a resolved hostname.
|
|
390
297
|
if (_isBlockedIp(host)) throw E(C("blocked-address"), "refusing to connect to the private / loopback / link-local address literal " + host);
|
|
391
298
|
options.lookup = _guardedLookup;
|
|
392
299
|
}
|
|
@@ -409,29 +316,17 @@ function httpsTransport(defaults) {
|
|
|
409
316
|
settled = true;
|
|
410
317
|
clearTimer();
|
|
411
318
|
// allow:swallow-unverified req.destroy() is idempotent and does not throw in practice; a
|
|
412
|
-
|
|
413
|
-
try { if (req) req.destroy(); } catch (_e) { /* best-effort abort */ }
|
|
319
|
+
try { if (req) req.destroy(); } catch (_e) { }
|
|
414
320
|
reject(E(code, msg, cause));
|
|
415
321
|
}
|
|
416
|
-
// An independent wall-clock deadline bounds DNS resolution and connection setup as well as
|
|
417
|
-
// socket inactivity: node's request/socket timeout does not run during a DNS lookup, so a
|
|
418
|
-
// stalled resolver would otherwise leave this promise pending forever despite a finite timeout.
|
|
419
322
|
timer = setTimeout(function () { fail(C("timeout"), "the request timed out after " + prep.timeout + "ms"); }, prep.timeout);
|
|
420
323
|
try {
|
|
421
324
|
req = nodeHttps.request(prep.options, function (res) {
|
|
422
|
-
// Capture the TLS session facts while the socket is live -- getProtocol() / getCipher() /
|
|
423
|
-
// getPeerCertificate() return null once the socket detaches at stream end. The negotiated cipher lets a
|
|
424
|
-
// caller refuse a NULL / anonymous / EXPORT suite before it trusts confidentiality (RFC 7030 sec. 4.4).
|
|
425
325
|
var proto = res.socket && res.socket.getProtocol ? res.socket.getProtocol() : null;
|
|
426
326
|
var cipher = res.socket && res.socket.getCipher ? res.socket.getCipher() : null;
|
|
427
327
|
var peer = res.socket && res.socket.getPeerCertificate ? res.socket.getPeerCertificate() : null;
|
|
428
|
-
// Pre-check a declared content-length so an oversized body is refused before it streams.
|
|
429
328
|
var declared = parseInt((res.headers || {})["content-length"], 10);
|
|
430
329
|
if (Number.isFinite(declared) && declared > prep.maxBytes) { fail(C("response-too-large"), "the declared content-length " + declared + " exceeds the " + prep.maxBytes + "-byte cap (RFC 7030 sec. 6)"); return; }
|
|
431
|
-
// Accumulate into a single doubling buffer rather than an array of chunks: a body sent as
|
|
432
|
-
// many tiny chunks (one-byte TLS records) would otherwise retain millions of Buffer objects
|
|
433
|
-
// under the byte cap and exhaust the heap. One live buffer bounds the metadata as well as the
|
|
434
|
-
// bytes; only the written prefix [0, len) is ever surfaced (the unwritten tail is never read).
|
|
435
330
|
var buf = Buffer.allocUnsafe(0);
|
|
436
331
|
var len = 0;
|
|
437
332
|
var total = 0;
|
|
@@ -463,16 +358,11 @@ function httpsTransport(defaults) {
|
|
|
463
358
|
res.on("error", function (e) { fail(C("transport-error"), "the response stream failed", e); });
|
|
464
359
|
});
|
|
465
360
|
req.on("error", function (e) { fail(_classifyError(e, C), "the request failed: " + ((e && e.message) || String(e)), e); });
|
|
466
|
-
// Write a provided body for ANY method (POST / PUT / PATCH / ...), not only POST, so a body
|
|
467
|
-
// is never silently dropped on a body-bearing request through this generic transport seam.
|
|
468
361
|
if (prep.body != null && prep.body !== "") req.write(prep.body);
|
|
469
362
|
req.end();
|
|
470
363
|
} catch (e) { fail(C("transport-error"), "the request could not be initiated: " + ((e && e.message) || String(e)), e); }
|
|
471
364
|
});
|
|
472
365
|
};
|
|
473
|
-
// Advertise that this transport HONORS the blockPrivateAddresses request flag (it filters and pins a resolved
|
|
474
|
-
// address). A consumer of an UNTRUSTED URL (pki.path.build's AIA fetch) checks this marker before relying on the
|
|
475
|
-
// flag for SSRF protection -- an injected transport that does not set it is treated as unguarded (fail-closed).
|
|
476
366
|
_transportFn.blocksPrivateAddresses = true;
|
|
477
367
|
return _transportFn;
|
|
478
368
|
}
|