@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.
@@ -2,15 +2,15 @@
2
2
  // Copyright (c) blamejs contributors
3
3
  "use strict";
4
4
  //
5
- // @internal no operator-facing namespace. The documented surface is the
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 the
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
- if (Buffer.isBuffer(text)) text = text.toString("latin1");
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
- return Buffer.from(b64, "base64");
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" || label.length === 0) throw new PemError("pem/bad-label", "pemEncode requires a label");
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 its
67
- // leading tag+length bytes are non-printable so a non-SEQUENCE DER (a bare
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 a definite-length constructed IMPLICIT
87
- // OCTET STRING decodes strictly and only diverges at the typed reader so
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 §3 + common use).
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 withCause ignores
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 the cert
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 is expressed purely as the accept map (RFC 5280 §4.1.2.1 / §5.1.2.1,
135
- // RFC 2986 §4.1, RFC 5958 §2). read.integer is strict, so an ENUMERATED-tagged
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 §6.2.4 pwri.keyDerivationAlgorithm [0]. With no opts the shape is a
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
- // RFC 9909 §3 / RFC 9814 §4 / RFC 9881 §2 / RFC 8410 §3: for the PQC
160
- // signature families (ML-DSA, SLH-DSA) and the Edwards/Montgomery curves,
161
- // the `parameters` field MUST be absent no explicit NULL, no bytes.
162
- // Enforced ONCE here so every consumer of the shared AlgorithmIdentifier
163
- // inherits the rule; the guard-parity bug class is structurally impossible.
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 (RFC 9909 §3 / RFC 9814 §4 / RFC 9881 §2 / RFC 8410 §3)");
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 do NOT hex-encode it away, or the decoder's strict string validation
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 §2.4 as "#" plus the
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
- try { return asn1.read.string(node); }
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 "#hex" escape (RFC 4514 §2.4) round-trips its raw DER verbatim; any
193
- // other string encodes as UTF8String (the decode does not preserve the original
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) === "#") return Buffer.from(value.slice(1), "hex");
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
- return v.replace(/([,+"\\<>;])/g, "\\$1");
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
- // bare-constructed (min 2) matching the historical guard that never checked
207
- // the SEQUENCE tag — and repeated RDN attribute types stay legal (no uniqueness).
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: "constructed", arity: { min: 2 }, code: ns.prefix + "/bad-atv", what: "AttributeTypeAndValue",
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) an empty SET {} is malformed.
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 the
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 §5). With no opts the shape is a bare
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 + "=" + _escapeDnValue(a.value));
338
+ atvParts.push(label + "=" + _dnDisplayValue(a.value));
240
339
  });
241
340
  rdns.push(atvs);
242
341
  parts.push(atvParts.join("+"));
243
342
  });
244
- return { rdns: rdns, dn: parts.join(", ") };
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 §4.2.1.6). A validate-and-surface-raw leaf for a
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
- // §10.2), and content otherName [0] is a SEQUENCE { type-id OID, value [0] EXPLICIT
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 then surfaces the
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 IA5 text (string), an
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
- // §4.2.1.10) an iPAddress base is an address+mask (8 octets IPv4 / 32 IPv6),
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 §4.2.1.6)");
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 §4.2.1.6)");
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 validate the wrapped RDNSequence.
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 §10.2)");
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 an embedded NUL/control in a
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 §4.2.1.10)");
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 §4.2.1.6), SIZE (1..MAX). Every
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 { bytes, tagClass,
345
- // tagNumber } and, when opts.decodeValue is set, the decoded `value` and `bytes`
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 the context tag REPLACES
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) the ns-parameterized RFC 5280 §4.2.1 extension
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 read.* / schema.walk, wrapped). Returns
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) RFC 5280 §4.2.1.4: a PolicyInformation's
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 §4.2.1.4)");
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 §4.2.1.4)");
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 §4.2.1)");
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 §11.5)");
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 §4.2.1.9)");
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 a value past the safe-integer range
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 §4.2.1.3)");
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 §4.2.1.3) an all-zero value (a
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 §4.2.1.3)");
454
- // KeyUsage is a NamedBitList enforce the shared X.690 §11.2.2 minimal-DER rule.
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) an explicitly present but empty subtree
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 §4.2.1.10)");
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 §4.2.1.10 requires minimum = 0)");
479
- if (f.tagNumber === 1) throw ns.E(C, "GeneralSubtree maximum is not permitted in the RFC 5280 profile (§4.2.1.10)");
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 §4.2.1.10)");
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 §4.2.1.4)");
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 } exactly one or two fields; a second
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 §4.2.1.4)");
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 §4.2.1.5)");
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 §4.2.1.11)");
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) exact-or-rejected.
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 §4.2.1.14)");
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) exact-or-rejected.
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 decoded values surfaced.
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 §4.2.1.12)");
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 §4.2.1.1)");
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 §4.2.1.2)", e); }
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 §3.3) the extension value is a DER
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 §3.3)", e); }
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 §3.1) a critical extension whose value is
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 §3.1)", e); }
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
- function O(nm) { return ns.oid.byName(nm); }
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 §4.2 per-OID uniqueness.
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 } a
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 DER omits the field when false, so an
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 the context tag REPLACES the universal SEQUENCE tag, for the CRMF
703
- // CertTemplate extensions [9] (RFC 4211 §5). With no opts the shape is a bare
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 §4.1.2.7, RFC 2986 §4.1). Asserted as a
719
- // universal SEQUENCE a context-tagged or SET-tagged constructed node carrying
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 §5). With no
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) an unrecognized attribute
749
- // type never fails the parse. values is SET SIZE (1..MAX): an empty SET is
750
- // rejected; there is NO SET-OF uniqueness. Shared by the CSR (requested
751
- // attributes, RFC 2986 §4.1) and PKCS#8 (private-key attributes, RFC 5958 §2).
752
- function attribute(ns) {
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: 1, max: constants.LIMITS.ATTRIBUTE_MAX_VALUES,
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) the shared root-shape guard the
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 §4.1.1.3): the outer
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 its CRI has no inner
815
- // signature AlgorithmIdentifier so the omission is structural, not a copy that
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,