@blamejs/pki 0.1.21 → 0.1.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +47 -0
- package/README.md +17 -6
- package/index.js +16 -12
- package/lib/asn1-der.js +126 -68
- package/lib/constants.js +46 -21
- package/lib/ct.js +54 -39
- package/lib/framework-error.js +64 -32
- package/lib/oid.js +114 -58
- package/lib/path-validate.js +302 -135
- package/lib/schema-all.js +54 -40
- package/lib/schema-attrcert.js +101 -64
- package/lib/schema-cmp.js +152 -131
- package/lib/schema-cms.js +621 -163
- package/lib/schema-crl.js +43 -21
- package/lib/schema-crmf.js +136 -79
- package/lib/schema-csr.js +63 -14
- package/lib/schema-engine.js +92 -43
- package/lib/schema-ocsp.js +101 -55
- package/lib/schema-pkcs12.js +80 -64
- package/lib/schema-pkcs8.js +12 -12
- package/lib/schema-pkix.js +306 -111
- package/lib/schema-smime.js +363 -0
- package/lib/schema-tsp.js +108 -59
- package/lib/schema-x509.js +36 -25
- package/lib/webcrypto.js +156 -22
- package/package.json +3 -2
- package/sbom.cdx.json +6 -6
package/lib/schema-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,7 +335,7 @@ 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("+"));
|
|
@@ -251,25 +350,85 @@ function name(ns, opts) {
|
|
|
251
350
|
assert: "sequence", code: ns.prefix + "/bad-name", what: "Name", build: nameBuild });
|
|
252
351
|
}
|
|
253
352
|
|
|
254
|
-
// GeneralName ::= CHOICE (RFC 5280
|
|
353
|
+
// GeneralName ::= CHOICE (RFC 5280 sec. 4.2.1.6). A validate-and-surface-raw leaf for a
|
|
255
354
|
// caller that keeps the value RAW but needs it to be a well-formed GeneralName: it
|
|
256
355
|
// checks the chosen alternative's tag, form (constructed vs primitive per X.690
|
|
257
|
-
//
|
|
356
|
+
// sec. 10.2), and content -- otherName [0] is a SEQUENCE { type-id OID, value [0] EXPLICIT
|
|
258
357
|
// }; x400Address [3] / ediPartyName [5] are non-empty constructed; directoryName [4]
|
|
259
358
|
// EXPLICIT wraps a valid Name; rfc822Name [1] / dNSName [2] / uniformResourceIdentifier
|
|
260
359
|
// [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
|
|
360
|
+
// OCTET STRING; registeredID [8] is a primitive OBJECT IDENTIFIER -- then surfaces the
|
|
262
361
|
// value RAW ({ bytes, tagClass, tagNumber }). `opts.code` is the caller's error code
|
|
263
362
|
// (e.g. ocsp/bad-requestor-name, tsp/bad-tsa). Shared so the two parsers cannot drift.
|
|
264
363
|
// opts.decodeValue (default false): in addition to the raw { bytes, tagClass,
|
|
265
|
-
// tagNumber }, surface the DECODED `value` per arm
|
|
364
|
+
// tagNumber }, surface the DECODED `value` per arm -- IA5 text (string), an
|
|
266
365
|
// iPAddress Buffer, a directoryName { rdns, dn }, a registeredID OID string,
|
|
267
366
|
// or an otherName { typeId, valueBytes }. The path validator's name-constraint
|
|
268
367
|
// matcher needs the decoded value; the tsp/ocsp/attrcert consumers pass no
|
|
269
368
|
// flag and get the byte-identical raw-only shape.
|
|
270
369
|
// opts.subtreeBase (default false): the GeneralSubtree.base form (RFC 5280
|
|
271
|
-
//
|
|
370
|
+
// sec. 4.2.1.10) -- an iPAddress base is an address+mask (8 octets IPv4 / 32 IPv6),
|
|
272
371
|
// NOT the 4/16-octet SAN address form. Only the iPAddress size rule changes.
|
|
372
|
+
// A GeneralizedTime-only leaf. Several formats fix their times to
|
|
373
|
+
// GeneralizedTime and reject UTCTime (OCSP RFC 6960, TSP RFC 3161 sec. 2.4.2,
|
|
374
|
+
// CMP RFC 9810 sec. 5.1.1, attribute-certificate validity RFC 5755 sec. 4.2.6);
|
|
375
|
+
// ONE ns-parameterized factory owns the tag assert so the per-format code and
|
|
376
|
+
// message differ but the check cannot drift. opts.allowFractional enables the
|
|
377
|
+
// X.690 sec. 11.7 fractional-seconds profile (the RFC 3161 genTime).
|
|
378
|
+
function generalizedTime(ns, opts) {
|
|
379
|
+
opts = opts || {};
|
|
380
|
+
var code = opts.code || (ns.prefix + "/bad-time");
|
|
381
|
+
var message = opts.message || "the time must be a GeneralizedTime";
|
|
382
|
+
var readOpts = opts.allowFractional ? { allowFractional: true } : undefined;
|
|
383
|
+
return schema.decode(function (n, ctx) {
|
|
384
|
+
if (n.tagClass !== "universal" || n.tagNumber !== asn1.TAGS.GENERALIZED_TIME) {
|
|
385
|
+
throw ctx.E(code, message);
|
|
386
|
+
}
|
|
387
|
+
return asn1.read.time(n, readOpts);
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// A UTF8String-only leaf -- the PKIFreeText element shape (RFC 3161 sec. 2.4.2,
|
|
392
|
+
// RFC 9810 sec. 5.1.1) shared by the TSP and CMP parsers.
|
|
393
|
+
function utf8Text(ns, opts) {
|
|
394
|
+
opts = opts || {};
|
|
395
|
+
var code = opts.code || (ns.prefix + "/bad-freetext");
|
|
396
|
+
var message = opts.message || "the element must be a UTF8String";
|
|
397
|
+
return schema.decode(function (n, ctx) {
|
|
398
|
+
if (n.tagClass !== "universal" || n.tagNumber !== asn1.TAGS.UTF8_STRING) {
|
|
399
|
+
throw ctx.E(code, message);
|
|
400
|
+
}
|
|
401
|
+
return asn1.read.string(n);
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// A raw NON-EMPTY universal SEQUENCE leaf -- surfaces the exact TLV bytes of an
|
|
406
|
+
// embedded element (a Certificate, a CertificateList, a PKIPublicationInfo)
|
|
407
|
+
// after asserting the SEQUENCE tag and at least one child. An empty SEQUENCE
|
|
408
|
+
// (30 00) has the right tag but is none of those structures, so it rejects
|
|
409
|
+
// rather than surfacing degenerate bytes to certificate processing.
|
|
410
|
+
function rawNonEmptySequence(ns, opts) {
|
|
411
|
+
opts = opts || {};
|
|
412
|
+
var code = opts.code || (ns.prefix + "/bad-sequence");
|
|
413
|
+
var message = opts.message || "expected a non-empty universal SEQUENCE";
|
|
414
|
+
return schema.decode(function (n, ctx) {
|
|
415
|
+
if (!(n.tagClass === "universal" && n.tagNumber === asn1.TAGS.SEQUENCE && n.children && n.children.length >= 1)) {
|
|
416
|
+
throw ctx.E(code, message);
|
|
417
|
+
}
|
|
418
|
+
return n.bytes;
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// CRLReason ::= ENUMERATED (RFC 5280 sec. 5.3.1) -- value 7 is unused/reserved.
|
|
423
|
+
// The ONE canonical value->name table every consumer validates against: the
|
|
424
|
+
// CRL reasonCode decoder surfaces the numeric code, the OCSP RevokedInfo
|
|
425
|
+
// decoder the name; both draw the legal set from here so they cannot drift.
|
|
426
|
+
var CRL_REASON_NAMES = {
|
|
427
|
+
"0": "unspecified", "1": "keyCompromise", "2": "cACompromise", "3": "affiliationChanged",
|
|
428
|
+
"4": "superseded", "5": "cessationOfOperation", "6": "certificateHold",
|
|
429
|
+
"8": "removeFromCRL", "9": "privilegeWithdrawn", "10": "aACompromise",
|
|
430
|
+
};
|
|
431
|
+
|
|
273
432
|
var GN_CONSTRUCTED = { 0: 1, 3: 1, 4: 1, 5: 1 };
|
|
274
433
|
var GN_IA5 = { 1: 1, 2: 1, 6: 1 };
|
|
275
434
|
function generalName(ns, opts) {
|
|
@@ -280,13 +439,13 @@ function generalName(ns, opts) {
|
|
|
280
439
|
var NAME = name(ns);
|
|
281
440
|
return schema.decode(function (n, ctx) {
|
|
282
441
|
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
|
|
442
|
+
throw ctx.E(code, "value must be a GeneralName (context tag [0]..[8]) (RFC 5280 sec. 4.2.1.6)");
|
|
284
443
|
}
|
|
285
444
|
var t = n.tagNumber;
|
|
286
445
|
var constructed = !!n.children;
|
|
287
446
|
var value;
|
|
288
447
|
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
|
|
448
|
+
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
449
|
if (t === 0) {
|
|
291
450
|
// otherName ::= SEQUENCE { type-id OBJECT IDENTIFIER, value [0] EXPLICIT ANY }.
|
|
292
451
|
if (n.children.length !== 2) throw ctx.E(code, "GeneralName otherName [0] must be a SEQUENCE { type-id, value [0] }");
|
|
@@ -299,17 +458,17 @@ function generalName(ns, opts) {
|
|
|
299
458
|
}
|
|
300
459
|
if (decodeValue) value = { typeId: typeId, valueBytes: ov.children[0].bytes };
|
|
301
460
|
} else if (t === 4) {
|
|
302
|
-
// directoryName [4] EXPLICIT Name
|
|
461
|
+
// directoryName [4] EXPLICIT Name -- validate the wrapped RDNSequence.
|
|
303
462
|
if (n.children.length !== 1) throw ctx.E(code, "GeneralName directoryName [4] must wrap exactly one Name");
|
|
304
463
|
var dnMatch = schema.walk(NAME, n.children[0], ctx);
|
|
305
464
|
if (decodeValue) value = dnMatch.result;
|
|
306
465
|
}
|
|
307
466
|
} else {
|
|
308
|
-
if (constructed) throw ctx.E(code, "GeneralName [" + t + "] must be primitive (X.690
|
|
467
|
+
if (constructed) throw ctx.E(code, "GeneralName [" + t + "] must be primitive (X.690 sec. 10.2)");
|
|
309
468
|
if (GN_IA5[t]) {
|
|
310
469
|
if (n.content.length === 0) throw ctx.E(code, "GeneralName [" + t + "] must be a non-empty IA5String");
|
|
311
470
|
for (var i = 0; i < n.content.length; i++) {
|
|
312
|
-
// 7-bit IA5, and no C0/DEL control byte
|
|
471
|
+
// 7-bit IA5, and no C0/DEL control byte -- an embedded NUL/control in a
|
|
313
472
|
// dNSName/rfc822Name/URI enables a name-truncation/confusion bypass
|
|
314
473
|
// (CVE-2009-2408 class) downstream, so reject it at decode.
|
|
315
474
|
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 +476,7 @@ function generalName(ns, opts) {
|
|
|
317
476
|
if (decodeValue) value = n.content.toString("latin1");
|
|
318
477
|
} else if (t === 7) {
|
|
319
478
|
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
|
|
479
|
+
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
480
|
} else if (n.content.length !== 4 && n.content.length !== 16) {
|
|
322
481
|
throw ctx.E(code, "GeneralName iPAddress [7] must be a 4- or 16-octet address");
|
|
323
482
|
}
|
|
@@ -337,14 +496,14 @@ function generalName(ns, opts) {
|
|
|
337
496
|
});
|
|
338
497
|
}
|
|
339
498
|
|
|
340
|
-
// GeneralNames ::= SEQUENCE OF GeneralName (RFC 5280
|
|
499
|
+
// GeneralNames ::= SEQUENCE OF GeneralName (RFC 5280 sec. 4.2.1.6), SIZE (1..MAX). Every
|
|
341
500
|
// element is validated by generalName (its CHOICE alternative's form + content) and
|
|
342
501
|
// surfaced raw, so a caller carrying a GeneralNames field cannot accept a malformed
|
|
343
502
|
// 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`
|
|
503
|
+
// where each `names[i]` is the generalName leaf's return -- { bytes, tagClass,
|
|
504
|
+
// tagNumber } and, when opts.decodeValue is set, the decoded `value` -- and `bytes`
|
|
346
505
|
// is the raw outer DER.
|
|
347
|
-
// `opts.implicitTag` handles a [tag] IMPLICIT GeneralNames
|
|
506
|
+
// `opts.implicitTag` handles a [tag] IMPLICIT GeneralNames -- the context tag REPLACES
|
|
348
507
|
// the universal SEQUENCE tag (RFC 5755 Holder.entityName [1]); otherwise it is a bare
|
|
349
508
|
// universal SEQUENCE OF. `opts.code` is the caller's error code. Shared so the x509 /
|
|
350
509
|
// attribute-certificate / (future) CRMF parsers validate a GeneralNames identically.
|
|
@@ -361,15 +520,37 @@ function generalNames(ns, opts) {
|
|
|
361
520
|
return schema.seqOf(gn, { assert: "sequence", min: 1, code: code, what: opts.what || "GeneralNames", build: build });
|
|
362
521
|
}
|
|
363
522
|
|
|
364
|
-
// certExtensionDecoders(ns)
|
|
523
|
+
// certExtensionDecoders(ns) -- the ns-parameterized RFC 5280 sec. 4.2.1 extension
|
|
365
524
|
// VALUE decoders. `x509.parse` surfaces each extension as { oid, name, critical,
|
|
366
525
|
// value } with `value` the raw inner OCTET-STRING content (a Buffer); the path
|
|
367
526
|
// validator and the future complete-extension-set surface need the STRUCTURE
|
|
368
527
|
// inside. Each decoder takes that raw Buffer and returns the decoded value, or
|
|
369
528
|
// throws a typed `<prefix>/bad-*` code, mirroring the CRL's fail-closed
|
|
370
|
-
// `decodeExt` pattern (asn1.decode
|
|
529
|
+
// `decodeExt` pattern (asn1.decode -> read.* / schema.walk, wrapped). Returns
|
|
371
530
|
// { byOid } keyed by dotted OID so a consumer dispatches by ext.oid.
|
|
372
531
|
var _T = asn1.TAGS;
|
|
532
|
+
|
|
533
|
+
// assertPolicyQualifiers(qNode, fail) -- RFC 5280 sec. 4.2.1.4: a PolicyInformation's
|
|
534
|
+
// policyQualifiers is a non-empty SEQUENCE OF PolicyQualifierInfo, each a SEQUENCE
|
|
535
|
+
// { policyQualifierId OID, qualifier } of EXACTLY two members led by an OID. The
|
|
536
|
+
// structure is validated fail-closed; the qualifier body stays opaque (surfaced
|
|
537
|
+
// raw by the caller). `fail(msg)` throws the caller's typed code. Shared by the
|
|
538
|
+
// certificatePolicies extension decoder and the RFC 5035 ESS SigningCertificate
|
|
539
|
+
// policies field so the same PolicyInformation shape cannot drift between them.
|
|
540
|
+
// `fail(msg, cause?)` throws the caller's typed code, carrying the underlying
|
|
541
|
+
// leaf fault as the error cause when one is available.
|
|
542
|
+
function assertPolicyQualifiers(qNode, fail) {
|
|
543
|
+
if (qNode.tagClass !== "universal" || qNode.tagNumber !== _T.SEQUENCE || !qNode.children || !qNode.children.length) {
|
|
544
|
+
fail("policyQualifiers must be a non-empty SEQUENCE (RFC 5280 sec. 4.2.1.4)");
|
|
545
|
+
}
|
|
546
|
+
qNode.children.forEach(function (pq) {
|
|
547
|
+
if (pq.tagClass !== "universal" || pq.tagNumber !== _T.SEQUENCE || !pq.children || pq.children.length !== 2) {
|
|
548
|
+
fail("policyQualifiers element must be a PolicyQualifierInfo SEQUENCE { policyQualifierId, qualifier } (RFC 5280 sec. 4.2.1.4)");
|
|
549
|
+
}
|
|
550
|
+
try { asn1.read.oid(pq.children[0]); } catch (e) { fail("PolicyQualifierInfo must lead with a policyQualifierId OID", e); }
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
|
|
373
554
|
function certExtensionDecoders(ns) {
|
|
374
555
|
var GN_SUBTREE = generalName(ns, { decodeValue: true, subtreeBase: true, code: ns.prefix + "/bad-name-constraints" });
|
|
375
556
|
|
|
@@ -382,7 +563,7 @@ function certExtensionDecoders(ns) {
|
|
|
382
563
|
function seqChildren(buf, code, what) {
|
|
383
564
|
var n = decodeTop(buf, code, what);
|
|
384
565
|
if (n.tagClass !== "universal" || n.tagNumber !== _T.SEQUENCE || !n.children) {
|
|
385
|
-
throw ns.E(code, what + " must be a SEQUENCE (RFC 5280
|
|
566
|
+
throw ns.E(code, what + " must be a SEQUENCE (RFC 5280 sec. 4.2.1)");
|
|
386
567
|
}
|
|
387
568
|
return n.children;
|
|
388
569
|
}
|
|
@@ -399,13 +580,13 @@ function certExtensionDecoders(ns) {
|
|
|
399
580
|
if (kids[i] && kids[i].tagClass === "universal" && kids[i].tagNumber === _T.BOOLEAN) {
|
|
400
581
|
var v;
|
|
401
582
|
try { v = asn1.read.boolean(kids[i]); } catch (e) { throw ns.E(C, "BasicConstraints cA must be a BOOLEAN", e); }
|
|
402
|
-
if (v !== true) throw ns.E(C, "BasicConstraints cA DEFAULT FALSE must be omitted, not an explicit FALSE (X.690
|
|
583
|
+
if (v !== true) throw ns.E(C, "BasicConstraints cA DEFAULT FALSE must be omitted, not an explicit FALSE (X.690 sec. 11.5)");
|
|
403
584
|
cA = true; i++;
|
|
404
585
|
}
|
|
405
586
|
if (kids[i] && kids[i].tagClass === "universal" && kids[i].tagNumber === _T.INTEGER) {
|
|
406
|
-
if (!cA) throw ns.E(C, "BasicConstraints pathLenConstraint is only permitted when cA is TRUE (RFC 5280
|
|
587
|
+
if (!cA) throw ns.E(C, "BasicConstraints pathLenConstraint is only permitted when cA is TRUE (RFC 5280 sec. 4.2.1.9)");
|
|
407
588
|
var pl = readInt(kids[i], C, "pathLenConstraint");
|
|
408
|
-
// Bounded before Number() narrowing
|
|
589
|
+
// Bounded before Number() narrowing -- a value past the safe-integer range
|
|
409
590
|
// would round silently and be compared as a path-length ceiling, so it is
|
|
410
591
|
// exact-or-rejected (the same rule the RSASSA-PSS/PKCS#12 counters follow).
|
|
411
592
|
if (pl < 0n || pl > 2147483647n) throw ns.E(C, "BasicConstraints pathLenConstraint must be a non-negative integer within range");
|
|
@@ -421,15 +602,15 @@ function certExtensionDecoders(ns) {
|
|
|
421
602
|
function keyUsage(buf) {
|
|
422
603
|
var C = ns.prefix + "/bad-key-usage";
|
|
423
604
|
var n = decodeTop(buf, C, "KeyUsage");
|
|
424
|
-
if (n.tagClass !== "universal" || n.tagNumber !== _T.BIT_STRING) throw ns.E(C, "KeyUsage must be a BIT STRING (RFC 5280
|
|
605
|
+
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)");
|
|
425
606
|
var bs;
|
|
426
607
|
try { bs = asn1.read.bitString(n); } catch (e) { throw ns.E(C, "KeyUsage must be a well-formed BIT STRING", e); }
|
|
427
|
-
// At least one bit MUST be set (RFC 5280
|
|
608
|
+
// At least one bit MUST be set (RFC 5280 sec. 4.2.1.3) -- an all-zero value (a
|
|
428
609
|
// non-empty byte run of only zero bits, e.g. 03 02 07 00) is malformed too.
|
|
429
610
|
var anyBit = false;
|
|
430
611
|
for (var z = 0; z < bs.bytes.length; z++) { if (bs.bytes[z] !== 0) { anyBit = true; break; } }
|
|
431
|
-
if (!anyBit) throw ns.E(C, "KeyUsage must assert at least one bit (RFC 5280
|
|
432
|
-
// KeyUsage is a NamedBitList
|
|
612
|
+
if (!anyBit) throw ns.E(C, "KeyUsage must assert at least one bit (RFC 5280 sec. 4.2.1.3)");
|
|
613
|
+
// KeyUsage is a NamedBitList -- enforce the shared X.690 sec. 11.2.2 minimal-DER rule.
|
|
433
614
|
schema.assertMinimalNamedBits(bs.unusedBits, bs.bytes, function (m) { throw ns.E(C, m); });
|
|
434
615
|
var out = {};
|
|
435
616
|
KU_BITS.forEach(function (nm, bit) {
|
|
@@ -442,9 +623,9 @@ function certExtensionDecoders(ns) {
|
|
|
442
623
|
// GeneralSubtree ::= SEQUENCE { base GeneralName, minimum [0] DEFAULT 0, maximum [1] OPTIONAL }
|
|
443
624
|
function subtreeList(node, C) {
|
|
444
625
|
// node is the [0]/[1] IMPLICIT SEQUENCE OF GeneralSubtree (context-constructed).
|
|
445
|
-
// GeneralSubtrees is SIZE(1..MAX)
|
|
626
|
+
// GeneralSubtrees is SIZE(1..MAX) -- an explicitly present but empty subtree
|
|
446
627
|
// list (a0 00) absorbs no constraint and is malformed, not a no-op.
|
|
447
|
-
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
|
|
628
|
+
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)");
|
|
448
629
|
return node.children.map(function (st) {
|
|
449
630
|
if (st.tagClass !== "universal" || st.tagNumber !== _T.SEQUENCE || !st.children || st.children.length < 1) {
|
|
450
631
|
throw ns.E(C, "GeneralSubtree must be a SEQUENCE { base, minimum?, maximum? }");
|
|
@@ -453,8 +634,8 @@ function certExtensionDecoders(ns) {
|
|
|
453
634
|
for (var j = 1; j < st.children.length; j++) {
|
|
454
635
|
var f = st.children[j];
|
|
455
636
|
if (f.tagClass !== "context" || (f.tagNumber !== 0 && f.tagNumber !== 1)) throw ns.E(C, "GeneralSubtree has an unexpected field");
|
|
456
|
-
if (f.tagNumber === 0) throw ns.E(C, "GeneralSubtree minimum DEFAULT 0 must be omitted (RFC 5280
|
|
457
|
-
if (f.tagNumber === 1) throw ns.E(C, "GeneralSubtree maximum is not permitted in the RFC 5280 profile (
|
|
637
|
+
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)");
|
|
638
|
+
if (f.tagNumber === 1) throw ns.E(C, "GeneralSubtree maximum is not permitted in the RFC 5280 profile (sec. 4.2.1.10)");
|
|
458
639
|
}
|
|
459
640
|
return { base: base };
|
|
460
641
|
});
|
|
@@ -471,7 +652,7 @@ function certExtensionDecoders(ns) {
|
|
|
471
652
|
else if (k.tagNumber === 1) { if (sawE) throw ns.E(C, "duplicate excludedSubtrees"); sawE = true; excluded = subtreeList(k, C); }
|
|
472
653
|
else throw ns.E(C, "NameConstraints has an unexpected field [" + k.tagNumber + "]");
|
|
473
654
|
});
|
|
474
|
-
if (!sawP && !sawE) throw ns.E(C, "NameConstraints must contain permittedSubtrees or excludedSubtrees (RFC 5280
|
|
655
|
+
if (!sawP && !sawE) throw ns.E(C, "NameConstraints must contain permittedSubtrees or excludedSubtrees (RFC 5280 sec. 4.2.1.10)");
|
|
475
656
|
return { permittedSubtrees: permitted, excludedSubtrees: excluded };
|
|
476
657
|
}
|
|
477
658
|
|
|
@@ -479,36 +660,25 @@ function certExtensionDecoders(ns) {
|
|
|
479
660
|
function certificatePolicies(buf) {
|
|
480
661
|
var C = ns.prefix + "/bad-policy";
|
|
481
662
|
var kids = seqChildren(buf, C, "CertificatePolicies");
|
|
482
|
-
if (kids.length < 1) throw ns.E(C, "CertificatePolicies must contain at least one PolicyInformation (RFC 5280
|
|
663
|
+
if (kids.length < 1) throw ns.E(C, "CertificatePolicies must contain at least one PolicyInformation (RFC 5280 sec. 4.2.1.4)");
|
|
483
664
|
var seen = {};
|
|
484
665
|
return kids.map(function (pi) {
|
|
485
666
|
// PolicyInformation ::= SEQUENCE { policyIdentifier, policyQualifiers
|
|
486
|
-
// SEQUENCE SIZE(1..MAX) OPTIONAL }
|
|
667
|
+
// SEQUENCE SIZE(1..MAX) OPTIONAL } -- exactly one or two fields; a second
|
|
487
668
|
// field, if present, MUST be a SEQUENCE. Extra/mis-typed fields are malformed.
|
|
488
669
|
if (pi.tagClass !== "universal" || pi.tagNumber !== _T.SEQUENCE || !pi.children || pi.children.length < 1 || pi.children.length > 2) {
|
|
489
670
|
throw ns.E(C, "PolicyInformation must be a SEQUENCE { policyIdentifier, policyQualifiers? }");
|
|
490
671
|
}
|
|
491
672
|
var pid;
|
|
492
673
|
try { pid = asn1.read.oid(pi.children[0]); } catch (e) { throw ns.E(C, "PolicyInformation policyIdentifier must be an OBJECT IDENTIFIER", e); }
|
|
493
|
-
if (seen[pid]) throw ns.E(C, "duplicate policy OID " + pid + " (RFC 5280
|
|
674
|
+
if (seen[pid]) throw ns.E(C, "duplicate policy OID " + pid + " (RFC 5280 sec. 4.2.1.4)");
|
|
494
675
|
seen[pid] = true;
|
|
495
676
|
var qualifiers = null;
|
|
496
677
|
if (pi.children.length > 1) {
|
|
497
678
|
var q = pi.children[1];
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
}
|
|
501
|
-
// Each element is a PolicyQualifierInfo ::= SEQUENCE { policyQualifierId
|
|
502
|
-
// OID, qualifier ANY DEFINED BY policyQualifierId } — EXACTLY two members
|
|
503
|
-
// (qualifier is not OPTIONAL). A SEQUENCE missing the qualifier or
|
|
504
|
-
// carrying trailing extra fields is malformed; the qualifier body itself
|
|
505
|
-
// stays opaque.
|
|
506
|
-
q.children.forEach(function (pq) {
|
|
507
|
-
if (pq.tagClass !== "universal" || pq.tagNumber !== _T.SEQUENCE || !pq.children || pq.children.length !== 2) {
|
|
508
|
-
throw ns.E(C, "policyQualifiers element must be a PolicyQualifierInfo SEQUENCE { policyQualifierId, qualifier } (RFC 5280 §4.2.1.4)");
|
|
509
|
-
}
|
|
510
|
-
try { asn1.read.oid(pq.children[0]); } catch (e) { throw ns.E(C, "PolicyQualifierInfo must lead with a policyQualifierId OID", e); }
|
|
511
|
-
});
|
|
679
|
+
// PolicyQualifierInfo structure validated by the shared assertion; the
|
|
680
|
+
// qualifier body stays opaque (surfaced raw).
|
|
681
|
+
assertPolicyQualifiers(q, function (msg, cause) { throw ns.E(C, msg, cause); });
|
|
512
682
|
qualifiers = q.bytes;
|
|
513
683
|
}
|
|
514
684
|
return { policyIdentifier: pid, qualifiersBytes: qualifiers };
|
|
@@ -519,7 +689,7 @@ function certExtensionDecoders(ns) {
|
|
|
519
689
|
function policyMappings(buf) {
|
|
520
690
|
var C = ns.prefix + "/bad-policy";
|
|
521
691
|
var kids = seqChildren(buf, C, "PolicyMappings");
|
|
522
|
-
if (kids.length < 1) throw ns.E(C, "PolicyMappings must contain at least one mapping (RFC 5280
|
|
692
|
+
if (kids.length < 1) throw ns.E(C, "PolicyMappings must contain at least one mapping (RFC 5280 sec. 4.2.1.5)");
|
|
523
693
|
return kids.map(function (mp) {
|
|
524
694
|
if (mp.tagClass !== "universal" || mp.tagNumber !== _T.SEQUENCE || !mp.children || mp.children.length !== 2) {
|
|
525
695
|
throw ns.E(C, "policy mapping must be a SEQUENCE { issuerDomainPolicy, subjectDomainPolicy }");
|
|
@@ -535,7 +705,7 @@ function certExtensionDecoders(ns) {
|
|
|
535
705
|
function policyConstraints(buf) {
|
|
536
706
|
var C = ns.prefix + "/bad-policy";
|
|
537
707
|
var kids = seqChildren(buf, C, "PolicyConstraints");
|
|
538
|
-
if (kids.length < 1) throw ns.E(C, "PolicyConstraints must contain at least one field (RFC 5280
|
|
708
|
+
if (kids.length < 1) throw ns.E(C, "PolicyConstraints must contain at least one field (RFC 5280 sec. 4.2.1.11)");
|
|
539
709
|
var rep = null, ipm = null, pcLastTag = -1;
|
|
540
710
|
kids.forEach(function (k) {
|
|
541
711
|
if (k.tagClass !== "context") throw ns.E(C, "PolicyConstraints fields are context-tagged [0]/[1]");
|
|
@@ -544,7 +714,7 @@ function certExtensionDecoders(ns) {
|
|
|
544
714
|
var v;
|
|
545
715
|
try { v = asn1.read.integerImplicit(k, k.tagNumber); } catch (e) { throw ns.E(C, "PolicyConstraints field must be an INTEGER", e); }
|
|
546
716
|
// Bounded before Number() narrowing (a skip count past the safe-integer
|
|
547
|
-
// range would round silently)
|
|
717
|
+
// range would round silently) -- exact-or-rejected.
|
|
548
718
|
if (v < 0n || v > 2147483647n) throw ns.E(C, "PolicyConstraints skip count must be a non-negative integer within range");
|
|
549
719
|
if (k.tagNumber === 0) rep = Number(v);
|
|
550
720
|
else if (k.tagNumber === 1) ipm = Number(v);
|
|
@@ -557,15 +727,15 @@ function certExtensionDecoders(ns) {
|
|
|
557
727
|
function inhibitAnyPolicy(buf) {
|
|
558
728
|
var C = ns.prefix + "/bad-policy";
|
|
559
729
|
var n = decodeTop(buf, C, "InhibitAnyPolicy");
|
|
560
|
-
if (n.tagClass !== "universal" || n.tagNumber !== _T.INTEGER) throw ns.E(C, "InhibitAnyPolicy must be an INTEGER (RFC 5280
|
|
730
|
+
if (n.tagClass !== "universal" || n.tagNumber !== _T.INTEGER) throw ns.E(C, "InhibitAnyPolicy must be an INTEGER (RFC 5280 sec. 4.2.1.14)");
|
|
561
731
|
var v = readInt(n, C, "InhibitAnyPolicy");
|
|
562
732
|
// Bounded before Number() narrowing (a skip count past the safe-integer
|
|
563
|
-
// range would round silently)
|
|
733
|
+
// range would round silently) -- exact-or-rejected.
|
|
564
734
|
if (v < 0n || v > 2147483647n) throw ns.E(C, "InhibitAnyPolicy skip count must be a non-negative integer within range");
|
|
565
735
|
return Number(v);
|
|
566
736
|
}
|
|
567
737
|
|
|
568
|
-
// subjectAltName / issuerAltName ::= GeneralNames
|
|
738
|
+
// subjectAltName / issuerAltName ::= GeneralNames -- decoded values surfaced.
|
|
569
739
|
function altName(buf) {
|
|
570
740
|
var C = ns.prefix + "/bad-extension-value";
|
|
571
741
|
var n = decodeTop(buf, C, "GeneralNames");
|
|
@@ -576,7 +746,7 @@ function certExtensionDecoders(ns) {
|
|
|
576
746
|
function extKeyUsage(buf) {
|
|
577
747
|
var C = ns.prefix + "/bad-extension-value";
|
|
578
748
|
var kids = seqChildren(buf, C, "ExtKeyUsage");
|
|
579
|
-
if (kids.length < 1) throw ns.E(C, "ExtKeyUsage must contain at least one KeyPurposeId (RFC 5280
|
|
749
|
+
if (kids.length < 1) throw ns.E(C, "ExtKeyUsage must contain at least one KeyPurposeId (RFC 5280 sec. 4.2.1.12)");
|
|
580
750
|
return kids.map(function (k) {
|
|
581
751
|
try { return asn1.read.oid(k); } catch (e) { throw ns.E(C, "ExtKeyUsage KeyPurposeId must be an OBJECT IDENTIFIER", e); }
|
|
582
752
|
});
|
|
@@ -603,7 +773,7 @@ function certExtensionDecoders(ns) {
|
|
|
603
773
|
else throw ns.E(C, "AuthorityKeyIdentifier has an unexpected field [" + k.tagNumber + "]");
|
|
604
774
|
});
|
|
605
775
|
if ((out.authorityCertIssuer === null) !== (out.authorityCertSerialNumber === null)) {
|
|
606
|
-
throw ns.E(C, "AuthorityKeyIdentifier authorityCertIssuer and authorityCertSerialNumber must both be present or both absent (RFC 5280
|
|
776
|
+
throw ns.E(C, "AuthorityKeyIdentifier authorityCertIssuer and authorityCertSerialNumber must both be present or both absent (RFC 5280 sec. 4.2.1.1)");
|
|
607
777
|
}
|
|
608
778
|
return out;
|
|
609
779
|
}
|
|
@@ -613,10 +783,10 @@ function certExtensionDecoders(ns) {
|
|
|
613
783
|
var C = ns.prefix + "/bad-extension-value";
|
|
614
784
|
var n = decodeTop(buf, C, "SubjectKeyIdentifier");
|
|
615
785
|
try { return Buffer.concat([asn1.read.octetString(n)]); }
|
|
616
|
-
catch (e) { throw ns.E(C, "SubjectKeyIdentifier must be an OCTET STRING (RFC 5280
|
|
786
|
+
catch (e) { throw ns.E(C, "SubjectKeyIdentifier must be an OCTET STRING (RFC 5280 sec. 4.2.1.2)", e); }
|
|
617
787
|
}
|
|
618
788
|
|
|
619
|
-
// signedCertificateTimestampList (RFC 6962
|
|
789
|
+
// signedCertificateTimestampList (RFC 6962 sec. 3.3) -- the extension value is a DER
|
|
620
790
|
// OCTET STRING wrapping the TLS-encoded SCT list. Delegated to the CT module,
|
|
621
791
|
// which owns the inner peel + the bounded TLS decode; structure is decoded, the
|
|
622
792
|
// signature stays raw and unverified. The registry contract is that a decoder
|
|
@@ -625,21 +795,29 @@ function certExtensionDecoders(ns) {
|
|
|
625
795
|
function sctList(buf) {
|
|
626
796
|
var C = ns.prefix + "/bad-extension-value";
|
|
627
797
|
try { return ct.parseSctList(buf); }
|
|
628
|
-
catch (e) { throw ns.E(C, "malformed signedCertificateTimestampList extension value (RFC 6962
|
|
798
|
+
catch (e) { throw ns.E(C, "malformed signedCertificateTimestampList extension value (RFC 6962 sec. 3.3)", e); }
|
|
629
799
|
}
|
|
630
800
|
|
|
631
|
-
// precertificatePoison (RFC 6962
|
|
801
|
+
// precertificatePoison (RFC 6962 sec. 3.1) -- a critical extension whose value is
|
|
632
802
|
// exactly ASN.1 NULL (05 00), the marker distinguishing a precertificate from
|
|
633
803
|
// a final certificate. Content-validated, not merely tag-checked.
|
|
634
804
|
function precertPoison(buf) {
|
|
635
805
|
var C = ns.prefix + "/bad-extension-value";
|
|
636
806
|
var n = decodeTop(buf, C, "PrecertificatePoison");
|
|
637
807
|
try { asn1.read.nullValue(n); }
|
|
638
|
-
catch (e) { throw ns.E(C, "the precertificate poison extension value must be ASN.1 NULL (RFC 6962
|
|
808
|
+
catch (e) { throw ns.E(C, "the precertificate poison extension value must be ASN.1 NULL (RFC 6962 sec. 3.1)", e); }
|
|
639
809
|
return { poison: true };
|
|
640
810
|
}
|
|
641
811
|
|
|
642
|
-
|
|
812
|
+
// byName returns undefined for an unregistered name; keying a decoder row
|
|
813
|
+
// under "undefined" would silently drop that extension from dispatch (the
|
|
814
|
+
// consumer would treat the extension as structurally unvalidated / absent),
|
|
815
|
+
// so a typo'd key-list entry must fail here, at module load.
|
|
816
|
+
function O(nm) {
|
|
817
|
+
var d = ns.oid.byName(nm);
|
|
818
|
+
if (typeof d !== "string") throw new TypeError("certExtensionDecoders: " + JSON.stringify(nm) + " is not a registered OID name");
|
|
819
|
+
return d;
|
|
820
|
+
}
|
|
643
821
|
var byOid = {};
|
|
644
822
|
byOid[O("basicConstraints")] = basicConstraints;
|
|
645
823
|
byOid[O("keyUsage")] = keyUsage;
|
|
@@ -662,10 +840,10 @@ function certExtensionDecoders(ns) {
|
|
|
662
840
|
// extnValue OCTET STRING }. `critical` is a universal BOOLEAN present-by-count
|
|
663
841
|
// (not context-tagged), so the per-extension decode handles the 2-vs-3-child
|
|
664
842
|
// shape directly; the seqOf centralizes the SEQUENCE / SIZE(1..MAX) assertion
|
|
665
|
-
// and the RFC 5280
|
|
843
|
+
// and the RFC 5280 sec. 4.2 per-OID uniqueness.
|
|
666
844
|
function extension(ns) {
|
|
667
845
|
return schema.decode(function (ext) {
|
|
668
|
-
// Extension ::= SEQUENCE { extnID, critical DEFAULT FALSE, extnValue }
|
|
846
|
+
// Extension ::= SEQUENCE { extnID, critical DEFAULT FALSE, extnValue } -- a
|
|
669
847
|
// UNIVERSAL SEQUENCE of exactly 2 (critical omitted) or 3 children. A
|
|
670
848
|
// context-tagged item (e.g. [5]{OID, OCTET STRING}) or a wrong child count
|
|
671
849
|
// is malformed; assert the tag, don't just count children (fail closed).
|
|
@@ -677,7 +855,7 @@ function extension(ns) {
|
|
|
677
855
|
var critical = false, valueNode;
|
|
678
856
|
if (ext.children.length === 3) {
|
|
679
857
|
critical = asn1.read.boolean(ext.children[1]);
|
|
680
|
-
// critical is BOOLEAN DEFAULT FALSE
|
|
858
|
+
// critical is BOOLEAN DEFAULT FALSE -- DER omits the field when false, so an
|
|
681
859
|
// explicitly-encoded FALSE is a non-canonical form; reject it fail-closed.
|
|
682
860
|
if (critical === false) throw ns.E(ns.prefix + "/bad-extension", "an explicit critical FALSE must be omitted (BOOLEAN DEFAULT FALSE)");
|
|
683
861
|
valueNode = ext.children[2];
|
|
@@ -688,8 +866,8 @@ function extension(ns) {
|
|
|
688
866
|
});
|
|
689
867
|
}
|
|
690
868
|
// opts.implicitTag (optional): read Extensions as a [tag] IMPLICIT SEQUENCE OF
|
|
691
|
-
// Extension
|
|
692
|
-
// CertTemplate extensions [9] (RFC 4211
|
|
869
|
+
// Extension -- the context tag REPLACES the universal SEQUENCE tag, for the CRMF
|
|
870
|
+
// CertTemplate extensions [9] (RFC 4211 sec. 5). With no opts the shape is a bare
|
|
693
871
|
// universal SEQUENCE OF, byte-identical to every existing caller.
|
|
694
872
|
function extensions(ns, opts) {
|
|
695
873
|
opts = opts || {};
|
|
@@ -704,13 +882,13 @@ function extensions(ns, opts) {
|
|
|
704
882
|
}
|
|
705
883
|
|
|
706
884
|
// SubjectPublicKeyInfo ::= SEQUENCE { algorithm AlgorithmIdentifier,
|
|
707
|
-
// subjectPublicKey BIT STRING } (RFC 5280
|
|
708
|
-
// universal SEQUENCE
|
|
885
|
+
// subjectPublicKey BIT STRING } (RFC 5280 sec. 4.1.2.7, RFC 2986 sec. 4.1). Asserted as a
|
|
886
|
+
// universal SEQUENCE -- a context-tagged or SET-tagged constructed node carrying
|
|
709
887
|
// two well-formed children is NOT a SubjectPublicKeyInfo. Shared by the
|
|
710
888
|
// certificate and CSR parsers.
|
|
711
889
|
// opts.implicitTag (optional): read the SubjectPublicKeyInfo as a [tag] IMPLICIT
|
|
712
890
|
// SEQUENCE (a context-class constructed node whose children are algorithm +
|
|
713
|
-
// subjectPublicKey), for the CRMF CertTemplate publicKey [6] (RFC 4211
|
|
891
|
+
// subjectPublicKey), for the CRMF CertTemplate publicKey [6] (RFC 4211 sec. 5). With no
|
|
714
892
|
// opts the shape is a universal SEQUENCE, byte-identical to every existing caller.
|
|
715
893
|
function spki(ns, opts) {
|
|
716
894
|
opts = opts || {};
|
|
@@ -734,10 +912,10 @@ function spki(ns, opts) {
|
|
|
734
912
|
}
|
|
735
913
|
|
|
736
914
|
// Attribute ::= SEQUENCE { type OBJECT IDENTIFIER, values SET OF AttributeValue }.
|
|
737
|
-
// AttributeValue is ANY, kept as raw DER (node.bytes)
|
|
915
|
+
// AttributeValue is ANY, kept as raw DER (node.bytes) -- an unrecognized attribute
|
|
738
916
|
// type never fails the parse. values is SET SIZE (1..MAX): an empty SET is
|
|
739
917
|
// rejected; there is NO SET-OF uniqueness. Shared by the CSR (requested
|
|
740
|
-
// attributes, RFC 2986
|
|
918
|
+
// attributes, RFC 2986 sec. 4.1) and PKCS#8 (private-key attributes, RFC 5958 sec. 2).
|
|
741
919
|
function attribute(ns) {
|
|
742
920
|
return schema.seq([
|
|
743
921
|
schema.field("type", schema.oidLeaf()),
|
|
@@ -772,7 +950,7 @@ function signedEnvelopeTbs(root) {
|
|
|
772
950
|
return tbs;
|
|
773
951
|
}
|
|
774
952
|
|
|
775
|
-
// rootSequenceChildren(root, minLen, maxLen)
|
|
953
|
+
// rootSequenceChildren(root, minLen, maxLen) -- the shared root-shape guard the
|
|
776
954
|
// format matches() detectors re-inline: returns root.children when root is a
|
|
777
955
|
// NON-EMPTY universal SEQUENCE whose child count is within [minLen, maxLen]
|
|
778
956
|
// (maxLen optional/Infinity), else null. Sibling of signedEnvelopeTbs.
|
|
@@ -789,20 +967,31 @@ function rootSequenceChildren(root, minLen, maxLen) {
|
|
|
789
967
|
// bound parser so a format declares its configuration once and never re-writes the
|
|
790
968
|
// coerce -> decode -> walk wrapper. `opts`: { pemLabel, PemError, ErrorClass,
|
|
791
969
|
// prefix, what, topSchema, ns }.
|
|
970
|
+
// The top-level schema's reject code -- the verdict for a DER that is not this
|
|
971
|
+
// structure at all -- is `<prefix>/not-a-<structure>` (x509/not-a-certificate,
|
|
972
|
+
// cms/not-a-content-info, ...). Three codes predate that convention and stay
|
|
973
|
+
// frozen on the public error-code surface (ocsp/bad-ocsp-response,
|
|
974
|
+
// tsp/bad-response, crmf/bad-cert-req-messages); a new format follows the
|
|
975
|
+
// not-a-* form, never those precedents.
|
|
792
976
|
function makeParser(opts) {
|
|
793
977
|
return function (input) { return runParse(input, opts); };
|
|
794
978
|
}
|
|
795
979
|
|
|
796
|
-
// The X.509 SIGNED{ToBeSigned} macro (RFC 5280
|
|
980
|
+
// The X.509 SIGNED{ToBeSigned} macro (RFC 5280 sec. 4.1.1.3): the outer
|
|
797
981
|
// SEQUENCE { toBeSigned, signatureAlgorithm AlgorithmIdentifier,
|
|
798
982
|
// signatureValue BIT STRING } shared by Certificate, CertificateList and
|
|
799
983
|
// CertificationRequest. `tbsSchema` parses the first element; the SEQUENCE-of-3
|
|
800
984
|
// shape, the arity, the signature extraction and the raw tbs / outer-signature
|
|
801
985
|
// bytes (for the cert/CRL outer==inner agreement check) are owned here once, and
|
|
802
986
|
// each format's `opts.build(envelope, ctx)` shapes its own object from the
|
|
803
|
-
// envelope. A CSR's build simply omits the agreement check
|
|
804
|
-
// signature AlgorithmIdentifier
|
|
987
|
+
// envelope. A CSR's build simply omits the agreement check -- its CRI has no inner
|
|
988
|
+
// signature AlgorithmIdentifier -- so the omission is structural, not a copy that
|
|
805
989
|
// forgot a guard.
|
|
990
|
+
// signatureValue is surfaced RAW as { unusedBits, bytes }: for the envelope
|
|
991
|
+
// family, octet alignment is a verify-layer rule (path-validate rejects
|
|
992
|
+
// unusedBits != 0 for certification paths; an external verifier must do the
|
|
993
|
+
// same). A format with no in-tree verify layer (OCSP, CMP protection, CRMF
|
|
994
|
+
// POP) instead enforces alignment at parse.
|
|
806
995
|
function signedEnvelope(ns, tbsSchema, opts) {
|
|
807
996
|
return schema.seq([
|
|
808
997
|
schema.field("toBeSigned", tbsSchema),
|
|
@@ -833,11 +1022,13 @@ module.exports = {
|
|
|
833
1022
|
makeNS: makeNS,
|
|
834
1023
|
versionReader: versionReader,
|
|
835
1024
|
DN_SHORT: DN_SHORT,
|
|
1025
|
+
time: time,
|
|
836
1026
|
algorithmIdentifier: algorithmIdentifier,
|
|
837
1027
|
spki: spki,
|
|
838
1028
|
makeParser: makeParser,
|
|
839
1029
|
signedEnvelopeTbs: signedEnvelopeTbs,
|
|
840
1030
|
rootSequenceChildren: rootSequenceChildren,
|
|
1031
|
+
assertPolicyQualifiers: assertPolicyQualifiers,
|
|
841
1032
|
signedEnvelope: signedEnvelope,
|
|
842
1033
|
attrValueToString: attrValueToString,
|
|
843
1034
|
attributeTypeAndValue: attributeTypeAndValue,
|
|
@@ -845,6 +1036,10 @@ module.exports = {
|
|
|
845
1036
|
name: name,
|
|
846
1037
|
generalName: generalName,
|
|
847
1038
|
generalNames: generalNames,
|
|
1039
|
+
generalizedTime: generalizedTime,
|
|
1040
|
+
utf8Text: utf8Text,
|
|
1041
|
+
rawNonEmptySequence: rawNonEmptySequence,
|
|
1042
|
+
CRL_REASON_NAMES: CRL_REASON_NAMES,
|
|
848
1043
|
certExtensionDecoders: certExtensionDecoders,
|
|
849
1044
|
attribute: attribute,
|
|
850
1045
|
extension: extension,
|