@blamejs/pki 0.6.13 → 0.6.15

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 (114) hide show
  1. package/CHANGELOG.md +22 -1
  2. package/README.md +1 -1
  3. package/index.js +1 -1
  4. package/lib/acme.js +59 -815
  5. package/lib/asn1-der.js +7 -245
  6. package/lib/attrcert-sign.js +12 -260
  7. package/lib/attrcert-verify.js +1 -7
  8. package/lib/byte-reader.js +0 -8
  9. package/lib/byte-writer.js +0 -11
  10. package/lib/cbor-det.js +0 -54
  11. package/lib/cmc-build.js +15 -357
  12. package/lib/cmc-verify.js +6 -239
  13. package/lib/cmp-build.js +41 -317
  14. package/lib/cmp-session.js +73 -760
  15. package/lib/cmp-verify.js +32 -488
  16. package/lib/cms-compress.js +3 -32
  17. package/lib/cms-decrypt.js +29 -294
  18. package/lib/cms-encrypt.js +14 -234
  19. package/lib/cms-sign.js +29 -324
  20. package/lib/cms-verify.js +9 -500
  21. package/lib/composite-kem.js +2 -102
  22. package/lib/composite-sig.js +1 -77
  23. package/lib/constants.js +0 -249
  24. package/lib/crl-sign.js +9 -237
  25. package/lib/crl-verify.js +1 -10
  26. package/lib/crmf-sign.js +19 -198
  27. package/lib/crmf-verify.js +1 -12
  28. package/lib/csr-sign.js +3 -34
  29. package/lib/csr-verify.js +1 -12
  30. package/lib/ct.js +28 -253
  31. package/lib/edwards-point.js +10 -44
  32. package/lib/est.js +28 -764
  33. package/lib/framework-error.js +4 -182
  34. package/lib/guard-all.js +1 -58
  35. package/lib/guard-async.js +1 -36
  36. package/lib/guard-bytes.js +6 -650
  37. package/lib/guard-compress.js +5 -102
  38. package/lib/guard-crypto.js +1 -40
  39. package/lib/guard-der.js +2 -93
  40. package/lib/guard-encoding.js +2 -60
  41. package/lib/guard-header.js +1 -36
  42. package/lib/guard-identifier.js +7 -531
  43. package/lib/guard-intrinsic.js +1 -150
  44. package/lib/guard-json.js +10 -73
  45. package/lib/guard-limits.js +1 -77
  46. package/lib/guard-list.js +1 -49
  47. package/lib/guard-name.js +3 -209
  48. package/lib/guard-parsed.js +7 -335
  49. package/lib/guard-range.js +1 -71
  50. package/lib/guard-secret.js +1 -102
  51. package/lib/guard-text.js +1 -64
  52. package/lib/guard-time.js +1 -97
  53. package/lib/hpke.js +3 -105
  54. package/lib/http-digest.js +14 -142
  55. package/lib/http-retry-after.js +1 -47
  56. package/lib/http-transport.js +21 -131
  57. package/lib/inspect.js +30 -284
  58. package/lib/ip-utils.js +6 -47
  59. package/lib/jose.js +7 -142
  60. package/lib/key.js +2 -67
  61. package/lib/lint.js +9 -194
  62. package/lib/merkle.js +1 -27
  63. package/lib/mime.js +9 -82
  64. package/lib/ocsp-request-verify.js +1 -14
  65. package/lib/ocsp-verify.js +1 -82
  66. package/lib/ocsp.js +11 -169
  67. package/lib/oid.js +5 -337
  68. package/lib/path-validate.js +103 -1492
  69. package/lib/pbes2.js +2 -89
  70. package/lib/pkcs12-build.js +31 -379
  71. package/lib/pki-build.js +64 -290
  72. package/lib/rc2.js +2 -18
  73. package/lib/rfc3339.js +4 -27
  74. package/lib/scep.js +59 -233
  75. package/lib/schema-all.js +0 -122
  76. package/lib/schema-attrcert.js +4 -146
  77. package/lib/schema-c509.js +153 -802
  78. package/lib/schema-cmc.js +5 -220
  79. package/lib/schema-cmp.js +2 -223
  80. package/lib/schema-cms.js +13 -427
  81. package/lib/schema-crl.js +5 -68
  82. package/lib/schema-crmf.js +6 -167
  83. package/lib/schema-csr.js +1 -54
  84. package/lib/schema-csrattrs.js +2 -87
  85. package/lib/schema-engine.js +0 -152
  86. package/lib/schema-ocsp.js +0 -117
  87. package/lib/schema-pkcs12.js +2 -93
  88. package/lib/schema-pkcs8.js +3 -35
  89. package/lib/schema-pkix.js +24 -655
  90. package/lib/schema-smime.js +0 -54
  91. package/lib/schema-tsp.js +3 -99
  92. package/lib/schema-x509.js +4 -63
  93. package/lib/shbs.js +11 -95
  94. package/lib/sign-scheme.js +2 -97
  95. package/lib/sigstore.js +9 -169
  96. package/lib/sleep.js +2 -8
  97. package/lib/smime.js +15 -408
  98. package/lib/tls-cert-compress.js +0 -62
  99. package/lib/trust.js +19 -185
  100. package/lib/tsp-sign.js +16 -270
  101. package/lib/validator-all.js +1 -30
  102. package/lib/validator-attcert.js +2 -46
  103. package/lib/validator-cose.js +2 -153
  104. package/lib/validator-keydesc.js +1 -37
  105. package/lib/validator-sig.js +1 -46
  106. package/lib/validator-tls.js +1 -88
  107. package/lib/validator-tpm.js +19 -122
  108. package/lib/verify-seam.js +1 -24
  109. package/lib/webauthn-mds.js +3 -400
  110. package/lib/webauthn.js +12 -833
  111. package/lib/webcrypto.js +5 -485
  112. package/lib/x509-sign.js +4 -108
  113. package/package.json +1 -1
  114. package/sbom.cdx.json +6 -6
@@ -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
- // (and, if both fail, node's default-store fallback) still applies. Best-effort store load.
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 || // this-network / RFC1918 10/8 / loopback / multicast 224/4 + reserved 240/4 + broadcast
145
- (a === 100 && b >= 64 && b <= 127) || // 100.64/10 CGNAT
146
- (a === 169 && b === 254) || // 169.254/16 link-local (cloud metadata)
147
- (a === 172 && b >= 16 && b <= 31) || // 172.16/12
148
- (a === 192 && b === 168) || // 192.168/16
149
- (a === 192 && b === 0 && (c === 0 || c === 2)) || // 192.0.0/24 IETF protocol + 192.0.2/24 TEST-NET-1
150
- (a === 192 && b === 88 && c === 99) || // 192.88.99/24 6to4 relay anycast (deprecated)
151
- (a === 198 && (b === 18 || b === 19)) || // 198.18/15 benchmarking
152
- (a === 198 && b === 51 && c === 100) || // 198.51.100/24 TEST-NET-2
153
- (a === 203 && b === 0 && c === 113); // 203.0.113/24 TEST-NET-3
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; // IPv4-mapped -- may embed a private v4; block all (fail-closed)
126
+ if (l.indexOf("::ffff:") === 0) return true;
158
127
  var parts = l.split(":");
159
- var h = parseInt(parts[0], 16); // first hextet ("" for a leading "::" -> NaN -> not in 2000::/3 -> blocked)
160
- if (!(h >= 0x2000 && h <= 0x3fff)) return true; // outside global unicast 2000::/3: loopback/ULA/link-local/site-local/multicast(ff00::/8)/unspecified/unallocated
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; // 2002::/16 6to4 (embeds an IPv4 that may be private)
166
- if (h === 0x2001 && h2 < 0x0200) return true; // 2001::/23 IETF protocol assignments (Teredo / benchmarking / ORCHID / AMT / ...)
167
- if (h === 0x2001 && h2 === 0x0db8) return true; // 2001:db8::/32 documentation (RFC 3849)
168
- if (h === 0x3fff && h2 < 0x1000) return true; // 3fff::/20 documentation (RFC 9637)
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)) { // options.all -> [{ address, family }, ...]; reject if ANY resolved address is blocked
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, // ALWAYS on -- there is no code path that disables server verification
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
- // raw throw from this best-effort abort would mask the real fail-closed verdict below.
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
  }