@blamejs/pki 0.1.21 → 0.1.23

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/schema-cms.js CHANGED
@@ -9,30 +9,39 @@
9
9
  * @slug cms
10
10
  *
11
11
  * @intro
12
- * CMS handling per RFC 5652 (§3 ContentInfo envelope). `parse` turns a DER or PEM
13
- * (`CMS`) message into a structured object and is an OID-dispatch envelope
14
- * ContentInfo reads its `contentType` and structurally decodes SignedData (§5),
15
- * EnvelopedData (§6, with all five RecipientInfo kinds key-transport,
16
- * key-agreement per RFC 5753, KEK, password, and other), and EncryptedData (§8);
17
- * the remaining PKCS#7 content types are recognized and rejected with a precise
18
- * `cms/unsupported-content-type` rather than a generic unknown-format error. A
19
- * SignedData surfaces its version, digest algorithms, encapsulated content,
20
- * certificate / CRL sets, and signer infos; an EnvelopedData its recipient infos
21
- * and encrypted content info; an EncryptedData its encrypted content info.
12
+ * CMS handling per RFC 5652 (sec. 3 ContentInfo envelope). `parse` turns a DER or PEM
13
+ * (`CMS`) message into a structured object and is an OID-dispatch envelope --
14
+ * ContentInfo reads its `contentType` and structurally decodes SignedData (sec. 5),
15
+ * EnvelopedData (sec. 6, with all five RecipientInfo kinds -- key-transport,
16
+ * key-agreement per RFC 5753, KEK, password, and other, including the RFC 9629
17
+ * KEMRecipientInfo carried under `id-ori-kem` with ML-KEM per RFC 9936),
18
+ * EncryptedData (sec. 8), AuthenticatedData (sec. 9), and AuthEnvelopedData (RFC 5083,
19
+ * with RFC 5084 AES-GCM/CCM parameter validation); the remaining PKCS#7 content
20
+ * types are recognized and rejected with a precise `cms/unsupported-content-type`
21
+ * rather than a generic unknown-format error. A SignedData surfaces its version,
22
+ * digest algorithms, encapsulated content, certificate / CRL sets, and signer
23
+ * infos; an EnvelopedData its recipient infos and encrypted content info; an
24
+ * EncryptedData its encrypted content info; an AuthenticatedData its MAC
25
+ * algorithm, optional digest algorithm, authenticated / unauthenticated
26
+ * attributes, and raw `mac`; an AuthEnvelopedData its recipient infos, encrypted
27
+ * content, validated AEAD parameters, and raw `mac`.
22
28
  *
23
29
  * CMS is a signed container: the bytes an external verifier must hash are
24
30
  * surfaced RAW and never re-serialized. `encapContentInfo.eContent` is the raw
25
31
  * content (or `null` for a detached signature); each SignerInfo's `signature` is
26
32
  * raw, and `signedAttrsBytes` is the on-wire `[0]` SignedAttributes TLV so a
27
33
  * verifier can re-tag it to the universal SET the signature is computed over
28
- * (§5.4). Embedded certificates and CRLs are surfaced as raw DER + their outer
29
- * tag, so an obsolete or unknown alternative never fails the parse. DER-only,
30
- * fail-closed.
34
+ * (sec. 5.4) -- `authAttrsBytes` plays the same role for the sec. 9.2 MAC input and the
35
+ * RFC 5083 sec. 2.2 AAD. Embedded certificates and CRLs are surfaced as raw DER +
36
+ * their outer tag, validated against the closed CertificateChoices /
37
+ * RevocationInfoChoice tag sets, so an obsolete alternative never fails the
38
+ * parse but an out-of-set element does. DER-only, fail-closed.
31
39
  *
32
40
  * @card
33
- * Parse DER / PEM CMS SignedData (RFC 5652) into structured, validated fields
34
- * encapsulated content, signer infos, raw signed-attribute bytes for external
35
- * verification, certificates/CRLs kept raw, fail-closed.
41
+ * Parse DER / PEM CMS (RFC 5652 / 5083 / 9629) into structured, validated fields
42
+ * -- signed, enveloped, encrypted, authenticated, and auth-enveloped content;
43
+ * raw attribute bytes for external verification; certificates/CRLs kept raw,
44
+ * fail-closed.
36
45
  */
37
46
 
38
47
  var asn1 = require("./asn1-der");
@@ -51,18 +60,24 @@ var ALGORITHM_IDENTIFIER = pkix.algorithmIdentifier(NS);
51
60
  var ATTRIBUTE = pkix.attribute(NS);
52
61
  var NAME = pkix.name(NS);
53
62
 
54
- // CMSVersion ::= INTEGER the SignedData version is {1,3,4,5} and the SignerInfo
55
- // version is {1,3} (RFC 5652 §5.1, §5.3). Wider accept maps than any other format.
63
+ // CMSVersion ::= INTEGER -- the SignedData version is {1,3,4,5} and the SignerInfo
64
+ // version is {1,3} (RFC 5652 sec. 5.1, sec. 5.3). Wider accept maps than any other format.
56
65
  var SIGNED_DATA_VERSION = pkix.versionReader(NS, { "1": 1, "3": 3, "4": 4, "5": 5 });
57
66
  var SIGNER_VERSION = pkix.versionReader(NS, { "1": 1, "3": 3 });
58
- // EnvelopedData §6.1 (0/2/3/4), EncryptedData §8 (0/2), and the per-RecipientInfo
59
- // versions (RFC 5652 §6.2.16.2.4): ktri {0,2}, kari {3}, kekri {4}, pwri {0}.
67
+ // EnvelopedData sec. 6.1 (0/2/3/4), EncryptedData sec. 8 (0/2), and the per-RecipientInfo
68
+ // versions (RFC 5652 sec. 6.2.1-sec. 6.2.4): ktri {0,2}, kari {3}, kekri {4}, pwri {0}.
60
69
  var ENVELOPED_DATA_VERSION = pkix.versionReader(NS, { "0": 0, "2": 2, "3": 3, "4": 4 });
61
70
  var ENCRYPTED_DATA_VERSION = pkix.versionReader(NS, { "0": 0, "2": 2 });
62
71
  var KTRI_VERSION = pkix.versionReader(NS, { "0": 0, "2": 2 });
63
72
  var KARI_VERSION = pkix.versionReader(NS, { "3": 3 });
64
73
  var KEKRI_VERSION = pkix.versionReader(NS, { "4": 4 });
65
74
  var PWRI_VERSION = pkix.versionReader(NS, { "0": 0 });
75
+ // AuthenticatedData sec. 9.1: computed {0,1,3} (see _expectedAuthDataVersion). The
76
+ // AuthEnvelopedData version (RFC 5083 sec. 2.1) and the KEMRecipientInfo version
77
+ // (RFC 9629 sec. 3) are both a fixed 0.
78
+ var AUTHDATA_VERSION = pkix.versionReader(NS, { "0": 0, "1": 1, "3": 3 });
79
+ var AUTHENV_VERSION = pkix.versionReader(NS, { "0": 0 });
80
+ var KEMRI_VERSION = pkix.versionReader(NS, { "0": 0 });
66
81
 
67
82
  // id-signedData / id-envelopedData / id-encryptedData are the content types this
68
83
  // build structurally decodes; the rest are recognized-and-deferred (a precise
@@ -71,63 +86,238 @@ var PWRI_VERSION = pkix.versionReader(NS, { "0": 0 });
71
86
  var OID_SIGNED_DATA = oid.byName("signedData");
72
87
  var OID_ENVELOPED_DATA = oid.byName("envelopedData");
73
88
  var OID_ENCRYPTED_DATA = oid.byName("encryptedData");
89
+ var OID_AUTH_DATA = oid.byName("authData"); // RFC 5652 sec. 9
90
+ var OID_AUTH_ENVELOPED_DATA = oid.byName("authEnvelopedData"); // RFC 5083
74
91
  var OID_DATA = oid.byName("data");
92
+ var OID_ORI_KEM = oid.byName("kem"); // id-ori-kem (RFC 9629)
93
+
94
+ // AES key-wrap OID -> the KEK length in octets it wraps (RFC 3565). RFC 9629 sec. 3:
95
+ // a KEMRecipientInfo kekLength MUST be consistent with the wrap algorithm, so a
96
+ // recognized wrap pins the exact length; an unrecognized wrap carries no rule.
97
+ var WRAP_KEK_LENGTHS = {};
98
+ WRAP_KEK_LENGTHS[oid.byName("aes128-wrap")] = 16;
99
+ WRAP_KEK_LENGTHS[oid.byName("aes192-wrap")] = 24;
100
+ WRAP_KEK_LENGTHS[oid.byName("aes256-wrap")] = 32;
101
+
102
+ // ML-KEM OID -> the exact ciphertext (kemct) length in octets (FIPS 203). A
103
+ // recognized ML-KEM kem carries a fixed-size ciphertext; any other length can
104
+ // never decapsulate. (The params-absent rule rides the shared oid registry.)
105
+ var KEM_CT_LENGTHS = {};
106
+ KEM_CT_LENGTHS[oid.byName("id-ml-kem-512")] = 768;
107
+ KEM_CT_LENGTHS[oid.byName("id-ml-kem-768")] = 1088;
108
+ KEM_CT_LENGTHS[oid.byName("id-ml-kem-1024")] = 1568;
109
+
110
+ // Recognized AEAD content-encryption OIDs -> the AES-GCM/CCM parameter shape + the
111
+ // legal ICVlen set (RFC 5084). An unrecognized content-encryption OID surfaces its
112
+ // parameters raw with no AEAD validation (registry, not switch).
113
+ var AEAD_GCM_ICVLENS = new Set([12, 13, 14, 15, 16]);
114
+ var AEAD_CCM_ICVLENS = new Set([4, 6, 8, 10, 12, 14, 16]);
115
+ var AEAD_ALGS = {};
116
+ ["aes128-GCM", "aes192-GCM", "aes256-GCM"].forEach(function (n) { AEAD_ALGS[oid.byName(n)] = "gcm"; });
117
+ ["aes128-CCM", "aes192-CCM", "aes256-CCM"].forEach(function (n) { AEAD_ALGS[oid.byName(n)] = "ccm"; });
118
+
75
119
  var DEFERRED = new Set([
76
120
  oid.byName("data"), oid.byName("signedAndEnvelopedData"),
77
- oid.byName("digestedData"), oid.byName("authData"),
121
+ oid.byName("digestedData"),
78
122
  ]);
79
- // The two mandatory signed-attribute types (RFC 5652 §5.3, §11.1/§11.2).
123
+ // The RFC 5652 sec. 11 attribute types with per-context placement + value rules.
80
124
  var OID_CONTENT_TYPE = oid.byName("contentType");
81
125
  var OID_MESSAGE_DIGEST = oid.byName("messageDigest");
126
+ var OID_SIGNING_TIME = oid.byName("signingTime");
127
+ var OID_COUNTERSIGNATURE = oid.byName("countersignature");
128
+
129
+ // RFC 5652 sec. 11.1-sec. 11.4 -- where each sec. 11 attribute type may appear. Keyed by
130
+ // attribute OID; each row names the attribute-set contexts the type MUST NOT
131
+ // appear in: content-type / message-digest / signing-time are signed-or-
132
+ // authenticated only; countersignature is unsigned only.
133
+ var ATTR_FORBIDDEN_IN = {};
134
+ ATTR_FORBIDDEN_IN[OID_CONTENT_TYPE] = { unsigned: true, unauth: true, unprotected: true };
135
+ ATTR_FORBIDDEN_IN[OID_MESSAGE_DIGEST] = { unsigned: true, unauth: true, unprotected: true };
136
+ ATTR_FORBIDDEN_IN[OID_SIGNING_TIME] = { unsigned: true, unauth: true, unprotected: true };
137
+ ATTR_FORBIDDEN_IN[OID_COUNTERSIGNATURE] = { signed: true, auth: true, unauth: true, unprotected: true };
138
+ var ATTR_PLACE_LABELS = {
139
+ signed: "a signed attribute", unsigned: "an unsigned attribute", auth: "an authenticated attribute",
140
+ unauth: "an unauthenticated attribute", unprotected: "an unprotected attribute",
141
+ };
82
142
 
83
- // Enforce the SignedAttributes value constraints (RFC 5652 §5.3, §11) if
84
- // signedAttrs is present it MUST contain exactly one content-type attribute and
85
- // exactly one message-digest attribute, each single-valued. The content-type value
86
- // == eContentType and messageDigest == the actual content hash are §5.6
87
- // VERIFICATION concerns (surfaced, checked by the verifier), not structural-parse
88
- // concerns.
89
- function _checkSignedAttrs(attrs) {
143
+ // Enforce the sec. 11 placement rows on one parsed attribute set. `place` names the
144
+ // context the set occupies (signed / unsigned / auth / unauth / unprotected).
145
+ function _checkAttrPlacement(attrs, place) {
146
+ for (var i = 0; i < attrs.length; i++) {
147
+ var row = ATTR_FORBIDDEN_IN[attrs[i].type];
148
+ if (row && row[place]) {
149
+ throw NS.E("cms/misplaced-attr", "the " + (oid.name(attrs[i].type) || attrs[i].type) +
150
+ " attribute must not be " + ATTR_PLACE_LABELS[place] + " (RFC 5652 sec. 11)");
151
+ }
152
+ }
153
+ }
154
+
155
+ // Enforce the content-binding-attribute value constraints (RFC 5652 sec. 11.1-sec. 11.3)
156
+ // shared by SignedData signedAttrs (sec. 5.3), AuthenticatedData authAttrs (sec. 9.1),
157
+ // and a countersignature value's signedAttrs (sec. 11.4): the set MUST contain
158
+ // exactly one message-digest attribute, each sec. 11 attribute is single-valued with
159
+ // valid syntax, and no attribute type repeats. `mode` selects the content-type
160
+ // presence rule -- "content" (sec. 5.3/sec. 9.1) REQUIRES one; "countersig" (sec. 11.4)
161
+ // FORBIDS it (there is no content type for what a countersignature signs). The
162
+ // content-type value == eContentType cross-check is a separate per-build step
163
+ // (it needs the eContentType in scope); messageDigest == the actual content
164
+ // hash is a sec. 5.6 VERIFICATION concern surfaced, not a structural-parse concern.
165
+ function _checkContentBindingAttrs(attrs, mode) {
90
166
  var ct = 0, md = 0, seen = Object.create(null);
91
167
  for (var i = 0; i < attrs.length; i++) {
92
168
  var a = attrs[i];
93
- // RFC 5652 §5.3 a signerInfo MUST NOT include multiple instances of the
169
+ // RFC 5652 sec. 5.3 -- a signerInfo MUST NOT include multiple instances of the
94
170
  // same signed-attribute type (specific codes for the two mandatory types).
95
171
  if (seen[a.type]) {
96
- if (a.type === OID_CONTENT_TYPE) throw NS.E("cms/duplicate-content-type", "signedAttrs must not repeat the content-type attribute");
97
- if (a.type === OID_MESSAGE_DIGEST) throw NS.E("cms/duplicate-message-digest", "signedAttrs must not repeat the message-digest attribute");
98
- throw NS.E("cms/duplicate-signed-attr", "signedAttrs must not include multiple instances of the same attribute type (" + a.type + ")");
172
+ if (a.type === OID_CONTENT_TYPE) throw NS.E("cms/duplicate-content-type", "the attribute set must not repeat the content-type attribute");
173
+ if (a.type === OID_MESSAGE_DIGEST) throw NS.E("cms/duplicate-message-digest", "the attribute set must not repeat the message-digest attribute");
174
+ throw NS.E("cms/duplicate-signed-attr", "the attribute set must not include multiple instances of the same attribute type (" + a.type + ")");
99
175
  }
100
176
  seen[a.type] = true;
101
177
  if (a.type === OID_CONTENT_TYPE) {
102
178
  ct += 1;
103
- if (a.values.length !== 1) throw NS.E("cms/bad-content-type-attr", "the content-type signed attribute must be single-valued");
104
- // ContentType ::= OBJECT IDENTIFIER (RFC 5652 §11.1) validate the value's
179
+ // RFC 5652 sec. 11.4 -- a countersignature's signedAttrs MUST NOT carry a
180
+ // content-type attribute (what it signs has no content type).
181
+ if (mode === "countersig") throw NS.E("cms/misplaced-attr", "a countersignature's signedAttrs must not carry a content-type attribute (RFC 5652 sec. 11.4)");
182
+ if (a.values.length !== 1) throw NS.E("cms/bad-content-type-attr", "the content-type attribute must be single-valued");
183
+ // ContentType ::= OBJECT IDENTIFIER (RFC 5652 sec. 11.1) -- validate the value's
105
184
  // full syntax (tag AND minimal base-128 OID content), not just the tag, so a
106
185
  // truncated / non-minimal subidentifier is rejected here, not at verify time.
107
186
  try { asn1.read.oid(asn1.decode(a.values[0])); }
108
- catch (e) { throw NS.E("cms/bad-content-type-attr", "the content-type signed attribute value must be a valid OBJECT IDENTIFIER", e); }
187
+ catch (e) { throw NS.E("cms/bad-content-type-attr", "the content-type attribute value must be a valid OBJECT IDENTIFIER", e); }
109
188
  } else if (a.type === OID_MESSAGE_DIGEST) {
110
189
  md += 1;
111
- if (a.values.length !== 1) throw NS.E("cms/bad-message-digest-attr", "the message-digest signed attribute must be single-valued");
112
- // MessageDigest ::= OCTET STRING (RFC 5652 §11.2) validate the full syntax.
190
+ if (a.values.length !== 1) throw NS.E("cms/bad-message-digest-attr", "the message-digest attribute must be single-valued");
191
+ // MessageDigest ::= OCTET STRING (RFC 5652 sec. 11.2) -- validate the full syntax.
113
192
  try { asn1.read.octetString(asn1.decode(a.values[0])); }
114
- catch (e) { throw NS.E("cms/bad-message-digest-attr", "the message-digest signed attribute value must be an OCTET STRING", e); }
193
+ catch (e) { throw NS.E("cms/bad-message-digest-attr", "the message-digest attribute value must be an OCTET STRING", e); }
194
+ } else if (a.type === OID_SIGNING_TIME) {
195
+ // RFC 5652 sec. 11.3 -- signing-time MUST be single-valued and its value is
196
+ // SigningTime ::= Time (UTCTime | GeneralizedTime, full syntax validated).
197
+ if (a.values.length !== 1) throw NS.E("cms/bad-signing-time-attr", "the signing-time attribute must be single-valued (RFC 5652 sec. 11.3)");
198
+ try { asn1.read.time(asn1.decode(a.values[0])); }
199
+ catch (e) { throw NS.E("cms/bad-signing-time-attr", "the signing-time attribute value must be a Time (RFC 5652 sec. 11.3)", e); }
115
200
  }
116
201
  }
117
202
  // Duplicates are rejected in the loop (the seen-set); here only presence.
118
- if (ct === 0) throw NS.E("cms/missing-content-type", "signedAttrs must contain a content-type attribute (RFC 5652 §11.1)");
119
- if (md === 0) throw NS.E("cms/missing-message-digest", "signedAttrs must contain a message-digest attribute (RFC 5652 §11.2)");
203
+ if (mode !== "countersig" && ct === 0) throw NS.E("cms/missing-content-type", "the attribute set must contain a content-type attribute (RFC 5652 sec. 11.1)");
204
+ if (md === 0) throw NS.E("cms/missing-message-digest", "the attribute set must contain a message-digest attribute (RFC 5652 sec. 11.2)");
205
+ }
206
+
207
+ // RFC 5652 sec. 5.3 / sec. 9.3 -- when a content-type attribute is present, it MUST
208
+ // be single-valued (sec. 11.1) and its value MUST equal the eContentType (a
209
+ // cross-field consistency both parsed here). Shared by SignedData signedAttrs,
210
+ // AuthenticatedData authAttrs, and AuthEnvelopedData authAttrs -- the single-value
211
+ // rule holds even where content-type is not REQUIRED (RFC 5083), so an
212
+ // expected-first-value-plus-extra set can never surface as ambiguous.
213
+ function _assertContentTypeMatchesAttrs(attrs, eContentType) {
214
+ for (var i = 0; i < attrs.length; i++) {
215
+ if (attrs[i].type !== OID_CONTENT_TYPE) continue;
216
+ if (attrs[i].values.length !== 1) throw NS.E("cms/bad-content-type-attr", "the content-type attribute must be single-valued (RFC 5652 sec. 11.1)");
217
+ var ctv = asn1.read.oid(asn1.decode(attrs[i].values[0]));
218
+ if (ctv !== eContentType) throw NS.E("cms/content-type-mismatch", "the content-type attribute (" + ctv + ") must equal the eContentType (" + eContentType + ") (RFC 5652 sec. 5.3)");
219
+ }
220
+ }
221
+
222
+ // RFC 5652 sec. 5.3 -- an attribute set MUST NOT include multiple instances of the
223
+ // same attribute type. AuthEnvelopedData authAttrs (RFC 5083 sec. 2.1) gets ONLY this
224
+ // duplicate check -- NOT the content-type/message-digest presence rules (RFC 5652
225
+ // sec. 11.1/sec. 11.2 bind only signed-data and authenticated-data), so it must not reuse
226
+ // _checkContentBindingAttrs, which would over-enforce.
227
+ function _checkNoDuplicateAttrs(attrs) {
228
+ var seen = Object.create(null);
229
+ for (var i = 0; i < attrs.length; i++) {
230
+ if (seen[attrs[i].type]) throw NS.E("cms/duplicate-attr", "an attribute set must not include multiple instances of the same attribute type (" + attrs[i].type + ", RFC 5652 sec. 5.3)");
231
+ seen[attrs[i].type] = true;
232
+ }
233
+ }
234
+
235
+ // RFC 5084 -- validate the AES-GCM/CCM content-encryption parameters when the OID
236
+ // is a recognized AEAD (an unknown OID surfaces its parameters raw with no AEAD
237
+ // check). Returns { kind, nonce, icvLen } or null. `macLen` (the AuthEnvelopedData
238
+ // mac length) is cross-checked against the effective ICVlen. The bounded nonce read
239
+ // is the pre-auth surface the oversized-IV class (CVE-2025-15467) targets.
240
+ function _validateAeadParams(alg, macLen) {
241
+ var kind = AEAD_ALGS[alg.oid];
242
+ if (!kind) return null;
243
+ var K = kind.toUpperCase();
244
+ // sec. 3.1/sec. 3.2: the parameters field MUST be present and carry the Parameters SEQUENCE.
245
+ if (alg.parameters === null) throw NS.E("cms/bad-aead-params", "an AES-" + K + " content-encryption algorithm MUST carry its parameters (RFC 5084 sec. 3." + (kind === "gcm" ? "2" : "1") + ")");
246
+ var node;
247
+ try { node = asn1.decode(alg.parameters); }
248
+ catch (e) { throw NS.E("cms/bad-aead-params", "malformed AES-" + K + " parameters", e); }
249
+ if (node.tagClass !== "universal" || node.tagNumber !== T.SEQUENCE || !node.children || node.children.length < 1 || node.children.length > 2) {
250
+ throw NS.E("cms/bad-aead-params", "AES-" + K + " parameters must be a SEQUENCE { aes-nonce, aes-ICVlen DEFAULT 12 } (RFC 5084)");
251
+ }
252
+ var nonce;
253
+ try { nonce = asn1.read.octetString(node.children[0]); }
254
+ catch (e) { throw NS.E("cms/bad-aead-params", "the AEAD aes-nonce must be an OCTET STRING", e); }
255
+ // CCM: aes-nonce is SIZE(7..13) (RFC 5084 sec. 3.1). GCM has no ASN.1 bound; the raw
256
+ // nonce is surfaced (a subarray, never copied into a fixed buffer), bounded by the
257
+ // DER size cap -- a nonzero length is the only structural floor.
258
+ if (kind === "ccm" && (nonce.length < 7 || nonce.length > 13)) throw NS.E("cms/bad-aead-params", "the AES-CCM aes-nonce must be 7..13 octets (RFC 5084 sec. 3.1)");
259
+ if (nonce.length < 1) throw NS.E("cms/bad-aead-params", "the AEAD aes-nonce must be non-empty");
260
+ // aes-ICVlen INTEGER DEFAULT 12.
261
+ var icvLen = 12, icvEncoded = false;
262
+ if (node.children.length === 2) {
263
+ var iv;
264
+ try { iv = asn1.read.integer(node.children[1]); }
265
+ catch (e) { throw NS.E("cms/bad-aead-params", "the AEAD aes-ICVlen must be an INTEGER", e); }
266
+ if (iv < 0n || iv > 16n) throw NS.E("cms/bad-aead-params", "the AEAD aes-ICVlen is out of range");
267
+ icvLen = Number(iv); icvEncoded = true;
268
+ }
269
+ var legal = kind === "gcm" ? AEAD_GCM_ICVLENS : AEAD_CCM_ICVLENS;
270
+ if (!legal.has(icvLen)) throw NS.E("cms/bad-aead-params", "the AES-" + K + " aes-ICVlen " + icvLen + " is not an allowed value (RFC 5084)");
271
+ // X.690 sec. 11.5: a DEFAULT value MUST be omitted in DER -- an encoded ICVlen equal
272
+ // to the default 12 is non-canonical.
273
+ if (icvEncoded && icvLen === 12) throw NS.E("cms/non-canonical-default", "the AEAD aes-ICVlen equal to the DEFAULT 12 MUST be omitted (X.690 sec. 11.5)");
274
+ // RFC 5084 sec. 3.1/sec. 3.2: aes-ICVlen MUST match the AuthEnvelopedData mac length.
275
+ if (macLen != null && icvLen !== macLen) throw NS.E("cms/mac-length-mismatch", "the AEAD aes-ICVlen " + icvLen + " must equal the mac length " + macLen + " (RFC 5084)");
276
+ return { kind: kind, nonce: nonce, icvLen: icvLen };
277
+ }
278
+
279
+ // RFC 5652 sec. 5.3 / sec. 9.1 + RFC 5083 sec. 2.1 -- a signed / authenticated attribute SET
280
+ // MUST be DER encoded even when the enclosing structure was decoded as BER (its
281
+ // bytes feed the sec. 5.4 / sec. 9.2 re-tagged hash / MAC input, so a non-DER TLV makes
282
+ // the surfaced raw bytes unusable for verification). Strictly re-decoding the
283
+ // on-wire TLV rejects any indefinite-length or non-minimal encoding in the
284
+ // subtree; on the strict parse path this always passes.
285
+ function _assertDerEncodedAttrs(node, code) {
286
+ try { asn1.decode(node.bytes); }
287
+ catch (e) { throw NS.E(code, "the attribute set must be DER encoded even inside a BER envelope (RFC 5652 sec. 5.3)", e); }
120
288
  }
121
289
 
122
290
  // A CertificateChoices / RevocationInfoChoice element, surfaced RAW (its DER +
123
- // outer tag) rather than recursively parsed the obsolete CHOICE alternatives
291
+ // outer tag) rather than recursively parsed -- the obsolete CHOICE alternatives
124
292
  // (extendedCertificate, attribute certs, otherRevocationInfo) never fail the
125
293
  // parse, and a caller re-parses a `certificate`/`CertificateList` element itself.
126
294
  function rawElement(item) {
127
295
  return { bytes: item.node.bytes, tagClass: item.node.tagClass, tagNumber: item.node.tagNumber };
128
296
  }
129
297
 
130
- // RFC 5652 §5.1 the exact SignedData CMSVersion, computed from the raw
298
+ // CertificateChoices (RFC 5652 sec. 10.2.2) and RevocationInfoChoice (sec. 10.2.1) are
299
+ // CLOSED CHOICE sets -- Certificate / CertificateList (a universal SEQUENCE) or
300
+ // the listed context tags, every alternative constructed. An element outside
301
+ // the set is structurally malformed even though members are surfaced raw: the
302
+ // [3] / [1] `other` arms ARE the RFC's extension points for unknown formats,
303
+ // so an open tag set is never needed. The tag sets also feed the sec. 5.1 / sec. 6.1
304
+ // version rules, which must never be computed over undefined-type elements.
305
+ var CERT_CHOICE_TAGS = { universal: {}, context: { 0: true, 1: true, 2: true, 3: true } };
306
+ var CRL_CHOICE_TAGS = { universal: {}, context: { 1: true } };
307
+ CERT_CHOICE_TAGS.universal[asn1.TAGS.SEQUENCE] = true;
308
+ CRL_CHOICE_TAGS.universal[asn1.TAGS.SEQUENCE] = true;
309
+ function rawChoiceElement(allowed, code, what) {
310
+ return function (item) {
311
+ var n = item.node;
312
+ var byClass = allowed[n.tagClass];
313
+ if (!n.constructed || !byClass || byClass[n.tagNumber] !== true) {
314
+ throw NS.E(code, what + " element tag is outside the closed CHOICE set (RFC 5652 sec. 10.2.1-sec. 10.2.2)");
315
+ }
316
+ return rawElement(item);
317
+ };
318
+ }
319
+
320
+ // RFC 5652 sec. 5.1 -- the exact SignedData CMSVersion, computed from the raw
131
321
  // CertificateChoices / RevocationInfoChoice outer tags (a certificate `other` is
132
322
  // [3], a v2AttrCert [2], a v1AttrCert [1]; a RevocationInfoChoice `other` is [1]),
133
323
  // the SignerInfo versions, and whether the content type is id-data.
@@ -143,7 +333,7 @@ function _expectedSignedDataVersion(certificates, crls, signerInfos, eContentTyp
143
333
  }
144
334
 
145
335
  // EncapsulatedContentInfo ::= SEQUENCE { eContentType OID,
146
- // eContent [0] EXPLICIT OCTET STRING OPTIONAL } (RFC 5652 §5.2). Absent eContent
336
+ // eContent [0] EXPLICIT OCTET STRING OPTIONAL } (RFC 5652 sec. 5.2). Absent eContent
147
337
  // is a detached signature (surfaced as null); present is the raw content bytes.
148
338
  var ENCAP_CONTENT_INFO = schema.seq([
149
339
  schema.field("eContentType", schema.oidLeaf()),
@@ -159,7 +349,7 @@ var ENCAP_CONTENT_INFO = schema.seq([
159
349
  });
160
350
 
161
351
  // IssuerAndSerialNumber ::= SEQUENCE { issuer Name, serialNumber INTEGER }
162
- // (RFC 5652 §10.2.4). serialNumberHex preserves the DER sign padding.
352
+ // (RFC 5652 sec. 10.2.4). serialNumberHex preserves the DER sign padding.
163
353
  var ISSUER_AND_SERIAL = schema.seq([
164
354
  schema.field("issuer", NAME),
165
355
  schema.field("serialNumber", schema.integerLeaf()),
@@ -175,7 +365,7 @@ var ISSUER_AND_SERIAL = schema.seq([
175
365
  });
176
366
 
177
367
  // SignerIdentifier ::= CHOICE { issuerAndSerialNumber IssuerAndSerialNumber,
178
- // subjectKeyIdentifier [0] IMPLICIT OCTET STRING } (RFC 5652 §5.3) the arm is
368
+ // subjectKeyIdentifier [0] IMPLICIT OCTET STRING } (RFC 5652 sec. 5.3) -- the arm is
179
369
  // disambiguated by tag (universal SEQUENCE vs context [0]).
180
370
  var SIGNER_IDENTIFIER = schema.choice([
181
371
  { when: { tagClass: "universal", tagNumber: asn1.TAGS.SEQUENCE }, schema: ISSUER_AND_SERIAL },
@@ -184,58 +374,88 @@ var SIGNER_IDENTIFIER = schema.choice([
184
374
 
185
375
  // SignerInfo ::= SEQUENCE { version, sid SignerIdentifier, digestAlgorithm,
186
376
  // signedAttrs [0] IMPLICIT OPTIONAL, signatureAlgorithm, signature OCTET STRING,
187
- // unsignedAttrs [1] IMPLICIT OPTIONAL } (RFC 5652 §5.3). signedAttrs/unsignedAttrs
377
+ // unsignedAttrs [1] IMPLICIT OPTIONAL } (RFC 5652 sec. 5.3). signedAttrs/unsignedAttrs
188
378
  // are positional optionals (a required signatureAlgorithm sits between them, so
189
- // they cannot be a trailing block).
190
- var SIGNER_INFO = schema.seq([
191
- schema.field("version", SIGNER_VERSION),
192
- schema.field("sid", SIGNER_IDENTIFIER),
193
- schema.field("digestAlgorithm", ALGORITHM_IDENTIFIER),
194
- schema.optional("signedAttrs", schema.implicitSetOf(0, ATTRIBUTE, { min: 1, code: "cms/bad-signed-attrs", what: "signedAttrs" }), { tag: 0 }),
195
- schema.field("signatureAlgorithm", ALGORITHM_IDENTIFIER),
196
- schema.field("signature", schema.octetString()),
197
- schema.optional("unsignedAttrs", schema.implicitSetOf(1, ATTRIBUTE, { min: 1, code: "cms/bad-unsigned-attrs", what: "unsignedAttrs" }), { tag: 1 }),
198
- ], {
199
- assert: "sequence", code: "cms/bad-signer-info", what: "SignerInfo",
200
- build: function (m) {
201
- var version = m.fields.version.value;
202
- var sidNode = m.fields.sid.node;
203
- var isSkid = sidNode.tagClass === "context" && sidNode.tagNumber === 0;
204
- var sid;
205
- if (isSkid) {
206
- // RFC 5652 §5.3 — a subjectKeyIdentifier sid forces SignerInfo version 3.
207
- if (version !== 3) throw NS.E("cms/bad-signer-version", "a subjectKeyIdentifier signer identifier requires SignerInfo version 3");
208
- sid = { subjectKeyIdentifier: m.fields.sid.value };
209
- } else {
210
- // RFC 5652 §5.3 — an issuerAndSerialNumber sid forces SignerInfo version 1.
211
- if (version !== 1) throw NS.E("cms/bad-signer-version", "an issuerAndSerialNumber signer identifier requires SignerInfo version 1");
212
- sid = m.fields.sid.value.result;
213
- }
214
- var signedAttrs = null, signedAttrsBytes = null;
215
- if (m.fields.signedAttrs.present) {
216
- signedAttrs = m.fields.signedAttrs.value.items.map(function (it) { return it.value.result; });
217
- _checkSignedAttrs(signedAttrs);
218
- // The raw on-wire signedAttrs bytes (leading 0xA0) so a verifier can re-tag to
219
- // a universal SET and reproduce the signed hash (RFC 5652 §5.4).
220
- signedAttrsBytes = m.fields.signedAttrs.node.bytes;
221
- }
222
- return {
223
- version: version,
224
- sid: sid,
225
- digestAlgorithm: m.fields.digestAlgorithm.value.result,
226
- signedAttrs: signedAttrs,
227
- signedAttrsBytes: signedAttrsBytes,
228
- signatureAlgorithm: m.fields.signatureAlgorithm.value.result,
229
- signature: m.fields.signature.value,
230
- unsignedAttrs: m.fields.unsignedAttrs.present ? m.fields.unsignedAttrs.value.items.map(function (it) { return it.value.result; }) : null,
231
- };
232
- },
233
- });
379
+ // they cannot be a trailing block). ONE definition drives both consumers -- the
380
+ // signerInfos of a SignedData (mode "content") and a countersignature attribute
381
+ // value (mode "countersig", RFC 5652 sec. 11.4: syntactically a SignerInfo whose
382
+ // signedAttrs MUST carry message-digest and MUST NOT carry content-type) -- so
383
+ // the two can never diverge structurally.
384
+ function makeSignerInfo(mode) {
385
+ return schema.seq([
386
+ schema.field("version", SIGNER_VERSION),
387
+ schema.field("sid", SIGNER_IDENTIFIER),
388
+ schema.field("digestAlgorithm", ALGORITHM_IDENTIFIER),
389
+ schema.optional("signedAttrs", schema.implicitSetOf(0, ATTRIBUTE, { min: 1, code: "cms/bad-signed-attrs", what: "signedAttrs" }), { tag: 0 }),
390
+ schema.field("signatureAlgorithm", ALGORITHM_IDENTIFIER),
391
+ schema.field("signature", schema.octetString()),
392
+ schema.optional("unsignedAttrs", schema.implicitSetOf(1, ATTRIBUTE, { min: 1, code: "cms/bad-unsigned-attrs", what: "unsignedAttrs" }), { tag: 1 }),
393
+ ], {
394
+ assert: "sequence", code: "cms/bad-signer-info", what: "SignerInfo",
395
+ build: function (m, ctx) {
396
+ var version = m.fields.version.value;
397
+ var sidNode = m.fields.sid.node;
398
+ var isSkid = sidNode.tagClass === "context" && sidNode.tagNumber === 0;
399
+ var sid;
400
+ if (isSkid) {
401
+ // RFC 5652 sec. 5.3 -- a subjectKeyIdentifier sid forces SignerInfo version 3.
402
+ if (version !== 3) throw NS.E("cms/bad-signer-version", "a subjectKeyIdentifier signer identifier requires SignerInfo version 3");
403
+ sid = { subjectKeyIdentifier: m.fields.sid.value };
404
+ } else {
405
+ // RFC 5652 sec. 5.3 -- an issuerAndSerialNumber sid forces SignerInfo version 1.
406
+ if (version !== 1) throw NS.E("cms/bad-signer-version", "an issuerAndSerialNumber signer identifier requires SignerInfo version 1");
407
+ sid = m.fields.sid.value.result;
408
+ }
409
+ var signedAttrs = null, signedAttrsBytes = null;
410
+ if (m.fields.signedAttrs.present) {
411
+ // sec. 5.3 -- signedAttrs MUST be DER even in a BER envelope (feeds the sec. 5.4 hash).
412
+ _assertDerEncodedAttrs(m.fields.signedAttrs.node, "cms/bad-signed-attrs");
413
+ signedAttrs = m.fields.signedAttrs.value.items.map(function (it) { return it.value.result; });
414
+ _checkAttrPlacement(signedAttrs, "signed");
415
+ _checkContentBindingAttrs(signedAttrs, mode);
416
+ // The raw on-wire signedAttrs bytes (leading 0xA0) so a verifier can re-tag to
417
+ // a universal SET and reproduce the signed hash (RFC 5652 sec. 5.4).
418
+ signedAttrsBytes = m.fields.signedAttrs.node.bytes;
419
+ }
420
+ var unsignedAttrs = null;
421
+ if (m.fields.unsignedAttrs.present) {
422
+ unsignedAttrs = m.fields.unsignedAttrs.value.items.map(function (it) { return it.value.result; });
423
+ _checkAttrPlacement(unsignedAttrs, "unsigned");
424
+ // sec. 11.4 -- every countersignature value IS a SignerInfo (validated by
425
+ // content, never accepted on the attribute type alone). Multiple
426
+ // countersignature instances are explicitly permitted here, and a
427
+ // countersignature's own unsignedAttrs may nest further ones -- the
428
+ // recursion is bounded by the decoder's depth cap.
429
+ for (var u = 0; u < unsignedAttrs.length; u++) {
430
+ if (unsignedAttrs[u].type !== OID_COUNTERSIGNATURE) continue;
431
+ for (var v = 0; v < unsignedAttrs[u].values.length; v++) {
432
+ var csNode;
433
+ try { csNode = asn1.decode(unsignedAttrs[u].values[v]); }
434
+ catch (e) { throw NS.E("cms/bad-countersignature", "a countersignature attribute value must be DER (RFC 5652 sec. 11.4)", e); }
435
+ schema.walk(COUNTERSIGNATURE_SIGNER_INFO, csNode, ctx);
436
+ }
437
+ }
438
+ }
439
+ return {
440
+ version: version,
441
+ sid: sid,
442
+ digestAlgorithm: m.fields.digestAlgorithm.value.result,
443
+ signedAttrs: signedAttrs,
444
+ signedAttrsBytes: signedAttrsBytes,
445
+ signatureAlgorithm: m.fields.signatureAlgorithm.value.result,
446
+ signature: m.fields.signature.value,
447
+ unsignedAttrs: unsignedAttrs,
448
+ };
449
+ },
450
+ });
451
+ }
452
+ var SIGNER_INFO = makeSignerInfo("content");
453
+ var COUNTERSIGNATURE_SIGNER_INFO = makeSignerInfo("countersig");
234
454
 
235
455
  // SignedData ::= SEQUENCE { version CMSVersion, digestAlgorithms SET OF,
236
456
  // encapContentInfo, certificates [0] IMPLICIT OPTIONAL, crls [1] IMPLICIT
237
- // OPTIONAL, signerInfos SET OF } (RFC 5652 §5.1). digestAlgorithms and
238
- // signerInfos are min:0 a degenerate certs-only SignedData carries neither.
457
+ // OPTIONAL, signerInfos SET OF } (RFC 5652 sec. 5.1). digestAlgorithms and
458
+ // signerInfos are min:0 -- a degenerate certs-only SignedData carries neither.
239
459
  var SIGNED_DATA = schema.seq([
240
460
  schema.field("version", SIGNED_DATA_VERSION),
241
461
  schema.field("digestAlgorithms", schema.setOf(ALGORITHM_IDENTIFIER, { min: 0, code: "cms/bad-digest-algorithms", what: "digestAlgorithms" })),
@@ -248,35 +468,29 @@ var SIGNED_DATA = schema.seq([
248
468
  build: function (m) {
249
469
  var version = m.fields.version.value;
250
470
  var encapContentInfo = m.fields.encapContentInfo.value.result;
251
- var certificates = m.fields.certificates.present ? m.fields.certificates.value.items.map(rawElement) : [];
252
- var crls = m.fields.crls.present ? m.fields.crls.value.items.map(rawElement) : [];
471
+ var certificates = m.fields.certificates.present ? m.fields.certificates.value.items.map(rawChoiceElement(CERT_CHOICE_TAGS, "cms/bad-certificates", "certificates")) : [];
472
+ var crls = m.fields.crls.present ? m.fields.crls.value.items.map(rawChoiceElement(CRL_CHOICE_TAGS, "cms/bad-crls", "crls")) : [];
253
473
  var signerInfos = m.fields.signerInfos.value.items.map(function (it) { return it.value.result; });
254
474
 
255
- // RFC 5652 §5.3 signedAttrs MAY be omitted only when the content type is
475
+ // RFC 5652 sec. 5.3 -- signedAttrs MAY be omitted only when the content type is
256
476
  // id-data; any other eContentType requires each SignerInfo to carry signedAttrs
257
477
  // (so the content-type + message-digest attributes bind the signature).
258
478
  if (encapContentInfo.eContentType !== OID_DATA) {
259
479
  for (var s = 0; s < signerInfos.length; s++) {
260
- if (signerInfos[s].signedAttrs === null) throw NS.E("cms/missing-signed-attrs", "a SignerInfo must carry signedAttrs when the content type is not id-data (RFC 5652 §5.3)");
480
+ if (signerInfos[s].signedAttrs === null) throw NS.E("cms/missing-signed-attrs", "a SignerInfo must carry signedAttrs when the content type is not id-data (RFC 5652 sec. 5.3)");
261
481
  }
262
482
  }
263
483
 
264
- // RFC 5652 §5.3 when signedAttrs are present, the content-type attribute's
484
+ // RFC 5652 sec. 5.3 -- when signedAttrs are present, the content-type attribute's
265
485
  // value MUST equal the eContentType (a cross-field consistency both parsed
266
486
  // here; a mismatch is an internally-inconsistent SignedData).
267
487
  for (var si = 0; si < signerInfos.length; si++) {
268
- var sa = signerInfos[si].signedAttrs;
269
- if (!sa) continue;
270
- for (var ai = 0; ai < sa.length; ai++) {
271
- if (sa[ai].type !== OID_CONTENT_TYPE) continue;
272
- var ctv = asn1.read.oid(asn1.decode(sa[ai].values[0]));
273
- if (ctv !== encapContentInfo.eContentType) throw NS.E("cms/content-type-mismatch", "the content-type signed attribute (" + ctv + ") must equal the eContentType (" + encapContentInfo.eContentType + ") (RFC 5652 §5.3)");
274
- }
488
+ if (signerInfos[si].signedAttrs) _assertContentTypeMatchesAttrs(signerInfos[si].signedAttrs, encapContentInfo.eContentType);
275
489
  }
276
490
 
277
- // RFC 5652 §5.1 the SignedData CMSVersion is determined by its contents.
491
+ // RFC 5652 sec. 5.1 -- the SignedData CMSVersion is determined by its contents.
278
492
  var expected = _expectedSignedDataVersion(certificates, crls, signerInfos, encapContentInfo.eContentType);
279
- if (version !== expected) throw NS.E("cms/bad-version", "SignedData version " + version + " does not match its contents (RFC 5652 §5.1 requires v" + expected + ")");
493
+ if (version !== expected) throw NS.E("cms/bad-version", "SignedData version " + version + " does not match its contents (RFC 5652 sec. 5.1 requires v" + expected + ")");
280
494
 
281
495
  return {
282
496
  version: version,
@@ -289,12 +503,12 @@ var SIGNED_DATA = schema.seq([
289
503
  },
290
504
  });
291
505
 
292
- // ==== EnvelopedData / EncryptedData (RFC 5652 §68, RFC 5753) ========
506
+ // ==== EnvelopedData / EncryptedData (RFC 5652 sec. 6/sec. 8, RFC 5753) ========
293
507
  var T = asn1.TAGS;
294
508
 
295
509
  // EncryptedContentInfo ::= SEQUENCE { contentType OID, contentEncryptionAlgorithm
296
510
  // AlgorithmIdentifier, encryptedContent [0] IMPLICIT OCTET STRING OPTIONAL } (RFC
297
- // 5652 §6.1). encryptedContent is [0] IMPLICIT (context PRIMITIVE) its content
511
+ // 5652 sec. 6.1). encryptedContent is [0] IMPLICIT (context PRIMITIVE) -- its content
298
512
  // octets ARE the ciphertext directly, so it reads through implicitOctetString(0),
299
513
  // NOT the [0] EXPLICIT shape ENCAP_CONTENT_INFO uses (which would double-strip a
300
514
  // length header). The ciphertext + algorithm parameters are surfaced RAW.
@@ -314,7 +528,7 @@ var ENCRYPTED_CONTENT_INFO = schema.seq([
314
528
  });
315
529
 
316
530
  // RecipientIdentifier ::= CHOICE { issuerAndSerialNumber, subjectKeyIdentifier [0]
317
- // IMPLICIT OCTET STRING } (RFC 5652 §6.2.1) structurally identical to
531
+ // IMPLICIT OCTET STRING } (RFC 5652 sec. 6.2.1) -- structurally identical to
318
532
  // SignerIdentifier; reuse ISSUER_AND_SERIAL + the implicitOctetString(0) leaf.
319
533
  var RECIPIENT_IDENTIFIER = schema.choice([
320
534
  { when: { tagClass: "universal", tagNumber: T.SEQUENCE }, schema: ISSUER_AND_SERIAL },
@@ -322,7 +536,7 @@ var RECIPIENT_IDENTIFIER = schema.choice([
322
536
  ], { code: "cms/bad-recipient-identifier", what: "RecipientIdentifier" });
323
537
 
324
538
  // KeyTransRecipientInfo ::= SEQUENCE { version(0|2), rid RecipientIdentifier,
325
- // keyEncryptionAlgorithm, encryptedKey OCTET STRING } (RFC 5652 §6.2.1).
539
+ // keyEncryptionAlgorithm, encryptedKey OCTET STRING } (RFC 5652 sec. 6.2.1).
326
540
  var KEY_TRANS_RECIPIENT_INFO = schema.seq([
327
541
  schema.field("version", KTRI_VERSION),
328
542
  schema.field("rid", RECIPIENT_IDENTIFIER),
@@ -334,9 +548,9 @@ var KEY_TRANS_RECIPIENT_INFO = schema.seq([
334
548
  var version = m.fields.version.value;
335
549
  var ridNode = m.fields.rid.node;
336
550
  var isSkid = ridNode.tagClass === "context" && ridNode.tagNumber === 0;
337
- // RFC 5652 §6.2.1 rid version: issuerAndSerialNumber 0, subjectKeyIdentifier 2.
338
- if (isSkid && version !== 2) throw NS.E("cms/bad-recipient-version", "a subjectKeyIdentifier recipient identifier requires KeyTransRecipientInfo version 2 (RFC 5652 §6.2.1)");
339
- if (!isSkid && version !== 0) throw NS.E("cms/bad-recipient-version", "an issuerAndSerialNumber recipient identifier requires KeyTransRecipientInfo version 0 (RFC 5652 §6.2.1)");
551
+ // RFC 5652 sec. 6.2.1 -- rid <=> version: issuerAndSerialNumber => 0, subjectKeyIdentifier => 2.
552
+ if (isSkid && version !== 2) throw NS.E("cms/bad-recipient-version", "a subjectKeyIdentifier recipient identifier requires KeyTransRecipientInfo version 2 (RFC 5652 sec. 6.2.1)");
553
+ if (!isSkid && version !== 0) throw NS.E("cms/bad-recipient-version", "an issuerAndSerialNumber recipient identifier requires KeyTransRecipientInfo version 0 (RFC 5652 sec. 6.2.1)");
340
554
  return {
341
555
  type: "ktri", version: version,
342
556
  rid: isSkid ? { subjectKeyIdentifier: m.fields.rid.value } : m.fields.rid.value.result,
@@ -348,7 +562,7 @@ var KEY_TRANS_RECIPIENT_INFO = schema.seq([
348
562
  });
349
563
 
350
564
  // OriginatorPublicKey ::= SEQUENCE { algorithm, publicKey BIT STRING } (RFC 5753
351
- // §3.1.1), reached as originatorKey [1] IMPLICIT SPKI-shaped but cannot reuse
565
+ // sec. 3.1.1), reached as originatorKey [1] IMPLICIT -- SPKI-shaped but cannot reuse
352
566
  // pkix.spki (which asserts a universal SEQUENCE), so assert:"constructed".
353
567
  var ORIGINATOR_PUBLIC_KEY = schema.seq([
354
568
  schema.field("algorithm", ALGORITHM_IDENTIFIER),
@@ -366,9 +580,9 @@ var ORIGINATOR_IDENTIFIER_OR_KEY = schema.choice([
366
580
  { when: { tagClass: "context", tagNumber: 1 }, schema: ORIGINATOR_PUBLIC_KEY },
367
581
  ], { code: "cms/bad-originator-identifier", what: "OriginatorIdentifierOrKey" });
368
582
 
369
- // RecipientKeyIdentifier (RFC 5652 §6.2.2) and KEKIdentifier (§6.2.3) are one
370
- // shape { <keyId> OCTET STRING, date GeneralizedTime OPTIONAL, other
371
- // OtherKeyAttribute OPTIONAL } differing only in the key-id field's name and
583
+ // RecipientKeyIdentifier (RFC 5652 sec. 6.2.2) and KEKIdentifier (sec. 6.2.3) are one
584
+ // shape -- { <keyId> OCTET STRING, date GeneralizedTime OPTIONAL, other
585
+ // OtherKeyAttribute OPTIONAL } -- differing only in the key-id field's name and
372
586
  // the enclosing tag form. One factory defines both so the OPTIONAL handling
373
587
  // (date and the raw-surfaced OtherKeyAttribute) cannot diverge between them.
374
588
  function keyIdentifierSchema(keyIdName, assert, code, what) {
@@ -388,19 +602,19 @@ function keyIdentifierSchema(keyIdName, assert, code, what) {
388
602
  });
389
603
  }
390
604
 
391
- // Reached as rKeyId [0] IMPLICIT (a SEQUENCE constructed, unlike ktri's [0] leaf).
605
+ // Reached as rKeyId [0] IMPLICIT (a SEQUENCE -- constructed, unlike ktri's [0] leaf).
392
606
  var RECIPIENT_KEY_IDENTIFIER = keyIdentifierSchema("subjectKeyIdentifier",
393
607
  "constructed", "cms/bad-recipient-key-identifier", "RecipientKeyIdentifier");
394
608
 
395
609
  // KeyAgreeRecipientIdentifier ::= CHOICE { issuerAndSerialNumber, rKeyId [0] IMPLICIT
396
- // RecipientKeyIdentifier } (RFC 5652 §6.2.2).
610
+ // RecipientKeyIdentifier } (RFC 5652 sec. 6.2.2).
397
611
  var KEY_AGREE_RECIPIENT_IDENTIFIER = schema.choice([
398
612
  { when: { tagClass: "universal", tagNumber: T.SEQUENCE }, schema: ISSUER_AND_SERIAL },
399
613
  { when: { tagClass: "context", tagNumber: 0 }, schema: RECIPIENT_KEY_IDENTIFIER },
400
614
  ], { code: "cms/bad-kari-identifier", what: "KeyAgreeRecipientIdentifier" });
401
615
 
402
616
  // RecipientEncryptedKey ::= SEQUENCE { rid KeyAgreeRecipientIdentifier, encryptedKey
403
- // OCTET STRING } (RFC 5652 §6.2.2).
617
+ // OCTET STRING } (RFC 5652 sec. 6.2.2).
404
618
  var RECIPIENT_ENCRYPTED_KEY = schema.seq([
405
619
  schema.field("rid", KEY_AGREE_RECIPIENT_IDENTIFIER),
406
620
  schema.field("encryptedKey", schema.octetString()),
@@ -410,7 +624,10 @@ var RECIPIENT_ENCRYPTED_KEY = schema.seq([
410
624
  var ridNode = m.fields.rid.node;
411
625
  var isRkid = ridNode.tagClass === "context" && ridNode.tagNumber === 0;
412
626
  return {
413
- rid: isRkid ? m.fields.rid.value.result : m.fields.rid.value.result,
627
+ rid: m.fields.rid.value.result,
628
+ // Which KeyAgreeRecipientIdentifier arm was taken -- the recipient-matching
629
+ // form differs (mirrors ktri.ridType / kari.originator.form).
630
+ ridType: isRkid ? "rKeyId" : "issuerAndSerialNumber",
414
631
  encryptedKey: m.fields.encryptedKey.value,
415
632
  };
416
633
  },
@@ -418,8 +635,8 @@ var RECIPIENT_ENCRYPTED_KEY = schema.seq([
418
635
 
419
636
  // KeyAgreeRecipientInfo ::= [1] IMPLICIT SEQUENCE { version(3), originator [0]
420
637
  // EXPLICIT OriginatorIdentifierOrKey, ukm [1] EXPLICIT OPTIONAL,
421
- // keyEncryptionAlgorithm, recipientEncryptedKeys SEQUENCE OF } (RFC 5652 §6.2.2 +
422
- // RFC 5753 §3.1.1). originator [0] is EXPLICIT (wraps a CHOICE).
638
+ // keyEncryptionAlgorithm, recipientEncryptedKeys SEQUENCE OF } (RFC 5652 sec. 6.2.2 +
639
+ // RFC 5753 sec. 3.1.1). originator [0] is EXPLICIT (wraps a CHOICE).
423
640
  var KEY_AGREE_RECIPIENT_INFO = schema.seq([
424
641
  schema.field("version", KARI_VERSION),
425
642
  schema.field("originator", schema.explicit(0, ORIGINATOR_IDENTIFIER_OR_KEY, { code: "cms/bad-kari" })),
@@ -443,7 +660,7 @@ var KEY_AGREE_RECIPIENT_INFO = schema.seq([
443
660
  });
444
661
 
445
662
  // KEKRecipientInfo ::= [2] IMPLICIT SEQUENCE { version(4), kekid KEKIdentifier,
446
- // keyEncryptionAlgorithm, encryptedKey } (RFC 5652 §6.2.3).
663
+ // keyEncryptionAlgorithm, encryptedKey } (RFC 5652 sec. 6.2.3).
447
664
  var KEK_IDENTIFIER = keyIdentifierSchema("keyIdentifier",
448
665
  "sequence", "cms/bad-kek-identifier", "KEKIdentifier");
449
666
  var KEK_RECIPIENT_INFO = schema.seq([
@@ -465,7 +682,7 @@ var KEK_RECIPIENT_INFO = schema.seq([
465
682
 
466
683
  // PasswordRecipientInfo ::= [3] IMPLICIT SEQUENCE { version(0), keyDerivationAlgorithm
467
684
  // [0] IMPLICIT AlgorithmIdentifier OPTIONAL, keyEncryptionAlgorithm, encryptedKey }
468
- // (RFC 5652 §6.2.4). keyDerivationAlgorithm [0] IMPLICIT is the one field needing
685
+ // (RFC 5652 sec. 6.2.4). keyDerivationAlgorithm [0] IMPLICIT is the one field needing
469
686
  // the implicitTag AlgorithmIdentifier; present iff the first post-version node is [0].
470
687
  var PASSWORD_RECIPIENT_INFO = schema.seq([
471
688
  schema.field("version", PWRI_VERSION),
@@ -484,20 +701,85 @@ var PASSWORD_RECIPIENT_INFO = schema.seq([
484
701
  },
485
702
  });
486
703
 
704
+ // KEMRecipientInfo ::= SEQUENCE { version CMSVersion (0), rid RecipientIdentifier,
705
+ // kem KEMAlgorithmIdentifier, kemct OCTET STRING, kdf KeyDerivationAlgorithm-
706
+ // Identifier, kekLength INTEGER (1..65535), ukm [0] EXPLICIT OPTIONAL, wrap
707
+ // KeyEncryptionAlgorithmIdentifier, encryptedKey OCTET STRING } (RFC 9629 sec. 3).
708
+ // Reached through the ori [4] arm under id-ori-kem. The kem / kdf params-absent
709
+ // rules (ML-KEM, HKDF) ride the shared AlgorithmIdentifier registry guard.
710
+ var KEM_RECIPIENT_INFO = schema.seq([
711
+ schema.field("version", KEMRI_VERSION),
712
+ schema.field("rid", RECIPIENT_IDENTIFIER),
713
+ schema.field("kem", ALGORITHM_IDENTIFIER),
714
+ schema.field("kemct", schema.octetString()),
715
+ schema.field("kdf", ALGORITHM_IDENTIFIER),
716
+ schema.field("kekLength", schema.integerLeaf()),
717
+ schema.optional("ukm", schema.octetString(), { tag: 0, explicit: true, emptyCode: "cms/bad-kem-recipient-info" }),
718
+ schema.field("wrap", ALGORITHM_IDENTIFIER),
719
+ schema.field("encryptedKey", schema.octetString()),
720
+ ], {
721
+ assert: "sequence", code: "cms/bad-kem-recipient-info", what: "KEMRecipientInfo",
722
+ build: function (m) {
723
+ var ridNode = m.fields.rid.node;
724
+ var isSkid = ridNode.tagClass === "context" && ridNode.tagNumber === 0;
725
+ var kem = m.fields.kem.value.result;
726
+ var kemct = m.fields.kemct.value;
727
+ var wrap = m.fields.wrap.value.result;
728
+ // kekLength INTEGER (1..65535) -- bound the unbounded INTEGER before narrowing.
729
+ var kl = m.fields.kekLength.value;
730
+ if (kl < 1n || kl > 65535n) throw NS.E("cms/bad-kek-length", "KEMRecipientInfo kekLength must be 1..65535 (RFC 9629 sec. 3)");
731
+ var kekLength = Number(kl);
732
+ // RFC 9629 sec. 3 -- kekLength MUST be consistent with the wrap algorithm; a
733
+ // recognized AES key-wrap pins the exact KEK size (registry, not switch).
734
+ var wrapLen = WRAP_KEK_LENGTHS[wrap.oid];
735
+ if (wrapLen !== undefined && kekLength !== wrapLen) {
736
+ throw NS.E("cms/kek-length-mismatch", "kekLength " + kekLength + " does not match the " + (oid.name(wrap.oid) || wrap.oid) + " KEK size " + wrapLen + " (RFC 9629 sec. 3)");
737
+ }
738
+ // FIPS 203 -- a recognized ML-KEM kem produces a fixed-size ciphertext; any
739
+ // other kemct length can never decapsulate.
740
+ var ctLen = KEM_CT_LENGTHS[kem.oid];
741
+ if (ctLen !== undefined && kemct.length !== ctLen) {
742
+ throw NS.E("cms/bad-kem-ciphertext", "the " + (oid.name(kem.oid) || kem.oid) + " kemct must be exactly " + ctLen + " octets (FIPS 203)");
743
+ }
744
+ return {
745
+ version: m.fields.version.value,
746
+ rid: isSkid ? { subjectKeyIdentifier: m.fields.rid.value } : m.fields.rid.value.result,
747
+ ridType: isSkid ? "subjectKeyIdentifier" : "issuerAndSerialNumber",
748
+ kem: kem,
749
+ kemct: kemct,
750
+ kdf: m.fields.kdf.value.result,
751
+ kekLength: kekLength,
752
+ ukm: m.fields.ukm.present ? m.fields.ukm.value : null,
753
+ wrap: wrap,
754
+ encryptedKey: m.fields.encryptedKey.value,
755
+ };
756
+ },
757
+ });
758
+
487
759
  // OtherRecipientInfo ::= [4] IMPLICIT SEQUENCE { oriType OID, oriValue ANY } (RFC
488
- // 5652 §6.2.5) oriValue opaque, surfaced RAW (the shipped encoder routes
489
- // KEMRecipientInfo through here).
760
+ // 5652 sec. 6.2.5). A RECOGNIZED oriType is validated by content, never accepted on
761
+ // the type OID alone: id-ori-kem walks KEMRecipientInfo (RFC 9629) and surfaces
762
+ // the parsed structure as `kemri` alongside the raw oriValue. An unrecognized
763
+ // oriType stays raw-opaque (the ORI extension point), kemri null.
490
764
  var OTHER_RECIPIENT_INFO = schema.seq([
491
765
  schema.field("oriType", schema.oidLeaf()),
492
766
  schema.field("oriValue", schema.any()),
493
767
  ], {
494
768
  assert: "constructed", code: "cms/bad-ori", what: "OtherRecipientInfo",
495
- build: function (m) { return { type: "ori", oriType: m.fields.oriType.value, oriValue: m.fields.oriValue.node.bytes }; },
769
+ build: function (m, ctx) {
770
+ var oriType = m.fields.oriType.value;
771
+ var raw = m.fields.oriValue.node.bytes;
772
+ if (oriType === OID_ORI_KEM) {
773
+ var kemri = schema.walk(KEM_RECIPIENT_INFO, m.fields.oriValue.node, ctx).result;
774
+ return { type: "ori", oriType: oriType, oriValue: raw, kemri: kemri };
775
+ }
776
+ return { type: "ori", oriType: oriType, oriValue: raw, kemri: null };
777
+ },
496
778
  });
497
779
 
498
780
  // RecipientInfo ::= CHOICE { ktri KeyTransRecipientInfo, kari [1], kekri [2], pwri
499
- // [3], ori [4] } (RFC 5652 §6.2). A bare universal SEQUENCE is ktri (the untagged
500
- // alternative). An unknown context tag no arm cms/bad-recipient-info.
781
+ // [3], ori [4] } (RFC 5652 sec. 6.2). A bare universal SEQUENCE is ktri (the untagged
782
+ // alternative). An unknown context tag -> no arm -> cms/bad-recipient-info.
501
783
  var RECIPIENT_INFO = schema.choice([
502
784
  { when: { tagClass: "universal", tagNumber: T.SEQUENCE }, schema: KEY_TRANS_RECIPIENT_INFO },
503
785
  { when: { tagClass: "context", tagNumber: 1 }, schema: KEY_AGREE_RECIPIENT_INFO },
@@ -507,7 +789,7 @@ var RECIPIENT_INFO = schema.choice([
507
789
  ], { code: "cms/bad-recipient-info", what: "RecipientInfo" });
508
790
 
509
791
  // OriginatorInfo ::= [0] IMPLICIT SEQUENCE { certs [0] IMPLICIT OPTIONAL, crls [1]
510
- // IMPLICIT OPTIONAL } (RFC 5652 §6.1). Members surfaced RAW (their outer tag feeds
792
+ // IMPLICIT OPTIONAL } (RFC 5652 sec. 6.1). Members surfaced RAW (their outer tag feeds
511
793
  // the version rule).
512
794
  var ORIGINATOR_INFO = schema.seq([
513
795
  schema.optional("certs", schema.implicitSetOf(0, schema.any(), { min: 1, code: "cms/bad-originator-certs", what: "certs" }), { tag: 0 }),
@@ -516,13 +798,13 @@ var ORIGINATOR_INFO = schema.seq([
516
798
  assert: "constructed", code: "cms/bad-originator-info", what: "OriginatorInfo",
517
799
  build: function (m) {
518
800
  return {
519
- certs: m.fields.certs.present ? m.fields.certs.value.items.map(rawElement) : [],
520
- crls: m.fields.crls.present ? m.fields.crls.value.items.map(rawElement) : [],
801
+ certs: m.fields.certs.present ? m.fields.certs.value.items.map(rawChoiceElement(CERT_CHOICE_TAGS, "cms/bad-originator-certs", "OriginatorInfo certs")) : [],
802
+ crls: m.fields.crls.present ? m.fields.crls.value.items.map(rawChoiceElement(CRL_CHOICE_TAGS, "cms/bad-originator-crls", "OriginatorInfo crls")) : [],
521
803
  };
522
804
  },
523
805
  });
524
806
 
525
- // RFC 5652 §6.1 the exact EnvelopedData CMSVersion, from originatorInfo's raw
807
+ // RFC 5652 sec. 6.1 -- the exact EnvelopedData CMSVersion, from originatorInfo's raw
526
808
  // cert/crl outer tags and the recipient arms (a cert `other` is [3], a v2AttrCert
527
809
  // [2]; a crl `other` is [1]; a pwri or ori forces v3; all-ktri-IAS with no
528
810
  // originatorInfo/unprotectedAttrs is v0; everything else v2).
@@ -538,10 +820,22 @@ function _expectedEnvelopedDataVersion(originatorInfo, recipientInfos, hasUnprot
538
820
  return 2;
539
821
  }
540
822
 
823
+ // RFC 5652 sec. 9.1 -- the exact AuthenticatedData CMSVersion, from originatorInfo's raw
824
+ // cert/crl outer tags ONLY. Unlike EnvelopedData sec. 6.1, the recipient-info kinds do
825
+ // NOT influence the version. IF originatorInfo present AND (other-cert [3] OR
826
+ // other-crl [1]) -> 3; ELSE IF originatorInfo present AND v2AttrCert [2] -> 1; ELSE 0.
827
+ function _expectedAuthDataVersion(originatorInfo) {
828
+ if (!originatorInfo) return 0;
829
+ var certs = originatorInfo.certs, crls = originatorInfo.crls;
830
+ if (certs.some(function (c) { return schema.isContext(c, 3); }) || crls.some(function (c) { return schema.isContext(c, 1); })) return 3;
831
+ if (certs.some(function (c) { return schema.isContext(c, 2); })) return 1;
832
+ return 0;
833
+ }
834
+
541
835
  // EnvelopedData ::= SEQUENCE { version, originatorInfo [0] IMPLICIT OPTIONAL,
542
836
  // recipientInfos RecipientInfos (SET SIZE 1..MAX), encryptedContentInfo,
543
- // unprotectedAttrs [1] IMPLICIT OPTIONAL } (RFC 5652 §6.1). recipientInfos is
544
- // min:1 (an empty SET is non-conformant the INVERSE of SignedData's degenerate
837
+ // unprotectedAttrs [1] IMPLICIT OPTIONAL } (RFC 5652 sec. 6.1). recipientInfos is
838
+ // min:1 (an empty SET is non-conformant -- the INVERSE of SignedData's degenerate
545
839
  // signerInfos).
546
840
  var ENVELOPED_DATA = schema.seq([
547
841
  schema.field("version", ENVELOPED_DATA_VERSION),
@@ -557,19 +851,21 @@ var ENVELOPED_DATA = schema.seq([
557
851
  var recipientInfos = m.fields.recipientInfos.value.items.map(function (it) { return it.value.result; });
558
852
  var hasUnprotectedAttrs = m.fields.unprotectedAttrs.present;
559
853
  var expected = _expectedEnvelopedDataVersion(originatorInfo, recipientInfos, hasUnprotectedAttrs);
560
- if (version !== expected) throw NS.E("cms/bad-version", "EnvelopedData version " + version + " does not match its contents (RFC 5652 §6.1 requires v" + expected + ")");
854
+ if (version !== expected) throw NS.E("cms/bad-version", "EnvelopedData version " + version + " does not match its contents (RFC 5652 sec. 6.1 requires v" + expected + ")");
855
+ var unprotectedAttrs = hasUnprotectedAttrs ? m.fields.unprotectedAttrs.value.items.map(function (it) { return it.value.result; }) : null;
856
+ if (unprotectedAttrs) _checkAttrPlacement(unprotectedAttrs, "unprotected");
561
857
  return {
562
858
  version: version,
563
859
  originatorInfo: originatorInfo,
564
860
  recipientInfos: recipientInfos,
565
861
  encryptedContentInfo: m.fields.encryptedContentInfo.value.result,
566
- unprotectedAttrs: hasUnprotectedAttrs ? m.fields.unprotectedAttrs.value.items.map(function (it) { return it.value.result; }) : null,
862
+ unprotectedAttrs: unprotectedAttrs,
567
863
  };
568
864
  },
569
865
  });
570
866
 
571
867
  // EncryptedData ::= SEQUENCE { version, encryptedContentInfo, unprotectedAttrs [1]
572
- // IMPLICIT OPTIONAL } (RFC 5652 §8) no recipients, no originatorInfo; the CEK is
868
+ // IMPLICIT OPTIONAL } (RFC 5652 sec. 8) -- no recipients, no originatorInfo; the CEK is
573
869
  // distributed out of band. version is 0, or 2 iff unprotectedAttrs are present.
574
870
  var ENCRYPTED_DATA = schema.seq([
575
871
  schema.field("version", ENCRYPTED_DATA_VERSION),
@@ -581,20 +877,157 @@ var ENCRYPTED_DATA = schema.seq([
581
877
  var version = m.fields.version.value;
582
878
  var hasUnprotectedAttrs = m.fields.unprotectedAttrs.present;
583
879
  var expected = hasUnprotectedAttrs ? 2 : 0;
584
- if (version !== expected) throw NS.E("cms/bad-version", "EncryptedData version " + version + " does not match its contents (RFC 5652 §8 requires v" + expected + ")");
880
+ if (version !== expected) throw NS.E("cms/bad-version", "EncryptedData version " + version + " does not match its contents (RFC 5652 sec. 8 requires v" + expected + ")");
881
+ var unprotectedAttrs = hasUnprotectedAttrs ? m.fields.unprotectedAttrs.value.items.map(function (it) { return it.value.result; }) : null;
882
+ if (unprotectedAttrs) _checkAttrPlacement(unprotectedAttrs, "unprotected");
585
883
  return {
586
884
  version: version,
587
885
  encryptedContentInfo: m.fields.encryptedContentInfo.value.result,
588
- unprotectedAttrs: hasUnprotectedAttrs ? m.fields.unprotectedAttrs.value.items.map(function (it) { return it.value.result; }) : null,
886
+ unprotectedAttrs: unprotectedAttrs,
887
+ };
888
+ },
889
+ });
890
+
891
+ // AuthenticatedData ::= SEQUENCE { version, originatorInfo [0] IMPLICIT OPTIONAL,
892
+ // recipientInfos RecipientInfos, macAlgorithm, digestAlgorithm [1] OPTIONAL,
893
+ // encapContentInfo, authAttrs [2] IMPLICIT OPTIONAL, mac OCTET STRING,
894
+ // unauthAttrs [3] IMPLICIT OPTIONAL } (RFC 5652 sec. 9.1). digestAlgorithm [1] is
895
+ // IMPLICIT (module default) -- the same shape as pwri's keyDerivationAlgorithm.
896
+ var AUTHENTICATED_DATA = schema.seq([
897
+ schema.field("version", AUTHDATA_VERSION),
898
+ schema.optional("originatorInfo", ORIGINATOR_INFO, { tag: 0 }),
899
+ schema.field("recipientInfos", schema.setOf(RECIPIENT_INFO, { min: 1, code: "cms/bad-recipient-infos", what: "recipientInfos" })),
900
+ schema.field("macAlgorithm", ALGORITHM_IDENTIFIER),
901
+ schema.optional("digestAlgorithm", pkix.algorithmIdentifier(NS, { implicitTag: 1 }), { tag: 1 }),
902
+ schema.field("encapContentInfo", ENCAP_CONTENT_INFO),
903
+ schema.optional("authAttrs", schema.implicitSetOf(2, ATTRIBUTE, { min: 1, code: "cms/bad-auth-attrs", what: "authAttrs" }), { tag: 2 }),
904
+ schema.field("mac", schema.octetString()),
905
+ schema.optional("unauthAttrs", schema.implicitSetOf(3, ATTRIBUTE, { min: 1, code: "cms/bad-unauth-attrs", what: "unauthAttrs" }), { tag: 3 }),
906
+ ], {
907
+ assert: "sequence", code: "cms/bad-auth-data", what: "AuthenticatedData",
908
+ build: function (m) {
909
+ var version = m.fields.version.value;
910
+ var originatorInfo = m.fields.originatorInfo.present ? m.fields.originatorInfo.value.result : null;
911
+ var encapContentInfo = m.fields.encapContentInfo.value.result;
912
+ var hasDigestAlg = m.fields.digestAlgorithm.present;
913
+ var hasAuthAttrs = m.fields.authAttrs.present;
914
+
915
+ // RFC 5652 sec. 9.1 -- the version is computed from originatorInfo's raw cert/crl
916
+ // tags only (recipient kinds never influence it, unlike EnvelopedData sec. 6.1).
917
+ var expected = _expectedAuthDataVersion(originatorInfo);
918
+ if (version !== expected) throw NS.E("cms/bad-version", "AuthenticatedData version " + version + " does not match its contents (RFC 5652 sec. 9.1 requires v" + expected + ")");
919
+
920
+ // RFC 5652 sec. 9.1 -- authAttrs MUST be present when the content type is not
921
+ // id-data, and digestAlgorithm <=> authAttrs is a strict biconditional.
922
+ if (!hasAuthAttrs && encapContentInfo.eContentType !== OID_DATA) {
923
+ throw NS.E("cms/missing-auth-attrs", "AuthenticatedData must carry authAttrs when the content type is not id-data (RFC 5652 sec. 9.1)");
924
+ }
925
+ if (hasDigestAlg && !hasAuthAttrs) throw NS.E("cms/missing-auth-attrs", "a digestAlgorithm requires authAttrs (RFC 5652 sec. 9.1)");
926
+ if (hasAuthAttrs && !hasDigestAlg) throw NS.E("cms/missing-digest-algorithm", "authAttrs require a digestAlgorithm (RFC 5652 sec. 9.1)");
927
+
928
+ var authAttrs = null, authAttrsBytes = null;
929
+ if (hasAuthAttrs) {
930
+ // sec. 9.1 -- AuthAttributes MUST be DER even in a BER envelope (feeds the sec. 9.2 MAC).
931
+ _assertDerEncodedAttrs(m.fields.authAttrs.node, "cms/bad-auth-attrs");
932
+ authAttrs = m.fields.authAttrs.value.items.map(function (it) { return it.value.result; });
933
+ _checkAttrPlacement(authAttrs, "auth");
934
+ // sec. 9.1 -- authAttrs MUST contain content-type + message-digest (sec. 11.1/sec. 11.2),
935
+ // and the content-type value MUST equal the eContentType.
936
+ _checkContentBindingAttrs(authAttrs, "content");
937
+ _assertContentTypeMatchesAttrs(authAttrs, encapContentInfo.eContentType);
938
+ // The raw on-wire authAttrs bytes (leading 0xA2) so a verifier can re-tag to
939
+ // a universal SET and reproduce the MAC input (RFC 5652 sec. 9.2).
940
+ authAttrsBytes = m.fields.authAttrs.node.bytes;
941
+ }
942
+ var unauthAttrs = null;
943
+ if (m.fields.unauthAttrs.present) {
944
+ unauthAttrs = m.fields.unauthAttrs.value.items.map(function (it) { return it.value.result; });
945
+ _checkAttrPlacement(unauthAttrs, "unauth");
946
+ }
947
+ return {
948
+ version: version,
949
+ originatorInfo: originatorInfo,
950
+ recipientInfos: m.fields.recipientInfos.value.items.map(function (it) { return it.value.result; }),
951
+ macAlgorithm: m.fields.macAlgorithm.value.result,
952
+ digestAlgorithm: hasDigestAlg ? m.fields.digestAlgorithm.value.result : null,
953
+ encapContentInfo: encapContentInfo,
954
+ authAttrs: authAttrs,
955
+ authAttrsBytes: authAttrsBytes,
956
+ mac: m.fields.mac.value,
957
+ unauthAttrs: unauthAttrs,
958
+ };
959
+ },
960
+ });
961
+
962
+ // AuthEnvelopedData ::= SEQUENCE { version CMSVersion (0), originatorInfo [0]
963
+ // IMPLICIT OPTIONAL, recipientInfos RecipientInfos, authEncryptedContentInfo
964
+ // EncryptedContentInfo, authAttrs [1] IMPLICIT OPTIONAL, mac OCTET STRING,
965
+ // unauthAttrs [2] IMPLICIT OPTIONAL } (RFC 5083 sec. 2.1). The version is a fixed 0
966
+ // -- originatorInfo contents never change it. The authAttrs tags are SHIFTED
967
+ // ([1]/[2]) relative to AuthenticatedData's ([2]/[3]).
968
+ var AUTH_ENVELOPED_DATA = schema.seq([
969
+ schema.field("version", AUTHENV_VERSION),
970
+ schema.optional("originatorInfo", ORIGINATOR_INFO, { tag: 0 }),
971
+ schema.field("recipientInfos", schema.setOf(RECIPIENT_INFO, { min: 1, code: "cms/bad-recipient-infos", what: "recipientInfos" })),
972
+ schema.field("authEncryptedContentInfo", ENCRYPTED_CONTENT_INFO),
973
+ schema.optional("authAttrs", schema.implicitSetOf(1, ATTRIBUTE, { min: 1, code: "cms/bad-auth-attrs", what: "authAttrs" }), { tag: 1 }),
974
+ schema.field("mac", schema.octetString()),
975
+ schema.optional("unauthAttrs", schema.implicitSetOf(2, ATTRIBUTE, { min: 1, code: "cms/bad-unauth-attrs", what: "unauthAttrs" }), { tag: 2 }),
976
+ ], {
977
+ assert: "sequence", code: "cms/bad-auth-enveloped-data", what: "AuthEnvelopedData",
978
+ build: function (m) {
979
+ var encryptedContentInfo = m.fields.authEncryptedContentInfo.value.result;
980
+ var mac = m.fields.mac.value;
981
+ // RFC 5084 -- a recognized AES-GCM/CCM content-encryption algorithm carries
982
+ // validated parameters, and its aes-ICVlen MUST equal the mac length.
983
+ var aead = _validateAeadParams(encryptedContentInfo.contentEncryptionAlgorithm, mac.length);
984
+
985
+ // RFC 5083 sec. 2.1 -- authAttrs MUST be present when the content type carried in
986
+ // EncryptedContentInfo is not id-data.
987
+ var hasAuthAttrs = m.fields.authAttrs.present;
988
+ if (!hasAuthAttrs && encryptedContentInfo.contentType !== OID_DATA) {
989
+ throw NS.E("cms/missing-auth-attrs", "AuthEnvelopedData must carry authAttrs when the content type is not id-data (RFC 5083 sec. 2.1)");
990
+ }
991
+ var authAttrs = null, authAttrsBytes = null;
992
+ if (hasAuthAttrs) {
993
+ // RFC 5083 sec. 2.1 -- AuthAttributes MUST be DER even in a BER envelope (the
994
+ // sec. 2.2 AAD is the re-tagged DER SET). No content-type/message-digest
995
+ // presence rules bind here (sec. 11.1/sec. 11.2 name signed-data and
996
+ // authenticated-data; sec. 2.1 says message-digest SHOULD NOT appear), so only
997
+ // the duplicate rule, the sec. 11 placement rows, and -- when a content-type
998
+ // attribute IS present -- the value == contentType coherence apply.
999
+ _assertDerEncodedAttrs(m.fields.authAttrs.node, "cms/bad-auth-attrs");
1000
+ authAttrs = m.fields.authAttrs.value.items.map(function (it) { return it.value.result; });
1001
+ _checkAttrPlacement(authAttrs, "auth");
1002
+ _checkNoDuplicateAttrs(authAttrs);
1003
+ _assertContentTypeMatchesAttrs(authAttrs, encryptedContentInfo.contentType);
1004
+ authAttrsBytes = m.fields.authAttrs.node.bytes;
1005
+ }
1006
+ var unauthAttrs = null;
1007
+ if (m.fields.unauthAttrs.present) {
1008
+ unauthAttrs = m.fields.unauthAttrs.value.items.map(function (it) { return it.value.result; });
1009
+ _checkAttrPlacement(unauthAttrs, "unauth");
1010
+ }
1011
+ return {
1012
+ version: m.fields.version.value,
1013
+ originatorInfo: m.fields.originatorInfo.present ? m.fields.originatorInfo.value.result : null,
1014
+ recipientInfos: m.fields.recipientInfos.value.items.map(function (it) { return it.value.result; }),
1015
+ encryptedContentInfo: encryptedContentInfo,
1016
+ aead: aead,
1017
+ authAttrs: authAttrs,
1018
+ authAttrsBytes: authAttrsBytes,
1019
+ mac: mac,
1020
+ unauthAttrs: unauthAttrs,
589
1021
  };
590
1022
  },
591
1023
  });
592
1024
 
593
1025
  // ContentInfo ::= SEQUENCE { contentType OID, content [0] EXPLICIT ANY DEFINED BY
594
- // contentType } (RFC 5652 §3). The content is captured raw (explicit(0, any()))
1026
+ // contentType } (RFC 5652 sec. 3). The content is captured raw (explicit(0, any()))
595
1027
  // and re-dispatched by contentType inside the build: id-signedData walks
596
- // SIGNED_DATA, id-envelopedData / id-encryptedData walk their schemas, the other
597
- // PKCS#7 types are recognized-and-deferred, unknown OIDs are rejected.
1028
+ // SIGNED_DATA; id-envelopedData / id-encryptedData / id-ct-authData /
1029
+ // id-ct-authEnvelopedData walk their schemas; the other PKCS#7 types are
1030
+ // recognized-and-deferred; unknown OIDs are rejected.
598
1031
  var CONTENT_INFO = schema.seq([
599
1032
  schema.field("contentType", schema.oidLeaf()),
600
1033
  schema.field("content", schema.explicit(0, schema.any(), { code: "cms/not-a-content-info" })),
@@ -602,32 +1035,57 @@ var CONTENT_INFO = schema.seq([
602
1035
  assert: "sequence", arity: { exact: 2 }, code: "cms/not-a-content-info", what: "ContentInfo",
603
1036
  build: function (m, ctx) {
604
1037
  var ct = m.fields.contentType.value;
605
- if (ct === OID_SIGNED_DATA) return schema.walk(SIGNED_DATA, m.fields.content.value, ctx).result;
606
- if (ct === OID_ENVELOPED_DATA) return schema.walk(ENVELOPED_DATA, m.fields.content.value, ctx).result;
607
- if (ct === OID_ENCRYPTED_DATA) return schema.walk(ENCRYPTED_DATA, m.fields.content.value, ctx).result;
608
- if (DEFERRED.has(ct)) {
609
- throw NS.E("cms/unsupported-content-type", (ctx.oid.name(ct) || ct) + " is recognized but not parsed by this build");
1038
+ var inner = null;
1039
+ if (ct === OID_SIGNED_DATA) inner = SIGNED_DATA;
1040
+ else if (ct === OID_ENVELOPED_DATA) inner = ENVELOPED_DATA;
1041
+ else if (ct === OID_ENCRYPTED_DATA) inner = ENCRYPTED_DATA;
1042
+ else if (ct === OID_AUTH_DATA) inner = AUTHENTICATED_DATA;
1043
+ else if (ct === OID_AUTH_ENVELOPED_DATA) inner = AUTH_ENVELOPED_DATA;
1044
+ if (inner === null) {
1045
+ if (DEFERRED.has(ct)) {
1046
+ throw NS.E("cms/unsupported-content-type", (ctx.oid.name(ct) || ct) + " is recognized but not parsed by this build");
1047
+ }
1048
+ throw NS.E("cms/unknown-content-type", "unrecognized ContentInfo content type " + ct);
610
1049
  }
611
- throw NS.E("cms/unknown-content-type", "unrecognized ContentInfo content type " + ct);
1050
+ // The dispatched ContentInfo type rides the result (mirroring the ocsp
1051
+ // responseType/responseTypeName pair and the RecipientInfo `type` tags), so
1052
+ // a consumer branches on contentTypeName instead of duck-typing the shape.
1053
+ var result = schema.walk(inner, m.fields.content.value, ctx).result;
1054
+ result.contentType = ct;
1055
+ result.contentTypeName = ctx.oid.name(ct) || null;
1056
+ return result;
612
1057
  },
613
1058
  });
614
1059
 
615
1060
  /**
616
1061
  * @primitive pki.schema.cms.parse
617
- * @signature pki.schema.cms.parse(input) -> signedData
1062
+ * @signature pki.schema.cms.parse(input) -> content
618
1063
  * @since 0.1.10
619
1064
  * @status stable
620
- * @spec RFC 5652
1065
+ * @spec RFC 5652, RFC 5083, RFC 9629
621
1066
  * @related pki.schema.parse, pki.schema.x509.parse
622
1067
  *
623
- * Parse a DER `Buffer` or a PEM (`CMS`) string into a structured CMS SignedData:
1068
+ * Parse a DER `Buffer` or a PEM (`CMS`) string into the structured content the
1069
+ * ContentInfo carries, dispatched by its content type. `id-signedData` returns
624
1070
  * `{ version, digestAlgorithms, encapContentInfo, certificates, crls,
625
- * signerInfos }`. `encapContentInfo.eContent` is the raw content (or `null` when
626
- * detached); each SignerInfo carries its raw `signature` and, when present, the
627
- * on-wire `signedAttrsBytes` for external verification. A ContentInfo whose type
628
- * is not `id-signedData` throws `cms/unsupported-content-type` (a recognized
629
- * PKCS#7 type) or `cms/unknown-content-type`; a malformed structure throws a typed
630
- * `CmsError` (`cms/*`) and a leaf-level codec fault surfaces as `asn1/*`.
1071
+ * signerInfos }`; `id-envelopedData` returns `{ version, originatorInfo,
1072
+ * recipientInfos, encryptedContentInfo, unprotectedAttrs }`; `id-encryptedData`
1073
+ * returns `{ version, encryptedContentInfo, unprotectedAttrs }`; `id-ct-authData`
1074
+ * returns `{ version, originatorInfo, recipientInfos, macAlgorithm,
1075
+ * digestAlgorithm, encapContentInfo, authAttrs, authAttrsBytes, mac,
1076
+ * unauthAttrs }`; `id-ct-authEnvelopedData` returns `{ version, originatorInfo,
1077
+ * recipientInfos, encryptedContentInfo, aead, authAttrs, authAttrsBytes, mac,
1078
+ * unauthAttrs }` (`aead` holds the validated AES-GCM/CCM nonce + ICV length, or
1079
+ * `null` for an unrecognized algorithm). A KEM recipient (RFC 9629) surfaces as
1080
+ * `{ type: "ori", oriType, oriValue, kemri }` with the parsed KEMRecipientInfo in
1081
+ * `kemri`. Every result additionally carries `contentType` (the dotted OID) and
1082
+ * `contentTypeName` (its registry name) naming which of the five shapes was
1083
+ * dispatched. Raw byte ranges an external verifier hashes -- `eContent`,
1084
+ * `signature`, `signedAttrsBytes`, `authAttrsBytes`, `mac` -- are surfaced
1085
+ * exactly as on the wire. The remaining PKCS#7 types throw `cms/unsupported-content-type`; an
1086
+ * unrecognized OID throws `cms/unknown-content-type`; a malformed structure
1087
+ * throws a typed `CmsError` (`cms/*`) and a leaf-level codec fault surfaces as
1088
+ * `asn1/*`.
631
1089
  *
632
1090
  * @example
633
1091
  * var cms = pki.schema.cms.parse(der);
@@ -681,7 +1139,7 @@ function matches(root) {
681
1139
  return true;
682
1140
  }
683
1141
 
684
- // Validate a bare EnvelopedData (RFC 5652 §6.1) a universal SEQUENCE node
1142
+ // Validate a bare EnvelopedData (RFC 5652 sec. 6.1) -- a universal SEQUENCE node --
685
1143
  // returning its structured value; throws a typed cms/* error on a malformed
686
1144
  // structure. Exposed for a composer that carries a bare EnvelopedData OUTSIDE a
687
1145
  // ContentInfo (an RFC 4211 CRMF encryptedKey [4] POPOPrivKey arm), which the
@@ -689,7 +1147,7 @@ function matches(root) {
689
1147
  // universal SEQUENCE (an IMPLICIT [n] EnvelopedData is retagged by the caller).
690
1148
  function walkEnvelopedData(node) { return schema.walk(ENVELOPED_DATA, node, NS).result; }
691
1149
 
692
- // Validate a bare SignedData / EncryptedData node the same way for a composer
1150
+ // Validate a bare SignedData / EncryptedData node the same way -- for a composer
693
1151
  // that holds an already-decoded content node and must not re-decode its bytes
694
1152
  // (an RFC 7292 PFX authSafe or encrypted safe, whose wire encoding may be BER
695
1153
  // that the strict `parse` entry would refuse). Same contract as