@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/CHANGELOG.md +47 -0
- package/README.md +17 -6
- package/index.js +16 -12
- package/lib/asn1-der.js +126 -68
- package/lib/constants.js +46 -21
- package/lib/ct.js +54 -39
- package/lib/framework-error.js +64 -32
- package/lib/oid.js +114 -58
- package/lib/path-validate.js +302 -135
- package/lib/schema-all.js +54 -40
- package/lib/schema-attrcert.js +101 -64
- package/lib/schema-cmp.js +152 -131
- package/lib/schema-cms.js +621 -163
- package/lib/schema-crl.js +43 -21
- package/lib/schema-crmf.js +136 -79
- package/lib/schema-csr.js +63 -14
- package/lib/schema-engine.js +92 -43
- package/lib/schema-ocsp.js +101 -55
- package/lib/schema-pkcs12.js +80 -64
- package/lib/schema-pkcs8.js +12 -12
- package/lib/schema-pkix.js +306 -111
- package/lib/schema-smime.js +363 -0
- package/lib/schema-tsp.js +108 -59
- package/lib/schema-x509.js +36 -25
- package/lib/webcrypto.js +156 -22
- package/package.json +3 -2
- package/sbom.cdx.json +6 -6
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 (
|
|
13
|
-
* (`CMS`) message into a structured object and is an OID-dispatch envelope
|
|
14
|
-
* ContentInfo reads its `contentType` and structurally decodes SignedData (
|
|
15
|
-
* EnvelopedData (
|
|
16
|
-
* key-agreement per RFC 5753, KEK, password, and other
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
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
|
-
* (
|
|
29
|
-
*
|
|
30
|
-
*
|
|
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
|
|
34
|
-
*
|
|
35
|
-
* verification
|
|
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
|
|
55
|
-
// version is {1,3} (RFC 5652
|
|
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
|
|
59
|
-
// versions (RFC 5652
|
|
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"),
|
|
121
|
+
oid.byName("digestedData"),
|
|
78
122
|
]);
|
|
79
|
-
// The
|
|
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
|
|
84
|
-
//
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
|
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", "
|
|
97
|
-
if (a.type === OID_MESSAGE_DIGEST) throw NS.E("cms/duplicate-message-digest", "
|
|
98
|
-
throw NS.E("cms/duplicate-signed-attr", "
|
|
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
|
-
|
|
104
|
-
//
|
|
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
|
|
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
|
|
112
|
-
// MessageDigest ::= OCTET STRING (RFC 5652
|
|
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
|
|
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", "
|
|
119
|
-
if (md === 0) throw NS.E("cms/missing-message-digest", "
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
schema.
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
|
238
|
-
// signerInfos are min:0
|
|
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(
|
|
252
|
-
var crls = m.fields.crls.present ? m.fields.crls.value.items.map(
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
338
|
-
if (isSkid && version !== 2) throw NS.E("cms/bad-recipient-version", "a subjectKeyIdentifier recipient identifier requires KeyTransRecipientInfo version 2 (RFC 5652
|
|
339
|
-
if (!isSkid && version !== 0) throw NS.E("cms/bad-recipient-version", "an issuerAndSerialNumber recipient identifier requires KeyTransRecipientInfo version 0 (RFC 5652
|
|
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
|
-
//
|
|
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
|
|
370
|
-
// shape
|
|
371
|
-
// OtherKeyAttribute OPTIONAL }
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
422
|
-
// RFC 5753
|
|
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
|
|
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
|
|
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
|
|
489
|
-
// KEMRecipientInfo
|
|
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) {
|
|
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
|
|
500
|
-
// alternative). An unknown context tag
|
|
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
|
|
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(
|
|
520
|
-
crls: m.fields.crls.present ? m.fields.crls.value.items.map(
|
|
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
|
|
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
|
|
544
|
-
// min:1 (an empty SET is non-conformant
|
|
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
|
|
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:
|
|
862
|
+
unprotectedAttrs: unprotectedAttrs,
|
|
567
863
|
};
|
|
568
864
|
},
|
|
569
865
|
});
|
|
570
866
|
|
|
571
867
|
// EncryptedData ::= SEQUENCE { version, encryptedContentInfo, unprotectedAttrs [1]
|
|
572
|
-
// IMPLICIT OPTIONAL } (RFC 5652
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
597
|
-
// PKCS#7 types are
|
|
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
|
-
|
|
606
|
-
if (ct ===
|
|
607
|
-
if (ct ===
|
|
608
|
-
if (
|
|
609
|
-
|
|
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
|
-
|
|
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) ->
|
|
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
|
|
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 }
|
|
626
|
-
*
|
|
627
|
-
*
|
|
628
|
-
*
|
|
629
|
-
*
|
|
630
|
-
*
|
|
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
|
|
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
|
|
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
|