@blamejs/pki 0.8.49 → 0.8.51
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 +27 -0
- package/README.md +3 -3
- package/lib/acme.js +53 -27
- package/lib/cmp-build.js +19 -8
- package/lib/constants.js +3 -2
- package/lib/ct.js +190 -43
- package/lib/est.js +42 -19
- package/lib/guard-bytes.js +17 -2
- package/lib/guard-identifier.js +22 -0
- package/lib/guard-intrinsic.js +55 -0
- package/lib/http-digest.js +8 -1
- package/lib/http-transport.js +48 -20
- package/lib/inspect.js +3 -2
- package/lib/lint.js +105 -2
- package/lib/ocsp.js +3 -1
- package/lib/path-validate.js +93 -9
- package/lib/pki-build.js +40 -0
- package/lib/revocation-fetch.js +14 -5
- package/lib/scep.js +36 -15
- package/lib/x509-sign.js +22 -2
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/guard-intrinsic.js
CHANGED
|
@@ -68,6 +68,32 @@ var _promiseAny = Promise.any;
|
|
|
68
68
|
* present on the supported runtime floor. */
|
|
69
69
|
var _promiseTry = Promise["try"];
|
|
70
70
|
var _promiseWithResolvers = Promise.withResolvers;
|
|
71
|
+
/** @internal The URL parser, captured for the reason the promise constructor is: what it returns names
|
|
72
|
+
* the host a request is sent to. A replacement installed while a call is in flight answers with a
|
|
73
|
+
* genuine URL of its own, and an endpoint built from that answer reaches a host the caller never
|
|
74
|
+
* wrote while the url the caller passed is still the one the call reports. `canParse` is captured
|
|
75
|
+
* beside it because a module that decides a form with it is deciding from the same binding. */
|
|
76
|
+
var _URL = URL;
|
|
77
|
+
var _urlCanParse = URL.canParse;
|
|
78
|
+
/** @internal And its accessors, because capturing a constructor does not capture the prototype the
|
|
79
|
+
* value it returns reads through. MEASURED: with the constructor captured, a header getter that
|
|
80
|
+
* redefined `URL.prototype.href` had every request go to the host its own getter named while the url
|
|
81
|
+
* the caller passed was still the one the call reported. The parts of a destination are read through
|
|
82
|
+
* these, and the two that build an endpoint are written through the captured setters. */
|
|
83
|
+
var _urlHref = getter(URL.prototype, "href", "URL.prototype");
|
|
84
|
+
var _urlOrigin = getter(URL.prototype, "origin", "URL.prototype");
|
|
85
|
+
var _urlProtocol = getter(URL.prototype, "protocol", "URL.prototype");
|
|
86
|
+
var _urlPathname = getter(URL.prototype, "pathname", "URL.prototype");
|
|
87
|
+
var _urlSearch = getter(URL.prototype, "search", "URL.prototype");
|
|
88
|
+
var _urlHash = getter(URL.prototype, "hash", "URL.prototype");
|
|
89
|
+
var _urlHostname = getter(URL.prototype, "hostname", "URL.prototype");
|
|
90
|
+
var _urlPort = getter(URL.prototype, "port", "URL.prototype");
|
|
91
|
+
var _urlUsername = getter(URL.prototype, "username", "URL.prototype");
|
|
92
|
+
var _urlPassword = getter(URL.prototype, "password", "URL.prototype");
|
|
93
|
+
var _urlSearchParams = getter(URL.prototype, "searchParams", "URL.prototype");
|
|
94
|
+
var _urlSetPathname = setter(URL.prototype, "pathname", "URL.prototype");
|
|
95
|
+
var _urlSetSearch = setter(URL.prototype, "search", "URL.prototype");
|
|
96
|
+
var _urlSetHash = setter(URL.prototype, "hash", "URL.prototype");
|
|
71
97
|
var _asyncIterator = Symbol.asyncIterator;
|
|
72
98
|
|
|
73
99
|
var _arrForEach = uncurry(Array.prototype.forEach);
|
|
@@ -160,6 +186,18 @@ function getter(proto, name, who) {
|
|
|
160
186
|
return uncurry(d.get);
|
|
161
187
|
}
|
|
162
188
|
|
|
189
|
+
/** @internal The write half of the same question: a replaced setter can drop the write and leave the
|
|
190
|
+
* value it was given to change unchanged. */
|
|
191
|
+
// @enforced-by behavioral -- reading a descriptor's `set` is not distinguishable from testing one
|
|
192
|
+
function setter(proto, name, who) {
|
|
193
|
+
var d = _getOwnPropertyDescriptor(proto, name);
|
|
194
|
+
if (!d || typeof d.set !== "function") {
|
|
195
|
+
throw new TypeError("guard.intrinsic.setter: this runtime has no intrinsic " + who + "." +
|
|
196
|
+
name + " accessor, so the value's own would have to be invoked instead");
|
|
197
|
+
}
|
|
198
|
+
return uncurry(d.set);
|
|
199
|
+
}
|
|
200
|
+
|
|
163
201
|
var _taByteLength = getter(_getPrototypeOf(Uint8Array.prototype), "byteLength", "%TypedArray%.prototype");
|
|
164
202
|
var _dvByteLength = getter(DataView.prototype, "byteLength", "DataView.prototype");
|
|
165
203
|
|
|
@@ -231,6 +269,23 @@ module.exports = Object.freeze({
|
|
|
231
269
|
promiseAny: _promiseAny,
|
|
232
270
|
promiseTry: _promiseTry,
|
|
233
271
|
promiseWithResolvers: _promiseWithResolvers,
|
|
272
|
+
setter: setter,
|
|
273
|
+
URL: _URL,
|
|
274
|
+
urlCanParse: _urlCanParse,
|
|
275
|
+
urlHref: _urlHref,
|
|
276
|
+
urlOrigin: _urlOrigin,
|
|
277
|
+
urlProtocol: _urlProtocol,
|
|
278
|
+
urlPathname: _urlPathname,
|
|
279
|
+
urlSearch: _urlSearch,
|
|
280
|
+
urlHash: _urlHash,
|
|
281
|
+
urlHostname: _urlHostname,
|
|
282
|
+
urlPort: _urlPort,
|
|
283
|
+
urlUsername: _urlUsername,
|
|
284
|
+
urlPassword: _urlPassword,
|
|
285
|
+
urlSearchParams: _urlSearchParams,
|
|
286
|
+
urlSetPathname: _urlSetPathname,
|
|
287
|
+
urlSetSearch: _urlSetSearch,
|
|
288
|
+
urlSetHash: _urlSetHash,
|
|
234
289
|
asyncIterator: _asyncIterator,
|
|
235
290
|
forEach: _arrForEach,
|
|
236
291
|
map: _arrMap,
|
package/lib/http-digest.js
CHANGED
|
@@ -7,6 +7,13 @@ var crypto = require("crypto");
|
|
|
7
7
|
var constants = require("./constants");
|
|
8
8
|
var intrinsic = require("./guard-intrinsic");
|
|
9
9
|
var _hasOwn = intrinsic.hasOwn;
|
|
10
|
+
/** @internal The URL parser, taken at load: the origin it reports is what a challenge is bound to.
|
|
11
|
+
* Its accessors are taken with it, since the constructor's capture does not cover the prototype the
|
|
12
|
+
* value reads its parts off and this origin is what a credential is scoped by. */
|
|
13
|
+
var _URL = intrinsic.URL;
|
|
14
|
+
var _urlOrigin = intrinsic.urlOrigin;
|
|
15
|
+
var _urlPathname = intrinsic.urlPathname;
|
|
16
|
+
var _urlSearch = intrinsic.urlSearch;
|
|
10
17
|
var _charCodeAt = intrinsic.uncurry(String.prototype.charCodeAt);
|
|
11
18
|
var _strSlice = intrinsic.uncurry(String.prototype.slice);
|
|
12
19
|
var _fromCharCode = intrinsic.fromCharCode;
|
|
@@ -293,7 +300,7 @@ function answer(challenge, params, E) {
|
|
|
293
300
|
function _domainEntry(d) {
|
|
294
301
|
d = String(d == null ? "" : d);
|
|
295
302
|
if (d.charAt(0) === "/") return { origin: null, full: d };
|
|
296
|
-
try { var u = new
|
|
303
|
+
try { var u = new _URL(d); return { origin: _urlOrigin(u).toLowerCase(), full: (_urlPathname(u) || "/") + (_urlSearch(u) || "") }; }
|
|
297
304
|
catch (_e) { return null; }
|
|
298
305
|
}
|
|
299
306
|
|
package/lib/http-transport.js
CHANGED
|
@@ -79,6 +79,21 @@ var intrinsic = require("./guard-intrinsic");
|
|
|
79
79
|
* step rejected proceed, and `peerChain`'s refusal to tunnel through a proxy is one of those. Taken at
|
|
80
80
|
* load. */
|
|
81
81
|
var _Promise = intrinsic.Promise;
|
|
82
|
+
/** @internal The URL parser, taken at load: what it returns names the host the socket is opened to,
|
|
83
|
+
* for the request itself and for a proxy it is tunneled through. The conversion it is handed is taken
|
|
84
|
+
* with it, since a replacement answers with a host of its own. */
|
|
85
|
+
var _URL = intrinsic.URL;
|
|
86
|
+
var _stringOf = intrinsic.String;
|
|
87
|
+
/** @internal The parts of that destination are read through the accessors captured at load, not off the
|
|
88
|
+
* prototype the parsed value reads through: a replaced `URL.prototype.hostname` names the host this
|
|
89
|
+
* module then opens a socket to. */
|
|
90
|
+
var _urlProtocol = intrinsic.urlProtocol;
|
|
91
|
+
var _urlHostname = intrinsic.urlHostname;
|
|
92
|
+
var _urlPort = intrinsic.urlPort;
|
|
93
|
+
var _urlPathname = intrinsic.urlPathname;
|
|
94
|
+
var _urlSearch = intrinsic.urlSearch;
|
|
95
|
+
var _urlUsername = intrinsic.urlUsername;
|
|
96
|
+
var _urlPassword = intrinsic.urlPassword;
|
|
82
97
|
var _promiseResolve = intrinsic.uncurry(intrinsic.promiseResolve);
|
|
83
98
|
var _promiseReject = intrinsic.uncurry(intrinsic.promiseReject);
|
|
84
99
|
var frameworkError = require("./framework-error");
|
|
@@ -331,7 +346,10 @@ function snapshotProxy(p) {
|
|
|
331
346
|
}
|
|
332
347
|
}
|
|
333
348
|
|
|
334
|
-
|
|
349
|
+
/** @internal `originHost`, `statedPort` and `scheme` are the values the request path already read off
|
|
350
|
+
* the parsed origin; they are passed in rather than read again here, so the authority this builds for
|
|
351
|
+
* the CONNECT target is the same authority the socket options were built from. */
|
|
352
|
+
function _validateProxy(request, defaults, E, C, originHost, originStatedPort, originScheme) {
|
|
335
353
|
var proxy = request.proxy !== undefined ? request.proxy : defaults.proxy;
|
|
336
354
|
if (proxy === undefined || proxy === null) return null;
|
|
337
355
|
guard.identifier.assertPlainRecord(proxy, E, C("bad-proxy"), "opts.proxy");
|
|
@@ -339,11 +357,12 @@ function _validateProxy(request, defaults, E, C, url) {
|
|
|
339
357
|
guard.identifier.assertKnownKeys(proxy, PROXY_KEYS, E, C("bad-proxy"), "opts.proxy has an unknown option ");
|
|
340
358
|
if (typeof proxy.url !== "string") throw E(C("bad-proxy"), "opts.proxy.url must be a string, got " + guard.text.showValue(proxy.url));
|
|
341
359
|
var purl;
|
|
342
|
-
try { purl = new
|
|
360
|
+
try { purl = new _URL(proxy.url); }
|
|
343
361
|
catch (e) { throw E(C("bad-proxy"), "opts.proxy.url did not parse: " + guard.text.showValue(proxy.url), e); }
|
|
344
|
-
|
|
345
|
-
if (
|
|
346
|
-
|
|
362
|
+
var pscheme = _urlProtocol(purl);
|
|
363
|
+
if (pscheme !== "http:" && pscheme !== "https:") throw E(C("bad-proxy"), "opts.proxy.url must be an http or https URL, got " + pscheme);
|
|
364
|
+
if (_urlUsername(purl) || _urlPassword(purl)) throw E(C("bad-proxy"), "opts.proxy.url must not carry credentials in its userinfo; supply opts.proxy.auth over an https proxy");
|
|
365
|
+
var secure = pscheme === "https:";
|
|
347
366
|
if (!secure && proxy.tls !== undefined && proxy.tls !== null) throw E(C("bad-proxy"), "opts.proxy.tls applies only to an https proxy; an http proxy connection is plaintext (a supplied tls policy would be silently ignored)");
|
|
348
367
|
|
|
349
368
|
var auth = null;
|
|
@@ -396,14 +415,14 @@ function _validateProxy(request, defaults, E, C, url) {
|
|
|
396
415
|
proxyTls = { ca: pCa.length ? pCa : undefined, minVersion: pMinVersion, servername: (ptls && ptls.servername) || null };
|
|
397
416
|
}
|
|
398
417
|
|
|
399
|
-
var originHost = _stripIpv6Brackets(url.hostname);
|
|
400
418
|
var originAuthHost = nodeNet.isIP(originHost) === 6 ? "[" + originHost + "]" : originHost;
|
|
401
|
-
var phost = _stripIpv6Brackets(purl
|
|
419
|
+
var phost = _stripIpv6Brackets(_urlHostname(purl));
|
|
402
420
|
/** @internal The CONNECT target is the origin's own authority, so an origin URL that states no
|
|
403
421
|
* port takes the default for the origin's scheme, not the proxy's. Defaulting it to 443 opens
|
|
404
422
|
* the tunnel to a port an `http:` origin does not listen on. */
|
|
405
|
-
var originPort =
|
|
406
|
-
|
|
423
|
+
var originPort = originStatedPort || (originScheme === "http:" ? 80 : 443);
|
|
424
|
+
var pport = _urlPort(purl);
|
|
425
|
+
return { secure: secure, host: phost, port: pport ? +pport : (secure ? 443 : 80), originHost: originHost, originAuthority: originAuthHost + ":" + originPort, auth: auth, proxyTls: proxyTls };
|
|
407
426
|
}
|
|
408
427
|
|
|
409
428
|
function _basicProxyHeader(auth) {
|
|
@@ -484,11 +503,14 @@ function httpsTransport(defaults) {
|
|
|
484
503
|
var E = typeof defaults.E === "function" ? defaults.E : defaultE;
|
|
485
504
|
var prefix = defaults.errPrefix || "transport";
|
|
486
505
|
function C(name) { return prefix + "/" + name; }
|
|
487
|
-
|
|
506
|
+
/** @internal No prototype where no TLS was bound: an empty object literal inherits from
|
|
507
|
+
* `Object.prototype`, so a value installed there would answer for a setting nobody set, and one of
|
|
508
|
+
* the settings read from this record is the opt-in that turns away an unpinned server. */
|
|
509
|
+
var tlsDefaults = defaults.tls != null ? defaults.tls : intrinsic.create(null);
|
|
488
510
|
|
|
489
511
|
function _prepare(request) {
|
|
490
512
|
var url;
|
|
491
|
-
try { url = new
|
|
513
|
+
try { url = new _URL(_stringOf(request.url)); }
|
|
492
514
|
catch (e) { throw E(C("bad-url"), "the request URL did not parse: " + String(request.url), e); }
|
|
493
515
|
/** @internal https is the floor, and `request.allowPlaintextHttp` is the one thing that moves
|
|
494
516
|
* it, per request and never as a default. It exists for the two objects whose integrity does
|
|
@@ -499,11 +521,16 @@ function httpsTransport(defaults) {
|
|
|
499
521
|
* timeout, and the refusal to follow a redirect, which is what would otherwise turn one
|
|
500
522
|
* plaintext hop into a downgrade of the next. */
|
|
501
523
|
var plaintextAllowed = request.allowPlaintextHttp === true;
|
|
502
|
-
|
|
503
|
-
|
|
524
|
+
/** @internal Read ONCE, through the captured accessor, and every decision below made from that
|
|
525
|
+
* string. The scheme decides whether the request is allowed at all, whether an anchor is
|
|
526
|
+
* required and which port it defaults to, so a prototype whose getter answers each read
|
|
527
|
+
* differently would have those three agree on nothing. */
|
|
528
|
+
var scheme = _urlProtocol(url);
|
|
529
|
+
if (scheme !== "https:" && !(plaintextAllowed && scheme === "http:")) {
|
|
530
|
+
throw E(C("insecure-url"), "transport requires https (RFC 7030 sec. 3.3), got " + scheme);
|
|
504
531
|
}
|
|
505
532
|
|
|
506
|
-
var reqTls = request.tls
|
|
533
|
+
var reqTls = request.tls != null ? request.tls : intrinsic.create(null);
|
|
507
534
|
var anchors = reqTls.anchors !== undefined ? reqTls.anchors : tlsDefaults.anchors;
|
|
508
535
|
var useSystem = (reqTls.useSystemStore !== undefined ? reqTls.useSystemStore : tlsDefaults.useSystemStore) === true;
|
|
509
536
|
var hasAnchors = anchors !== undefined && anchors !== null && !(Array.isArray(anchors) && anchors.length === 0);
|
|
@@ -511,7 +538,7 @@ function httpsTransport(defaults) {
|
|
|
511
538
|
* neither an anchor nor the system store is refused rather than left unpinned. A plaintext hop
|
|
512
539
|
* negotiates no certificate, so there is nothing for an anchor to pin; what protects the object
|
|
513
540
|
* fetched over one is its own signature, checked after the fetch by the verb that asked. */
|
|
514
|
-
if (
|
|
541
|
+
if (scheme === "https:" && !hasAnchors && !useSystem) {
|
|
515
542
|
throw E(C("no-trust-anchors"), "no explicit trust anchor and useSystemStore not set -- refusing an unpinned server (RFC 7030 sec. 3.6)");
|
|
516
543
|
}
|
|
517
544
|
|
|
@@ -534,13 +561,14 @@ function httpsTransport(defaults) {
|
|
|
534
561
|
reqHeaders["Content-Length"] = Buffer.byteLength(body);
|
|
535
562
|
}
|
|
536
563
|
|
|
537
|
-
var host = _stripIpv6Brackets(url
|
|
538
|
-
var plaintext =
|
|
564
|
+
var host = _stripIpv6Brackets(_urlHostname(url));
|
|
565
|
+
var plaintext = scheme === "http:";
|
|
566
|
+
var statedPort = _urlPort(url);
|
|
539
567
|
var options = {
|
|
540
568
|
method: request.method || "GET",
|
|
541
569
|
hostname: host,
|
|
542
|
-
port:
|
|
543
|
-
path: url
|
|
570
|
+
port: statedPort || (plaintext ? 80 : 443),
|
|
571
|
+
path: _urlPathname(url) + _urlSearch(url),
|
|
544
572
|
headers: reqHeaders,
|
|
545
573
|
minVersion: minVersion,
|
|
546
574
|
rejectUnauthorized: true,
|
|
@@ -566,7 +594,7 @@ function httpsTransport(defaults) {
|
|
|
566
594
|
};
|
|
567
595
|
}
|
|
568
596
|
var proxyInfo;
|
|
569
|
-
try { proxyInfo = _validateProxy(request, defaults, E, C,
|
|
597
|
+
try { proxyInfo = _validateProxy(request, defaults, E, C, host, statedPort, scheme); }
|
|
570
598
|
catch (e) {
|
|
571
599
|
var typedProxyError = false;
|
|
572
600
|
try {
|
package/lib/inspect.js
CHANGED
|
@@ -449,8 +449,9 @@ var EXT_RENDERERS = Object.assign(Object.create(null), {
|
|
|
449
449
|
}
|
|
450
450
|
return akiLines.length ? akiLines.join("\n") : inner + "keyid:(none)";
|
|
451
451
|
},
|
|
452
|
-
/** @internal RFC 7633 sec.
|
|
453
|
-
* where the toolkit knows it and printed as its number where it does not.
|
|
452
|
+
/** @internal RFC 7633 sec. 4.1 binds these values to the IANA TLS Extensions registry, so each is
|
|
453
|
+
* named from the registry where the toolkit knows it and printed as its number where it does not.
|
|
454
|
+
* sec. 6 is IANA Considerations and registers the extension's own OID; it carries no value. */
|
|
454
455
|
tlsFeature: function (decoded, inner) {
|
|
455
456
|
return (decoded.features || []).map(function (f) {
|
|
456
457
|
var text = String(f);
|
package/lib/lint.js
CHANGED
|
@@ -303,9 +303,16 @@ var OID_CPS = oid.byName("cps");
|
|
|
303
303
|
/** @internal The extensions RFC 5280 says a conforming CA SHOULD mark non-critical, keyed by OID
|
|
304
304
|
* with the clause; the MUSTs live in the shared CERT_FIXED_CRITICALITY table. The subjectAltName
|
|
305
305
|
* and extKeyUsage recommendations are conditional and have their own rules. */
|
|
306
|
+
/** @internal The per-extension citation travels with the finding, so a row here may come from a
|
|
307
|
+
* document other than RFC 5280: RFC 7633 sec. 4 states the same shape of recommendation for the TLS
|
|
308
|
+
* feature extension, and its clause closes by naming the case where critical is what the caller
|
|
309
|
+
* wants, so this grades it rather than refusing it. Adding a row here grades the extension in the
|
|
310
|
+
* DEFAULT set, which a named profile would not; it does not feed `_isUnknownExtension`, so the
|
|
311
|
+
* unknown-critical verdict a critical one draws is unaffected. */
|
|
306
312
|
var CERT_RECOMMENDED_NON_CRITICAL = Object.create(null);
|
|
307
313
|
CERT_RECOMMENDED_NON_CRITICAL[oid.byName("issuerAltName")] = "RFC 5280 sec. 4.2.1.7";
|
|
308
314
|
CERT_RECOMMENDED_NON_CRITICAL[oid.byName("cRLDistributionPoints")] = "RFC 5280 sec. 4.2.1.13";
|
|
315
|
+
CERT_RECOMMENDED_NON_CRITICAL[oid.byName("tlsFeature")] = "RFC 7633 sec. 4";
|
|
309
316
|
var _T_BMP = asn1.TAGS.BMP_STRING, _T_VISIBLE = asn1.TAGS.VISIBLE_STRING, _T_UTF8 = asn1.TAGS.UTF8_STRING;
|
|
310
317
|
function _hasControlChar(str) {
|
|
311
318
|
for (var i = 0; i < str.length; i++) {
|
|
@@ -488,7 +495,7 @@ var RFC5280_RULES = [
|
|
|
488
495
|
_criticalityRule("keyUsage", "lint/rfc5280/key-usage-not-critical", "warn",
|
|
489
496
|
"RFC 5280 4.2.1.3", "the keyUsage extension should be marked critical"),
|
|
490
497
|
{
|
|
491
|
-
id: "lint/rfc5280/recommended-criticality", severity: "warn", source: "rfc5280", citation: "RFC 5280 4.2.1.7 / 4.2.1.13",
|
|
498
|
+
id: "lint/rfc5280/recommended-criticality", severity: "warn", source: "rfc5280", citation: "RFC 5280 4.2.1.7 / 4.2.1.13, RFC 7633 sec. 4",
|
|
492
499
|
message: "an extension a conforming CA should mark non-critical is marked critical",
|
|
493
500
|
check: function (cert) {
|
|
494
501
|
var rows = [];
|
|
@@ -4056,6 +4063,96 @@ var RFC9763_RULES = [
|
|
|
4056
4063
|
},
|
|
4057
4064
|
];
|
|
4058
4065
|
|
|
4066
|
+
/** @internal RFC 7633. The criticality recommendation lives in the always-on table above, because
|
|
4067
|
+
* sec. 4 states it for every certificate carrying the extension. These rows read the VALUE, which
|
|
4068
|
+
* RFC 7633 leaves open in three ways and another document speaks to in a fourth:
|
|
4069
|
+
*
|
|
4070
|
+
* sec. 4 writes `Features ::= SEQUENCE OF INTEGER` with no range, no ordering rule and no prohibition
|
|
4071
|
+
* on repeats, so none of those is a conformance failure and none is refused by the encoder. What
|
|
4072
|
+
* sec. 4.1 does say is that the values are "TLS extension identifiers ... as specified in the IANA
|
|
4073
|
+
* Transport Layer Security (TLS) Extensions registry", and that registry is 16 bits wide, so a value
|
|
4074
|
+
* outside 0..65535 names nothing in it. That is a `warn`, not an `error`: the finding rests on a
|
|
4075
|
+
* reference to a registry rather than on a syntax rule, and at `error` in a named profile it would
|
|
4076
|
+
* make `pki.x509.sign` refuse to emit the certificate.
|
|
4077
|
+
*
|
|
4078
|
+
* The transparency_info row is RFC 9162 sec. 7.2, read directly: "A certification authority SHOULD
|
|
4079
|
+
* NOT issue any certificate that identifies the transparency_info TLS extension in a TLS feature
|
|
4080
|
+
* extension [RFC7633], because TLS servers are not required to support the transparency_info TLS
|
|
4081
|
+
* extension in order to participate in CT". That is the only clause outside RFC 7633 that constrains
|
|
4082
|
+
* this field.
|
|
4083
|
+
*
|
|
4084
|
+
* There is NO deprecation row for status_request_v2, and the absence is deliberate. The IANA registry
|
|
4085
|
+
* still lists `17 status_request_v2 [RFC 6961]` as Recommended and marks it neither obsolete nor
|
|
4086
|
+
* deprecated; RFC 9846 contains no occurrence of "status_request_v2" and none of "7633", so no
|
|
4087
|
+
* document states a deprecation to cite. What is true is narrower: RFC 9846 obsoletes RFC 6961, which
|
|
4088
|
+
* defined the extension, and the ExtensionType enum at its sec. 4.3 does not list 17 at all, so a
|
|
4089
|
+
* certificate naming feature 17 asks a TLS 1.3 server for something that version does not define
|
|
4090
|
+
* while a TLS 1.2 deployment can still satisfy it. That is reported at `notice`, with the clause that
|
|
4091
|
+
* supports it and nothing more. */
|
|
4092
|
+
var RFC7633_RULES = [
|
|
4093
|
+
{
|
|
4094
|
+
id: "lint/rfc7633/feature-outside-registry-width", severity: "warn", source: "rfc7633", citation: "RFC 7633 4.1",
|
|
4095
|
+
message: "a TLS feature value lies outside the 16-bit width of the IANA TLS ExtensionType registry sec. 4.1 points at, so it names no extension in it",
|
|
4096
|
+
appliesTo: function (cert, ctx) { return !!ctx.raw("tlsFeature"); },
|
|
4097
|
+
check: function (cert, ctx) {
|
|
4098
|
+
var d = ctx.decode("tlsFeature");
|
|
4099
|
+
var features = (d && d.value && d.value.features) || [];
|
|
4100
|
+
var rows = [];
|
|
4101
|
+
for (var i = 0; i < features.length; i++) {
|
|
4102
|
+
var f = features[i];
|
|
4103
|
+
if (f < 0n || f > 65535n) rows.push({ context: { feature: String(f), width: "0..65535" } });
|
|
4104
|
+
}
|
|
4105
|
+
return rows.length ? rows : null;
|
|
4106
|
+
},
|
|
4107
|
+
},
|
|
4108
|
+
{
|
|
4109
|
+
id: "lint/rfc7633/feature-repeated", severity: "notice", source: "rfc7633", citation: "RFC 7633 4",
|
|
4110
|
+
message: "a TLS feature value appears more than once; the syntax permits it and the set it names is the same, so the repeat carries no meaning",
|
|
4111
|
+
appliesTo: function (cert, ctx) { return !!ctx.raw("tlsFeature"); },
|
|
4112
|
+
check: function (cert, ctx) {
|
|
4113
|
+
var d = ctx.decode("tlsFeature");
|
|
4114
|
+
var features = (d && d.value && d.value.features) || [];
|
|
4115
|
+
/** @internal One pass with a set of what has been seen, not a rescan of every preceding value.
|
|
4116
|
+
* This rule is in the default set and a certificate reaching it is unauthenticated, so the
|
|
4117
|
+
* pairwise form is work the input chooses: MEASURED, 20000 distinct features in an 80 KiB
|
|
4118
|
+
* certificate took 355 ms against 5 ms for 50 of them. A set compares the bigint values. */
|
|
4119
|
+
var seen = new Set();
|
|
4120
|
+
var rows = [];
|
|
4121
|
+
for (var i = 0; i < features.length; i++) {
|
|
4122
|
+
if (seen.has(features[i])) rows.push({ context: { feature: String(features[i]) } });
|
|
4123
|
+
else seen.add(features[i]);
|
|
4124
|
+
}
|
|
4125
|
+
return rows.length ? rows : null;
|
|
4126
|
+
},
|
|
4127
|
+
},
|
|
4128
|
+
{
|
|
4129
|
+
id: "lint/rfc7633/transparency-info-feature", severity: "warn", source: "rfc7633", citation: "RFC 9162 7.2",
|
|
4130
|
+
message: "a certification authority should not issue a certificate naming the transparency_info TLS extension (52) in a TLS feature extension, because a TLS server need not support it to participate in certificate transparency",
|
|
4131
|
+
appliesTo: function (cert, ctx) { return !!ctx.raw("tlsFeature"); },
|
|
4132
|
+
check: function (cert, ctx) {
|
|
4133
|
+
var d = ctx.decode("tlsFeature");
|
|
4134
|
+
var features = (d && d.value && d.value.features) || [];
|
|
4135
|
+
for (var i = 0; i < features.length; i++) {
|
|
4136
|
+
if (features[i] === 52n) return { context: { feature: "52", name: "transparency_info" } };
|
|
4137
|
+
}
|
|
4138
|
+
return null;
|
|
4139
|
+
},
|
|
4140
|
+
},
|
|
4141
|
+
{
|
|
4142
|
+
id: "lint/rfc7633/feature-undefined-in-tls13", severity: "notice", source: "rfc7633", citation: "RFC 9846 4.3",
|
|
4143
|
+
message: "a TLS feature value names status_request_v2 (17), which RFC 9846 does not list in its ExtensionType enum and whose defining document RFC 6961 it obsoletes, so a TLS 1.3 server cannot satisfy it",
|
|
4144
|
+
appliesTo: function (cert, ctx) { return !!ctx.raw("tlsFeature"); },
|
|
4145
|
+
check: function (cert, ctx) {
|
|
4146
|
+
var d = ctx.decode("tlsFeature");
|
|
4147
|
+
var features = (d && d.value && d.value.features) || [];
|
|
4148
|
+
for (var i = 0; i < features.length; i++) {
|
|
4149
|
+
if (features[i] === 17n) return { context: { feature: "17", name: "status_request_v2", obsoletedBy: "RFC 9846" } };
|
|
4150
|
+
}
|
|
4151
|
+
return null;
|
|
4152
|
+
},
|
|
4153
|
+
},
|
|
4154
|
+
];
|
|
4155
|
+
|
|
4059
4156
|
/** @internal RFC 5280 sec. 5 states the criticality of each CRL extension it profiles, and sec. 5.3
|
|
4060
4157
|
* does the same for the entry extensions. certificateIssuer is critical (sec. 5.3.3) because a
|
|
4061
4158
|
* consumer that ignores it attributes an entry to the wrong issuer. */
|
|
@@ -6413,6 +6510,7 @@ var PROFILES = Object.assign(Object.create(null), {
|
|
|
6413
6510
|
"rfc9909": RFC9909_RULES.concat(RFC5280_EXTENSION_SYNTAX_RULES),
|
|
6414
6511
|
"rfc9935": RFC9935_RULES.concat(RFC5280_EXTENSION_SYNTAX_RULES),
|
|
6415
6512
|
"rfc9763": RFC9763_RULES.concat(RFC5280_EXTENSION_SYNTAX_RULES),
|
|
6513
|
+
"rfc7633": RFC7633_RULES.concat(RFC5280_EXTENSION_SYNTAX_RULES),
|
|
6416
6514
|
"x509-altsig": X509_ALTSIG_RULES.concat(RFC5280_EXTENSION_SYNTAX_RULES),
|
|
6417
6515
|
"cabf-tls": CABF_TLS_RULES.concat(RFC5280_EXTENSION_SYNTAX_RULES),
|
|
6418
6516
|
"cabf-smime": CABF_SMIME_RULES.concat(RFC5280_EXTENSION_SYNTAX_RULES),
|
|
@@ -6436,7 +6534,7 @@ var PROFILES = Object.assign(Object.create(null), {
|
|
|
6436
6534
|
.concat(RFC5280_EXTENSION_SYNTAX_RULES),
|
|
6437
6535
|
});
|
|
6438
6536
|
var ALL_RULES = RFC5280_RULES.concat(RFC9881_RULES).concat(RFC9909_RULES).concat(RFC9935_RULES)
|
|
6439
|
-
.concat(RFC9763_RULES).concat(X509_ALTSIG_RULES)
|
|
6537
|
+
.concat(RFC9763_RULES).concat(RFC7633_RULES).concat(X509_ALTSIG_RULES)
|
|
6440
6538
|
.concat(CABF_TLS_RULES).concat(CABF_SMIME_RULES).concat(CABF_CS_RULES).concat(ETSI_QC_RULES)
|
|
6441
6539
|
/** @internal The root-program rows are in the registry so `rules()` enumerates them, and stay
|
|
6442
6540
|
* inert under the default set because each one asks whether the caller named the profile. */
|
|
@@ -6725,6 +6823,11 @@ function _applyThreshold(report, severity) {
|
|
|
6725
6823
|
* id-kp-serverAuth and is not a CA. `"nist-pqc-transition"` and `"cnsa-2.0"` are
|
|
6726
6824
|
* named the same way: which algorithm policy a certificate is held to is the
|
|
6727
6825
|
* caller's assertion, so neither is detected and both are inert until named.
|
|
6826
|
+
* `"rfc7633"` adds the rows that read a TLS Feature extension's VALUES: a feature
|
|
6827
|
+
* outside the 16-bit width of the registry sec. 4.1 points at, a repeated feature,
|
|
6828
|
+
* the transparency_info feature RFC 9162 sec. 7.2 asks a CA not to issue, and the
|
|
6829
|
+
* status_request_v2 feature RFC 9846 leaves out of its ExtensionType enum. The
|
|
6830
|
+
* criticality of that extension is graded by default and needs no profile.
|
|
6728
6831
|
* @opts severity Suppress findings below this floor (default `"notice"`). `counts` and
|
|
6729
6832
|
* `worst` always reflect the complete, unfiltered result.
|
|
6730
6833
|
* @example
|
package/lib/ocsp.js
CHANGED
|
@@ -32,6 +32,8 @@ var _ocspIntrinsic = require("./guard-intrinsic");
|
|
|
32
32
|
* the construction to whatever that binding holds, and one settling without awaiting its argument runs
|
|
33
33
|
* the continuation before the check has finished. */
|
|
34
34
|
var _Promise = _ocspIntrinsic.Promise;
|
|
35
|
+
/** @internal The URL parser, taken at load: the href it reports is the responder a request is sent to. */
|
|
36
|
+
var _URL = _ocspIntrinsic.URL;
|
|
35
37
|
var _promiseResolve = _ocspIntrinsic.uncurry(_ocspIntrinsic.promiseResolve);
|
|
36
38
|
/** @internal A refusal returned as a REJECTED promise is a verdict too: read from the global binding, a
|
|
37
39
|
* replacement whose `reject` resolves instead turns each `ocsp/bad-input` below into a pass. */
|
|
@@ -921,7 +923,7 @@ function httpRequest(der, responderUrl, opts) {
|
|
|
921
923
|
* the string the sec. 5 size bound has to be measured on. The appended segment is base64 under
|
|
922
924
|
* the sec. 2.3 unreserved set, which holds no `.`, so it cannot become a dot segment itself. */
|
|
923
925
|
var normalized;
|
|
924
|
-
try { normalized = new
|
|
926
|
+
try { normalized = new _URL(responderUrl).href; }
|
|
925
927
|
catch (e) { throw E("ocsp/bad-input", "httpRequest: the responder URL does not parse as a URL: " + guard.text.describeThrown(e), e); }
|
|
926
928
|
var base = normalized.charAt(normalized.length - 1) === "/"
|
|
927
929
|
? normalized : normalized + "/";
|
package/lib/path-validate.js
CHANGED
|
@@ -67,6 +67,15 @@ var _reject = intrinsic.uncurry(intrinsic.promiseReject);
|
|
|
67
67
|
* whatever the binding holds: a descriptor failure answering `false` through a replaced constructor is
|
|
68
68
|
* not a refusal, and this module's verdicts are what a caller acts on. */
|
|
69
69
|
var _Promise = intrinsic.Promise;
|
|
70
|
+
/** @internal The URL parser, taken at load: the host it reports from an access descriptor is the host a
|
|
71
|
+
* revocation fetch would be directed to. Its accessors are taken with it, because the constructor's
|
|
72
|
+
* capture does not cover the prototype the value reads its parts off, and the host read here is the
|
|
73
|
+
* one the address filter is applied to. */
|
|
74
|
+
var _URL = intrinsic.URL;
|
|
75
|
+
var _urlHref = intrinsic.urlHref;
|
|
76
|
+
var _urlProtocol = intrinsic.urlProtocol;
|
|
77
|
+
var _urlHostname = intrinsic.urlHostname;
|
|
78
|
+
var _urlSetHash = intrinsic.urlSetHash;
|
|
70
79
|
var constants = require("./constants");
|
|
71
80
|
var validator = require("./validator-all");
|
|
72
81
|
var cms = require("./schema-cms");
|
|
@@ -122,6 +131,7 @@ var OID = intrinsic.assign(intrinsic.create(null), {
|
|
|
122
131
|
anyExtendedKeyUsage: oid.byName("anyExtendedKeyUsage"),
|
|
123
132
|
cRLDistributionPoints: oid.byName("cRLDistributionPoints"),
|
|
124
133
|
subjectKeyIdentifier: oid.byName("subjectKeyIdentifier"),
|
|
134
|
+
tlsFeature: oid.byName("tlsFeature"),
|
|
125
135
|
authorityKeyIdentifier: oid.byName("authorityKeyIdentifier"),
|
|
126
136
|
authorityInfoAccess: oid.byName("authorityInfoAccess"),
|
|
127
137
|
caIssuers: oid.byName("caIssuers"),
|
|
@@ -297,6 +307,66 @@ function _isCaCert(cert) {
|
|
|
297
307
|
return !!(bc && bc.value && bc.value.cA === true);
|
|
298
308
|
}
|
|
299
309
|
|
|
310
|
+
/** @internal RFC 7633 sec. 4.2.2, the one requirement in that document decidable from certificate
|
|
311
|
+
* bytes: "a certificate that is signed by a Certificate Signing Certificate that contains a TLS
|
|
312
|
+
* feature extension MUST contain a TLS feature extension that offers the same set or a superset of
|
|
313
|
+
* the features advertised in the signing certificate." The clause's own gate is the issuer's key
|
|
314
|
+
* usage, "Certification Authority certificate with the key usage extension value set to
|
|
315
|
+
* keyCertSign", which is a different question from its basicConstraints cA bit, so this asks
|
|
316
|
+
* keyUsage. A subject carrying NO extension under an issuer that advertises fails, because the
|
|
317
|
+
* clause says the subject MUST contain one; an issuer carrying none constrains nothing, so the
|
|
318
|
+
* constraint is one-directional.
|
|
319
|
+
*
|
|
320
|
+
* Set semantics. SEQUENCE OF fixes no order and forbids no repeat, so neither side's order nor its
|
|
321
|
+
* multiplicity may be read as meaning, and the comparison cannot stop at a first match.
|
|
322
|
+
*
|
|
323
|
+
* Returns null when there is nothing to check, so a boundary that was not checked reports no row
|
|
324
|
+
* rather than a passing one.
|
|
325
|
+
*
|
|
326
|
+
* The anchor is not read. `anchorFromCert` keeps the name, key and algorithm and drops the
|
|
327
|
+
* certificate's extensions, so a trust anchor's own advertised features cannot reach this and the
|
|
328
|
+
* rule is applied from the first certificate in the path downward. An anchor that advertises
|
|
329
|
+
* features constrains nothing here, which is stated rather than implied. */
|
|
330
|
+
function tlsFeatureChainCheck(issuerCert, cert) {
|
|
331
|
+
if (!issuerCert) return null;
|
|
332
|
+
var issuerKu;
|
|
333
|
+
try { issuerKu = decodeExt(issuerCert, OID.keyUsage); }
|
|
334
|
+
// allow:swallow-unverified a keyUsage that cannot be read is refused by its own check on that certificate
|
|
335
|
+
catch (_e) { return null; }
|
|
336
|
+
if (!(issuerKu && issuerKu.value && issuerKu.value.keyCertSign === true)) return null;
|
|
337
|
+
var issuerFeatures;
|
|
338
|
+
try { issuerFeatures = decodeExt(issuerCert, OID.tlsFeature); }
|
|
339
|
+
catch (e) { return guard.verdict.of({ ok: false, code: pathCode(e, "path/bad-tls-feature") }); }
|
|
340
|
+
if (!issuerFeatures) return null;
|
|
341
|
+
/** @internal The subject's extension is read BEFORE the issuer's set is looked at, because the
|
|
342
|
+
* clause is two requirements and the first one does not mention the set: a certificate signed by a
|
|
343
|
+
* signing certificate that CONTAINS the extension MUST CONTAIN one, and what it contains must then
|
|
344
|
+
* cover the issuer's features. Testing the set first read an issuer whose Features list is EMPTY as
|
|
345
|
+
* nothing to require and returned success for a subject that carried no extension at all, which the
|
|
346
|
+
* MUST CONTAIN half forbids; it also meant a malformed subject value under such an issuer was never
|
|
347
|
+
* decoded. An empty SEQUENCE is a conforming Features, the syntax carrying no SIZE constraint. */
|
|
348
|
+
var offeredExt;
|
|
349
|
+
try { offeredExt = decodeExt(cert, OID.tlsFeature); }
|
|
350
|
+
catch (e) { return guard.verdict.of({ ok: false, code: pathCode(e, "path/bad-tls-feature") }); }
|
|
351
|
+
if (!offeredExt) return guard.verdict.of({ ok: false, code: "path/tls-feature-not-offered" });
|
|
352
|
+
var required = (issuerFeatures.value && issuerFeatures.value.features) || [];
|
|
353
|
+
if (!required.length) return guard.verdict.of({ ok: true });
|
|
354
|
+
var offered = (offeredExt.value && offeredExt.value.features) || [];
|
|
355
|
+
/** @internal Membership through a set rather than a scan of the offered list per required value.
|
|
356
|
+
* Both lists come off untrusted certificates and the syntax bounds neither, so the pairwise form is
|
|
357
|
+
* work an issuer and a subject choose together: MEASURED, 20000 values on each side is 4 x 10^8
|
|
358
|
+
* comparisons, synchronous, on a path whose signatures may already have failed. A set makes it
|
|
359
|
+
* linear in the two lengths, and the values are bigints, which a set compares by value. */
|
|
360
|
+
var offeredSet = new intrinsic.Set();
|
|
361
|
+
for (var j = 0; j < offered.length; j++) intrinsic.setAdd(offeredSet, offered[j]);
|
|
362
|
+
for (var i = 0; i < required.length; i++) {
|
|
363
|
+
if (!intrinsic.setHas(offeredSet, required[i])) {
|
|
364
|
+
return guard.verdict.of({ ok: false, code: "path/tls-feature-not-offered" });
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
return guard.verdict.of({ ok: true });
|
|
368
|
+
}
|
|
369
|
+
|
|
300
370
|
function statefulHbsKeyUsageCheck(cert, isCa) {
|
|
301
371
|
var ku;
|
|
302
372
|
try { ku = decodeExt(cert, OID.keyUsage); }
|
|
@@ -1549,6 +1619,16 @@ async function _validate(path, opts, run, scope) {
|
|
|
1549
1619
|
guard.list.append(checks,{ name: "nameChaining", ok: chainOk === true, code: chainOk === true ? undefined : "path/name-chaining" });
|
|
1550
1620
|
if (chainOk !== true) failed = true;
|
|
1551
1621
|
|
|
1622
|
+
/** @internal In the loop body, so it runs at EVERY issuer-to-subject boundary rather than only at
|
|
1623
|
+
* the target: RFC 7633 sec. 4.2.2 constrains each certificate against the one that signed it. The
|
|
1624
|
+
* issuer is the previous entry, which is `null` for the first certificate because the anchor's
|
|
1625
|
+
* extensions are not part of the path. */
|
|
1626
|
+
var tfRes = tlsFeatureChainCheck(idx > 0 ? certs[idx - 1] : null, cert);
|
|
1627
|
+
if (tfRes) {
|
|
1628
|
+
guard.list.append(checks, { name: "tlsFeatureChain", ok: tfRes.ok, code: tfRes.ok ? undefined : tfRes.code });
|
|
1629
|
+
if (!tfRes.ok) failed = true;
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1552
1632
|
if (!(selfIssued(cert) && i !== n)) {
|
|
1553
1633
|
var ncRes;
|
|
1554
1634
|
try { ncRes = checkNameConstraints(state, cert); }
|
|
@@ -3146,16 +3226,20 @@ async function _fetchAiaIssuers(current, aia) {
|
|
|
3146
3226
|
if (ad.accessMethod !== OID.caIssuers) continue;
|
|
3147
3227
|
if (!ad.accessLocation || ad.accessLocation.tag !== 6) continue;
|
|
3148
3228
|
var u;
|
|
3149
|
-
try { u = new
|
|
3229
|
+
try { u = new _URL(ad.accessLocation.value); }
|
|
3150
3230
|
catch (_e) { continue; }
|
|
3151
|
-
if (u
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
if (
|
|
3231
|
+
if (_urlProtocol(u) !== "https:") continue;
|
|
3232
|
+
/** @internal The host read ONCE and both address tests applied to that one string: the blocklist
|
|
3233
|
+
* and the literal-versus-name question have to be answered about the same host. */
|
|
3234
|
+
var uHost = _urlHostname(u);
|
|
3235
|
+
if (_isBlockedAiaHost(uHost)) continue;
|
|
3236
|
+
if (net.isIP(_stripBrackets(uHost)) === 0 && !aia.transportGuardsAddresses) continue;
|
|
3237
|
+
_urlSetHash(u, "");
|
|
3238
|
+
var uHref = _urlHref(u);
|
|
3239
|
+
if (intrinsic.setHas(aia.fetchedUrls, uHref) || intrinsic.setHas(seenThisCert, uHref)) continue;
|
|
3156
3240
|
if (seenThisCert.size >= aia.maxPerCert) break;
|
|
3157
|
-
intrinsic.setAdd(seenThisCert,
|
|
3158
|
-
guard.list.append(uris,
|
|
3241
|
+
intrinsic.setAdd(seenThisCert, uHref);
|
|
3242
|
+
guard.list.append(uris, uHref);
|
|
3159
3243
|
}
|
|
3160
3244
|
var out = [];
|
|
3161
3245
|
for (var k = 0; k < uris.length; k++) {
|
|
@@ -3348,7 +3432,7 @@ async function build(leaf, opts) {
|
|
|
3348
3432
|
* hostname taken from a certificate would be opened on that promise.
|
|
3349
3433
|
* @guard-via guard.identifier.declaresOwn */
|
|
3350
3434
|
transportGuardsAddresses: guard.identifier.declaresOwn(aiaTransport, "blocksPrivateAddresses"),
|
|
3351
|
-
tls: opts.tls
|
|
3435
|
+
tls: opts.tls != null ? opts.tls : intrinsic.create(null),
|
|
3352
3436
|
timeout: opts.aiaTimeout,
|
|
3353
3437
|
maxResponseBytes: guard.limits.cap(opts.maxResponseBytes, "build: opts.maxResponseBytes", constants.LIMITS.PATH_AIA_MAX_RESPONSE_BYTES, { E: E, code: "path/bad-input", min: 1, max: constants.LIMITS.PATH_AIA_MAX_RESPONSE_BYTES }),
|
|
3354
3438
|
maxPerCert: guard.limits.cap(opts.maxAiaPerCert, "build: opts.maxAiaPerCert", constants.LIMITS.PATH_AIA_MAX_PER_CERT, { E: E, code: "path/bad-input", min: 0 }),
|
package/lib/pki-build.js
CHANGED
|
@@ -851,6 +851,39 @@ function makeBuilder(ctx) {
|
|
|
851
851
|
}
|
|
852
852
|
/** @internal InhibitAnyPolicy (RFC 5280 sec. 4.2.1.14): a bare SkipCerts INTEGER. */
|
|
853
853
|
function extInhibitAnyPolicy(v) { return b.integer(skipCount(v, "inhibitAnyPolicy")); }
|
|
854
|
+
/** @internal Features (RFC 7633 sec. 4): `SEQUENCE OF INTEGER`, each a TLS ExtensionType value.
|
|
855
|
+
* sec. 4.1 binds those values to the IANA TLS Extensions registry, so the registry names this
|
|
856
|
+
* build knows are accepted beside the numbers, resolved through the one table `C.NAMES.TLS_FEATURE`
|
|
857
|
+
* that `pki.inspect` prints them from rather than a second copy of it. The type states no range and
|
|
858
|
+
* no ordering, so a value outside the registry's 16-bit width and a repeat both encode, and the
|
|
859
|
+
* linter is what grades them: an encoder that dropped or sorted them would hide what it emitted
|
|
860
|
+
* from the rule that reads it. An EMPTY list is refused here although the decoder accepts one on
|
|
861
|
+
* the wire, because an empty Features advertises nothing and makes the extension inert, which is a
|
|
862
|
+
* caller's typo rather than a document to read. */
|
|
863
|
+
function extTlsFeature(list) {
|
|
864
|
+
if (!_isArray(list) || !list.length) {
|
|
865
|
+
throw E("bad-input", "tlsFeature must name at least one TLS ExtensionType value (RFC 7633 sec. 4.1); " +
|
|
866
|
+
"an empty Features advertises nothing, so the extension it would emit is inert");
|
|
867
|
+
}
|
|
868
|
+
var dense = reqDenseArray(list, "tlsFeature");
|
|
869
|
+
return b.sequence(_mapIntrinsic(dense, function (f, i) {
|
|
870
|
+
return b.integer(tlsFeatureValue(f, "tlsFeature entry " + i));
|
|
871
|
+
}));
|
|
872
|
+
}
|
|
873
|
+
/** @internal A registry name resolved off the printing table, so the two directions cannot drift. */
|
|
874
|
+
function tlsFeatureValue(f, who) {
|
|
875
|
+
if (typeof f !== "string") {
|
|
876
|
+
return guard.range.authoredInteger(f, rawErr, ctx.prefix + "/bad-input", who);
|
|
877
|
+
}
|
|
878
|
+
var table = C.NAMES.TLS_FEATURE;
|
|
879
|
+
var keys = intrinsic.keys(table);
|
|
880
|
+
for (var i = 0; i < keys.length; i++) {
|
|
881
|
+
if (table[keys[i]] === f) return intrinsic.BigInt(keys[i]);
|
|
882
|
+
}
|
|
883
|
+
throw E("bad-input", who + " names no TLS ExtensionType this build knows: " + _stringify(f) +
|
|
884
|
+
" (accepted names: " + intrinsic.join(_mapIntrinsic(keys, function (k) { return table[k]; }), ", ") +
|
|
885
|
+
"; any other value is given as its number)");
|
|
886
|
+
}
|
|
854
887
|
/** @internal PolicyMappings (RFC 5280 sec. 4.2.1.5): SEQUENCE SIZE (1..MAX) OF
|
|
855
888
|
* { issuerDomainPolicy, subjectDomainPolicy }. The clause forbids mapping either to or from
|
|
856
889
|
* anyPolicy, so neither side may name it. */
|
|
@@ -1282,6 +1315,7 @@ function makeBuilder(ctx) {
|
|
|
1282
1315
|
nameConstraints: 1, authorityInfoAccess: 1, cRLDistributionPoints: 1, freshestCRL: 1,
|
|
1283
1316
|
policyConstraints: 1, inhibitAnyPolicy: 1, policyMappings: 1, policyMappingsCritical: 1,
|
|
1284
1317
|
issuerAltName: 1, qcStatements: 1, qcStatementsCritical: 1,
|
|
1318
|
+
tlsFeature: 1, tlsFeatureCritical: 1,
|
|
1285
1319
|
msCertificateTemplate: 1, msEnrollCertType: 1, msApplicationPolicies: 1,
|
|
1286
1320
|
subjectInfoAccess: 1, subjectDirectoryAttributes: 1,
|
|
1287
1321
|
};
|
|
@@ -1336,6 +1370,11 @@ function makeBuilder(ctx) {
|
|
|
1336
1370
|
if (extSpec.policyMappings != null) guard.list.append(out, ext(O("policyMappings"), !!extSpec.policyMappingsCritical, extPolicyMappings(extSpec.policyMappings)));
|
|
1337
1371
|
if (extSpec.issuerAltName != null) guard.list.append(out, ext(O("issuerAltName"), false, encodeGeneralNames(reqDenseArray(extSpec.issuerAltName, "issuerAltName"))));
|
|
1338
1372
|
if (extSpec.qcStatements != null) guard.list.append(out, ext(O("qcStatements"), !!extSpec.qcStatementsCritical, extQcStatements(extSpec.qcStatements)));
|
|
1373
|
+
/** @internal RFC 7633 sec. 4.2.1 lets a request state the features the server supports, and
|
|
1374
|
+
* sec. 4.3.2 asks a server to generate it, so a feature declaration is the requester's wish and
|
|
1375
|
+
* belongs in this table. The criticality knob is here too, because a request carries the
|
|
1376
|
+
* Extension shape whole and the CA decides what it issues. */
|
|
1377
|
+
if (extSpec.tlsFeature != null) guard.list.append(out, ext(O("tlsFeature"), !!extSpec.tlsFeatureCritical, extTlsFeature(extSpec.tlsFeature)));
|
|
1339
1378
|
if (extSpec.msCertificateTemplate != null) guard.list.append(out, ext(O("msCertificateTemplate"), false, extMsCertificateTemplate(extSpec.msCertificateTemplate)));
|
|
1340
1379
|
if (extSpec.msEnrollCertType != null) guard.list.append(out, ext(O("msEnrollCertType"), false, extMsEnrollCertType(extSpec.msEnrollCertType)));
|
|
1341
1380
|
if (extSpec.msApplicationPolicies != null) guard.list.append(out, ext(O("msApplicationPolicies"), false, extCertPolicies(extSpec.msApplicationPolicies)));
|
|
@@ -1395,6 +1434,7 @@ function makeBuilder(ctx) {
|
|
|
1395
1434
|
extNameConstraints: extNameConstraints, extAuthorityInfoAccess: extAuthorityInfoAccess,
|
|
1396
1435
|
extCrlDistributionPoints: extCrlDistributionPoints, extPolicyConstraints: extPolicyConstraints,
|
|
1397
1436
|
extInhibitAnyPolicy: extInhibitAnyPolicy, extPolicyMappings: extPolicyMappings,
|
|
1437
|
+
extTlsFeature: extTlsFeature,
|
|
1398
1438
|
extQcStatements: extQcStatements,
|
|
1399
1439
|
encodeGeneralNames: encodeGeneralNames, serialInteger: serialInteger, timeDer: timeDer,
|
|
1400
1440
|
requestedExtensions: requestedExtensions,
|