@blamejs/pki 0.1.9 → 0.1.11
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 +24 -0
- package/README.md +60 -1
- package/lib/asn1-der.js +30 -0
- package/lib/framework-error.js +10 -0
- package/lib/oid.js +31 -2
- package/lib/schema-all.js +38 -2
- package/lib/schema-cms.js +375 -0
- package/lib/schema-engine.js +12 -1
- package/lib/schema-ocsp.js +571 -0
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (c) blamejs contributors
|
|
3
|
+
"use strict";
|
|
4
|
+
/**
|
|
5
|
+
* @module pki.schema.cms
|
|
6
|
+
* @nav Schema
|
|
7
|
+
* @title CMS
|
|
8
|
+
* @order 160
|
|
9
|
+
* @slug cms
|
|
10
|
+
*
|
|
11
|
+
* @intro
|
|
12
|
+
* CMS SignedData handling per RFC 5652 (§3 ContentInfo envelope, §5 SignedData).
|
|
13
|
+
* `parse` turns a DER or PEM (`CMS`) message into a structured object: the
|
|
14
|
+
* SignedData version, the digest algorithms, the encapsulated content, the
|
|
15
|
+
* certificate / CRL sets, and the signer infos. It is an OID-dispatch envelope —
|
|
16
|
+
* ContentInfo reads its `contentType` and structurally decodes only
|
|
17
|
+
* `id-signedData`; the other PKCS#7 content types (EnvelopedData, EncryptedData,
|
|
18
|
+
* …) are recognized and rejected with a precise `cms/unsupported-content-type`
|
|
19
|
+
* rather than a generic unknown-format error.
|
|
20
|
+
*
|
|
21
|
+
* CMS is a signed container: the bytes an external verifier must hash are
|
|
22
|
+
* surfaced RAW and never re-serialized. `encapContentInfo.eContent` is the raw
|
|
23
|
+
* content (or `null` for a detached signature); each SignerInfo's `signature` is
|
|
24
|
+
* raw, and `signedAttrsBytes` is the on-wire `[0]` SignedAttributes TLV so a
|
|
25
|
+
* verifier can re-tag it to the universal SET the signature is computed over
|
|
26
|
+
* (§5.4). Embedded certificates and CRLs are surfaced as raw DER + their outer
|
|
27
|
+
* tag, so an obsolete or unknown alternative never fails the parse. DER-only,
|
|
28
|
+
* fail-closed.
|
|
29
|
+
*
|
|
30
|
+
* @card
|
|
31
|
+
* Parse DER / PEM CMS SignedData (RFC 5652) into structured, validated fields —
|
|
32
|
+
* encapsulated content, signer infos, raw signed-attribute bytes for external
|
|
33
|
+
* verification, certificates/CRLs kept raw, fail-closed.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
var asn1 = require("./asn1-der");
|
|
37
|
+
var schema = require("./schema-engine");
|
|
38
|
+
var pkix = require("./schema-pkix");
|
|
39
|
+
var oid = require("./oid");
|
|
40
|
+
var frameworkError = require("./framework-error");
|
|
41
|
+
|
|
42
|
+
var CmsError = frameworkError.CmsError;
|
|
43
|
+
var PemError = frameworkError.PemError;
|
|
44
|
+
|
|
45
|
+
// The cms error namespace the schema engine walks under.
|
|
46
|
+
var NS = pkix.makeNS("cms", CmsError, oid);
|
|
47
|
+
|
|
48
|
+
var ALGORITHM_IDENTIFIER = pkix.algorithmIdentifier(NS);
|
|
49
|
+
var ATTRIBUTE = pkix.attribute(NS);
|
|
50
|
+
var NAME = pkix.name(NS);
|
|
51
|
+
|
|
52
|
+
// CMSVersion ::= INTEGER — the SignedData version is {1,3,4,5} and the SignerInfo
|
|
53
|
+
// version is {1,3} (RFC 5652 §5.1, §5.3). Wider accept maps than any other format.
|
|
54
|
+
var SIGNED_DATA_VERSION = pkix.versionReader(NS, { "1": 1, "3": 3, "4": 4, "5": 5 });
|
|
55
|
+
var SIGNER_VERSION = pkix.versionReader(NS, { "1": 1, "3": 3 });
|
|
56
|
+
|
|
57
|
+
// id-signedData is the one content type this build structurally decodes; the rest
|
|
58
|
+
// are recognized-and-deferred (a precise diagnostic, not a silent unknown-format).
|
|
59
|
+
// OIDs resolve from the registry (pkcs7 / smimeCt families), never dotted literals.
|
|
60
|
+
var OID_SIGNED_DATA = oid.byName("signedData");
|
|
61
|
+
var OID_DATA = oid.byName("data");
|
|
62
|
+
var DEFERRED = new Set([
|
|
63
|
+
oid.byName("data"), oid.byName("envelopedData"), oid.byName("signedAndEnvelopedData"),
|
|
64
|
+
oid.byName("digestedData"), oid.byName("encryptedData"), oid.byName("authData"),
|
|
65
|
+
]);
|
|
66
|
+
// The two mandatory signed-attribute types (RFC 5652 §5.3, §11.1/§11.2).
|
|
67
|
+
var OID_CONTENT_TYPE = oid.byName("contentType");
|
|
68
|
+
var OID_MESSAGE_DIGEST = oid.byName("messageDigest");
|
|
69
|
+
|
|
70
|
+
// Enforce the SignedAttributes value constraints (RFC 5652 §5.3, §11) — if
|
|
71
|
+
// signedAttrs is present it MUST contain exactly one content-type attribute and
|
|
72
|
+
// exactly one message-digest attribute, each single-valued. The content-type value
|
|
73
|
+
// == eContentType and messageDigest == the actual content hash are §5.6
|
|
74
|
+
// VERIFICATION concerns (surfaced, checked by the verifier), not structural-parse
|
|
75
|
+
// concerns.
|
|
76
|
+
function _checkSignedAttrs(attrs) {
|
|
77
|
+
var ct = 0, md = 0, seen = Object.create(null);
|
|
78
|
+
for (var i = 0; i < attrs.length; i++) {
|
|
79
|
+
var a = attrs[i];
|
|
80
|
+
// RFC 5652 §5.3 — a signerInfo MUST NOT include multiple instances of the
|
|
81
|
+
// same signed-attribute type (specific codes for the two mandatory types).
|
|
82
|
+
if (seen[a.type]) {
|
|
83
|
+
if (a.type === OID_CONTENT_TYPE) throw NS.E("cms/duplicate-content-type", "signedAttrs must not repeat the content-type attribute");
|
|
84
|
+
if (a.type === OID_MESSAGE_DIGEST) throw NS.E("cms/duplicate-message-digest", "signedAttrs must not repeat the message-digest attribute");
|
|
85
|
+
throw NS.E("cms/duplicate-signed-attr", "signedAttrs must not include multiple instances of the same attribute type (" + a.type + ")");
|
|
86
|
+
}
|
|
87
|
+
seen[a.type] = true;
|
|
88
|
+
if (a.type === OID_CONTENT_TYPE) {
|
|
89
|
+
ct += 1;
|
|
90
|
+
if (a.values.length !== 1) throw NS.E("cms/bad-content-type-attr", "the content-type signed attribute must be single-valued");
|
|
91
|
+
// ContentType ::= OBJECT IDENTIFIER (RFC 5652 §11.1) — validate the value's
|
|
92
|
+
// full syntax (tag AND minimal base-128 OID content), not just the tag, so a
|
|
93
|
+
// truncated / non-minimal subidentifier is rejected here, not at verify time.
|
|
94
|
+
try { asn1.read.oid(asn1.decode(a.values[0])); }
|
|
95
|
+
catch (e) { throw NS.E("cms/bad-content-type-attr", "the content-type signed attribute value must be a valid OBJECT IDENTIFIER", e); }
|
|
96
|
+
} else if (a.type === OID_MESSAGE_DIGEST) {
|
|
97
|
+
md += 1;
|
|
98
|
+
if (a.values.length !== 1) throw NS.E("cms/bad-message-digest-attr", "the message-digest signed attribute must be single-valued");
|
|
99
|
+
// MessageDigest ::= OCTET STRING (RFC 5652 §11.2) — validate the full syntax.
|
|
100
|
+
try { asn1.read.octetString(asn1.decode(a.values[0])); }
|
|
101
|
+
catch (e) { throw NS.E("cms/bad-message-digest-attr", "the message-digest signed attribute value must be an OCTET STRING", e); }
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// Duplicates are rejected in the loop (the seen-set); here only presence.
|
|
105
|
+
if (ct === 0) throw NS.E("cms/missing-content-type", "signedAttrs must contain a content-type attribute (RFC 5652 §11.1)");
|
|
106
|
+
if (md === 0) throw NS.E("cms/missing-message-digest", "signedAttrs must contain a message-digest attribute (RFC 5652 §11.2)");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// A CertificateChoices / RevocationInfoChoice element, surfaced RAW (its DER +
|
|
110
|
+
// outer tag) rather than recursively parsed — the obsolete CHOICE alternatives
|
|
111
|
+
// (extendedCertificate, attribute certs, otherRevocationInfo) never fail the
|
|
112
|
+
// parse, and a caller re-parses a `certificate`/`CertificateList` element itself.
|
|
113
|
+
function rawElement(item) {
|
|
114
|
+
return { bytes: item.node.bytes, tagClass: item.node.tagClass, tagNumber: item.node.tagNumber };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// RFC 5652 §5.1 — the exact SignedData CMSVersion, computed from the raw
|
|
118
|
+
// CertificateChoices / RevocationInfoChoice outer tags (a certificate `other` is
|
|
119
|
+
// [3], a v2AttrCert [2], a v1AttrCert [1]; a RevocationInfoChoice `other` is [1]),
|
|
120
|
+
// the SignerInfo versions, and whether the content type is id-data.
|
|
121
|
+
function _expectedSignedDataVersion(certificates, crls, signerInfos, eContentType) {
|
|
122
|
+
function ctx(el, n) { return el.tagClass === "context" && el.tagNumber === n; }
|
|
123
|
+
var otherCert = certificates.some(function (c) { return ctx(c, 3); });
|
|
124
|
+
var otherCrl = crls.some(function (c) { return ctx(c, 1); });
|
|
125
|
+
if (otherCert || otherCrl) return 5;
|
|
126
|
+
if (certificates.some(function (c) { return ctx(c, 2); })) return 4; // v2AttrCert [2]
|
|
127
|
+
if (certificates.some(function (c) { return ctx(c, 1); }) || // v1AttrCert [1]
|
|
128
|
+
signerInfos.some(function (si) { return si.version === 3; }) ||
|
|
129
|
+
eContentType !== OID_DATA) return 3;
|
|
130
|
+
return 1;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// EncapsulatedContentInfo ::= SEQUENCE { eContentType OID,
|
|
134
|
+
// eContent [0] EXPLICIT OCTET STRING OPTIONAL } (RFC 5652 §5.2). Absent eContent
|
|
135
|
+
// is a detached signature (surfaced as null); present is the raw content bytes.
|
|
136
|
+
var ENCAP_CONTENT_INFO = schema.seq([
|
|
137
|
+
schema.field("eContentType", schema.oidLeaf()),
|
|
138
|
+
schema.optional("eContent", schema.octetString(), { tag: 0, explicit: true, emptyCode: "cms/bad-econtent" }),
|
|
139
|
+
], {
|
|
140
|
+
assert: "sequence", arity: { min: 1, max: 2 }, code: "cms/bad-encap-content-info", what: "EncapsulatedContentInfo",
|
|
141
|
+
build: function (m) {
|
|
142
|
+
return {
|
|
143
|
+
eContentType: m.fields.eContentType.value,
|
|
144
|
+
eContent: m.fields.eContent.present ? m.fields.eContent.value : null,
|
|
145
|
+
};
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// IssuerAndSerialNumber ::= SEQUENCE { issuer Name, serialNumber INTEGER }
|
|
150
|
+
// (RFC 5652 §10.2.4). serialNumberHex preserves the DER sign padding.
|
|
151
|
+
var ISSUER_AND_SERIAL = schema.seq([
|
|
152
|
+
schema.field("issuer", NAME),
|
|
153
|
+
schema.field("serialNumber", schema.integerLeaf()),
|
|
154
|
+
], {
|
|
155
|
+
assert: "sequence", arity: { exact: 2 }, code: "cms/bad-issuer-and-serial", what: "IssuerAndSerialNumber",
|
|
156
|
+
build: function (m) {
|
|
157
|
+
return {
|
|
158
|
+
issuer: m.fields.issuer.value.result,
|
|
159
|
+
serialNumber: m.fields.serialNumber.value,
|
|
160
|
+
serialNumberHex: m.fields.serialNumber.node.content.toString("hex"),
|
|
161
|
+
};
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// SignerIdentifier ::= CHOICE { issuerAndSerialNumber IssuerAndSerialNumber,
|
|
166
|
+
// subjectKeyIdentifier [0] IMPLICIT OCTET STRING } (RFC 5652 §5.3) — the arm is
|
|
167
|
+
// disambiguated by tag (universal SEQUENCE vs context [0]).
|
|
168
|
+
var SIGNER_IDENTIFIER = schema.choice([
|
|
169
|
+
{ when: { tagClass: "universal", tagNumber: asn1.TAGS.SEQUENCE }, schema: ISSUER_AND_SERIAL },
|
|
170
|
+
{ when: { tagClass: "context", tagNumber: 0 }, schema: schema.implicitOctetString(0) },
|
|
171
|
+
], { code: "cms/bad-signer-identifier", what: "SignerIdentifier" });
|
|
172
|
+
|
|
173
|
+
// SignerInfo ::= SEQUENCE { version, sid SignerIdentifier, digestAlgorithm,
|
|
174
|
+
// signedAttrs [0] IMPLICIT OPTIONAL, signatureAlgorithm, signature OCTET STRING,
|
|
175
|
+
// unsignedAttrs [1] IMPLICIT OPTIONAL } (RFC 5652 §5.3). signedAttrs/unsignedAttrs
|
|
176
|
+
// are positional optionals (a required signatureAlgorithm sits between them, so
|
|
177
|
+
// they cannot be a trailing block).
|
|
178
|
+
var SIGNER_INFO = schema.seq([
|
|
179
|
+
schema.field("version", SIGNER_VERSION),
|
|
180
|
+
schema.field("sid", SIGNER_IDENTIFIER),
|
|
181
|
+
schema.field("digestAlgorithm", ALGORITHM_IDENTIFIER),
|
|
182
|
+
schema.optional("signedAttrs", schema.implicitSetOf(0, ATTRIBUTE, { min: 1, code: "cms/bad-signed-attrs", what: "signedAttrs" }), { tag: 0 }),
|
|
183
|
+
schema.field("signatureAlgorithm", ALGORITHM_IDENTIFIER),
|
|
184
|
+
schema.field("signature", schema.octetString()),
|
|
185
|
+
schema.optional("unsignedAttrs", schema.implicitSetOf(1, ATTRIBUTE, { min: 1, code: "cms/bad-unsigned-attrs", what: "unsignedAttrs" }), { tag: 1 }),
|
|
186
|
+
], {
|
|
187
|
+
assert: "sequence", code: "cms/bad-signer-info", what: "SignerInfo",
|
|
188
|
+
build: function (m) {
|
|
189
|
+
var version = m.fields.version.value;
|
|
190
|
+
var sidNode = m.fields.sid.node;
|
|
191
|
+
var isSkid = sidNode.tagClass === "context" && sidNode.tagNumber === 0;
|
|
192
|
+
var sid;
|
|
193
|
+
if (isSkid) {
|
|
194
|
+
// RFC 5652 §5.3 — a subjectKeyIdentifier sid forces SignerInfo version 3.
|
|
195
|
+
if (version !== 3) throw NS.E("cms/bad-signer-version", "a subjectKeyIdentifier signer identifier requires SignerInfo version 3");
|
|
196
|
+
sid = { subjectKeyIdentifier: m.fields.sid.value };
|
|
197
|
+
} else {
|
|
198
|
+
// RFC 5652 §5.3 — an issuerAndSerialNumber sid forces SignerInfo version 1.
|
|
199
|
+
if (version !== 1) throw NS.E("cms/bad-signer-version", "an issuerAndSerialNumber signer identifier requires SignerInfo version 1");
|
|
200
|
+
sid = m.fields.sid.value.result;
|
|
201
|
+
}
|
|
202
|
+
var signedAttrs = null, signedAttrsBytes = null;
|
|
203
|
+
if (m.fields.signedAttrs.present) {
|
|
204
|
+
signedAttrs = m.fields.signedAttrs.value.items.map(function (it) { return it.value.result; });
|
|
205
|
+
_checkSignedAttrs(signedAttrs);
|
|
206
|
+
// The raw on-wire signedAttrs bytes (leading 0xA0) so a verifier can re-tag to
|
|
207
|
+
// a universal SET and reproduce the signed hash (RFC 5652 §5.4).
|
|
208
|
+
signedAttrsBytes = m.fields.signedAttrs.node.bytes;
|
|
209
|
+
}
|
|
210
|
+
return {
|
|
211
|
+
version: version,
|
|
212
|
+
sid: sid,
|
|
213
|
+
digestAlgorithm: m.fields.digestAlgorithm.value.result,
|
|
214
|
+
signedAttrs: signedAttrs,
|
|
215
|
+
signedAttrsBytes: signedAttrsBytes,
|
|
216
|
+
signatureAlgorithm: m.fields.signatureAlgorithm.value.result,
|
|
217
|
+
signature: m.fields.signature.value,
|
|
218
|
+
unsignedAttrs: m.fields.unsignedAttrs.present ? m.fields.unsignedAttrs.value.items.map(function (it) { return it.value.result; }) : null,
|
|
219
|
+
};
|
|
220
|
+
},
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
// SignedData ::= SEQUENCE { version CMSVersion, digestAlgorithms SET OF,
|
|
224
|
+
// encapContentInfo, certificates [0] IMPLICIT OPTIONAL, crls [1] IMPLICIT
|
|
225
|
+
// OPTIONAL, signerInfos SET OF } (RFC 5652 §5.1). digestAlgorithms and
|
|
226
|
+
// signerInfos are min:0 — a degenerate certs-only SignedData carries neither.
|
|
227
|
+
var SIGNED_DATA = schema.seq([
|
|
228
|
+
schema.field("version", SIGNED_DATA_VERSION),
|
|
229
|
+
schema.field("digestAlgorithms", schema.setOf(ALGORITHM_IDENTIFIER, { min: 0, code: "cms/bad-digest-algorithms", what: "digestAlgorithms" })),
|
|
230
|
+
schema.field("encapContentInfo", ENCAP_CONTENT_INFO),
|
|
231
|
+
schema.optional("certificates", schema.implicitSetOf(0, schema.any(), { min: 1, code: "cms/bad-certificates", what: "certificates" }), { tag: 0 }),
|
|
232
|
+
schema.optional("crls", schema.implicitSetOf(1, schema.any(), { min: 1, code: "cms/bad-crls", what: "crls" }), { tag: 1 }),
|
|
233
|
+
schema.field("signerInfos", schema.setOf(SIGNER_INFO, { min: 0, code: "cms/bad-signer-infos", what: "signerInfos" })),
|
|
234
|
+
], {
|
|
235
|
+
assert: "sequence", code: "cms/bad-signed-data", what: "SignedData",
|
|
236
|
+
build: function (m) {
|
|
237
|
+
var version = m.fields.version.value;
|
|
238
|
+
var encapContentInfo = m.fields.encapContentInfo.value.result;
|
|
239
|
+
var certificates = m.fields.certificates.present ? m.fields.certificates.value.items.map(rawElement) : [];
|
|
240
|
+
var crls = m.fields.crls.present ? m.fields.crls.value.items.map(rawElement) : [];
|
|
241
|
+
var signerInfos = m.fields.signerInfos.value.items.map(function (it) { return it.value.result; });
|
|
242
|
+
|
|
243
|
+
// RFC 5652 §5.3 — signedAttrs MAY be omitted only when the content type is
|
|
244
|
+
// id-data; any other eContentType requires each SignerInfo to carry signedAttrs
|
|
245
|
+
// (so the content-type + message-digest attributes bind the signature).
|
|
246
|
+
if (encapContentInfo.eContentType !== OID_DATA) {
|
|
247
|
+
for (var s = 0; s < signerInfos.length; s++) {
|
|
248
|
+
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)");
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// RFC 5652 §5.3 — when signedAttrs are present, the content-type attribute's
|
|
253
|
+
// value MUST equal the eContentType (a cross-field consistency both parsed
|
|
254
|
+
// here; a mismatch is an internally-inconsistent SignedData).
|
|
255
|
+
for (var si = 0; si < signerInfos.length; si++) {
|
|
256
|
+
var sa = signerInfos[si].signedAttrs;
|
|
257
|
+
if (!sa) continue;
|
|
258
|
+
for (var ai = 0; ai < sa.length; ai++) {
|
|
259
|
+
if (sa[ai].type !== OID_CONTENT_TYPE) continue;
|
|
260
|
+
var ctv = asn1.read.oid(asn1.decode(sa[ai].values[0]));
|
|
261
|
+
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)");
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// RFC 5652 §5.1 — the SignedData CMSVersion is determined by its contents.
|
|
266
|
+
var expected = _expectedSignedDataVersion(certificates, crls, signerInfos, encapContentInfo.eContentType);
|
|
267
|
+
if (version !== expected) throw NS.E("cms/bad-version", "SignedData version " + version + " does not match its contents (RFC 5652 §5.1 requires v" + expected + ")");
|
|
268
|
+
|
|
269
|
+
return {
|
|
270
|
+
version: version,
|
|
271
|
+
digestAlgorithms: m.fields.digestAlgorithms.value.items.map(function (it) { return it.value.result; }),
|
|
272
|
+
encapContentInfo: encapContentInfo,
|
|
273
|
+
certificates: certificates,
|
|
274
|
+
crls: crls,
|
|
275
|
+
signerInfos: signerInfos,
|
|
276
|
+
};
|
|
277
|
+
},
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
// ContentInfo ::= SEQUENCE { contentType OID, content [0] EXPLICIT ANY DEFINED BY
|
|
281
|
+
// contentType } (RFC 5652 §3). The content is captured raw (explicit(0, any()))
|
|
282
|
+
// and re-dispatched by contentType inside the build: id-signedData walks
|
|
283
|
+
// SIGNED_DATA, the other PKCS#7 types are recognized-and-deferred, unknown OIDs
|
|
284
|
+
// are rejected.
|
|
285
|
+
var CONTENT_INFO = schema.seq([
|
|
286
|
+
schema.field("contentType", schema.oidLeaf()),
|
|
287
|
+
schema.field("content", schema.explicit(0, schema.any(), { code: "cms/not-a-content-info" })),
|
|
288
|
+
], {
|
|
289
|
+
assert: "sequence", arity: { exact: 2 }, code: "cms/not-a-content-info", what: "ContentInfo",
|
|
290
|
+
build: function (m, ctx) {
|
|
291
|
+
var ct = m.fields.contentType.value;
|
|
292
|
+
if (ct === OID_SIGNED_DATA) return schema.walk(SIGNED_DATA, m.fields.content.value, ctx).result;
|
|
293
|
+
if (DEFERRED.has(ct)) {
|
|
294
|
+
throw NS.E("cms/unsupported-content-type", (ctx.oid.name(ct) || ct) + " is recognized but not parsed by this build");
|
|
295
|
+
}
|
|
296
|
+
throw NS.E("cms/unknown-content-type", "unrecognized ContentInfo content type " + ct);
|
|
297
|
+
},
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* @primitive pki.schema.cms.parse
|
|
302
|
+
* @signature pki.schema.cms.parse(input) -> signedData
|
|
303
|
+
* @since 0.1.10
|
|
304
|
+
* @status experimental
|
|
305
|
+
* @spec RFC 5652
|
|
306
|
+
* @related pki.schema.parse, pki.schema.x509.parse
|
|
307
|
+
*
|
|
308
|
+
* Parse a DER `Buffer` or a PEM (`CMS`) string into a structured CMS SignedData:
|
|
309
|
+
* `{ version, digestAlgorithms, encapContentInfo, certificates, crls,
|
|
310
|
+
* signerInfos }`. `encapContentInfo.eContent` is the raw content (or `null` when
|
|
311
|
+
* detached); each SignerInfo carries its raw `signature` and, when present, the
|
|
312
|
+
* on-wire `signedAttrsBytes` for external verification. A ContentInfo whose type
|
|
313
|
+
* is not `id-signedData` throws `cms/unsupported-content-type` (a recognized
|
|
314
|
+
* PKCS#7 type) or `cms/unknown-content-type`; a malformed structure throws a typed
|
|
315
|
+
* `CmsError` (`cms/*`) and a leaf-level codec fault surfaces as `asn1/*`.
|
|
316
|
+
*
|
|
317
|
+
* @example
|
|
318
|
+
* var cms = pki.schema.cms.parse(der);
|
|
319
|
+
* cms.signerInfos[0].sid.serialNumberHex; // -> "0a1b..."
|
|
320
|
+
* cms.encapContentInfo.eContent; // -> Buffer | null (detached)
|
|
321
|
+
*/
|
|
322
|
+
var parse = pkix.makeParser({ pemLabel: "CMS", PemError: PemError, ErrorClass: CmsError, prefix: "cms", what: "CMS ContentInfo", topSchema: CONTENT_INFO, ns: NS });
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* @primitive pki.schema.cms.pemDecode
|
|
326
|
+
* @signature pki.schema.cms.pemDecode(text, label?) -> Buffer
|
|
327
|
+
* @since 0.1.10
|
|
328
|
+
* @status experimental
|
|
329
|
+
* @spec RFC 7468, RFC 5652
|
|
330
|
+
* @related pki.schema.cms.parse
|
|
331
|
+
*
|
|
332
|
+
* Extract the DER bytes from a PEM CMS block (default label `CMS`). Throws
|
|
333
|
+
* `PemError` on a missing / mismatched envelope or a non-base64 body.
|
|
334
|
+
*
|
|
335
|
+
* @example
|
|
336
|
+
* var der = pki.schema.cms.pemDecode(pemText);
|
|
337
|
+
*/
|
|
338
|
+
function pemDecode(text, label) { return pkix.pemDecode(text, label || "CMS", PemError); }
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* @primitive pki.schema.cms.pemEncode
|
|
342
|
+
* @signature pki.schema.cms.pemEncode(der, label?) -> string
|
|
343
|
+
* @since 0.1.10
|
|
344
|
+
* @status experimental
|
|
345
|
+
* @spec RFC 7468
|
|
346
|
+
* @related pki.schema.cms.pemDecode
|
|
347
|
+
*
|
|
348
|
+
* Wrap DER bytes in a PEM CMS envelope (default label `CMS`).
|
|
349
|
+
*
|
|
350
|
+
* @example
|
|
351
|
+
* var pem = pki.schema.cms.pemEncode(der);
|
|
352
|
+
*/
|
|
353
|
+
function pemEncode(der, label) { return pkix.pemEncode(der, label || "CMS", PemError); }
|
|
354
|
+
|
|
355
|
+
// A CMS ContentInfo root is the only registered structure whose root leads with an
|
|
356
|
+
// OBJECT IDENTIFIER child: a SEQUENCE of exactly 2 whose first child is an OID and
|
|
357
|
+
// second a context [0] constructed wrapper. x509/crl/csr lead with a tbs SEQUENCE
|
|
358
|
+
// and pkcs8 with an INTEGER, so the detectors are mutually exclusive regardless of
|
|
359
|
+
// registry order.
|
|
360
|
+
function matches(root) {
|
|
361
|
+
var TAGS = asn1.TAGS;
|
|
362
|
+
if (!root || root.tagClass !== "universal" || root.tagNumber !== TAGS.SEQUENCE) return false;
|
|
363
|
+
var k = root.children;
|
|
364
|
+
if (!k || k.length !== 2) return false;
|
|
365
|
+
if (!(k[0].tagClass === "universal" && k[0].tagNumber === TAGS.OBJECT_IDENTIFIER)) return false;
|
|
366
|
+
if (!(k[1].tagClass === "context" && k[1].tagNumber === 0 && k[1].children)) return false;
|
|
367
|
+
return true;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
module.exports = {
|
|
371
|
+
parse: parse,
|
|
372
|
+
pemDecode: pemDecode,
|
|
373
|
+
pemEncode: pemEncode,
|
|
374
|
+
matches: matches,
|
|
375
|
+
};
|
package/lib/schema-engine.js
CHANGED
|
@@ -100,6 +100,15 @@ function bitString() { return { kind: "leaf", read: function (n) { var b =
|
|
|
100
100
|
// A [tag] IMPLICIT BIT STRING leaf (context-class primitive) — the primitive-leaf
|
|
101
101
|
// counterpart to implicitSetOf, for e.g. the PKCS#8 publicKey [1] (RFC 5958 §2).
|
|
102
102
|
function implicitBitString(tag) { return { kind: "leaf", read: function (n) { var b = asn1.read.bitStringImplicit(n, tag); return { unusedBits: b.unusedBits, bytes: b.bytes }; } }; }
|
|
103
|
+
// A [tag] IMPLICIT OCTET STRING leaf (context-class primitive) — the sibling of
|
|
104
|
+
// implicitBitString, for e.g. the CMS SignerIdentifier subjectKeyIdentifier [0]
|
|
105
|
+
// (RFC 5652 §5.3). Yields the raw content bytes.
|
|
106
|
+
function implicitOctetString(tag) { return { kind: "leaf", read: function (n) { return asn1.read.octetStringImplicit(n, tag); } }; }
|
|
107
|
+
// A [tag] IMPLICIT NULL leaf (context-class primitive, empty content) — the sibling
|
|
108
|
+
// of implicitBitString/implicitOctetString, for e.g. the OCSP CertStatus good [0] /
|
|
109
|
+
// unknown [2] arms (RFC 6960 §4.2.1). Yields null; rejects a constructed or
|
|
110
|
+
// non-empty [tag] node fail-closed.
|
|
111
|
+
function implicitNull(tag) { return { kind: "leaf", read: function (n) { return asn1.read.nullImplicit(n, tag); } }; }
|
|
103
112
|
function any() { return { kind: "any" }; }
|
|
104
113
|
function decode(fn) { return { kind: "decode", fn: fn }; }
|
|
105
114
|
|
|
@@ -371,7 +380,9 @@ module.exports = {
|
|
|
371
380
|
seqOf: seqOf, setOf: setOf, setOfUnique: setOfUnique, implicitSetOf: implicitSetOf, choice: choice,
|
|
372
381
|
// leaves
|
|
373
382
|
oidLeaf: oidLeaf, integerLeaf: integerLeaf, boolean: boolean, octetString: octetString,
|
|
374
|
-
bitString: bitString, implicitBitString: implicitBitString,
|
|
383
|
+
bitString: bitString, implicitBitString: implicitBitString, implicitOctetString: implicitOctetString,
|
|
384
|
+
implicitNull: implicitNull,
|
|
385
|
+
any: any, decode: decode, time: time,
|
|
375
386
|
// engine
|
|
376
387
|
walk: walk,
|
|
377
388
|
};
|