Transport

The shared, fail-closed node:https transport the enrollment protocol clients drive -- pki.est now, pki.acme and pki.cmp next. This is the ONLY module in the toolkit that opens a socket; every protocol layer stays transport-agnostic and composes it (or an injected substitute) through one contract: transport(request) -> Promise<{ status, headers, body }>. The response triple is exactly what a message layer's classifier consumes, so no protocol semantics leak into the socket layer -- the transport owns socket lifecycle, the TLS trust policy, the streaming size cap, and the timeout budget; the caller owns HTTP status, content-type, redirect, and authentication decisions.

pki.transport.https(defaults?) binds TLS + budget defaults and returns a transport. Trust is EXPLICIT and fail-closed: a request is refused unless it carries an https URL and either a tls.anchors set (an Explicit trust-anchor database, mapped to the node ca option) or an explicit tls.useSystemStore opt-in to node's bundled roots. rejectUnauthorized is ALWAYS on -- there is no code path that disables server-certificate verification. The response body is bounded WHILE it streams: the accumulator aborts the socket the instant the running total crosses maxResponseBytes, before a byte reaches a decoder. A protocol client MAY parameterize the transport with its own (code, message, cause) error factory + code prefix, so the same choke point surfaces domain-specific codes.

pki.transport.https

since 0.3.16 experimental
pki.transport.https(defaults?) -> transport

Build a fail-closed node:https transport: transport(request) -> Promise<{ status, headers, body }>. defaults binds a tls policy (anchors -> the node ca; useSystemStore to opt into the bundled roots; cert/key for mutual TLS; minVersion 'TLSv1.2' (default) or 'TLSv1.3'; servername; a checkServerIdentity that may only tighten) plus timeout and maxResponseBytes budgets. Each request ({ method, url, headers, body, tls, timeout, maxResponseBytes }) may override them. A non-https URL (transport/insecure-url), a request with neither an explicit anchor nor useSystemStore (transport/no-trust-anchors), a body over the streaming cap (transport/response-too-large), a stalled socket (transport/timeout), a below -floor negotiation (transport/tls-floor), or a failed server authentication (transport/server-auth-failed) all fail closed; rejectUnauthorized is always on. A protocol client passes its own error factory (defaults.E) + defaults.errPrefix to surface domain codes (est/...). The transport owns no HTTP/redirect/auth semantics -- those live in the message layer that consumes the response triple.

Options

- `tls.anchors` -- Explicit trust anchor(s): a DER/PEM Buffer, an array, or PEM string(s) (node `ca`).
- `tls.useSystemStore` -- boolean; the ONLY opt-in to node's bundled CA store (default false).
- `tls.cert` / `tls.key` -- client certificate + key for mutual-TLS re-enrollment.
- `tls.minVersion` -- 'TLSv1.2' (default) or 'TLSv1.3'; never below the floor.
- `tls.servername` / `tls.checkServerIdentity` -- SNI + RFC 6125 identity; may tighten, never disable.
- `timeout` -- ms (default C.TIME.seconds(30)); `maxResponseBytes` -- default LIMITS.HTTP_MAX_RESPONSE_BYTES, tightenable downward only.
- `blockPrivateAddresses` -- boolean; when true, an IP-literal host OR a hostname resolving to a private / loopback / link-local address is refused (`transport/blocked-address`), and a resolved address is pinned for the connection. For fetching an untrusted-certificate URL (AIA caIssuers); default false.

Example

async function example() {
  var t = pki.transport.https({ tls: { anchors: [caPem] } });
  var res = await t({ method: "GET", url: "https://ca.example/.well-known/est/cacerts" });
  res.status;   // 200
}
example();

References

  • spec RFC 7030
  • spec RFC 8996
  • defends tls-downgrade (CWE-757)
  • defends server-impersonation (CWE-297)
  • defends response-flooding (CWE-770)
  • defends ssrf (CWE-918)