@blamejs/pki 0.1.22 → 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.
@@ -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,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 + "=" + _escapeDnValue(a.value));
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 §4.2.1.6). A validate-and-surface-raw leaf for a
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
- // §10.2), and content otherName [0] is a SEQUENCE { type-id OID, value [0] EXPLICIT
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 then surfaces the
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 IA5 text (string), an
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
- // §4.2.1.10) an iPAddress base is an address+mask (8 octets IPv4 / 32 IPv6),
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 §4.2.1.6)");
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 §4.2.1.6)");
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 validate the wrapped RDNSequence.
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 §10.2)");
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 an embedded NUL/control in a
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 §4.2.1.10)");
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 §4.2.1.6), SIZE (1..MAX). Every
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 { bytes, tagClass,
345
- // tagNumber } and, when opts.decodeValue is set, the decoded `value` and `bytes`
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 the context tag REPLACES
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,17 +520,17 @@ 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) the ns-parameterized RFC 5280 §4.2.1 extension
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 read.* / schema.walk, wrapped). Returns
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;
373
532
 
374
- // assertPolicyQualifiers(qNode, fail) RFC 5280 §4.2.1.4: a PolicyInformation's
533
+ // assertPolicyQualifiers(qNode, fail) -- RFC 5280 sec. 4.2.1.4: a PolicyInformation's
375
534
  // policyQualifiers is a non-empty SEQUENCE OF PolicyQualifierInfo, each a SEQUENCE
376
535
  // { policyQualifierId OID, qualifier } of EXACTLY two members led by an OID. The
377
536
  // structure is validated fail-closed; the qualifier body stays opaque (surfaced
@@ -382,11 +541,11 @@ var _T = asn1.TAGS;
382
541
  // leaf fault as the error cause when one is available.
383
542
  function assertPolicyQualifiers(qNode, fail) {
384
543
  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)");
544
+ fail("policyQualifiers must be a non-empty SEQUENCE (RFC 5280 sec. 4.2.1.4)");
386
545
  }
387
546
  qNode.children.forEach(function (pq) {
388
547
  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)");
548
+ fail("policyQualifiers element must be a PolicyQualifierInfo SEQUENCE { policyQualifierId, qualifier } (RFC 5280 sec. 4.2.1.4)");
390
549
  }
391
550
  try { asn1.read.oid(pq.children[0]); } catch (e) { fail("PolicyQualifierInfo must lead with a policyQualifierId OID", e); }
392
551
  });
@@ -404,7 +563,7 @@ function certExtensionDecoders(ns) {
404
563
  function seqChildren(buf, code, what) {
405
564
  var n = decodeTop(buf, code, what);
406
565
  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)");
566
+ throw ns.E(code, what + " must be a SEQUENCE (RFC 5280 sec. 4.2.1)");
408
567
  }
409
568
  return n.children;
410
569
  }
@@ -421,13 +580,13 @@ function certExtensionDecoders(ns) {
421
580
  if (kids[i] && kids[i].tagClass === "universal" && kids[i].tagNumber === _T.BOOLEAN) {
422
581
  var v;
423
582
  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)");
583
+ if (v !== true) throw ns.E(C, "BasicConstraints cA DEFAULT FALSE must be omitted, not an explicit FALSE (X.690 sec. 11.5)");
425
584
  cA = true; i++;
426
585
  }
427
586
  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)");
587
+ if (!cA) throw ns.E(C, "BasicConstraints pathLenConstraint is only permitted when cA is TRUE (RFC 5280 sec. 4.2.1.9)");
429
588
  var pl = readInt(kids[i], C, "pathLenConstraint");
430
- // Bounded before Number() narrowing a value past the safe-integer range
589
+ // Bounded before Number() narrowing -- a value past the safe-integer range
431
590
  // would round silently and be compared as a path-length ceiling, so it is
432
591
  // exact-or-rejected (the same rule the RSASSA-PSS/PKCS#12 counters follow).
433
592
  if (pl < 0n || pl > 2147483647n) throw ns.E(C, "BasicConstraints pathLenConstraint must be a non-negative integer within range");
@@ -443,15 +602,15 @@ function certExtensionDecoders(ns) {
443
602
  function keyUsage(buf) {
444
603
  var C = ns.prefix + "/bad-key-usage";
445
604
  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)");
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)");
447
606
  var bs;
448
607
  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
608
+ // At least one bit MUST be set (RFC 5280 sec. 4.2.1.3) -- an all-zero value (a
450
609
  // non-empty byte run of only zero bits, e.g. 03 02 07 00) is malformed too.
451
610
  var anyBit = false;
452
611
  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.
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.
455
614
  schema.assertMinimalNamedBits(bs.unusedBits, bs.bytes, function (m) { throw ns.E(C, m); });
456
615
  var out = {};
457
616
  KU_BITS.forEach(function (nm, bit) {
@@ -464,9 +623,9 @@ function certExtensionDecoders(ns) {
464
623
  // GeneralSubtree ::= SEQUENCE { base GeneralName, minimum [0] DEFAULT 0, maximum [1] OPTIONAL }
465
624
  function subtreeList(node, C) {
466
625
  // node is the [0]/[1] IMPLICIT SEQUENCE OF GeneralSubtree (context-constructed).
467
- // GeneralSubtrees is SIZE(1..MAX) an explicitly present but empty subtree
626
+ // GeneralSubtrees is SIZE(1..MAX) -- an explicitly present but empty subtree
468
627
  // 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)");
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)");
470
629
  return node.children.map(function (st) {
471
630
  if (st.tagClass !== "universal" || st.tagNumber !== _T.SEQUENCE || !st.children || st.children.length < 1) {
472
631
  throw ns.E(C, "GeneralSubtree must be a SEQUENCE { base, minimum?, maximum? }");
@@ -475,8 +634,8 @@ function certExtensionDecoders(ns) {
475
634
  for (var j = 1; j < st.children.length; j++) {
476
635
  var f = st.children[j];
477
636
  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)");
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)");
480
639
  }
481
640
  return { base: base };
482
641
  });
@@ -493,7 +652,7 @@ function certExtensionDecoders(ns) {
493
652
  else if (k.tagNumber === 1) { if (sawE) throw ns.E(C, "duplicate excludedSubtrees"); sawE = true; excluded = subtreeList(k, C); }
494
653
  else throw ns.E(C, "NameConstraints has an unexpected field [" + k.tagNumber + "]");
495
654
  });
496
- if (!sawP && !sawE) throw ns.E(C, "NameConstraints must contain permittedSubtrees or excludedSubtrees (RFC 5280 §4.2.1.10)");
655
+ if (!sawP && !sawE) throw ns.E(C, "NameConstraints must contain permittedSubtrees or excludedSubtrees (RFC 5280 sec. 4.2.1.10)");
497
656
  return { permittedSubtrees: permitted, excludedSubtrees: excluded };
498
657
  }
499
658
 
@@ -501,18 +660,18 @@ function certExtensionDecoders(ns) {
501
660
  function certificatePolicies(buf) {
502
661
  var C = ns.prefix + "/bad-policy";
503
662
  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)");
663
+ if (kids.length < 1) throw ns.E(C, "CertificatePolicies must contain at least one PolicyInformation (RFC 5280 sec. 4.2.1.4)");
505
664
  var seen = {};
506
665
  return kids.map(function (pi) {
507
666
  // PolicyInformation ::= SEQUENCE { policyIdentifier, policyQualifiers
508
- // SEQUENCE SIZE(1..MAX) OPTIONAL } exactly one or two fields; a second
667
+ // SEQUENCE SIZE(1..MAX) OPTIONAL } -- exactly one or two fields; a second
509
668
  // field, if present, MUST be a SEQUENCE. Extra/mis-typed fields are malformed.
510
669
  if (pi.tagClass !== "universal" || pi.tagNumber !== _T.SEQUENCE || !pi.children || pi.children.length < 1 || pi.children.length > 2) {
511
670
  throw ns.E(C, "PolicyInformation must be a SEQUENCE { policyIdentifier, policyQualifiers? }");
512
671
  }
513
672
  var pid;
514
673
  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)");
674
+ if (seen[pid]) throw ns.E(C, "duplicate policy OID " + pid + " (RFC 5280 sec. 4.2.1.4)");
516
675
  seen[pid] = true;
517
676
  var qualifiers = null;
518
677
  if (pi.children.length > 1) {
@@ -530,7 +689,7 @@ function certExtensionDecoders(ns) {
530
689
  function policyMappings(buf) {
531
690
  var C = ns.prefix + "/bad-policy";
532
691
  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)");
692
+ if (kids.length < 1) throw ns.E(C, "PolicyMappings must contain at least one mapping (RFC 5280 sec. 4.2.1.5)");
534
693
  return kids.map(function (mp) {
535
694
  if (mp.tagClass !== "universal" || mp.tagNumber !== _T.SEQUENCE || !mp.children || mp.children.length !== 2) {
536
695
  throw ns.E(C, "policy mapping must be a SEQUENCE { issuerDomainPolicy, subjectDomainPolicy }");
@@ -546,7 +705,7 @@ function certExtensionDecoders(ns) {
546
705
  function policyConstraints(buf) {
547
706
  var C = ns.prefix + "/bad-policy";
548
707
  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)");
708
+ if (kids.length < 1) throw ns.E(C, "PolicyConstraints must contain at least one field (RFC 5280 sec. 4.2.1.11)");
550
709
  var rep = null, ipm = null, pcLastTag = -1;
551
710
  kids.forEach(function (k) {
552
711
  if (k.tagClass !== "context") throw ns.E(C, "PolicyConstraints fields are context-tagged [0]/[1]");
@@ -555,7 +714,7 @@ function certExtensionDecoders(ns) {
555
714
  var v;
556
715
  try { v = asn1.read.integerImplicit(k, k.tagNumber); } catch (e) { throw ns.E(C, "PolicyConstraints field must be an INTEGER", e); }
557
716
  // Bounded before Number() narrowing (a skip count past the safe-integer
558
- // range would round silently) exact-or-rejected.
717
+ // range would round silently) -- exact-or-rejected.
559
718
  if (v < 0n || v > 2147483647n) throw ns.E(C, "PolicyConstraints skip count must be a non-negative integer within range");
560
719
  if (k.tagNumber === 0) rep = Number(v);
561
720
  else if (k.tagNumber === 1) ipm = Number(v);
@@ -568,15 +727,15 @@ function certExtensionDecoders(ns) {
568
727
  function inhibitAnyPolicy(buf) {
569
728
  var C = ns.prefix + "/bad-policy";
570
729
  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)");
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)");
572
731
  var v = readInt(n, C, "InhibitAnyPolicy");
573
732
  // Bounded before Number() narrowing (a skip count past the safe-integer
574
- // range would round silently) exact-or-rejected.
733
+ // range would round silently) -- exact-or-rejected.
575
734
  if (v < 0n || v > 2147483647n) throw ns.E(C, "InhibitAnyPolicy skip count must be a non-negative integer within range");
576
735
  return Number(v);
577
736
  }
578
737
 
579
- // subjectAltName / issuerAltName ::= GeneralNames decoded values surfaced.
738
+ // subjectAltName / issuerAltName ::= GeneralNames -- decoded values surfaced.
580
739
  function altName(buf) {
581
740
  var C = ns.prefix + "/bad-extension-value";
582
741
  var n = decodeTop(buf, C, "GeneralNames");
@@ -587,7 +746,7 @@ function certExtensionDecoders(ns) {
587
746
  function extKeyUsage(buf) {
588
747
  var C = ns.prefix + "/bad-extension-value";
589
748
  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)");
749
+ if (kids.length < 1) throw ns.E(C, "ExtKeyUsage must contain at least one KeyPurposeId (RFC 5280 sec. 4.2.1.12)");
591
750
  return kids.map(function (k) {
592
751
  try { return asn1.read.oid(k); } catch (e) { throw ns.E(C, "ExtKeyUsage KeyPurposeId must be an OBJECT IDENTIFIER", e); }
593
752
  });
@@ -614,7 +773,7 @@ function certExtensionDecoders(ns) {
614
773
  else throw ns.E(C, "AuthorityKeyIdentifier has an unexpected field [" + k.tagNumber + "]");
615
774
  });
616
775
  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)");
776
+ throw ns.E(C, "AuthorityKeyIdentifier authorityCertIssuer and authorityCertSerialNumber must both be present or both absent (RFC 5280 sec. 4.2.1.1)");
618
777
  }
619
778
  return out;
620
779
  }
@@ -624,10 +783,10 @@ function certExtensionDecoders(ns) {
624
783
  var C = ns.prefix + "/bad-extension-value";
625
784
  var n = decodeTop(buf, C, "SubjectKeyIdentifier");
626
785
  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); }
786
+ catch (e) { throw ns.E(C, "SubjectKeyIdentifier must be an OCTET STRING (RFC 5280 sec. 4.2.1.2)", e); }
628
787
  }
629
788
 
630
- // signedCertificateTimestampList (RFC 6962 §3.3) the extension value is a DER
789
+ // signedCertificateTimestampList (RFC 6962 sec. 3.3) -- the extension value is a DER
631
790
  // OCTET STRING wrapping the TLS-encoded SCT list. Delegated to the CT module,
632
791
  // which owns the inner peel + the bounded TLS decode; structure is decoded, the
633
792
  // signature stays raw and unverified. The registry contract is that a decoder
@@ -636,21 +795,29 @@ function certExtensionDecoders(ns) {
636
795
  function sctList(buf) {
637
796
  var C = ns.prefix + "/bad-extension-value";
638
797
  try { return ct.parseSctList(buf); }
639
- catch (e) { throw ns.E(C, "malformed signedCertificateTimestampList extension value (RFC 6962 §3.3)", e); }
798
+ catch (e) { throw ns.E(C, "malformed signedCertificateTimestampList extension value (RFC 6962 sec. 3.3)", e); }
640
799
  }
641
800
 
642
- // precertificatePoison (RFC 6962 §3.1) a critical extension whose value is
801
+ // precertificatePoison (RFC 6962 sec. 3.1) -- a critical extension whose value is
643
802
  // exactly ASN.1 NULL (05 00), the marker distinguishing a precertificate from
644
803
  // a final certificate. Content-validated, not merely tag-checked.
645
804
  function precertPoison(buf) {
646
805
  var C = ns.prefix + "/bad-extension-value";
647
806
  var n = decodeTop(buf, C, "PrecertificatePoison");
648
807
  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); }
808
+ catch (e) { throw ns.E(C, "the precertificate poison extension value must be ASN.1 NULL (RFC 6962 sec. 3.1)", e); }
650
809
  return { poison: true };
651
810
  }
652
811
 
653
- function O(nm) { return ns.oid.byName(nm); }
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
+ }
654
821
  var byOid = {};
655
822
  byOid[O("basicConstraints")] = basicConstraints;
656
823
  byOid[O("keyUsage")] = keyUsage;
@@ -673,10 +840,10 @@ function certExtensionDecoders(ns) {
673
840
  // extnValue OCTET STRING }. `critical` is a universal BOOLEAN present-by-count
674
841
  // (not context-tagged), so the per-extension decode handles the 2-vs-3-child
675
842
  // shape directly; the seqOf centralizes the SEQUENCE / SIZE(1..MAX) assertion
676
- // and the RFC 5280 §4.2 per-OID uniqueness.
843
+ // and the RFC 5280 sec. 4.2 per-OID uniqueness.
677
844
  function extension(ns) {
678
845
  return schema.decode(function (ext) {
679
- // Extension ::= SEQUENCE { extnID, critical DEFAULT FALSE, extnValue } a
846
+ // Extension ::= SEQUENCE { extnID, critical DEFAULT FALSE, extnValue } -- a
680
847
  // UNIVERSAL SEQUENCE of exactly 2 (critical omitted) or 3 children. A
681
848
  // context-tagged item (e.g. [5]{OID, OCTET STRING}) or a wrong child count
682
849
  // is malformed; assert the tag, don't just count children (fail closed).
@@ -688,7 +855,7 @@ function extension(ns) {
688
855
  var critical = false, valueNode;
689
856
  if (ext.children.length === 3) {
690
857
  critical = asn1.read.boolean(ext.children[1]);
691
- // critical is BOOLEAN DEFAULT FALSE DER omits the field when false, so an
858
+ // critical is BOOLEAN DEFAULT FALSE -- DER omits the field when false, so an
692
859
  // explicitly-encoded FALSE is a non-canonical form; reject it fail-closed.
693
860
  if (critical === false) throw ns.E(ns.prefix + "/bad-extension", "an explicit critical FALSE must be omitted (BOOLEAN DEFAULT FALSE)");
694
861
  valueNode = ext.children[2];
@@ -699,8 +866,8 @@ function extension(ns) {
699
866
  });
700
867
  }
701
868
  // 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
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
704
871
  // universal SEQUENCE OF, byte-identical to every existing caller.
705
872
  function extensions(ns, opts) {
706
873
  opts = opts || {};
@@ -715,13 +882,13 @@ function extensions(ns, opts) {
715
882
  }
716
883
 
717
884
  // 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
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
720
887
  // two well-formed children is NOT a SubjectPublicKeyInfo. Shared by the
721
888
  // certificate and CSR parsers.
722
889
  // opts.implicitTag (optional): read the SubjectPublicKeyInfo as a [tag] IMPLICIT
723
890
  // SEQUENCE (a context-class constructed node whose children are algorithm +
724
- // subjectPublicKey), for the CRMF CertTemplate publicKey [6] (RFC 4211 §5). With no
891
+ // subjectPublicKey), for the CRMF CertTemplate publicKey [6] (RFC 4211 sec. 5). With no
725
892
  // opts the shape is a universal SEQUENCE, byte-identical to every existing caller.
726
893
  function spki(ns, opts) {
727
894
  opts = opts || {};
@@ -745,10 +912,10 @@ function spki(ns, opts) {
745
912
  }
746
913
 
747
914
  // Attribute ::= SEQUENCE { type OBJECT IDENTIFIER, values SET OF AttributeValue }.
748
- // AttributeValue is ANY, kept as raw DER (node.bytes) an unrecognized attribute
915
+ // AttributeValue is ANY, kept as raw DER (node.bytes) -- an unrecognized attribute
749
916
  // type never fails the parse. values is SET SIZE (1..MAX): an empty SET is
750
917
  // 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).
918
+ // attributes, RFC 2986 sec. 4.1) and PKCS#8 (private-key attributes, RFC 5958 sec. 2).
752
919
  function attribute(ns) {
753
920
  return schema.seq([
754
921
  schema.field("type", schema.oidLeaf()),
@@ -783,7 +950,7 @@ function signedEnvelopeTbs(root) {
783
950
  return tbs;
784
951
  }
785
952
 
786
- // rootSequenceChildren(root, minLen, maxLen) the shared root-shape guard the
953
+ // rootSequenceChildren(root, minLen, maxLen) -- the shared root-shape guard the
787
954
  // format matches() detectors re-inline: returns root.children when root is a
788
955
  // NON-EMPTY universal SEQUENCE whose child count is within [minLen, maxLen]
789
956
  // (maxLen optional/Infinity), else null. Sibling of signedEnvelopeTbs.
@@ -800,20 +967,31 @@ function rootSequenceChildren(root, minLen, maxLen) {
800
967
  // bound parser so a format declares its configuration once and never re-writes the
801
968
  // coerce -> decode -> walk wrapper. `opts`: { pemLabel, PemError, ErrorClass,
802
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.
803
976
  function makeParser(opts) {
804
977
  return function (input) { return runParse(input, opts); };
805
978
  }
806
979
 
807
- // The X.509 SIGNED{ToBeSigned} macro (RFC 5280 §4.1.1.3): the outer
980
+ // The X.509 SIGNED{ToBeSigned} macro (RFC 5280 sec. 4.1.1.3): the outer
808
981
  // SEQUENCE { toBeSigned, signatureAlgorithm AlgorithmIdentifier,
809
982
  // signatureValue BIT STRING } shared by Certificate, CertificateList and
810
983
  // CertificationRequest. `tbsSchema` parses the first element; the SEQUENCE-of-3
811
984
  // shape, the arity, the signature extraction and the raw tbs / outer-signature
812
985
  // bytes (for the cert/CRL outer==inner agreement check) are owned here once, and
813
986
  // 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
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
816
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.
817
995
  function signedEnvelope(ns, tbsSchema, opts) {
818
996
  return schema.seq([
819
997
  schema.field("toBeSigned", tbsSchema),
@@ -844,6 +1022,7 @@ module.exports = {
844
1022
  makeNS: makeNS,
845
1023
  versionReader: versionReader,
846
1024
  DN_SHORT: DN_SHORT,
1025
+ time: time,
847
1026
  algorithmIdentifier: algorithmIdentifier,
848
1027
  spki: spki,
849
1028
  makeParser: makeParser,
@@ -857,6 +1036,10 @@ module.exports = {
857
1036
  name: name,
858
1037
  generalName: generalName,
859
1038
  generalNames: generalNames,
1039
+ generalizedTime: generalizedTime,
1040
+ utf8Text: utf8Text,
1041
+ rawNonEmptySequence: rawNonEmptySequence,
1042
+ CRL_REASON_NAMES: CRL_REASON_NAMES,
860
1043
  certExtensionDecoders: certExtensionDecoders,
861
1044
  attribute: attribute,
862
1045
  extension: extension,