@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/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
- var VALUE_READERS = null;
16
- function _readers() {
17
- if (VALUE_READERS !== null) return VALUE_READERS;
18
- var asn1 = _asn1();
19
- var m = _create(null), R = asn1.read, T = asn1.TAGS;
20
- m[T.BOOLEAN] = R.boolean; m[T.INTEGER] = R.integer; m[T.ENUMERATED] = R.enumerated;
21
- m[T.BIT_STRING] = R.bitString; m[T.OCTET_STRING] = R.octetString; m[T.NULL] = R.nullValue;
22
- m[T.OBJECT_IDENTIFIER] = R.oid; m[T.UTC_TIME] = R.time; m[T.GENERALIZED_TIME] = R.time;
23
- m[T.NUMERIC_STRING] = R.numericString;
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) { m[t] = R.string; });
26
- VALUE_READERS = m;
27
- return VALUE_READERS;
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 guard-shape-reinlined
52
- // @guard-shape _ANY_VALUE_READERS\[
53
- function element(node, E, code, label) {
54
- if (node.tagClass === "universal" && node.tagNumber === 0) {
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
- var T = _asn1().TAGS;
58
- if (node.constructed) {
59
- if (node.tagClass === "universal" && node.tagNumber !== T.SEQUENCE && node.tagNumber !== T.SET) {
60
- throw E(code, label + " of universal constructed type " + node.tagNumber + " has no strict DER structure validator here");
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 = node.children;
63
- for (var i = 0; i < kids.length; i++) element(kids[i], E, code, label);
64
- if (node.tagClass === "universal" && node.tagNumber === T.SET && !setOrderOk(kids)) {
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 (node.tagClass === "universal") {
70
- var reader = _readers()[node.tagNumber];
71
- if (!reader) throw E(code, label + " of universal type " + node.tagNumber + " has no strict DER content validator here");
72
- try { reader(node); }
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 = _asn1().decode(content); }
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
  });
@@ -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 /charCodeAt\s*\(\s*[\w$]+\s*,\s*[\w$]+\s*\)\s*>\s*(?:0x7f|127)\b/ */
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++) {
@@ -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 /\.\s*blocksPrivateAddresses\s*===\s*true/ */
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
- if (h.length === 0) return false;
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 /([\w$]+)\s*&&\s*\1\.message\s*\?\s*\1\.message\s*:|([\w$]+)\.message\s*\|\|\s*\2\b/ */
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
- if (m === undefined || m === null) return _thrownFallback(e);
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 _cmsSignedAttr(signerInfo, dotted) {
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
- var CMS_PROFILES = Object.assign(Object.create(null), { "rfc5652": RFC5652_CMS_RULES });
5082
- var ALL_CMS_RULES = RFC5652_CMS_RULES;
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, or `"cabf-tls"` to add the TLS rows
6982
- * (default runs the structural rows). A certificate, CRL or OCSP profile name is
6983
- * refused rather than run against a request.
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"`, which is also what the default runs. A certificate, CRL or OCSP
7221
- * profile name is refused rather than run against a message.
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", "cabf-tls", "cabf-smime", "cabf-cs",
7521
- * // "etsi-qc", "mozilla-root", "chrome-root", "microsoft-root", "apple-root",
7522
- * // "nist-pqc-transition", "cnsa-2.0", "rfc2986", "rfc5280-crl", "rfc6960", "rfc5019",
7523
- * // "rfc5652", "rfc3161", "rfc5755"]
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: {