@blamejs/pki 0.9.5 → 0.9.6
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 +37 -0
- package/README.md +7 -6
- package/bin/pki.js +110 -34
- package/lib/acme.js +91 -46
- package/lib/attrcert-sign.js +4 -3
- package/lib/cmp-build.js +16 -6
- package/lib/cmp-session.js +19 -3
- package/lib/cmp-verify.js +25 -2
- package/lib/cms-compress.js +3 -3
- package/lib/cms-digest.js +3 -3
- package/lib/cms-verify.js +18 -8
- package/lib/constants.js +1 -0
- package/lib/crl-sign.js +3 -3
- package/lib/csr-sign.js +1 -1
- package/lib/ct.js +16 -3
- package/lib/est.js +44 -29
- package/lib/guard-all.js +2 -0
- package/lib/guard-distrust.js +224 -0
- package/lib/guard-json.js +1 -1
- package/lib/http-transport.js +299 -36
- package/lib/jose.js +1 -1
- package/lib/lint.js +67 -7
- package/lib/ocsp-verify.js +9 -3
- package/lib/ocsp.js +8 -4
- package/lib/oid.js +4 -0
- package/lib/path-validate.js +112 -16
- package/lib/possession.js +6 -2
- package/lib/related-cert.js +2 -2
- package/lib/revocation-fetch.js +38 -9
- package/lib/scep.js +28 -13
- package/lib/schema-pkix.js +3 -3
- package/lib/sigstore.js +11 -6
- package/lib/smime.js +18 -13
- package/lib/tls-cert-compress.js +6 -5
- package/lib/trust.js +121 -20
- package/lib/trustanchor-build.js +16 -1
- package/lib/tsp-sign.js +7 -1
- package/lib/webauthn-mds.js +29 -8
- package/lib/webauthn.js +46 -14
- package/lib/x509-sign.js +8 -4
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/http-transport.js
CHANGED
|
@@ -51,8 +51,9 @@
|
|
|
51
51
|
* transport. Trust is EXPLICIT and fail-closed: a request is refused unless it
|
|
52
52
|
* carries an https URL and either a `tls.anchors` set (an Explicit trust-anchor
|
|
53
53
|
* database, mapped to the node `ca` option) or an explicit `tls.useSystemStore`
|
|
54
|
-
* opt-in to
|
|
55
|
-
* code path that disables server-certificate verification.
|
|
54
|
+
* opt-in to the platform certificate store. `rejectUnauthorized` is always on: there is no
|
|
55
|
+
* code path that disables server-certificate verification. `tls.distrustedKeys` refuses a
|
|
56
|
+
* server whose verified chain carries a listed key. The response body is
|
|
56
57
|
* bounded WHILE it streams: the accumulator aborts the socket the instant the running
|
|
57
58
|
* total crosses `maxResponseBytes`, before a byte reaches a decoder. A protocol
|
|
58
59
|
* client MAY parameterize the transport with its own `(code, message, cause)` error
|
|
@@ -87,6 +88,17 @@ var nodeUtil = require("node:util");
|
|
|
87
88
|
var constants = require("./constants");
|
|
88
89
|
var guard = require("./guard-all");
|
|
89
90
|
var intrinsic = require("./guard-intrinsic");
|
|
91
|
+
/** @internal The certificate reader, and the two operations the distrust walk proves a link with,
|
|
92
|
+
* taken at load. A replaced `verify` would answer whether each link of a server chain is signed by
|
|
93
|
+
* the certificate above it. */
|
|
94
|
+
var _X509Certificate = require("node:crypto").X509Certificate;
|
|
95
|
+
var _x509Verify = intrinsic.uncurry(_X509Certificate.prototype.verify);
|
|
96
|
+
var _x509PublicKey = intrinsic.uncurry(intrinsic.getOwnPropertyDescriptor(_X509Certificate.prototype, "publicKey").get);
|
|
97
|
+
var _x509Ca = intrinsic.uncurry(intrinsic.getOwnPropertyDescriptor(_X509Certificate.prototype, "ca").get);
|
|
98
|
+
var _x509CheckIssued = intrinsic.uncurry(_X509Certificate.prototype.checkIssued);
|
|
99
|
+
var _x509ValidFrom = intrinsic.uncurry(intrinsic.getOwnPropertyDescriptor(_X509Certificate.prototype, "validFromDate").get);
|
|
100
|
+
var _x509ValidTo = intrinsic.uncurry(intrinsic.getOwnPropertyDescriptor(_X509Certificate.prototype, "validToDate").get);
|
|
101
|
+
var _Date = intrinsic.Date;
|
|
90
102
|
/** @internal `resolve` and `reject` build their promise from the RECEIVER, so reading the global
|
|
91
103
|
* binding at the call hands the construction to whatever that binding holds. A refusal returned as a
|
|
92
104
|
* rejected promise is a verdict: a replacement whose `reject` resolves lets a request the preparation
|
|
@@ -245,10 +257,16 @@ function _peerChain(socket) {
|
|
|
245
257
|
return out;
|
|
246
258
|
}
|
|
247
259
|
|
|
260
|
+
/** @internal Whether an anchor buffer is PEM text rather than DER. A DER certificate is a SEQUENCE,
|
|
261
|
+
* so its first byte is 0x30; a buffer that starts with anything else is PEM text, which may carry a
|
|
262
|
+
* comment ahead of its armor. */
|
|
263
|
+
function _isPemBuffer(a) {
|
|
264
|
+
return _looksLikePemArmor(_bufToString(a, "latin1", 0, 64)) || (a.length > 0 && a[0] !== 0x30);
|
|
265
|
+
}
|
|
248
266
|
function _pemifyAnchor(a) {
|
|
249
267
|
if (typeof a === "string") return a;
|
|
250
268
|
if (_isBuffer(a)) {
|
|
251
|
-
if (
|
|
269
|
+
if (_isPemBuffer(a)) return a;
|
|
252
270
|
var b64 = _wrap64Lines(_bufToString(a, "base64"));
|
|
253
271
|
return "-----BEGIN CERTIFICATE-----\n" + b64 + "-----END CERTIFICATE-----\n";
|
|
254
272
|
}
|
|
@@ -264,20 +282,207 @@ function _pemifyAnchors(anchors) {
|
|
|
264
282
|
* trust a `useSystemStore` connection is accepted under. */
|
|
265
283
|
var _getCACertificates = typeof nodeTls.getCACertificates === "function" ? nodeTls.getCACertificates : null;
|
|
266
284
|
|
|
285
|
+
/** @internal The platform store alone: the certificates the operating system trusts. Node's
|
|
286
|
+
* compiled-in Mozilla set is a separate store that changes only when Node is upgraded, so a root a
|
|
287
|
+
* distribution has removed would stay trusted if it were added here. A store that cannot be read
|
|
288
|
+
* answers an empty list, and the request paths refuse an empty list rather than leave `ca` unset,
|
|
289
|
+
* which would hand the connection Node's default store instead. */
|
|
267
290
|
var _systemCaCache = null;
|
|
268
291
|
function _systemCa() {
|
|
269
292
|
if (_systemCaCache !== null) return _systemCaCache;
|
|
270
293
|
var out = [];
|
|
271
294
|
if (_getCACertificates !== null) {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
try { var c = intrinsic.apply(_getCACertificates, nodeTls, [t]); if (_isArray(c)) out = _concatList(out, c); } catch (_e) { }
|
|
275
|
-
});
|
|
295
|
+
// allow:swallow-unverified an unreadable platform store answers empty, and an empty list is refused by every caller
|
|
296
|
+
try { var c = intrinsic.apply(_getCACertificates, nodeTls, ["system"]); if (_isArray(c)) out = _concatList(out, c); } catch (_e) { }
|
|
276
297
|
}
|
|
277
298
|
_systemCaCache = out;
|
|
278
299
|
return out;
|
|
279
300
|
}
|
|
280
301
|
|
|
302
|
+
/** @internal The DER SubjectPublicKeyInfo of a certificate the TLS layer verified. The toolkit's
|
|
303
|
+
* parser is reached through an inline require because of a circular load (schema-x509 ->
|
|
304
|
+
* schema-pkix -> ct -> this module). A certificate the strict parser refuses was still accepted by
|
|
305
|
+
* the TLS stack, so its key is taken from node's own reading rather than left unchecked. */
|
|
306
|
+
function _peerSpki(raw) {
|
|
307
|
+
var x509 = require("./schema-x509"); // allow:inline-require -- circular load: schema-x509 -> schema-pkix -> ct -> this module
|
|
308
|
+
try { return x509.parse(raw).subjectPublicKeyInfo.bytes; }
|
|
309
|
+
catch (_e) {
|
|
310
|
+
// allow:swallow-unverified the parser's refusal is answered from node's reading of the same certificate
|
|
311
|
+
return guard.crypto.exportKey(_x509PublicKey(new _X509Certificate(raw)), { type: "spki", format: "der" });
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/** @internal Whether `child` carries a signature that verifies under `parent`'s key. A certificate
|
|
316
|
+
* node cannot read, or whose signature algorithm it cannot verify, answers false. */
|
|
317
|
+
function _signedBy(child, parent) {
|
|
318
|
+
try { return _x509Verify(new _X509Certificate(child), _x509PublicKey(new _X509Certificate(parent))) === true; }
|
|
319
|
+
catch (_e) {
|
|
320
|
+
// allow:swallow-unverified a link node cannot verify is reported as unproven, and the caller refuses it
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/** @internal Whether `raw` is inside its validity window now and, for a certificate that issues
|
|
326
|
+
* another on the walk, is a CA certificate. A certificate node cannot read answers false. */
|
|
327
|
+
function _usableLink(raw, mustBeCa, now) {
|
|
328
|
+
try {
|
|
329
|
+
var c = new _X509Certificate(raw);
|
|
330
|
+
if (mustBeCa && _x509Ca(c) !== true) return false;
|
|
331
|
+
return guard.time.instantOf(_x509ValidFrom(c)) <= now && now <= guard.time.instantOf(_x509ValidTo(c));
|
|
332
|
+
} catch (_e) {
|
|
333
|
+
// allow:swallow-unverified a certificate node cannot read is reported as unproven, and the caller refuses it
|
|
334
|
+
return false;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/** @internal The trust anchors a connection is opened under, as `{ keys, entries }`: `keys` is a set
|
|
339
|
+
* keyed by `guard.distrust.keyDigest`, and `entries` holds each anchor certificate node reads, with
|
|
340
|
+
* its key, for the branches through the trust store `_storeBranchListed` follows. `caList` holds PEM
|
|
341
|
+
* text, which may carry several certificates, and DER buffers. The codec is reached through an
|
|
342
|
+
* inline require for the circular load named at `_peerSpki`. */
|
|
343
|
+
function _anchorKeySet(caList) {
|
|
344
|
+
var pkix = require("./schema-pkix"); // allow:inline-require -- circular load: schema-pkix -> ct -> this module
|
|
345
|
+
var set = _objectCreate(null);
|
|
346
|
+
var entries = [];
|
|
347
|
+
for (var i = 0; i < caList.length; i++) {
|
|
348
|
+
var a = caList[i];
|
|
349
|
+
var ders = [];
|
|
350
|
+
if (_isBuffer(a) && !_isPemBuffer(a)) ders = [a];
|
|
351
|
+
else {
|
|
352
|
+
var rows;
|
|
353
|
+
try { rows = pkix.pemDecodeBundle(typeof a === "string" ? a : _bufToString(a, "latin1"), {}, Error); }
|
|
354
|
+
catch (_e) {
|
|
355
|
+
// allow:swallow-unverified an anchor this reader cannot decode adds no key, so a chain ending there is refused
|
|
356
|
+
rows = [];
|
|
357
|
+
}
|
|
358
|
+
for (var r = 0; r < rows.length; r++) if (rows[r].label === "CERTIFICATE") _arrPush(ders, rows[r].der);
|
|
359
|
+
}
|
|
360
|
+
for (var d = 0; d < ders.length; d++) {
|
|
361
|
+
try {
|
|
362
|
+
var spki = _peerSpki(ders[d]);
|
|
363
|
+
set[guard.distrust.keyDigest(spki)] = true;
|
|
364
|
+
_arrPush(entries, { x509: new _X509Certificate(ders[d]), spki: spki });
|
|
365
|
+
} catch (_e2) {
|
|
366
|
+
// allow:swallow-unverified an anchor whose key cannot be read adds no key, so a chain ending there is refused
|
|
367
|
+
void 0;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
return { keys: set, entries: entries };
|
|
372
|
+
}
|
|
373
|
+
var _systemAnchorKeys = null;
|
|
374
|
+
function _systemAnchorKeySet() {
|
|
375
|
+
if (_systemAnchorKeys === null) _systemAnchorKeys = _anchorKeySet(_systemCa());
|
|
376
|
+
return _systemAnchorKeys;
|
|
377
|
+
}
|
|
378
|
+
/** @internal The anchors for one hop: the caller's, and the platform store's when the hop uses it.
|
|
379
|
+
* Computed only when the hop has a distrust list to apply. */
|
|
380
|
+
function _hopAnchorKeys(distrust, anchorPems, useSystem) {
|
|
381
|
+
if (distrust === null || distrust.length === 0) return null;
|
|
382
|
+
var own = _anchorKeySet(anchorPems);
|
|
383
|
+
if (!useSystem) return own;
|
|
384
|
+
var sys = _systemAnchorKeySet();
|
|
385
|
+
var names = _objectKeys(sys.keys);
|
|
386
|
+
for (var i = 0; i < names.length; i++) own.keys[names[i]] = true;
|
|
387
|
+
return { keys: own.keys, entries: _concatList(own.entries, sys.entries) };
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/** @internal The digest of a listed key on a branch through the trust store above `childX509`, or
|
|
391
|
+
* null. OpenSSL looks in the trust store for an issuer before it looks at what the server sent, so
|
|
392
|
+
* when the store holds a certificate that issues one on node's walk (an intermediate, or a
|
|
393
|
+
* cross-certificate), the session may have been verified up that branch instead. Every such branch
|
|
394
|
+
* is followed upward through the store, and a listed key anywhere on one refuses the session.
|
|
395
|
+
* `visited` bounds the search to one visit per anchor certificate. */
|
|
396
|
+
function _storeBranchListed(childX509, anchors, list, visited) {
|
|
397
|
+
for (var i = 0; i < anchors.entries.length; i++) {
|
|
398
|
+
var e = anchors.entries[i];
|
|
399
|
+
if (_arrIndexOf(visited, e) !== -1) continue;
|
|
400
|
+
var issues;
|
|
401
|
+
try { issues = _x509CheckIssued(childX509, e.x509) === true && _x509Verify(childX509, _x509PublicKey(e.x509)) === true; }
|
|
402
|
+
catch (_e) {
|
|
403
|
+
// allow:swallow-unverified a store certificate node cannot compare is not an issuer on any branch
|
|
404
|
+
issues = false;
|
|
405
|
+
}
|
|
406
|
+
if (!issues) continue;
|
|
407
|
+
_arrPush(visited, e);
|
|
408
|
+
if (guard.distrust.has(list, e.spki)) return guard.distrust.digest(e.spki);
|
|
409
|
+
var above = _storeBranchListed(e.x509, anchors, list, visited);
|
|
410
|
+
if (above !== null) return above;
|
|
411
|
+
}
|
|
412
|
+
return null;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/** @internal The first distrusted key on the chain the session verified, as `{ digest }`; null
|
|
416
|
+
* when none is listed; or `{ unproven: reason }` when the walk cannot stand for the verified chain.
|
|
417
|
+
*
|
|
418
|
+
* The walk follows the detailed peer certificate's `issuerCertificate` links, which node builds by
|
|
419
|
+
* name and key identifier from the certificates the server sent and then from the trust store,
|
|
420
|
+
* without checking a signature, a validity period or whether the top is trusted. OpenSSL may have
|
|
421
|
+
* verified the session through different certificates with the same names, so the walk is held to
|
|
422
|
+
* what makes it a path in its own right: each link is signed by the certificate above it, each
|
|
423
|
+
* certificate below the top is inside its validity window, each one that issues another is a CA,
|
|
424
|
+
* and the top is self-issued with the key of a configured trust anchor (`anchors.keys`). OpenSSL
|
|
425
|
+
* looks in the trust store for an issuer first, so where the store holds a certificate that issues
|
|
426
|
+
* one on the walk, the session may have been verified up the store's branch rather than the walk;
|
|
427
|
+
* every such branch is followed (`_storeBranchListed`) and a listed key on one refuses the session.
|
|
428
|
+
* Node gives no way to read the chain OpenSSL verified, so the check covers every chain the session
|
|
429
|
+
* could have been verified through, and may refuse a session whose verified chain was clean. The
|
|
430
|
+
* walk reads up to `LIMITS.PATH_MAX_CERTS` certificates. */
|
|
431
|
+
function _distrustedInChain(cert, list, anchors) {
|
|
432
|
+
var now = guard.time.instantOf(new _Date());
|
|
433
|
+
var seen = [];
|
|
434
|
+
var visitedStore = [];
|
|
435
|
+
var cur = cert;
|
|
436
|
+
for (;;) {
|
|
437
|
+
if (seen.length >= constants.LIMITS.PATH_MAX_CERTS) {
|
|
438
|
+
return { unproven: "the chain is longer than the " + constants.LIMITS.PATH_MAX_CERTS + " certificates the check reads" };
|
|
439
|
+
}
|
|
440
|
+
var spki = _peerSpki(cur.raw);
|
|
441
|
+
if (guard.distrust.has(list, spki)) return { digest: guard.distrust.digest(spki) };
|
|
442
|
+
var storeHit;
|
|
443
|
+
try { storeHit = _storeBranchListed(new _X509Certificate(cur.raw), anchors, list, visitedStore); }
|
|
444
|
+
catch (_se) {
|
|
445
|
+
// allow:swallow-unverified a certificate node cannot read leaves the store branches unchecked, so the walk is unproven
|
|
446
|
+
return { unproven: "a certificate on the chain could not be compared with the trust store" };
|
|
447
|
+
}
|
|
448
|
+
if (storeHit !== null) return { digest: storeHit };
|
|
449
|
+
var up = cur.issuerCertificate;
|
|
450
|
+
if (up === cur) {
|
|
451
|
+
return anchors.keys[guard.distrust.keyDigest(spki)] === true ? null
|
|
452
|
+
: { unproven: "the chain ends at a self-issued certificate whose key is not a configured trust anchor's" };
|
|
453
|
+
}
|
|
454
|
+
if (!_usableLink(cur.raw, seen.length > 0, now)) {
|
|
455
|
+
return { unproven: "a certificate on the chain is outside its validity period or issues without being a CA" };
|
|
456
|
+
}
|
|
457
|
+
_arrPush(seen, cur);
|
|
458
|
+
if (_arrIndexOf(seen, up) !== -1) return { unproven: "the chain loops back on a certificate it already passed" };
|
|
459
|
+
if (!up || !_isBuffer(up.raw) || !_signedBy(cur.raw, up.raw)) {
|
|
460
|
+
return { unproven: "a link of the chain is not signed by the certificate node places above it" };
|
|
461
|
+
}
|
|
462
|
+
cur = up;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function _distrustError(hit, hop) {
|
|
467
|
+
var e = new Error(hit.digest !== undefined
|
|
468
|
+
? "the " + hop + " certificate chain carries a key listed in tls.distrustedKeys (SubjectPublicKeyInfo SHA-256 " + hit.digest + ")"
|
|
469
|
+
: "the " + hop + " certificate chain could not be checked against tls.distrustedKeys: " + hit.unproven);
|
|
470
|
+
e.pkiDistrustedKey = hop;
|
|
471
|
+
return e;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/** @internal An identity check that also refuses a chain carrying a distrusted key. The identity
|
|
475
|
+
* verdict comes first, so a name mismatch is still reported as one. */
|
|
476
|
+
function _withDistrust(check, list, hop, anchorKeys) {
|
|
477
|
+
if (list === null || list.length === 0) return check;
|
|
478
|
+
return function (host, cert) {
|
|
479
|
+
var err = check(host, cert);
|
|
480
|
+
if (err) return err;
|
|
481
|
+
var hit = _distrustedInChain(cert, list, anchorKeys);
|
|
482
|
+
return hit === null ? undefined : _distrustError(hit, hop);
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
|
|
281
486
|
function _isBlockedIp(ip) {
|
|
282
487
|
/** @internal The argument has to BE a string, and what cannot be classified is blocked. `isIP`
|
|
283
488
|
* converts its argument, so a value whose conversion answers a loopback literal to the family
|
|
@@ -389,6 +594,8 @@ function _hasWordBounded(l, word) {
|
|
|
389
594
|
function _isLowerWordChar(c) { return (c >= 97 && c <= 122) || (c >= 48 && c <= 57) || c === 95; }
|
|
390
595
|
function _classifyError(e, C) {
|
|
391
596
|
if (e && e.pkiBlockedAddress) return C("blocked-address");
|
|
597
|
+
if (e && e.pkiDistrustedKey === "proxy") return C("proxy-key-distrusted");
|
|
598
|
+
if (e && e.pkiDistrustedKey === "server") return C("server-key-distrusted");
|
|
392
599
|
var s = _stringOf((e && e.code) || "") + " " + _stringOf((e && e.message) || "");
|
|
393
600
|
var l = _toLowerCase(s);
|
|
394
601
|
if (_strIndexOf(l, "protocol_version") !== -1 || _strIndexOf(l, "unsupported_protocol") !== -1 || _strIndexOf(l, "version_too_low") !== -1 ||
|
|
@@ -403,7 +610,11 @@ function _keySet(names) { var m = _objectCreate(null); for (var i = 0; i < names
|
|
|
403
610
|
var PROXY_KEYS = _keySet(["url", "auth", "tls"]);
|
|
404
611
|
var PROXY_AUTH_KEYS = _keySet(["scheme", "username", "password", "allowMD5", "allowLegacyQop"]);
|
|
405
612
|
var PROXY_DIGEST_KNOBS = ["allowMD5", "allowLegacyQop"];
|
|
406
|
-
var PROXY_TLS_KEYS = _keySet(["anchors", "useSystemStore", "servername", "minVersion"]);
|
|
613
|
+
var PROXY_TLS_KEYS = _keySet(["anchors", "useSystemStore", "servername", "minVersion", "distrustedKeys"]);
|
|
614
|
+
/** @internal The names a `tls` policy bag may carry. A name outside them is refused rather than
|
|
615
|
+
* ignored: a misspelled `distrustedKeys` or `anchors` would otherwise leave the connection under a
|
|
616
|
+
* policy the caller did not state. */
|
|
617
|
+
var TLS_KEYS = _keySet(["anchors", "useSystemStore", "cert", "key", "minVersion", "servername", "checkServerIdentity", "distrustedKeys"]);
|
|
407
618
|
|
|
408
619
|
function _copyAnchor(a) { return (a !== null && typeof a === "object" && nodeUtil.types.isProxy(a)) ? a : (_isBuffer(a) ? _bufferFrom(a) : a); }
|
|
409
620
|
function _noopProxyErr(code, message) { var e = new Error(message); e.code = code; e.isPkiError = true; return e; }
|
|
@@ -461,6 +672,12 @@ function snapshotProxy(p) {
|
|
|
461
672
|
out.tls.anchors = copied;
|
|
462
673
|
} else if (typeof a === "string") out.tls.anchors = a;
|
|
463
674
|
else if (a !== null && typeof a === "object" && !nodeUtil.types.isProxy(a) && _isBuffer(a)) out.tls.anchors = _bufferFrom(a);
|
|
675
|
+
/** @internal The distrust list is copied for the reason the anchors are: the snapshot is taken
|
|
676
|
+
* so a caller's later edit cannot change the policy, and a shared array would carry the edit. */
|
|
677
|
+
var dk = p.tls.distrustedKeys;
|
|
678
|
+
if (dk !== undefined && dk !== null) {
|
|
679
|
+
out.tls.distrustedKeys = guard.distrust.keys(dk, _noopProxyErr, "transport/bad-proxy", "opts.proxy.tls.distrustedKeys");
|
|
680
|
+
}
|
|
464
681
|
}
|
|
465
682
|
return out;
|
|
466
683
|
} catch (_e) {
|
|
@@ -472,7 +689,7 @@ function snapshotProxy(p) {
|
|
|
472
689
|
/** @internal `originHost`, `statedPort` and `scheme` are the values the request path already read off
|
|
473
690
|
* the parsed origin; they are passed in rather than read again here, so the authority this builds for
|
|
474
691
|
* the CONNECT target is the same authority the socket options were built from. */
|
|
475
|
-
function _validateProxy(request, defaults, E, C, originHost, originStatedPort, originScheme) {
|
|
692
|
+
function _validateProxy(request, defaults, E, C, originHost, originStatedPort, originScheme, originDistrust) {
|
|
476
693
|
var rawProxy = request.proxy;
|
|
477
694
|
var proxy = rawProxy !== undefined ? rawProxy : defaults.proxy;
|
|
478
695
|
if (proxy === undefined || proxy === null) return null;
|
|
@@ -544,7 +761,17 @@ function _validateProxy(request, defaults, E, C, originHost, originStatedPort, o
|
|
|
544
761
|
* the CONNECT options. `concatList` copies own indexes and appends own indexes. */
|
|
545
762
|
if (pHasAnchors) pCa = _concatList(pCa, _pemifyAnchors(pAnchors));
|
|
546
763
|
if (pUseSystem) pCa = _concatList(pCa, _systemCa());
|
|
547
|
-
|
|
764
|
+
if (pCa.length === 0) {
|
|
765
|
+
throw E(C("no-trust-anchors"), "proxy.tls.useSystemStore is set and the platform certificate store returned no certificates, " +
|
|
766
|
+
"so the proxy cannot be authenticated; set proxy.tls.anchors");
|
|
767
|
+
}
|
|
768
|
+
/** @internal A key the caller distrusts is refused on this hop too, whether it was listed for
|
|
769
|
+
* the proxy or for the origin. */
|
|
770
|
+
var pDistrust = guard.distrust.union(
|
|
771
|
+
guard.distrust.keys(ptls ? ptls.distrustedKeys : undefined, E, C("bad-proxy"), "opts.proxy.tls.distrustedKeys"),
|
|
772
|
+
originDistrust);
|
|
773
|
+
proxyTls = { ca: pCa, minVersion: pMinVersion, servername: pServername, distrust: pDistrust,
|
|
774
|
+
anchorKeys: _hopAnchorKeys(pDistrust, pHasAnchors ? _pemifyAnchors(pAnchors) : [], pUseSystem) };
|
|
548
775
|
}
|
|
549
776
|
|
|
550
777
|
var originAuthHost = _isIP(originHost) === 6 ? "[" + originHost + "]" : originHost;
|
|
@@ -587,6 +814,7 @@ function _digestProxyCredential(headers, pinfo, C, E) {
|
|
|
587
814
|
}, E);
|
|
588
815
|
}
|
|
589
816
|
function _proxyErrorClass(e, C) {
|
|
817
|
+
if (e && e.pkiDistrustedKey === "proxy") return C("proxy-key-distrusted");
|
|
590
818
|
var code = _stringOf((e && e.code) || "");
|
|
591
819
|
if (_strIndexOf(code, "ERR_TLS") === 0 || _strIndexOf(code, "ERR_SSL") === 0 || _strIndexOf(code, "SSL") !== -1 ||
|
|
592
820
|
_strIndexOf(code, "CERT") !== -1 || _strIndexOf(code, "SELF_SIGNED") !== -1 || _strIndexOf(code, "VERIFY") !== -1 ||
|
|
@@ -605,28 +833,34 @@ function _proxyErrorClass(e, C) {
|
|
|
605
833
|
*
|
|
606
834
|
* Build a fail-closed `node:https` transport: `transport(request) -> Promise<{ status,
|
|
607
835
|
* headers, body }>`. `defaults` binds a `tls` policy (`anchors` -> the node `ca`;
|
|
608
|
-
* `useSystemStore` to opt into the
|
|
836
|
+
* `useSystemStore` to opt into the platform certificate store; `distrustedKeys` to refuse
|
|
837
|
+
* listed keys; `cert`/`key` for mutual TLS;
|
|
609
838
|
* `minVersion` 'TLSv1.2' (default) or 'TLSv1.3'; `servername`; a `checkServerIdentity`
|
|
610
839
|
* that may only tighten) plus `timeout` and `maxResponseBytes` budgets. Each `request`
|
|
611
|
-
* ({ method, url, headers, body, tls, timeout, maxResponseBytes }) may override them
|
|
840
|
+
* ({ method, url, headers, body, tls, timeout, maxResponseBytes }) may override them,
|
|
841
|
+
* except `distrustedKeys`: the request's list is added to the default's.
|
|
612
842
|
* A non-https URL (`transport/insecure-url`), a request with neither an explicit
|
|
613
|
-
* anchor nor `useSystemStore
|
|
843
|
+
* anchor nor `useSystemStore`, or with `useSystemStore` and an empty platform store
|
|
844
|
+
* (`transport/no-trust-anchors`), a body over the streaming
|
|
614
845
|
* cap (`transport/response-too-large`), a stalled socket (`transport/timeout`), a below
|
|
615
|
-
* -floor negotiation (`transport/tls-floor`),
|
|
616
|
-
* (`transport/server-auth-failed`)
|
|
846
|
+
* -floor negotiation (`transport/tls-floor`), a failed server authentication
|
|
847
|
+
* (`transport/server-auth-failed`), or a verified chain carrying a distrusted key
|
|
848
|
+
* (`transport/server-key-distrusted`) all fail closed; `rejectUnauthorized` is always on.
|
|
617
849
|
* A protocol client passes its own error factory (`defaults.E`) + `defaults.errPrefix`
|
|
618
850
|
* to surface domain codes (`est/...`). The transport owns no HTTP/redirect/auth
|
|
619
851
|
* semantics; those live in the message layer that consumes the response triple.
|
|
620
852
|
*
|
|
621
853
|
* @opts
|
|
622
854
|
* - `tls.anchors` -- Explicit trust anchor(s): a DER/PEM Buffer, an array, or PEM string(s) (node `ca`).
|
|
623
|
-
* - `tls.useSystemStore` -- boolean; the one opt-in to
|
|
855
|
+
* - `tls.useSystemStore` -- boolean; the one opt-in to the platform certificate store, `tls.getCACertificates("system")` (default false). Node's compiled-in roots are not included; pass them as `tls.anchors` to use them. An empty platform store is refused with `transport/no-trust-anchors` unless `tls.anchors` is also set.
|
|
856
|
+
* - `tls.distrustedKeys` -- SHA-256 digests of SubjectPublicKeyInfo values (64 hex characters or 32 bytes each, see `pki.trust.spkiDigest`). A server whose verified chain carries a listed key, at any position including the root, is refused with `transport/server-key-distrusted`. The chain node reports is checked as a path of its own: each link is signed by the certificate above it, each certificate below the top is inside its validity period and each one that issues another is a CA, and the top is self-issued with the key of a configured anchor. Where a configured anchor certificate (an intermediate or a cross-certificate, as a platform store can hold) issues a certificate on that chain, the branch up through the anchors is checked too, since OpenSSL may have verified the session that way. A chain that fails any of these, or is longer than `C.LIMITS.PATH_MAX_CERTS`, is refused with the same code while a list is set, so a session can be refused when the chain OpenSSL verified was clean but another chain it could have used is not. A request's list is added to the transport default's, which is read when the transport is built.
|
|
857
|
+
* - A `tls` bag naming anything other than the options above is refused with `transport/bad-input`, the transport default's when the transport is built and a request's before a socket opens.
|
|
624
858
|
* - `tls.cert` / `tls.key` -- client certificate + key for mutual-TLS re-enrollment.
|
|
625
859
|
* - `tls.minVersion` -- 'TLSv1.2' (default) or 'TLSv1.3'; never below the floor.
|
|
626
860
|
* - `tls.servername` / `tls.checkServerIdentity` -- SNI + RFC 6125 identity; may tighten, never disable.
|
|
627
861
|
* - `timeout` -- ms (default C.TIME.seconds(30)); `maxResponseBytes` -- default LIMITS.HTTP_MAX_RESPONSE_BYTES, tightenable downward only.
|
|
628
862
|
* - `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. It cannot be combined with `proxy`: a forward proxy resolves the origin itself, so the transport cannot enforce the block on it, and the combination is refused (`transport/bad-proxy`).
|
|
629
|
-
* - `proxy` -- reach the https origin through a forward HTTP proxy: `{ url: "http://proxy:3128" | "https://proxy:3128", auth?: { scheme: "basic" | "digest", username, password, allowMD5?, allowLegacyQop? }, tls?: { anchors, useSystemStore, servername, minVersion } }`. A CONNECT tunnel is opened to the proxy and the origin's TLS is negotiated inside it under the identical origin trust policy, so the proxy cannot read the origin's encrypted session or substitute the origin certificate. `auth` requires an `https://` proxy: the credentials ride the authenticated TLS-to-proxy channel (verified against `proxy.tls`, which is required for an https proxy), so a plaintext `http://` proxy carrying `auth` is refused (`transport/proxy-auth-requires-tls`) rather than exposing them to the proxy hop. An `http://` proxy is tunnel-only. Basic (RFC 7617) sends its credential on the first CONNECT. Digest (RFC 7616) answers the proxy's `Proxy-Authenticate` challenge on a `407`, hashing the method `CONNECT` over the authority-form target, and answers exactly once: a proxy that repeats its challenge has rejected the credential. The Digest policy is the one the origin verbs use, so MD5 and a challenge carrying no `qop` are refused unless `allowMD5` / `allowLegacyQop` says otherwise (`transport/proxy-digest-weak-algorithm`, `transport/proxy-digest-unsupported-algorithm`, `transport/proxy-digest-no-qop`, `transport/proxy-digest-bad-challenge`). A `407` is `transport/proxy-auth-failed` (credentials rejected) or `transport/proxy-auth-required` (none supplied, or none of the offered schemes is the configured one); a proxy certificate that does not verify is `transport/proxy-tls-failed`; a non-2xx CONNECT is `transport/proxy-connect-failed`; a malformed option is `transport/bad-proxy`. Off by default (a direct connection).
|
|
863
|
+
* - `proxy` -- reach the https origin through a forward HTTP proxy: `{ url: "http://proxy:3128" | "https://proxy:3128", auth?: { scheme: "basic" | "digest", username, password, allowMD5?, allowLegacyQop? }, tls?: { anchors, useSystemStore, servername, minVersion, distrustedKeys } }`. A CONNECT tunnel is opened to the proxy and the origin's TLS is negotiated inside it under the identical origin trust policy, so the proxy cannot read the origin's encrypted session or substitute the origin certificate. `auth` requires an `https://` proxy: the credentials ride the authenticated TLS-to-proxy channel (verified against `proxy.tls`, which is required for an https proxy), so a plaintext `http://` proxy carrying `auth` is refused (`transport/proxy-auth-requires-tls`) rather than exposing them to the proxy hop. An `http://` proxy is tunnel-only. Basic (RFC 7617) sends its credential on the first CONNECT. Digest (RFC 7616) answers the proxy's `Proxy-Authenticate` challenge on a `407`, hashing the method `CONNECT` over the authority-form target, and answers exactly once: a proxy that repeats its challenge has rejected the credential. The Digest policy is the one the origin verbs use, so MD5 and a challenge carrying no `qop` are refused unless `allowMD5` / `allowLegacyQop` says otherwise (`transport/proxy-digest-weak-algorithm`, `transport/proxy-digest-unsupported-algorithm`, `transport/proxy-digest-no-qop`, `transport/proxy-digest-bad-challenge`). A `407` is `transport/proxy-auth-failed` (credentials rejected) or `transport/proxy-auth-required` (none supplied, or none of the offered schemes is the configured one); a proxy certificate that does not verify is `transport/proxy-tls-failed`; a proxy chain carrying a key listed in `proxy.tls.distrustedKeys` or the origin's `tls.distrustedKeys` is `transport/proxy-key-distrusted`; a non-2xx CONNECT is `transport/proxy-connect-failed`; a malformed option is `transport/bad-proxy`. Off by default (a direct connection).
|
|
630
864
|
* @example
|
|
631
865
|
* var t = pki.transport.https({ tls: { anchors: [caPem] } });
|
|
632
866
|
* var res = await t({ method: "GET", url: "https://ca.example/.well-known/est/cacerts" });
|
|
@@ -645,6 +879,12 @@ function httpsTransport(defaults) {
|
|
|
645
879
|
* the settings read from this record is the opt-in that turns away an unpinned server. */
|
|
646
880
|
var rawDefaultTls = defaults.tls;
|
|
647
881
|
var tlsDefaults = rawDefaultTls != null ? rawDefaultTls : intrinsic.create(null);
|
|
882
|
+
if (rawDefaultTls != null) {
|
|
883
|
+
guard.identifier.assertKnownKeys(tlsDefaults, TLS_KEYS, E, C("bad-input"), "the transport default tls has an unknown option ");
|
|
884
|
+
}
|
|
885
|
+
/** @internal The default distrust list is read and copied once, here, so editing the caller's
|
|
886
|
+
* array after the transport is built changes nothing. */
|
|
887
|
+
var defaultDistrust = guard.distrust.keys(tlsDefaults.distrustedKeys, E, C("bad-input"), "the transport default tls.distrustedKeys");
|
|
648
888
|
|
|
649
889
|
function _prepare(request) {
|
|
650
890
|
var url;
|
|
@@ -674,6 +914,7 @@ function httpsTransport(defaults) {
|
|
|
674
914
|
* public verb instead of this module's typed refusal. */
|
|
675
915
|
var rawReqTls = request.tls;
|
|
676
916
|
var reqTls = rawReqTls != null ? rawReqTls : intrinsic.create(null);
|
|
917
|
+
if (rawReqTls != null) guard.identifier.assertKnownKeys(reqTls, TLS_KEYS, E, C("bad-input"), "tls has an unknown option ");
|
|
677
918
|
var rawAnchors = reqTls.anchors;
|
|
678
919
|
var anchors = rawAnchors !== undefined ? rawAnchors : tlsDefaults.anchors;
|
|
679
920
|
var rawUseSystem = reqTls.useSystemStore;
|
|
@@ -686,6 +927,20 @@ function httpsTransport(defaults) {
|
|
|
686
927
|
if (scheme === "https:" && !hasAnchors && !useSystem) {
|
|
687
928
|
throw E(C("no-trust-anchors"), "no explicit trust anchor and useSystemStore not set -- refusing an unpinned server (RFC 7030 sec. 3.6)");
|
|
688
929
|
}
|
|
930
|
+
/** @internal The platform store is read here so an empty one is refused before any socket
|
|
931
|
+
* opens. A connection given an empty `ca` list falls back to Node's default store, which is
|
|
932
|
+
* Node's compiled-in set plus NODE_EXTRA_CA_CERTS, and that is not the store the caller asked
|
|
933
|
+
* for. */
|
|
934
|
+
var systemCa = useSystem ? _systemCa() : [];
|
|
935
|
+
if (scheme === "https:" && !hasAnchors && systemCa.length === 0) {
|
|
936
|
+
throw E(C("no-trust-anchors"), "useSystemStore is set and the platform certificate store returned no certificates, " +
|
|
937
|
+
"so the server cannot be authenticated; set tls.anchors, or install the host's CA bundle");
|
|
938
|
+
}
|
|
939
|
+
/** @internal The transport's list and the request's are combined, so a request cannot drop a
|
|
940
|
+
* key the transport was built to refuse by naming a shorter list. */
|
|
941
|
+
var rawDistrust = reqTls.distrustedKeys;
|
|
942
|
+
var distrust = guard.distrust.union(defaultDistrust,
|
|
943
|
+
guard.distrust.keys(rawDistrust, E, C("bad-input"), "tls.distrustedKeys"));
|
|
689
944
|
|
|
690
945
|
var rawTimeout = request.timeout;
|
|
691
946
|
var timeout = _budget(rawTimeout !== undefined ? rawTimeout : defaults.timeout, "timeout", DEFAULT_TIMEOUT, MAX_TIMEOUT, E, C("bad-input"));
|
|
@@ -711,10 +966,14 @@ function httpsTransport(defaults) {
|
|
|
711
966
|
var host = _stripIpv6Brackets(_urlHostname(url));
|
|
712
967
|
var plaintext = scheme === "http:";
|
|
713
968
|
var statedPort = _urlPort(url);
|
|
969
|
+
/** @internal `defaultPort` is what node compares the port with when it writes the Host header.
|
|
970
|
+
* With no agent there is no default to compare with, so node appends the port every time and a
|
|
971
|
+
* URL with no port is sent as `Host: name:80`. Some servers answer that with an error. */
|
|
714
972
|
var options = {
|
|
715
973
|
method: request.method || "GET",
|
|
716
974
|
hostname: host,
|
|
717
975
|
port: statedPort || (plaintext ? 80 : 443),
|
|
976
|
+
defaultPort: plaintext ? 80 : 443,
|
|
718
977
|
path: _urlPathname(url) + _urlSearch(url),
|
|
719
978
|
headers: reqHeaders,
|
|
720
979
|
minVersion: minVersion,
|
|
@@ -747,16 +1006,17 @@ function httpsTransport(defaults) {
|
|
|
747
1006
|
if (sni && !_isIP(sni)) options.servername = sni;
|
|
748
1007
|
var caList = [];
|
|
749
1008
|
if (hasAnchors) caList = _concatList(caList, _pemifyAnchors(anchors));
|
|
750
|
-
if (useSystem) caList = _concatList(caList,
|
|
1009
|
+
if (useSystem) caList = _concatList(caList, systemCa);
|
|
751
1010
|
if (caList.length) options.ca = caList;
|
|
752
1011
|
if (cert) options.cert = cert;
|
|
753
1012
|
if (key) options.key = key;
|
|
1013
|
+
var anchorKeys = _hopAnchorKeys(distrust, hasAnchors ? _pemifyAnchors(anchors) : [], useSystem);
|
|
754
1014
|
if (callerCsi) {
|
|
755
|
-
options.checkServerIdentity = function (host, cert2) {
|
|
1015
|
+
options.checkServerIdentity = _withDistrust(function (host, cert2) {
|
|
756
1016
|
var baseErr = _checkServerIdentity(host, cert2);
|
|
757
1017
|
if (baseErr) return baseErr;
|
|
758
1018
|
return callerCsi(host, cert2);
|
|
759
|
-
};
|
|
1019
|
+
}, distrust, "server", anchorKeys);
|
|
760
1020
|
} else {
|
|
761
1021
|
/** @internal With no caller hook, the option is set to the CAPTURE rather than left out. Left
|
|
762
1022
|
* out, node's own TLS stack looks the function up on its module export when the handshake
|
|
@@ -764,15 +1024,15 @@ function httpsTransport(defaults) {
|
|
|
764
1024
|
* another host is accepted. Measured: with this absent, a request asking for a name the
|
|
765
1025
|
* server's certificate does not carry was admitted. The function is node's own default, taken
|
|
766
1026
|
* at load, so the verdict is the one it would have reached anyway. */
|
|
767
|
-
options.checkServerIdentity = _checkServerIdentity;
|
|
1027
|
+
options.checkServerIdentity = _withDistrust(_checkServerIdentity, distrust, "server", anchorKeys);
|
|
768
1028
|
}
|
|
769
1029
|
var proxyInfo;
|
|
770
|
-
try { proxyInfo = _validateProxy(request, defaults, E, C, host, statedPort, scheme); }
|
|
1030
|
+
try { proxyInfo = _validateProxy(request, defaults, E, C, host, statedPort, scheme, distrust); }
|
|
771
1031
|
catch (e) {
|
|
772
1032
|
var typedProxyError = false;
|
|
773
1033
|
try {
|
|
774
1034
|
var ec = (e instanceof Error) && !nodeUtil.types.isProxy(e) ? e.code : undefined;
|
|
775
|
-
typedProxyError = ec === C("bad-proxy") || ec === C("proxy-auth-requires-tls");
|
|
1035
|
+
typedProxyError = ec === C("bad-proxy") || ec === C("proxy-auth-requires-tls") || ec === C("no-trust-anchors");
|
|
776
1036
|
} catch (_ie) { /* allow:swallow-unverified reading .code on a hostile error object can throw; treat any such failure as untyped */ }
|
|
777
1037
|
if (typedProxyError) throw e;
|
|
778
1038
|
throw E(C("bad-proxy"), "opts.proxy could not be validated", e);
|
|
@@ -785,7 +1045,7 @@ function httpsTransport(defaults) {
|
|
|
785
1045
|
options.lookup = _guardedLookup;
|
|
786
1046
|
}
|
|
787
1047
|
|
|
788
|
-
return { options: options, timeout: timeout, maxBytes: maxBytes, body: body, proxy: proxyInfo, blockPrivate: blockPrivate, callerCsi: callerCsi, plaintext: plaintext };
|
|
1048
|
+
return { options: options, timeout: timeout, maxBytes: maxBytes, body: body, proxy: proxyInfo, blockPrivate: blockPrivate, callerCsi: callerCsi, plaintext: plaintext, distrust: distrust, anchorKeys: anchorKeys };
|
|
789
1049
|
}
|
|
790
1050
|
|
|
791
1051
|
var _transportFn = function transport(request) {
|
|
@@ -833,7 +1093,7 @@ function httpsTransport(defaults) {
|
|
|
833
1093
|
* options are the ones node computed for this hop, forwarded unchanged, so every TLS
|
|
834
1094
|
* setting the request carries still applies; only the function that opens the socket is
|
|
835
1095
|
* this module's. The tunnel path already supplies its own and keeps it. */
|
|
836
|
-
if (options.createConnection
|
|
1096
|
+
if (options.createConnection == null) {
|
|
837
1097
|
options.createConnection = prep.plaintext
|
|
838
1098
|
? function (opts) { return intrinsic.apply(_netConnect, nodeNet, [opts]); }
|
|
839
1099
|
: function (opts) { return intrinsic.apply(_tlsConnect, nodeTls, [opts]); };
|
|
@@ -949,7 +1209,8 @@ function httpsTransport(defaults) {
|
|
|
949
1209
|
connectOpts.minVersion = pinfo.proxyTls.minVersion;
|
|
950
1210
|
var pIdentity = pinfo.proxyTls.servername || pinfo.host;
|
|
951
1211
|
connectOpts.servername = (pIdentity && !_isIP(pIdentity)) ? pIdentity : "";
|
|
952
|
-
connectOpts.checkServerIdentity = function (host, cert) { return _checkServerIdentity(pIdentity, cert); }
|
|
1212
|
+
connectOpts.checkServerIdentity = _withDistrust(function (host, cert) { return _checkServerIdentity(pIdentity, cert); },
|
|
1213
|
+
pinfo.proxyTls.distrust, "proxy", pinfo.proxyTls.anchorKeys);
|
|
953
1214
|
connectOpts.createConnection = function (opts) { return intrinsic.apply(_tlsConnect, nodeTls, [opts]); };
|
|
954
1215
|
cr = intrinsic.apply(_httpsRequest, nodeHttps, [connectOpts]);
|
|
955
1216
|
} else {
|
|
@@ -997,11 +1258,11 @@ function httpsTransport(defaults) {
|
|
|
997
1258
|
ca: prep.options.ca,
|
|
998
1259
|
minVersion: prep.options.minVersion,
|
|
999
1260
|
rejectUnauthorized: true,
|
|
1000
|
-
checkServerIdentity: function (host, cert) {
|
|
1261
|
+
checkServerIdentity: _withDistrust(function (host, cert) {
|
|
1001
1262
|
var baseErr = _checkServerIdentity(idHost, cert);
|
|
1002
1263
|
if (baseErr) return baseErr;
|
|
1003
1264
|
return prep.callerCsi ? prep.callerCsi(idHost, cert) : undefined;
|
|
1004
|
-
},
|
|
1265
|
+
}, prep.distrust, "server", prep.anchorKeys),
|
|
1005
1266
|
cert: prep.options.cert,
|
|
1006
1267
|
key: prep.options.key,
|
|
1007
1268
|
}]);
|
|
@@ -1060,12 +1321,12 @@ function httpsTransport(defaults) {
|
|
|
1060
1321
|
host: o.hostname, port: o.port, minVersion: o.minVersion,
|
|
1061
1322
|
rejectUnauthorized: o.rejectUnauthorized,
|
|
1062
1323
|
};
|
|
1063
|
-
if (o.servername
|
|
1064
|
-
if (o.ca
|
|
1065
|
-
if (o.cert
|
|
1066
|
-
if (o.key
|
|
1067
|
-
if (o.checkServerIdentity
|
|
1068
|
-
if (o.lookup
|
|
1324
|
+
if (o.servername != null) connectOpts.servername = o.servername;
|
|
1325
|
+
if (o.ca != null) connectOpts.ca = o.ca;
|
|
1326
|
+
if (o.cert != null) connectOpts.cert = o.cert;
|
|
1327
|
+
if (o.key != null) connectOpts.key = o.key;
|
|
1328
|
+
if (o.checkServerIdentity != null) connectOpts.checkServerIdentity = o.checkServerIdentity;
|
|
1329
|
+
if (o.lookup != null) connectOpts.lookup = o.lookup;
|
|
1069
1330
|
|
|
1070
1331
|
return new _Promise(function (resolve, reject) {
|
|
1071
1332
|
var settled = false;
|
|
@@ -1134,7 +1395,8 @@ function httpsTransport(defaults) {
|
|
|
1134
1395
|
* one. A request with neither an explicit anchor nor `useSystemStore` is refused
|
|
1135
1396
|
* (`transport/no-trust-anchors`) before any socket is opened, and an endpoint whose chain does
|
|
1136
1397
|
* not build to the configured anchors, or whose name does not match the URL, is refused
|
|
1137
|
-
* (`transport/server-auth-failed`). A
|
|
1398
|
+
* (`transport/server-auth-failed`). A chain carrying a key listed in `tls.distrustedKeys` is
|
|
1399
|
+
* refused (`transport/server-key-distrusted`). A configured proxy is refused (`transport/bad-proxy`)
|
|
1138
1400
|
* rather than bypassed, since connecting directly would reach an origin the caller asked to
|
|
1139
1401
|
* reach through the proxy.
|
|
1140
1402
|
*
|
|
@@ -1151,4 +1413,5 @@ function httpsTransport(defaults) {
|
|
|
1151
1413
|
*/
|
|
1152
1414
|
function peerChain(request, defaults) { return httpsTransport(defaults || intrinsic.create(null)).peerChain(request); }
|
|
1153
1415
|
|
|
1154
|
-
module.exports = { https: httpsTransport, peerChain: peerChain, isBlockedIp: _isBlockedIp, _makeGuardedLookup: _makeGuardedLookup, MAX_TIMEOUT: MAX_TIMEOUT, snapshotProxy: snapshotProxy
|
|
1416
|
+
module.exports = { https: httpsTransport, peerChain: peerChain, isBlockedIp: _isBlockedIp, _makeGuardedLookup: _makeGuardedLookup, MAX_TIMEOUT: MAX_TIMEOUT, snapshotProxy: snapshotProxy,
|
|
1417
|
+
TLS_KEYS: intrinsic.freeze(TLS_KEYS) };
|
package/lib/jose.js
CHANGED
|
@@ -373,7 +373,7 @@ async function verify(jws, opts) {
|
|
|
373
373
|
* `snapshotDeep` refuses an accessor-backed member rather than reading it once, which is the stronger
|
|
374
374
|
* answer for a value that selects an algorithm. The embedded jwk comes from the parsed header and is
|
|
375
375
|
* this module's own object, so it needs no copy. */
|
|
376
|
-
var suppliedKey = opts.key
|
|
376
|
+
var suppliedKey = opts.key == null ? undefined
|
|
377
377
|
: guard.bytes.snapshotDeep(opts.key, JoseError, "jose/bad-key", "opts.key");
|
|
378
378
|
var jwk = header.jwk || suppliedKey;
|
|
379
379
|
if (!jwk) throw E("jose/bad-key", "a verification key is required (opts.key) when the profile does not embed a jwk");
|