@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
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (c) blamejs contributors
|
|
3
|
+
"use strict";
|
|
4
|
+
/**
|
|
5
|
+
* @module pki.schema.smime
|
|
6
|
+
* @nav Schema
|
|
7
|
+
* @title S/MIME (ESS)
|
|
8
|
+
* @order 180
|
|
9
|
+
* @slug smime
|
|
10
|
+
*
|
|
11
|
+
* @intro
|
|
12
|
+
* S/MIME Enhanced Security Services signed-attribute values per RFC 5035 (ESS)
|
|
13
|
+
* and RFC 8551 (S/MIME 4.0). These are the DER-decoded VALUES of CMS signed
|
|
14
|
+
* attributes -- they ride inside a `SignerInfo.signedAttrs`, so this is a
|
|
15
|
+
* companion decoder a CMS consumer invokes by attribute OID, NOT a top-level
|
|
16
|
+
* format the schema orchestrator auto-routes.
|
|
17
|
+
*
|
|
18
|
+
* `parseSigningCertificate` / `parseSigningCertificateV2` decode the ESS
|
|
19
|
+
* signing-certificate attributes that bind a signature to the exact certificate
|
|
20
|
+
* that made it: each surfaces its list of `ESSCertID`(v2) -- the certificate hash
|
|
21
|
+
* (raw), the hash algorithm (decoded for v2, or the implied SHA-1 for v1), and
|
|
22
|
+
* the optional `issuerSerial` (issuer `GeneralNames` validated + surfaced raw,
|
|
23
|
+
* serial as a BigInt + hex) -- plus the optional certificate policies.
|
|
24
|
+
* `parseSmimeCapabilities` decodes the ordered `SMIMECapabilities` list (each a
|
|
25
|
+
* capability OID + raw parameters). `decodeAttribute` takes a CMS-shaped
|
|
26
|
+
* `{ type, values }` attribute, enforces the single-`AttributeValue` rule
|
|
27
|
+
* (RFC 8551 sec. 2.5.2), routes on the attribute OID, and recognize-and-defers an
|
|
28
|
+
* unknown attribute type with its raw values intact.
|
|
29
|
+
*
|
|
30
|
+
* Structure is decoded; verification is the consumer's -- the parser surfaces
|
|
31
|
+
* `certHash` + `hashAlgorithm` + `issuerSerial` so a verifier recomputes the
|
|
32
|
+
* certificate hash (compose `webcrypto`) and matches the issuer/serial against
|
|
33
|
+
* the actual signing certificate; it never recomputes a hash or trusts a cert.
|
|
34
|
+
* Whether an attribute is correctly placed in `signedAttrs` (vs `unsignedAttrs`)
|
|
35
|
+
* is the CMS consumer's knowledge. DER-only, fail-closed.
|
|
36
|
+
*
|
|
37
|
+
* @card
|
|
38
|
+
* Decode RFC 5035 ESS SigningCertificate / SigningCertificateV2 and RFC 8551
|
|
39
|
+
* SMIMECapabilities signed-attribute values -- cert-hash binding, validated
|
|
40
|
+
* issuer GeneralNames, ordered capability list, OID-dispatched, fail-closed.
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
var asn1 = require("./asn1-der");
|
|
44
|
+
var schema = require("./schema-engine");
|
|
45
|
+
var pkix = require("./schema-pkix");
|
|
46
|
+
var oid = require("./oid");
|
|
47
|
+
var frameworkError = require("./framework-error");
|
|
48
|
+
|
|
49
|
+
var SmimeError = frameworkError.SmimeError;
|
|
50
|
+
var PemError = frameworkError.PemError;
|
|
51
|
+
|
|
52
|
+
var NS = pkix.makeNS("smime", SmimeError, oid);
|
|
53
|
+
var TAGS = asn1.TAGS;
|
|
54
|
+
|
|
55
|
+
var ALGID = pkix.algorithmIdentifier(NS);
|
|
56
|
+
|
|
57
|
+
// Registry constants -- never a dotted literal in a format module.
|
|
58
|
+
var OID_SHA1 = oid.byName("sha1");
|
|
59
|
+
var OID_SHA256 = oid.byName("sha256");
|
|
60
|
+
var OID_SIGNING_CERTIFICATE = oid.byName("signingCertificate");
|
|
61
|
+
var OID_SIGNING_CERTIFICATE_V2 = oid.byName("signingCertificateV2");
|
|
62
|
+
var OID_SMIME_CAPABILITIES = oid.byName("smimeCapabilities");
|
|
63
|
+
|
|
64
|
+
// IssuerSerial ::= SEQUENCE { issuer GeneralNames, serialNumber CertificateSerialNumber }
|
|
65
|
+
// (RFC 5035 App A). A bare universal SEQUENCE of exactly two fields -- distinct
|
|
66
|
+
// from the RFC 5755 attribute-certificate IssuerSerial (three fields, reached
|
|
67
|
+
// IMPLICIT-tagged), so declared per-format. `issuer` composes the shared
|
|
68
|
+
// GeneralNames factory (every CHOICE arm validated + surfaced raw, SIZE 1..MAX)
|
|
69
|
+
// and is surfaced unchanged here: the RFC 5035 sec. 5 directoryName narrowing applies
|
|
70
|
+
// only to a non-attribute (public-key) certificate reference, and an ESSCertID
|
|
71
|
+
// entry may reference an attribute certificate whose issuer is a full GeneralNames.
|
|
72
|
+
// The FIRST cert is known to be the signing public-key cert (RFC 5035 sec. 3), so the
|
|
73
|
+
// directoryName rule is enforced there -- in the SigningCertificate build -- not here.
|
|
74
|
+
var GENERAL_NAME_DIRECTORY = 4;
|
|
75
|
+
var ISSUER_SERIAL = schema.seq([
|
|
76
|
+
schema.field("issuer", pkix.generalNames(NS, { code: "smime/bad-general-names" })),
|
|
77
|
+
schema.field("serialNumber", schema.integerLeaf()),
|
|
78
|
+
], {
|
|
79
|
+
assert: "sequence", arity: { exact: 2 }, code: "smime/bad-issuer-serial", what: "IssuerSerial",
|
|
80
|
+
build: function (m) {
|
|
81
|
+
return {
|
|
82
|
+
issuer: m.fields.issuer.value.result,
|
|
83
|
+
serialNumber: m.fields.serialNumber.value,
|
|
84
|
+
serialNumberHex: m.fields.serialNumber.node.content.toString("hex"),
|
|
85
|
+
};
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// RFC 5035 sec. 5: the signing certificate (certs[0], the public-key cert that made
|
|
90
|
+
// the signature per sec. 3) MUST identify its issuer as exactly one directoryName [4].
|
|
91
|
+
// Additional certs[1..] may reference attribute certificates, whose issuer is a
|
|
92
|
+
// GeneralNames -- left unconstrained (a verifier-tier profile concern). `fail`
|
|
93
|
+
// throws the caller's code.
|
|
94
|
+
function assertSignerIssuerIsDirectoryName(certs, fail) {
|
|
95
|
+
if (!certs.length || !certs[0].issuerSerial) return;
|
|
96
|
+
var names = certs[0].issuerSerial.issuer.names;
|
|
97
|
+
if (names.length !== 1 || names[0].tagClass !== "context" || names[0].tagNumber !== GENERAL_NAME_DIRECTORY) {
|
|
98
|
+
fail("the signing certificate's IssuerSerial issuer MUST be exactly one directoryName [4] GeneralName (RFC 5035 sec. 5)");
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// PolicyInformation ::= SEQUENCE { policyIdentifier CertPolicyId,
|
|
103
|
+
// policyQualifiers SEQUENCE OF PolicyQualifierInfo OPTIONAL } (RFC 5280 sec. 4.2.1.4).
|
|
104
|
+
// Decode the policy OID (+ registry name); surface policyQualifiers raw.
|
|
105
|
+
var POLICY_INFORMATION = schema.seq([
|
|
106
|
+
schema.field("policyIdentifier", schema.oidLeaf()),
|
|
107
|
+
schema.optional("policyQualifiers", schema.any(), { whenUniversal: [TAGS.SEQUENCE] }),
|
|
108
|
+
], {
|
|
109
|
+
assert: "sequence", code: "smime/bad-policy-information", what: "PolicyInformation",
|
|
110
|
+
build: function (m, ctx) {
|
|
111
|
+
var qualifiers = null;
|
|
112
|
+
if (m.fields.policyQualifiers.present) {
|
|
113
|
+
// The qualifier body stays raw (per the ESS scope), but the RFC 5280
|
|
114
|
+
// sec. 4.2.1.4 structure is validated fail-closed by the shared assertion -- the
|
|
115
|
+
// same PolicyInformation shape the certificatePolicies decoder enforces.
|
|
116
|
+
var q = m.fields.policyQualifiers.node;
|
|
117
|
+
pkix.assertPolicyQualifiers(q, function (msg, cause) { throw ctx.E("smime/bad-policy-information", msg, cause); });
|
|
118
|
+
qualifiers = q.bytes;
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
policyIdentifier: m.fields.policyIdentifier.value,
|
|
122
|
+
name: ctx.oid.name(m.fields.policyIdentifier.value) || null,
|
|
123
|
+
policyQualifiers: qualifiers,
|
|
124
|
+
};
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
// policies SEQUENCE OF PolicyInformation OPTIONAL -- order-preserving; the ASN.1
|
|
128
|
+
// module carries no SIZE bound, so an explicitly present but empty list is legal.
|
|
129
|
+
var POLICIES = schema.seqOf(POLICY_INFORMATION, {
|
|
130
|
+
assert: "sequence", min: 0, code: "smime/bad-policies", what: "policies",
|
|
131
|
+
build: function (m) { return m.items.map(function (it) { return it.value.result; }); },
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// ESSCertID ::= SEQUENCE { certHash Hash, issuerSerial IssuerSerial OPTIONAL }
|
|
135
|
+
// (RFC 5035 sec. 5.4.2). Hash ::= OCTET STRING -- for v1 it is the SHA-1 hash of the
|
|
136
|
+
// whole certificate with NO algorithm field, so the hash algorithm is SYNTHESIZED
|
|
137
|
+
// as the implied SHA-1 to make v1 shape-compatible with v2 for a verifier.
|
|
138
|
+
var ESS_CERT_ID = schema.seq([
|
|
139
|
+
schema.field("certHash", schema.octetString()),
|
|
140
|
+
schema.optional("issuerSerial", ISSUER_SERIAL, { whenUniversal: [TAGS.SEQUENCE] }),
|
|
141
|
+
], {
|
|
142
|
+
assert: "sequence", code: "smime/bad-ess-cert-id", what: "ESSCertID",
|
|
143
|
+
build: function (m) {
|
|
144
|
+
return {
|
|
145
|
+
certHash: m.fields.certHash.value,
|
|
146
|
+
hashAlgorithm: { oid: OID_SHA1, name: "sha1", parameters: null, implied: true },
|
|
147
|
+
issuerSerial: m.fields.issuerSerial.present ? m.fields.issuerSerial.value.result : null,
|
|
148
|
+
};
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// ESSCertIDv2 ::= SEQUENCE { hashAlgorithm AlgorithmIdentifier DEFAULT
|
|
153
|
+
// {algorithm id-sha256}, certHash Hash, issuerSerial IssuerSerial OPTIONAL }
|
|
154
|
+
// (RFC 5035 sec. 5.4.1 / sec. 4). The mandatory certHash OCTET STRING pivots between the
|
|
155
|
+
// two optional SEQUENCEs, so a leading SEQUENCE is hashAlgorithm and a leading
|
|
156
|
+
// OCTET STRING means hashAlgorithm defaulted.
|
|
157
|
+
var ESS_CERT_ID_V2 = schema.seq([
|
|
158
|
+
schema.optional("hashAlgorithm", ALGID, { whenUniversal: [TAGS.SEQUENCE] }),
|
|
159
|
+
schema.field("certHash", schema.octetString()),
|
|
160
|
+
schema.optional("issuerSerial", ISSUER_SERIAL, { whenUniversal: [TAGS.SEQUENCE] }),
|
|
161
|
+
], {
|
|
162
|
+
assert: "sequence", code: "smime/bad-ess-cert-id-v2", what: "ESSCertIDv2",
|
|
163
|
+
build: function (m, ctx) {
|
|
164
|
+
var hashAlgorithm;
|
|
165
|
+
if (m.fields.hashAlgorithm.present) {
|
|
166
|
+
var alg = m.fields.hashAlgorithm.value.result;
|
|
167
|
+
// X.690 sec. 11.5: a DEFAULT value MUST be omitted in DER. The default is
|
|
168
|
+
// {algorithm id-sha256, parameters ABSENT}; an explicit encoding byte-equal
|
|
169
|
+
// to it is a non-canonical DEFAULT and is rejected fail-closed (the
|
|
170
|
+
// structured-value analogue of the primitive-DEFAULT rejection the toolkit
|
|
171
|
+
// already enforces). A present hashAlgorithm carrying a redundant NULL
|
|
172
|
+
// parameters is NOT byte-equal to the params-absent default and decodes.
|
|
173
|
+
if (alg.oid === OID_SHA256 && alg.parameters === null) {
|
|
174
|
+
throw ctx.E("smime/non-canonical-default",
|
|
175
|
+
"ESSCertIDv2 hashAlgorithm equal to the DEFAULT {algorithm id-sha256} MUST be omitted (X.690 sec. 11.5)");
|
|
176
|
+
}
|
|
177
|
+
hashAlgorithm = { oid: alg.oid, name: alg.name, parameters: alg.parameters, defaulted: false };
|
|
178
|
+
} else {
|
|
179
|
+
hashAlgorithm = { oid: OID_SHA256, name: "sha256", parameters: null, defaulted: true };
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
certHash: m.fields.certHash.value,
|
|
183
|
+
hashAlgorithm: hashAlgorithm,
|
|
184
|
+
issuerSerial: m.fields.issuerSerial.present ? m.fields.issuerSerial.value.result : null,
|
|
185
|
+
};
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// SigningCertificate ::= SEQUENCE { certs SEQUENCE OF ESSCertID,
|
|
190
|
+
// policies SEQUENCE OF PolicyInformation OPTIONAL } (RFC 5035 sec. 5.4.2). certs is
|
|
191
|
+
// order-preserving -- RFC 5035 sec. 3 makes the FIRST element the signing certificate
|
|
192
|
+
// -- and non-empty (an empty certs cannot name a signing cert).
|
|
193
|
+
function signingCertificateSchema(essCertId, code, what) {
|
|
194
|
+
return schema.seq([
|
|
195
|
+
schema.field("certs", schema.seqOf(essCertId, { assert: "sequence", min: 1, code: "smime/bad-certs", what: "certs" })),
|
|
196
|
+
schema.optional("policies", POLICIES, { whenUniversal: [TAGS.SEQUENCE] }),
|
|
197
|
+
], {
|
|
198
|
+
assert: "sequence", code: code, what: what,
|
|
199
|
+
build: function (m, ctx) {
|
|
200
|
+
var certs = m.fields.certs.value.items.map(function (it) { return it.value.result; });
|
|
201
|
+
assertSignerIssuerIsDirectoryName(certs, function (msg) { throw ctx.E("smime/bad-issuer-serial", msg); });
|
|
202
|
+
return {
|
|
203
|
+
certs: certs,
|
|
204
|
+
policies: m.fields.policies.present ? m.fields.policies.value.result : null,
|
|
205
|
+
};
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
var SIGNING_CERTIFICATE = signingCertificateSchema(ESS_CERT_ID, "smime/bad-signing-certificate", "SigningCertificate");
|
|
210
|
+
var SIGNING_CERTIFICATE_V2 = signingCertificateSchema(ESS_CERT_ID_V2, "smime/bad-signing-certificate-v2", "SigningCertificateV2");
|
|
211
|
+
|
|
212
|
+
// SMIMECapability ::= SEQUENCE { capabilityID OBJECT IDENTIFIER,
|
|
213
|
+
// parameters ANY DEFINED BY capabilityID OPTIONAL } (RFC 8551 sec. 2.5.2). Distinct
|
|
214
|
+
// domain from AlgorithmIdentifier though isomorphic -- surfaced as capabilityID +
|
|
215
|
+
// raw parameters (the ANY-DEFINED-BY interpretation is a negotiation concern).
|
|
216
|
+
var SMIME_CAPABILITY = schema.seq([
|
|
217
|
+
schema.field("capabilityID", schema.oidLeaf()),
|
|
218
|
+
schema.optional("parameters", schema.any(), { whenAny: true }),
|
|
219
|
+
], {
|
|
220
|
+
assert: "sequence", code: "smime/bad-capability", what: "SMIMECapability",
|
|
221
|
+
build: function (m, ctx) {
|
|
222
|
+
return {
|
|
223
|
+
capabilityID: m.fields.capabilityID.value,
|
|
224
|
+
name: ctx.oid.name(m.fields.capabilityID.value) || null,
|
|
225
|
+
parameters: m.fields.parameters.present ? m.fields.parameters.node.bytes : null,
|
|
226
|
+
};
|
|
227
|
+
},
|
|
228
|
+
});
|
|
229
|
+
// SMIMECapabilities ::= SEQUENCE OF SMIMECapability -- ordered by preference
|
|
230
|
+
// (RFC 8551 sec. 2.5.2), never sorted; an empty list is legal.
|
|
231
|
+
var SMIME_CAPABILITIES = schema.seqOf(SMIME_CAPABILITY, {
|
|
232
|
+
assert: "sequence", min: 0, code: "smime/bad-capabilities", what: "SMIMECapabilities",
|
|
233
|
+
build: function (m) { return { capabilities: m.items.map(function (it) { return it.value.result; }) }; },
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* @primitive pki.schema.smime.parseSigningCertificate
|
|
238
|
+
* @signature pki.schema.smime.parseSigningCertificate(der) -> { certs, policies }
|
|
239
|
+
* @since 0.1.22
|
|
240
|
+
* @status experimental
|
|
241
|
+
* @spec RFC 5035, RFC 2634
|
|
242
|
+
* @related pki.schema.smime.parseSigningCertificateV2, pki.schema.smime.decodeAttribute
|
|
243
|
+
*
|
|
244
|
+
* Decode an ESS v1 `SigningCertificate` attribute value (RFC 5035 sec. 5.4.2) -- the
|
|
245
|
+
* raw `AttributeValue` a CMS consumer plucks off `SignerInfo.signedAttrs`. Returns
|
|
246
|
+
* `{ certs, policies }`: each `certs` entry is `{ certHash, hashAlgorithm,
|
|
247
|
+
* issuerSerial }` in wire order (the first is the signing certificate), where
|
|
248
|
+
* `hashAlgorithm` is the implied SHA-1 (v1 carries no algorithm field) and
|
|
249
|
+
* `issuerSerial` (or `null`) surfaces the issuer `GeneralNames` + serial. Throws a
|
|
250
|
+
* typed `smime/*` (or leaf `asn1/*`) error on malformed input.
|
|
251
|
+
*
|
|
252
|
+
* @example
|
|
253
|
+
* var b = pki.asn1.build;
|
|
254
|
+
* var essCertId = b.sequence([b.octetString(Buffer.alloc(20, 1))]); // SHA-1 hash, no issuerSerial
|
|
255
|
+
* var av = b.sequence([b.sequence([essCertId])]); // SigningCertificate { certs }
|
|
256
|
+
* var sc = pki.schema.smime.parseSigningCertificate(av);
|
|
257
|
+
* sc.certs[0].hashAlgorithm.name; // "sha1" (implied)
|
|
258
|
+
*/
|
|
259
|
+
var parseSigningCertificate = pkix.makeParser({ pemLabel: null, PemError: PemError, ErrorClass: SmimeError, prefix: "smime", what: "SigningCertificate", topSchema: SIGNING_CERTIFICATE, ns: NS });
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* @primitive pki.schema.smime.parseSigningCertificateV2
|
|
263
|
+
* @signature pki.schema.smime.parseSigningCertificateV2(der) -> { certs, policies }
|
|
264
|
+
* @since 0.1.22
|
|
265
|
+
* @status experimental
|
|
266
|
+
* @spec RFC 5035, RFC 5816
|
|
267
|
+
* @related pki.schema.smime.parseSigningCertificate, pki.schema.smime.decodeAttribute
|
|
268
|
+
*
|
|
269
|
+
* Decode an ESS v2 `SigningCertificateV2` attribute value (RFC 5035 sec. 5.4.1).
|
|
270
|
+
* Identical shape to v1 but each `certs` entry carries a real `hashAlgorithm`:
|
|
271
|
+
* decoded when present, or the RFC 5035 sec. 4 default `id-sha256` (with
|
|
272
|
+
* `defaulted: true`) when omitted. An explicit `hashAlgorithm` byte-equal to that
|
|
273
|
+
* default is a non-canonical DER encoding and is rejected `smime/non-canonical-default`
|
|
274
|
+
* (X.690 sec. 11.5). Throws a typed `smime/*` error on malformed input.
|
|
275
|
+
*
|
|
276
|
+
* @example
|
|
277
|
+
* var b = pki.asn1.build;
|
|
278
|
+
* var essCertId = b.sequence([b.octetString(Buffer.alloc(32, 2))]); // hashAlgorithm defaulted
|
|
279
|
+
* var av = b.sequence([b.sequence([essCertId])]);
|
|
280
|
+
* var sc = pki.schema.smime.parseSigningCertificateV2(av);
|
|
281
|
+
* sc.certs[0].hashAlgorithm.defaulted; // true (SHA-256 default)
|
|
282
|
+
*/
|
|
283
|
+
var parseSigningCertificateV2 = pkix.makeParser({ pemLabel: null, PemError: PemError, ErrorClass: SmimeError, prefix: "smime", what: "SigningCertificateV2", topSchema: SIGNING_CERTIFICATE_V2, ns: NS });
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* @primitive pki.schema.smime.parseSmimeCapabilities
|
|
287
|
+
* @signature pki.schema.smime.parseSmimeCapabilities(der) -> { capabilities }
|
|
288
|
+
* @since 0.1.22
|
|
289
|
+
* @status experimental
|
|
290
|
+
* @spec RFC 8551
|
|
291
|
+
* @related pki.schema.smime.decodeAttribute
|
|
292
|
+
*
|
|
293
|
+
* Decode an `SMIMECapabilities` attribute value (RFC 8551 sec. 2.5.2) into
|
|
294
|
+
* `{ capabilities }` -- an ORDERED list (preference order, never sorted), each
|
|
295
|
+
* `{ capabilityID, name, parameters }` with `parameters` the raw
|
|
296
|
+
* `ANY DEFINED BY capabilityID` bytes (or `null`). Throws a typed `smime/*` error
|
|
297
|
+
* on malformed input.
|
|
298
|
+
*
|
|
299
|
+
* @example
|
|
300
|
+
* var b = pki.asn1.build;
|
|
301
|
+
* var cap = b.sequence([b.oid(pki.oid.byName("aes256-CBC"))]);
|
|
302
|
+
* var caps = pki.schema.smime.parseSmimeCapabilities(b.sequence([cap]));
|
|
303
|
+
* caps.capabilities[0].name; // "aes256-CBC"
|
|
304
|
+
*/
|
|
305
|
+
var parseSmimeCapabilities = pkix.makeParser({ pemLabel: null, PemError: PemError, ErrorClass: SmimeError, prefix: "smime", what: "SMIMECapabilities", topSchema: SMIME_CAPABILITIES, ns: NS });
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* @primitive pki.schema.smime.decodeAttribute
|
|
309
|
+
* @signature pki.schema.smime.decodeAttribute(attr) -> { kind, ... }
|
|
310
|
+
* @since 0.1.22
|
|
311
|
+
* @status experimental
|
|
312
|
+
* @spec RFC 8551, RFC 5035
|
|
313
|
+
* @related pki.schema.smime.parseSigningCertificate, pki.schema.cms.parse
|
|
314
|
+
*
|
|
315
|
+
* OID-dispatch convenience over the three value decoders for a CMS-shaped
|
|
316
|
+
* `{ type, values }` attribute (the shape `cms.parse` surfaces on
|
|
317
|
+
* `signerInfos[i].signedAttrs`). Enforces the single-`AttributeValue` rule
|
|
318
|
+
* (RFC 8551 sec. 2.5.2 / sec. 2.5) -- a `values` length other than 1 is rejected
|
|
319
|
+
* `smime/multi-valued-attribute` -- then routes on `attr.type`:
|
|
320
|
+
* `signingCertificate` / `signingCertificateV2` / `smimeCapabilities` decode to
|
|
321
|
+
* `{ kind, ...result }`; any other type is recognize-and-deferred
|
|
322
|
+
* `smime/unsupported-attribute` (its `type`, registry `name`, and raw `values`
|
|
323
|
+
* carried on the error so a caller keeps the bytes).
|
|
324
|
+
*
|
|
325
|
+
* @example
|
|
326
|
+
* var b = pki.asn1.build;
|
|
327
|
+
* var essCertId = b.sequence([b.octetString(Buffer.alloc(32, 2))]); // ESSCertIDv2, hashAlgorithm defaulted
|
|
328
|
+
* var av = b.sequence([b.sequence([essCertId])]); // SigningCertificateV2 { certs: [ ESSCertIDv2 ] }
|
|
329
|
+
* var got = pki.schema.smime.decodeAttribute({ type: pki.oid.byName("signingCertificateV2"), values: [av] });
|
|
330
|
+
* got.kind; // "signingCertificateV2"
|
|
331
|
+
*/
|
|
332
|
+
function decodeAttribute(attr) {
|
|
333
|
+
if (!attr || typeof attr !== "object" || typeof attr.type !== "string" || !Array.isArray(attr.values)) {
|
|
334
|
+
throw new SmimeError("smime/bad-input", "decodeAttribute expects a CMS attribute { type, values }");
|
|
335
|
+
}
|
|
336
|
+
// Route on the attribute OID FIRST. The single-AttributeValue MUST (RFC 8551
|
|
337
|
+
// sec. 2.5.2) is specific to the ESS / SMIMECapabilities attributes, so it is
|
|
338
|
+
// enforced only inside the known-type branch; an unknown / custom attribute
|
|
339
|
+
// recognize-and-defers with its raw values intact regardless of value count
|
|
340
|
+
// (its own cardinality rules are not this decoder's to enforce).
|
|
341
|
+
if (attr.type === OID_SIGNING_CERTIFICATE || attr.type === OID_SIGNING_CERTIFICATE_V2 || attr.type === OID_SMIME_CAPABILITIES) {
|
|
342
|
+
if (attr.values.length !== 1) {
|
|
343
|
+
throw new SmimeError("smime/multi-valued-attribute",
|
|
344
|
+
"an ESS / SMIMECapabilities attribute MUST carry exactly one AttributeValue, got " + attr.values.length + " (RFC 8551 sec. 2.5.2)");
|
|
345
|
+
}
|
|
346
|
+
var value = attr.values[0];
|
|
347
|
+
if (attr.type === OID_SIGNING_CERTIFICATE) { var v1 = parseSigningCertificate(value); return { kind: "signingCertificate", certs: v1.certs, policies: v1.policies }; }
|
|
348
|
+
if (attr.type === OID_SIGNING_CERTIFICATE_V2) { var v2 = parseSigningCertificateV2(value); return { kind: "signingCertificateV2", certs: v2.certs, policies: v2.policies }; }
|
|
349
|
+
return { kind: "smimeCapabilities", capabilities: parseSmimeCapabilities(value).capabilities };
|
|
350
|
+
}
|
|
351
|
+
var e = new SmimeError("smime/unsupported-attribute", "unsupported S/MIME attribute type " + attr.type + (oid.name(attr.type) ? " (" + oid.name(attr.type) + ")" : ""));
|
|
352
|
+
e.type = attr.type;
|
|
353
|
+
e.name = oid.name(attr.type) || null;
|
|
354
|
+
e.values = attr.values;
|
|
355
|
+
throw e;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
module.exports = {
|
|
359
|
+
parseSigningCertificate: parseSigningCertificate,
|
|
360
|
+
parseSigningCertificateV2: parseSigningCertificateV2,
|
|
361
|
+
parseSmimeCapabilities: parseSmimeCapabilities,
|
|
362
|
+
decodeAttribute: decodeAttribute,
|
|
363
|
+
};
|