@blamejs/blamejs-shop 0.5.19 → 0.5.21
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 +4 -0
- package/README.md +1 -0
- package/SECURITY.md +17 -0
- package/lib/addresses.js +2 -2
- package/lib/admin.js +49 -45
- package/lib/affiliates.js +7 -23
- package/lib/announcement-bar.js +5 -22
- package/lib/api-keys.js +4 -13
- package/lib/asset-manifest.json +1 -1
- package/lib/banner-ab-tests.js +5 -9
- package/lib/blog-articles.js +8 -26
- package/lib/captcha-gate.js +7 -17
- package/lib/carrier-accounts.js +3 -10
- package/lib/catalog-drafts.js +5 -12
- package/lib/category-navigation.js +4 -20
- package/lib/cms-blocks.js +4 -7
- package/lib/compliance-export.js +4 -11
- package/lib/consent-ledger.js +2 -4
- package/lib/cost-layers.js +56 -21
- package/lib/customer-import.js +2 -2
- package/lib/customer-notes.js +7 -23
- package/lib/customer-roles.js +3 -5
- package/lib/customer-segments.js +17 -14
- package/lib/customer-surveys.js +7 -14
- package/lib/customers.js +2 -4
- package/lib/dispute-resolution.js +5 -13
- package/lib/dropship-forwarding.js +3 -5
- package/lib/dunning.js +2 -7
- package/lib/email-ab-tests.js +4 -8
- package/lib/email-campaigns.js +25 -14
- package/lib/email-warmup.js +2 -7
- package/lib/experiments.js +6 -10
- package/lib/externaldb-d1.js +78 -44
- package/lib/geolocation.js +18 -28
- package/lib/gift-options.js +5 -23
- package/lib/knowledge-base.js +7 -22
- package/lib/line-gift-wrap.js +3 -17
- package/lib/live-chat.js +5 -18
- package/lib/metered-usage.js +3 -8
- package/lib/notifications.js +3 -7
- package/lib/operator-accounts.js +3 -5
- package/lib/operator-approvals.js +6 -8
- package/lib/operator-audit-log.js +2 -7
- package/lib/operator-help-center.js +5 -21
- package/lib/operator-inbox.js +4 -11
- package/lib/operator-roles.js +5 -7
- package/lib/order-escalation.js +2 -2
- package/lib/order-export.js +24 -20
- package/lib/order-notes.js +4 -14
- package/lib/order-ratings.js +4 -10
- package/lib/payment-methods.js +5 -13
- package/lib/payment-retries.js +2 -7
- package/lib/payment.js +103 -58
- package/lib/pixel-events.js +8 -23
- package/lib/plan-changes.js +2 -7
- package/lib/product-qa.js +4 -8
- package/lib/promo-banners.js +7 -15
- package/lib/purchase-orders.js +3 -6
- package/lib/push-notifications.js +7 -32
- package/lib/pwa-manifest.js +6 -11
- package/lib/quotes.js +5 -9
- package/lib/referrals.js +10 -6
- package/lib/reviews.js +4 -10
- package/lib/robots-config.js +6 -14
- package/lib/sales-tax-filings.js +2 -2
- package/lib/search-facets.js +5 -5
- package/lib/search-ranking.js +4 -7
- package/lib/search-synonyms.js +7 -21
- package/lib/security-middleware.js +125 -70
- package/lib/seller-signup.js +10 -26
- package/lib/sidebar-widgets.js +6 -15
- package/lib/site-redirects.js +49 -137
- package/lib/sitemap-generator.js +3 -7
- package/lib/sms-dispatcher.js +7 -23
- package/lib/stock-receipts.js +4 -10
- package/lib/storefront-dashboards.js +2 -10
- package/lib/storefront-forms.js +9 -22
- package/lib/storefront-pages.js +6 -20
- package/lib/storefront.js +21 -23
- package/lib/subscription-billing.js +3 -8
- package/lib/subscription-controls.js +2 -7
- package/lib/suggestion-box.js +9 -26
- package/lib/support-tickets.js +6 -24
- package/lib/tax-remittance.js +2 -2
- package/lib/tenants.js +2 -7
- package/lib/text-guard.js +137 -35
- package/lib/theme-assets.js +4 -16
- package/lib/translations.js +3 -6
- package/lib/trust-badges.js +4 -7
- package/lib/vendor/MANIFEST.json +107 -97
- package/lib/vendor/blamejs/CHANGELOG.md +180 -0
- package/lib/vendor/blamejs/NOTICE +14 -10
- package/lib/vendor/blamejs/README.md +17 -5
- package/lib/vendor/blamejs/index.js +2 -0
- package/lib/vendor/blamejs/lib/acme.js +9 -9
- package/lib/vendor/blamejs/lib/api-snapshot.js +35 -16
- package/lib/vendor/blamejs/lib/app-shutdown.js +1 -1
- package/lib/vendor/blamejs/lib/archive-adapters.js +1 -1
- package/lib/vendor/blamejs/lib/archive.js +13 -24
- package/lib/vendor/blamejs/lib/auth/access-lock.js +2 -1
- package/lib/vendor/blamejs/lib/auth/bot-challenge.js +3 -6
- package/lib/vendor/blamejs/lib/auth/ciba.js +32 -3
- package/lib/vendor/blamejs/lib/auth/oauth.js +65 -8
- package/lib/vendor/blamejs/lib/auth/password.js +1 -1
- package/lib/vendor/blamejs/lib/auth/saml.js +21 -3
- package/lib/vendor/blamejs/lib/auth-bot-challenge.js +3 -3
- package/lib/vendor/blamejs/lib/backup/index.js +5 -5
- package/lib/vendor/blamejs/lib/case-fold-classes.js +491 -0
- package/lib/vendor/blamejs/lib/codepoint-class.js +131 -0
- package/lib/vendor/blamejs/lib/constants.js +79 -0
- package/lib/vendor/blamejs/lib/db-declare-view.js +8 -2
- package/lib/vendor/blamejs/lib/error-page.js +20 -1
- package/lib/vendor/blamejs/lib/external-db.js +5 -1
- package/lib/vendor/blamejs/lib/flag-evaluation-context.js +24 -6
- package/lib/vendor/blamejs/lib/flag.js +28 -2
- package/lib/vendor/blamejs/lib/framework-schema.js +14 -3
- package/lib/vendor/blamejs/lib/guard-regex.js +2345 -89
- package/lib/vendor/blamejs/lib/http-client.js +217 -40
- package/lib/vendor/blamejs/lib/mail-bimi.js +1 -1
- package/lib/vendor/blamejs/lib/mail-crypto-pgp.js +3 -2
- package/lib/vendor/blamejs/lib/mail-dav.js +26 -26
- package/lib/vendor/blamejs/lib/mail-deploy.js +10 -10
- package/lib/vendor/blamejs/lib/mail-scan.js +3 -0
- package/lib/vendor/blamejs/lib/mail-store.js +7 -1
- package/lib/vendor/blamejs/lib/mail.js +5 -1
- package/lib/vendor/blamejs/lib/middleware/age-gate.js +3 -2
- package/lib/vendor/blamejs/lib/middleware/api-encrypt.js +1 -1
- package/lib/vendor/blamejs/lib/middleware/assetlinks.js +2 -1
- package/lib/vendor/blamejs/lib/middleware/asyncapi-serve.js +3 -2
- package/lib/vendor/blamejs/lib/middleware/bearer-auth.js +2 -1
- package/lib/vendor/blamejs/lib/middleware/compression.js +1 -1
- package/lib/vendor/blamejs/lib/middleware/cors.js +1 -1
- package/lib/vendor/blamejs/lib/middleware/csp-report.js +5 -5
- package/lib/vendor/blamejs/lib/middleware/csrf-protect.js +2 -1
- package/lib/vendor/blamejs/lib/middleware/daily-byte-quota.js +2 -2
- package/lib/vendor/blamejs/lib/middleware/deny-response.js +2 -1
- package/lib/vendor/blamejs/lib/middleware/dpop.js +2 -2
- package/lib/vendor/blamejs/lib/middleware/fetch-metadata.js +2 -1
- package/lib/vendor/blamejs/lib/middleware/flag-context.js +17 -0
- package/lib/vendor/blamejs/lib/middleware/idempotency-key.js +10 -8
- package/lib/vendor/blamejs/lib/middleware/openapi-serve.js +3 -2
- package/lib/vendor/blamejs/lib/middleware/require-aal.js +3 -2
- package/lib/vendor/blamejs/lib/middleware/require-auth.js +4 -3
- package/lib/vendor/blamejs/lib/middleware/require-bound-key.js +4 -3
- package/lib/vendor/blamejs/lib/middleware/require-content-type.js +3 -2
- package/lib/vendor/blamejs/lib/middleware/require-methods.js +3 -2
- package/lib/vendor/blamejs/lib/middleware/require-mtls.js +3 -2
- package/lib/vendor/blamejs/lib/middleware/security-txt.js +2 -1
- package/lib/vendor/blamejs/lib/middleware/web-app-manifest.js +2 -1
- package/lib/vendor/blamejs/lib/network-dns-resolver.js +1 -1
- package/lib/vendor/blamejs/lib/network-dns.js +3 -3
- package/lib/vendor/blamejs/lib/network-nts.js +35 -14
- package/lib/vendor/blamejs/lib/network-proxy.js +42 -4
- package/lib/vendor/blamejs/lib/network-smtp-policy.js +1 -1
- package/lib/vendor/blamejs/lib/network-tls.js +341 -11
- package/lib/vendor/blamejs/lib/nist-crosswalk.js +2 -2
- package/lib/vendor/blamejs/lib/ntp-check.js +30 -19
- package/lib/vendor/blamejs/lib/object-store/azure-blob.js +1 -1
- package/lib/vendor/blamejs/lib/object-store/gcs.js +1 -1
- package/lib/vendor/blamejs/lib/object-store/http-put.js +2 -1
- package/lib/vendor/blamejs/lib/object-store/sigv4-bucket-ops.js +5 -5
- package/lib/vendor/blamejs/lib/object-store/sigv4.js +2 -2
- package/lib/vendor/blamejs/lib/observability-otlp-exporter.js +4 -2
- package/lib/vendor/blamejs/lib/openapi.js +1 -1
- package/lib/vendor/blamejs/lib/pqc-agent.js +4 -7
- package/lib/vendor/blamejs/lib/problem-details.js +2 -1
- package/lib/vendor/blamejs/lib/redis-client.js +12 -1
- package/lib/vendor/blamejs/lib/regex-linear.js +1349 -0
- package/lib/vendor/blamejs/lib/render.js +531 -7
- package/lib/vendor/blamejs/lib/request-helpers.js +181 -5
- package/lib/vendor/blamejs/lib/router.js +37 -13
- package/lib/vendor/blamejs/lib/safe-async.js +142 -0
- package/lib/vendor/blamejs/lib/safe-icap.js +3 -0
- package/lib/vendor/blamejs/lib/safe-sql.js +15 -3
- package/lib/vendor/blamejs/lib/self-update.js +2 -2
- package/lib/vendor/blamejs/lib/testing.js +54 -7
- package/lib/vendor/blamejs/lib/validate-opts.js +56 -0
- package/lib/vendor/blamejs/lib/vendor/MANIFEST.json +62 -25
- package/lib/vendor/blamejs/lib/vendor/blamejs-pki.cjs +2004 -142
- package/lib/vendor/blamejs/lib/vendor/browser/noble-ciphers.mjs +727 -0
- package/lib/vendor/blamejs/lib/vendor/browser/noble-hashes.mjs +1070 -0
- package/lib/vendor/blamejs/lib/vendor/browser/noble-post-quantum.mjs +1090 -0
- package/lib/vendor/blamejs/lib/vendor/noble-post-quantum.cjs +510 -227
- package/lib/vendor/blamejs/lib/webhook-hmac.js +19 -2
- package/lib/vendor/blamejs/lib/webhook.js +1 -1
- package/lib/vendor/blamejs/lib/ws-client.js +24 -2
- package/lib/vendor/blamejs/package.json +1 -1
- package/lib/vendor/blamejs/sbom.cdx.json +6 -6
- package/lib/vendor-invoices.js +3 -6
- package/lib/vendors.js +5 -19
- package/lib/webhook-receiver.js +46 -83
- package/lib/webhook-subscriptions.js +3 -13
- package/lib/wishlist-sharing.js +5 -10
- package/package.json +1 -1
|
@@ -442,14 +442,17 @@ function querySingle(opts) {
|
|
|
442
442
|
throw new NtsError("nts/no-cookies", "nts.querySingle: cookies array required");
|
|
443
443
|
}
|
|
444
444
|
return new Promise(function (resolve, reject) {
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
445
|
+
// Build the request BEFORE opening the socket. Encoding it can fail — an
|
|
446
|
+
// aeadId the encoder does not recognise is the reachable case, and
|
|
447
|
+
// refusing it is the point — and a socket opened first has nobody left to
|
|
448
|
+
// close it once that throw unwinds. Holding the acquisition until every
|
|
449
|
+
// step that can throw has succeeded is what makes the leak impossible,
|
|
450
|
+
// rather than something each new failure path has to remember to undo.
|
|
451
|
+
//
|
|
452
|
+
// Inside the executor, not above it: a throw here still reaches the
|
|
453
|
+
// caller as a REJECTION, so `querySingle(opts).catch(...)` keeps seeing
|
|
454
|
+
// the documented refusal. Hoisting this out would leak the throw
|
|
455
|
+
// synchronously past a caller that only attached a .catch.
|
|
453
456
|
var unique = nodeCrypto.randomBytes(UNIQUE_ID_BYTES);
|
|
454
457
|
var cookie = opts.cookies[0];
|
|
455
458
|
var packet = Buffer.alloc(NTP_PACKET_BYTES);
|
|
@@ -468,6 +471,15 @@ function querySingle(opts) {
|
|
|
468
471
|
encrypted.copy(authBody, 4 + nonceLen);
|
|
469
472
|
var ext3 = _encodeExtensionField(EXTENSION_NTS_AUTHENTICATOR_AND_ENC, authBody);
|
|
470
473
|
var fullPacket = Buffer.concat([packet, ext1, ext2, ext3]);
|
|
474
|
+
|
|
475
|
+
var sock = dgram.createSocket("udp4");
|
|
476
|
+
var settled = false;
|
|
477
|
+
function done(err, result) {
|
|
478
|
+
if (settled) return;
|
|
479
|
+
settled = true;
|
|
480
|
+
try { sock.close(); } catch (_e) { /* best-effort socket close */ }
|
|
481
|
+
if (err) reject(err); else resolve(result);
|
|
482
|
+
}
|
|
471
483
|
var sendTimeMs = Date.now();
|
|
472
484
|
var timer = setTimeout(function () {
|
|
473
485
|
done(new NtsError("nts/timeout", "NTS query timed out after " + timeoutMs + "ms"));
|
|
@@ -553,12 +565,21 @@ function querySingle(opts) {
|
|
|
553
565
|
done(new NtsError("nts/bad-reply", "NTS reply processing failed: " + e.message));
|
|
554
566
|
}
|
|
555
567
|
});
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
568
|
+
// send() rejects a malformed destination synchronously. Routing that
|
|
569
|
+
// throw through done() closes the socket; letting it escape the executor
|
|
570
|
+
// would reject the caller and strand the handle, which is the failure
|
|
571
|
+
// holding the request build above the acquisition.
|
|
572
|
+
try {
|
|
573
|
+
sock.send(fullPacket, 0, fullPacket.length, opts.port || NTPV4_DEFAULT_PORT, opts.host, function (err) {
|
|
574
|
+
if (err) {
|
|
575
|
+
clearTimeout(timer);
|
|
576
|
+
done(new NtsError("nts/send", "NTS send failed: " + err.message));
|
|
577
|
+
}
|
|
578
|
+
});
|
|
579
|
+
} catch (e) {
|
|
580
|
+
clearTimeout(timer);
|
|
581
|
+
done(new NtsError("nts/send", "NTS send failed: " + e.message));
|
|
582
|
+
}
|
|
562
583
|
});
|
|
563
584
|
}
|
|
564
585
|
|
|
@@ -183,12 +183,16 @@ function _proxyAuthHeader(proxyUrl) {
|
|
|
183
183
|
|
|
184
184
|
function _connectThroughTunnel(proxyUrl, targetHost, targetPort, callback) {
|
|
185
185
|
var proxyPort = proxyUrl.port || (proxyUrl.protocol === "https:" ? DEFAULT_HTTPS_PORT : DEFAULT_HTTP_PORT);
|
|
186
|
+
// Read ONCE, so the diagnosis below describes the handshake that happened.
|
|
187
|
+
// A preferredGroups.set(...) landing while this handshake is in flight would
|
|
188
|
+
// otherwise have the error handler read a posture this dial never offered.
|
|
189
|
+
var proxyPosture = proxyUrl.protocol === "https:" ? networkTls().outboundPosture() : null;
|
|
186
190
|
var proxySocket = proxyUrl.protocol === "https:"
|
|
187
191
|
? nodeTls.connect(Object.assign({
|
|
188
192
|
host: proxyUrl.hostname,
|
|
189
193
|
port: proxyPort,
|
|
190
194
|
servername: proxyUrl.hostname,
|
|
191
|
-
},
|
|
195
|
+
}, proxyPosture))
|
|
192
196
|
: net.connect({ host: proxyUrl.hostname, port: proxyPort });
|
|
193
197
|
var settled = false;
|
|
194
198
|
var connectDeadline = null;
|
|
@@ -212,7 +216,25 @@ function _connectThroughTunnel(proxyUrl, targetHost, targetPort, callback) {
|
|
|
212
216
|
try { proxySocket.destroy(); } catch (_e) { /* best-effort socket teardown */ }
|
|
213
217
|
}, connectTimeoutMs);
|
|
214
218
|
if (connectDeadline && typeof connectDeadline.unref === "function") connectDeadline.unref();
|
|
215
|
-
proxySocket.on("error", function (e) {
|
|
219
|
+
proxySocket.on("error", function (e) {
|
|
220
|
+
// The handshake that failed here is with the PROXY, not the destination:
|
|
221
|
+
// no tunnel exists yet. This error travels out through the agent callback
|
|
222
|
+
// of the request to the destination, so it is claimed here, with the
|
|
223
|
+
// proxy's identity and the options this leg dialled with. Otherwise it
|
|
224
|
+
// would be reported as the destination refusing a connection nothing ever
|
|
225
|
+
// attempted, sending an operator to troubleshoot the wrong peer.
|
|
226
|
+
if (proxyPosture) {
|
|
227
|
+
networkTls().annotateOutboundFailure(e, {
|
|
228
|
+
host: proxyUrl.hostname,
|
|
229
|
+
// `URL.port` is a string, and the explanation appends a port only when
|
|
230
|
+
// it is given a number — so an explicit `:8443` was dropped and the
|
|
231
|
+
// message named a host that may run several TLS listeners.
|
|
232
|
+
port: Number(proxyPort),
|
|
233
|
+
tlsOpts: proxyPosture,
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
done(e);
|
|
237
|
+
});
|
|
216
238
|
proxySocket.on(proxyUrl.protocol === "https:" ? "secureConnect" : "connect", function () {
|
|
217
239
|
if (proxyUrl.protocol === "https:") {
|
|
218
240
|
// The CONNECT-tunnel leg to an https proxy is itself a TLS handshake;
|
|
@@ -275,11 +297,17 @@ function agentFor(targetUrl) {
|
|
|
275
297
|
agent.createConnection = function (options, cb) {
|
|
276
298
|
_connectThroughTunnel(proxy, options.host, options.port, function (err, tunnel) {
|
|
277
299
|
if (err) return cb(err);
|
|
300
|
+
// Read ONCE, and diagnose against what was read. The agent above
|
|
301
|
+
// snapshotted a posture when it was built and is then cached, so a
|
|
302
|
+
// later preferredGroups.set(...) leaves agent.options describing
|
|
303
|
+
// groups this dial did not offer — the caller reading them back would
|
|
304
|
+
// be told about a handshake that never happened.
|
|
305
|
+
var livePosture = networkTls().outboundPosture();
|
|
278
306
|
var secure = nodeTls.connect(Object.assign({
|
|
279
307
|
socket: tunnel,
|
|
280
308
|
servername: options.servername || options.host,
|
|
281
309
|
ALPNProtocols: options.ALPNProtocols,
|
|
282
|
-
},
|
|
310
|
+
}, livePosture), function () {
|
|
283
311
|
// Audit a classical-group fallback on the upstream (target) handshake
|
|
284
312
|
// reached through the proxy tunnel, so the "every outbound TLS path
|
|
285
313
|
// emits tls.classical_downgrade" guarantee holds for proxied requests
|
|
@@ -291,7 +319,17 @@ function agentFor(targetUrl) {
|
|
|
291
319
|
});
|
|
292
320
|
cb(null, secure);
|
|
293
321
|
});
|
|
294
|
-
secure.on("error", function (e) {
|
|
322
|
+
secure.on("error", function (e) {
|
|
323
|
+
// Annotated HERE, with the settings this leg actually used. The
|
|
324
|
+
// annotation is one-shot, so naming the destination leg first stops
|
|
325
|
+
// the outer handler describing it from the stale cached agent.
|
|
326
|
+
networkTls().annotateOutboundFailure(e, {
|
|
327
|
+
host: options.servername || options.host,
|
|
328
|
+
port: Number(options.port),
|
|
329
|
+
tlsOpts: livePosture,
|
|
330
|
+
});
|
|
331
|
+
cb(e);
|
|
332
|
+
});
|
|
295
333
|
});
|
|
296
334
|
};
|
|
297
335
|
} else {
|
|
@@ -189,7 +189,7 @@ async function mtaStsFetch(domain, opts) {
|
|
|
189
189
|
// this is a deliberate opportunistic fallback, not a silent swallow.
|
|
190
190
|
return null;
|
|
191
191
|
}
|
|
192
|
-
if (res.statusCode ===
|
|
192
|
+
if (res.statusCode === C.HTTP.STATUS.NOT_FOUND) return null; // HTTP 404
|
|
193
193
|
if (res.statusCode < 200 || res.statusCode >= 300) { // HTTP 2xx range
|
|
194
194
|
throw new SmtpPolicyError("smtp/mta-sts-fetch-failed",
|
|
195
195
|
"MTA-STS fetch returned " + res.statusCode + " for " + url);
|
|
@@ -476,6 +476,79 @@ function detectBaselineDrift() {
|
|
|
476
476
|
return { added: added, removed: removed, drifted: added.length > 0 || removed.length > 0 };
|
|
477
477
|
}
|
|
478
478
|
|
|
479
|
+
// Normalise an operator-supplied named-group preference to the colon-separated
|
|
480
|
+
// string node:tls reads. Accepts a non-empty string or a non-empty array of
|
|
481
|
+
// non-empty strings; anything else is a config-time mistake and throws, so it
|
|
482
|
+
// surfaces at boot rather than as a handshake that negotiated something other
|
|
483
|
+
// than what was configured.
|
|
484
|
+
function _groupPreferenceString(value, where) {
|
|
485
|
+
if (Array.isArray(value)) {
|
|
486
|
+
if (value.length === 0) {
|
|
487
|
+
throw new TlsTrustError("tls/bad-group-preference",
|
|
488
|
+
where + ": group preference must name at least one group");
|
|
489
|
+
}
|
|
490
|
+
for (var i = 0; i < value.length; i++) {
|
|
491
|
+
if (typeof value[i] !== "string" || value[i].length === 0) {
|
|
492
|
+
throw new TlsTrustError("tls/bad-group-preference",
|
|
493
|
+
where + "[" + i + "]: group preference entries must be non-empty strings, got " +
|
|
494
|
+
(typeof value[i] === "string" ? "empty string" : typeof value[i]));
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
return value.join(":");
|
|
498
|
+
}
|
|
499
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
500
|
+
throw new TlsTrustError("tls/bad-group-preference",
|
|
501
|
+
where + ": group preference must be a non-empty string or array of group " +
|
|
502
|
+
"names, got " + (typeof value === "string" ? "empty string" : typeof value));
|
|
503
|
+
}
|
|
504
|
+
return value;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// A dial or context built from these options can reach TLS 1.3 unless the
|
|
508
|
+
// caller capped it below — through `maxVersion`, or a `secureProtocol` method
|
|
509
|
+
// that pins one version. Two things turn on the answer and both used to get
|
|
510
|
+
// it wrong: RFC 8879 certificate compression is a TLS 1.3 extension that Node
|
|
511
|
+
// refuses outright when the range excludes 1.3, and a handshake explanation
|
|
512
|
+
// must not send an operator after a TLS 1.3 cipher list their connection
|
|
513
|
+
// never offered. One predicate answers for both.
|
|
514
|
+
// The OpenSSL methods that pin one version. SSLv23_method is the trap: it
|
|
515
|
+
// reads as the version-flexible one and is not — Node maps it to TLS_method
|
|
516
|
+
// with the ceiling held at TLS 1.2. Matching whole names rather than a prefix
|
|
517
|
+
// keeps a hypothetical TLSv1_3_method out of the set, which a prefix on
|
|
518
|
+
// `TLSv1_` would swallow.
|
|
519
|
+
var VERSION_PINNED_METHOD_RE =
|
|
520
|
+
/^(?:SSLv2|SSLv23|SSLv3|TLSv1|TLSv1_1|TLSv1_2)_(?:client_|server_)?method$/;
|
|
521
|
+
var MAX_METHOD_NAME = 32;
|
|
522
|
+
|
|
523
|
+
function _reachesTls13(opts) {
|
|
524
|
+
if (opts === null || typeof opts !== "object") return true;
|
|
525
|
+
var cap = opts.maxVersion;
|
|
526
|
+
if (cap !== undefined && cap !== null && cap !== "TLSv1.3") return false;
|
|
527
|
+
// An OpenSSL method name is a short fixed identifier — the longest Node
|
|
528
|
+
// accepts is `TLSv1_2_client_method` — so bound the input before matching
|
|
529
|
+
// rather than scanning whatever a caller put there. Anything longer is not
|
|
530
|
+
// a method name and pins no version.
|
|
531
|
+
var method = opts.secureProtocol;
|
|
532
|
+
if (typeof method !== "string" || method.length > MAX_METHOD_NAME) return true;
|
|
533
|
+
return !VERSION_PINNED_METHOD_RE.test(method);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// Drop the certificate-compression advertisement the framework contributed
|
|
537
|
+
// when the merged options cannot reach TLS 1.3. Without this, capping a
|
|
538
|
+
// connection — a documented override on the WebSocket client and the mail
|
|
539
|
+
// sender — turns into ERR_INVALID_ARG_VALUE before any bytes move, over an
|
|
540
|
+
// option the caller never asked for. A caller who asked for it themselves
|
|
541
|
+
// keeps it, and keeps the error: they named two settings that contradict each
|
|
542
|
+
// other, and quietly dropping one would hide the contradiction.
|
|
543
|
+
function _stripUnreachableCertCompression(merged, caller) {
|
|
544
|
+
if (!merged || typeof merged !== "object") return merged;
|
|
545
|
+
if (_reachesTls13(merged)) return merged;
|
|
546
|
+
if (caller && typeof caller === "object" &&
|
|
547
|
+
caller.certificateCompression !== undefined) return merged;
|
|
548
|
+
delete merged.certificateCompression;
|
|
549
|
+
return merged;
|
|
550
|
+
}
|
|
551
|
+
|
|
479
552
|
function applyToContext(opts) {
|
|
480
553
|
opts = opts || {};
|
|
481
554
|
validateOpts(opts, ["base"], "tls.applyToContext");
|
|
@@ -492,18 +565,38 @@ function applyToContext(opts) {
|
|
|
492
565
|
// (the framework default is the three ML-KEM hybrids plus the classical
|
|
493
566
|
// X25519 fallback) so https.Server / https.Agent
|
|
494
567
|
// negotiate the hybrid KEM with peers that support it and fall back
|
|
495
|
-
// to classical X25519 with peers that don't.
|
|
496
|
-
//
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
568
|
+
// to classical X25519 with peers that don't.
|
|
569
|
+
//
|
|
570
|
+
// node:tls reads that preference as `ecdhCurve`. A `groups` key is accepted
|
|
571
|
+
// and silently ignored — an unimplemented option is not an error there,
|
|
572
|
+
// whereas a malformed `ecdhCurve` throws — so the list has to land under
|
|
573
|
+
// the name the TLS layer actually reads. A base config naming a preference
|
|
574
|
+
// under either spelling keeps its override; `groups` is carried over rather
|
|
575
|
+
// than dropped, since that is the name this function asked operators for.
|
|
576
|
+
//
|
|
577
|
+
// An override that is PRESENT but not a usable list is refused rather than
|
|
578
|
+
// replaced. node:tls rejects a non-string ecdhCurve, so before the
|
|
579
|
+
// preference was translated to that key a malformed one stopped the server
|
|
580
|
+
// at createSecureContext; quietly substituting the framework default here
|
|
581
|
+
// would instead start a listener on groups the operator did not choose,
|
|
582
|
+
// with nothing said. Only a genuinely absent override takes the default.
|
|
583
|
+
var overrideKey = base.ecdhCurve !== undefined ? "ecdhCurve"
|
|
584
|
+
: base.groups !== undefined ? "groups"
|
|
585
|
+
: null;
|
|
586
|
+
if (overrideKey !== null) {
|
|
587
|
+
base.ecdhCurve = _groupPreferenceString(base[overrideKey],
|
|
588
|
+
"tls.applyToContext: base." + overrideKey);
|
|
589
|
+
} else if (STATE.tlsKeyShares.length > 0) {
|
|
590
|
+
base.ecdhCurve = STATE.tlsKeyShares.join(":");
|
|
591
|
+
}
|
|
592
|
+
if (base.groups !== undefined) delete base.groups;
|
|
500
593
|
// RFC 8879 certificate compression — advertise what this runtime can
|
|
501
594
|
// decompress so a peer may send its Certificate message compressed. The
|
|
502
595
|
// framework's own leaf certificates are ML-DSA-87, so the Certificate
|
|
503
596
|
// message is the largest thing on the wire during a handshake. Operators
|
|
504
597
|
// who set `certificateCompression` in their base config keep the override,
|
|
505
598
|
// including `[]` to advertise none.
|
|
506
|
-
if (base.certificateCompression === undefined) {
|
|
599
|
+
if (base.certificateCompression === undefined && _reachesTls13(base)) {
|
|
507
600
|
var certAlgs = C.TLS_CERT_COMPRESSION();
|
|
508
601
|
if (certAlgs.length > 0) base.certificateCompression = certAlgs.slice();
|
|
509
602
|
}
|
|
@@ -636,9 +729,14 @@ function getCaPems() {
|
|
|
636
729
|
//
|
|
637
730
|
// buildOptions({ ecdhCurve, groups, cert, key, ca, minVersion, sni,
|
|
638
731
|
// certificateCompression })
|
|
639
|
-
// returns { minVersion, ecdhCurve,
|
|
732
|
+
// returns { minVersion, ecdhCurve, cert, key, ca, servername,
|
|
640
733
|
// certificateCompression }
|
|
641
734
|
//
|
|
735
|
+
// `groups` is accepted as a spelling of the preference and is NOT returned:
|
|
736
|
+
// node:tls reads the list as `ecdhCurve` and silently ignores `groups`, so
|
|
737
|
+
// emitting both would put the operator's preference under a name the TLS
|
|
738
|
+
// layer never looks at beside the one it does.
|
|
739
|
+
//
|
|
642
740
|
// `ca` accepts a PEM string OR Buffer OR Array<string|Buffer>; arrays
|
|
643
741
|
// are concatenated with `\n` so Node's TLS layer parses every block.
|
|
644
742
|
function _normalizeCaInput(ca) {
|
|
@@ -726,9 +824,11 @@ function buildOptions(opts) {
|
|
|
726
824
|
}
|
|
727
825
|
resolved = requested;
|
|
728
826
|
}
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
827
|
+
// Emitted only as `ecdhCurve` — the one name node:tls reads. Returning a
|
|
828
|
+
// matching `groups` key looked like a second, equivalent handle on the
|
|
829
|
+
// preference and was inert, which is the shape that let a group list ship
|
|
830
|
+
// for several releases without ever reaching a handshake.
|
|
831
|
+
out.ecdhCurve = resolved.join(":");
|
|
732
832
|
|
|
733
833
|
// cert / key — pass-through with light shape check. Both are
|
|
734
834
|
// typically PEM strings or Buffers; arrays are valid for cert
|
|
@@ -885,6 +985,233 @@ function outboundPosture() {
|
|
|
885
985
|
return posture;
|
|
886
986
|
}
|
|
887
987
|
|
|
988
|
+
/**
|
|
989
|
+
* @primitive b.network.tls.explainOutboundFailure
|
|
990
|
+
* @signature b.network.tls.explainOutboundFailure(err, ctx?)
|
|
991
|
+
* @since 0.18.18
|
|
992
|
+
* @status stable
|
|
993
|
+
* @related b.network.tls.outboundPosture
|
|
994
|
+
*
|
|
995
|
+
* Turn a refused outbound handshake into a sentence naming the likely cause.
|
|
996
|
+
*
|
|
997
|
+
* OpenSSL reports a rejected ClientHello as a bare alert — `tlsv1 alert
|
|
998
|
+
* protocol version` — which names neither the peer nor anything an operator
|
|
999
|
+
* can act on, and reads as a problem with the request rather than with the
|
|
1000
|
+
* posture the client applied to it. Two of those alerts are routinely the
|
|
1001
|
+
* posture doing its job:
|
|
1002
|
+
*
|
|
1003
|
+
* A `protocol_version` alert against a client pinning `minVersion: "TLSv1.3"`
|
|
1004
|
+
* means the peer offers no TLS 1.3. That is a version refusal and has nothing
|
|
1005
|
+
* to do with the key-exchange groups, which is worth stating plainly: the
|
|
1006
|
+
* post-quantum posture is the framework's most visible property, so it draws
|
|
1007
|
+
* the blame for handshake failures it did not cause.
|
|
1008
|
+
*
|
|
1009
|
+
* A `handshake_failure` alert against a narrowed group list means the peer
|
|
1010
|
+
* shares none of the offered groups. Under the shipped preference this cannot
|
|
1011
|
+
* happen — the list ends in classical X25519, so a peer with no post-quantum
|
|
1012
|
+
* hybrid negotiates that — so it points at a list an operator has narrowed
|
|
1013
|
+
* past what the peer can do.
|
|
1014
|
+
*
|
|
1015
|
+
* Neither alert is exclusive to these causes, so the wording is hedged and
|
|
1016
|
+
* the original error text is carried through rather than replaced. Returns
|
|
1017
|
+
* null when the error is not one this can speak to, which callers use to fall
|
|
1018
|
+
* back to the original message.
|
|
1019
|
+
*
|
|
1020
|
+
* Pass `tlsOpts` — the options object the dial was made with — rather than
|
|
1021
|
+
* copying settings out of it. Handing the object over names every setting it
|
|
1022
|
+
* could carry, so the explanation stays right as the diagnosis learns to read
|
|
1023
|
+
* settings a caller was not thinking about when the error handler was written.
|
|
1024
|
+
*
|
|
1025
|
+
* @opts
|
|
1026
|
+
* host: string, // peer named in the message; default: omitted
|
|
1027
|
+
* port: number, // appended to host when both are present
|
|
1028
|
+
* tlsOpts: object, // the options the dial used; each setting below is read from it
|
|
1029
|
+
* minVersion: string, // the floor that was applied; default: the live posture
|
|
1030
|
+
* maxVersion: string, // the ceiling that was applied, when a caller capped the dial below TLS 1.3
|
|
1031
|
+
* secureProtocol: string, // an OpenSSL method name, when a caller pinned one version that way
|
|
1032
|
+
* ecdhCurve: string, // the group list that was offered; default: the live posture
|
|
1033
|
+
*
|
|
1034
|
+
* @example
|
|
1035
|
+
* var why = b.network.tls.explainOutboundFailure(err, { host: "registry.example.com", port: 443 });
|
|
1036
|
+
* // -> "TLS handshake refused by registry.example.com:443 - the peer most
|
|
1037
|
+
* // likely does not offer TLS 1.3, ..."
|
|
1038
|
+
*/
|
|
1039
|
+
var EXPLAIN_PREFIX = "TLS handshake refused";
|
|
1040
|
+
var DIAGNOSED = "_blamejsTlsDiagnosed";
|
|
1041
|
+
|
|
1042
|
+
function _hasOwn(obj, key) {
|
|
1043
|
+
return Object.prototype.hasOwnProperty.call(obj, key);
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
// Resolve one setting of the dial being diagnosed. A caller either names the
|
|
1047
|
+
// setting or hands over `tlsOpts` — the options object the dial was made with.
|
|
1048
|
+
// Handing that object over names every setting it could carry, so a value
|
|
1049
|
+
// missing from it means "not pinned for this dial", exactly as a named-but-
|
|
1050
|
+
// undefined field does. Copying fields out of it one at a time is how a
|
|
1051
|
+
// setting the explanation needs goes missing at a call site nobody remembered
|
|
1052
|
+
// to update, which is what kept every capped dial reading as a TLS 1.3 one;
|
|
1053
|
+
// the framework's own callers pass the object.
|
|
1054
|
+
function _dialSetting(ctx, key, fallback) {
|
|
1055
|
+
if (_hasOwn(ctx, key)) return ctx[key];
|
|
1056
|
+
var dialed = ctx.tlsOpts;
|
|
1057
|
+
if (dialed !== null && typeof dialed === "object") return dialed[key];
|
|
1058
|
+
return fallback;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
// A port the caller had as text is still a port. `URL.port` is a string, so
|
|
1062
|
+
// requiring a number here silently dropped the endpoint from the message for
|
|
1063
|
+
// any caller that passed one through — and a host running several TLS
|
|
1064
|
+
// listeners is exactly where the operator needs to know which one refused.
|
|
1065
|
+
function _portSuffix(port) {
|
|
1066
|
+
var n = typeof port === "string" && port !== "" ? Number(port) : port;
|
|
1067
|
+
if (typeof n !== "number" || !isFinite(n)) return "";
|
|
1068
|
+
return ":" + n;
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
function explainOutboundFailure(err, ctx) {
|
|
1072
|
+
if (!err || typeof err !== "object") return null;
|
|
1073
|
+
ctx = ctx || {};
|
|
1074
|
+
var code = typeof err.code === "string" ? err.code : "";
|
|
1075
|
+
var text = typeof err.message === "string" ? err.message : "";
|
|
1076
|
+
// A caller that names a setting is reporting what THIS dial actually used,
|
|
1077
|
+
// and an absent value there means "not pinned for this dial" rather than
|
|
1078
|
+
// "ask the shared posture" -- a request handed a caller-supplied agent, or a
|
|
1079
|
+
// dial whose own TLS options overrode the posture, negotiates on settings
|
|
1080
|
+
// the shared object does not describe. Diagnosing those against the shared
|
|
1081
|
+
// posture can invert the answer: an agent capped at TLS 1.2 talking to a
|
|
1082
|
+
// 1.3-only peer would be reported as the peer lacking 1.3. Only a caller
|
|
1083
|
+
// that names nothing at all falls back to the live posture.
|
|
1084
|
+
var posture = outboundPosture();
|
|
1085
|
+
var minVersion = _dialSetting(ctx, "minVersion", posture.minVersion);
|
|
1086
|
+
var offered = _dialSetting(ctx, "ecdhCurve", posture.ecdhCurve);
|
|
1087
|
+
|
|
1088
|
+
var peer = "";
|
|
1089
|
+
if (typeof ctx.host === "string" && ctx.host.length > 0) {
|
|
1090
|
+
peer = " by " + ctx.host +
|
|
1091
|
+
_portSuffix(ctx.port);
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
// Alert 70 (protocol_version) and the local equivalents Node raises when the
|
|
1095
|
+
// floor cannot be met. Matched on code first; the text is the fallback for
|
|
1096
|
+
// builds that surface the alert without a distinct code.
|
|
1097
|
+
var isVersion = code === "ERR_SSL_TLSV1_ALERT_PROTOCOL_VERSION" ||
|
|
1098
|
+
code === "ERR_SSL_UNSUPPORTED_PROTOCOL" ||
|
|
1099
|
+
code === "ERR_SSL_VERSION_TOO_LOW" ||
|
|
1100
|
+
/alert protocol version|unsupported protocol|version too low/i.test(text);
|
|
1101
|
+
if (isVersion && minVersion === "TLSv1.3") {
|
|
1102
|
+
return EXPLAIN_PREFIX + peer + " - the peer most likely does not " +
|
|
1103
|
+
"offer TLS 1.3, which this client requires (minVersion TLSv1.3). This is " +
|
|
1104
|
+
"a protocol-version refusal and is unrelated to the post-quantum group " +
|
|
1105
|
+
"preference. Underlying error: " + text;
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
var list = typeof offered === "string" && offered.length > 0 ? offered.split(":") : [];
|
|
1109
|
+
if (list.length === 0) return null;
|
|
1110
|
+
|
|
1111
|
+
var hasClassicalFallback = false;
|
|
1112
|
+
for (var i = 0; i < list.length; i += 1) {
|
|
1113
|
+
if (list[i].length > 0 && list[i].indexOf("MLKEM") === -1) hasClassicalFallback = true;
|
|
1114
|
+
}
|
|
1115
|
+
// Only the hybrids are on offer, so a peer without one has nothing left --
|
|
1116
|
+
// worth saying wherever the group list is in the frame.
|
|
1117
|
+
var hybridOnlyNote = hasClassicalFallback ? ""
|
|
1118
|
+
: " The list names only post-quantum hybrids, so a peer without one has " +
|
|
1119
|
+
"nothing left to negotiate.";
|
|
1120
|
+
|
|
1121
|
+
// Codes that mean the group specifically. Carrying a classical group is NOT
|
|
1122
|
+
// proof the peer can pick one -- a TLS 1.3 peer restricted to secp256r1
|
|
1123
|
+
// shares nothing with the hybrids plus X25519 -- so a fallback in the list
|
|
1124
|
+
// does not suppress this.
|
|
1125
|
+
if (code === "ERR_SSL_NO_SHARED_GROUP" || code === "ERR_SSL_WRONG_CURVE" ||
|
|
1126
|
+
/no shared group|wrong curve/i.test(text)) {
|
|
1127
|
+
return EXPLAIN_PREFIX + peer + " - the peer supports none of the " +
|
|
1128
|
+
"key-exchange groups this client offers (" + offered + ")." + hybridOnlyNote +
|
|
1129
|
+
" Underlying error: " + text;
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
// Alert 40 is generic: the peer says it could not proceed without saying
|
|
1133
|
+
// what it objected to. A disjoint TLS 1.3 cipher list produces exactly this
|
|
1134
|
+
// code with a group both sides support, so naming the group list as the
|
|
1135
|
+
// cause would send an operator after the wrong setting. Report what was
|
|
1136
|
+
// pinned and let them narrow it down.
|
|
1137
|
+
if (code === "ERR_SSL_SSL/TLS_ALERT_HANDSHAKE_FAILURE" ||
|
|
1138
|
+
/alert handshake failure/i.test(text)) {
|
|
1139
|
+
// Name TLS 1.3 only when this dial could have reached it. An operator who
|
|
1140
|
+
// capped the connection lower — through a WebSocket tlsOpts override,
|
|
1141
|
+
// their own agent, or a version-pinned secureProtocol — never attempted
|
|
1142
|
+
// 1.3, so pointing at its cipher list sends them after a setting that had
|
|
1143
|
+
// no part in the failure.
|
|
1144
|
+
var reached13 = _reachesTls13({
|
|
1145
|
+
maxVersion: _dialSetting(ctx, "maxVersion", undefined),
|
|
1146
|
+
secureProtocol: _dialSetting(ctx, "secureProtocol", undefined),
|
|
1147
|
+
});
|
|
1148
|
+
return EXPLAIN_PREFIX + peer + " - the peer rejected the handshake with a " +
|
|
1149
|
+
"generic failure alert, which does not say what it objected to. With " +
|
|
1150
|
+
"this client's posture the usual candidates are no mutually supported " +
|
|
1151
|
+
"key-exchange group (it offers " + offered + "), " +
|
|
1152
|
+
(reached13 ? "no shared TLS 1.3 cipher suite" : "no shared cipher suite") +
|
|
1153
|
+
", and certificate selection." + hybridOnlyNote +
|
|
1154
|
+
" Underlying error: " + text;
|
|
1155
|
+
}
|
|
1156
|
+
return null;
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
/**
|
|
1160
|
+
* @primitive b.network.tls.annotateOutboundFailure
|
|
1161
|
+
* @signature b.network.tls.annotateOutboundFailure(err, ctx?)
|
|
1162
|
+
* @since 0.18.18
|
|
1163
|
+
* @status stable
|
|
1164
|
+
* @related b.network.tls.explainOutboundFailure
|
|
1165
|
+
*
|
|
1166
|
+
* Rewrite a refused handshake's message in place to name the likely cause,
|
|
1167
|
+
* and hand the same error back.
|
|
1168
|
+
*
|
|
1169
|
+
* The error object is kept rather than replaced so `err.code`, the error
|
|
1170
|
+
* class and anything a caller already branches on survive untouched; only the
|
|
1171
|
+
* message changes, and only when there is something to say. The stack's
|
|
1172
|
+
* leading line is refreshed alongside it, since it embeds a copy of the
|
|
1173
|
+
* message captured at construction.
|
|
1174
|
+
*
|
|
1175
|
+
* Diagnosis never fails a request: any error raised while working out the
|
|
1176
|
+
* explanation is swallowed and the original message stands.
|
|
1177
|
+
*
|
|
1178
|
+
* @opts
|
|
1179
|
+
* host: string, // peer named in the message
|
|
1180
|
+
* port: number, // appended to host when both are present
|
|
1181
|
+
* tlsOpts: object, // the options the dial used; see explainOutboundFailure for the settings read from it
|
|
1182
|
+
*
|
|
1183
|
+
* @example
|
|
1184
|
+
* socket.on("error", function (err) {
|
|
1185
|
+
* b.network.tls.annotateOutboundFailure(err, { host: host, port: port });
|
|
1186
|
+
* handle(err);
|
|
1187
|
+
* });
|
|
1188
|
+
*/
|
|
1189
|
+
function annotateOutboundFailure(err, ctx) {
|
|
1190
|
+
try {
|
|
1191
|
+
// An error can reach more than one handler on its way out: an h2 session
|
|
1192
|
+
// error retried on h1, or a proxy-leg failure surfacing through the agent
|
|
1193
|
+
// callback of the request to the destination. Whichever leg owns the peer
|
|
1194
|
+
// the handshake was with annotates first and claims the error, because a
|
|
1195
|
+
// later handler would attach a different peer's name to it -- reporting
|
|
1196
|
+
// that the destination refused a handshake that never reached it. The
|
|
1197
|
+
// claim is recorded even when there was nothing to say, since silence
|
|
1198
|
+
// from the owning leg does not make an outer leg's guess correct.
|
|
1199
|
+
if (!err || typeof err !== "object") return err;
|
|
1200
|
+
if (err[DIAGNOSED] === true) return err;
|
|
1201
|
+
Object.defineProperty(err, DIAGNOSED, {
|
|
1202
|
+
value: true, enumerable: false, writable: true, configurable: true,
|
|
1203
|
+
});
|
|
1204
|
+
var why = explainOutboundFailure(err, ctx);
|
|
1205
|
+
if (typeof why !== "string" || why.length === 0) return err;
|
|
1206
|
+
var previous = err.message;
|
|
1207
|
+
err.message = why;
|
|
1208
|
+
if (typeof err.stack === "string" && previous && err.stack.indexOf(previous) !== -1) {
|
|
1209
|
+
err.stack = err.stack.replace(previous, why);
|
|
1210
|
+
}
|
|
1211
|
+
} catch (_e) { /* best-effort: an unexplained error beats a masked one */ }
|
|
1212
|
+
return err;
|
|
1213
|
+
}
|
|
1214
|
+
|
|
888
1215
|
function _emitAuditAdd(metaList, opts) {
|
|
889
1216
|
if (opts.audit === false) return;
|
|
890
1217
|
var sink;
|
|
@@ -1707,7 +2034,7 @@ async function fetchOcspResponse(opts) {
|
|
|
1707
2034
|
throw new TlsTrustError("tls/ocsp-fetch-failed",
|
|
1708
2035
|
"ocsp.fetch: responder request to " + responderUrl + " failed: " + ((e && e.message) || String(e)));
|
|
1709
2036
|
}
|
|
1710
|
-
if (res.status !==
|
|
2037
|
+
if (res.status !== C.HTTP.STATUS.OK || !Buffer.isBuffer(res.body) || res.body.length === 0) {
|
|
1711
2038
|
throw new TlsTrustError("tls/ocsp-fetch-bad-status",
|
|
1712
2039
|
"ocsp.fetch: responder returned status " + res.status + " with an empty/non-buffer body");
|
|
1713
2040
|
}
|
|
@@ -3707,6 +4034,8 @@ module.exports = {
|
|
|
3707
4034
|
auditInsecureTls: auditInsecureTls,
|
|
3708
4035
|
certificateCompressionAlgorithms: certificateCompressionAlgorithms,
|
|
3709
4036
|
outboundPosture: outboundPosture,
|
|
4037
|
+
explainOutboundFailure: explainOutboundFailure,
|
|
4038
|
+
annotateOutboundFailure: annotateOutboundFailure,
|
|
3710
4039
|
postureGeneration: postureGeneration,
|
|
3711
4040
|
addCa: addCa,
|
|
3712
4041
|
addCaBundle: addCaBundle,
|
|
@@ -3736,6 +4065,7 @@ module.exports = {
|
|
|
3736
4065
|
TlsTrustError: TlsTrustError,
|
|
3737
4066
|
NetworkTlsError: NetworkTlsError,
|
|
3738
4067
|
_resetForTest: _resetForTest,
|
|
4068
|
+
_stripUnreachableCertCompression: _stripUnreachableCertCompression,
|
|
3739
4069
|
_checkServerIdentityStrict: _checkServerIdentityStrict,
|
|
3740
4070
|
_stripSctExtensionFromCert: _stripSctExtensionFromCert,
|
|
3741
4071
|
_buildSctSignedEntry: _buildSctSignedEntry,
|
|
@@ -29,10 +29,10 @@
|
|
|
29
29
|
* evidence-of-control at the SSP / ATO level.
|
|
30
30
|
*/
|
|
31
31
|
|
|
32
|
-
var
|
|
32
|
+
var frameworkError = require("./framework-error");
|
|
33
33
|
var validateOpts = require("./validate-opts");
|
|
34
34
|
|
|
35
|
-
var NistCrosswalkError =
|
|
35
|
+
var NistCrosswalkError = frameworkError.defineClass(
|
|
36
36
|
"NistCrosswalkError",
|
|
37
37
|
"nist-crosswalk"
|
|
38
38
|
);
|
|
@@ -181,6 +181,23 @@ function querySingle(server, opts) {
|
|
|
181
181
|
var timeoutMs = opts.timeoutMs || DEFAULT_TIMEOUT_MS;
|
|
182
182
|
|
|
183
183
|
return new Promise(function (resolve, reject) {
|
|
184
|
+
// SNTPv4 client request: NTP_PACKET_BYTES buffer, byte 0 = 0b00_100_011 = 0x23
|
|
185
|
+
// LI=0 (no warning), VN=4, Mode=3 (client). Other bytes zero.
|
|
186
|
+
//
|
|
187
|
+
// Built before the socket is opened, and inside this executor: entropy for
|
|
188
|
+
// the nonce can fail, a socket acquired first would have nobody left to
|
|
189
|
+
// close it, and a throw out here still reaches the caller as a rejection.
|
|
190
|
+
var req = Buffer.alloc(NTP_PACKET_BYTES);
|
|
191
|
+
req[0] = 0x23;
|
|
192
|
+
// RFC 5905 §8 client-cookie: put a random 64-bit nonce in the request's
|
|
193
|
+
// Transmit Timestamp (bytes 40-47). A conformant server copies it verbatim
|
|
194
|
+
// into the reply's Originate Timestamp (bytes 24-31). Verifying that echo
|
|
195
|
+
// rejects an off-path spoofed reply — without it ANY 48-byte UDP datagram
|
|
196
|
+
// reaching our ephemeral port becomes the authoritative time, letting a
|
|
197
|
+
// spoofer force a fatal-drift refuse-to-boot under BLAMEJS_NTP_STRICT.
|
|
198
|
+
var originCookie = nodeCrypto.randomBytes(8);
|
|
199
|
+
originCookie.copy(req, 40);
|
|
200
|
+
|
|
184
201
|
// udp6 for IPv6 literals (`::1`, `fd00::…`), udp4 otherwise. Without
|
|
185
202
|
// this branch a query to an IPv6 NTP host fails with EINVAL because
|
|
186
203
|
// you can't send IPv6 packets through a udp4 socket.
|
|
@@ -199,19 +216,6 @@ function querySingle(server, opts) {
|
|
|
199
216
|
done({ code: "ntp/timeout", message: "no reply from " + server + " within " + timeoutMs + "ms" });
|
|
200
217
|
}, timeoutMs);
|
|
201
218
|
timer.unref();
|
|
202
|
-
|
|
203
|
-
// SNTPv4 client request: NTP_PACKET_BYTES buffer, byte 0 = 0b00_100_011 = 0x23
|
|
204
|
-
// LI=0 (no warning), VN=4, Mode=3 (client). Other bytes zero.
|
|
205
|
-
var req = Buffer.alloc(NTP_PACKET_BYTES);
|
|
206
|
-
req[0] = 0x23;
|
|
207
|
-
// RFC 5905 §8 client-cookie: put a random 64-bit nonce in the request's
|
|
208
|
-
// Transmit Timestamp (bytes 40-47). A conformant server copies it verbatim
|
|
209
|
-
// into the reply's Originate Timestamp (bytes 24-31). Verifying that echo
|
|
210
|
-
// rejects an off-path spoofed reply — without it ANY 48-byte UDP datagram
|
|
211
|
-
// reaching our ephemeral port becomes the authoritative time, letting a
|
|
212
|
-
// spoofer force a fatal-drift refuse-to-boot under BLAMEJS_NTP_STRICT.
|
|
213
|
-
var originCookie = nodeCrypto.randomBytes(8);
|
|
214
|
-
originCookie.copy(req, 40);
|
|
215
219
|
var sendTimeMs = Date.now();
|
|
216
220
|
|
|
217
221
|
socket.on("error", function (e) {
|
|
@@ -270,12 +274,19 @@ function querySingle(server, opts) {
|
|
|
270
274
|
done(null, { driftMs: driftMs, serverTimeMs: serverTimeMs, server: server });
|
|
271
275
|
});
|
|
272
276
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
277
|
+
// send() rejects a malformed destination synchronously; routing that
|
|
278
|
+
// through done() closes the socket rather than stranding it.
|
|
279
|
+
try {
|
|
280
|
+
socket.send(req, 0, req.length, port, server, function (err) {
|
|
281
|
+
if (err) {
|
|
282
|
+
clearTimeout(timer);
|
|
283
|
+
done({ code: "ntp/refused", message: "send to " + server + ": " + err.message });
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
} catch (e) {
|
|
287
|
+
clearTimeout(timer);
|
|
288
|
+
done({ code: "ntp/refused", message: "send to " + server + ": " + e.message });
|
|
289
|
+
}
|
|
279
290
|
});
|
|
280
291
|
}
|
|
281
292
|
|