@blamejs/pki 0.8.51 → 0.8.53
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 +42 -0
- package/README.md +1 -1
- package/index.js +2 -1
- package/lib/cms-decrypt.js +40 -3
- package/lib/cms-encrypt.js +64 -2
- package/lib/cms-sign.js +321 -2
- package/lib/cms-verify.js +602 -30
- package/lib/constants.js +15 -0
- package/lib/ct.js +3 -2
- package/lib/guard-der.js +26 -0
- package/lib/guard-identifier.js +27 -0
- package/lib/guard-time.js +98 -0
- package/lib/lint.js +69 -13
- package/lib/oid.js +11 -1
- package/lib/path-validate.js +82 -12
- package/lib/revocation-fetch.js +1 -3
- package/lib/scep.js +8 -3
- package/lib/schema-all.js +3 -2
- package/lib/schema-cms.js +377 -6
- package/lib/schema-smime.js +215 -5
- package/lib/tsp-sign.js +58 -95
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/constants.js
CHANGED
|
@@ -293,6 +293,21 @@ var NAMES = Object.assign(Object.create(null), {
|
|
|
293
293
|
* this length: an OCSP CertID's issuer hashes, a timestamp's messageImprint. The XOFs (SHAKE) have
|
|
294
294
|
* no single length and are absent. */
|
|
295
295
|
DIGEST_OCTETS: { sha1: 20, sha256: 32, sha384: 48, sha512: 64, "sha3-256": 32, "sha3-512": 64 },
|
|
296
|
+
/** @internal The digests a verify path refuses by default and admits only under an explicit
|
|
297
|
+
* opt-in, by the OID registry's name, with the WebCrypto hash name each maps to. A digest is weak
|
|
298
|
+
* once a collision is reachable, which breaks the binding of every structure that carries "the
|
|
299
|
+
* hash of X": a signature's message digest, a timestamp's messageImprint, an ESS certificate
|
|
300
|
+
* identifier. Membership is defined once here, so a path that refuses one refuses all of them and
|
|
301
|
+
* a path that admits them under an opt-in admits the same set. A digest no path will compute at
|
|
302
|
+
* all is not listed; it is absent from DIGEST_HASH instead. */
|
|
303
|
+
WEAK_DIGESTS: { sha1: "SHA-1" },
|
|
304
|
+
/** @internal The digests an RFC 3161 message imprint may name in this build, by the OID registry's
|
|
305
|
+
* name, with the node name each computes under. One table for the whole timestamp round trip: a
|
|
306
|
+
* verb that TAKES an imprint, a verb that attaches a token over one, and the verifier that reads
|
|
307
|
+
* it all answer from this, so none of them can produce an imprint another would refuse. SHA-1 is
|
|
308
|
+
* listed because archived tokens carry it and the verifier admits it under an explicit opt-in;
|
|
309
|
+
* WEAK_DIGESTS is what decides that, not absence from here. */
|
|
310
|
+
TIMESTAMP_IMPRINT_DIGESTS: { sha1: "sha1", sha256: "sha256", sha384: "sha384", sha512: "sha512" },
|
|
296
311
|
});
|
|
297
312
|
|
|
298
313
|
/** @internal Each inner table is looked up by a name a certificate supplies, so it answers from
|
package/lib/ct.js
CHANGED
|
@@ -2177,7 +2177,7 @@ function _b64Array(obj, name, code, label, exactLen, maxCount) {
|
|
|
2177
2177
|
* @opts headers OPTIONAL extra request headers (the request-framing headers are stripped; the verb owns the GET method).
|
|
2178
2178
|
* @opts timeout OPTIONAL ms budget, default 30s (cap-validated).
|
|
2179
2179
|
* @opts maxResponseBytes OPTIONAL per-GET size cap, default 4 MiB, tightenable DOWNWARD only.
|
|
2180
|
-
* @opts requireJsonContentType OPTIONAL boolean (default false) -- opt in to a strict `ct/bad-content-type` gate on the JSON GET.
|
|
2180
|
+
* @opts requireJsonContentType OPTIONAL boolean (default false) -- opt in to a strict `ct/bad-content-type` gate on the JSON GET. A value other than `true`, `false`, `null` or an absent option is refused `ct/bad-input` before the request goes out.
|
|
2181
2181
|
* @example
|
|
2182
2182
|
* // a live distributor uses the default pki.transport.https; here an injected transport returns the pair
|
|
2183
2183
|
* var r = await pki.ct.fetchLogList({ url: "https://ct.example/log_list.json", signerKey: googleSignerSpki,
|
|
@@ -2241,7 +2241,8 @@ async function fetchLogList(opts) {
|
|
|
2241
2241
|
var req = { headers: headers, tls: fetchTls, timeout: timeout, maxResponseBytes: maxResponseBytes };
|
|
2242
2242
|
/** @internal Read before the request goes out, so the option that decides the check is the one the call
|
|
2243
2243
|
* was made with rather than whatever it says once the response is back. */
|
|
2244
|
-
var requireJsonContentType = opts.requireJsonContentType
|
|
2244
|
+
var requireJsonContentType = guard.identifier.booleanOption(opts.requireJsonContentType, _ctErr,
|
|
2245
|
+
"ct/bad-input", "fetchLogList opts.requireJsonContentType");
|
|
2245
2246
|
var jsonRes = await _fetchBody(transport, jsonUrl, req, "CT log-list JSON");
|
|
2246
2247
|
if (requireJsonContentType && jsonRes.contentType !== "application/json") throw _ctErr("ct/bad-content-type", "the CT log-list JSON GET returned content-type " + JSON.stringify(jsonRes.contentType || null) + " (opts.requireJsonContentType is set)");
|
|
2247
2248
|
var sigRes = await _fetchBody(transport, sigUrl, req, "CT log-list signature");
|
package/lib/guard-der.js
CHANGED
|
@@ -96,6 +96,31 @@ function paramsAbsentOrNull(params) {
|
|
|
96
96
|
return params === null || params === undefined || isNull(params);
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
/** Whether two decoded `AlgorithmIdentifier` records name the same algorithm with the same
|
|
100
|
+
* parameters, comparing the parameters AS THEY ARE: absent equals absent, and an explicit DER NULL
|
|
101
|
+
* is a different identifier from one with no parameters field.
|
|
102
|
+
*
|
|
103
|
+
* This is the comparison for an identifier whose parameters carry meaning. A digest identifier has
|
|
104
|
+
* the opposite rule, because RFC 3370 clause 2.1 and RFC 5754 clause 2 require an implementation to
|
|
105
|
+
* accept both spellings of the same digest, and `schema-cms.sameDigestAlgorithm` grants that. A
|
|
106
|
+
* signature identifier does not: RFC 4055 requires the `*WithRSAEncryption` identifiers to carry a
|
|
107
|
+
* NULL, RFC 5758 and RFC 8410 require the `ecdsaWith*` and the Edwards and post-quantum ones to omit
|
|
108
|
+
* the field, and RSASSA-PSS carries its hash, mask generator, salt length and trailer field there
|
|
109
|
+
* under RFC 4056 clause 2.2. Collapsing the two spellings for one of those is admitting a different
|
|
110
|
+
* algorithm, which is why the grant does not travel.
|
|
111
|
+
*
|
|
112
|
+
* @enforced-by behavioral -- an equality that is too permissive has no rename-proof shape; the RED
|
|
113
|
+
* vector is an identifier pair differing only in an explicit NULL, which must compare unequal here
|
|
114
|
+
* and equal through the digest helper.
|
|
115
|
+
*/
|
|
116
|
+
function sameAlgorithmExactly(a, c) {
|
|
117
|
+
if (!a || !c || a.oid !== c.oid) return false;
|
|
118
|
+
var ap = a.parameters === undefined ? null : a.parameters;
|
|
119
|
+
var cp = c.parameters === undefined ? null : c.parameters;
|
|
120
|
+
if (ap === null || cp === null) return ap === cp;
|
|
121
|
+
return _isBuffer(ap) && _isBuffer(cp) && _sizeOf(ap) === _sizeOf(cp) && _compare(ap, cp) === 0;
|
|
122
|
+
}
|
|
123
|
+
|
|
99
124
|
// @enforced-by behavioral -- see paramsAbsentOrNull.
|
|
100
125
|
function assertParams(params, shape, E, code, label) {
|
|
101
126
|
var ok = shape === "null" ? isNull(params) : (shape === "absent"
|
|
@@ -109,4 +134,5 @@ function assertParams(params, shape, E, code, label) {
|
|
|
109
134
|
module.exports = _freeze({
|
|
110
135
|
element: element, tlv: tlv,
|
|
111
136
|
isNull: isNull, paramsAbsentOrNull: paramsAbsentOrNull, assertParams: assertParams,
|
|
137
|
+
sameAlgorithmExactly: sameAlgorithmExactly,
|
|
112
138
|
});
|
package/lib/guard-identifier.js
CHANGED
|
@@ -600,6 +600,32 @@ function assertCallableOption(opts, key, E, code, label, shape) {
|
|
|
600
600
|
return assertCallable(opts[key], E, code, label, shape);
|
|
601
601
|
}
|
|
602
602
|
|
|
603
|
+
/** @internal How a switch is read: as the boolean it documents, with an absent value standing for
|
|
604
|
+
* off and every other value refused. A switch that turns a requirement on is the case that decides
|
|
605
|
+
* the shape. `requireRevocation: "true"` out of a config file is truthy to JavaScript and unequal
|
|
606
|
+
* to `true`, so a boundary testing `=== true` runs without the requirement and says nothing, while
|
|
607
|
+
* one testing truthiness turns the requirement on for `0` and `""` as well. The value is rendered
|
|
608
|
+
* from its own type rather than coerced, so a caller value with a throwing `toString` reports the
|
|
609
|
+
* typed refusal. A boundary that reads `null` as a value its caller supplied keeps its own check:
|
|
610
|
+
* this one reads it as absence. */
|
|
611
|
+
// @enforced-by behavioral -- reading a caller switch has no rename-proof code shape; `=== true` is
|
|
612
|
+
// @enforced-by behavioral -- the correct read for a switch that turns a control OFF, so a lexical
|
|
613
|
+
// @enforced-by behavioral -- rule would flag the safe direction. The per-verb vectors are the
|
|
614
|
+
// @enforced-by behavioral -- guard: each drives the shipped verb with a non-boolean and asserts the
|
|
615
|
+
// @enforced-by behavioral -- typed refusal, and with false and absence to pin the two off forms.
|
|
616
|
+
function booleanOption(value, E, code, label) {
|
|
617
|
+
if (value === undefined || value === null) return false;
|
|
618
|
+
if (value === true || value === false) return value;
|
|
619
|
+
throw E(code, label + " must be true or false, got " + _showSwitch(value));
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/** @internal The value's own type, for the message above. Nothing here calls into the value. */
|
|
623
|
+
function _showSwitch(value) {
|
|
624
|
+
if (typeof value === "string") return _stringify(value);
|
|
625
|
+
if (typeof value === "number" || typeof value === "bigint") return _String(value);
|
|
626
|
+
return typeof value;
|
|
627
|
+
}
|
|
628
|
+
|
|
603
629
|
/** @internal Whether a caller's object says something about itself, as opposed to inheriting the
|
|
604
630
|
* answer. A declaration is a promise the author of that object made, so it has to sit on the object
|
|
605
631
|
* they wrote: reading the flag through the object finds a value on `Function.prototype` that every
|
|
@@ -619,6 +645,7 @@ function declaresOwn(obj, name) {
|
|
|
619
645
|
|
|
620
646
|
module.exports = intrinsic.freeze({
|
|
621
647
|
assertCallable: assertCallable,
|
|
648
|
+
booleanOption: booleanOption,
|
|
622
649
|
declaresOwn: declaresOwn,
|
|
623
650
|
assertCallableOption: assertCallableOption,
|
|
624
651
|
assertCanonicalOid: assertCanonicalOid,
|
package/lib/guard-time.js
CHANGED
|
@@ -19,17 +19,112 @@ var _dateGetUTCMinutes = require("./guard-intrinsic").uncurry(Date.prototype.get
|
|
|
19
19
|
var _dateGetUTCSeconds = require("./guard-intrinsic").uncurry(Date.prototype.getUTCSeconds);
|
|
20
20
|
var _dateGetUTCMilliseconds = require("./guard-intrinsic").uncurry(Date.prototype.getUTCMilliseconds);
|
|
21
21
|
var _dateSetUTCFullYear = require("./guard-intrinsic").uncurry(Date.prototype.setUTCFullYear);
|
|
22
|
+
var _dateToISOString = require("./guard-intrinsic").uncurry(Date.prototype.toISOString);
|
|
23
|
+
var _charCodeAt = require("./guard-intrinsic").uncurry(String.prototype.charCodeAt);
|
|
24
|
+
var _Number = require("./guard-intrinsic").Number;
|
|
25
|
+
var constants = require("./constants");
|
|
26
|
+
/** @internal RFC 3161 clause 2.4.2 states an Accuracy's third field in microseconds, which no
|
|
27
|
+
* `C.TIME` scale names because no PKI structure carries a microsecond duration anywhere else. */
|
|
28
|
+
var MICROS_PER_MILLISECOND = 1000;
|
|
22
29
|
var _dateUTC = Date.UTC;
|
|
23
30
|
var _floor = Math.floor;
|
|
24
31
|
var _ceil = Math.ceil;
|
|
25
32
|
/** @internal A DER GeneralizedTime carries a four-digit year and a UTCTime a narrower one, so a Date
|
|
26
33
|
* outside 0000..9999 has no PKI encoding. */
|
|
27
34
|
var DER_YEAR_MIN = 0, DER_YEAR_MAX = 9999;
|
|
35
|
+
/** @internal The widest accuracy a date comparison can use, the DER year range in seconds, and the
|
|
36
|
+
* instant a wider one saturates to: the last instant a DER time can carry, which is past every
|
|
37
|
+
* certificate date and, unlike an arbitrary ceiling, is a `Date` a verdict can render. Declared
|
|
38
|
+
* after the range it derives from, since a `var` read above its own initializer is `undefined`. */
|
|
39
|
+
var MAX_ACCURACY_SECONDS = constants.TIME.days((DER_YEAR_MAX + 1) * 366) / constants.TIME.seconds(1);
|
|
40
|
+
var MAX_INSTANT = _dateUTC(DER_YEAR_MAX, 11, 31, 23, 59, 59, 999);
|
|
28
41
|
|
|
29
42
|
// @enforced-by guard-shape-reinlined
|
|
30
43
|
// @guard-shape \.getTime\s*\(
|
|
31
44
|
function instantOf(value) { return _dateGetTime(value); }
|
|
32
45
|
|
|
46
|
+
/** The instant an error message or a verdict names, rendered through the operation captured at load.
|
|
47
|
+
* A replaced `toISOString` chooses the time a refusal reports, which is what an operator reads when
|
|
48
|
+
* deciding whether the refusal was right, so the rendering is taken from the same place every other
|
|
49
|
+
* read of a Date is.
|
|
50
|
+
*
|
|
51
|
+
* @enforced-by behavioral -- the live-read budget each module declares is what holds a call-time
|
|
52
|
+
* `toISOString` out of a converted file; a `@guard-shape` here would name the read in every module
|
|
53
|
+
* that renders a date and the budget already answers for those.
|
|
54
|
+
*/
|
|
55
|
+
function isoOf(value) { return _dateToISOString(value); }
|
|
56
|
+
|
|
57
|
+
/** The instant a DER GeneralizedTime names, rounded UP to the next whole millisecond where its
|
|
58
|
+
* fractional seconds carry a digit a `Date` cannot hold. `fraction` is the fraction's digit string;
|
|
59
|
+
* the first three digits ARE the milliseconds, so a nonzero digit after them puts the real instant
|
|
60
|
+
* strictly between `value` and `value + 1ms`.
|
|
61
|
+
*
|
|
62
|
+
* An upper bound is decided against this rather than against the Date, because the Date truncates:
|
|
63
|
+
* a time stamped at `...000000.000001Z` reads as `...000000.000Z`, which compares equal to a bound
|
|
64
|
+
* of `...000000Z` and so passes an "after the bound" test it is on the wrong side of. The two
|
|
65
|
+
* consumers are the timestamp verifier's own check of the authority's certificate window and the
|
|
66
|
+
* signature-timestamp row's check of the signing certificate's expiry.
|
|
67
|
+
*
|
|
68
|
+
* @enforced-by behavioral -- a truncating comparison has no rename-proof shape; the RED vector is a
|
|
69
|
+
* token minted with a sub-millisecond fraction one tick past a certificate's notAfter.
|
|
70
|
+
*/
|
|
71
|
+
function ceilInstantOf(value, fraction) { return latestInstantOf(value, fraction, null); }
|
|
72
|
+
|
|
73
|
+
/** @internal The part of a DER GeneralizedTime's fractional seconds a `Date` cannot hold, in whole
|
|
74
|
+
* microseconds. The first three digits ARE the milliseconds; the next three are microseconds, and
|
|
75
|
+
* anything finer than a microsecond counts as one, since a bound is only a bound if it is not
|
|
76
|
+
* exceeded. Read digit by digit through the captured accessor: a fraction is caller bytes. */
|
|
77
|
+
function _fractionMicros(fraction) {
|
|
78
|
+
if (typeof fraction !== "string" || fraction.length <= 3) return 0;
|
|
79
|
+
var micros = 0;
|
|
80
|
+
var finer = false;
|
|
81
|
+
for (var i = 3; i < fraction.length; i++) {
|
|
82
|
+
var c = _charCodeAt(fraction, i);
|
|
83
|
+
if (c < 48 || c > 57) break;
|
|
84
|
+
var digit = c - 48;
|
|
85
|
+
if (i === 3) micros += digit * 100;
|
|
86
|
+
else if (i === 4) micros += digit * 10;
|
|
87
|
+
else if (i === 5) micros += digit;
|
|
88
|
+
else if (digit > 0) finer = true;
|
|
89
|
+
}
|
|
90
|
+
return finer ? micros + 1 : micros;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** The LATEST instant a timestamp could have been created at: its `genTime` ceiling plus the
|
|
94
|
+
* accuracy the token states. RFC 3161 clause 2.4.2 defines accuracy as the bound on the difference
|
|
95
|
+
* between the stated time and the real one, so a token saying it is accurate to two seconds places
|
|
96
|
+
* its creation anywhere in a four-second window, and an upper bound that reads only `genTime`
|
|
97
|
+
* concludes something the token does not establish. `accuracy` is the parsed `{ seconds, millis,
|
|
98
|
+
* micros }` or null; `seconds` arrives as a BigInt, the other two as numbers, and a micros
|
|
99
|
+
* remainder rounds up because a bound is only a bound if it is not exceeded.
|
|
100
|
+
*
|
|
101
|
+
* @enforced-by behavioral -- a bound that ignores a field has no rename-proof shape; the RED vector
|
|
102
|
+
* is a token whose genTime precedes a certificate's notAfter while its accuracy window does not.
|
|
103
|
+
*/
|
|
104
|
+
function latestInstantOf(value, fraction, accuracy) {
|
|
105
|
+
var at = instantOf(value);
|
|
106
|
+
var ms = 0;
|
|
107
|
+
/** @internal The sub-millisecond parts are summed and rounded ONCE. Rounding the genTime fraction
|
|
108
|
+
* and the accuracy up separately overstates the bound by a millisecond when both carry one, which
|
|
109
|
+
* refuses a token whose real window ends before the instant it is compared against. */
|
|
110
|
+
var micros = _fractionMicros(fraction);
|
|
111
|
+
if (accuracy !== null && typeof accuracy === "object") {
|
|
112
|
+
/** @internal An Accuracy's seconds field is an arbitrary-size INTEGER, so it is bounded BEFORE
|
|
113
|
+
* it is scaled: a token claiming more than the DER time range can hold is saturated to the
|
|
114
|
+
* widest instant rather than scaled into a value the scale helper refuses. The refusal would
|
|
115
|
+
* escape the row this builds and take the whole verify call down, where what the token has
|
|
116
|
+
* established is nothing about which side of any certificate date it falls on, which is the
|
|
117
|
+
* answer a saturated bound gives. */
|
|
118
|
+
if (accuracy.seconds > MAX_ACCURACY_SECONDS) return MAX_INSTANT;
|
|
119
|
+
if (typeof accuracy.seconds === "bigint") ms += constants.TIME.seconds(_Number(accuracy.seconds));
|
|
120
|
+
else if (typeof accuracy.seconds === "number") ms += constants.TIME.seconds(accuracy.seconds);
|
|
121
|
+
if (typeof accuracy.millis === "number") ms += constants.TIME.milliseconds(accuracy.millis);
|
|
122
|
+
if (typeof accuracy.micros === "number") micros += accuracy.micros;
|
|
123
|
+
}
|
|
124
|
+
if (micros > 0) ms += _ceil(micros / MICROS_PER_MILLISECOND);
|
|
125
|
+
return ms > 0 ? at + ms : at;
|
|
126
|
+
}
|
|
127
|
+
|
|
33
128
|
// @enforced-by nan-date-comparison-unguarded
|
|
34
129
|
function assertValid(value, E, code, label) {
|
|
35
130
|
if (!util.types.isDate(value) || _isNaN(instantOf(value))) {
|
|
@@ -120,6 +215,9 @@ module.exports = _freeze({
|
|
|
120
215
|
assertEncodable: assertEncodable,
|
|
121
216
|
addMonths: addMonths,
|
|
122
217
|
instantOf: instantOf,
|
|
218
|
+
isoOf: isoOf,
|
|
219
|
+
ceilInstantOf: ceilInstantOf,
|
|
220
|
+
latestInstantOf: latestInstantOf,
|
|
123
221
|
isDate: isDate,
|
|
124
222
|
toDate: toDate,
|
|
125
223
|
within: within,
|
package/lib/lint.js
CHANGED
|
@@ -4929,12 +4929,21 @@ var ALL_CRL_RULES = RFC5280_CRL_RULES.concat(CNSA2_CRL_RULES);
|
|
|
4929
4929
|
* for YY >= 50 and 20YY otherwise, so every UTCTime names a year inside 1950..2049 and only the
|
|
4930
4930
|
* GeneralizedTime direction of that clause can be broken. */
|
|
4931
4931
|
var OID_SIGNING_TIME = oid.byName("signingTime");
|
|
4932
|
+
/** @internal The two identifiers RFC 6211's attribute is assigned, both read: the body's PKCS 9 leaf
|
|
4933
|
+
* and the S/MIME attribute arc value the IANA registry and the update draft give it. */
|
|
4934
|
+
var OID_ALG_PROTECTION = oid.byName("cmsAlgorithmProtection");
|
|
4935
|
+
var OID_ALG_PROTECTION_AA = oid.byName("cmsAlgorithmProtect");
|
|
4932
4936
|
var OID_SIGNED_DATA = oid.byName("signedData");
|
|
4933
4937
|
|
|
4934
4938
|
/** @internal The content type decides which of these rows apply, and the parser sets `contentType`
|
|
4935
4939
|
* as an own property on every content it builds. A content type that carries no signers has no own
|
|
4936
4940
|
* `signerInfos`, so testing for that field answers from whatever the prototype chain holds. */
|
|
4937
4941
|
function _isSignedData(cms) { return cms.contentType === OID_SIGNED_DATA; }
|
|
4942
|
+
var OID_AUTH_DATA = oid.byName("authData");
|
|
4943
|
+
/** @internal The other content type whose originator carries attributes, and the one whose algorithm
|
|
4944
|
+
* fields nothing else covers: RFC 5652 sec. 9.2 makes the MAC input the DER encoding of `authAttrs`
|
|
4945
|
+
* alone, so `digestAlgorithm` and `macAlgorithm` sit outside it. */
|
|
4946
|
+
function _isAuthData(cms) { return cms.contentType === OID_AUTH_DATA; }
|
|
4938
4947
|
var OID_COUNTERSIGNATURE = oid.byName("countersignature");
|
|
4939
4948
|
/** @internal Every SignerInfo a message carries: its own signers, and the countersignatures their
|
|
4940
4949
|
* unsigned attributes hold, which sec. 11.4 defines as SignerInfo values and which carry their own
|
|
@@ -4969,14 +4978,16 @@ function _cmsAllSignerInfos(cms) {
|
|
|
4969
4978
|
visit(cms.signerInfos, 0, false, 0);
|
|
4970
4979
|
return out;
|
|
4971
4980
|
}
|
|
4972
|
-
function
|
|
4973
|
-
var attrs = signerInfo.signedAttrs;
|
|
4981
|
+
function _cmsAttrInList(attrs, dotted) {
|
|
4974
4982
|
if (!Array.isArray(attrs)) return null;
|
|
4975
4983
|
for (var i = 0; i < attrs.length; i++) {
|
|
4976
4984
|
if (attrs[i].type === dotted) return attrs[i];
|
|
4977
4985
|
}
|
|
4978
4986
|
return null;
|
|
4979
4987
|
}
|
|
4988
|
+
function _cmsSignedAttr(signerInfo, dotted) {
|
|
4989
|
+
return _cmsAttrInList(signerInfo.signedAttrs, dotted);
|
|
4990
|
+
}
|
|
4980
4991
|
/** @internal Whether two DIGEST AlgorithmIdentifiers name one algorithm, under RFC 5754 sec. 2. The
|
|
4981
4992
|
* rule lives in the schema module because the set this row reads is also WRITTEN there, by the verb
|
|
4982
4993
|
* that adds a countersigner's digest to it, and a set written under one rule and read under another
|
|
@@ -5078,8 +5089,50 @@ var RFC5652_CMS_RULES = [
|
|
|
5078
5089
|
},
|
|
5079
5090
|
];
|
|
5080
5091
|
|
|
5081
|
-
|
|
5082
|
-
|
|
5092
|
+
/** @internal RFC 8933 updates RFC 5652 rather than restating it, so its rows are their own profile:
|
|
5093
|
+
* `pki.lint.cms(der, { profile: "rfc8933" })` asks only what that document added. */
|
|
5094
|
+
var RFC8933_CMS_RULES = [
|
|
5095
|
+
{
|
|
5096
|
+
id: "lint/rfc8933/algorithm-protection-absent", severity: "notice", source: "rfc8933", citation: "RFC 8933 4.1",
|
|
5097
|
+
/** @internal "the originator of a signed-data content type that includes signed attributes
|
|
5098
|
+
* SHOULD include the CMSAlgorithmProtection attribute [RFC6211] as one of the signed
|
|
5099
|
+
* attributes. Likewise, the originator of an authenticated-data content type that includes
|
|
5100
|
+
* authenticated attributes SHOULD include the CMSAlgorithmProtection attribute [RFC6211] as one
|
|
5101
|
+
* of the authenticated attributes." RFC 9882 sec. 4 and RFC 9814 sec. 5 repeat the signed-data
|
|
5102
|
+
* half for ML-DSA and SLH-DSA. Both content types are read, and the authenticated-data one is
|
|
5103
|
+
* where the exposure is widest: sec. 9.2 makes the MAC input `authAttrs` alone, so the two outer
|
|
5104
|
+
* algorithm fields are covered by nothing. The SHOULD is conditioned on attributes being
|
|
5105
|
+
* present, so an originator that signs or authenticates the content directly has nowhere to
|
|
5106
|
+
* carry it and draws no finding. Both identifiers count, since the two spell one attribute. A
|
|
5107
|
+
* notice rather than a warning: it is a SHOULD, and most CMS in the field carries no such
|
|
5108
|
+
* attribute. */
|
|
5109
|
+
message: "an originator with signed or authenticated attributes carries no CMSAlgorithmProtection attribute, which this section says it should include",
|
|
5110
|
+
appliesTo: function (cms) { return _isSignedData(cms) || _isAuthData(cms); },
|
|
5111
|
+
check: function (cms) {
|
|
5112
|
+
var out = [];
|
|
5113
|
+
if (_isAuthData(cms)) {
|
|
5114
|
+
var authAttrs = cms.authAttrs;
|
|
5115
|
+
if (!Array.isArray(authAttrs) || !authAttrs.length) return null;
|
|
5116
|
+
if (_cmsAttrInList(authAttrs, OID_ALG_PROTECTION) ||
|
|
5117
|
+
_cmsAttrInList(authAttrs, OID_ALG_PROTECTION_AA)) return null;
|
|
5118
|
+
return { context: { placement: "authAttrs" } };
|
|
5119
|
+
}
|
|
5120
|
+
_cmsAllSignerInfos(cms).forEach(function (entry) {
|
|
5121
|
+
if (!Array.isArray(entry.signerInfo.signedAttrs) || !entry.signerInfo.signedAttrs.length) return;
|
|
5122
|
+
if (_cmsSignedAttr(entry.signerInfo, OID_ALG_PROTECTION) ||
|
|
5123
|
+
_cmsSignedAttr(entry.signerInfo, OID_ALG_PROTECTION_AA)) return;
|
|
5124
|
+
out.push({ context: { signerIndex: entry.signerIndex, countersignature: entry.countersignature } });
|
|
5125
|
+
});
|
|
5126
|
+
return out.length ? out : null;
|
|
5127
|
+
},
|
|
5128
|
+
},
|
|
5129
|
+
];
|
|
5130
|
+
|
|
5131
|
+
var CMS_PROFILES = Object.assign(Object.create(null), {
|
|
5132
|
+
"rfc5652": RFC5652_CMS_RULES,
|
|
5133
|
+
"rfc8933": RFC8933_CMS_RULES,
|
|
5134
|
+
});
|
|
5135
|
+
var ALL_CMS_RULES = RFC5652_CMS_RULES.concat(RFC8933_CMS_RULES);
|
|
5083
5136
|
|
|
5084
5137
|
/** @internal The RFC 3161 profile is the shortest of the three, for the reason the CMS one is
|
|
5085
5138
|
* short: `schema.tsp.parseToken` refuses nearly everything sec. 2.4.2 requires of a token before a
|
|
@@ -6978,9 +7031,10 @@ function _ingestCrl(input) {
|
|
|
6978
7031
|
* `lint/rfc2986/extension-request-ambiguous`; every one of them is read, so nothing a later
|
|
6979
7032
|
* attribute asks for escapes the rows below.
|
|
6980
7033
|
*
|
|
6981
|
-
* @opts profile `"rfc2986"` for the structural rows alone,
|
|
6982
|
-
*
|
|
6983
|
-
*
|
|
7034
|
+
* @opts profile `"rfc2986"` for the structural rows alone, `"cabf-tls"` to add the TLS rows, or
|
|
7035
|
+
* `"rfc9883"` for the rows about a request carrying a possession statement
|
|
7036
|
+
* (default runs the structural rows and the rfc9883 rows). A certificate, CRL
|
|
7037
|
+
* or OCSP profile name is refused rather than run against a request.
|
|
6984
7038
|
* @opts severity Suppress findings below this floor (default `"notice"`). `counts` and `worst`
|
|
6985
7039
|
* always reflect the complete, unfiltered result.
|
|
6986
7040
|
* @example
|
|
@@ -7217,8 +7271,9 @@ function _ingestCms(input) {
|
|
|
7217
7271
|
* `pki.cms.verify`'s, and the certificates a message carries are linted with
|
|
7218
7272
|
* `pki.lint.certificate`.
|
|
7219
7273
|
*
|
|
7220
|
-
* @opts profile `"rfc5652"`,
|
|
7221
|
-
* profile name is refused rather than run against a
|
|
7274
|
+
* @opts profile `"rfc5652"`, or `"rfc8933"` for the rows that update it (default runs both).
|
|
7275
|
+
* A certificate, CRL or OCSP profile name is refused rather than run against a
|
|
7276
|
+
* message.
|
|
7222
7277
|
* @opts severity Suppress findings below this floor (default `"notice"`). `counts` and `worst`
|
|
7223
7278
|
* always reflect the complete, unfiltered result.
|
|
7224
7279
|
* @example
|
|
@@ -7517,10 +7572,11 @@ function rules(profile, artifact) {
|
|
|
7517
7572
|
* and `"default"`, are not among them.
|
|
7518
7573
|
* @example
|
|
7519
7574
|
* pki.lint.profiles();
|
|
7520
|
-
* // -> ["rfc5280", "rfc9881", "rfc9909", "rfc9935", "
|
|
7521
|
-
* // "
|
|
7522
|
-
* // "nist-pqc-transition", "cnsa-2.0", "rfc2986",
|
|
7523
|
-
* // "rfc5652", "
|
|
7575
|
+
* // -> ["rfc5280", "rfc9881", "rfc9909", "rfc9935", "rfc9763", "rfc7633", "x509-altsig",
|
|
7576
|
+
* // "cabf-tls", "cabf-smime", "cabf-cs", "etsi-qc", "mozilla-root", "chrome-root",
|
|
7577
|
+
* // "microsoft-root", "apple-root", "nist-pqc-transition", "cnsa-2.0", "rfc2986",
|
|
7578
|
+
* // "rfc9883", "rfc5280-crl", "rfc6960", "rfc5019", "rfc5652", "rfc8933", "rfc3161",
|
|
7579
|
+
* // "rfc5755"]
|
|
7524
7580
|
*/
|
|
7525
7581
|
/** @internal `cabf-tls` names the certificate profile in this list and is not repeated for the
|
|
7526
7582
|
* request form: `pki.lint.csr` takes the same name and runs those rows against what a request
|
package/lib/oid.js
CHANGED
|
@@ -232,7 +232,14 @@ var FAMILIES = Object.assign(Object.create(null), {
|
|
|
232
232
|
pkcs9: { base: [1, 2, 840, 113549, 1, 9], of: {
|
|
233
233
|
emailAddress: 1, contentType: 3, messageDigest: 4, signingTime: 5,
|
|
234
234
|
countersignature: 6, challengePassword: 7, extensionRequest: 14,
|
|
235
|
-
smimeCapabilities: 15, friendlyName: 20, localKeyId: 21
|
|
235
|
+
smimeCapabilities: 15, friendlyName: 20, localKeyId: 21,
|
|
236
|
+
/** @internal RFC 6211 sec. 2 and Appendix A both state the CMS algorithm protection attribute
|
|
237
|
+
* here, directly under PKCS 9, and that is the value OpenSSL and Bouncy Castle register. The
|
|
238
|
+
* same document's sec. 4 says "All identifiers are assigned out of the S/MIME OID arc", which
|
|
239
|
+
* the IANA registry, errata 9144 and 9145, and draft-ietf-lamps-rfc6211-update-03 read as
|
|
240
|
+
* `1.2.840.113549.1.9.16.2.52`, registered beside it as `cmsAlgorithmProtect`. Both are
|
|
241
|
+
* accepted on parse, because a mismatched identifier is equivalent to no protection. */
|
|
242
|
+
cmsAlgorithmProtection: 52 } },
|
|
236
243
|
|
|
237
244
|
keyPackageContentTypes: { base: [2, 16, 840, 1, 101, 2, 1, 2, 78], of: { aKeyPackage: 5 } },
|
|
238
245
|
|
|
@@ -324,6 +331,9 @@ var FAMILIES = Object.assign(Object.create(null), {
|
|
|
324
331
|
cmcUnsignedData: 34,
|
|
325
332
|
binarySigningTime: 46,
|
|
326
333
|
asymmDecryptKeyID: 54, relatedCertRequest: 60,
|
|
334
|
+
/** @internal The second value the CMS algorithm protection attribute is identified by. See
|
|
335
|
+
* `pkcs9.cmsAlgorithmProtection` for which document states which, and why both are read. */
|
|
336
|
+
cmsAlgorithmProtect: 52,
|
|
327
337
|
certificationRequestInfoTemplate: 61, extensionReqTemplate: 62 } },
|
|
328
338
|
|
|
329
339
|
ansiX962: { base: [1, 2, 840, 10045], of: {
|
package/lib/path-validate.js
CHANGED
|
@@ -945,16 +945,20 @@ function _flagSet(callerValue, anchorFlags, name) {
|
|
|
945
945
|
return anchorFlags !== null && anchorFlags[name] === true;
|
|
946
946
|
}
|
|
947
947
|
|
|
948
|
-
|
|
948
|
+
/** @internal `policySwitches` carries the three sec. 6.1.1 inputs as the door read and typed them.
|
|
949
|
+
* They are not read off `params` here: normalizing an anchor runs caller accessors for its name,
|
|
950
|
+
* key and algorithm, so an options object the caller still holds can carry a different value by
|
|
951
|
+
* the time this runs, and the state would be built from a value nothing typed. */
|
|
952
|
+
function initialize(certs, params, seeds, anchor, ta, policySwitches) {
|
|
949
953
|
var n = certs.length;
|
|
950
|
-
var state = _initialState(certs, params, seeds, anchor, n, ta);
|
|
954
|
+
var state = _initialState(certs, params, seeds, anchor, n, ta, policySwitches);
|
|
951
955
|
/** @internal The root is the depth-zero anyPolicy node, and it is indexed like any other, so the
|
|
952
956
|
* "is there a node at this depth for this policy" lookup answers for depth zero too. */
|
|
953
957
|
policyLayer(state, 0)[OID.anyPolicy] = state.validPolicyGraph;
|
|
954
958
|
return state;
|
|
955
959
|
}
|
|
956
960
|
|
|
957
|
-
function _initialState(certs, params, seeds, anchor, n, ta) {
|
|
961
|
+
function _initialState(certs, params, seeds, anchor, n, ta, policySwitches) {
|
|
958
962
|
/** @internal The anchor's constraints are read HERE rather than merged into a copy of `params`,
|
|
959
963
|
* because a copy answers only for the own properties the copy loop enumerated. A caller option
|
|
960
964
|
* defined non-enumerably would be dropped by the copy and the anchor would then WIDEN the
|
|
@@ -969,9 +973,9 @@ function _initialState(certs, params, seeds, anchor, n, ta) {
|
|
|
969
973
|
minRsaModulusBits: params.minRsaModulusBits !== undefined ? params.minRsaModulusBits : constants.LIMITS.RSA_MIN_MODULUS_BITS,
|
|
970
974
|
permittedGenerations: intrinsic.filter(seeds.permittedGenerations, function (g) { return g.length > 0; }),
|
|
971
975
|
excludedSubtrees: seeds.excluded,
|
|
972
|
-
explicitPolicy: _flagSet(
|
|
973
|
-
inhibitAnyPolicy: _flagSet(
|
|
974
|
-
policyMapping: _flagSet(
|
|
976
|
+
explicitPolicy: _flagSet(policySwitches.initialExplicitPolicy, ta.policyFlags, "requireExplicitPolicy") ? 0 : n + 1,
|
|
977
|
+
inhibitAnyPolicy: _flagSet(policySwitches.initialAnyPolicyInhibit, ta.policyFlags, "inhibitAnyPolicy") ? 0 : n + 1,
|
|
978
|
+
policyMapping: _flagSet(policySwitches.initialPolicyMappingInhibit, ta.policyFlags, "inhibitPolicyMapping") ? 0 : n + 1,
|
|
975
979
|
workingPublicKeyAlgorithm: anchor.algorithm,
|
|
976
980
|
workingPublicKey: anchor.publicKey,
|
|
977
981
|
workingPublicKeyParameters: anchor.parameters || null,
|
|
@@ -1286,7 +1290,12 @@ function _revocationRow(row, rv) {
|
|
|
1286
1290
|
* The value-carrying options (`time`, `maxPathCerts`,
|
|
1287
1291
|
* `maxPolicyNodes`, `minRsaModulusBits`, the subtree seeds, `userInitialPolicySet`, `requiredEku`)
|
|
1288
1292
|
* are validated at the entry point. A mis-shaped value throws `path/bad-input`,
|
|
1289
|
-
* so it cannot silently go unapplied.
|
|
1293
|
+
* so it cannot silently go unapplied. `requireRevocation` makes the revocation
|
|
1294
|
+
* determination mandatory, failing the path with `path/revocation-undetermined`
|
|
1295
|
+
* where it could not be made. It and the three sec. 6.1.1 policy inputs
|
|
1296
|
+
* (`initialExplicitPolicy`, `initialAnyPolicyInhibit`,
|
|
1297
|
+
* `initialPolicyMappingInhibit`) each take `true`, `false`, `null` or an absent
|
|
1298
|
+
* option, and any other value throws `path/bad-input` for the same reason. Returns `{ valid, revocationChecked, anchorConstraints,
|
|
1290
1299
|
* path, results, workingPublicKey, workingPublicKeyAlgorithm,
|
|
1291
1300
|
* workingPublicKeyParameters, validPolicyGraph, userConstrainedPolicySet }` where `results[i].checks`
|
|
1292
1301
|
* carries a per-check reason code (`path/*`) for every step. Pure and
|
|
@@ -1473,6 +1482,23 @@ async function _validate(path, opts, run, scope) {
|
|
|
1473
1482
|
var certs = path.map(function (c, ci) { return coerceCert(c, "validate: path[" + ci + "]"); });
|
|
1474
1483
|
var n = certs.length;
|
|
1475
1484
|
if (n < 1) throw E("path/empty-path", "validate: the certification path is empty");
|
|
1485
|
+
/** @internal The four switches that turn a requirement on, each read as the boolean it documents
|
|
1486
|
+
* and read HERE, before any anchor is normalized. An anchor's own copies of the three policy
|
|
1487
|
+
* flags are typed where they are copied, and `_flagSet` compares the caller's value with the
|
|
1488
|
+
* anchor's, so typing one side and not the other would accept at the door what the comparison
|
|
1489
|
+
* refuses. Normalizing an anchor reads its name, key and algorithm, and those reads run caller
|
|
1490
|
+
* code when the anchor carries accessors: the typed values below are what every anchor attempt
|
|
1491
|
+
* is handed, so a requirement cannot be turned off between the attempts. */
|
|
1492
|
+
var policySwitches = {
|
|
1493
|
+
requireRevocation: guard.identifier.booleanOption(opts.requireRevocation, E,
|
|
1494
|
+
"path/bad-input", "validate: opts.requireRevocation"),
|
|
1495
|
+
initialExplicitPolicy: guard.identifier.booleanOption(opts.initialExplicitPolicy, E,
|
|
1496
|
+
"path/bad-input", "validate: opts.initialExplicitPolicy"),
|
|
1497
|
+
initialAnyPolicyInhibit: guard.identifier.booleanOption(opts.initialAnyPolicyInhibit, E,
|
|
1498
|
+
"path/bad-input", "validate: opts.initialAnyPolicyInhibit"),
|
|
1499
|
+
initialPolicyMappingInhibit: guard.identifier.booleanOption(opts.initialPolicyMappingInhibit, E,
|
|
1500
|
+
"path/bad-input", "validate: opts.initialPolicyMappingInhibit"),
|
|
1501
|
+
};
|
|
1476
1502
|
/** @internal Captured before any anchor is normalized: an anchor field may be an accessor, and
|
|
1477
1503
|
* that getter runs caller code which could otherwise clear these lists first. */
|
|
1478
1504
|
var callerPermitted = checkedSubtreeSeeds(opts.initialPermittedSubtrees, "initialPermittedSubtrees");
|
|
@@ -1506,6 +1532,14 @@ async function _validate(path, opts, run, scope) {
|
|
|
1506
1532
|
* question the caller asked. Copying `opts.identity` across would have each attempt read
|
|
1507
1533
|
* the caller's own list again, after the awaits the earlier attempts spent. */
|
|
1508
1534
|
if (identityRefs !== null) o.identity = identityRefs;
|
|
1535
|
+
/** @internal The four switches as this call typed them, carried into every attempt rather
|
|
1536
|
+
* than copied from an options object the caller still holds: the tuples above were built by
|
|
1537
|
+
* reading each anchor, which runs the caller's accessors, so a value copied here would be
|
|
1538
|
+
* whatever those left behind. */
|
|
1539
|
+
o.requireRevocation = policySwitches.requireRevocation;
|
|
1540
|
+
o.initialExplicitPolicy = policySwitches.initialExplicitPolicy;
|
|
1541
|
+
o.initialAnyPolicyInhibit = policySwitches.initialAnyPolicyInhibit;
|
|
1542
|
+
o.initialPolicyMappingInhibit = policySwitches.initialPolicyMappingInhibit;
|
|
1509
1543
|
return o;
|
|
1510
1544
|
};
|
|
1511
1545
|
var lastMatched = null, anyMatched = false;
|
|
@@ -1538,7 +1572,7 @@ async function _validate(path, opts, run, scope) {
|
|
|
1538
1572
|
var requiredEku = purposeOpts.requiredEku;
|
|
1539
1573
|
var checkPurpose = purposeOpts.checkPurpose;
|
|
1540
1574
|
|
|
1541
|
-
var state = initialize(certs, opts, seeds, anchor, taConstraints);
|
|
1575
|
+
var state = initialize(certs, opts, seeds, anchor, taConstraints, policySwitches);
|
|
1542
1576
|
state._n = n;
|
|
1543
1577
|
state.requiredEku = requiredEku;
|
|
1544
1578
|
/** @internal The verifier comes from the per-call scope the door built, never from `opts`: the option
|
|
@@ -1548,7 +1582,7 @@ async function _validate(path, opts, run, scope) {
|
|
|
1548
1582
|
var verifierVerify = scope.verify;
|
|
1549
1583
|
var revocationChecker = opts.revocationChecker || null;
|
|
1550
1584
|
var softFail = opts.softFail === true;
|
|
1551
|
-
var requireRevocation =
|
|
1585
|
+
var requireRevocation = policySwitches.requireRevocation;
|
|
1552
1586
|
var failed = false;
|
|
1553
1587
|
var revocationRan = false, revocationWaived = false, revocationUndetermined = false;
|
|
1554
1588
|
var prefixAuthentic = true;
|
|
@@ -3330,7 +3364,7 @@ async function _fetchAiaIssuers(current, aia) {
|
|
|
3330
3364
|
* @opts direction Search direction: `"forward"` (default, from the leaf toward an anchor), `"reverse"` (from an anchor toward the leaf, RFC 4158 sec. 3.1, narrower when the anchor set is small and the leaf fans out), or `"auto"` (pick by first-hop fan-out). Reverse building is pool-only and does not combine with `fetchAia`. Every direction hands each assembled path to `validate`, so the direction changes only search order, never which paths are accepted.
|
|
3331
3365
|
* @opts maxDepth Chain-length depth cap (default `C.LIMITS.PATH_BUILD_MAX_DEPTH`).
|
|
3332
3366
|
* @opts maxCandidatesConsidered Total-work cap on candidate expansions (default `C.LIMITS.PATH_BUILD_MAX_CANDIDATES`).
|
|
3333
|
-
* @opts validate `false` returns the ordered path without validating (pure-builder mode; default `true`).
|
|
3367
|
+
* @opts validate `false` returns the ordered path without validating (pure-builder mode; default `true`). It takes `true`, `false`, `null` or an absent option, and any other value is refused `path/bad-input` at the door rather than read as on.
|
|
3334
3368
|
* @opts fetchAia `true` opts in to AIA caIssuers network fetching of a missing intermediate (default `false`, fully offline). Off unless set; when set, a fetch runs only on a pool miss for a non-anchor-adjacent cert, and (with validation on) every fetched cert still flows through `validate`.
|
|
3335
3369
|
* @opts transport The injectable transport seam (`fn(request) -> Promise<{ status, headers, body }>`); tests drive the fetch offline. With none, the default `pki.transport.https` is used, which fails closed unless `opts.tls` carries trust. SSRF: for a caIssuers URL with a DNS hostname, a custom transport is used only if it declares `fn.blocksPrivateAddresses = true`, which vouches that it refuses (and pins) a resolved private / loopback / link-local / special-use address, as the default transport does. Without that marker a DNS-name AIA URL is fail-closed (skipped) and only an IP-literal URL (validated up front) is fetched; set the marker on your transport when it filters resolved addresses.
|
|
3336
3370
|
* @opts tls The TLS trust for the AIA HTTPS host (`{ anchors, useSystemStore, ... }`). This is distinct from `opts.trustAnchors`, the PKI trust store the path validates against. The default transport refuses an unpinned server.
|
|
@@ -3338,7 +3372,7 @@ async function _fetchAiaIssuers(current, aia) {
|
|
|
3338
3372
|
* @opts maxAiaPerCert Cap on caIssuers URLs tried per certificate (default `C.LIMITS.PATH_AIA_MAX_PER_CERT`).
|
|
3339
3373
|
* @opts aiaTimeout Per-fetch timeout in ms, forwarded to the transport.
|
|
3340
3374
|
* @opts maxResponseBytes Per-fetch response size cap, forwarded to the transport (tightenable downward only).
|
|
3341
|
-
* @opts (validate options) Every `pki.path.validate` option (`requiredEku`, `revocationChecker`, `checkPurpose`, the initial policy inputs, ...) is forwarded unchanged. When `userInitialPolicySet` is supplied, build returns the accepted path whose user-constrained policy set best satisfies it (RFC 4158 sec. 4 forward policy chaining) rather than the first accepted path; `validate` still gates every path, so this changes only which valid path is chosen. The ranking explores more of the candidate graph than a first-accept build, so it is bounded by `maxCandidatesConsidered` like the rest of the search. Reaching that cap means stop looking, not nothing found: if a valid path was already accepted, build returns the best-policy one of those; it reaches `path/build-limit` only when the cap is hit before any valid path is accepted. An `anyPolicy` entry makes the set unconstrained, so ranking is skipped and the first accepted path is returned.
|
|
3375
|
+
* @opts (validate options) Every `pki.path.validate` option (`requiredEku`, `revocationChecker`, `checkPurpose`, the initial policy inputs, ...) is forwarded unchanged. `requireRevocation` and the three policy inputs are typed at this door as well as at `validate`'s, because a build reaches an AIA fetch before it reaches `validate` and a build that assembles no path never calls it. When `userInitialPolicySet` is supplied, build returns the accepted path whose user-constrained policy set best satisfies it (RFC 4158 sec. 4 forward policy chaining) rather than the first accepted path; `validate` still gates every path, so this changes only which valid path is chosen. The ranking explores more of the candidate graph than a first-accept build, so it is bounded by `maxCandidatesConsidered` like the rest of the search. Reaching that cap means stop looking, not nothing found: if a valid path was already accepted, build returns the best-policy one of those; it reaches `path/build-limit` only when the cap is hit before any valid path is accepted. An `anyPolicy` entry makes the set unconstrained, so ranking is skipped and the first accepted path is returned.
|
|
3342
3376
|
* @example
|
|
3343
3377
|
* var pair = await pki.key.generate("Ed25519");
|
|
3344
3378
|
* var pemString = await pki.x509.sign({ subject: "Example Root", subjectPublicKey: await pki.key.export(pair.publicKey),
|
|
@@ -3372,6 +3406,38 @@ async function build(leaf, opts) {
|
|
|
3372
3406
|
* second time and an accessor can answer differently on each read. */
|
|
3373
3407
|
var checkedTransport = guard.identifier.assertCallableOption(opts, "transport", E, "path/bad-input",
|
|
3374
3408
|
"build: opts.transport", "(request) -> Promise<{ status, headers, body }>");
|
|
3409
|
+
/** @internal The switches build forwards to `validate` are typed at THIS door too, because build
|
|
3410
|
+
* reaches the network before it reaches `validate`: with `fetchAia` on it fetches AIA issuers
|
|
3411
|
+
* while assembling a chain, and a build that assembles none returns `path/no-path` with no
|
|
3412
|
+
* `validate` call having read them. A door that only the successful route passes through is not
|
|
3413
|
+
* a door. */
|
|
3414
|
+
var FORWARDED_SWITCHES = ["requireRevocation", "initialExplicitPolicy", "initialAnyPolicyInhibit",
|
|
3415
|
+
"initialPolicyMappingInhibit"];
|
|
3416
|
+
var forwardedSwitches = intrinsic.create(null);
|
|
3417
|
+
intrinsic.forEach(FORWARDED_SWITCHES, function (k) {
|
|
3418
|
+
/** @internal Every one of the four is typed and recorded, including the ones the caller left
|
|
3419
|
+
* out. An absent switch recorded as `false` is the same question answered the same way, and
|
|
3420
|
+
* recording it is what stops a later read from finding one that was not there at this door:
|
|
3421
|
+
* reading each anchor runs the caller's accessors, and an accessor can add an option. */
|
|
3422
|
+
forwardedSwitches[k] = guard.identifier.booleanOption(opts[k], E, "path/bad-input", "build: opts." + k);
|
|
3423
|
+
});
|
|
3424
|
+
/** @internal `validate` defaults ON, so absence means on here and the off form is `false`. A
|
|
3425
|
+
* value outside that set is a caller asking for pure-builder mode in a spelling this verb does
|
|
3426
|
+
* not read, and running the validation they asked to skip is as silent as skipping it. */
|
|
3427
|
+
var doValidateOpt = (opts.validate === undefined || opts.validate === null) ? true
|
|
3428
|
+
: guard.identifier.booleanOption(opts.validate, E, "path/bad-input", "build: opts.validate");
|
|
3429
|
+
/** @internal Pure-builder mode runs no validation, so a requirement asked for alongside it is a
|
|
3430
|
+
* requirement nothing will apply. The combination is refused rather than answered with an
|
|
3431
|
+
* ordered path, the same way an asked-for check that its input cannot satisfy is refused. */
|
|
3432
|
+
if (!doValidateOpt) {
|
|
3433
|
+
intrinsic.forEach(FORWARDED_SWITCHES, function (k) {
|
|
3434
|
+
if (forwardedSwitches[k] === true) {
|
|
3435
|
+
throw E("path/bad-input", "build: opts." + k + " asks for a check that only validation " +
|
|
3436
|
+
"applies, and opts.validate is false, which returns the ordered path without validating; " +
|
|
3437
|
+
"drop one of the two");
|
|
3438
|
+
}
|
|
3439
|
+
});
|
|
3440
|
+
}
|
|
3375
3441
|
var leafCert;
|
|
3376
3442
|
try { leafCert = coerceCert(leaf); }
|
|
3377
3443
|
catch (e) { throw E("path/bad-input", "build: the leaf certificate did not parse", e); }
|
|
@@ -3409,7 +3475,7 @@ async function build(leaf, opts) {
|
|
|
3409
3475
|
// allow:guard-shape-reinlined -- opts.maxDepth here is the path-building chain depth (capped via guard.limits.cap against the PATH_BUILD_MAX_DEPTH ceiling), a different concept from the decode-recursion depth guard.limits.depthCap enforces
|
|
3410
3476
|
var maxDepth = guard.limits.cap(opts.maxDepth, "build: opts.maxDepth", Math.min(constants.LIMITS.PATH_BUILD_MAX_DEPTH, depthCeiling), { E: E, code: "path/bad-input", min: 0, max: depthCeiling });
|
|
3411
3477
|
var maxConsidered = guard.limits.cap(opts.maxCandidatesConsidered, "build: opts.maxCandidatesConsidered", poolCeiling, { E: E, code: "path/bad-input", min: 1 });
|
|
3412
|
-
var doValidate =
|
|
3478
|
+
var doValidate = doValidateOpt;
|
|
3413
3479
|
|
|
3414
3480
|
var direction = opts.direction !== undefined ? opts.direction : "forward";
|
|
3415
3481
|
if (direction !== "forward" && direction !== "reverse" && direction !== "auto") {
|
|
@@ -3448,6 +3514,10 @@ async function build(leaf, opts) {
|
|
|
3448
3514
|
var forwarded = Object.create(null);
|
|
3449
3515
|
intrinsic.forEach(intrinsic.keys(_VALIDATE_OPTS), function (k) {
|
|
3450
3516
|
if (intrinsic.hasOwn(BUILD_ONLY_OPT, k)) return;
|
|
3517
|
+
/** @internal A switch this door typed is forwarded as the value it typed. Reading it off the
|
|
3518
|
+
* options object again would ask a second time, and normalizing an anchor between the two runs
|
|
3519
|
+
* caller accessors, so the value `validate` is handed need not be the one that passed here. */
|
|
3520
|
+
if (intrinsic.hasOwn(forwardedSwitches, k)) { forwarded[k] = forwardedSwitches[k]; return; }
|
|
3451
3521
|
if (k in opts) forwarded[k] = opts[k];
|
|
3452
3522
|
});
|
|
3453
3523
|
if (buildPermRaw != null) forwarded.initialPermittedSubtrees = buildPermSeeds;
|
package/lib/revocation-fetch.js
CHANGED
|
@@ -192,9 +192,7 @@ function _no(why) { return { url: null, why: why }; }
|
|
|
192
192
|
* under a `=== true` test, and the requests would go out carrying no nonce with nothing said. An
|
|
193
193
|
* absent value is the documented default; anything else is a configuration fault. */
|
|
194
194
|
function _booleanOption(value, label) {
|
|
195
|
-
|
|
196
|
-
if (value === true || value === false) return value;
|
|
197
|
-
throw E("path/bad-input", label + " must be true or false, got " + guard.text.showValue(value));
|
|
195
|
+
return guard.identifier.booleanOption(value, E, "path/bad-input", label);
|
|
198
196
|
}
|
|
199
197
|
|
|
200
198
|
/** @internal Whether this transport says it filters the address it resolved. A transport is a
|