@blamejs/pki 0.8.12 → 0.8.14
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 +33 -0
- package/README.md +2 -1
- package/index.js +6 -0
- package/lib/acme.js +13 -5
- package/lib/attrcert-sign.js +3 -3
- package/lib/cmc-build.js +9 -9
- package/lib/cmc-verify.js +5 -5
- package/lib/cmp-build.js +3 -4
- package/lib/cmp-session.js +6 -6
- package/lib/cms-verify.js +4 -4
- package/lib/crl-sign.js +1 -1
- package/lib/crmf-sign.js +1 -1
- package/lib/ct.js +2 -2
- package/lib/est.js +12 -13
- package/lib/framework-error.js +3 -0
- package/lib/guard-header.js +15 -1
- package/lib/guard-identifier.js +34 -0
- package/lib/hpke.js +7 -2
- package/lib/http-transport.js +5 -6
- package/lib/identity-match.js +4 -3
- package/lib/ocsp.js +5 -3
- package/lib/oid.js +1 -1
- package/lib/path-validate.js +155 -16
- package/lib/pki-build.js +1 -1
- package/lib/revocation-fetch.js +1 -1
- package/lib/scep.js +21 -9
- package/lib/schema-all.js +10 -1
- package/lib/schema-engine.js +7 -2
- package/lib/schema-pkix.js +40 -0
- package/lib/schema-trustanchor.js +469 -0
- package/lib/schema-x509.js +66 -33
- package/lib/smime.js +5 -1
- package/lib/trust.js +192 -11
- package/lib/trustanchor-build.js +278 -0
- package/lib/validator-tpm.js +1 -1
- package/lib/webauthn-mds.js +1 -1
- package/lib/webauthn.js +9 -10
- package/lib/webcrypto.js +3 -3
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (c) blamejs contributors
|
|
3
|
+
"use strict";
|
|
4
|
+
/**
|
|
5
|
+
* @module pki.schema.trustanchor
|
|
6
|
+
* @nav Schema
|
|
7
|
+
* @title Trust anchors
|
|
8
|
+
* @order 175
|
|
9
|
+
* @slug trustanchor
|
|
10
|
+
*
|
|
11
|
+
* @intro
|
|
12
|
+
* RFC 5914 trust anchors. A trust anchor as a structure rather than as a
|
|
13
|
+
* certificate, carrying the constraints a root program otherwise states out
|
|
14
|
+
* of band: the namespace the root is trusted for, the policies it may assert,
|
|
15
|
+
* and how far below it a path may run. `parse` reads a `TrustAnchorList` and
|
|
16
|
+
* `parseInfo` a bare `TrustAnchorInfo`; `pki.trust.parseTrustAnchorList`
|
|
17
|
+
* turns either into the anchors `pki.path.validate` consumes, and RFC 5937
|
|
18
|
+
* section 3.2 says how each constraint reaches the validation state.
|
|
19
|
+
*
|
|
20
|
+
* @card
|
|
21
|
+
* Read and write RFC 5914 trust anchor lists: the anchor, its name, its
|
|
22
|
+
* constraints, and the certificate that carries them.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
var asn1 = require("./asn1-der");
|
|
26
|
+
var schema = require("./schema-engine");
|
|
27
|
+
var pkix = require("./schema-pkix");
|
|
28
|
+
var x509 = require("./schema-x509");
|
|
29
|
+
var oid = require("./oid");
|
|
30
|
+
var frameworkError = require("./framework-error");
|
|
31
|
+
var intrinsic = require("./guard-intrinsic");
|
|
32
|
+
var guard = require("./guard-all");
|
|
33
|
+
|
|
34
|
+
var TrustAnchorError = frameworkError.TrustAnchorError;
|
|
35
|
+
var PemError = frameworkError.PemError;
|
|
36
|
+
var TAGS = asn1.TAGS;
|
|
37
|
+
|
|
38
|
+
var NS = pkix.makeNS("trustanchor", TrustAnchorError, oid);
|
|
39
|
+
|
|
40
|
+
var NAME = pkix.name(NS);
|
|
41
|
+
var SPKI = pkix.spki(NS);
|
|
42
|
+
var EXTENSIONS = pkix.extensions(NS);
|
|
43
|
+
|
|
44
|
+
/** @internal `TrustAnchorTitle ::= UTF8String (SIZE (1..64))` bounds the CHARACTERS of the string,
|
|
45
|
+
* which is what `Array.from` counts; `String.prototype.length` counts UTF-16 code units and would
|
|
46
|
+
* count a character outside the basic plane twice. Shared by the reader and the writer so the two
|
|
47
|
+
* admit the same titles. */
|
|
48
|
+
var _arrayFrom = Array.from;
|
|
49
|
+
function titleChars(s) { return _arrayFrom(s).length; }
|
|
50
|
+
|
|
51
|
+
/** @internal RFC 5914 sec. 3: these four MUST NOT appear in `exts` and "are ignored if they do
|
|
52
|
+
* appear". Ignored rather than refused, so they are reported and left out of the list a caller
|
|
53
|
+
* reads: each states a constraint `certPath` states properly, and honoring both would let one
|
|
54
|
+
* anchor say two different things. */
|
|
55
|
+
var EXTS_EXCLUDED = intrinsic.assign(intrinsic.create(null), {});
|
|
56
|
+
intrinsic.forEach(["certificatePolicies", "policyConstraints", "inhibitAnyPolicy", "nameConstraints"],
|
|
57
|
+
function (n) { EXTS_EXCLUDED[oid.byName(n)] = true; });
|
|
58
|
+
|
|
59
|
+
/** @internal RFC 5914 sec. 2: CertificatePolicies where "the OPTIONAL policyQualifiers structure
|
|
60
|
+
* MUST NOT be included", which is the whole difference from the certificate extension's form. */
|
|
61
|
+
var POLICY_INFORMATION = schema.decode(function (n, ctx) {
|
|
62
|
+
if (n.tagClass !== "universal" || n.tagNumber !== TAGS.SEQUENCE || !n.children || n.children.length < 1) {
|
|
63
|
+
throw ctx.E("trustanchor/bad-policy-set", "a policySet element must be a PolicyInformation SEQUENCE");
|
|
64
|
+
}
|
|
65
|
+
if (n.children.length > 1) {
|
|
66
|
+
throw ctx.E("trustanchor/bad-policy-set", "a policySet PolicyInformation must not carry policyQualifiers (RFC 5914 sec. 2)");
|
|
67
|
+
}
|
|
68
|
+
var id;
|
|
69
|
+
try { id = asn1.read.oid(n.children[0]); }
|
|
70
|
+
catch (e) { throw ctx.E("trustanchor/bad-policy-set", "a policySet PolicyInformation must lead with a policyIdentifier OBJECT IDENTIFIER", e); }
|
|
71
|
+
return { policyIdentifier: id, name: oid.name(id) || null };
|
|
72
|
+
});
|
|
73
|
+
var POLICY_SET = schema.seqOf(POLICY_INFORMATION, {
|
|
74
|
+
assert: "sequence", min: 1, code: "trustanchor/bad-policy-set", what: "policySet",
|
|
75
|
+
build: function (m) { return intrinsic.map(m.items, function (it) { return it.value; }); },
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
var POLICY_FLAG_BITS = ["inhibitPolicyMapping", "requireExplicitPolicy", "inhibitAnyPolicy"];
|
|
79
|
+
var POLICY_FLAGS = schema.decode(function (n, ctx) {
|
|
80
|
+
var bs;
|
|
81
|
+
try { bs = asn1.read.bitStringImplicit(n, 2); }
|
|
82
|
+
catch (e) { throw ctx.E("trustanchor/bad-policy-flags", "policyFlags must be a CertPolicyFlags BIT STRING", e); }
|
|
83
|
+
try { schema.assertMinimalNamedBits(bs.unusedBits, bs.bytes, function (msg) { throw ctx.E("trustanchor/bad-policy-flags", msg); }); }
|
|
84
|
+
catch (e) { throw e; }
|
|
85
|
+
var out = intrinsic.create(null);
|
|
86
|
+
for (var i = 0; i < POLICY_FLAG_BITS.length; i++) {
|
|
87
|
+
var byteI = i >> 3;
|
|
88
|
+
var set = byteI < bs.bytes.length && ((bs.bytes[byteI] >> (7 - (i & 7))) & 1) === 1;
|
|
89
|
+
out[POLICY_FLAG_BITS[i]] = set;
|
|
90
|
+
}
|
|
91
|
+
return out;
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* @internal The RFC 5914 module is `DEFINITIONS IMPLICIT TAGS`, so a tagged member carries the
|
|
96
|
+
* context tag IN PLACE OF its own unless the module writes EXPLICIT beside it. Only three members
|
|
97
|
+
* in the whole module are written EXPLICIT: `TrustAnchorInfo.exts [1]` and both tagged arms of
|
|
98
|
+
* `TrustAnchorChoice`. Every other tagged member here is implicit, so the inner value's universal
|
|
99
|
+
* tag is gone from the wire and has to be put back before the inner schema can read it.
|
|
100
|
+
*/
|
|
101
|
+
function _asUniversalSequence(n, ctx, code, what) {
|
|
102
|
+
if (!n.children) throw ctx.E(code, what + " must be a constructed value");
|
|
103
|
+
return pkix.decodeNested(asn1.sequenceTlv(n), ctx, what);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** @internal NameConstraints as RFC 5280 sec. 4.2.1.10 states it, read through the shared
|
|
107
|
+
* certificate decoder so an anchor's namespace and a certificate's are the same shape. */
|
|
108
|
+
var CERT_EXT_DECODERS = pkix.certExtensionDecoders(NS).byOid;
|
|
109
|
+
var OID_NAME_CONSTRAINTS = oid.byName("nameConstraints");
|
|
110
|
+
var NAME_CONSTRAINTS = schema.decode(function (n, ctx) {
|
|
111
|
+
if (!n.children) throw ctx.E("trustanchor/bad-name-constraints", "nameConstr [3] must be a constructed value");
|
|
112
|
+
try { return CERT_EXT_DECODERS[OID_NAME_CONSTRAINTS](asn1.sequenceTlv(n), ctx); }
|
|
113
|
+
catch (e) { throw ctx.E("trustanchor/bad-name-constraints", "nameConstr is not a well-formed NameConstraints (RFC 5280 sec. 4.2.1.10): " + guard.text.describeThrown(e), e); }
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
/** @internal The Certificate the [0] arm carries, read through the certificate parser's own schema
|
|
117
|
+
* rather than a second definition of it. */
|
|
118
|
+
var EMBEDDED_CERTIFICATE = schema.decode(function (n, ctx) {
|
|
119
|
+
return schema.walk(x509.certificateSchema,
|
|
120
|
+
_asUniversalSequence(n, ctx, "trustanchor/bad-certificate", "certificate [0]"), ctx);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
/** @internal CertificatePolicies under an implicit [1]: the SEQUENCE OF tag is replaced, so the
|
|
124
|
+
* elements are read after the universal tag is restored. */
|
|
125
|
+
var POLICY_SET_IMPLICIT = schema.decode(function (n, ctx) {
|
|
126
|
+
return schema.walk(POLICY_SET,
|
|
127
|
+
_asUniversalSequence(n, ctx, "trustanchor/bad-policy-set", "policySet [1]"), ctx);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
var CERT_PATH_CONTROLS = schema.seq([
|
|
131
|
+
schema.field("taName", NAME),
|
|
132
|
+
/** @internal Every one of these five is IMPLICIT: the module is IMPLICIT TAGS and none of them
|
|
133
|
+
* is written EXPLICIT. */
|
|
134
|
+
schema.trailing([
|
|
135
|
+
{ tag: 0, name: "certificate", schema: EMBEDDED_CERTIFICATE },
|
|
136
|
+
{ tag: 1, name: "policySet", schema: POLICY_SET_IMPLICIT },
|
|
137
|
+
{ tag: 2, name: "policyFlags", schema: POLICY_FLAGS },
|
|
138
|
+
{ tag: 3, name: "nameConstr", schema: NAME_CONSTRAINTS },
|
|
139
|
+
{ tag: 4, name: "pathLenConstraint", schema: schema.implicitInteger(4) },
|
|
140
|
+
], { minTag: 0, maxTag: 4, unexpectedCode: "trustanchor/bad-cert-path", orderCode: "trustanchor/bad-cert-path" }),
|
|
141
|
+
], {
|
|
142
|
+
assert: "sequence", code: "trustanchor/bad-cert-path", what: "CertPathControls",
|
|
143
|
+
build: function (m, ctx) {
|
|
144
|
+
var f = m.fields;
|
|
145
|
+
var taName = f.taName.value.result;
|
|
146
|
+
/** @internal RFC 5914 sec. 2: "The name MUST NOT be an empty sequence." An anchor whose name
|
|
147
|
+
* matches everything would authorize every subject the path chains to. */
|
|
148
|
+
if (!taName.rdns || taName.rdns.length === 0) {
|
|
149
|
+
throw ctx.E("trustanchor/bad-name", "CertPathControls taName must not be an empty distinguished name (RFC 5914 sec. 2)");
|
|
150
|
+
}
|
|
151
|
+
var pathLen = null;
|
|
152
|
+
if (f.pathLenConstraint.present) {
|
|
153
|
+
var raw = f.pathLenConstraint.value;
|
|
154
|
+
if (raw < 0n) throw ctx.E("trustanchor/bad-path-len", "pathLenConstraint must be non-negative (INTEGER (0..MAX), RFC 5914 sec. 2)");
|
|
155
|
+
pathLen = intrinsic.Number(guard.range.uint31(raw, ctx.E, "trustanchor/bad-path-len", "CertPathControls pathLenConstraint"));
|
|
156
|
+
}
|
|
157
|
+
/** @internal `policySet` is a repeat, so its match carries the built list in `result`; the
|
|
158
|
+
* other members are decode leaves and answer with their value directly. */
|
|
159
|
+
/** @internal `policySet` reads through a decode leaf that restores the implicit tag and walks
|
|
160
|
+
* the repeat, so the match it answers with carries the built list in `result`. */
|
|
161
|
+
var policySet = f.policySet.present ? f.policySet.value.result : null;
|
|
162
|
+
var flags = f.policyFlags.present ? f.policyFlags.value : null;
|
|
163
|
+
/** @internal RFC 5914 sec. 2: "This bit MUST be set to FALSE if policySet is absent." The other
|
|
164
|
+
* two flags carry no such condition, since neither names a policy. */
|
|
165
|
+
if (flags !== null && flags.requireExplicitPolicy === true && policySet === null) {
|
|
166
|
+
throw ctx.E("trustanchor/bad-policy-flags", "requireExplicitPolicy must be FALSE when policySet is absent (RFC 5914 sec. 2)");
|
|
167
|
+
}
|
|
168
|
+
var certificate = f.certificate.present ? f.certificate.value.result : null;
|
|
169
|
+
if (certificate !== null) _assertCertificateMatches(certificate, taName, ctx);
|
|
170
|
+
return {
|
|
171
|
+
taName: taName, certificate: certificate, policySet: policySet,
|
|
172
|
+
policyFlags: flags, nameConstr: f.nameConstr.present ? f.nameConstr.value : null,
|
|
173
|
+
pathLenConstraint: pathLen,
|
|
174
|
+
};
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
var OID_SKI = oid.byName("subjectKeyIdentifier");
|
|
179
|
+
/**
|
|
180
|
+
* @internal RFC 5914 sec. 2, where `certificate` is present: "the subject name in the certificate
|
|
181
|
+
* MUST exactly match the X.500 distinguished name provided in the taName field, the public key MUST
|
|
182
|
+
* exactly match the public key in the pubKey field, and the subjectKeyIdentifier extension, if
|
|
183
|
+
* present, MUST exactly match the key identifier in the keyId field."
|
|
184
|
+
*
|
|
185
|
+
* The public key and key identifier halves are checked in `_buildInfo`, which is where `pubKey` and
|
|
186
|
+
* `keyId` are in scope; this half is the name, compared under the RFC 5280 sec. 7.1 canonical rule
|
|
187
|
+
* the toolkit decides every distinguished-name identity with.
|
|
188
|
+
*/
|
|
189
|
+
function _assertCertificateMatches(certificate, taName, ctx) {
|
|
190
|
+
/** @internal The error factory is not optional: the canonical comparison refuses a name carrying
|
|
191
|
+
* an embedded control byte rather than comparing it (CVE-2009-2408), and it reports that refusal
|
|
192
|
+
* through this factory. Omitting it turns that refusal into a bare TypeError, which is not the
|
|
193
|
+
* typed verdict every other fault on this path answers with. */
|
|
194
|
+
if (!guard.name.dnEqual(certificate.subject.rdns, taName.rdns, ctx.E,
|
|
195
|
+
"trustanchor/certificate-mismatch", "the certPath certificate's subject")) {
|
|
196
|
+
throw ctx.E("trustanchor/certificate-mismatch",
|
|
197
|
+
"the certPath certificate's subject does not match taName (RFC 5914 sec. 2)");
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
var TA_INFO_VERSION = schema.decode(function (n, ctx) {
|
|
202
|
+
var v = asn1.read.integer(n);
|
|
203
|
+
if (v !== 1n) throw ctx.E("trustanchor/bad-version", "TrustAnchorInfo version must be v1(1), got " + intrinsic.bigIntToString(v));
|
|
204
|
+
/** @internal DER omits a DEFAULT at its default value (X.690 sec. 11.5), so an explicitly encoded
|
|
205
|
+
* v1 is non-canonical and there is exactly one encoding of a v1 anchor. */
|
|
206
|
+
throw ctx.E("trustanchor/bad-version", "TrustAnchorInfo version v1 is the DEFAULT and must be omitted (X.690 sec. 11.5)");
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
var TRUST_ANCHOR_INFO = schema.seq([
|
|
210
|
+
schema.optional("version", TA_INFO_VERSION, { whenUniversal: [TAGS.INTEGER] }),
|
|
211
|
+
schema.field("pubKey", SPKI),
|
|
212
|
+
schema.field("keyId", schema.octetString()),
|
|
213
|
+
schema.optional("taTitle", schema.decode(function (n, ctx) {
|
|
214
|
+
if (n.tagClass !== "universal" || n.tagNumber !== TAGS.UTF8_STRING) {
|
|
215
|
+
throw ctx.E("trustanchor/bad-title", "taTitle must be a UTF8String");
|
|
216
|
+
}
|
|
217
|
+
var s;
|
|
218
|
+
try { s = asn1.read.string(n); }
|
|
219
|
+
catch (e) { throw ctx.E("trustanchor/bad-title", "taTitle must be a well-formed UTF8String", e); }
|
|
220
|
+
/** @internal The SIZE constraint counts CHARACTERS of the UTF8String, and a JavaScript string's
|
|
221
|
+
* `length` counts UTF-16 code units, so a title using characters outside the basic plane counts
|
|
222
|
+
* double and a conforming one would be refused. */
|
|
223
|
+
var chars = titleChars(s);
|
|
224
|
+
if (chars < 1 || chars > 64) {
|
|
225
|
+
throw ctx.E("trustanchor/bad-title", "taTitle must be 1 to 64 characters (SIZE (1..64), RFC 5914 sec. 2), got " + chars);
|
|
226
|
+
}
|
|
227
|
+
return s;
|
|
228
|
+
}), { whenUniversal: [TAGS.UTF8_STRING] }),
|
|
229
|
+
schema.optional("certPath", CERT_PATH_CONTROLS, { whenUniversal: [TAGS.SEQUENCE] }),
|
|
230
|
+
/** @internal The tag numbering has a gap: TrustAnchorInfo has no [0]. Its two tagged members are
|
|
231
|
+
* tagged DIFFERENTLY from each other, `exts [1] EXPLICIT` and `taTitleLangTag [2]` implicit under
|
|
232
|
+
* the module's IMPLICIT TAGS default, so neither the numbering nor the form can be copied from
|
|
233
|
+
* one to the other. Numbering them from zero reads each field as its neighbor. */
|
|
234
|
+
schema.trailing([
|
|
235
|
+
{ tag: 1, name: "exts", schema: EXTENSIONS, explicit: true, emptyCode: "trustanchor/bad-exts" },
|
|
236
|
+
/** @internal `[2] UTF8String` with no EXPLICIT beside it, so the context tag replaces the
|
|
237
|
+
* UTF8String tag and the content is the characters themselves. */
|
|
238
|
+
{ tag: 2, name: "taTitleLangTag", schema: schema.decode(function (n, ctx) {
|
|
239
|
+
if (n.children || n.content === null) {
|
|
240
|
+
throw ctx.E("trustanchor/bad-title", "taTitleLangTag [2] must be a primitive UTF8String value");
|
|
241
|
+
}
|
|
242
|
+
/** @internal The universal tag the implicit one replaced is put back, so the string is read
|
|
243
|
+
* by the codec's own UTF8String reader and a malformed encoding is refused there rather than
|
|
244
|
+
* passed through as bytes. */
|
|
245
|
+
var universal = pkix.decodeNested(
|
|
246
|
+
asn1.encodeTLV(0, false, TAGS.UTF8_STRING, n.content), ctx, "a taTitleLangTag value");
|
|
247
|
+
try { return asn1.read.string(universal); }
|
|
248
|
+
catch (e) { throw ctx.E("trustanchor/bad-title", "taTitleLangTag must be a well-formed UTF8String", e); }
|
|
249
|
+
}) },
|
|
250
|
+
], { minTag: 1, maxTag: 2, unexpectedCode: "trustanchor/bad-info", orderCode: "trustanchor/bad-info" }),
|
|
251
|
+
], {
|
|
252
|
+
assert: "sequence", code: "trustanchor/bad-info", what: "TrustAnchorInfo",
|
|
253
|
+
build: function (m, ctx) {
|
|
254
|
+
var f = m.fields;
|
|
255
|
+
/** @internal `version` carries no value to surface: the reader refuses anything but an omitted
|
|
256
|
+
* DEFAULT, so reaching the build at all means v1, which the record states outright. */
|
|
257
|
+
void f.version;
|
|
258
|
+
var pubKey = f.pubKey.value.result;
|
|
259
|
+
var keyId = f.keyId.value;
|
|
260
|
+
var certPath = f.certPath.present ? f.certPath.value.result : null;
|
|
261
|
+
if (certPath !== null && certPath.certificate !== null) {
|
|
262
|
+
_assertCertificateKeyMatches(certPath.certificate, pubKey, keyId, ctx);
|
|
263
|
+
}
|
|
264
|
+
var kept = [], excluded = [];
|
|
265
|
+
if (f.exts.present) {
|
|
266
|
+
intrinsic.forEach(f.exts.value.result, function (ext) {
|
|
267
|
+
if (EXTS_EXCLUDED[ext.oid] === true) { guard.list.append(excluded, ext.oid); return; }
|
|
268
|
+
guard.list.append(kept, ext);
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
return {
|
|
272
|
+
version: 1,
|
|
273
|
+
pubKey: pubKey,
|
|
274
|
+
keyId: keyId,
|
|
275
|
+
taTitle: f.taTitle.present ? f.taTitle.value : null,
|
|
276
|
+
certPath: certPath,
|
|
277
|
+
exts: kept,
|
|
278
|
+
excludedExtensions: excluded,
|
|
279
|
+
taTitleLangTag: f.taTitleLangTag.present ? f.taTitleLangTag.value : null,
|
|
280
|
+
};
|
|
281
|
+
},
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
function _assertCertificateKeyMatches(certificate, pubKey, keyId, ctx) {
|
|
285
|
+
if (!guard.crypto.constantTimeEqual(certificate.subjectPublicKeyInfo.bytes, pubKey.bytes)) {
|
|
286
|
+
throw ctx.E("trustanchor/certificate-mismatch",
|
|
287
|
+
"the certPath certificate's public key does not match pubKey (RFC 5914 sec. 2)");
|
|
288
|
+
}
|
|
289
|
+
var ski = null;
|
|
290
|
+
intrinsic.forEach(certificate.extensions || [], function (ext) { if (ext.oid === OID_SKI) ski = ext.value; });
|
|
291
|
+
if (ski === null) return;
|
|
292
|
+
var decoded;
|
|
293
|
+
try { decoded = CERT_EXT_DECODERS[OID_SKI](ski, ctx); }
|
|
294
|
+
catch (e) {
|
|
295
|
+
throw ctx.E("trustanchor/certificate-mismatch", "the certPath certificate's subjectKeyIdentifier does not read, so it cannot be compared to keyId (RFC 5914 sec. 2)", e);
|
|
296
|
+
}
|
|
297
|
+
if (!guard.crypto.constantTimeEqual(decoded, keyId)) {
|
|
298
|
+
throw ctx.E("trustanchor/certificate-mismatch",
|
|
299
|
+
"the certPath certificate's subjectKeyIdentifier does not match keyId (RFC 5914 sec. 2)");
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/** @internal RFC 5914 sec. 3's TrustAnchorChoice. The gap is the trap: there is no [0], the
|
|
304
|
+
* `certificate` arm is UNTAGGED, and both tagged arms are EXPLICIT. */
|
|
305
|
+
var TRUST_ANCHOR_CHOICE = schema.decode(function (n, ctx) {
|
|
306
|
+
if (n.tagClass === "universal" && n.tagNumber === TAGS.SEQUENCE) {
|
|
307
|
+
return { kind: "certificate", certificate: schema.walk(x509.certificateSchema, n, ctx).result,
|
|
308
|
+
tbsCert: null, taInfo: null };
|
|
309
|
+
}
|
|
310
|
+
if (n.tagClass === "context" && n.tagNumber === 1) {
|
|
311
|
+
if (!n.children || n.children.length !== 1) throw ctx.E("trustanchor/bad-anchor", "tbsCert [1] must wrap exactly one TBSCertificate");
|
|
312
|
+
/** @internal `tbsCertificateSchema` is a decode leaf, so the walk answers with the record
|
|
313
|
+
* itself rather than with a match object carrying it. */
|
|
314
|
+
return { kind: "tbsCert", certificate: null,
|
|
315
|
+
tbsCert: schema.walk(x509.tbsCertificateSchema, n.children[0], ctx), taInfo: null };
|
|
316
|
+
}
|
|
317
|
+
if (n.tagClass === "context" && n.tagNumber === 2) {
|
|
318
|
+
if (!n.children || n.children.length !== 1) throw ctx.E("trustanchor/bad-anchor", "taInfo [2] must wrap exactly one TrustAnchorInfo");
|
|
319
|
+
return { kind: "taInfo", certificate: null, tbsCert: null,
|
|
320
|
+
taInfo: schema.walk(TRUST_ANCHOR_INFO, n.children[0], ctx).result };
|
|
321
|
+
}
|
|
322
|
+
throw ctx.E("trustanchor/bad-anchor",
|
|
323
|
+
"a TrustAnchorChoice is an untagged Certificate, a [1] EXPLICIT TBSCertificate or a [2] EXPLICIT TrustAnchorInfo (RFC 5914 sec. 3)");
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
var TRUST_ANCHOR_LIST = schema.seqOf(TRUST_ANCHOR_CHOICE, {
|
|
327
|
+
assert: "sequence", min: 1, code: "trustanchor/bad-list", what: "TrustAnchorList",
|
|
328
|
+
build: function (m) { return { anchors: intrinsic.map(m.items, function (it) { return it.value; }) }; },
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* @primitive pki.schema.trustanchor.parse
|
|
333
|
+
* @signature pki.schema.trustanchor.parse(input, caps?) -> trustAnchorList
|
|
334
|
+
* @since 0.8.13
|
|
335
|
+
* @status stable
|
|
336
|
+
* @spec RFC 5914
|
|
337
|
+
* @related pki.schema.trustanchor.parseInfo, pki.trust.parseTrustAnchorList, pki.trustanchor.build
|
|
338
|
+
*
|
|
339
|
+
* Parse a DER `Buffer` or a PEM (`TRUST ANCHOR LIST`) string into `{ anchors }`, one entry per
|
|
340
|
+
* `TrustAnchorChoice`. Each entry names the arm it came from in `kind`, one of `"certificate"`,
|
|
341
|
+
* `"tbsCert"` or `"taInfo"`, and carries that arm's decoded value with the other two `null`.
|
|
342
|
+
*
|
|
343
|
+
* A `taInfo` entry is `{ version, pubKey, keyId, taTitle, certPath, exts, excludedExtensions,
|
|
344
|
+
* taTitleLangTag }`. `certPath` is the RFC 5914 section 2 `CertPathControls`, carrying the name the
|
|
345
|
+
* anchor is known by and the constraints a relying party applies: `policySet`, `policyFlags`,
|
|
346
|
+
* `nameConstr` and `pathLenConstraint`. `excludedExtensions` lists the identifiers of any
|
|
347
|
+
* extensions the RFC excludes from `exts` that the anchor carried anyway; those are left out of
|
|
348
|
+
* `exts`, because the RFC ignores them rather than refusing the anchor.
|
|
349
|
+
*
|
|
350
|
+
* Every rule RFC 5914 states is enforced: an empty `taName` is refused, a `policySet` carrying
|
|
351
|
+
* policy qualifiers is refused, a negative `pathLenConstraint` is refused, `requireExplicitPolicy`
|
|
352
|
+
* with no `policySet` is refused, and where `certPath` carries a certificate its subject, its
|
|
353
|
+
* public key and its `subjectKeyIdentifier` must match `taName`, `pubKey` and `keyId`.
|
|
354
|
+
*
|
|
355
|
+
* @opts
|
|
356
|
+
* - `maxBytes` / `maxDepth` / `maxItems` (number) -- decode caps for this parse. Each
|
|
357
|
+
* defaults to the matching `pki.C.LIMITS` figure and may only be set lower.
|
|
358
|
+
* @example
|
|
359
|
+
* var pair = await pki.key.generate("Ed25519");
|
|
360
|
+
* var spki = await pki.key.export(pair.publicKey);
|
|
361
|
+
* var der = pki.trustanchor.build({ anchors: [{ taInfo: {
|
|
362
|
+
* pubKey: spki, keyId: Buffer.alloc(20, 1), certPath: { taName: "CN=Example Root" } } }] });
|
|
363
|
+
* pki.schema.trustanchor.parse(der).anchors[0].kind; // -> "taInfo"
|
|
364
|
+
*/
|
|
365
|
+
var parse = pkix.makeParser({
|
|
366
|
+
pemLabel: "TRUST ANCHOR LIST", PemError: PemError, ErrorClass: TrustAnchorError,
|
|
367
|
+
prefix: "trustanchor", what: "trust anchor list", topSchema: TRUST_ANCHOR_LIST, ns: NS,
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* @primitive pki.schema.trustanchor.parseInfo
|
|
372
|
+
* @signature pki.schema.trustanchor.parseInfo(input, caps?) -> trustAnchorInfo
|
|
373
|
+
* @since 0.8.13
|
|
374
|
+
* @status stable
|
|
375
|
+
* @spec RFC 5914 sec. 2
|
|
376
|
+
* @related pki.schema.trustanchor.parse
|
|
377
|
+
*
|
|
378
|
+
* Parse a bare `TrustAnchorInfo`, the structure the `[2]` arm of a `TrustAnchorChoice` wraps, for a
|
|
379
|
+
* caller holding one on its own rather than inside a list. The result is the same record
|
|
380
|
+
* `parse().anchors[i].taInfo` carries.
|
|
381
|
+
*
|
|
382
|
+
* @opts
|
|
383
|
+
* - `maxBytes` / `maxDepth` / `maxItems` (number) -- decode caps for this parse.
|
|
384
|
+
* @example
|
|
385
|
+
* var pair = await pki.key.generate("Ed25519");
|
|
386
|
+
* var spki = await pki.key.export(pair.publicKey);
|
|
387
|
+
* var list = pki.schema.trustanchor.parse(pki.trustanchor.build({ anchors: [{ taInfo: {
|
|
388
|
+
* pubKey: spki, keyId: Buffer.alloc(20, 1), certPath: { taName: "CN=Example Root" } } }] }));
|
|
389
|
+
* list.anchors[0].taInfo.certPath.taName.dn; // -> "CN=Example Root"
|
|
390
|
+
*/
|
|
391
|
+
var parseInfo = pkix.makeParser({
|
|
392
|
+
pemLabel: "TRUST ANCHOR INFO", PemError: PemError, ErrorClass: TrustAnchorError,
|
|
393
|
+
prefix: "trustanchor", what: "trust anchor", topSchema: TRUST_ANCHOR_INFO, ns: NS,
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* @primitive pki.schema.trustanchor.pemDecode
|
|
398
|
+
* @signature pki.schema.trustanchor.pemDecode(text, label?) -> Buffer
|
|
399
|
+
* @since 0.8.13
|
|
400
|
+
* @status stable
|
|
401
|
+
* @spec RFC 7468, RFC 5914
|
|
402
|
+
* @related pki.schema.trustanchor.pemEncode
|
|
403
|
+
*
|
|
404
|
+
* Extract the DER bytes from a PEM trust anchor list (default label `TRUST ANCHOR LIST`).
|
|
405
|
+
*
|
|
406
|
+
* @example
|
|
407
|
+
* var pair = await pki.key.generate("Ed25519");
|
|
408
|
+
* var der = pki.trustanchor.build({ anchors: [{ taInfo: {
|
|
409
|
+
* pubKey: await pki.key.export(pair.publicKey), keyId: Buffer.alloc(20, 1),
|
|
410
|
+
* certPath: { taName: "CN=Example Root" } } }] });
|
|
411
|
+
* pki.schema.trustanchor.pemDecode(pki.schema.trustanchor.pemEncode(der)).equals(der); // -> true
|
|
412
|
+
*/
|
|
413
|
+
function pemDecode(text, label) { return pkix.pemDecode(text, label || "TRUST ANCHOR LIST", PemError); }
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* @primitive pki.schema.trustanchor.pemEncode
|
|
417
|
+
* @signature pki.schema.trustanchor.pemEncode(der, label?) -> string
|
|
418
|
+
* @since 0.8.13
|
|
419
|
+
* @status stable
|
|
420
|
+
* @spec RFC 7468
|
|
421
|
+
* @related pki.schema.trustanchor.pemDecode
|
|
422
|
+
*
|
|
423
|
+
* Wrap DER bytes in a PEM trust anchor list envelope (default label `TRUST ANCHOR LIST`).
|
|
424
|
+
*
|
|
425
|
+
* @example
|
|
426
|
+
* var pair = await pki.key.generate("Ed25519");
|
|
427
|
+
* var der = pki.trustanchor.build({ anchors: [{ taInfo: {
|
|
428
|
+
* pubKey: await pki.key.export(pair.publicKey), keyId: Buffer.alloc(20, 1),
|
|
429
|
+
* certPath: { taName: "CN=Example Root" } } }] });
|
|
430
|
+
* pki.schema.trustanchor.pemEncode(der).indexOf("-----BEGIN TRUST ANCHOR LIST-----"); // -> 0
|
|
431
|
+
*/
|
|
432
|
+
function pemEncode(der, label) { return pkix.pemEncode(der, label || "TRUST ANCHOR LIST", PemError); }
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* @internal A structural detector, mutually exclusive with the other formats: a TrustAnchorList is a
|
|
436
|
+
* SEQUENCE whose every element is a context [1] or [2], or a universal SEQUENCE that is itself a
|
|
437
|
+
* Certificate. The all-certificates case cannot be told from a bare certificate chain by shape
|
|
438
|
+
* alone, so it is NOT detected: a list carrying only untagged arms is read by calling `parse`
|
|
439
|
+
* explicitly. Detecting it would take `pki.schema.parse` on a Certificate away from `x509`.
|
|
440
|
+
*/
|
|
441
|
+
function matches(root) {
|
|
442
|
+
var kids = pkix.rootSequenceChildren(root, 1);
|
|
443
|
+
if (kids === null) return false;
|
|
444
|
+
var sawTagged = false;
|
|
445
|
+
for (var i = 0; i < kids.length; i++) {
|
|
446
|
+
var k = kids[i];
|
|
447
|
+
if (schema.isContextOneOf(k, [1, 2])) { sawTagged = true; continue; }
|
|
448
|
+
if (schema.isUniversal(k, TAGS.SEQUENCE) && x509.matches(k)) continue;
|
|
449
|
+
return false;
|
|
450
|
+
}
|
|
451
|
+
return sawTagged;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
module.exports = {
|
|
455
|
+
parse: parse,
|
|
456
|
+
parseInfo: parseInfo,
|
|
457
|
+
pemDecode: pemDecode,
|
|
458
|
+
pemEncode: pemEncode,
|
|
459
|
+
matches: matches,
|
|
460
|
+
/** @internal The schemas the builder encodes against, so the writer and the reader cannot
|
|
461
|
+
* disagree about a shape. Not on the curated `pki.schema.trustanchor` surface. */
|
|
462
|
+
trustAnchorListSchema: TRUST_ANCHOR_LIST,
|
|
463
|
+
trustAnchorInfoSchema: TRUST_ANCHOR_INFO,
|
|
464
|
+
policyFlagBits: POLICY_FLAG_BITS,
|
|
465
|
+
excludedExtensionOids: EXTS_EXCLUDED,
|
|
466
|
+
/** @internal The reader's own character count, so the writer bounds a title the same way rather
|
|
467
|
+
* than with a second reading of the SIZE constraint. */
|
|
468
|
+
titleChars: titleChars,
|
|
469
|
+
};
|
package/lib/schema-x509.js
CHANGED
|
@@ -120,6 +120,58 @@ var CERTIFICATE_TBS = schema.seq([
|
|
|
120
120
|
], { minTag: 1, maxTag: 3, unexpectedCode: "x509/bad-tbs", orderCode: "x509/bad-tbs" }),
|
|
121
121
|
], { assert: "sequence", code: "x509/bad-tbs", what: "tbsCertificate" });
|
|
122
122
|
|
|
123
|
+
/**
|
|
124
|
+
* @internal The tbsCertificate fields as a record, without the signature around them. RFC 5914
|
|
125
|
+
* sec. 3's TrustAnchorChoice carries a bare TBSCertificate in its [1] arm, which is a certificate
|
|
126
|
+
* with nothing to verify, and reading it with a second mapping is how the two would come to
|
|
127
|
+
* disagree about what a field is called. The envelope's own checks stay with the envelope: a bare
|
|
128
|
+
* TBSCertificate has no outer signatureAlgorithm to compare its own against.
|
|
129
|
+
*/
|
|
130
|
+
/** @internal A field written onto a record the caller receives, as an own data property. A plain
|
|
131
|
+
* assignment dispatches through an accessor an attacker planted on Object.prototype under the
|
|
132
|
+
* same name, which either throws out of a getter-only slot or swallows the parsed value. */
|
|
133
|
+
function _own(rec, name, value) {
|
|
134
|
+
intrinsic.defineProperty(rec, name, { value: value, enumerable: true, configurable: true, writable: true });
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function tbsRecord(tbs, ctx) {
|
|
138
|
+
var version = tbs.fields.version.value;
|
|
139
|
+
var issuer = tbs.fields.issuer.value.result;
|
|
140
|
+
if (!issuer.rdns.length) {
|
|
141
|
+
throw ctx.E("x509/bad-issuer", "issuer must be a non-empty distinguished name");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
var extField = tbs.fields.extensions;
|
|
145
|
+
var hasExtensions = !!(extField && extField.present);
|
|
146
|
+
if (hasExtensions && version !== 3) {
|
|
147
|
+
throw ctx.E("x509/bad-version", "extensions are only permitted in a v3 certificate");
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if ((tbs.fields.issuerUniqueID.present || tbs.fields.subjectUniqueID.present) && version < 2) {
|
|
151
|
+
throw ctx.E("x509/bad-version", "issuerUniqueID/subjectUniqueID require a v2 or v3 certificate (RFC 5280 sec. 4.1.2.8)");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
var serialNode = tbs.fields.serialNumber.node;
|
|
155
|
+
return {
|
|
156
|
+
version: version,
|
|
157
|
+
serialNumber: tbs.fields.serialNumber.value,
|
|
158
|
+
serialNumberHex: intrinsic.bufToString(serialNode.content, "hex"),
|
|
159
|
+
tbsSignatureAlgorithm: tbs.fields.signature.value.result,
|
|
160
|
+
issuer: issuer,
|
|
161
|
+
subject: tbs.fields.subject.value.result,
|
|
162
|
+
validity: tbs.fields.validity.value.result,
|
|
163
|
+
subjectPublicKeyInfo: tbs.fields.subjectPublicKeyInfo.value.result,
|
|
164
|
+
issuerUniqueID: tbs.fields.issuerUniqueID.present ? tbs.fields.issuerUniqueID.value : null,
|
|
165
|
+
subjectUniqueID: tbs.fields.subjectUniqueID.present ? tbs.fields.subjectUniqueID.value : null,
|
|
166
|
+
extensions: hasExtensions ? extField.value.result : [],
|
|
167
|
+
tbsBytes: tbs.node.bytes,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
var TBS_CERTIFICATE_ALONE = schema.decode(function (n, ctx) {
|
|
172
|
+
return tbsRecord(schema.walk(CERTIFICATE_TBS, n, ctx), ctx);
|
|
173
|
+
});
|
|
174
|
+
|
|
123
175
|
var CERTIFICATE = pkix.signedEnvelope(NS, CERTIFICATE_TBS, {
|
|
124
176
|
code: "x509/not-a-certificate", what: "Certificate",
|
|
125
177
|
build: function (e, ctx) {
|
|
@@ -129,39 +181,14 @@ var CERTIFICATE = pkix.signedEnvelope(NS, CERTIFICATE_TBS, {
|
|
|
129
181
|
throw ctx.E("x509/bad-signature-algorithm", "signatureAlgorithm must match tbsCertificate.signature (RFC 5280 sec. 4.1.1.2)");
|
|
130
182
|
}
|
|
131
183
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
if (hasExtensions && version !== 3) {
|
|
141
|
-
throw ctx.E("x509/bad-version", "extensions are only permitted in a v3 certificate");
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if ((tbs.fields.issuerUniqueID.present || tbs.fields.subjectUniqueID.present) && version < 2) {
|
|
145
|
-
throw ctx.E("x509/bad-version", "issuerUniqueID/subjectUniqueID require a v2 or v3 certificate (RFC 5280 sec. 4.1.2.8)");
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
var serialNode = tbs.fields.serialNumber.node;
|
|
149
|
-
return {
|
|
150
|
-
version: version,
|
|
151
|
-
serialNumber: tbs.fields.serialNumber.value,
|
|
152
|
-
serialNumberHex: intrinsic.bufToString(serialNode.content, "hex"),
|
|
153
|
-
signatureAlgorithm: e.signatureAlgorithm,
|
|
154
|
-
tbsSignatureAlgorithm: tbs.fields.signature.value.result,
|
|
155
|
-
issuer: issuer,
|
|
156
|
-
subject: tbs.fields.subject.value.result,
|
|
157
|
-
validity: tbs.fields.validity.value.result,
|
|
158
|
-
subjectPublicKeyInfo: tbs.fields.subjectPublicKeyInfo.value.result,
|
|
159
|
-
issuerUniqueID: tbs.fields.issuerUniqueID.present ? tbs.fields.issuerUniqueID.value : null,
|
|
160
|
-
subjectUniqueID: tbs.fields.subjectUniqueID.present ? tbs.fields.subjectUniqueID.value : null,
|
|
161
|
-
extensions: hasExtensions ? extField.value.result : [],
|
|
162
|
-
tbsBytes: e.tbsBytes,
|
|
163
|
-
signatureValue: e.signatureValue,
|
|
164
|
-
};
|
|
184
|
+
/** @internal Defined, not assigned: the record is an object literal, so it inherits
|
|
185
|
+
* Object.prototype, and a plain write under one of these names would dispatch through an
|
|
186
|
+
* accessor planted there instead of creating the certificate's own field. */
|
|
187
|
+
var out = tbsRecord(tbs, ctx);
|
|
188
|
+
_own(out, "signatureAlgorithm", e.signatureAlgorithm);
|
|
189
|
+
_own(out, "tbsBytes", e.tbsBytes);
|
|
190
|
+
_own(out, "signatureValue", e.signatureValue);
|
|
191
|
+
return out;
|
|
165
192
|
},
|
|
166
193
|
});
|
|
167
194
|
|
|
@@ -317,4 +344,10 @@ module.exports = {
|
|
|
317
344
|
matches: matches,
|
|
318
345
|
decodeExtensions: decodeExtensions,
|
|
319
346
|
decodeExtension: decodeExtension,
|
|
347
|
+
/** @internal The TBSCertificate and Certificate schemas, for a format that embeds one rather than
|
|
348
|
+
* being one: RFC 5914 sec. 3's TrustAnchorChoice carries a Certificate in its untagged arm and a
|
|
349
|
+
* TBSCertificate in its [1], and reading them with a second definition is how the two drift.
|
|
350
|
+
* Not on the curated `pki.schema.x509` surface. */
|
|
351
|
+
tbsCertificateSchema: TBS_CERTIFICATE_ALONE,
|
|
352
|
+
certificateSchema: CERTIFICATE,
|
|
320
353
|
};
|
package/lib/smime.js
CHANGED
|
@@ -108,7 +108,11 @@ function _hpHeaderList(headers) {
|
|
|
108
108
|
out.push({ name: name, value: v == null ? "" : String(v) });
|
|
109
109
|
});
|
|
110
110
|
} else if (typeof headers === "object") {
|
|
111
|
-
|
|
111
|
+
var hn = guard.identifier.optionNames(headers);
|
|
112
|
+
for (var hi = 0; hi < hn.length; hi++) {
|
|
113
|
+
var hv = headers[hn[hi]];
|
|
114
|
+
out.push({ name: hn[hi], value: hv == null ? "" : String(hv) });
|
|
115
|
+
}
|
|
112
116
|
} else {
|
|
113
117
|
throw _err("smime/bad-input", "opts.headers must be an object or an array of { name, value }");
|
|
114
118
|
}
|