@blamejs/pki 0.8.52 → 0.8.54
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +37 -0
- package/README.md +2 -2
- package/lib/asn1-der.js +112 -4
- package/lib/cmc-build.js +3 -3
- package/lib/cmp-session.js +2 -2
- package/lib/cmp-verify.js +2 -2
- package/lib/cms-decrypt.js +40 -3
- package/lib/cms-encrypt.js +64 -2
- package/lib/cms-sign.js +67 -2
- package/lib/cms-verify.js +148 -24
- package/lib/crmf-sign.js +1 -1
- package/lib/ct.js +3 -2
- package/lib/guard-der.js +118 -29
- package/lib/guard-encoding.js +2 -1
- package/lib/guard-identifier.js +29 -1
- package/lib/guard-name.js +6 -3
- package/lib/guard-text.js +6 -2
- 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 +2 -1
- package/lib/schema-cms.js +377 -6
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/guard-der.js
CHANGED
|
@@ -8,23 +8,42 @@ var _forEach = require("./guard-intrinsic").forEach;
|
|
|
8
8
|
var _sizeOf = require("./guard-intrinsic").sizeOf;
|
|
9
9
|
var _create = require("./guard-intrinsic").create;
|
|
10
10
|
var _isBuffer = require("./guard-intrinsic").isBuffer;
|
|
11
|
+
var _isArray = require("./guard-intrinsic").isArray;
|
|
11
12
|
|
|
12
13
|
var asn1 = null;
|
|
13
14
|
function _asn1() { if (asn1 === null) asn1 = require("./asn1-der"); return asn1; }
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
16
|
+
/** @internal Everything this file needs from the codec, captured in ONE step on first use: the two
|
|
17
|
+
* functions it calls, the two tag NUMBERS it compares against, and the reader table. Read per call
|
|
18
|
+
* instead, each is a property of a module object anything sharing the process can assign to, and
|
|
19
|
+
* the verdict follows the assignment: `reservedUniversalTag` replaced by a function answering
|
|
20
|
+
* `true` made `tlv` refuse a valid NULL, and `TAGS.SEQUENCE` reassigned made both `element` and
|
|
21
|
+
* `tlv` refuse a valid empty SEQUENCE. The tag numbers are copied out rather than the table kept,
|
|
22
|
+
* because freezing a record holding a reference leaves what it points at writable. One step rather
|
|
23
|
+
* than two because two initialize at different first calls, and the gap between them was itself a
|
|
24
|
+
* window: warming the codec through a context node left the readers uncaptured, and `read.nullValue`
|
|
25
|
+
* replaced in between turned a NULL with content from refused into accepted. The capture is lazy
|
|
26
|
+
* because the codec requires this file, so neither can be required at the other's load. */
|
|
27
|
+
var CODEC = null;
|
|
28
|
+
function _codec() {
|
|
29
|
+
if (CODEC !== null) return CODEC;
|
|
30
|
+
var m = _asn1();
|
|
31
|
+
var R = m.read, T = m.TAGS;
|
|
32
|
+
var readers = _create(null);
|
|
33
|
+
readers[T.BOOLEAN] = R.boolean; readers[T.INTEGER] = R.integer; readers[T.ENUMERATED] = R.enumerated;
|
|
34
|
+
readers[T.BIT_STRING] = R.bitString; readers[T.OCTET_STRING] = R.octetString;
|
|
35
|
+
readers[T.NULL] = R.nullValue; readers[T.OBJECT_IDENTIFIER] = R.oid; readers[T.UTC_TIME] = R.time;
|
|
36
|
+
readers[T.GENERALIZED_TIME] = R.time; readers[T.NUMERIC_STRING] = R.numericString;
|
|
24
37
|
_forEach([T.UTF8_STRING, T.PRINTABLE_STRING, T.IA5_STRING, T.TELETEX_STRING, T.VISIBLE_STRING,
|
|
25
|
-
T.BMP_STRING, T.UNIVERSAL_STRING], function (t) {
|
|
26
|
-
|
|
27
|
-
|
|
38
|
+
T.BMP_STRING, T.UNIVERSAL_STRING], function (t) { readers[t] = R.string; });
|
|
39
|
+
CODEC = _freeze({
|
|
40
|
+
decode: m.decode,
|
|
41
|
+
reservedUniversalTag: m.reservedUniversalTag,
|
|
42
|
+
SEQUENCE: T.SEQUENCE,
|
|
43
|
+
SET: T.SET,
|
|
44
|
+
readers: _freeze(readers),
|
|
45
|
+
});
|
|
46
|
+
return CODEC;
|
|
28
47
|
}
|
|
29
48
|
|
|
30
49
|
var TAG_CLASS_RANK = _create(null);
|
|
@@ -48,30 +67,69 @@ function setOrderOk(kids) {
|
|
|
48
67
|
return dup ? octetAsc : (octetAsc || tagAsc);
|
|
49
68
|
}
|
|
50
69
|
|
|
51
|
-
// @enforced-by
|
|
52
|
-
// @
|
|
53
|
-
|
|
54
|
-
|
|
70
|
+
// @enforced-by behavioral -- validating a universal element's content through the codec's own reader
|
|
71
|
+
// @enforced-by behavioral -- for its tag has no rename-proof shape: a re-inline builds a table keyed
|
|
72
|
+
// @enforced-by behavioral -- by a TAGS member under any local name, and an anchor on one name goes
|
|
73
|
+
// @enforced-by behavioral -- silently green on a rename. The RED vectors in guard-der.test.js are the
|
|
74
|
+
// @enforced-by behavioral -- guard, and every format parser's strict-DER vectors route through tlv.
|
|
75
|
+
function element(node, E, code, label) { _element(node, E, code, label); }
|
|
76
|
+
|
|
77
|
+
/** @internal Every field of the node is taken ONCE, into the plain record every rule below decides
|
|
78
|
+
* on, every reader is handed, and the SET-order check compares. Read per rule instead, a node whose
|
|
79
|
+
* `tagNumber` answers from a getter clears one check as the tag it is not and is then handled as the
|
|
80
|
+
* tag it is: a node answering 15 and then 5 was ACCEPTED, over four reads. A Proxy saw two reads of
|
|
81
|
+
* each field, the content validator was handed the caller's object rather than what was checked, and
|
|
82
|
+
* a SET member's getter answered the order comparison with a value the member's own validation never
|
|
83
|
+
* saw. The record is TYPED because each field is read as something: the tag becomes a property key at
|
|
84
|
+
* the reader lookup, which runs a `Symbol.toPrimitive` an object carries, and the form decides a
|
|
85
|
+
* branch a truthy non-boolean enters. `decode` answers every field with a plain value for every
|
|
86
|
+
* input it accepts, so this refuses only a node the codec did not produce. The node `tlv` passes
|
|
87
|
+
* holds plain values; what the record closes is the next caller, which hands this a node it decoded
|
|
88
|
+
* elsewhere or built itself. Returned so the caller's walk compares the members it validated. */
|
|
89
|
+
function _element(node, E, code, label) {
|
|
90
|
+
if (!node || typeof node !== "object") throw E(code, label + " must be a decoded DER node");
|
|
91
|
+
var s = {
|
|
92
|
+
tagClass: node.tagClass,
|
|
93
|
+
tagNumber: node.tagNumber,
|
|
94
|
+
constructed: node.constructed,
|
|
95
|
+
content: node.content,
|
|
96
|
+
bytes: node.bytes,
|
|
97
|
+
};
|
|
98
|
+
var kids = s.constructed === true ? node.children : null;
|
|
99
|
+
if (typeof s.tagClass !== "string" || typeof s.tagNumber !== "number" ||
|
|
100
|
+
typeof s.constructed !== "boolean" || (s.constructed && !_isArray(kids))) {
|
|
101
|
+
throw E(code, label + " must be a node this codec decoded, carrying a tag class, a tag number, " +
|
|
102
|
+
"a form, and members when it is constructed");
|
|
103
|
+
}
|
|
104
|
+
var cls = s.tagClass, tag = s.tagNumber, C = _codec();
|
|
105
|
+
if (cls === "universal" && tag === 0) {
|
|
55
106
|
throw E(code, label + " must not use the reserved end-of-contents encoding (tag 0)");
|
|
56
107
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
108
|
+
/** @internal The other reserved rows, asked of the codec's own predicate so the two cannot
|
|
109
|
+
* disagree about which universal tags X.680 Table 1 assigns to no type. The codec door refuses
|
|
110
|
+
* these before a node exists, so this answers for a node that reached here by another route. */
|
|
111
|
+
if (cls === "universal" && C.reservedUniversalTag(tag)) {
|
|
112
|
+
throw E(code, label + " must not use universal tag " + tag +
|
|
113
|
+
", which X.680 Table 1 reserves and assigns to no type");
|
|
114
|
+
}
|
|
115
|
+
if (s.constructed) {
|
|
116
|
+
if (cls === "universal" && tag !== C.SEQUENCE && tag !== C.SET) {
|
|
117
|
+
throw E(code, label + " of universal constructed type " + tag + " has no strict DER structure validator here");
|
|
61
118
|
}
|
|
62
|
-
var kids =
|
|
63
|
-
for (var i = 0; i <
|
|
64
|
-
if (
|
|
119
|
+
var n = kids.length, members = [];
|
|
120
|
+
for (var i = 0; i < n; i++) members[i] = _element(kids[i], E, code, label);
|
|
121
|
+
if (cls === "universal" && tag === C.SET && !setOrderOk(members)) {
|
|
65
122
|
throw E(code, label + " has a SET whose members are in no canonical DER order (X.690 sec. 11.6 / X.680 sec. 8.6)");
|
|
66
123
|
}
|
|
67
|
-
return;
|
|
124
|
+
return s;
|
|
68
125
|
}
|
|
69
|
-
if (
|
|
70
|
-
var reader =
|
|
71
|
-
if (!reader) throw E(code, label + " of universal type " +
|
|
72
|
-
try { reader(
|
|
126
|
+
if (cls === "universal") {
|
|
127
|
+
var reader = C.readers[tag];
|
|
128
|
+
if (!reader) throw E(code, label + " of universal type " + tag + " has no strict DER content validator here");
|
|
129
|
+
try { reader(s); }
|
|
73
130
|
catch (e) { throw E(code, label + " is not a valid DER element for its type", e); }
|
|
74
131
|
}
|
|
132
|
+
return s;
|
|
75
133
|
}
|
|
76
134
|
|
|
77
135
|
// @enforced-by guard-shape-reinlined
|
|
@@ -79,7 +137,7 @@ function element(node, E, code, label) {
|
|
|
79
137
|
function tlv(content, E, code, label) {
|
|
80
138
|
if (!content || _sizeOf(content) === 0) throw E(code, label + " must be a non-empty DER element");
|
|
81
139
|
var node;
|
|
82
|
-
try { node =
|
|
140
|
+
try { node = _codec().decode(content); }
|
|
83
141
|
catch (e) { throw E(code, label + " must be exactly one well-formed DER element (no trailing data)", e); }
|
|
84
142
|
element(node, E, code, label);
|
|
85
143
|
return content;
|
|
@@ -96,6 +154,36 @@ function paramsAbsentOrNull(params) {
|
|
|
96
154
|
return params === null || params === undefined || isNull(params);
|
|
97
155
|
}
|
|
98
156
|
|
|
157
|
+
/** Whether two decoded `AlgorithmIdentifier` records name the same algorithm with the same
|
|
158
|
+
* parameters, comparing the parameters AS THEY ARE: absent equals absent, and an explicit DER NULL
|
|
159
|
+
* is a different identifier from one with no parameters field.
|
|
160
|
+
*
|
|
161
|
+
* This is the comparison for an identifier whose parameters carry meaning. A digest identifier has
|
|
162
|
+
* the opposite rule, because RFC 3370 clause 2.1 and RFC 5754 clause 2 require an implementation to
|
|
163
|
+
* accept both spellings of the same digest, and `schema-cms.sameDigestAlgorithm` grants that. A
|
|
164
|
+
* signature identifier does not: RFC 4055 requires the `*WithRSAEncryption` identifiers to carry a
|
|
165
|
+
* NULL, RFC 5758 and RFC 8410 require the `ecdsaWith*` and the Edwards and post-quantum ones to omit
|
|
166
|
+
* the field, and RSASSA-PSS carries its hash, mask generator, salt length and trailer field there
|
|
167
|
+
* under RFC 4056 clause 2.2. Collapsing the two spellings for one of those is admitting a different
|
|
168
|
+
* algorithm, which is why the grant does not travel.
|
|
169
|
+
*
|
|
170
|
+
* @enforced-by behavioral -- an equality that is too permissive has no rename-proof shape; the RED
|
|
171
|
+
* vector is an identifier pair differing only in an explicit NULL, which must compare unequal here
|
|
172
|
+
* and equal through the digest helper.
|
|
173
|
+
*/
|
|
174
|
+
function sameAlgorithmExactly(a, c) {
|
|
175
|
+
if (!a || !c || a.oid !== c.oid) return false;
|
|
176
|
+
/** @internal Each operand's `parameters` is read ONCE, into the value the absence test and the
|
|
177
|
+
* comparison share. Read twice, the first value decided which branch ran and the second was the
|
|
178
|
+
* one compared, so a getter answering a DER NULL and then `null` made an algorithm carrying
|
|
179
|
+
* parameters compare EQUAL to one carrying none. */
|
|
180
|
+
var araw = a.parameters, craw = c.parameters;
|
|
181
|
+
var ap = araw === undefined ? null : araw;
|
|
182
|
+
var cp = craw === undefined ? null : craw;
|
|
183
|
+
if (ap === null || cp === null) return ap === cp;
|
|
184
|
+
return _isBuffer(ap) && _isBuffer(cp) && _sizeOf(ap) === _sizeOf(cp) && _compare(ap, cp) === 0;
|
|
185
|
+
}
|
|
186
|
+
|
|
99
187
|
// @enforced-by behavioral -- see paramsAbsentOrNull.
|
|
100
188
|
function assertParams(params, shape, E, code, label) {
|
|
101
189
|
var ok = shape === "null" ? isNull(params) : (shape === "absent"
|
|
@@ -109,4 +197,5 @@ function assertParams(params, shape, E, code, label) {
|
|
|
109
197
|
module.exports = _freeze({
|
|
110
198
|
element: element, tlv: tlv,
|
|
111
199
|
isNull: isNull, paramsAbsentOrNull: paramsAbsentOrNull, assertParams: assertParams,
|
|
200
|
+
sameAlgorithmExactly: sameAlgorithmExactly,
|
|
112
201
|
});
|
package/lib/guard-encoding.js
CHANGED
|
@@ -171,7 +171,8 @@ function _rawError(E) {
|
|
|
171
171
|
* characters passes a URL the rule was meant to refuse.
|
|
172
172
|
*
|
|
173
173
|
* @enforced-by guard-shape-reinlined
|
|
174
|
-
* @guard-shape
|
|
174
|
+
* @guard-shape charCodeAt\s*\(\s*[\w$]+\s*,\s*[\w$]+\s*\)\s*>\s*(?:0x7f|127)\b
|
|
175
|
+
*/
|
|
175
176
|
function isAsciiText(s) {
|
|
176
177
|
if (typeof s !== "string") return false;
|
|
177
178
|
for (var i = 0; i < s.length; i++) {
|
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
|
|
@@ -608,7 +634,8 @@ function assertCallableOption(opts, key, E, code, label, shape) {
|
|
|
608
634
|
* the object, so an accessor further up the chain cannot answer either.
|
|
609
635
|
*
|
|
610
636
|
* @enforced-by guard-shape-reinlined
|
|
611
|
-
* @guard-shape
|
|
637
|
+
* @guard-shape \.\s*blocksPrivateAddresses\s*===\s*true
|
|
638
|
+
*/
|
|
612
639
|
function declaresOwn(obj, name) {
|
|
613
640
|
if (obj === null || obj === undefined) return false;
|
|
614
641
|
var d;
|
|
@@ -619,6 +646,7 @@ function declaresOwn(obj, name) {
|
|
|
619
646
|
|
|
620
647
|
module.exports = intrinsic.freeze({
|
|
621
648
|
assertCallable: assertCallable,
|
|
649
|
+
booleanOption: booleanOption,
|
|
622
650
|
declaresOwn: declaresOwn,
|
|
623
651
|
assertCallableOption: assertCallableOption,
|
|
624
652
|
assertCanonicalOid: assertCanonicalOid,
|
package/lib/guard-name.js
CHANGED
|
@@ -198,6 +198,7 @@ function escapeControlBytes(str) {
|
|
|
198
198
|
var DN_SPECIAL = intrinsic.assign(intrinsic.create(null), { 0x2c: 1, 0x2b: 1, 0x22: 1, 0x5c: 1, 0x3c: 1, 0x3e: 1, 0x3b: 1 });
|
|
199
199
|
|
|
200
200
|
var _hexNibble = require("./guard-encoding").hexNibble;
|
|
201
|
+
var _isAsciiText = require("./guard-encoding").isAsciiText;
|
|
201
202
|
function _isAttrTypeStart(c) { return (c >= 0x41 && c <= 0x5a) || (c >= 0x61 && c <= 0x7a); }
|
|
202
203
|
function _isAttrTypeChar(c) { return _isAttrTypeStart(c) || (c >= 0x30 && c <= 0x39) || c === 0x2d; }
|
|
203
204
|
function _isDigit(c) { return c >= 0x30 && c <= 0x39; }
|
|
@@ -335,10 +336,12 @@ function emailEqual(a, b) {
|
|
|
335
336
|
return lowerAscii(aHost) === lowerAscii(bHost) ? "match" : "no-match";
|
|
336
337
|
}
|
|
337
338
|
|
|
339
|
+
/** @internal An all-ASCII host, asked of the one place that scans for a byte above 0x7f, with the
|
|
340
|
+
* non-empty requirement kept here because a host part is not optional and `isAsciiText` answers
|
|
341
|
+
* `true` of the empty string. */
|
|
342
|
+
// @guard-via guard.encoding.isAsciiText
|
|
338
343
|
function _asciiHost(h) {
|
|
339
|
-
|
|
340
|
-
for (var i = 0; i < h.length; i++) if (_charCodeAt(h, i) > 0x7f) return false;
|
|
341
|
-
return true;
|
|
344
|
+
return h.length > 0 && _isAsciiText(h);
|
|
342
345
|
}
|
|
343
346
|
|
|
344
347
|
// @enforced-by guard-shape-reinlined
|
package/lib/guard-text.js
CHANGED
|
@@ -102,13 +102,17 @@ function keyOf(v) {
|
|
|
102
102
|
* by its type, which is what `showValue` would have said about it anyway.
|
|
103
103
|
*
|
|
104
104
|
* @enforced-by guard-shape-reinlined
|
|
105
|
-
* @guard-shape
|
|
105
|
+
* @guard-shape ([\w$]+)\s*&&\s*\1\.message\s*\?\s*\1\.message\s*:|([\w$]+)\.message\s*\|\|\s*\2\b
|
|
106
|
+
*/
|
|
106
107
|
function describeThrown(e) {
|
|
107
108
|
var m;
|
|
108
109
|
try { m = e === null || e === undefined ? undefined : e.message; }
|
|
109
110
|
catch (_readFailed) { return _thrownFallback(e); }
|
|
110
111
|
if (typeof m === "string" && m.length > 0) return m;
|
|
111
|
-
|
|
112
|
+
/** @internal An EMPTY message answers nothing readable, so it is described by its type like any
|
|
113
|
+
* other unreadable value. Returned as the empty string it was, it left the sentence built around
|
|
114
|
+
* it ending at its colon with nothing after it, which tells a reader less than the type does. */
|
|
115
|
+
if (m === undefined || m === null || m === "") return _thrownFallback(e);
|
|
112
116
|
try {
|
|
113
117
|
if (typeof m === "symbol") return intrinsic.String(m);
|
|
114
118
|
return "" + m;
|
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: {
|