@blamejs/pki 0.3.15 → 0.3.18
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 +30 -0
- package/README.md +7 -6
- package/index.js +11 -4
- package/lib/acme.js +511 -1
- package/lib/constants.js +8 -0
- package/lib/est.js +327 -75
- package/lib/framework-error.js +10 -0
- package/lib/http-retry-after.js +101 -0
- package/lib/http-transport.js +310 -0
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (c) blamejs contributors
|
|
3
|
+
"use strict";
|
|
4
|
+
/**
|
|
5
|
+
* @module pki.transport
|
|
6
|
+
* @nav Protocols
|
|
7
|
+
* @title Transport
|
|
8
|
+
* @order 195
|
|
9
|
+
* @slug transport
|
|
10
|
+
*
|
|
11
|
+
* @intro
|
|
12
|
+
* The shared, fail-closed `node:https` transport the enrollment protocol clients
|
|
13
|
+
* drive -- `pki.est` now, `pki.acme` and `pki.cmp` next. This is the ONLY module in
|
|
14
|
+
* the toolkit that opens a socket; every protocol layer stays transport-agnostic and
|
|
15
|
+
* composes it (or an injected substitute) through one contract:
|
|
16
|
+
* `transport(request) -> Promise<{ status, headers, body }>`. The response triple is
|
|
17
|
+
* exactly what a message layer's classifier consumes, so no protocol semantics leak
|
|
18
|
+
* into the socket layer -- the transport owns socket lifecycle, the TLS trust policy,
|
|
19
|
+
* the streaming size cap, and the timeout budget; the caller owns HTTP status,
|
|
20
|
+
* content-type, redirect, and authentication decisions.
|
|
21
|
+
*
|
|
22
|
+
* `pki.transport.https(defaults?)` binds TLS + budget defaults and returns a
|
|
23
|
+
* transport. Trust is EXPLICIT and fail-closed: a request is refused unless it
|
|
24
|
+
* carries an https URL and either a `tls.anchors` set (an Explicit trust-anchor
|
|
25
|
+
* database, mapped to the node `ca` option) or an explicit `tls.useSystemStore`
|
|
26
|
+
* opt-in to node's bundled roots. `rejectUnauthorized` is ALWAYS on -- there is no
|
|
27
|
+
* code path that disables server-certificate verification. The response body is
|
|
28
|
+
* bounded WHILE it streams: the accumulator aborts the socket the instant the running
|
|
29
|
+
* total crosses `maxResponseBytes`, before a byte reaches a decoder. A protocol
|
|
30
|
+
* client MAY parameterize the transport with its own `(code, message, cause)` error
|
|
31
|
+
* factory + code prefix, so the same choke point surfaces domain-specific codes.
|
|
32
|
+
*
|
|
33
|
+
* @card
|
|
34
|
+
* The shared fail-closed node:https transport (est / acme / cmp): explicit trust
|
|
35
|
+
* anchors, rejectUnauthorized always on, a TLS floor, a streaming response-size cap,
|
|
36
|
+
* and a timeout -- behind one `transport(request) -> {status, headers, body}` seam.
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
var nodeHttps = require("node:https");
|
|
40
|
+
var nodeNet = require("node:net");
|
|
41
|
+
var nodeTls = require("node:tls");
|
|
42
|
+
var constants = require("./constants");
|
|
43
|
+
var guard = require("./guard-all");
|
|
44
|
+
var frameworkError = require("./framework-error");
|
|
45
|
+
|
|
46
|
+
var TransportError = frameworkError.TransportError;
|
|
47
|
+
function defaultE(code, message, cause) { return new TransportError(code, message, cause); }
|
|
48
|
+
|
|
49
|
+
var DEFAULT_TIMEOUT = constants.TIME.seconds(30);
|
|
50
|
+
var MAX_TIMEOUT = constants.TIME.seconds(600);
|
|
51
|
+
var DEFAULT_MIN_VERSION = "TLSv1.2";
|
|
52
|
+
|
|
53
|
+
// A config-time budget: cap() with the caller's typed factory + a >= 1 floor and an
|
|
54
|
+
// upper bound (maxResponseBytes may only be tightened DOWNWARD from the default). A
|
|
55
|
+
// NaN / negative / over-max value is a typed reject, never a silently-disabled bound.
|
|
56
|
+
function _budget(value, key, dflt, max, E, code) {
|
|
57
|
+
return guard.limits.cap(value, key, dflt, { E: E, code: code, min: 1, max: max, label: key });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Node's `ca` option expects PEM; a raw DER certificate Buffer -- the toolkit's native trust-anchor
|
|
61
|
+
// form (e.g. what pki.est.cacerts / parseCertsOnly return) -- is silently ignored there, so the
|
|
62
|
+
// connection would fail server authentication despite a valid anchor. Wrap a DER anchor as PEM
|
|
63
|
+
// (a Buffer already carrying PEM text, or a PEM string, passes through unchanged).
|
|
64
|
+
function _pemifyAnchor(a) {
|
|
65
|
+
if (typeof a === "string") return a;
|
|
66
|
+
if (Buffer.isBuffer(a)) {
|
|
67
|
+
// Detect PEM by its armor at the START (after optional whitespace) only: a DER certificate begins
|
|
68
|
+
// with the SEQUENCE tag 0x30 and may itself contain the bytes "-----BEGIN" in a subject / extension
|
|
69
|
+
// value, so an anywhere-search would leave such a DER anchor unwrapped and unusable.
|
|
70
|
+
if (/^\s*-----BEGIN /.test(a.toString("latin1", 0, 64))) return a;
|
|
71
|
+
var b64 = a.toString("base64").replace(/.{1,64}/g, "$&\n");
|
|
72
|
+
return "-----BEGIN CERTIFICATE-----\n" + b64 + "-----END CERTIFICATE-----\n";
|
|
73
|
+
}
|
|
74
|
+
return a;
|
|
75
|
+
}
|
|
76
|
+
function _pemifyAnchors(anchors) {
|
|
77
|
+
var list = Array.isArray(anchors) ? anchors.map(_pemifyAnchor) : [_pemifyAnchor(anchors)];
|
|
78
|
+
return list;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// The OS system certificate store plus node's bundled roots (cached; the read is not free), for a
|
|
82
|
+
// tls.useSystemStore opt-in. Node's default (no `ca`) is ONLY the bundled Mozilla set, so a server
|
|
83
|
+
// chaining to an OS-installed enterprise CA would fail without this. On a node too old for
|
|
84
|
+
// tls.getCACertificates the cache stays empty and the caller falls back to node's default store.
|
|
85
|
+
var _systemCaCache = null;
|
|
86
|
+
function _systemCa() {
|
|
87
|
+
if (_systemCaCache !== null) return _systemCaCache;
|
|
88
|
+
var out = [];
|
|
89
|
+
if (typeof nodeTls.getCACertificates === "function") {
|
|
90
|
+
["system", "bundled"].forEach(function (t) {
|
|
91
|
+
// allow:swallow-unverified a store TYPE unsupported on this node is skipped; the other type
|
|
92
|
+
// (and, if both fail, node's default-store fallback) still applies -- a best-effort store load.
|
|
93
|
+
try { var c = nodeTls.getCACertificates(t); if (Array.isArray(c)) out = out.concat(c); } catch (_e) { /* unsupported type */ }
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
_systemCaCache = out;
|
|
97
|
+
return out;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Classify a node request/TLS error into the transport's fail-closed verdict: a
|
|
101
|
+
// protocol-version mismatch is the TLS floor; a certificate / identity / handshake
|
|
102
|
+
// failure is a server-authentication failure; anything else is a generic transport
|
|
103
|
+
// error. Every arm threads the raw fault as `.cause`, so the diagnostic survives.
|
|
104
|
+
function _classifyError(e, C) {
|
|
105
|
+
var s = String((e && e.code) || "") + " " + String((e && e.message) || "");
|
|
106
|
+
if (/PROTOCOL_VERSION|UNSUPPORTED_PROTOCOL|VERSION_TOO_LOW|WRONG_VERSION|NO_PROTOCOLS_AVAILABLE|INAPPROPRIATE_FALLBACK/i.test(s)) return C("tls-floor");
|
|
107
|
+
if (/CERT|SELF.?SIGNED|VERIFY|ALTNAME|HOSTNAME|DEPTH_ZERO|LOCAL_ISSUER|HANDSHAKE|\bSSL\b|\bTLS\b/i.test(s)) return C("server-auth-failed");
|
|
108
|
+
return C("transport-error");
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* @primitive pki.transport.https
|
|
113
|
+
* @signature pki.transport.https(defaults?) -> transport
|
|
114
|
+
* @since 0.3.16
|
|
115
|
+
* @status experimental
|
|
116
|
+
* @spec RFC 7030, RFC 8996
|
|
117
|
+
* @defends tls-downgrade (CWE-757), server-impersonation (CWE-297), response-flooding (CWE-770)
|
|
118
|
+
* @related pki.est.cacerts, pki.est.simpleenroll
|
|
119
|
+
*
|
|
120
|
+
* Build a fail-closed `node:https` transport: `transport(request) -> Promise<{ status,
|
|
121
|
+
* headers, body }>`. `defaults` binds a `tls` policy (`anchors` -> the node `ca`;
|
|
122
|
+
* `useSystemStore` to opt into the bundled roots; `cert`/`key` for mutual TLS;
|
|
123
|
+
* `minVersion` 'TLSv1.2' (default) or 'TLSv1.3'; `servername`; a `checkServerIdentity`
|
|
124
|
+
* that may only tighten) plus `timeout` and `maxResponseBytes` budgets. Each `request`
|
|
125
|
+
* ({ method, url, headers, body, tls, timeout, maxResponseBytes }) may override them.
|
|
126
|
+
* A non-https URL (`transport/insecure-url`), a request with neither an explicit
|
|
127
|
+
* anchor nor `useSystemStore` (`transport/no-trust-anchors`), a body over the streaming
|
|
128
|
+
* cap (`transport/response-too-large`), a stalled socket (`transport/timeout`), a below
|
|
129
|
+
* -floor negotiation (`transport/tls-floor`), or a failed server authentication
|
|
130
|
+
* (`transport/server-auth-failed`) all fail closed; `rejectUnauthorized` is always on.
|
|
131
|
+
* A protocol client passes its own error factory (`defaults.E`) + `defaults.errPrefix`
|
|
132
|
+
* to surface domain codes (`est/...`). The transport owns no HTTP/redirect/auth
|
|
133
|
+
* semantics -- those live in the message layer that consumes the response triple.
|
|
134
|
+
*
|
|
135
|
+
* @opts
|
|
136
|
+
* - `tls.anchors` -- Explicit trust anchor(s): a DER/PEM Buffer, an array, or PEM string(s) (node `ca`).
|
|
137
|
+
* - `tls.useSystemStore` -- boolean; the ONLY opt-in to node's bundled CA store (default false).
|
|
138
|
+
* - `tls.cert` / `tls.key` -- client certificate + key for mutual-TLS re-enrollment.
|
|
139
|
+
* - `tls.minVersion` -- 'TLSv1.2' (default) or 'TLSv1.3'; never below the floor.
|
|
140
|
+
* - `tls.servername` / `tls.checkServerIdentity` -- SNI + RFC 6125 identity; may tighten, never disable.
|
|
141
|
+
* - `timeout` -- ms (default C.TIME.seconds(30)); `maxResponseBytes` -- default LIMITS.HTTP_MAX_RESPONSE_BYTES, tightenable downward only.
|
|
142
|
+
* @example
|
|
143
|
+
* var t = pki.transport.https({ tls: { anchors: [caPem] } });
|
|
144
|
+
* var res = await t({ method: "GET", url: "https://ca.example/.well-known/est/cacerts" });
|
|
145
|
+
* res.status; // 200
|
|
146
|
+
*/
|
|
147
|
+
function httpsTransport(defaults) {
|
|
148
|
+
defaults = defaults || {};
|
|
149
|
+
var E = typeof defaults.E === "function" ? defaults.E : defaultE;
|
|
150
|
+
var prefix = defaults.errPrefix || "transport";
|
|
151
|
+
function C(name) { return prefix + "/" + name; }
|
|
152
|
+
var tlsDefaults = defaults.tls || {};
|
|
153
|
+
|
|
154
|
+
// Synchronous config validation + node-options build. Throws the typed verdict on any
|
|
155
|
+
// fault (a non-https URL, no trust anchor, a bad budget, a sub-floor minVersion) so the
|
|
156
|
+
// async executor below never has to `return reject(...)`. rejectUnauthorized is forced on.
|
|
157
|
+
function _prepare(request) {
|
|
158
|
+
var url;
|
|
159
|
+
try { url = new URL(String(request.url)); }
|
|
160
|
+
catch (e) { throw E(C("bad-url"), "the request URL did not parse: " + String(request.url), e); }
|
|
161
|
+
if (url.protocol !== "https:") throw E(C("insecure-url"), "transport requires https (RFC 7030 sec. 3.3), got " + url.protocol);
|
|
162
|
+
|
|
163
|
+
var reqTls = request.tls || {};
|
|
164
|
+
var anchors = reqTls.anchors !== undefined ? reqTls.anchors : tlsDefaults.anchors;
|
|
165
|
+
// System-store trust requires a STRICT boolean true; a non-boolean (a "false" / "true" string from
|
|
166
|
+
// JSON/env, a truthy object) is treated as absent, so a malformed config fails closed rather than
|
|
167
|
+
// silently trusting node's bundled roots.
|
|
168
|
+
var useSystem = (reqTls.useSystemStore !== undefined ? reqTls.useSystemStore : tlsDefaults.useSystemStore) === true;
|
|
169
|
+
var hasAnchors = anchors !== undefined && anchors !== null && !(Array.isArray(anchors) && anchors.length === 0);
|
|
170
|
+
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)");
|
|
171
|
+
|
|
172
|
+
var timeout = _budget(request.timeout !== undefined ? request.timeout : defaults.timeout, "timeout", DEFAULT_TIMEOUT, MAX_TIMEOUT, E, C("bad-input"));
|
|
173
|
+
var maxBytes = _budget(request.maxResponseBytes !== undefined ? request.maxResponseBytes : defaults.maxResponseBytes, "maxResponseBytes", constants.LIMITS.HTTP_MAX_RESPONSE_BYTES, constants.LIMITS.HTTP_MAX_RESPONSE_BYTES, E, C("bad-input"));
|
|
174
|
+
|
|
175
|
+
var minVersion = reqTls.minVersion || tlsDefaults.minVersion || DEFAULT_MIN_VERSION;
|
|
176
|
+
if (minVersion !== "TLSv1.2" && minVersion !== "TLSv1.3") throw E(C("bad-input"), "tls.minVersion must be 'TLSv1.2' or 'TLSv1.3' (never below the floor), got " + minVersion);
|
|
177
|
+
|
|
178
|
+
var body = request.body;
|
|
179
|
+
if (Buffer.isBuffer(body)) body = guard.bytes.view(body, E, C("bad-input"), "the request body");
|
|
180
|
+
|
|
181
|
+
// URL.hostname keeps the square brackets on an IPv6 literal ([2001:db8::1]); node's `hostname`
|
|
182
|
+
// option and its IP-detection expect the bare address, so strip the brackets once here.
|
|
183
|
+
var host = url.hostname.replace(/^\[(.*)\]$/, "$1");
|
|
184
|
+
var options = {
|
|
185
|
+
method: request.method || "GET",
|
|
186
|
+
hostname: host,
|
|
187
|
+
port: url.port || 443,
|
|
188
|
+
path: url.pathname + url.search,
|
|
189
|
+
headers: request.headers || {},
|
|
190
|
+
minVersion: minVersion,
|
|
191
|
+
rejectUnauthorized: true, // ALWAYS on -- there is no code path that disables server verification
|
|
192
|
+
// Opt out of connection pooling: node's keep-alive agent keys the socket pool on host + TLS
|
|
193
|
+
// options but NOT on checkServerIdentity, so a reused socket would skip a per-request identity /
|
|
194
|
+
// pinning callback. A fresh connection per request guarantees the identity check always runs.
|
|
195
|
+
agent: false,
|
|
196
|
+
};
|
|
197
|
+
// Every TLS field falls back to the factory defaults (as anchors / minVersion already do), so a
|
|
198
|
+
// reusable transport built with pki.transport.https({ tls: { cert, key, servername } }) applies
|
|
199
|
+
// them to a request that carries no per-request tls (mTLS enrollment, a configured SNI).
|
|
200
|
+
var cert = reqTls.cert !== undefined ? reqTls.cert : tlsDefaults.cert;
|
|
201
|
+
var key = reqTls.key !== undefined ? reqTls.key : tlsDefaults.key;
|
|
202
|
+
var callerCsi = typeof reqTls.checkServerIdentity === "function" ? reqTls.checkServerIdentity
|
|
203
|
+
: (typeof tlsDefaults.checkServerIdentity === "function" ? tlsDefaults.checkServerIdentity : null);
|
|
204
|
+
// SNI (and RFC 6125 identity) uses a hostname; node forbids a servername that is an IP
|
|
205
|
+
// literal, so it is omitted for an IP host (node then matches the IP against the cert's
|
|
206
|
+
// IP SANs). A caller connecting by hostname gets SNI + name verification by default.
|
|
207
|
+
var sni = reqTls.servername || tlsDefaults.servername || host;
|
|
208
|
+
if (sni && !nodeNet.isIP(sni)) options.servername = sni;
|
|
209
|
+
// Build node's `ca`: the explicit anchors (DER wrapped as PEM) and, when useSystemStore is opted
|
|
210
|
+
// in, the OS system store + bundled roots. Node's `ca` REPLACES the default set, so both sources
|
|
211
|
+
// are merged here. Left unset only when no anchors and (on an older node) the system store is
|
|
212
|
+
// empty -- then node's default bundled store applies (the documented fallback).
|
|
213
|
+
var caList = [];
|
|
214
|
+
if (hasAnchors) caList = caList.concat(_pemifyAnchors(anchors));
|
|
215
|
+
if (useSystem) caList = caList.concat(_systemCa());
|
|
216
|
+
if (caList.length) options.ca = caList;
|
|
217
|
+
if (cert) options.cert = cert;
|
|
218
|
+
if (key) options.key = key;
|
|
219
|
+
// A caller checkServerIdentity may only TIGHTEN, never REPLACE, name verification: node uses a
|
|
220
|
+
// supplied callback in place of its default RFC 6125 check, so a hook that returns undefined
|
|
221
|
+
// (pinning/metrics only) would silently disable hostname verification. Run node's default first
|
|
222
|
+
// and reject on its verdict; the caller's hook adds checks only after the default has passed.
|
|
223
|
+
if (callerCsi) {
|
|
224
|
+
options.checkServerIdentity = function (host, cert2) {
|
|
225
|
+
var baseErr = nodeTls.checkServerIdentity(host, cert2);
|
|
226
|
+
if (baseErr) return baseErr;
|
|
227
|
+
return callerCsi(host, cert2);
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return { options: options, timeout: timeout, maxBytes: maxBytes, body: body };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return function transport(request) {
|
|
235
|
+
request = request || {};
|
|
236
|
+
var prep;
|
|
237
|
+
try { prep = _prepare(request); }
|
|
238
|
+
catch (e) { return Promise.reject(e); }
|
|
239
|
+
return new Promise(function (resolve, reject) {
|
|
240
|
+
var settled = false;
|
|
241
|
+
var req;
|
|
242
|
+
var timer = null;
|
|
243
|
+
function clearTimer() { if (timer) { clearTimeout(timer); timer = null; } }
|
|
244
|
+
function fail(code, msg, cause) {
|
|
245
|
+
if (settled) return;
|
|
246
|
+
settled = true;
|
|
247
|
+
clearTimer();
|
|
248
|
+
// allow:swallow-unverified req.destroy() is idempotent and does not throw in practice; a
|
|
249
|
+
// raw throw from this best-effort abort would mask the real fail-closed verdict below.
|
|
250
|
+
try { if (req) req.destroy(); } catch (_e) { /* best-effort abort */ }
|
|
251
|
+
reject(E(code, msg, cause));
|
|
252
|
+
}
|
|
253
|
+
// An independent wall-clock deadline bounds DNS resolution and connection setup as well as
|
|
254
|
+
// socket inactivity: node's request/socket timeout does not run during a DNS lookup, so a
|
|
255
|
+
// stalled resolver would otherwise leave this promise pending forever despite a finite timeout.
|
|
256
|
+
timer = setTimeout(function () { fail(C("timeout"), "the request timed out after " + prep.timeout + "ms"); }, prep.timeout);
|
|
257
|
+
try {
|
|
258
|
+
req = nodeHttps.request(prep.options, function (res) {
|
|
259
|
+
// Capture the TLS session facts while the socket is live -- getProtocol() /
|
|
260
|
+
// getPeerCertificate() return null once the socket detaches at stream end.
|
|
261
|
+
var proto = res.socket && res.socket.getProtocol ? res.socket.getProtocol() : null;
|
|
262
|
+
var peer = res.socket && res.socket.getPeerCertificate ? res.socket.getPeerCertificate() : null;
|
|
263
|
+
// Pre-check a declared content-length so an oversized body is refused before it streams.
|
|
264
|
+
var declared = parseInt((res.headers || {})["content-length"], 10);
|
|
265
|
+
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; }
|
|
266
|
+
// Accumulate into a single doubling buffer rather than an array of chunks: a body sent as
|
|
267
|
+
// many tiny chunks (one-byte TLS records) would otherwise retain millions of Buffer objects
|
|
268
|
+
// under the byte cap and exhaust the heap. One live buffer bounds the metadata as well as the
|
|
269
|
+
// bytes; only the written prefix [0, len) is ever surfaced (the unwritten tail is never read).
|
|
270
|
+
var buf = Buffer.allocUnsafe(0);
|
|
271
|
+
var len = 0;
|
|
272
|
+
var total = 0;
|
|
273
|
+
res.on("data", function (chunk) {
|
|
274
|
+
total += chunk.length;
|
|
275
|
+
if (total > prep.maxBytes) { fail(C("response-too-large"), "the response exceeded the " + prep.maxBytes + "-byte cap (RFC 7030 sec. 6)"); return; }
|
|
276
|
+
if (len + chunk.length > buf.length) {
|
|
277
|
+
var want = Math.min(prep.maxBytes, Math.max(buf.length ? buf.length * 2 : 8192, len + chunk.length));
|
|
278
|
+
var grown = Buffer.allocUnsafe(want);
|
|
279
|
+
buf.copy(grown, 0, 0, len);
|
|
280
|
+
buf = grown;
|
|
281
|
+
}
|
|
282
|
+
chunk.copy(buf, len);
|
|
283
|
+
len += chunk.length;
|
|
284
|
+
});
|
|
285
|
+
res.on("end", function () {
|
|
286
|
+
if (settled) return;
|
|
287
|
+
settled = true;
|
|
288
|
+
clearTimer();
|
|
289
|
+
var lower = {};
|
|
290
|
+
Object.keys(res.headers || {}).forEach(function (k) { lower[k.toLowerCase()] = res.headers[k]; });
|
|
291
|
+
resolve({
|
|
292
|
+
status: res.statusCode,
|
|
293
|
+
headers: lower,
|
|
294
|
+
body: Buffer.from(buf.subarray(0, len)),
|
|
295
|
+
tls: { protocol: proto, peerCertificate: peer && peer.raw ? peer.raw : null },
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
res.on("error", function (e) { fail(C("transport-error"), "the response stream failed", e); });
|
|
299
|
+
});
|
|
300
|
+
req.on("error", function (e) { fail(_classifyError(e, C), "the request failed: " + ((e && e.message) || String(e)), e); });
|
|
301
|
+
// Write a provided body for ANY method (POST / PUT / PATCH / ...), not only POST, so a body
|
|
302
|
+
// is never silently dropped on a body-bearing request through this generic transport seam.
|
|
303
|
+
if (prep.body != null && prep.body !== "") req.write(prep.body);
|
|
304
|
+
req.end();
|
|
305
|
+
} catch (e) { fail(C("transport-error"), "the request could not be initiated: " + ((e && e.message) || String(e)), e); }
|
|
306
|
+
});
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
module.exports = { https: httpsTransport };
|
package/package.json
CHANGED
package/sbom.cdx.json
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
"$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
|
|
3
3
|
"bomFormat": "CycloneDX",
|
|
4
4
|
"specVersion": "1.5",
|
|
5
|
-
"serialNumber": "urn:uuid:
|
|
5
|
+
"serialNumber": "urn:uuid:38b1d773-a2de-428b-848c-aece31c991f9",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-07-
|
|
8
|
+
"timestamp": "2026-07-25T19:26:20.442Z",
|
|
9
9
|
"lifecycles": [
|
|
10
10
|
{
|
|
11
11
|
"phase": "build"
|
|
@@ -19,14 +19,14 @@
|
|
|
19
19
|
}
|
|
20
20
|
],
|
|
21
21
|
"component": {
|
|
22
|
-
"bom-ref": "@blamejs/pki@0.3.
|
|
22
|
+
"bom-ref": "@blamejs/pki@0.3.18",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "pki",
|
|
25
|
-
"version": "0.3.
|
|
25
|
+
"version": "0.3.18",
|
|
26
26
|
"scope": "required",
|
|
27
27
|
"author": "blamejs contributors",
|
|
28
28
|
"description": "Pure-JavaScript PKI toolkit that owns its stack — X.509, ASN.1/DER, CMS, PQC-first.",
|
|
29
|
-
"purl": "pkg:npm/%40blamejs/pki@0.3.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/pki@0.3.18",
|
|
30
30
|
"properties": [],
|
|
31
31
|
"externalReferences": [
|
|
32
32
|
{
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"components": [],
|
|
55
55
|
"dependencies": [
|
|
56
56
|
{
|
|
57
|
-
"ref": "@blamejs/pki@0.3.
|
|
57
|
+
"ref": "@blamejs/pki@0.3.18",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|