@blamejs/pki 0.1.22 → 0.1.24
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 +18 -6
- package/index.js +22 -12
- package/lib/asn1-der.js +126 -68
- package/lib/constants.js +46 -21
- package/lib/ct.js +54 -39
- package/lib/est.js +717 -0
- package/lib/framework-error.js +74 -34
- package/lib/oid.js +116 -60
- package/lib/path-validate.js +302 -135
- package/lib/schema-all.js +65 -41
- 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 +78 -15
- package/lib/schema-csrattrs.js +371 -0
- 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 +295 -105
- package/lib/schema-smime.js +39 -39
- package/lib/schema-tsp.js +108 -59
- package/lib/schema-x509.js +36 -25
- package/lib/webcrypto.js +161 -26
- package/package.json +3 -2
- package/sbom.cdx.json +6 -6
package/lib/schema-pkix.js
CHANGED
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
// Copyright (c) blamejs contributors
|
|
3
3
|
"use strict";
|
|
4
4
|
//
|
|
5
|
-
// @internal
|
|
6
|
-
// parsers that compose these factories (pki.schema.x509, pki.schema.crl,
|
|
5
|
+
// @internal -- no operator-facing namespace. The documented surface is the
|
|
6
|
+
// parsers that compose these factories (pki.schema.x509, pki.schema.crl, ...).
|
|
7
7
|
//
|
|
8
8
|
// Shared PKIX structure-schema factories (RFC 5280). Each is a namespace-
|
|
9
9
|
// parameterized FACTORY: given an error namespace `ns` ({ prefix, E, oid }) it
|
|
10
10
|
// returns an asn1-schema that walks the corresponding ASN.1 structure and emits
|
|
11
11
|
// the caller's own <prefix>/* error codes. x509.js, crl.js, and future CMS/CSR
|
|
12
12
|
// parsers compose these so AlgorithmIdentifier / Name / Extension are defined
|
|
13
|
-
// once, not re-derived per format. This module is internal infrastructure
|
|
13
|
+
// once, not re-derived per format. This module is internal infrastructure -- the
|
|
14
14
|
// operator-facing surface is the parsers that consume it.
|
|
15
15
|
|
|
16
16
|
var asn1 = require("./asn1-der");
|
|
@@ -30,8 +30,19 @@ var PEM_RE = /-----BEGIN ([A-Z0-9 ]+)-----([\s\S]*?)-----END \1-----/;
|
|
|
30
30
|
|
|
31
31
|
// pemDecode(text, label, PemError): `label` (when truthy) is enforced, else the
|
|
32
32
|
// first block is taken. Applies the LIMITS.PEM_MAX_BYTES cap before scanning.
|
|
33
|
+
// The body must be CANONICAL base64 (RFC 4648 sec. 3.5): whole 4-character
|
|
34
|
+
// groups, no stray padding, zero trailing bits in the final symbol. Node's
|
|
35
|
+
// lenient Buffer.from would otherwise let several distinct PEM texts alias one
|
|
36
|
+
// DER (PEM-layer malleability) and silently drop trailing content bits.
|
|
33
37
|
function pemDecode(text, label, PemError) {
|
|
34
|
-
|
|
38
|
+
// The size cap is enforced on the raw byte length BEFORE the latin1 string
|
|
39
|
+
// copy: converting first would allocate a full-size string for an input the
|
|
40
|
+
// cap is about to reject, and a buffer above Node's max string length would
|
|
41
|
+
// escape as an untyped ERR_STRING_TOO_LONG instead of pem/too-large.
|
|
42
|
+
if (Buffer.isBuffer(text)) {
|
|
43
|
+
if (text.length > constants.LIMITS.PEM_MAX_BYTES) throw new PemError("pem/too-large", "PEM input exceeds size cap");
|
|
44
|
+
text = text.toString("latin1");
|
|
45
|
+
}
|
|
35
46
|
if (typeof text !== "string") throw new PemError("pem/bad-input", "pemDecode expects a string or Buffer");
|
|
36
47
|
if (text.length > constants.LIMITS.PEM_MAX_BYTES) throw new PemError("pem/too-large", "PEM input exceeds size cap");
|
|
37
48
|
var m = PEM_RE.exec(text);
|
|
@@ -39,11 +50,21 @@ function pemDecode(text, label, PemError) {
|
|
|
39
50
|
if (label && m[1] !== label) throw new PemError("pem/label-mismatch", "expected " + JSON.stringify(label) + " block, got " + JSON.stringify(m[1]));
|
|
40
51
|
var b64 = m[2].replace(/[\r\n\t ]+/g, "");
|
|
41
52
|
if (!/^[A-Za-z0-9+/]*={0,2}$/.test(b64)) throw new PemError("pem/bad-base64", "PEM body is not valid base64");
|
|
42
|
-
|
|
53
|
+
if (b64.length % 4 !== 0) throw new PemError("pem/bad-base64", "PEM base64 body must be whole 4-character groups (RFC 4648 sec. 3.5)");
|
|
54
|
+
var der = Buffer.from(b64, "base64");
|
|
55
|
+
if (der.toString("base64") !== b64) throw new PemError("pem/bad-base64", "PEM base64 body is not canonical (RFC 4648 sec. 3.5)");
|
|
56
|
+
return der;
|
|
43
57
|
}
|
|
44
58
|
|
|
59
|
+
// The label must be re-readable by this module's own PEM_RE (uppercase
|
|
60
|
+
// alphanumeric words separated by single spaces, a subset of the RFC 7468
|
|
61
|
+
// sec. 3 label grammar). Anything else (lowercase, a leading/trailing space,
|
|
62
|
+
// an embedded "-----" or newline) would emit armor pemDecode itself rejects
|
|
63
|
+
// or corrupt the armor grammar -- a config-time authoring fault, thrown here.
|
|
45
64
|
function pemEncode(der, label, PemError) {
|
|
46
|
-
if (typeof label !== "string" ||
|
|
65
|
+
if (typeof label !== "string" || !/^[A-Z0-9]+( [A-Z0-9]+)*$/.test(label)) {
|
|
66
|
+
throw new PemError("pem/bad-label", "pemEncode requires an uppercase A-Z0-9 label with single spaces");
|
|
67
|
+
}
|
|
47
68
|
var buf = Buffer.isBuffer(der) ? der : Buffer.from(der);
|
|
48
69
|
var b64 = buf.toString("base64").replace(/(.{64})/g, "$1\n").replace(/\n$/, "");
|
|
49
70
|
return "-----BEGIN " + label + "-----\n" + b64 + "\n-----END " + label + "-----\n";
|
|
@@ -63,8 +84,8 @@ function coerceToDer(input, opts) {
|
|
|
63
84
|
|
|
64
85
|
// Does a Buffer carry PEM armor (a .pem read with fs.readFileSync) rather than
|
|
65
86
|
// raw DER? It does iff "-----BEGIN" appears and everything before it is TEXT
|
|
66
|
-
// (UTF-8 BOM / whitespace / RFC 7468 explanatory preamble). DER is binary
|
|
67
|
-
// leading tag+length bytes are non-printable
|
|
87
|
+
// (UTF-8 BOM / whitespace / RFC 7468 explanatory preamble). DER is binary -- its
|
|
88
|
+
// leading tag+length bytes are non-printable -- so a non-SEQUENCE DER (a bare
|
|
68
89
|
// SET / INTEGER) is NOT misrouted here; it decodes and fails closed structurally.
|
|
69
90
|
function _isPemArmor(buf) {
|
|
70
91
|
var head = buf.slice(0, 4096).toString("latin1");
|
|
@@ -83,8 +104,8 @@ function _isPemArmor(buf) {
|
|
|
83
104
|
function decodeRoot(der, opts) {
|
|
84
105
|
// A format whose wire encoding is normatively BER (RFC 7292 PKCS#12) opts
|
|
85
106
|
// in to the codec's ber mode for the WHOLE decode. BER-vs-DER divergence is
|
|
86
|
-
// not always a decode-time throw
|
|
87
|
-
// OCTET STRING decodes strictly and only diverges at the typed reader
|
|
107
|
+
// not always a decode-time throw -- a definite-length constructed IMPLICIT
|
|
108
|
+
// OCTET STRING decodes strictly and only diverges at the typed reader -- so
|
|
88
109
|
// a strict-first-retry-on-throw boundary would miss legal BER shapes. A
|
|
89
110
|
// strict-DER input decodes identically under the ber mode, every other
|
|
90
111
|
// strictness verdict included.
|
|
@@ -101,7 +122,7 @@ function runParse(input, opts) {
|
|
|
101
122
|
return schema.walk(opts.topSchema, decodeRoot(coerceToDer(input, opts), opts), opts.ns).result;
|
|
102
123
|
}
|
|
103
124
|
|
|
104
|
-
// Distinguished-name attribute short labels (RFC 4514
|
|
125
|
+
// Distinguished-name attribute short labels (RFC 4514 sec. 3 + common use).
|
|
105
126
|
var DN_SHORT = {
|
|
106
127
|
commonName: "CN",
|
|
107
128
|
countryName: "C",
|
|
@@ -121,7 +142,7 @@ var DN_SHORT = {
|
|
|
121
142
|
// The error/OID namespace every format module walks its schema under:
|
|
122
143
|
// { prefix, E:(code,message,cause)=>new ErrorClass(...), oid }. Factored so a
|
|
123
144
|
// format declares it in one line instead of repeating the error-constructor
|
|
124
|
-
// closure (a caller that never passes a cause is unaffected
|
|
145
|
+
// closure (a caller that never passes a cause is unaffected -- withCause ignores
|
|
125
146
|
// an undefined third arg).
|
|
126
147
|
function makeNS(prefix, ErrorClass, oidModule) {
|
|
127
148
|
return { prefix: prefix, E: function (code, message, cause) { return new ErrorClass(code, message, cause); }, oid: oidModule };
|
|
@@ -129,10 +150,10 @@ function makeNS(prefix, ErrorClass, oidModule) {
|
|
|
129
150
|
|
|
130
151
|
// A bounded universal-INTEGER version reader. `accept` maps each legal wire value
|
|
131
152
|
// (as a decimal string) to its surfaced version number; any other value is a
|
|
132
|
-
// <prefix>/bad-version fault. The one genuine per-format divergence
|
|
153
|
+
// <prefix>/bad-version fault. The one genuine per-format divergence -- the cert
|
|
133
154
|
// rejects 0 and maps 1->2/2->3, a CRL accepts only 1->2, a CSR only 0->1, a PKCS#8
|
|
134
|
-
// 0->1/1->2
|
|
135
|
-
// RFC 2986
|
|
155
|
+
// 0->1/1->2 -- is expressed purely as the accept map (RFC 5280 sec. 4.1.2.1 / sec. 5.1.2.1,
|
|
156
|
+
// RFC 2986 sec. 4.1, RFC 5958 sec. 2). read.integer is strict, so an ENUMERATED-tagged
|
|
136
157
|
// version is rejected at the leaf (asn1/*).
|
|
137
158
|
function versionReader(ns, accept) {
|
|
138
159
|
return schema.decode(function (n) {
|
|
@@ -144,8 +165,20 @@ function versionReader(ns, accept) {
|
|
|
144
165
|
|
|
145
166
|
// opts.implicitTag (optional): read the AlgorithmIdentifier as a [tag] IMPLICIT
|
|
146
167
|
// SEQUENCE (a context-class constructed node whose children are algorithm + parameters),
|
|
147
|
-
// for the RFC 5652
|
|
168
|
+
// for the RFC 5652 sec. 6.2.4 pwri.keyDerivationAlgorithm [0]. With no opts the shape is a
|
|
148
169
|
// universal SEQUENCE, byte-identical to every existing caller.
|
|
170
|
+
//
|
|
171
|
+
// Parameter-rule tiering: only the params-MUST-BE-ABSENT families (ML-DSA,
|
|
172
|
+
// SLH-DSA, the Edwards/Montgomery curves -- new registrations with no legacy
|
|
173
|
+
// corpus) are enforced at parse time, in the build below. The CLASSICAL
|
|
174
|
+
// per-OID rules (RSA parameters MUST be NULL, RFC 3279 sec. 2.2.1/2.3.1;
|
|
175
|
+
// ecdsa-with-SHA2 parameters MUST be omitted, RFC 5758 sec. 3.2) are
|
|
176
|
+
// deliberately NOT parse-time rejects: a long tail of deployed certificates
|
|
177
|
+
// omits the RSA NULL, so the parse tier stays interop-lenient for those
|
|
178
|
+
// families and the strict classical check runs on the signature-verification
|
|
179
|
+
// path (path-validate's signature-algorithm registry), where the verdict
|
|
180
|
+
// matters. SPKI key AlgorithmIdentifiers follow the same tiering: lenient at
|
|
181
|
+
// parse, validated by the key-import layer that consumes them.
|
|
149
182
|
function algorithmIdentifier(ns, opts) {
|
|
150
183
|
opts = opts || {};
|
|
151
184
|
return schema.seq([
|
|
@@ -156,14 +189,16 @@ function algorithmIdentifier(ns, opts) {
|
|
|
156
189
|
arity: { min: 1 }, code: ns.prefix + "/bad-algorithm-identifier", what: "AlgorithmIdentifier",
|
|
157
190
|
build: function (m, ctx) {
|
|
158
191
|
var dotted = m.fields.algorithm.value;
|
|
159
|
-
//
|
|
160
|
-
// signature families (ML-DSA, SLH-DSA
|
|
161
|
-
//
|
|
162
|
-
//
|
|
163
|
-
//
|
|
192
|
+
// For every algorithm the registry marks parameters-absent -- the PQC
|
|
193
|
+
// signature families (ML-DSA RFC 9881, SLH-DSA RFC 9909/9814), the
|
|
194
|
+
// Edwards/Montgomery curves (RFC 8410), ML-KEM (RFC 9936), and the HKDF
|
|
195
|
+
// identifiers (RFC 8619) -- the `parameters` field MUST be absent: no
|
|
196
|
+
// explicit NULL, no bytes. Enforced ONCE here so every consumer of the
|
|
197
|
+
// shared AlgorithmIdentifier inherits the rule; the guard-parity bug
|
|
198
|
+
// class is structurally impossible.
|
|
164
199
|
if (m.fields.parameters.present && ctx.oid.paramsMustBeAbsent(dotted)) {
|
|
165
200
|
throw ctx.E(ctx.prefix + "/bad-algorithm-parameters",
|
|
166
|
-
"the " + (ctx.oid.name(dotted) || dotted) + " AlgorithmIdentifier parameters field MUST be absent
|
|
201
|
+
"the " + (ctx.oid.name(dotted) || dotted) + " AlgorithmIdentifier parameters field MUST be absent");
|
|
167
202
|
}
|
|
168
203
|
return { oid: dotted, name: ctx.oid.name(dotted) || null, parameters: m.fields.parameters.present ? m.fields.parameters.node.bytes : null };
|
|
169
204
|
},
|
|
@@ -173,44 +208,108 @@ function algorithmIdentifier(ns, opts) {
|
|
|
173
208
|
// attrValueToString(ns): the AttributeValue decode-leaf. A malformed KNOWN
|
|
174
209
|
// string type (invalid UTF-8, a non-IA5 byte, a PrintableString character
|
|
175
210
|
// outside its set, ...) surfaces as an asn1/bad-* content error and must fail
|
|
176
|
-
// closed
|
|
211
|
+
// closed -- do NOT hex-encode it away, or the decoder's strict string validation
|
|
177
212
|
// is silently bypassed on the DN path. A value that is simply not a decodable
|
|
178
213
|
// primitive string is NOT malformed and stays representable: an ANY-typed
|
|
179
214
|
// non-string tag (asn1/expected-string) or a constructed universal type such as
|
|
180
|
-
// a SEQUENCE (asn1/expected-primitive) renders per RFC 4514
|
|
215
|
+
// a SEQUENCE (asn1/expected-primitive) renders per RFC 4514 sec. 2.4 as "#" plus the
|
|
181
216
|
// hex of its FULL DER encoding (node.bytes), round-tripping intact.
|
|
217
|
+
// A GENUINE string whose first character is '#' or '\' is surfaced with a
|
|
218
|
+
// leading '\' escape (RFC 4514 sec. 2.4) so it can never collide with the
|
|
219
|
+
// '#'+hex form; the encode direction strips that one leading escape back off.
|
|
182
220
|
function attrValueToString(ns) {
|
|
183
221
|
return schema.decode(function (node) {
|
|
184
|
-
|
|
222
|
+
var s;
|
|
223
|
+
try { s = asn1.read.string(node); }
|
|
185
224
|
catch (e) {
|
|
186
225
|
if (!e || (e.code !== "asn1/expected-string" && e.code !== "asn1/expected-primitive")) {
|
|
187
226
|
throw ns.E(ns.prefix + "/bad-atv", "malformed string in attribute value: " + ((e && e.message) || String(e)));
|
|
188
227
|
}
|
|
189
228
|
return "#" + node.bytes.toString("hex");
|
|
190
229
|
}
|
|
230
|
+
if (s.charAt(0) === "#" || s.charAt(0) === "\\") return "\\" + s;
|
|
231
|
+
return s;
|
|
191
232
|
}, function (value) {
|
|
192
|
-
// encode: a
|
|
193
|
-
//
|
|
233
|
+
// encode: a leading '\' escapes the next character (the decode's escape of a
|
|
234
|
+
// literal '#'/'\'); a "#hex" form (RFC 4514 sec. 2.4) round-trips its raw DER
|
|
235
|
+
// verbatim -- the hex is validated (even-length hex digits encoding exactly one
|
|
236
|
+
// DER TLV) and throws rather than silently emitting truncated bytes, since
|
|
237
|
+
// Buffer.from(str, "hex") stops at the first invalid character. Any other
|
|
238
|
+
// string encodes as UTF8String (the decode does not preserve the original
|
|
194
239
|
// string type, so this is the canonical re-encoding, not a byte-exact one).
|
|
195
|
-
if (typeof value === "string" && value.charAt(0) === "
|
|
240
|
+
if (typeof value === "string" && value.charAt(0) === "\\") return asn1.build.utf8(value.slice(1));
|
|
241
|
+
if (typeof value === "string" && value.charAt(0) === "#") {
|
|
242
|
+
var hex = value.slice(1);
|
|
243
|
+
if (!/^(?:[0-9A-Fa-f]{2})+$/.test(hex)) {
|
|
244
|
+
throw ns.E(ns.prefix + "/bad-atv", "a #hex attribute value must be a non-empty even run of hex digits (RFC 4514 sec. 2.4)");
|
|
245
|
+
}
|
|
246
|
+
var raw = Buffer.from(hex, "hex");
|
|
247
|
+
try { asn1.decode(raw); }
|
|
248
|
+
catch (e) { throw ns.E(ns.prefix + "/bad-atv", "a #hex attribute value must encode exactly one DER TLV", e); }
|
|
249
|
+
return raw;
|
|
250
|
+
}
|
|
196
251
|
return asn1.build.utf8(value);
|
|
197
252
|
});
|
|
198
253
|
}
|
|
199
254
|
|
|
255
|
+
// The always-escape specials of RFC 4514 sec. 2.4, plus its positional rules:
|
|
256
|
+
// NUL escapes as \00, a trailing space as '\ ', and a leading '#' or space as
|
|
257
|
+
// '\#' / '\ ' -- without them the rendered dn is ambiguous (a literal '#0500'
|
|
258
|
+
// value would be indistinguishable from the hexstring form) and leading /
|
|
259
|
+
// trailing spaces would be silently significant.
|
|
200
260
|
function _escapeDnValue(v) {
|
|
201
|
-
|
|
261
|
+
var s = v.replace(/([,+"\\<>;])/g, "\\$1").split("\u0000").join("\\00");
|
|
262
|
+
if (s.length && s.charAt(s.length - 1) === " ") s = s.slice(0, -1) + "\\ ";
|
|
263
|
+
if (s.charAt(0) === "#" || s.charAt(0) === " ") s = "\\" + s;
|
|
264
|
+
return s;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// The dn RENDERING of a surfaced AttributeValue: the '#'-leading hex form (a
|
|
268
|
+
// non-string value) renders bare -- escaping its '#' would turn the RFC 4514
|
|
269
|
+
// hexstring form into an escaped literal -- while a genuine string (surfaced
|
|
270
|
+
// with its leading '#'/'\' escape, see attrValueToString) is unescaped back to
|
|
271
|
+
// the semantic string and then rendered with the full escape set.
|
|
272
|
+
function _dnDisplayValue(v) {
|
|
273
|
+
if (typeof v !== "string") return v;
|
|
274
|
+
if (v.charAt(0) === "#") return v;
|
|
275
|
+
return _escapeDnValue(v.charAt(0) === "\\" ? v.slice(1) : v);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// time(ns): the RFC 5280 Time production under its profile encoding rule
|
|
279
|
+
// (sec. 4.1.2.5 for certificate validity, restated for the CRL fields in
|
|
280
|
+
// sec. 5.1.2.4-5.1.2.6): a date through the year 2049 MUST be UTCTime and
|
|
281
|
+
// GeneralizedTime is reserved for 2050 onward, so a GeneralizedTime carrying a
|
|
282
|
+
// 1950..2049 date is a non-conforming second encoding of a representable value
|
|
283
|
+
// and is rejected (<prefix>/bad-time) -- accepting it would let two wire forms
|
|
284
|
+
// alias one abstract time (a parser-differential channel). The engine's
|
|
285
|
+
// schema.time already applies the cutover on ENCODE; this leaf composes it and
|
|
286
|
+
// enforces the same rule on DECODE. A GeneralizedTime-only field (an RFC 3161
|
|
287
|
+
// genTime, a CRL invalidityDate, an RFC 5755 AttCertValidityPeriod) must NOT
|
|
288
|
+
// take this leaf -- its syntax has no UTCTime alternative to cut over to.
|
|
289
|
+
function time(ns) {
|
|
290
|
+
var base = schema.time(ns);
|
|
291
|
+
return schema.decode(function (n, ctx) {
|
|
292
|
+
var d = base.fn(n, ctx);
|
|
293
|
+
if (n.tagNumber === asn1.TAGS.GENERALIZED_TIME) {
|
|
294
|
+
var y = d.getUTCFullYear();
|
|
295
|
+
if (y >= 1950 && y < 2050) {
|
|
296
|
+
throw ctx.E(ctx.prefix + "/bad-time", "a date through 2049 must be encoded as UTCTime, not GeneralizedTime (RFC 5280 sec. 4.1.2.5)");
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return d;
|
|
300
|
+
}, base.write);
|
|
202
301
|
}
|
|
203
302
|
|
|
204
303
|
// Name ::= RDNSequence ::= SEQUENCE OF RelativeDistinguishedName; RDN ::= SET OF
|
|
205
304
|
// AttributeTypeAndValue ::= SEQUENCE { type OID, value ANY }. The atv asserts
|
|
206
|
-
//
|
|
207
|
-
//
|
|
305
|
+
// its SEQUENCE tag (a SET-tagged body is a different ASN.1 type), and repeated
|
|
306
|
+
// RDN attribute types stay legal (no uniqueness).
|
|
208
307
|
function attributeTypeAndValue(ns) {
|
|
209
308
|
return schema.seq([
|
|
210
309
|
schema.field("type", schema.oidLeaf()),
|
|
211
310
|
schema.field("value", attrValueToString(ns)),
|
|
212
311
|
], {
|
|
213
|
-
assert: "
|
|
312
|
+
assert: "sequence", arity: { min: 2 }, code: ns.prefix + "/bad-atv", what: "AttributeTypeAndValue",
|
|
214
313
|
build: function (m, ctx) {
|
|
215
314
|
var typeOid = m.fields.type.value;
|
|
216
315
|
return { type: typeOid, name: ctx.oid.name(typeOid) || null, value: m.fields.value.value };
|
|
@@ -218,13 +317,13 @@ function attributeTypeAndValue(ns) {
|
|
|
218
317
|
});
|
|
219
318
|
}
|
|
220
319
|
function relativeDistinguishedName(ns) {
|
|
221
|
-
// RelativeDistinguishedName ::= SET SIZE (1..MAX)
|
|
320
|
+
// RelativeDistinguishedName ::= SET SIZE (1..MAX) -- an empty SET {} is malformed.
|
|
222
321
|
return schema.setOf(attributeTypeAndValue(ns), { assert: "set", min: 1, code: ns.prefix + "/bad-rdn", what: "RelativeDistinguishedName" });
|
|
223
322
|
}
|
|
224
|
-
// opts.implicitTag (optional): read the Name as a [tag] IMPLICIT RDNSequence
|
|
323
|
+
// opts.implicitTag (optional): read the Name as a [tag] IMPLICIT RDNSequence -- the
|
|
225
324
|
// context tag REPLACES the universal SEQUENCE tag, so the node is a context-class
|
|
226
325
|
// constructed [tag] whose children ARE the RDN SETs (the CRMF CertTemplate issuer
|
|
227
|
-
// [3] / subject [5] IMPLICIT arm, RFC 4211
|
|
326
|
+
// [3] / subject [5] IMPLICIT arm, RFC 4211 sec. 5). With no opts the shape is a bare
|
|
228
327
|
// universal SEQUENCE OF, byte-identical to every existing caller.
|
|
229
328
|
function name(ns, opts) {
|
|
230
329
|
opts = opts || {};
|
|
@@ -236,12 +335,15 @@ function name(ns, opts) {
|
|
|
236
335
|
var a = atvItem.value.result; // atvItem.value = the atv seq-match; .result = its build result
|
|
237
336
|
atvs.push(a);
|
|
238
337
|
var label = (a.name && DN_SHORT[a.name]) || a.name || a.type;
|
|
239
|
-
atvParts.push(label + "=" +
|
|
338
|
+
atvParts.push(label + "=" + _dnDisplayValue(a.value));
|
|
240
339
|
});
|
|
241
340
|
rdns.push(atvs);
|
|
242
341
|
parts.push(atvParts.join("+"));
|
|
243
342
|
});
|
|
244
|
-
|
|
343
|
+
// `bytes` is the raw Name DER (the universal SEQUENCE TLV, recovered from the
|
|
344
|
+
// wire node even in IMPLICIT [tag] mode) -- a byte-exact comparison a consumer
|
|
345
|
+
// needs when a rendered DN is not enough (the EST re-enroll subject MUST).
|
|
346
|
+
return { rdns: rdns, dn: parts.join(", "), bytes: opts.implicitTag != null ? asn1.sequenceTlv(m.node) : m.node.bytes };
|
|
245
347
|
}
|
|
246
348
|
if (opts.implicitTag != null) {
|
|
247
349
|
return schema.implicitSeqOf(opts.implicitTag, relativeDistinguishedName(ns), {
|
|
@@ -251,25 +353,85 @@ function name(ns, opts) {
|
|
|
251
353
|
assert: "sequence", code: ns.prefix + "/bad-name", what: "Name", build: nameBuild });
|
|
252
354
|
}
|
|
253
355
|
|
|
254
|
-
// GeneralName ::= CHOICE (RFC 5280
|
|
356
|
+
// GeneralName ::= CHOICE (RFC 5280 sec. 4.2.1.6). A validate-and-surface-raw leaf for a
|
|
255
357
|
// caller that keeps the value RAW but needs it to be a well-formed GeneralName: it
|
|
256
358
|
// checks the chosen alternative's tag, form (constructed vs primitive per X.690
|
|
257
|
-
//
|
|
359
|
+
// sec. 10.2), and content -- otherName [0] is a SEQUENCE { type-id OID, value [0] EXPLICIT
|
|
258
360
|
// }; x400Address [3] / ediPartyName [5] are non-empty constructed; directoryName [4]
|
|
259
361
|
// EXPLICIT wraps a valid Name; rfc822Name [1] / dNSName [2] / uniformResourceIdentifier
|
|
260
362
|
// [6] are primitive non-empty IA5String (7-bit); iPAddress [7] is a 4- or 16-octet
|
|
261
|
-
// OCTET STRING; registeredID [8] is a primitive OBJECT IDENTIFIER
|
|
363
|
+
// OCTET STRING; registeredID [8] is a primitive OBJECT IDENTIFIER -- then surfaces the
|
|
262
364
|
// value RAW ({ bytes, tagClass, tagNumber }). `opts.code` is the caller's error code
|
|
263
365
|
// (e.g. ocsp/bad-requestor-name, tsp/bad-tsa). Shared so the two parsers cannot drift.
|
|
264
366
|
// opts.decodeValue (default false): in addition to the raw { bytes, tagClass,
|
|
265
|
-
// tagNumber }, surface the DECODED `value` per arm
|
|
367
|
+
// tagNumber }, surface the DECODED `value` per arm -- IA5 text (string), an
|
|
266
368
|
// iPAddress Buffer, a directoryName { rdns, dn }, a registeredID OID string,
|
|
267
369
|
// or an otherName { typeId, valueBytes }. The path validator's name-constraint
|
|
268
370
|
// matcher needs the decoded value; the tsp/ocsp/attrcert consumers pass no
|
|
269
371
|
// flag and get the byte-identical raw-only shape.
|
|
270
372
|
// opts.subtreeBase (default false): the GeneralSubtree.base form (RFC 5280
|
|
271
|
-
//
|
|
373
|
+
// sec. 4.2.1.10) -- an iPAddress base is an address+mask (8 octets IPv4 / 32 IPv6),
|
|
272
374
|
// NOT the 4/16-octet SAN address form. Only the iPAddress size rule changes.
|
|
375
|
+
// A GeneralizedTime-only leaf. Several formats fix their times to
|
|
376
|
+
// GeneralizedTime and reject UTCTime (OCSP RFC 6960, TSP RFC 3161 sec. 2.4.2,
|
|
377
|
+
// CMP RFC 9810 sec. 5.1.1, attribute-certificate validity RFC 5755 sec. 4.2.6);
|
|
378
|
+
// ONE ns-parameterized factory owns the tag assert so the per-format code and
|
|
379
|
+
// message differ but the check cannot drift. opts.allowFractional enables the
|
|
380
|
+
// X.690 sec. 11.7 fractional-seconds profile (the RFC 3161 genTime).
|
|
381
|
+
function generalizedTime(ns, opts) {
|
|
382
|
+
opts = opts || {};
|
|
383
|
+
var code = opts.code || (ns.prefix + "/bad-time");
|
|
384
|
+
var message = opts.message || "the time must be a GeneralizedTime";
|
|
385
|
+
var readOpts = opts.allowFractional ? { allowFractional: true } : undefined;
|
|
386
|
+
return schema.decode(function (n, ctx) {
|
|
387
|
+
if (n.tagClass !== "universal" || n.tagNumber !== asn1.TAGS.GENERALIZED_TIME) {
|
|
388
|
+
throw ctx.E(code, message);
|
|
389
|
+
}
|
|
390
|
+
return asn1.read.time(n, readOpts);
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// A UTF8String-only leaf -- the PKIFreeText element shape (RFC 3161 sec. 2.4.2,
|
|
395
|
+
// RFC 9810 sec. 5.1.1) shared by the TSP and CMP parsers.
|
|
396
|
+
function utf8Text(ns, opts) {
|
|
397
|
+
opts = opts || {};
|
|
398
|
+
var code = opts.code || (ns.prefix + "/bad-freetext");
|
|
399
|
+
var message = opts.message || "the element must be a UTF8String";
|
|
400
|
+
return schema.decode(function (n, ctx) {
|
|
401
|
+
if (n.tagClass !== "universal" || n.tagNumber !== asn1.TAGS.UTF8_STRING) {
|
|
402
|
+
throw ctx.E(code, message);
|
|
403
|
+
}
|
|
404
|
+
return asn1.read.string(n);
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// A raw NON-EMPTY universal SEQUENCE leaf -- surfaces the exact TLV bytes of an
|
|
409
|
+
// embedded element (a Certificate, a CertificateList, a PKIPublicationInfo)
|
|
410
|
+
// after asserting the SEQUENCE tag and at least one child. An empty SEQUENCE
|
|
411
|
+
// (30 00) has the right tag but is none of those structures, so it rejects
|
|
412
|
+
// rather than surfacing degenerate bytes to certificate processing.
|
|
413
|
+
function rawNonEmptySequence(ns, opts) {
|
|
414
|
+
opts = opts || {};
|
|
415
|
+
var code = opts.code || (ns.prefix + "/bad-sequence");
|
|
416
|
+
var message = opts.message || "expected a non-empty universal SEQUENCE";
|
|
417
|
+
return schema.decode(function (n, ctx) {
|
|
418
|
+
if (!(n.tagClass === "universal" && n.tagNumber === asn1.TAGS.SEQUENCE && n.children && n.children.length >= 1)) {
|
|
419
|
+
throw ctx.E(code, message);
|
|
420
|
+
}
|
|
421
|
+
return n.bytes;
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// CRLReason ::= ENUMERATED (RFC 5280 sec. 5.3.1) -- value 7 is unused/reserved.
|
|
426
|
+
// The ONE canonical value->name table every consumer validates against: the
|
|
427
|
+
// CRL reasonCode decoder surfaces the numeric code, the OCSP RevokedInfo
|
|
428
|
+
// decoder the name; both draw the legal set from here so they cannot drift.
|
|
429
|
+
var CRL_REASON_NAMES = {
|
|
430
|
+
"0": "unspecified", "1": "keyCompromise", "2": "cACompromise", "3": "affiliationChanged",
|
|
431
|
+
"4": "superseded", "5": "cessationOfOperation", "6": "certificateHold",
|
|
432
|
+
"8": "removeFromCRL", "9": "privilegeWithdrawn", "10": "aACompromise",
|
|
433
|
+
};
|
|
434
|
+
|
|
273
435
|
var GN_CONSTRUCTED = { 0: 1, 3: 1, 4: 1, 5: 1 };
|
|
274
436
|
var GN_IA5 = { 1: 1, 2: 1, 6: 1 };
|
|
275
437
|
function generalName(ns, opts) {
|
|
@@ -280,13 +442,13 @@ function generalName(ns, opts) {
|
|
|
280
442
|
var NAME = name(ns);
|
|
281
443
|
return schema.decode(function (n, ctx) {
|
|
282
444
|
if (n.tagClass !== "context" || n.tagNumber < 0 || n.tagNumber > 8) {
|
|
283
|
-
throw ctx.E(code, "value must be a GeneralName (context tag [0]..[8]) (RFC 5280
|
|
445
|
+
throw ctx.E(code, "value must be a GeneralName (context tag [0]..[8]) (RFC 5280 sec. 4.2.1.6)");
|
|
284
446
|
}
|
|
285
447
|
var t = n.tagNumber;
|
|
286
448
|
var constructed = !!n.children;
|
|
287
449
|
var value;
|
|
288
450
|
if (GN_CONSTRUCTED[t]) {
|
|
289
|
-
if (!constructed || n.children.length < 1) throw ctx.E(code, "GeneralName [" + t + "] must be a non-empty constructed value (RFC 5280
|
|
451
|
+
if (!constructed || n.children.length < 1) throw ctx.E(code, "GeneralName [" + t + "] must be a non-empty constructed value (RFC 5280 sec. 4.2.1.6)");
|
|
290
452
|
if (t === 0) {
|
|
291
453
|
// otherName ::= SEQUENCE { type-id OBJECT IDENTIFIER, value [0] EXPLICIT ANY }.
|
|
292
454
|
if (n.children.length !== 2) throw ctx.E(code, "GeneralName otherName [0] must be a SEQUENCE { type-id, value [0] }");
|
|
@@ -299,17 +461,17 @@ function generalName(ns, opts) {
|
|
|
299
461
|
}
|
|
300
462
|
if (decodeValue) value = { typeId: typeId, valueBytes: ov.children[0].bytes };
|
|
301
463
|
} else if (t === 4) {
|
|
302
|
-
// directoryName [4] EXPLICIT Name
|
|
464
|
+
// directoryName [4] EXPLICIT Name -- validate the wrapped RDNSequence.
|
|
303
465
|
if (n.children.length !== 1) throw ctx.E(code, "GeneralName directoryName [4] must wrap exactly one Name");
|
|
304
466
|
var dnMatch = schema.walk(NAME, n.children[0], ctx);
|
|
305
467
|
if (decodeValue) value = dnMatch.result;
|
|
306
468
|
}
|
|
307
469
|
} else {
|
|
308
|
-
if (constructed) throw ctx.E(code, "GeneralName [" + t + "] must be primitive (X.690
|
|
470
|
+
if (constructed) throw ctx.E(code, "GeneralName [" + t + "] must be primitive (X.690 sec. 10.2)");
|
|
309
471
|
if (GN_IA5[t]) {
|
|
310
472
|
if (n.content.length === 0) throw ctx.E(code, "GeneralName [" + t + "] must be a non-empty IA5String");
|
|
311
473
|
for (var i = 0; i < n.content.length; i++) {
|
|
312
|
-
// 7-bit IA5, and no C0/DEL control byte
|
|
474
|
+
// 7-bit IA5, and no C0/DEL control byte -- an embedded NUL/control in a
|
|
313
475
|
// dNSName/rfc822Name/URI enables a name-truncation/confusion bypass
|
|
314
476
|
// (CVE-2009-2408 class) downstream, so reject it at decode.
|
|
315
477
|
if (n.content[i] < 0x20 || n.content[i] > 0x7e) throw ctx.E(code, "GeneralName [" + t + "] must be a printable IA5String (no control bytes)");
|
|
@@ -317,7 +479,7 @@ function generalName(ns, opts) {
|
|
|
317
479
|
if (decodeValue) value = n.content.toString("latin1");
|
|
318
480
|
} else if (t === 7) {
|
|
319
481
|
if (subtreeBase) {
|
|
320
|
-
if (n.content.length !== 8 && n.content.length !== 32) throw ctx.E(code, "GeneralName iPAddress [7] subtree base must be an 8- or 32-octet address+mask (RFC 5280
|
|
482
|
+
if (n.content.length !== 8 && n.content.length !== 32) throw ctx.E(code, "GeneralName iPAddress [7] subtree base must be an 8- or 32-octet address+mask (RFC 5280 sec. 4.2.1.10)");
|
|
321
483
|
} else if (n.content.length !== 4 && n.content.length !== 16) {
|
|
322
484
|
throw ctx.E(code, "GeneralName iPAddress [7] must be a 4- or 16-octet address");
|
|
323
485
|
}
|
|
@@ -337,14 +499,14 @@ function generalName(ns, opts) {
|
|
|
337
499
|
});
|
|
338
500
|
}
|
|
339
501
|
|
|
340
|
-
// GeneralNames ::= SEQUENCE OF GeneralName (RFC 5280
|
|
502
|
+
// GeneralNames ::= SEQUENCE OF GeneralName (RFC 5280 sec. 4.2.1.6), SIZE (1..MAX). Every
|
|
341
503
|
// element is validated by generalName (its CHOICE alternative's form + content) and
|
|
342
504
|
// surfaced raw, so a caller carrying a GeneralNames field cannot accept a malformed
|
|
343
505
|
// element by treating the whole sequence as opaque bytes. Returns { names, bytes }
|
|
344
|
-
// where each `names[i]` is the generalName leaf's return
|
|
345
|
-
// tagNumber } and, when opts.decodeValue is set, the decoded `value`
|
|
506
|
+
// where each `names[i]` is the generalName leaf's return -- { bytes, tagClass,
|
|
507
|
+
// tagNumber } and, when opts.decodeValue is set, the decoded `value` -- and `bytes`
|
|
346
508
|
// is the raw outer DER.
|
|
347
|
-
// `opts.implicitTag` handles a [tag] IMPLICIT GeneralNames
|
|
509
|
+
// `opts.implicitTag` handles a [tag] IMPLICIT GeneralNames -- the context tag REPLACES
|
|
348
510
|
// the universal SEQUENCE tag (RFC 5755 Holder.entityName [1]); otherwise it is a bare
|
|
349
511
|
// universal SEQUENCE OF. `opts.code` is the caller's error code. Shared so the x509 /
|
|
350
512
|
// attribute-certificate / (future) CRMF parsers validate a GeneralNames identically.
|
|
@@ -361,17 +523,17 @@ function generalNames(ns, opts) {
|
|
|
361
523
|
return schema.seqOf(gn, { assert: "sequence", min: 1, code: code, what: opts.what || "GeneralNames", build: build });
|
|
362
524
|
}
|
|
363
525
|
|
|
364
|
-
// certExtensionDecoders(ns)
|
|
526
|
+
// certExtensionDecoders(ns) -- the ns-parameterized RFC 5280 sec. 4.2.1 extension
|
|
365
527
|
// VALUE decoders. `x509.parse` surfaces each extension as { oid, name, critical,
|
|
366
528
|
// value } with `value` the raw inner OCTET-STRING content (a Buffer); the path
|
|
367
529
|
// validator and the future complete-extension-set surface need the STRUCTURE
|
|
368
530
|
// inside. Each decoder takes that raw Buffer and returns the decoded value, or
|
|
369
531
|
// throws a typed `<prefix>/bad-*` code, mirroring the CRL's fail-closed
|
|
370
|
-
// `decodeExt` pattern (asn1.decode
|
|
532
|
+
// `decodeExt` pattern (asn1.decode -> read.* / schema.walk, wrapped). Returns
|
|
371
533
|
// { byOid } keyed by dotted OID so a consumer dispatches by ext.oid.
|
|
372
534
|
var _T = asn1.TAGS;
|
|
373
535
|
|
|
374
|
-
// assertPolicyQualifiers(qNode, fail)
|
|
536
|
+
// assertPolicyQualifiers(qNode, fail) -- RFC 5280 sec. 4.2.1.4: a PolicyInformation's
|
|
375
537
|
// policyQualifiers is a non-empty SEQUENCE OF PolicyQualifierInfo, each a SEQUENCE
|
|
376
538
|
// { policyQualifierId OID, qualifier } of EXACTLY two members led by an OID. The
|
|
377
539
|
// structure is validated fail-closed; the qualifier body stays opaque (surfaced
|
|
@@ -382,11 +544,11 @@ var _T = asn1.TAGS;
|
|
|
382
544
|
// leaf fault as the error cause when one is available.
|
|
383
545
|
function assertPolicyQualifiers(qNode, fail) {
|
|
384
546
|
if (qNode.tagClass !== "universal" || qNode.tagNumber !== _T.SEQUENCE || !qNode.children || !qNode.children.length) {
|
|
385
|
-
fail("policyQualifiers must be a non-empty SEQUENCE (RFC 5280
|
|
547
|
+
fail("policyQualifiers must be a non-empty SEQUENCE (RFC 5280 sec. 4.2.1.4)");
|
|
386
548
|
}
|
|
387
549
|
qNode.children.forEach(function (pq) {
|
|
388
550
|
if (pq.tagClass !== "universal" || pq.tagNumber !== _T.SEQUENCE || !pq.children || pq.children.length !== 2) {
|
|
389
|
-
fail("policyQualifiers element must be a PolicyQualifierInfo SEQUENCE { policyQualifierId, qualifier } (RFC 5280
|
|
551
|
+
fail("policyQualifiers element must be a PolicyQualifierInfo SEQUENCE { policyQualifierId, qualifier } (RFC 5280 sec. 4.2.1.4)");
|
|
390
552
|
}
|
|
391
553
|
try { asn1.read.oid(pq.children[0]); } catch (e) { fail("PolicyQualifierInfo must lead with a policyQualifierId OID", e); }
|
|
392
554
|
});
|
|
@@ -404,7 +566,7 @@ function certExtensionDecoders(ns) {
|
|
|
404
566
|
function seqChildren(buf, code, what) {
|
|
405
567
|
var n = decodeTop(buf, code, what);
|
|
406
568
|
if (n.tagClass !== "universal" || n.tagNumber !== _T.SEQUENCE || !n.children) {
|
|
407
|
-
throw ns.E(code, what + " must be a SEQUENCE (RFC 5280
|
|
569
|
+
throw ns.E(code, what + " must be a SEQUENCE (RFC 5280 sec. 4.2.1)");
|
|
408
570
|
}
|
|
409
571
|
return n.children;
|
|
410
572
|
}
|
|
@@ -421,13 +583,13 @@ function certExtensionDecoders(ns) {
|
|
|
421
583
|
if (kids[i] && kids[i].tagClass === "universal" && kids[i].tagNumber === _T.BOOLEAN) {
|
|
422
584
|
var v;
|
|
423
585
|
try { v = asn1.read.boolean(kids[i]); } catch (e) { throw ns.E(C, "BasicConstraints cA must be a BOOLEAN", e); }
|
|
424
|
-
if (v !== true) throw ns.E(C, "BasicConstraints cA DEFAULT FALSE must be omitted, not an explicit FALSE (X.690
|
|
586
|
+
if (v !== true) throw ns.E(C, "BasicConstraints cA DEFAULT FALSE must be omitted, not an explicit FALSE (X.690 sec. 11.5)");
|
|
425
587
|
cA = true; i++;
|
|
426
588
|
}
|
|
427
589
|
if (kids[i] && kids[i].tagClass === "universal" && kids[i].tagNumber === _T.INTEGER) {
|
|
428
|
-
if (!cA) throw ns.E(C, "BasicConstraints pathLenConstraint is only permitted when cA is TRUE (RFC 5280
|
|
590
|
+
if (!cA) throw ns.E(C, "BasicConstraints pathLenConstraint is only permitted when cA is TRUE (RFC 5280 sec. 4.2.1.9)");
|
|
429
591
|
var pl = readInt(kids[i], C, "pathLenConstraint");
|
|
430
|
-
// Bounded before Number() narrowing
|
|
592
|
+
// Bounded before Number() narrowing -- a value past the safe-integer range
|
|
431
593
|
// would round silently and be compared as a path-length ceiling, so it is
|
|
432
594
|
// exact-or-rejected (the same rule the RSASSA-PSS/PKCS#12 counters follow).
|
|
433
595
|
if (pl < 0n || pl > 2147483647n) throw ns.E(C, "BasicConstraints pathLenConstraint must be a non-negative integer within range");
|
|
@@ -443,15 +605,15 @@ function certExtensionDecoders(ns) {
|
|
|
443
605
|
function keyUsage(buf) {
|
|
444
606
|
var C = ns.prefix + "/bad-key-usage";
|
|
445
607
|
var n = decodeTop(buf, C, "KeyUsage");
|
|
446
|
-
if (n.tagClass !== "universal" || n.tagNumber !== _T.BIT_STRING) throw ns.E(C, "KeyUsage must be a BIT STRING (RFC 5280
|
|
608
|
+
if (n.tagClass !== "universal" || n.tagNumber !== _T.BIT_STRING) throw ns.E(C, "KeyUsage must be a BIT STRING (RFC 5280 sec. 4.2.1.3)");
|
|
447
609
|
var bs;
|
|
448
610
|
try { bs = asn1.read.bitString(n); } catch (e) { throw ns.E(C, "KeyUsage must be a well-formed BIT STRING", e); }
|
|
449
|
-
// At least one bit MUST be set (RFC 5280
|
|
611
|
+
// At least one bit MUST be set (RFC 5280 sec. 4.2.1.3) -- an all-zero value (a
|
|
450
612
|
// non-empty byte run of only zero bits, e.g. 03 02 07 00) is malformed too.
|
|
451
613
|
var anyBit = false;
|
|
452
614
|
for (var z = 0; z < bs.bytes.length; z++) { if (bs.bytes[z] !== 0) { anyBit = true; break; } }
|
|
453
|
-
if (!anyBit) throw ns.E(C, "KeyUsage must assert at least one bit (RFC 5280
|
|
454
|
-
// KeyUsage is a NamedBitList
|
|
615
|
+
if (!anyBit) throw ns.E(C, "KeyUsage must assert at least one bit (RFC 5280 sec. 4.2.1.3)");
|
|
616
|
+
// KeyUsage is a NamedBitList -- enforce the shared X.690 sec. 11.2.2 minimal-DER rule.
|
|
455
617
|
schema.assertMinimalNamedBits(bs.unusedBits, bs.bytes, function (m) { throw ns.E(C, m); });
|
|
456
618
|
var out = {};
|
|
457
619
|
KU_BITS.forEach(function (nm, bit) {
|
|
@@ -464,9 +626,9 @@ function certExtensionDecoders(ns) {
|
|
|
464
626
|
// GeneralSubtree ::= SEQUENCE { base GeneralName, minimum [0] DEFAULT 0, maximum [1] OPTIONAL }
|
|
465
627
|
function subtreeList(node, C) {
|
|
466
628
|
// node is the [0]/[1] IMPLICIT SEQUENCE OF GeneralSubtree (context-constructed).
|
|
467
|
-
// GeneralSubtrees is SIZE(1..MAX)
|
|
629
|
+
// GeneralSubtrees is SIZE(1..MAX) -- an explicitly present but empty subtree
|
|
468
630
|
// list (a0 00) absorbs no constraint and is malformed, not a no-op.
|
|
469
|
-
if (!node.children || node.children.length < 1) throw ns.E(C, "NameConstraints permittedSubtrees/excludedSubtrees must be a non-empty GeneralSubtrees (SIZE 1..MAX, RFC 5280
|
|
631
|
+
if (!node.children || node.children.length < 1) throw ns.E(C, "NameConstraints permittedSubtrees/excludedSubtrees must be a non-empty GeneralSubtrees (SIZE 1..MAX, RFC 5280 sec. 4.2.1.10)");
|
|
470
632
|
return node.children.map(function (st) {
|
|
471
633
|
if (st.tagClass !== "universal" || st.tagNumber !== _T.SEQUENCE || !st.children || st.children.length < 1) {
|
|
472
634
|
throw ns.E(C, "GeneralSubtree must be a SEQUENCE { base, minimum?, maximum? }");
|
|
@@ -475,8 +637,8 @@ function certExtensionDecoders(ns) {
|
|
|
475
637
|
for (var j = 1; j < st.children.length; j++) {
|
|
476
638
|
var f = st.children[j];
|
|
477
639
|
if (f.tagClass !== "context" || (f.tagNumber !== 0 && f.tagNumber !== 1)) throw ns.E(C, "GeneralSubtree has an unexpected field");
|
|
478
|
-
if (f.tagNumber === 0) throw ns.E(C, "GeneralSubtree minimum DEFAULT 0 must be omitted (RFC 5280
|
|
479
|
-
if (f.tagNumber === 1) throw ns.E(C, "GeneralSubtree maximum is not permitted in the RFC 5280 profile (
|
|
640
|
+
if (f.tagNumber === 0) throw ns.E(C, "GeneralSubtree minimum DEFAULT 0 must be omitted (RFC 5280 sec. 4.2.1.10 requires minimum = 0)");
|
|
641
|
+
if (f.tagNumber === 1) throw ns.E(C, "GeneralSubtree maximum is not permitted in the RFC 5280 profile (sec. 4.2.1.10)");
|
|
480
642
|
}
|
|
481
643
|
return { base: base };
|
|
482
644
|
});
|
|
@@ -493,7 +655,7 @@ function certExtensionDecoders(ns) {
|
|
|
493
655
|
else if (k.tagNumber === 1) { if (sawE) throw ns.E(C, "duplicate excludedSubtrees"); sawE = true; excluded = subtreeList(k, C); }
|
|
494
656
|
else throw ns.E(C, "NameConstraints has an unexpected field [" + k.tagNumber + "]");
|
|
495
657
|
});
|
|
496
|
-
if (!sawP && !sawE) throw ns.E(C, "NameConstraints must contain permittedSubtrees or excludedSubtrees (RFC 5280
|
|
658
|
+
if (!sawP && !sawE) throw ns.E(C, "NameConstraints must contain permittedSubtrees or excludedSubtrees (RFC 5280 sec. 4.2.1.10)");
|
|
497
659
|
return { permittedSubtrees: permitted, excludedSubtrees: excluded };
|
|
498
660
|
}
|
|
499
661
|
|
|
@@ -501,18 +663,18 @@ function certExtensionDecoders(ns) {
|
|
|
501
663
|
function certificatePolicies(buf) {
|
|
502
664
|
var C = ns.prefix + "/bad-policy";
|
|
503
665
|
var kids = seqChildren(buf, C, "CertificatePolicies");
|
|
504
|
-
if (kids.length < 1) throw ns.E(C, "CertificatePolicies must contain at least one PolicyInformation (RFC 5280
|
|
666
|
+
if (kids.length < 1) throw ns.E(C, "CertificatePolicies must contain at least one PolicyInformation (RFC 5280 sec. 4.2.1.4)");
|
|
505
667
|
var seen = {};
|
|
506
668
|
return kids.map(function (pi) {
|
|
507
669
|
// PolicyInformation ::= SEQUENCE { policyIdentifier, policyQualifiers
|
|
508
|
-
// SEQUENCE SIZE(1..MAX) OPTIONAL }
|
|
670
|
+
// SEQUENCE SIZE(1..MAX) OPTIONAL } -- exactly one or two fields; a second
|
|
509
671
|
// field, if present, MUST be a SEQUENCE. Extra/mis-typed fields are malformed.
|
|
510
672
|
if (pi.tagClass !== "universal" || pi.tagNumber !== _T.SEQUENCE || !pi.children || pi.children.length < 1 || pi.children.length > 2) {
|
|
511
673
|
throw ns.E(C, "PolicyInformation must be a SEQUENCE { policyIdentifier, policyQualifiers? }");
|
|
512
674
|
}
|
|
513
675
|
var pid;
|
|
514
676
|
try { pid = asn1.read.oid(pi.children[0]); } catch (e) { throw ns.E(C, "PolicyInformation policyIdentifier must be an OBJECT IDENTIFIER", e); }
|
|
515
|
-
if (seen[pid]) throw ns.E(C, "duplicate policy OID " + pid + " (RFC 5280
|
|
677
|
+
if (seen[pid]) throw ns.E(C, "duplicate policy OID " + pid + " (RFC 5280 sec. 4.2.1.4)");
|
|
516
678
|
seen[pid] = true;
|
|
517
679
|
var qualifiers = null;
|
|
518
680
|
if (pi.children.length > 1) {
|
|
@@ -530,7 +692,7 @@ function certExtensionDecoders(ns) {
|
|
|
530
692
|
function policyMappings(buf) {
|
|
531
693
|
var C = ns.prefix + "/bad-policy";
|
|
532
694
|
var kids = seqChildren(buf, C, "PolicyMappings");
|
|
533
|
-
if (kids.length < 1) throw ns.E(C, "PolicyMappings must contain at least one mapping (RFC 5280
|
|
695
|
+
if (kids.length < 1) throw ns.E(C, "PolicyMappings must contain at least one mapping (RFC 5280 sec. 4.2.1.5)");
|
|
534
696
|
return kids.map(function (mp) {
|
|
535
697
|
if (mp.tagClass !== "universal" || mp.tagNumber !== _T.SEQUENCE || !mp.children || mp.children.length !== 2) {
|
|
536
698
|
throw ns.E(C, "policy mapping must be a SEQUENCE { issuerDomainPolicy, subjectDomainPolicy }");
|
|
@@ -546,7 +708,7 @@ function certExtensionDecoders(ns) {
|
|
|
546
708
|
function policyConstraints(buf) {
|
|
547
709
|
var C = ns.prefix + "/bad-policy";
|
|
548
710
|
var kids = seqChildren(buf, C, "PolicyConstraints");
|
|
549
|
-
if (kids.length < 1) throw ns.E(C, "PolicyConstraints must contain at least one field (RFC 5280
|
|
711
|
+
if (kids.length < 1) throw ns.E(C, "PolicyConstraints must contain at least one field (RFC 5280 sec. 4.2.1.11)");
|
|
550
712
|
var rep = null, ipm = null, pcLastTag = -1;
|
|
551
713
|
kids.forEach(function (k) {
|
|
552
714
|
if (k.tagClass !== "context") throw ns.E(C, "PolicyConstraints fields are context-tagged [0]/[1]");
|
|
@@ -555,7 +717,7 @@ function certExtensionDecoders(ns) {
|
|
|
555
717
|
var v;
|
|
556
718
|
try { v = asn1.read.integerImplicit(k, k.tagNumber); } catch (e) { throw ns.E(C, "PolicyConstraints field must be an INTEGER", e); }
|
|
557
719
|
// Bounded before Number() narrowing (a skip count past the safe-integer
|
|
558
|
-
// range would round silently)
|
|
720
|
+
// range would round silently) -- exact-or-rejected.
|
|
559
721
|
if (v < 0n || v > 2147483647n) throw ns.E(C, "PolicyConstraints skip count must be a non-negative integer within range");
|
|
560
722
|
if (k.tagNumber === 0) rep = Number(v);
|
|
561
723
|
else if (k.tagNumber === 1) ipm = Number(v);
|
|
@@ -568,15 +730,15 @@ function certExtensionDecoders(ns) {
|
|
|
568
730
|
function inhibitAnyPolicy(buf) {
|
|
569
731
|
var C = ns.prefix + "/bad-policy";
|
|
570
732
|
var n = decodeTop(buf, C, "InhibitAnyPolicy");
|
|
571
|
-
if (n.tagClass !== "universal" || n.tagNumber !== _T.INTEGER) throw ns.E(C, "InhibitAnyPolicy must be an INTEGER (RFC 5280
|
|
733
|
+
if (n.tagClass !== "universal" || n.tagNumber !== _T.INTEGER) throw ns.E(C, "InhibitAnyPolicy must be an INTEGER (RFC 5280 sec. 4.2.1.14)");
|
|
572
734
|
var v = readInt(n, C, "InhibitAnyPolicy");
|
|
573
735
|
// Bounded before Number() narrowing (a skip count past the safe-integer
|
|
574
|
-
// range would round silently)
|
|
736
|
+
// range would round silently) -- exact-or-rejected.
|
|
575
737
|
if (v < 0n || v > 2147483647n) throw ns.E(C, "InhibitAnyPolicy skip count must be a non-negative integer within range");
|
|
576
738
|
return Number(v);
|
|
577
739
|
}
|
|
578
740
|
|
|
579
|
-
// subjectAltName / issuerAltName ::= GeneralNames
|
|
741
|
+
// subjectAltName / issuerAltName ::= GeneralNames -- decoded values surfaced.
|
|
580
742
|
function altName(buf) {
|
|
581
743
|
var C = ns.prefix + "/bad-extension-value";
|
|
582
744
|
var n = decodeTop(buf, C, "GeneralNames");
|
|
@@ -587,7 +749,7 @@ function certExtensionDecoders(ns) {
|
|
|
587
749
|
function extKeyUsage(buf) {
|
|
588
750
|
var C = ns.prefix + "/bad-extension-value";
|
|
589
751
|
var kids = seqChildren(buf, C, "ExtKeyUsage");
|
|
590
|
-
if (kids.length < 1) throw ns.E(C, "ExtKeyUsage must contain at least one KeyPurposeId (RFC 5280
|
|
752
|
+
if (kids.length < 1) throw ns.E(C, "ExtKeyUsage must contain at least one KeyPurposeId (RFC 5280 sec. 4.2.1.12)");
|
|
591
753
|
return kids.map(function (k) {
|
|
592
754
|
try { return asn1.read.oid(k); } catch (e) { throw ns.E(C, "ExtKeyUsage KeyPurposeId must be an OBJECT IDENTIFIER", e); }
|
|
593
755
|
});
|
|
@@ -614,7 +776,7 @@ function certExtensionDecoders(ns) {
|
|
|
614
776
|
else throw ns.E(C, "AuthorityKeyIdentifier has an unexpected field [" + k.tagNumber + "]");
|
|
615
777
|
});
|
|
616
778
|
if ((out.authorityCertIssuer === null) !== (out.authorityCertSerialNumber === null)) {
|
|
617
|
-
throw ns.E(C, "AuthorityKeyIdentifier authorityCertIssuer and authorityCertSerialNumber must both be present or both absent (RFC 5280
|
|
779
|
+
throw ns.E(C, "AuthorityKeyIdentifier authorityCertIssuer and authorityCertSerialNumber must both be present or both absent (RFC 5280 sec. 4.2.1.1)");
|
|
618
780
|
}
|
|
619
781
|
return out;
|
|
620
782
|
}
|
|
@@ -624,10 +786,10 @@ function certExtensionDecoders(ns) {
|
|
|
624
786
|
var C = ns.prefix + "/bad-extension-value";
|
|
625
787
|
var n = decodeTop(buf, C, "SubjectKeyIdentifier");
|
|
626
788
|
try { return Buffer.concat([asn1.read.octetString(n)]); }
|
|
627
|
-
catch (e) { throw ns.E(C, "SubjectKeyIdentifier must be an OCTET STRING (RFC 5280
|
|
789
|
+
catch (e) { throw ns.E(C, "SubjectKeyIdentifier must be an OCTET STRING (RFC 5280 sec. 4.2.1.2)", e); }
|
|
628
790
|
}
|
|
629
791
|
|
|
630
|
-
// signedCertificateTimestampList (RFC 6962
|
|
792
|
+
// signedCertificateTimestampList (RFC 6962 sec. 3.3) -- the extension value is a DER
|
|
631
793
|
// OCTET STRING wrapping the TLS-encoded SCT list. Delegated to the CT module,
|
|
632
794
|
// which owns the inner peel + the bounded TLS decode; structure is decoded, the
|
|
633
795
|
// signature stays raw and unverified. The registry contract is that a decoder
|
|
@@ -636,21 +798,29 @@ function certExtensionDecoders(ns) {
|
|
|
636
798
|
function sctList(buf) {
|
|
637
799
|
var C = ns.prefix + "/bad-extension-value";
|
|
638
800
|
try { return ct.parseSctList(buf); }
|
|
639
|
-
catch (e) { throw ns.E(C, "malformed signedCertificateTimestampList extension value (RFC 6962
|
|
801
|
+
catch (e) { throw ns.E(C, "malformed signedCertificateTimestampList extension value (RFC 6962 sec. 3.3)", e); }
|
|
640
802
|
}
|
|
641
803
|
|
|
642
|
-
// precertificatePoison (RFC 6962
|
|
804
|
+
// precertificatePoison (RFC 6962 sec. 3.1) -- a critical extension whose value is
|
|
643
805
|
// exactly ASN.1 NULL (05 00), the marker distinguishing a precertificate from
|
|
644
806
|
// a final certificate. Content-validated, not merely tag-checked.
|
|
645
807
|
function precertPoison(buf) {
|
|
646
808
|
var C = ns.prefix + "/bad-extension-value";
|
|
647
809
|
var n = decodeTop(buf, C, "PrecertificatePoison");
|
|
648
810
|
try { asn1.read.nullValue(n); }
|
|
649
|
-
catch (e) { throw ns.E(C, "the precertificate poison extension value must be ASN.1 NULL (RFC 6962
|
|
811
|
+
catch (e) { throw ns.E(C, "the precertificate poison extension value must be ASN.1 NULL (RFC 6962 sec. 3.1)", e); }
|
|
650
812
|
return { poison: true };
|
|
651
813
|
}
|
|
652
814
|
|
|
653
|
-
|
|
815
|
+
// byName returns undefined for an unregistered name; keying a decoder row
|
|
816
|
+
// under "undefined" would silently drop that extension from dispatch (the
|
|
817
|
+
// consumer would treat the extension as structurally unvalidated / absent),
|
|
818
|
+
// so a typo'd key-list entry must fail here, at module load.
|
|
819
|
+
function O(nm) {
|
|
820
|
+
var d = ns.oid.byName(nm);
|
|
821
|
+
if (typeof d !== "string") throw new TypeError("certExtensionDecoders: " + JSON.stringify(nm) + " is not a registered OID name");
|
|
822
|
+
return d;
|
|
823
|
+
}
|
|
654
824
|
var byOid = {};
|
|
655
825
|
byOid[O("basicConstraints")] = basicConstraints;
|
|
656
826
|
byOid[O("keyUsage")] = keyUsage;
|
|
@@ -673,10 +843,10 @@ function certExtensionDecoders(ns) {
|
|
|
673
843
|
// extnValue OCTET STRING }. `critical` is a universal BOOLEAN present-by-count
|
|
674
844
|
// (not context-tagged), so the per-extension decode handles the 2-vs-3-child
|
|
675
845
|
// shape directly; the seqOf centralizes the SEQUENCE / SIZE(1..MAX) assertion
|
|
676
|
-
// and the RFC 5280
|
|
846
|
+
// and the RFC 5280 sec. 4.2 per-OID uniqueness.
|
|
677
847
|
function extension(ns) {
|
|
678
848
|
return schema.decode(function (ext) {
|
|
679
|
-
// Extension ::= SEQUENCE { extnID, critical DEFAULT FALSE, extnValue }
|
|
849
|
+
// Extension ::= SEQUENCE { extnID, critical DEFAULT FALSE, extnValue } -- a
|
|
680
850
|
// UNIVERSAL SEQUENCE of exactly 2 (critical omitted) or 3 children. A
|
|
681
851
|
// context-tagged item (e.g. [5]{OID, OCTET STRING}) or a wrong child count
|
|
682
852
|
// is malformed; assert the tag, don't just count children (fail closed).
|
|
@@ -688,7 +858,7 @@ function extension(ns) {
|
|
|
688
858
|
var critical = false, valueNode;
|
|
689
859
|
if (ext.children.length === 3) {
|
|
690
860
|
critical = asn1.read.boolean(ext.children[1]);
|
|
691
|
-
// critical is BOOLEAN DEFAULT FALSE
|
|
861
|
+
// critical is BOOLEAN DEFAULT FALSE -- DER omits the field when false, so an
|
|
692
862
|
// explicitly-encoded FALSE is a non-canonical form; reject it fail-closed.
|
|
693
863
|
if (critical === false) throw ns.E(ns.prefix + "/bad-extension", "an explicit critical FALSE must be omitted (BOOLEAN DEFAULT FALSE)");
|
|
694
864
|
valueNode = ext.children[2];
|
|
@@ -699,8 +869,8 @@ function extension(ns) {
|
|
|
699
869
|
});
|
|
700
870
|
}
|
|
701
871
|
// opts.implicitTag (optional): read Extensions as a [tag] IMPLICIT SEQUENCE OF
|
|
702
|
-
// Extension
|
|
703
|
-
// CertTemplate extensions [9] (RFC 4211
|
|
872
|
+
// Extension -- the context tag REPLACES the universal SEQUENCE tag, for the CRMF
|
|
873
|
+
// CertTemplate extensions [9] (RFC 4211 sec. 5). With no opts the shape is a bare
|
|
704
874
|
// universal SEQUENCE OF, byte-identical to every existing caller.
|
|
705
875
|
function extensions(ns, opts) {
|
|
706
876
|
opts = opts || {};
|
|
@@ -715,13 +885,13 @@ function extensions(ns, opts) {
|
|
|
715
885
|
}
|
|
716
886
|
|
|
717
887
|
// SubjectPublicKeyInfo ::= SEQUENCE { algorithm AlgorithmIdentifier,
|
|
718
|
-
// subjectPublicKey BIT STRING } (RFC 5280
|
|
719
|
-
// universal SEQUENCE
|
|
888
|
+
// subjectPublicKey BIT STRING } (RFC 5280 sec. 4.1.2.7, RFC 2986 sec. 4.1). Asserted as a
|
|
889
|
+
// universal SEQUENCE -- a context-tagged or SET-tagged constructed node carrying
|
|
720
890
|
// two well-formed children is NOT a SubjectPublicKeyInfo. Shared by the
|
|
721
891
|
// certificate and CSR parsers.
|
|
722
892
|
// opts.implicitTag (optional): read the SubjectPublicKeyInfo as a [tag] IMPLICIT
|
|
723
893
|
// SEQUENCE (a context-class constructed node whose children are algorithm +
|
|
724
|
-
// subjectPublicKey), for the CRMF CertTemplate publicKey [6] (RFC 4211
|
|
894
|
+
// subjectPublicKey), for the CRMF CertTemplate publicKey [6] (RFC 4211 sec. 5). With no
|
|
725
895
|
// opts the shape is a universal SEQUENCE, byte-identical to every existing caller.
|
|
726
896
|
function spki(ns, opts) {
|
|
727
897
|
opts = opts || {};
|
|
@@ -745,14 +915,18 @@ function spki(ns, opts) {
|
|
|
745
915
|
}
|
|
746
916
|
|
|
747
917
|
// Attribute ::= SEQUENCE { type OBJECT IDENTIFIER, values SET OF AttributeValue }.
|
|
748
|
-
// AttributeValue is ANY, kept as raw DER (node.bytes)
|
|
749
|
-
// type never fails the parse. values is SET SIZE (1..MAX): an empty SET
|
|
750
|
-
// rejected; there is NO SET-OF uniqueness. Shared by the CSR (requested
|
|
751
|
-
// attributes, RFC 2986
|
|
752
|
-
|
|
918
|
+
// AttributeValue is ANY, kept as raw DER (node.bytes) -- an unrecognized attribute
|
|
919
|
+
// type never fails the parse. values is SET SIZE (1..MAX) by default: an empty SET
|
|
920
|
+
// is rejected; there is NO SET-OF uniqueness. Shared by the CSR (requested
|
|
921
|
+
// attributes, RFC 2986 sec. 4.1) and PKCS#8 (private-key attributes, RFC 5958 sec. 2).
|
|
922
|
+
// opts.minValues (default 1) lowers the floor to 0 for the EST CsrAttrs key-type
|
|
923
|
+
// hints, whose empty values SET means "any key of this type" (RFC 9908 sec. 3.2).
|
|
924
|
+
function attribute(ns, opts) {
|
|
925
|
+
opts = opts || {};
|
|
926
|
+
var minValues = opts.minValues === undefined ? 1 : opts.minValues;
|
|
753
927
|
return schema.seq([
|
|
754
928
|
schema.field("type", schema.oidLeaf()),
|
|
755
|
-
schema.field("values", schema.setOf(schema.any(), { assert: "set", min:
|
|
929
|
+
schema.field("values", schema.setOf(schema.any(), { assert: "set", min: minValues, max: constants.LIMITS.ATTRIBUTE_MAX_VALUES,
|
|
756
930
|
code: ns.prefix + "/bad-attribute-values", what: "attribute values" })),
|
|
757
931
|
], {
|
|
758
932
|
assert: "sequence", arity: { exact: 2 }, code: ns.prefix + "/bad-attribute", what: "Attribute",
|
|
@@ -783,7 +957,7 @@ function signedEnvelopeTbs(root) {
|
|
|
783
957
|
return tbs;
|
|
784
958
|
}
|
|
785
959
|
|
|
786
|
-
// rootSequenceChildren(root, minLen, maxLen)
|
|
960
|
+
// rootSequenceChildren(root, minLen, maxLen) -- the shared root-shape guard the
|
|
787
961
|
// format matches() detectors re-inline: returns root.children when root is a
|
|
788
962
|
// NON-EMPTY universal SEQUENCE whose child count is within [minLen, maxLen]
|
|
789
963
|
// (maxLen optional/Infinity), else null. Sibling of signedEnvelopeTbs.
|
|
@@ -800,20 +974,31 @@ function rootSequenceChildren(root, minLen, maxLen) {
|
|
|
800
974
|
// bound parser so a format declares its configuration once and never re-writes the
|
|
801
975
|
// coerce -> decode -> walk wrapper. `opts`: { pemLabel, PemError, ErrorClass,
|
|
802
976
|
// prefix, what, topSchema, ns }.
|
|
977
|
+
// The top-level schema's reject code -- the verdict for a DER that is not this
|
|
978
|
+
// structure at all -- is `<prefix>/not-a-<structure>` (x509/not-a-certificate,
|
|
979
|
+
// cms/not-a-content-info, ...). Three codes predate that convention and stay
|
|
980
|
+
// frozen on the public error-code surface (ocsp/bad-ocsp-response,
|
|
981
|
+
// tsp/bad-response, crmf/bad-cert-req-messages); a new format follows the
|
|
982
|
+
// not-a-* form, never those precedents.
|
|
803
983
|
function makeParser(opts) {
|
|
804
984
|
return function (input) { return runParse(input, opts); };
|
|
805
985
|
}
|
|
806
986
|
|
|
807
|
-
// The X.509 SIGNED{ToBeSigned} macro (RFC 5280
|
|
987
|
+
// The X.509 SIGNED{ToBeSigned} macro (RFC 5280 sec. 4.1.1.3): the outer
|
|
808
988
|
// SEQUENCE { toBeSigned, signatureAlgorithm AlgorithmIdentifier,
|
|
809
989
|
// signatureValue BIT STRING } shared by Certificate, CertificateList and
|
|
810
990
|
// CertificationRequest. `tbsSchema` parses the first element; the SEQUENCE-of-3
|
|
811
991
|
// shape, the arity, the signature extraction and the raw tbs / outer-signature
|
|
812
992
|
// bytes (for the cert/CRL outer==inner agreement check) are owned here once, and
|
|
813
993
|
// each format's `opts.build(envelope, ctx)` shapes its own object from the
|
|
814
|
-
// envelope. A CSR's build simply omits the agreement check
|
|
815
|
-
// signature AlgorithmIdentifier
|
|
994
|
+
// envelope. A CSR's build simply omits the agreement check -- its CRI has no inner
|
|
995
|
+
// signature AlgorithmIdentifier -- so the omission is structural, not a copy that
|
|
816
996
|
// forgot a guard.
|
|
997
|
+
// signatureValue is surfaced RAW as { unusedBits, bytes }: for the envelope
|
|
998
|
+
// family, octet alignment is a verify-layer rule (path-validate rejects
|
|
999
|
+
// unusedBits != 0 for certification paths; an external verifier must do the
|
|
1000
|
+
// same). A format with no in-tree verify layer (OCSP, CMP protection, CRMF
|
|
1001
|
+
// POP) instead enforces alignment at parse.
|
|
817
1002
|
function signedEnvelope(ns, tbsSchema, opts) {
|
|
818
1003
|
return schema.seq([
|
|
819
1004
|
schema.field("toBeSigned", tbsSchema),
|
|
@@ -844,6 +1029,7 @@ module.exports = {
|
|
|
844
1029
|
makeNS: makeNS,
|
|
845
1030
|
versionReader: versionReader,
|
|
846
1031
|
DN_SHORT: DN_SHORT,
|
|
1032
|
+
time: time,
|
|
847
1033
|
algorithmIdentifier: algorithmIdentifier,
|
|
848
1034
|
spki: spki,
|
|
849
1035
|
makeParser: makeParser,
|
|
@@ -857,6 +1043,10 @@ module.exports = {
|
|
|
857
1043
|
name: name,
|
|
858
1044
|
generalName: generalName,
|
|
859
1045
|
generalNames: generalNames,
|
|
1046
|
+
generalizedTime: generalizedTime,
|
|
1047
|
+
utf8Text: utf8Text,
|
|
1048
|
+
rawNonEmptySequence: rawNonEmptySequence,
|
|
1049
|
+
CRL_REASON_NAMES: CRL_REASON_NAMES,
|
|
860
1050
|
certExtensionDecoders: certExtensionDecoders,
|
|
861
1051
|
attribute: attribute,
|
|
862
1052
|
extension: extension,
|