@blamejs/pki 0.6.13 → 0.6.15

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.
Files changed (114) hide show
  1. package/CHANGELOG.md +22 -1
  2. package/README.md +1 -1
  3. package/index.js +1 -1
  4. package/lib/acme.js +59 -815
  5. package/lib/asn1-der.js +7 -245
  6. package/lib/attrcert-sign.js +12 -260
  7. package/lib/attrcert-verify.js +1 -7
  8. package/lib/byte-reader.js +0 -8
  9. package/lib/byte-writer.js +0 -11
  10. package/lib/cbor-det.js +0 -54
  11. package/lib/cmc-build.js +15 -357
  12. package/lib/cmc-verify.js +6 -239
  13. package/lib/cmp-build.js +41 -317
  14. package/lib/cmp-session.js +73 -760
  15. package/lib/cmp-verify.js +32 -488
  16. package/lib/cms-compress.js +3 -32
  17. package/lib/cms-decrypt.js +29 -294
  18. package/lib/cms-encrypt.js +14 -234
  19. package/lib/cms-sign.js +29 -324
  20. package/lib/cms-verify.js +9 -500
  21. package/lib/composite-kem.js +2 -102
  22. package/lib/composite-sig.js +1 -77
  23. package/lib/constants.js +0 -249
  24. package/lib/crl-sign.js +9 -237
  25. package/lib/crl-verify.js +1 -10
  26. package/lib/crmf-sign.js +19 -198
  27. package/lib/crmf-verify.js +1 -12
  28. package/lib/csr-sign.js +3 -34
  29. package/lib/csr-verify.js +1 -12
  30. package/lib/ct.js +28 -253
  31. package/lib/edwards-point.js +10 -44
  32. package/lib/est.js +28 -764
  33. package/lib/framework-error.js +4 -182
  34. package/lib/guard-all.js +1 -58
  35. package/lib/guard-async.js +1 -36
  36. package/lib/guard-bytes.js +6 -650
  37. package/lib/guard-compress.js +5 -102
  38. package/lib/guard-crypto.js +1 -40
  39. package/lib/guard-der.js +2 -93
  40. package/lib/guard-encoding.js +2 -60
  41. package/lib/guard-header.js +1 -36
  42. package/lib/guard-identifier.js +7 -531
  43. package/lib/guard-intrinsic.js +1 -150
  44. package/lib/guard-json.js +10 -73
  45. package/lib/guard-limits.js +1 -77
  46. package/lib/guard-list.js +1 -49
  47. package/lib/guard-name.js +3 -209
  48. package/lib/guard-parsed.js +7 -335
  49. package/lib/guard-range.js +1 -71
  50. package/lib/guard-secret.js +1 -102
  51. package/lib/guard-text.js +1 -64
  52. package/lib/guard-time.js +1 -97
  53. package/lib/hpke.js +3 -105
  54. package/lib/http-digest.js +14 -142
  55. package/lib/http-retry-after.js +1 -47
  56. package/lib/http-transport.js +21 -131
  57. package/lib/inspect.js +30 -284
  58. package/lib/ip-utils.js +6 -47
  59. package/lib/jose.js +7 -142
  60. package/lib/key.js +2 -67
  61. package/lib/lint.js +9 -194
  62. package/lib/merkle.js +1 -27
  63. package/lib/mime.js +9 -82
  64. package/lib/ocsp-request-verify.js +1 -14
  65. package/lib/ocsp-verify.js +1 -82
  66. package/lib/ocsp.js +11 -169
  67. package/lib/oid.js +5 -337
  68. package/lib/path-validate.js +103 -1492
  69. package/lib/pbes2.js +2 -89
  70. package/lib/pkcs12-build.js +31 -379
  71. package/lib/pki-build.js +64 -290
  72. package/lib/rc2.js +2 -18
  73. package/lib/rfc3339.js +4 -27
  74. package/lib/scep.js +59 -233
  75. package/lib/schema-all.js +0 -122
  76. package/lib/schema-attrcert.js +4 -146
  77. package/lib/schema-c509.js +153 -802
  78. package/lib/schema-cmc.js +5 -220
  79. package/lib/schema-cmp.js +2 -223
  80. package/lib/schema-cms.js +13 -427
  81. package/lib/schema-crl.js +5 -68
  82. package/lib/schema-crmf.js +6 -167
  83. package/lib/schema-csr.js +1 -54
  84. package/lib/schema-csrattrs.js +2 -87
  85. package/lib/schema-engine.js +0 -152
  86. package/lib/schema-ocsp.js +0 -117
  87. package/lib/schema-pkcs12.js +2 -93
  88. package/lib/schema-pkcs8.js +3 -35
  89. package/lib/schema-pkix.js +24 -655
  90. package/lib/schema-smime.js +0 -54
  91. package/lib/schema-tsp.js +3 -99
  92. package/lib/schema-x509.js +4 -63
  93. package/lib/shbs.js +11 -95
  94. package/lib/sign-scheme.js +2 -97
  95. package/lib/sigstore.js +9 -169
  96. package/lib/sleep.js +2 -8
  97. package/lib/smime.js +15 -408
  98. package/lib/tls-cert-compress.js +0 -62
  99. package/lib/trust.js +19 -185
  100. package/lib/tsp-sign.js +16 -270
  101. package/lib/validator-all.js +1 -30
  102. package/lib/validator-attcert.js +2 -46
  103. package/lib/validator-cose.js +2 -153
  104. package/lib/validator-keydesc.js +1 -37
  105. package/lib/validator-sig.js +1 -46
  106. package/lib/validator-tls.js +1 -88
  107. package/lib/validator-tpm.js +19 -122
  108. package/lib/verify-seam.js +1 -24
  109. package/lib/webauthn-mds.js +3 -400
  110. package/lib/webauthn.js +12 -833
  111. package/lib/webcrypto.js +5 -485
  112. package/lib/x509-sign.js +4 -108
  113. package/package.json +1 -1
  114. package/sbom.cdx.json +6 -6
@@ -1,32 +1,14 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
  // Copyright (c) blamejs contributors
3
3
  "use strict";
4
- //
5
- // @internal -- no operator-facing namespace. The documented surface is the
6
- // parsers that compose these factories (pki.schema.x509, pki.schema.crl, ...).
7
- //
8
- // Shared PKIX structure-schema factories (RFC 5280). Each is a namespace-
9
- // parameterized FACTORY: given an error namespace `ns` ({ prefix, E, oid }) it
10
- // returns an asn1-schema that walks the corresponding ASN.1 structure and emits
11
- // the caller's own <prefix>/* error codes. x509.js, crl.js, and future CMS/CSR
12
- // parsers compose these so AlgorithmIdentifier / Name / Extension are defined
13
- // once, not re-derived per format. This module is internal infrastructure: the
14
- // operator-facing surface is the parsers that consume it.
4
+ // @internal
15
5
 
16
6
  var asn1 = require("./asn1-der");
17
7
  var constants = require("./constants");
18
8
  var schema = require("./schema-engine");
19
9
  var guard = require("./guard-all");
20
10
  var intrinsic = require("./guard-intrinsic");
21
- // Own-key membership through an operation taken at load. Written out, the question reads two
22
- // replaceable properties to answer one thing, and here it decides which version numbers a format
23
- // accepts -- shared by every format module, so one admitted name widens all of them at once.
24
11
  var _hasOwn = intrinsic.hasOwn;
25
- // The rest of the operations this module decides with, taken at load for the same reason. What
26
- // these factories produce is what a consumer compares an identity against -- a certificate's
27
- // subject alternative names against an enrollment's own, a distinguished name against an issuer's
28
- // -- so a name that traversal, folding or a byte copy invents here is a name the encoded structure
29
- // never carried, and the comparison downstream still answers true.
30
12
  var _forEach = intrinsic.forEach;
31
13
  var _map = intrinsic.map;
32
14
  var _push = intrinsic.push;
@@ -53,10 +35,6 @@ var _WeakMapI = intrinsic.WeakMap;
53
35
  var _arrayFrom = Array.from;
54
36
  var _getUTCFullYear = intrinsic.uncurry(Date.prototype.getUTCFullYear);
55
37
 
56
- // The JavaScript regex `\s` class, decided by code point, so the whitespace tests below match
57
- // exactly what `/\s/` and `/\S/` matched with no caller-replaceable match protocol standing in for
58
- // them. Set = ECMAScript WhiteSpace + LineTerminator: TAB LF VT FF CR SP, U+00A0, U+1680,
59
- // U+2000..U+200A, U+2028, U+2029, U+202F, U+205F, U+3000, U+FEFF.
60
38
  function _isJsWhitespace(c) {
61
39
  return c === 0x09 || c === 0x0a || c === 0x0b || c === 0x0c || c === 0x0d || c === 0x20 ||
62
40
  c === 0xa0 || c === 0x1680 || (c >= 0x2000 && c <= 0x200a) || c === 0x2028 || c === 0x2029 ||
@@ -70,33 +48,18 @@ function _hasNonWhitespace(s) {
70
48
  for (var i = 0; i < s.length; i++) { if (!_isJsWhitespace(_charCodeAt(s, i))) return true; }
71
49
  return false;
72
50
  }
73
- // Charset validation by character scan (the shared home for the `/^[chars]+$/`-family regexes format
74
- // modules used to carry): `charTable` builds a code-point membership table from a string of the allowed
75
- // characters, and `allCharsIn` checks that a NON-EMPTY string is composed only of them (`+`, not `*`).
76
- // One data-table loop, not a per-charset inlined character-class chain.
77
51
  function _charTable(chars) { var t = []; for (var i = 0; i < chars.length; i++) t[_charCodeAt(chars, i)] = true; return t; }
78
52
  function _allCharsIn(s, table) {
79
53
  if (s.length === 0) return false;
80
54
  for (var i = 0; i < s.length; i++) { if (!table[_charCodeAt(s, i)]) return false; }
81
55
  return true;
82
56
  }
83
- // ct is a leaf format module (requires only the codec / oid / constants / error
84
- // core, never pkix) so the SCT-list extension VALUE decoder registers here as a
85
- // registry row, not a switch. The one-directional edge keeps the layering acyclic.
86
57
  var ct = require("./ct");
87
58
 
88
- // PEM armor markers, matched literally so no regex -- with its live, replaceable match protocol and
89
- // the backreference the END repeats -- stands between the parser and the wire.
90
59
  var _PEM_BEGIN = "-----BEGIN ";
91
60
  var _PEM_END = "-----END ";
92
61
  var _PEM_DASHES = "-----";
93
62
 
94
- // The next PEM block at or after `from`: "-----BEGIN <label>-----<body>-----END <label>-----" where
95
- // the label is one or more of [A-Z0-9 ] (RFC 7468 label octets, spaces included) and the END label
96
- // repeats the BEGIN label exactly (the backreference). Returns { index, label, body, end } -- `index`
97
- // the BEGIN offset, `end` the offset past the closing dashes -- or null when no such block remains.
98
- // A "-----BEGIN " with no valid label or no matching END is skipped, mirroring the regex trying each
99
- // start position; the body is the shortest span to the first matching END (the lazy quantifier).
100
63
  function _findPemBlock(text, from) {
101
64
  var searchFrom = from;
102
65
  for (;;) {
@@ -105,7 +68,7 @@ function _findPemBlock(text, from) {
105
68
  var p = bi + _PEM_BEGIN.length, ls = p;
106
69
  while (p < text.length) {
107
70
  var c = _charCodeAt(text, p);
108
- if (!((c >= 65 && c <= 90) || (c >= 48 && c <= 57) || c === 32)) break; // A-Z 0-9 space
71
+ if (!((c >= 65 && c <= 90) || (c >= 48 && c <= 57) || c === 32)) break;
109
72
  p += 1;
110
73
  }
111
74
  if (p === ls || _strSlice(text, p, p + 5) !== _PEM_DASHES) { searchFrom = bi + 1; continue; }
@@ -118,7 +81,6 @@ function _findPemBlock(text, from) {
118
81
  }
119
82
  }
120
83
 
121
- // All ASCII CR / LF / TAB / SPACE removed (the base64 body's permitted layout whitespace).
122
84
  function _stripB64Whitespace(s) {
123
85
  var out = "", runStart = 0, i;
124
86
  for (i = 0; i < s.length; i++) {
@@ -132,8 +94,6 @@ function _stripB64Whitespace(s) {
132
94
  return out;
133
95
  }
134
96
 
135
- // A base64 string wrapped to 64-column lines joined by LF, no trailing LF (matching (.{64})->"$1\n"
136
- // then a trailing-newline strip; base64 output carries no newline of its own to strip).
137
97
  function _wrap64(s) {
138
98
  var out = "";
139
99
  for (var i = 0; i < s.length; i += 64) {
@@ -143,22 +103,8 @@ function _wrap64(s) {
143
103
  return out;
144
104
  }
145
105
 
146
- // ---- shared parse-entry ----------------------------------------------
147
- // Input handling, PEM unwrapping (with the size cap), DER-decode wrapping, and
148
- // the walk are defined ONCE here so no format can diverge on a guard. Each
149
- // format supplies only its labels, error class, code prefix, and top schema.
150
-
151
- // pemDecode(text, label, PemError): `label` (when truthy) is enforced, else the
152
- // first block is taken. Applies the LIMITS.PEM_MAX_BYTES cap before scanning.
153
- // The body must be CANONICAL base64 (RFC 4648 sec. 3.5): whole 4-character
154
- // groups, no stray padding, zero trailing bits in the final symbol. Node's
155
- // lenient Buffer.from would otherwise let several distinct PEM texts alias one
156
- // DER (PEM-layer malleability) and silently drop trailing content bits.
106
+
157
107
  function pemDecode(text, label, PemError) {
158
- // guard.text.decode caps the raw byte length before the latin1 string copy:
159
- // converting first would allocate a full-size string for input the cap is
160
- // about to reject, and a buffer above Node's max string length would escape as
161
- // an untyped ERR_STRING_TOO_LONG instead of pem/too-large.
162
108
  text = guard.text.decode(text, constants.LIMITS.PEM_MAX_BYTES, PemError, {
163
109
  charset: "latin1", tooLarge: "pem/too-large", badInput: "pem/bad-input", label: "PEM input",
164
110
  });
@@ -166,16 +112,9 @@ function pemDecode(text, label, PemError) {
166
112
  if (!m) throw new PemError("pem/no-block", "no PEM block found");
167
113
  if (label && m.label !== label) throw new PemError("pem/label-mismatch", "expected " + _stringify(label) + " block, got " + _stringify(m.label));
168
114
  var b64 = _stripB64Whitespace(m.body);
169
- // Strict canonical base64 (RFC 4648 sec. 3.5) via the shared encoding guard.
170
115
  return guard.encoding.base64(b64, null, function (c, msg) { return new PemError(c, msg); }, "pem/bad-base64", "PEM base64 body");
171
116
  }
172
117
 
173
- // pemDecodeAll(text, label, PemError): decode every PEM block under the strict
174
- // RFC 7468 profile for application/pem-certificate-chain (RFC 8555 sec. 9.1) --
175
- // each block must carry `label` (default CERTIFICATE), explanatory text MUST NOT
176
- // appear before/between/after blocks (errata 5983: certchain = stricttextualmsg
177
- // *(eol stricttextualmsg)), and at least one block is required. Every block's
178
- // base64 body is canonical (the pemDecode rule); returns an array of DER buffers.
179
118
  function pemDecodeAll(text, label, PemError) {
180
119
  label = label || "CERTIFICATE";
181
120
  text = guard.text.decode(text, constants.LIMITS.PEM_MAX_BYTES, PemError, {
@@ -195,14 +134,6 @@ function pemDecodeAll(text, label, PemError) {
195
134
  return blocks;
196
135
  }
197
136
 
198
- // The label must be re-readable by this module's own PEM label scan (uppercase
199
- // alphanumeric words separated by single spaces, a subset of the RFC 7468
200
- // sec. 3 label grammar). Anything else (lowercase, a leading/trailing space,
201
- // an embedded "-----" or newline) would emit armor pemDecode itself rejects
202
- // or corrupt the armor grammar: a config-time authoring fault, thrown here.
203
- // The armor label grammar, decided character by character (no regex protocol on the emit path):
204
- // one or more A-Z0-9 words joined by single spaces -- so no leading/trailing space and no doubled
205
- // space, and every other character in [A-Z0-9].
206
137
  function _isPemLabel(s) {
207
138
  var n = s.length;
208
139
  if (n === 0) return false;
@@ -221,16 +152,11 @@ function pemEncode(der, label, PemError) {
221
152
  if (typeof label !== "string" || !_isPemLabel(label)) {
222
153
  throw new PemError("pem/bad-label", "pemEncode requires an uppercase A-Z0-9 label with single spaces");
223
154
  }
224
- // Re-view through the byte guard: a string input would silently utf8-armor into
225
- // a bogus PEM, and a detached-backed Buffer would armor an empty body; both
226
- // fail closed (a Uint8Array / Buffer of real DER re-views cleanly).
227
155
  var buf = guard.bytes.view(der, PemError, "pem/bad-input", "pemEncode DER input");
228
156
  var b64 = _wrap64(_bufToString(buf, "base64"));
229
157
  return "-----BEGIN " + label + "-----\n" + b64 + "\n-----END " + label + "-----\n";
230
158
  }
231
159
 
232
- // Only the base64 alphabet (A-Z a-z 0-9 + / =) kept, every other character dropped -- the lenient
233
- // filter of the historic signer-certificate path, by character scan.
234
160
  function _keepBase64(s) {
235
161
  var out = "", runStart = 0, i;
236
162
  for (i = 0; i < s.length; i++) {
@@ -244,11 +170,7 @@ function _keepBase64(s) {
244
170
  return out;
245
171
  }
246
172
 
247
- // The @internal LENIENT PEM decode of a fixed-`label` block (the historic signer-certificate path,
248
- // distinct from the strict canonical `pemDecode` above): the body of the first
249
- // "-----BEGIN <label>-----...-----END <label>-----" block with any non-base64 character stripped,
250
- // decoded through Node's lenient base64. Returns the DER Buffer, or null when no such block exists so
251
- // the caller keeps its own domain error. NOT for verified input -- pemDecode is the fail-closed door.
173
+ // @internal
252
174
  function pemDecodeLenient(text, label) {
253
175
  var begin = _PEM_BEGIN + label + _PEM_DASHES;
254
176
  var end = _PEM_END + label + _PEM_DASHES;
@@ -260,27 +182,15 @@ function pemDecodeLenient(text, label) {
260
182
  return _bufferFrom(_keepBase64(_strSlice(text, bodyStart, ei)), "base64");
261
183
  }
262
184
 
263
- // Coerce parse input to DER bytes. A PEM string OR a PEM Buffer (a .pem file
264
- // read with fs.readFileSync) is unwrapped with opts.pemLabel; a DER Buffer or
265
- // Uint8Array is taken as bytes. opts: { pemLabel, PemError, ErrorClass, prefix }.
266
185
  function coerceToDer(input, opts) {
267
186
  if (typeof input === "string") return pemDecode(input, opts.pemLabel, opts.PemError);
268
187
  if (guard.bytes.isByteSource(input)) {
269
- // Route any DER BufferSource (Buffer, typed array, DataView, or ArrayBuffer) through the shared byte
270
- // guard so a detached backing store fails closed here for every format that composes this boundary
271
- // (x509/crl/csr/pkcs8/cms/pkcs12), not per-format, and its byte view is read from the slot -- a
272
- // cross-realm ArrayBuffer or an offset DataView reaches the decoder exactly as a Buffer does.
273
188
  var buf = guard.bytes.source(input, opts.ErrorClass, opts.prefix + "/bad-input", "parse");
274
189
  return _isPemArmor(buf) ? pemDecode(buf, opts.pemLabel, opts.PemError) : buf;
275
190
  }
276
191
  throw new opts.ErrorClass(opts.prefix + "/bad-input", "parse expects a DER BufferSource (Buffer, typed array, DataView, or ArrayBuffer) or a PEM string");
277
192
  }
278
193
 
279
- // Does a Buffer carry PEM armor (a .pem read with fs.readFileSync) instead of
280
- // raw DER? It does iff "-----BEGIN" appears and everything before it is TEXT
281
- // (UTF-8 BOM / whitespace / RFC 7468 explanatory preamble). DER is binary and its
282
- // leading tag+length bytes are non-printable, so a non-SEQUENCE DER (a bare
283
- // SET / INTEGER) is not misrouted here; it decodes and fails closed structurally.
284
194
  function _isPemArmor(buf) {
285
195
  var head = _bufToString(_subarray(buf, 0, 4096), "latin1");
286
196
  var idx = _strIndexOf(head, "-----BEGIN");
@@ -288,56 +198,29 @@ function _isPemArmor(buf) {
288
198
  for (var i = 0; i < idx; i++) {
289
199
  var c = buf[i];
290
200
  var textByte = (c >= 0x20 && c < 0x7f) || c === 0x09 || c === 0x0a || c === 0x0d ||
291
- c === 0xef || c === 0xbb || c === 0xbf; // printable ASCII, tab/newlines, UTF-8 BOM
201
+ c === 0xef || c === 0xbb || c === 0xbf;
292
202
  if (!textByte) return false;
293
203
  }
294
204
  return true;
295
205
  }
296
206
 
297
- // Decode the DER root, wrapping a codec fault in the caller's <prefix>/bad-der.
298
207
  function decodeRoot(der, opts) {
299
- // A format whose wire encoding is normatively BER (RFC 7292 PKCS#12) opts
300
- // in to the codec's ber mode for the whole decode. BER-vs-DER divergence is
301
- // not always a decode-time throw: a definite-length constructed IMPLICIT
302
- // OCTET STRING decodes strictly and only diverges at the typed reader, so
303
- // a strict-first-retry-on-throw boundary would miss legal BER shapes. A
304
- // strict-DER input decodes identically under the ber mode, every other
305
- // strictness verdict included.
306
208
  try { return asn1.decode(der, opts.ber ? { ber: true } : undefined); }
307
209
  catch (e) {
308
210
  throw new opts.ErrorClass(opts.prefix + "/bad-der", (opts.what || "input") + " DER did not decode: " + ((e && e.message) || _String(e)), e);
309
211
  }
310
212
  }
311
213
 
312
- // The shared parse entry: coerce -> decode -> walk the top schema. A format's
313
- // parse() is one call to this; the guard-parity bug class (a new format not
314
- // mirroring an existing format's input handling) is structurally impossible.
315
214
  function runParse(input, opts) {
316
215
  return schema.walk(opts.topSchema, decodeRoot(coerceToDer(input, opts), opts), opts.ns).result;
317
216
  }
318
217
 
319
- // Distinguished-name attribute short labels (RFC 4514 sec. 3 + common use).
320
- // RFC 4514 short names for the DN string representation; the single source is
321
- // pki.C.NAMES.DN_SHORT (shared with the human-readable renderer so the two can't drift).
322
218
  var DN_SHORT = constants.NAMES.DN_SHORT;
323
219
 
324
- // AlgorithmIdentifier ::= SEQUENCE { algorithm OBJECT IDENTIFIER, parameters ANY OPTIONAL }
325
- // The error/OID namespace every format module walks its schema under:
326
- // { prefix, E:(code,message,cause)=>new ErrorClass(...), oid }. Factored so a
327
- // format declares it in one line instead of repeating the error-constructor
328
- // closure (a caller that never passes a cause is unaffected, since withCause ignores
329
- // an undefined third arg).
330
220
  function makeNS(prefix, ErrorClass, oidModule) {
331
221
  return { prefix: prefix, E: function (code, message, cause) { return new ErrorClass(code, message, cause); }, oid: oidModule };
332
222
  }
333
223
 
334
- // A bounded universal-INTEGER version reader. `accept` maps each legal wire value
335
- // (as a decimal string) to its surfaced version number; any other value is a
336
- // <prefix>/bad-version fault. The one genuine per-format divergence, the cert
337
- // rejects 0 and maps 1->2/2->3, a CRL accepts only 1->2, a CSR only 0->1, a PKCS#8
338
- // 0->1/1->2, is expressed purely as the accept map (RFC 5280 sec. 4.1.2.1 / sec. 5.1.2.1,
339
- // RFC 2986 sec. 4.1, RFC 5958 sec. 2). read.integer is strict, so an ENUMERATED-tagged
340
- // version is rejected at the leaf (asn1/*).
341
224
  function versionReader(ns, accept) {
342
225
  return schema.decode(function (n) {
343
226
  var key = _bigIntToString(asn1.read.integer(n));
@@ -346,22 +229,6 @@ function versionReader(ns, accept) {
346
229
  });
347
230
  }
348
231
 
349
- // opts.implicitTag (optional): read the AlgorithmIdentifier as a [tag] IMPLICIT
350
- // SEQUENCE (a context-class constructed node whose children are algorithm + parameters),
351
- // for the RFC 5652 sec. 6.2.4 pwri.keyDerivationAlgorithm [0]. With no opts the shape is a
352
- // universal SEQUENCE, byte-identical to every existing caller.
353
- //
354
- // Parameter-rule tiering: only the params-MUST-BE-ABSENT families (ML-DSA,
355
- // SLH-DSA, the Edwards/Montgomery curves), new registrations with no legacy
356
- // corpus) are enforced at parse time, in the build below. The CLASSICAL
357
- // per-OID rules (RSA parameters MUST be NULL, RFC 3279 sec. 2.2.1/2.3.1;
358
- // ecdsa-with-SHA2 parameters MUST be omitted, RFC 5758 sec. 3.2) are
359
- // deliberately not parse-time rejects: a long tail of deployed certificates
360
- // omits the RSA NULL, so the parse tier stays interop-lenient for those
361
- // families and the strict classical check runs on the signature-verification
362
- // path (path-validate's signature-algorithm registry), where the verdict
363
- // matters. SPKI key AlgorithmIdentifiers follow the same tiering: lenient at
364
- // parse, validated by the key-import layer that consumes them.
365
232
  function algorithmIdentifier(ns, opts) {
366
233
  opts = opts || {};
367
234
  return schema.seq([
@@ -372,13 +239,6 @@ function algorithmIdentifier(ns, opts) {
372
239
  arity: { min: 1 }, code: ns.prefix + "/bad-algorithm-identifier", what: "AlgorithmIdentifier",
373
240
  build: function (m, ctx) {
374
241
  var dotted = m.fields.algorithm.value;
375
- // The registry marks several algorithm families parameters-absent: the PQC
376
- // signature families (ML-DSA RFC 9881, SLH-DSA RFC 9909/9814), the
377
- // Edwards/Montgomery curves (RFC 8410), ML-KEM (RFC 9936), and the HKDF
378
- // identifiers (RFC 8619). For all of them the `parameters` field MUST be
379
- // absent: no explicit NULL, no bytes. Enforced ONCE here so every consumer of the
380
- // shared AlgorithmIdentifier inherits the rule; the guard-parity bug
381
- // class is structurally impossible.
382
242
  if (m.fields.parameters.present && ctx.oid.paramsMustBeAbsent(dotted)) {
383
243
  throw ctx.E(ctx.prefix + "/bad-algorithm-parameters",
384
244
  "the " + (ctx.oid.name(dotted) || dotted) + " AlgorithmIdentifier parameters field MUST be absent");
@@ -388,10 +248,6 @@ function algorithmIdentifier(ns, opts) {
388
248
  });
389
249
  }
390
250
 
391
- // pbkdf2Params(ns): PBKDF2-params (RFC 8018 sec. 5.2) constrained to the RFC 9579 PBMAC1 profile, where the
392
- // salt uses the OCTET STRING choice and keyLength MUST be present (a MacData / PKIProtection consumer
393
- // cannot infer the derived MAC key size, RFC 9579 sec. 4.b/5). Shared by the PKCS#12 MacData reader and
394
- // the CMP PBMAC1 protection reader: one ns-parameterized decoder so neither format re-derives the shape.
395
251
  function pbkdf2Params(ns) {
396
252
  return schema.seq([
397
253
  schema.field("salt", schema.octetString()),
@@ -405,28 +261,13 @@ function pbkdf2Params(ns) {
405
261
  if (!m.fields.keyLength.present) {
406
262
  throw ctx.E(ctx.prefix + "/bad-mac-data", "PBMAC1 PBKDF2-params must carry keyLength (RFC 9579 sec. 5)");
407
263
  }
408
- // guard.range.positiveInt31 bounds + narrows each counter atomically, so a value past the bound
409
- // would round silently and hand a verifier wrong inputs.
410
264
  var iterationCount = guard.range.positiveInt31(m.fields.iterationCount.value, ctx.E, ctx.prefix + "/bad-mac-data", "PBKDF2 iterationCount");
411
265
  var keyLength = guard.range.positiveInt31(m.fields.keyLength.value, ctx.E, ctx.prefix + "/bad-mac-data", "PBKDF2 keyLength");
412
266
  var prf = m.fields.prf.present ? m.fields.prf.value.result : null;
413
- // X.690 sec. 11.5: the prf DEFAULT is algid-hmacWithSHA1 (hmacWithSHA1 with NULL parameters,
414
- // i.e. the 2-byte DER 05 00, RFC 8018 sec. 5.2); an explicit prf byte-equal to that default is
415
- // non-canonical and rejects. hmacWithSHA1 with ABSENT parameters is a different value and decodes.
416
- // This is a public structural check on the algorithm parameters, not a secret compare, so a
417
- // direct byte test on the (fixed 2-octet) NULL encoding, not a timing-safe comparison.
418
267
  var pp = prf ? prf.parameters : null;
419
- // pp is the public algorithm-identifier parameters field, a fixed 2-octet NULL encoding.
420
- // No MAC, tag or secret is involved, so a timing-safe compare has nothing to protect.
421
- // nosemgrep: pki-non-constant-time-secret-compare
422
268
  if (prf && prf.oid === hmacSha1 && pp !== null && pp.length === 2 && pp[0] === 0x05 && pp[1] === 0x00) {
423
269
  throw ctx.E(ctx.prefix + "/bad-mac-data", "a PBKDF2 prf equal to its DEFAULT algid-hmacWithSHA1 must be omitted (X.690 sec. 11.5, RFC 8018 sec. 5.2)");
424
270
  }
425
- // The PBKDF2 prf HMAC AlgorithmIdentifier likewise carries NULL (or absent) parameters (RFC 8018 App. B.1):
426
- // reject a present-but-non-NULL prf parameter (e.g. an INTEGER) instead of discarding it, since the prf
427
- // hash is dispatched by OID alone. (An absent prf, and hmacWithSHA1 with absent params, remain valid above.)
428
- // pp is the public algorithm-identifier parameters node.
429
- // nosemgrep: pki-non-constant-time-secret-compare
430
271
  if (prf && pp !== null && !(pp.length === 2 && pp[0] === 0x05 && pp[1] === 0x00)) {
431
272
  throw ctx.E(ctx.prefix + "/bad-mac-data", "the PBKDF2 prf parameters must be absent or NULL (RFC 8018 App. B.1)");
432
273
  }
@@ -441,11 +282,6 @@ function pbkdf2Params(ns) {
441
282
  });
442
283
  }
443
284
 
444
- // pbmac1Params(ns): PBMAC1-params ::= SEQUENCE { keyDerivationFunc AlgorithmIdentifier{PBKDF2},
445
- // messageAuthScheme AlgorithmIdentifier } (RFC 8018 App. A.5 / RFC 9579 sec. 4). The PBKDF2 prf and the
446
- // messageAuthScheme HMAC are INDEPENDENT (a SHA-512 prf with a SHA-256 HMAC is legal). Shared by PKCS#12
447
- // MacData and CMP PBMAC1 protection; returns { kdf: { salt, iterationCount, keyLength, prfOid, prfName },
448
- // schemeOid, schemeName }.
449
285
  function pbmac1Params(ns) {
450
286
  return schema.seq([
451
287
  schema.field("keyDerivationFunc", schema.seq([
@@ -461,14 +297,7 @@ function pbmac1Params(ns) {
461
297
  throw ctx.E(ctx.prefix + "/bad-mac-data", "PBMAC1 keyDerivationFunc must be PBKDF2 (RFC 9579 sec. 4)");
462
298
  }
463
299
  var scheme = m.fields.messageAuthScheme.value.result;
464
- // RFC 8018 App. B.1: an HMAC messageAuthScheme AlgorithmIdentifier carries NULL (or absent) parameters,
465
- // matching the builder (_hmacAlgId emits the 2-byte 05 00). Any other parameter encoding is
466
- // rejected: _verifyMac dispatches by the scheme OID alone, so a mismatched / malformed
467
- // parameter (e.g. an INTEGER) must not slip through unvalidated. A direct byte test on the fixed 2-octet
468
- // NULL encoding of the PUBLIC parameters, not a secret compare.
469
300
  var sp = scheme.parameters;
470
- // sp is the public algorithm-identifier parameters node.
471
- // nosemgrep: pki-non-constant-time-secret-compare
472
301
  if (sp !== null && !(sp.length === 2 && sp[0] === 0x05 && sp[1] === 0x00)) {
473
302
  throw ctx.E(ctx.prefix + "/bad-mac-data", "the PBMAC1 messageAuthScheme parameters must be absent or NULL (RFC 8018 App. B.1)");
474
303
  }
@@ -481,18 +310,6 @@ function pbmac1Params(ns) {
481
310
  });
482
311
  }
483
312
 
484
- // attrValueToString(ns): the AttributeValue decode-leaf. A malformed KNOWN
485
- // string type (invalid UTF-8, a non-IA5 byte, a PrintableString character
486
- // outside its set, ...) surfaces as an asn1/bad-* content error and must fail
487
- // closed. Do not hex-encode it away, or the decoder's strict string validation
488
- // is silently bypassed on the DN path. A value that is simply not a decodable
489
- // primitive string is not malformed and stays representable: an ANY-typed
490
- // non-string tag (asn1/expected-string) or a constructed universal type such as
491
- // a SEQUENCE (asn1/expected-primitive) renders per RFC 4514 sec. 2.4 as "#" plus the
492
- // hex of its complete DER encoding (node.bytes), round-tripping intact.
493
- // A GENUINE string whose first character is '#' or '\' is surfaced with a
494
- // leading '\' escape (RFC 4514 sec. 2.4) so it can never collide with the
495
- // '#'+hex form; the encode direction strips that one leading escape back off.
496
313
  function attrValueToString(ns) {
497
314
  return schema.decode(function (node) {
498
315
  var s;
@@ -501,12 +318,6 @@ function attrValueToString(ns) {
501
318
  if (!e || (e.code !== "asn1/expected-string" && e.code !== "asn1/expected-primitive")) {
502
319
  throw ns.E(ns.prefix + "/bad-atv", "malformed string in attribute value: " + ((e && e.message) || _String(e)));
503
320
  }
504
- // asn1.read.string does not read NumericString, and that is deliberate: a value that returns a plain
505
- // string joins the RFC 5280 sec. 7.1 name-comparison identity class, and NumericString is not a
506
- // DirectoryString type.
507
- // It does have its own strict reader though, so VALIDATE the content here (an out-of-alphabet value is
508
- // malformed DER and must not be accepted merely because it falls to the opaque form) while still
509
- // surfacing it in the type-distinct RFC 4514 hex form, which keeps it out of that identity class.
510
321
  if (node.tagClass === "universal" && node.tagNumber === asn1.TAGS.NUMERIC_STRING) {
511
322
  try { asn1.read.numericString(node); }
512
323
  catch (e2) { throw ns.E(ns.prefix + "/bad-atv", "malformed NumericString in attribute value: " + ((e2 && e2.message) || _String(e2))); }
@@ -516,21 +327,12 @@ function attrValueToString(ns) {
516
327
  if (_charAt(s, 0) === "#" || _charAt(s, 0) === "\\") return "\\" + s;
517
328
  return s;
518
329
  }, function (value) {
519
- // encode: a leading '\' escapes the next character (the decode's escape of a
520
- // literal '#'/'\'); a "#hex" form (RFC 4514 sec. 2.4) round-trips its raw DER
521
- // verbatim. The hex is validated (even-length hex digits encoding exactly one
522
- // DER TLV) and throws on anything else, since Buffer.from(str, "hex") stops at
523
- // the first invalid character and would emit truncated bytes. Any other
524
- // string encodes as UTF8String (the decode does not preserve the original
525
- // string type, so this is the canonical re-encoding, not a byte-exact one).
526
330
  if (typeof value === "string" && _charAt(value, 0) === "\\") return asn1.build.utf8(_strSlice(value, 1));
527
331
  if (typeof value === "string" && _charAt(value, 0) === "#") {
528
332
  var hex = _strSlice(value, 1);
529
333
  if (hex.length === 0) {
530
334
  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)");
531
335
  }
532
- // Strict canonical hex via the shared encoding guard (even-length, canonical,
533
- // no silent truncation at the first non-hex character).
534
336
  var raw = guard.encoding.hex(hex, null, ns.E, ns.prefix + "/bad-atv", "a #hex attribute value");
535
337
  try { asn1.decode(raw); }
536
338
  catch (e) { throw ns.E(ns.prefix + "/bad-atv", "a #hex attribute value must encode exactly one DER TLV", e); }
@@ -540,38 +342,14 @@ function attrValueToString(ns) {
540
342
  });
541
343
  }
542
344
 
543
- // The always-escape specials of RFC 4514 sec. 2.4, plus its positional rules:
544
- // NUL escapes as \00, a trailing space as '\ ', and a leading '#' or space as
545
- // '\#' / '\ '. Without them the rendered dn is ambiguous (a literal '#0500'
546
- // value would be indistinguishable from the hexstring form) and leading /
547
- // trailing spaces would be silently significant.
548
- // RFC 4514 sec. 2.4 attribute-value escaping is the single guard-name primitive
549
- // (pki.inspect reuses the dn this produces and does not re-escape the already-
550
- // escaped value), so the display escaper can't diverge from a re-inline elsewhere.
551
345
  var _escapeDnValue = guard.name.escapeDnValue;
552
346
 
553
- // The dn RENDERING of a surfaced AttributeValue: the '#'-leading hex form (a
554
- // non-string value) renders bare, since escaping its '#' would turn the RFC 4514
555
- // hexstring form into an escaped literal. A genuine string (surfaced
556
- // with its leading '#'/'\' escape, see attrValueToString) is unescaped back to
557
- // the semantic string and then rendered with the full escape set.
558
347
  function _dnDisplayValue(v) {
559
348
  if (typeof v !== "string") return v;
560
349
  if (_charAt(v, 0) === "#") return v;
561
350
  return _escapeDnValue(_charAt(v, 0) === "\\" ? _strSlice(v, 1) : v);
562
351
  }
563
352
 
564
- // time(ns): the RFC 5280 Time production under its profile encoding rule
565
- // (sec. 4.1.2.5 for certificate validity, restated for the CRL fields in
566
- // sec. 5.1.2.4-5.1.2.6): a date through the year 2049 MUST be UTCTime and
567
- // GeneralizedTime is reserved for 2050 onward, so a GeneralizedTime carrying a
568
- // 1950..2049 date is a non-conforming second encoding of a representable value
569
- // and is rejected (<prefix>/bad-time); accepting it would let two wire forms
570
- // alias one abstract time (a parser-differential channel). The engine's
571
- // schema.time already applies the cutover on ENCODE; this leaf composes it and
572
- // enforces the same rule on DECODE. A GeneralizedTime-only field (an RFC 3161
573
- // genTime, a CRL invalidityDate, an RFC 5755 AttCertValidityPeriod) must not
574
- // take this leaf, whose syntax has no UTCTime alternative to cut over to.
575
353
  function time(ns) {
576
354
  var base = schema.time(ns);
577
355
  return schema.decode(function (n, ctx) {
@@ -586,10 +364,6 @@ function time(ns) {
586
364
  }, base.write);
587
365
  }
588
366
 
589
- // Name ::= RDNSequence ::= SEQUENCE OF RelativeDistinguishedName; RDN ::= SET OF
590
- // AttributeTypeAndValue ::= SEQUENCE { type OID, value ANY }. The atv asserts
591
- // its SEQUENCE tag (a SET-tagged body is a different ASN.1 type), and repeated
592
- // RDN attribute types stay legal (no uniqueness).
593
367
  function attributeTypeAndValue(ns) {
594
368
  return schema.seq([
595
369
  schema.field("type", schema.oidLeaf()),
@@ -603,25 +377,16 @@ function attributeTypeAndValue(ns) {
603
377
  });
604
378
  }
605
379
  function relativeDistinguishedName(ns) {
606
- // RelativeDistinguishedName ::= SET SIZE (1..MAX), so an empty SET {} is malformed.
607
380
  return schema.setOf(attributeTypeAndValue(ns), { assert: "set", min: 1, code: ns.prefix + "/bad-rdn", what: "RelativeDistinguishedName" });
608
381
  }
609
- // opts.implicitTag (optional): read the Name as a [tag] IMPLICIT RDNSequence. The
610
- // context tag REPLACES the universal SEQUENCE tag, so the node is a context-class
611
- // constructed [tag] whose children ARE the RDN SETs (the CRMF CertTemplate issuer
612
- // [3] / subject [5] IMPLICIT arm, RFC 4211 sec. 5). With no opts the shape is a bare
613
- // universal SEQUENCE OF, byte-identical to every existing caller.
614
382
  function name(ns, opts) {
615
383
  opts = opts || {};
616
384
  function nameBuild(m) {
617
385
  var rdns = [], parts = [];
618
- // Through the captured traversal: `rdns` is the parsed subject a consumer reads a common name
619
- // out of, so a replaced Array.prototype.forEach or push could drop the name the certificate
620
- // carries and seat one it does not.
621
386
  _forEach(m.items, function (rdnItem) {
622
387
  var atvs = [], atvParts = [];
623
388
  _forEach(rdnItem.value.items, function (atvItem) {
624
- var a = atvItem.value.result; // atvItem.value = the atv seq-match; .result = its build result
389
+ var a = atvItem.value.result;
625
390
  _push(atvs, a);
626
391
  var label = (a.name && _hasOwn(DN_SHORT, a.name) && DN_SHORT[a.name]) || a.name || a.type;
627
392
  _push(atvParts, label + "=" + _dnDisplayValue(a.value));
@@ -629,15 +394,6 @@ function name(ns, opts) {
629
394
  _push(rdns, atvs);
630
395
  _push(parts, _join(atvParts, "+"));
631
396
  });
632
- // `dn` is a DISPLAY string, and its spelling is `openssl x509 -subject`: the RDNs in the
633
- // certificate's own order, joined by ", ". That is deliberately not the RFC 4514 / LDAP
634
- // distinguishedName, which reverses the RDNs and joins them with a bare comma (`openssl -nameopt
635
- // RFC2253`), so this string must never be handed to an LDAP client. What IS taken from RFC 4514
636
- // is sec. 2.4 value escaping, so an attribute value holding a comma cannot read as two RDNs. The
637
- // interop gate pins this against openssl's own output. Identity never flows through this string:
638
- // guard.name.dnEqual compares the DER under RFC 5280 sec. 7.1, and `bytes` below carries the raw
639
- // Name DER for a consumer that needs the exact octets (the EST re-enroll subject MUST). Every
640
- // renderer in the toolkit uses this one spelling, so a certificate has one dn whichever parsed it.
641
397
  return { rdns: rdns, dn: _join(parts, ", "), bytes: opts.implicitTag != null ? asn1.sequenceTlv(m.node) : m.node.bytes };
642
398
  }
643
399
  if (opts.implicitTag != null) {
@@ -648,32 +404,6 @@ function name(ns, opts) {
648
404
  assert: "sequence", code: ns.prefix + "/bad-name", what: "Name", build: nameBuild });
649
405
  }
650
406
 
651
- // GeneralName ::= CHOICE (RFC 5280 sec. 4.2.1.6). A validate-and-surface-raw leaf for a
652
- // caller that keeps the value raw but needs it to be a well-formed GeneralName: it
653
- // checks the chosen alternative's tag, form (constructed vs primitive per X.690
654
- // sec. 10.2), and content, then surfaces the value raw ({ bytes, tagClass, tagNumber }).
655
- // The content rule per alternative: otherName [0] is a SEQUENCE { type-id OID, value [0] EXPLICIT
656
- // }; x400Address [3] / ediPartyName [5] are non-empty constructed; directoryName [4]
657
- // EXPLICIT wraps a valid Name; rfc822Name [1] / dNSName [2] / uniformResourceIdentifier
658
- // [6] are primitive non-empty IA5String (7-bit); iPAddress [7] is a 4- or 16-octet
659
- // OCTET STRING; registeredID [8] is a primitive OBJECT IDENTIFIER.
660
- // `opts.code` is the caller's error code
661
- // (e.g. ocsp/bad-requestor-name, tsp/bad-tsa). Shared so the two parsers cannot drift.
662
- // opts.decodeValue (default false): in addition to the raw { bytes, tagClass,
663
- // tagNumber }, surface the DECODED `value` per arm: IA5 text (string), an
664
- // iPAddress Buffer, a directoryName { rdns, dn }, a registeredID OID string,
665
- // or an otherName { typeId, valueBytes }. The path validator's name-constraint
666
- // matcher needs the decoded value; the tsp/ocsp/attrcert consumers pass no
667
- // flag and get the byte-identical raw-only shape.
668
- // opts.subtreeBase (default false): the GeneralSubtree.base form (RFC 5280
669
- // sec. 4.2.1.10). An iPAddress base is an address+mask (8 octets IPv4 / 32 IPv6),
670
- // and not the 4/16-octet SAN address form. Only the iPAddress size rule changes.
671
- // A GeneralizedTime-only leaf. Several formats fix their times to
672
- // GeneralizedTime and reject UTCTime (OCSP RFC 6960, TSP RFC 3161 sec. 2.4.2,
673
- // CMP RFC 9810 sec. 5.1.1, attribute-certificate validity RFC 5755 sec. 4.2.6);
674
- // a single ns-parameterized factory owns the tag assert, so the per-format code and
675
- // message differ but the check cannot drift. opts.allowFractional enables the
676
- // X.690 sec. 11.7 fractional-seconds profile (the RFC 3161 genTime).
677
407
  function generalizedTime(ns, opts) {
678
408
  opts = opts || {};
679
409
  var code = opts.code || (ns.prefix + "/bad-time");
@@ -687,8 +417,6 @@ function generalizedTime(ns, opts) {
687
417
  });
688
418
  }
689
419
 
690
- // A UTF8String-only leaf -- the PKIFreeText element shape (RFC 3161 sec. 2.4.2,
691
- // RFC 9810 sec. 5.1.1) shared by the TSP and CMP parsers.
692
420
  function utf8Text(ns, opts) {
693
421
  opts = opts || {};
694
422
  var code = opts.code || (ns.prefix + "/bad-freetext");
@@ -701,11 +429,6 @@ function utf8Text(ns, opts) {
701
429
  });
702
430
  }
703
431
 
704
- // A raw NON-EMPTY universal SEQUENCE leaf -- surfaces the exact TLV bytes of an
705
- // embedded element (a Certificate, a CertificateList, a PKIPublicationInfo)
706
- // after asserting the SEQUENCE tag and at least one child. An empty SEQUENCE
707
- // (30 00) has the right tag but is none of those structures, so it rejects and
708
- // no degenerate bytes reach certificate processing.
709
432
  function rawNonEmptySequence(ns, opts) {
710
433
  opts = opts || {};
711
434
  var code = opts.code || (ns.prefix + "/bad-sequence");
@@ -718,19 +441,8 @@ function rawNonEmptySequence(ns, opts) {
718
441
  });
719
442
  }
720
443
 
721
- // CRLReason ::= ENUMERATED (RFC 5280 sec. 5.3.1). Value 7 is unused/reserved.
722
- // The single canonical value->name table every consumer validates against: the
723
- // CRL reasonCode decoder surfaces the numeric code, the OCSP RevokedInfo
724
- // decoder the name; both draw the legal set from here so they cannot drift.
725
- // CRLReason ENUMERATED value names -- the single source is pki.C.NAMES.CRL_REASON
726
- // (shared with the OCSP revoked-status decoder, which surfaces the same numeric code).
727
444
  var CRL_REASON_NAMES = constants.NAMES.CRL_REASON;
728
445
 
729
- // Which GeneralName alternatives are constructed, and which carry an IA5String. Both tables answer
730
- // off a tag number the encoded value supplies, and both decide which validation an alternative
731
- // gets, so neither may inherit: with an ordinary prototype a planted numeric property routes a tag
732
- // down another alternative's arm, and a dNSName then reaches the IA5 arm's control-byte reject as
733
- // a constructed value, or misses it altogether.
734
446
  var GN_CONSTRUCTED = _assign(_create(null), { 0: 1, 3: 1, 4: 1, 5: 1 });
735
447
  var GN_IA5 = _assign(_create(null), { 1: 1, 2: 1, 6: 1 });
736
448
  function generalName(ns, opts) {
@@ -749,7 +461,6 @@ function generalName(ns, opts) {
749
461
  if (GN_CONSTRUCTED[t]) {
750
462
  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)");
751
463
  if (t === 0) {
752
- // otherName ::= SEQUENCE { type-id OBJECT IDENTIFIER, value [0] EXPLICIT ANY }.
753
464
  if (n.children.length !== 2) throw ctx.E(code, "GeneralName otherName [0] must be a SEQUENCE { type-id, value [0] }");
754
465
  var typeId;
755
466
  try { typeId = asn1.read.oid(n.children[0]); }
@@ -760,7 +471,6 @@ function generalName(ns, opts) {
760
471
  }
761
472
  if (decodeValue) value = { typeId: typeId, valueBytes: ov.children[0].bytes };
762
473
  } else if (t === 4) {
763
- // directoryName [4] EXPLICIT Name: validate the wrapped RDNSequence.
764
474
  if (n.children.length !== 1) throw ctx.E(code, "GeneralName directoryName [4] must wrap exactly one Name");
765
475
  var dnMatch = schema.walk(NAME, n.children[0], ctx);
766
476
  if (decodeValue) value = dnMatch.result;
@@ -769,8 +479,6 @@ function generalName(ns, opts) {
769
479
  if (constructed) throw ctx.E(code, "GeneralName [" + t + "] must be primitive (X.690 sec. 10.2)");
770
480
  if (GN_IA5[t]) {
771
481
  if (n.content.length === 0) throw ctx.E(code, "GeneralName [" + t + "] must be a non-empty IA5String");
772
- // 7-bit IA5, no C0/DEL control byte. An embedded NUL/control in a
773
- // dNSName/rfc822Name/URI enables a name-truncation bypass (CVE-2009-2408).
774
482
  guard.name.assertPrintableIa5(n.content, ctx.E, code, "GeneralName [" + t + "]");
775
483
  if (decodeValue) value = _bufToString(n.content, "latin1");
776
484
  } else if (t === 7) {
@@ -779,8 +487,6 @@ function generalName(ns, opts) {
779
487
  } else if (n.content.length !== 4 && n.content.length !== 16) {
780
488
  throw ctx.E(code, "GeneralName iPAddress [7] must be a 4- or 16-octet address");
781
489
  }
782
- // n.content is proven non-null here (the primitive-form guard above
783
- // threw on a constructed node); copy the octets out.
784
490
  if (decodeValue) value = _bufferConcat([n.content]);
785
491
  } else if (t === 8) {
786
492
  var regId;
@@ -795,26 +501,10 @@ function generalName(ns, opts) {
795
501
  });
796
502
  }
797
503
 
798
- // GeneralNames ::= SEQUENCE OF GeneralName (RFC 5280 sec. 4.2.1.6), SIZE (1..MAX). Every
799
- // element is validated by generalName (its CHOICE alternative's form + content) and
800
- // surfaced raw, so a caller carrying a GeneralNames field cannot accept a malformed
801
- // element by treating the whole sequence as opaque bytes. Returns { names, bytes }
802
- // where each `names[i]` is the generalName leaf's return: { bytes, tagClass,
803
- // tagNumber } plus, when opts.decodeValue is set, the decoded `value`. `bytes`
804
- // is the raw outer DER.
805
- // `opts.implicitTag` handles a [tag] IMPLICIT GeneralNames, where the context tag REPLACES
806
- // the universal SEQUENCE tag (RFC 5755 Holder.entityName [1]); otherwise it is a bare
807
- // universal SEQUENCE OF. `opts.code` is the caller's error code. Shared so the x509 /
808
- // attribute-certificate / (future) CRMF parsers validate a GeneralNames identically.
809
504
  function generalNames(ns, opts) {
810
505
  opts = opts || {};
811
506
  var code = opts.code || (ns.prefix + "/bad-general-names");
812
- // decodeValue / subtreeBase thread through to each element (the path
813
- // validator's constraint matcher passes decodeValue:true).
814
507
  var gn = generalName(ns, { code: code, decodeValue: opts.decodeValue === true, subtreeBase: opts.subtreeBase === true });
815
- // Through the captured traversal: the names this yields are what a consumer compares an identity
816
- // against -- a certificate's subject alternative names against an enrollment's own -- and a
817
- // replaced Array.prototype.map could present a name the encoded GeneralNames does not carry.
818
508
  function build(m) { return { names: _map(m.items, function (it) { return it.value; }), bytes: m.node.bytes }; }
819
509
  if (opts.implicitTag != null) {
820
510
  return schema.implicitSeqOf(opts.implicitTag, gn, { min: 1, code: code, what: opts.what || "GeneralNames", build: build });
@@ -822,29 +512,12 @@ function generalNames(ns, opts) {
822
512
  return schema.seqOf(gn, { assert: "sequence", min: 1, code: code, what: opts.what || "GeneralNames", build: build });
823
513
  }
824
514
 
825
- // DistributionPointName ::= CHOICE { fullName [0] IMPLICIT GeneralNames,
826
- // nameRelativeToCRLIssuer [1] IMPLICIT RelativeDistinguishedName } (RFC 5280
827
- // sec. 4.2.1.13). The caller hands the CHOICE node itself: the alternative
828
- // inside the [0]-tagged distributionPoint field wrapper (a context tag on a
829
- // CHOICE-typed field is always EXPLICIT, so the wire nests [0]{ [0]|[1] ... }).
830
- // fullName surfaces each element's raw GeneralName DER as { kind: "fullName",
831
- // names: [Buffer, ...] }, the byte-exact comparison key the sec. 5.2.5
832
- // "identical encoding MUST be used" correspondence rule requires;
833
- // nameRelativeToCRLIssuer surfaces its full [1]-tagged TLV as { kind: "rdn",
834
- // bytes: Buffer } (byte-identical [1] TLVs are byte-identical RDN fragments).
835
- // An empty fullName (GeneralNames is SIZE 1..MAX), a malformed element, or any
836
- // other alternative throws the caller's `code`. Shared by the
837
- // cRLDistributionPoints / freshestCRL extension decoders and the CRL checker's
838
- // IssuingDistributionPoint decode, so the two sides of the sec. 6.3.3(b)(2)(i)
839
- // correspondence comparison cannot drift.
840
515
  function distributionPointName(ns, node, code) {
841
516
  if (node && node.tagClass === "context" && node.tagNumber === 0) {
842
517
  var gns = schema.walk(generalNames(ns, { implicitTag: 0, code: code, what: "DistributionPointName fullName" }), node, ns).result;
843
518
  return { kind: "fullName", names: _map(gns.names, function (n) { return n.bytes; }) };
844
519
  }
845
520
  if (node && node.tagClass === "context" && node.tagNumber === 1) {
846
- // In [1] IMPLICIT RelativeDistinguishedName the context tag replaces the
847
- // SET tag, so the node's direct children are the AttributeTypeAndValues.
848
521
  schema.walk(schema.implicitSetOf(1, attributeTypeAndValue(ns), {
849
522
  min: 1, code: code, what: "DistributionPointName nameRelativeToCRLIssuer" }), node, ns);
850
523
  return { kind: "rdn", bytes: node.bytes };
@@ -852,20 +525,7 @@ function distributionPointName(ns, node, code) {
852
525
  throw ns.E(code, "DistributionPointName must be fullName [0] or nameRelativeToCRLIssuer [1] (RFC 5280 sec. 4.2.1.13)");
853
526
  }
854
527
 
855
- // A certificate's keyUsage as the named booleans the shared sec. 4.2.1 decoder produces, or null
856
- // when the certificate carries no keyUsage extension. An absent extension places no restriction
857
- // (sec. 4.2.1.3), a distinct answer from "carries one that permits nothing".
858
- //
859
- // Declared here because "may this certificate do X" is asked at five boundaries and the answer has
860
- // to be the same at all of them. keyUsage is a NamedBitList: DER drops its trailing zero bits
861
- // (X.690 sec. 11.2.2) and sec. 4.2.1.3 requires at least one bit set. A boundary that reads the
862
- // bits with a plain BIT STRING read applies neither, so the same certificate is authorized there
863
- // and rejected as malformed by the issuing side and the path validator. One extension gets two
864
- // readings, and the permissive one decides.
865
- //
866
- // `E(code, message, cause)` is the caller's typed error factory, so each boundary keeps its own
867
- // domain while the RULE stays single-homed.
868
- var _KU_DECODER = new _WeakMapI(); // one decoder table per namespace, built on first use
528
+ var _KU_DECODER = new _WeakMapI();
869
529
  function keyUsageOf(ns, cert, E, code, label) {
870
530
  var exts = (cert && cert.extensions) || [];
871
531
  var want = ns.oid.byName("keyUsage");
@@ -879,26 +539,6 @@ function keyUsageOf(ns, cert, E, code, label) {
879
539
  return null;
880
540
  }
881
541
 
882
- // IssuingDistributionPoint ::= SEQUENCE { distributionPoint [0] OPTIONAL,
883
- // onlyContainsUserCerts [1] DEFAULT FALSE, onlyContainsCACerts [2] DEFAULT FALSE,
884
- // onlySomeReasons [3] ReasonFlags OPTIONAL, indirectCRL [4] DEFAULT FALSE,
885
- // onlyContainsAttributeCerts [5] DEFAULT FALSE } (RFC 5280 sec. 5.2.5).
886
- //
887
- // It is declared here and not in a consumer because the extension answers the
888
- // question "which certificates does this CRL speak for", which both the path
889
- // validator and the standalone CRL verbs act on, and a scope flag read by a hand
890
- // walk over the raw children is read by the reader's own rules instead of the
891
- // encoding's. Each flag is an IMPLICIT BOOLEAN, so `implicitBoolean` is what
892
- // holds it to one content octet of 0x00 or 0xFF (X.690 sec. 11.1); a hand walk
893
- // testing a content byte reads an EMPTY [4] as absent and a multi-octet one by
894
- // whichever byte it happens to index, and "absent" is the reading that grants
895
- // permission to answer. `trailing` supplies the rest of the grammar: strictly
896
- // ascending tags, each field at most once, nothing outside 0..5.
897
- //
898
- // A present DEFAULT-FALSE flag encoding FALSE is well-formed at the leaf but is
899
- // still an encoding DER forbids (X.690 sec. 11.5 omits the default), so the
900
- // caller decides what that means for it. It is reported as a value of `false`
901
- // against a `present` of true, never silently normalized away.
902
542
  function issuingDistributionPoint(code) {
903
543
  return schema.seq([
904
544
  schema.trailing([
@@ -912,28 +552,8 @@ function issuingDistributionPoint(code) {
912
552
  ], { assert: "sequence", code: code, what: "IssuingDistributionPoint" });
913
553
  }
914
554
 
915
- // certExtensionDecoders(ns) -- the ns-parameterized RFC 5280 sec. 4.2.1 extension
916
- // VALUE decoders. `x509.parse` surfaces each extension as { oid, name, critical,
917
- // value } with `value` the raw inner OCTET-STRING content (a Buffer); the path
918
- // validator and the future complete-extension-set surface need the STRUCTURE
919
- // inside. Each decoder takes that raw Buffer and returns the decoded value, or
920
- // throws a typed `<prefix>/bad-*` code, mirroring the CRL's fail-closed
921
- // `decodeExt` pattern (asn1.decode -> read.* / schema.walk, wrapped). Returns
922
- // { byOid } keyed by dotted OID so a consumer dispatches by ext.oid. The decoded
923
- // set spans the RFC 5280 sec. 4.2.1 extensions plus the RFC 3739 sec. 3.2.6 /
924
- // ETSI EN 319 412-5 qualified-certificate `qcStatements` extension (each known
925
- // statement decoded, an unknown statementId preserved opaque).
926
555
  var _T = asn1.TAGS;
927
556
 
928
- // assertPolicyQualifiers(qNode, fail) -- RFC 5280 sec. 4.2.1.4: a PolicyInformation's
929
- // policyQualifiers is a non-empty SEQUENCE OF PolicyQualifierInfo, each a SEQUENCE
930
- // { policyQualifierId OID, qualifier } of exactly two members led by an OID. The
931
- // structure is validated fail-closed; the qualifier body stays opaque (surfaced
932
- // raw by the caller). `fail(msg)` throws the caller's typed code. Shared by the
933
- // certificatePolicies extension decoder and the RFC 5035 ESS SigningCertificate
934
- // policies field so the same PolicyInformation shape cannot drift between them.
935
- // `fail(msg, cause?)` throws the caller's typed code, carrying the underlying
936
- // leaf fault as the error cause when one is available.
937
557
  function assertPolicyQualifiers(qNode, fail) {
938
558
  if (qNode.tagClass !== "universal" || qNode.tagNumber !== _T.SEQUENCE || !qNode.children || !qNode.children.length) {
939
559
  fail("policyQualifiers must be a non-empty SEQUENCE (RFC 5280 sec. 4.2.1.4)");
@@ -946,68 +566,25 @@ function assertPolicyQualifiers(qNode, fail) {
946
566
  });
947
567
  }
948
568
 
949
- // DisplayText ::= CHOICE { ia5String, visibleString, bmpString, utf8String } each SIZE (1..200)
950
- // -- RFC 5280 sec. 4.2.1.4. The bound is deliberately not enforced here. That is a normative
951
- // requirement and not a gap: the same section closes with "While the explicitText has a maximum
952
- // size of 200 characters, some non-conforming CAs exceed this limit. Therefore, certificate users
953
- // SHOULD gracefully handle explicitText with more than 200 characters." A decoder IS the certificate
954
- // user that note addresses, so refusing an over-long notice would violate a SHOULD and reject
955
- // certificates that are in the wild and otherwise valid. The bound belongs to the two layers that
956
- // can act on it: an issuer, which must not MINT one, and pki.lint, which reports it as an advisory.
957
- //
958
- // Measured in CHARACTERS, not octets: a conforming 200-character UTF8String notice can occupy 800
959
- // octets, so `.length` (UTF-16 units) or a byte count would misjudge a valid value. Exported so
960
- // every layer that reports the bound counts it identically instead of keeping its own copy.
961
569
  var DISPLAY_TEXT_MAX = 200;
962
570
  function displayTextChars(str) { return _arrayFrom(str).length; }
963
571
 
964
572
  // @internal
965
- // userNoticeTexts(qualifier) -- the DisplayText values of a decoded UserNotice qualifier, as
966
- // [{ field, tagNumber, text, chars }] in encounter order. `field` is "explicitText" or
967
- // "organization" (the NoticeReference member), so a caller that must treat the two alike (the
968
- // SIZE bound applies to both) and one that must not (only explicitText carries the encoding and
969
- // normalization rules) can each select correctly.
970
- //
971
- // UserNotice ::= SEQUENCE { noticeRef NoticeReference OPTIONAL, explicitText DisplayText OPTIONAL }
972
- // NoticeReference ::= SEQUENCE { organization DisplayText, noticeNumbers SEQUENCE OF INTEGER }
973
- // Both members are OPTIONAL and distinguished by tag, so a leading SEQUENCE is the noticeRef and any
974
- // DisplayText member is the explicitText. Returns [] for a qualifier that is not a UserNotice --
975
- // this reads an already-decoded node for display and reporting, and is never a validity verdict.
976
573
  var _DISPLAY_TEXT_TAGS = null;
977
574
  function _isDisplayTextNode(n) {
978
575
  if (!_DISPLAY_TEXT_TAGS) {
979
- // Indexed by a tag number the encoded value supplies, so it holds no prototype: an inherited
980
- // numeric name would make an unregistered universal tag read as a DisplayText alternative.
981
576
  _DISPLAY_TEXT_TAGS = _create(null);
982
577
  _DISPLAY_TEXT_TAGS[_T.IA5_STRING] = 1; _DISPLAY_TEXT_TAGS[_T.VISIBLE_STRING] = 1;
983
578
  _DISPLAY_TEXT_TAGS[_T.BMP_STRING] = 1; _DISPLAY_TEXT_TAGS[_T.UTF8_STRING] = 1;
984
579
  }
985
580
  return !!n && n.tagClass === "universal" && _DISPLAY_TEXT_TAGS[n.tagNumber] === 1;
986
581
  }
987
- // Decoded through the STRICT reader, which validates each arm against its own declared string type
988
- // (invalid UTF-8, a high bit in an IA5String, an odd-length or lone-surrogate BMPString all throw).
989
- // Reading the content bytes directly would repair the value instead: `toString("utf8")` substitutes
990
- // U+FFFD for invalid sequences and a hand-rolled UCS-2 loop drops a trailing odd octet, so a caller
991
- // would render or measure text the certificate does not contain. Returns null when the value does
992
- // not decode, leaving the fallback to the caller.
993
- // An undecodable value yields an entry whose `text` and `chars` are null but whose `field` and
994
- // `tagNumber` are still present. The two are separable facts: which ASN.1 string type was used is
995
- // established by the tag alone, so a rule about the ENCODING stays answerable even when the contents
996
- // do not decode, while every rule that reads the TEXT must skip the entry and not analyze a
997
- // repair. Collapsing both into "not analyzable" would let a prohibited encoding escape its finding
998
- // by also being malformed inside.
999
582
  function _dtEntry(field, node) {
1000
583
  var text;
1001
584
  // allow:swallow-unverified an undecodable DisplayText keeps its tag and drops its text; the callers branch on text === null
1002
585
  try { text = asn1.read.string(node); } catch (_e) { text = null; }
1003
586
  return { field: field, tagNumber: node.tagNumber, text: text, chars: text === null ? null : displayTextChars(text) };
1004
587
  }
1005
- // A NoticeReference names its notice by ORGANIZATION **and** NUMBER. The organization alone does not
1006
- // identify which notice is meant, so a consumer that surfaced only the text would lose the lookup key.
1007
- // Returns null when the numbers are absent, wrongly shaped, or contain a member that is not a readable
1008
- // INTEGER: an incompletely decoded reference must not be presentable, because dropping the members that
1009
- // failed would render a partial reference indistinguishable from a whole one. That is the same defect as
1010
- // omitting the numbers entirely, in a form that is harder to notice. Numbers are decimal strings.
1011
588
  function _noticeNumbers(node) {
1012
589
  if (!node || node.tagClass !== "universal" || node.tagNumber !== _T.SEQUENCE || !node.children) return null;
1013
590
  var nums = [], ok = true;
@@ -1017,22 +594,11 @@ function _noticeNumbers(node) {
1017
594
  });
1018
595
  return ok ? nums : null;
1019
596
  }
1020
- // Every DisplayText member is returned, including one whose contents did not decode; that entry
1021
- // carries its tag with a null `text`. A caller that RENDERS the notice must check that every entry
1022
- // decoded before showing any of it (a partial notice is indistinguishable from a complete one); a
1023
- // caller that only classifies the encoding can read the tags regardless.
1024
- // The LAYOUT is validated before any member is read: UserNotice fixes the order (noticeRef first) and
1025
- // the cardinality (each member at most once, nothing else present). Collecting every recognized member
1026
- // and ignoring the rest would accept a duplicated explicitText, a reversed order, or an extra member.
1027
- // The caller can only see whether the entries it got look complete, so it would then present
1028
- // a structurally invalid notice as a whole one. A malformed layout yields NO entries, so a renderer
1029
- // falls back to hex and a reporter measures nothing.
1030
597
  function userNoticeTexts(qualifier) {
1031
598
  if (!qualifier || qualifier.tagClass !== "universal" || qualifier.tagNumber !== _T.SEQUENCE || !qualifier.children) return [];
1032
599
  var kids = qualifier.children;
1033
600
  if (kids.length > 2) return [];
1034
601
  var i = 0, out = [];
1035
- // noticeRef [absent | first]: a SEQUENCE of { organization DisplayText, noticeNumbers }.
1036
602
  if (i < kids.length && kids[i].tagClass === "universal" && kids[i].tagNumber === _T.SEQUENCE) {
1037
603
  var nr = kids[i];
1038
604
  if (!nr.children || nr.children.length !== 2 || !_isDisplayTextNode(nr.children[0])) return [];
@@ -1041,7 +607,6 @@ function userNoticeTexts(qualifier) {
1041
607
  _push(out, org);
1042
608
  i++;
1043
609
  }
1044
- // explicitText [absent | last]: a DisplayText. Anything else remaining is not a UserNotice member.
1045
610
  if (i < kids.length) {
1046
611
  if (!_isDisplayTextNode(kids[i])) return [];
1047
612
  _push(out, _dtEntry("explicitText", kids[i]));
@@ -1050,9 +615,6 @@ function userNoticeTexts(qualifier) {
1050
615
  return i === kids.length ? out : [];
1051
616
  }
1052
617
 
1053
- // Shared imperative decode helpers for the certExtensionDecoders + attrValueDecoders factories. Both
1054
- // take the caller's ns, so the helpers close over ns.E / ns.oid; extracted once so the two factories
1055
- // share one copy (a per-OID value-decoder body is the identical idiom to a per-OID extension body).
1056
618
  function _decodeHelpers(ns) {
1057
619
  function decodeTop(buf, code, what) {
1058
620
  var n;
@@ -1071,8 +633,6 @@ function _decodeHelpers(ns) {
1071
633
  try { return asn1.read.integer(node); }
1072
634
  catch (e) { throw ns.E(code, what + " must be an INTEGER", e); }
1073
635
  }
1074
- // Resolve a registered OID name -> dotted string, throwing at module load on a typo (a decoder keyed
1075
- // by an unresolved name would silently never match, treating a real extension/attribute as opaque).
1076
636
  function O(nm) {
1077
637
  var d = ns.oid.byName(nm);
1078
638
  if (typeof d !== "string") throw new TypeError("pki.schema.pkix: " + _stringify(nm) + " is not a registered OID name");
@@ -1085,7 +645,6 @@ function certExtensionDecoders(ns) {
1085
645
  var GN_SUBTREE = generalName(ns, { decodeValue: true, subtreeBase: true, code: ns.prefix + "/bad-name-constraints" });
1086
646
  var _h = _decodeHelpers(ns), decodeTop = _h.decodeTop, seqChildren = _h.seqChildren, readInt = _h.readInt, O = _h.O;
1087
647
 
1088
- // basicConstraints ::= SEQUENCE { cA BOOLEAN DEFAULT FALSE, pathLen INTEGER (0..MAX) OPTIONAL }
1089
648
  function basicConstraints(buf) {
1090
649
  var C = ns.prefix + "/bad-basic-constraints";
1091
650
  var kids = seqChildren(buf, C, "BasicConstraints");
@@ -1099,16 +658,12 @@ function certExtensionDecoders(ns) {
1099
658
  if (kids[i] && kids[i].tagClass === "universal" && kids[i].tagNumber === _T.INTEGER) {
1100
659
  if (!cA) throw ns.E(C, "BasicConstraints pathLenConstraint is only permitted when cA is TRUE (RFC 5280 sec. 4.2.1.9)");
1101
660
  var pl = readInt(kids[i], C, "pathLenConstraint");
1102
- // guard.range.uint31 bounds the decoded BigInt and narrows atomically. A
1103
- // value past the safe-integer range would otherwise round silently and be
1104
- // compared as a path-length ceiling.
1105
661
  pathLen = guard.range.uint31(pl, ns.E, C, "BasicConstraints pathLenConstraint (RFC 5280 sec. 4.2.1.9)"); i++;
1106
662
  }
1107
663
  if (i !== kids.length) throw ns.E(C, "BasicConstraints has unexpected trailing fields");
1108
664
  return { cA: cA, pathLenConstraint: pathLen };
1109
665
  }
1110
666
 
1111
- // keyUsage ::= BIT STRING (named bits). At least one bit MUST be set.
1112
667
  var KU_BITS = ["digitalSignature", "nonRepudiation", "keyEncipherment", "dataEncipherment",
1113
668
  "keyAgreement", "keyCertSign", "cRLSign", "encipherOnly", "decipherOnly"];
1114
669
  function keyUsage(buf) {
@@ -1117,21 +672,15 @@ function certExtensionDecoders(ns) {
1117
672
  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)");
1118
673
  var bs;
1119
674
  try { bs = asn1.read.bitString(n); } catch (e) { throw ns.E(C, "KeyUsage must be a well-formed BIT STRING", e); }
1120
- // At least one bit MUST be set (RFC 5280 sec. 4.2.1.3), so an all-zero value (a
1121
- // non-empty byte run of only zero bits, e.g. 03 02 07 00) is malformed too.
1122
675
  var anyBit = false;
1123
676
  for (var z = 0; z < bs.bytes.length; z++) { if (bs.bytes[z] !== 0) { anyBit = true; break; } }
1124
677
  if (!anyBit) throw ns.E(C, "KeyUsage must assert at least one bit (RFC 5280 sec. 4.2.1.3)");
1125
- // KeyUsage is a NamedBitList -- enforce the shared X.690 sec. 11.2.2 minimal-DER rule.
1126
678
  schema.assertMinimalNamedBits(bs.unusedBits, bs.bytes, function (m) { throw ns.E(C, m); });
1127
679
  var out = {};
1128
680
  _forEach(KU_BITS, function (nm, bit) {
1129
681
  var byte = bit >> 3, mask = 0x80 >> (bit & 7);
1130
682
  out[nm] = byte < bs.bytes.length && (bs.bytes[byte] & mask) !== 0;
1131
683
  });
1132
- // Any bit beyond the 9 named positions is a reserved/unknown usage. RFC 5280 tolerates it,
1133
- // but a "MUST be the ONLY bit" rule (e.g. RFC 9935 sec. 5 ML-KEM keyEncipherment-only) needs
1134
- // to see it, so bits >= 9 are surfaced on `reservedBitsSet`.
1135
684
  var reserved = false;
1136
685
  for (var bit2 = KU_BITS.length; bit2 < bs.bytes.length * 8 && !reserved; bit2++) {
1137
686
  if ((bs.bytes[bit2 >> 3] & (0x80 >> (bit2 & 7))) !== 0) reserved = true;
@@ -1140,11 +689,7 @@ function certExtensionDecoders(ns) {
1140
689
  return out;
1141
690
  }
1142
691
 
1143
- // GeneralSubtree ::= SEQUENCE { base GeneralName, minimum [0] DEFAULT 0, maximum [1] OPTIONAL }
1144
692
  function subtreeList(node, C) {
1145
- // node is the [0]/[1] IMPLICIT SEQUENCE OF GeneralSubtree (context-constructed).
1146
- // GeneralSubtrees is SIZE(1..MAX) -- an explicitly present but empty subtree
1147
- // list (a0 00) absorbs no constraint and is malformed, not a no-op.
1148
693
  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)");
1149
694
  return _map(node.children, function (st) {
1150
695
  if (st.tagClass !== "universal" || st.tagNumber !== _T.SEQUENCE || !st.children || st.children.length < 1) {
@@ -1176,18 +721,12 @@ function certExtensionDecoders(ns) {
1176
721
  return { permittedSubtrees: permitted, excludedSubtrees: excluded };
1177
722
  }
1178
723
 
1179
- // certificatePolicies ::= SEQUENCE SIZE(1..MAX) OF PolicyInformation
1180
724
  function certificatePolicies(buf) {
1181
725
  var C = ns.prefix + "/bad-policy";
1182
726
  var kids = seqChildren(buf, C, "CertificatePolicies");
1183
727
  if (kids.length < 1) throw ns.E(C, "CertificatePolicies must contain at least one PolicyInformation (RFC 5280 sec. 4.2.1.4)");
1184
- // The duplicate-policy set answers off a dotted OID the encoded value supplies, so it holds no
1185
- // prototype: an inherited name would report a policy as already seen and reject a valid one.
1186
728
  var seen = _create(null);
1187
729
  return _map(kids, function (pi) {
1188
- // PolicyInformation ::= SEQUENCE { policyIdentifier, policyQualifiers
1189
- // SEQUENCE SIZE(1..MAX) OPTIONAL }. Exactly one or two fields; a second
1190
- // field, if present, MUST be a SEQUENCE. Extra/mis-typed fields are malformed.
1191
730
  if (pi.tagClass !== "universal" || pi.tagNumber !== _T.SEQUENCE || !pi.children || pi.children.length < 1 || pi.children.length > 2) {
1192
731
  throw ns.E(C, "PolicyInformation must be a SEQUENCE { policyIdentifier, policyQualifiers? }");
1193
732
  }
@@ -1198,8 +737,6 @@ function certExtensionDecoders(ns) {
1198
737
  var qualifiers = null;
1199
738
  if (pi.children.length > 1) {
1200
739
  var q = pi.children[1];
1201
- // PolicyQualifierInfo structure validated by the shared assertion; the
1202
- // qualifier body stays opaque (surfaced raw).
1203
740
  assertPolicyQualifiers(q, function (msg, cause) { throw ns.E(C, msg, cause); });
1204
741
  qualifiers = q.bytes;
1205
742
  }
@@ -1207,7 +744,6 @@ function certExtensionDecoders(ns) {
1207
744
  });
1208
745
  }
1209
746
 
1210
- // policyMappings ::= SEQUENCE SIZE(1..MAX) OF SEQUENCE { issuerDomainPolicy, subjectDomainPolicy }
1211
747
  function policyMappings(buf) {
1212
748
  var C = ns.prefix + "/bad-policy";
1213
749
  var kids = seqChildren(buf, C, "PolicyMappings");
@@ -1223,7 +759,6 @@ function certExtensionDecoders(ns) {
1223
759
  });
1224
760
  }
1225
761
 
1226
- // policyConstraints ::= SEQUENCE { requireExplicitPolicy [0] INTEGER OPT, inhibitPolicyMapping [1] INTEGER OPT }
1227
762
  function policyConstraints(buf) {
1228
763
  var C = ns.prefix + "/bad-policy";
1229
764
  var kids = seqChildren(buf, C, "PolicyConstraints");
@@ -1235,8 +770,6 @@ function certExtensionDecoders(ns) {
1235
770
  pcLastTag = k.tagNumber;
1236
771
  var v;
1237
772
  try { v = asn1.read.integerImplicit(k, k.tagNumber); } catch (e) { throw ns.E(C, "PolicyConstraints field must be an INTEGER", e); }
1238
- // guard.range.uint31 bounds + narrows atomically (a skip count past the
1239
- // safe-integer range would round silently).
1240
773
  var skip = guard.range.uint31(v, ns.E, C, "PolicyConstraints skip count (RFC 5280 sec. 4.2.1.11)");
1241
774
  if (k.tagNumber === 0) rep = skip;
1242
775
  else if (k.tagNumber === 1) ipm = skip;
@@ -1245,31 +778,20 @@ function certExtensionDecoders(ns) {
1245
778
  return { requireExplicitPolicy: rep, inhibitPolicyMapping: ipm };
1246
779
  }
1247
780
 
1248
- // inhibitAnyPolicy ::= INTEGER (0..MAX)
1249
781
  function inhibitAnyPolicy(buf) {
1250
782
  var C = ns.prefix + "/bad-policy";
1251
783
  var n = decodeTop(buf, C, "InhibitAnyPolicy");
1252
784
  if (n.tagClass !== "universal" || n.tagNumber !== _T.INTEGER) throw ns.E(C, "InhibitAnyPolicy must be an INTEGER (RFC 5280 sec. 4.2.1.14)");
1253
785
  var v = readInt(n, C, "InhibitAnyPolicy");
1254
- // guard.range.uint31 bounds + narrows atomically (a skip count past the
1255
- // safe-integer range would round silently).
1256
786
  return guard.range.uint31(v, ns.E, C, "InhibitAnyPolicy skip count (RFC 5280 sec. 4.2.1.14)");
1257
787
  }
1258
788
 
1259
- // subjectAltName / issuerAltName ::= GeneralNames, with decoded values surfaced.
1260
789
  function altName(buf) {
1261
790
  var C = ns.prefix + "/bad-extension-value";
1262
791
  var n = decodeTop(buf, C, "GeneralNames");
1263
792
  return schema.walk(generalNames(ns, { decodeValue: true, code: C }), n, ns).result;
1264
793
  }
1265
794
 
1266
- // authorityInfoAccess ::= AuthorityInfoAccessSyntax ::= SEQUENCE SIZE(1..MAX) OF AccessDescription
1267
- // (RFC 5280 sec. 4.2.2.1); AccessDescription ::= SEQUENCE { accessMethod OBJECT IDENTIFIER, accessLocation
1268
- // GeneralName }. Surfaces [{ accessMethod: <dotted OID>, accessLocation: { tag, value } }] in wire order --
1269
- // accessLocation is the shared generalName leaf (its context tag number + decoded value, so a control-byte
1270
- // URI is rejected by the CVE-2009-2408 guard). Both accessMethods surface (id-ad-caIssuers AND id-ad-ocsp):
1271
- // a consumer filters by accessMethod (caIssuers for issuer fetching, ocsp for responder discovery). An empty
1272
- // SEQUENCE violates SIZE(1..MAX) and is malformed. A composed decoder + registry row, not a hand-roll.
1273
795
  function authorityInfoAccess(buf) {
1274
796
  var C = ns.prefix + "/bad-extension-value";
1275
797
  var descs = seqChildren(buf, C, "AuthorityInfoAccessSyntax");
@@ -1282,12 +804,11 @@ function certExtensionDecoders(ns) {
1282
804
  var method;
1283
805
  try { method = asn1.read.oid(d.children[0]); }
1284
806
  catch (e) { throw ns.E(C, "AccessDescription accessMethod must be an OBJECT IDENTIFIER", e); }
1285
- var loc = schema.walk(GN, d.children[1], ns); // a decode leaf returns its value directly (no .result wrapper)
807
+ var loc = schema.walk(GN, d.children[1], ns);
1286
808
  return { accessMethod: method, accessLocation: { tag: loc.tagNumber, value: loc.value } };
1287
809
  });
1288
810
  }
1289
811
 
1290
- // extKeyUsage ::= SEQUENCE SIZE(1..MAX) OF KeyPurposeId (OID)
1291
812
  function extKeyUsage(buf) {
1292
813
  var C = ns.prefix + "/bad-extension-value";
1293
814
  var kids = seqChildren(buf, C, "ExtKeyUsage");
@@ -1297,7 +818,6 @@ function certExtensionDecoders(ns) {
1297
818
  });
1298
819
  }
1299
820
 
1300
- // authorityKeyIdentifier ::= SEQUENCE { keyIdentifier [0] OPT, authorityCertIssuer [1] OPT, authorityCertSerialNumber [2] OPT }
1301
821
  function authorityKeyIdentifier(buf) {
1302
822
  var C = ns.prefix + "/bad-extension-value";
1303
823
  var kids = seqChildren(buf, C, "AuthorityKeyIdentifier");
@@ -1305,13 +825,8 @@ function certExtensionDecoders(ns) {
1305
825
  var lastTag = -1;
1306
826
  _forEach(kids, function (k) {
1307
827
  if (k.tagClass !== "context") throw ns.E(C, "AuthorityKeyIdentifier fields are context-tagged");
1308
- // DER: the OPTIONAL fields appear at most once and in ascending tag order.
1309
828
  if (k.tagNumber <= lastTag) throw ns.E(C, "AuthorityKeyIdentifier fields must be unique and in ascending order (DER)");
1310
829
  lastTag = k.tagNumber;
1311
- // keyIdentifier [0] IMPLICIT OCTET STRING and authorityCertSerialNumber
1312
- // [2] IMPLICIT INTEGER are primitive leaves; the readers enforce the
1313
- // primitive form fail-closed (a constructed context node is rejected,
1314
- // never dereferenced for its absent content).
1315
830
  if (k.tagNumber === 0) { try { out.keyIdentifier = _bufferFrom(asn1.read.octetStringImplicit(k, 0)); } catch (e) { throw ns.E(C, "AuthorityKeyIdentifier keyIdentifier [0] must be an IMPLICIT OCTET STRING", e); } }
1316
831
  else if (k.tagNumber === 1) out.authorityCertIssuer = schema.walk(generalNames(ns, { implicitTag: 1, decodeValue: true, code: C }), k, ns).result;
1317
832
  else if (k.tagNumber === 2) { try { out.authorityCertSerialNumber = asn1.read.integerImplicit(k, 2); } catch (e) { throw ns.E(C, "authorityCertSerialNumber must be an INTEGER", e); } }
@@ -1323,7 +838,6 @@ function certExtensionDecoders(ns) {
1323
838
  return out;
1324
839
  }
1325
840
 
1326
- // subjectKeyIdentifier ::= OCTET STRING
1327
841
  function subjectKeyIdentifier(buf) {
1328
842
  var C = ns.prefix + "/bad-extension-value";
1329
843
  var n = decodeTop(buf, C, "SubjectKeyIdentifier");
@@ -1331,21 +845,12 @@ function certExtensionDecoders(ns) {
1331
845
  catch (e) { throw ns.E(C, "SubjectKeyIdentifier must be an OCTET STRING (RFC 5280 sec. 4.2.1.2)", e); }
1332
846
  }
1333
847
 
1334
- // signedCertificateTimestampList (RFC 6962 sec. 3.3) -- the extension value is a DER
1335
- // OCTET STRING wrapping the TLS-encoded SCT list. Delegated to the CT module,
1336
- // which owns the inner peel + the bounded TLS decode; structure is decoded, the
1337
- // signature stays raw and unverified. The registry contract is that a decoder
1338
- // throws a `<prefix>/bad-*` code, so the CT module's own `ct/*` fault is wrapped
1339
- // and carried as `.cause`, so a foreign namespace never leaks out here.
1340
848
  function sctList(buf) {
1341
849
  var C = ns.prefix + "/bad-extension-value";
1342
850
  try { return ct.parseSctList(buf); }
1343
851
  catch (e) { throw ns.E(C, "malformed signedCertificateTimestampList extension value (RFC 6962 sec. 3.3)", e); }
1344
852
  }
1345
853
 
1346
- // precertificatePoison (RFC 6962 sec. 3.1) -- a critical extension whose value is
1347
- // exactly ASN.1 NULL (05 00), the marker distinguishing a precertificate from
1348
- // a final certificate. Content-validated, not merely tag-checked.
1349
854
  function precertPoison(buf) {
1350
855
  var C = ns.prefix + "/bad-extension-value";
1351
856
  var n = decodeTop(buf, C, "PrecertificatePoison");
@@ -1354,18 +859,6 @@ function certExtensionDecoders(ns) {
1354
859
  return { poison: true };
1355
860
  }
1356
861
 
1357
- // cRLDistributionPoints ::= SEQUENCE SIZE (1..MAX) OF DistributionPoint
1358
- // (RFC 5280 sec. 4.2.1.13); DistributionPoint ::= SEQUENCE {
1359
- // distributionPoint [0] DistributionPointName OPTIONAL, reasons [1]
1360
- // ReasonFlags OPTIONAL, cRLIssuer [2] GeneralNames OPTIONAL }. "a
1361
- // DistributionPoint MUST NOT consist of only the reasons field; either
1362
- // distributionPoint or cRLIssuer MUST be present". A reasons-only (or
1363
- // empty) DistributionPoint is malformed. The DistributionPointName is
1364
- // surfaced through the shared helper (raw name encodings, the sec. 5.2.5
1365
- // correspondence key); `reasons` stays a raw BIT STRING ({ unusedBits,
1366
- // bytes }); cRLIssuer is a validated GeneralNames with decoded values.
1367
- // FreshestCRL ::= CRLDistributionPoints (sec. 4.2.1.15), so the same decoder
1368
- // serves both OIDs: one codec, both registry rows.
1369
862
  function crlDistributionPoints(buf) {
1370
863
  var C = ns.prefix + "/bad-crl-distribution-points";
1371
864
  var kids = seqChildren(buf, C, "CRLDistributionPoints");
@@ -1381,18 +874,12 @@ function certExtensionDecoders(ns) {
1381
874
  if (f.tagNumber <= lastTag) throw ns.E(C, "DistributionPoint fields must be unique and in ascending order (DER)");
1382
875
  lastTag = f.tagNumber;
1383
876
  if (f.tagNumber === 0) {
1384
- // distributionPoint [0] EXPLICIT-wraps the DistributionPointName CHOICE.
1385
877
  if (!f.children || f.children.length !== 1) throw ns.E(C, "DistributionPoint distributionPoint [0] must wrap exactly one DistributionPointName");
1386
878
  out.distributionPoint = distributionPointName(ns, f.children[0], C);
1387
879
  } else if (f.tagNumber === 1) {
1388
880
  var bs;
1389
881
  try { bs = asn1.read.bitStringImplicit(f, 1); }
1390
882
  catch (e) { throw ns.E(C, "DistributionPoint reasons [1] must be an IMPLICIT ReasonFlags BIT STRING", e); }
1391
- // ReasonFlags is a NamedBitList, so DER requires trailing zero bits be
1392
- // dropped (X.690 sec. 11.2.2), the same rule the keyUsage decoder
1393
- // enforces above. Without it two different encodings of one reason set
1394
- // both parse, and RFC 5280 sec. 6.3.3(d)(1) would intersect the CRL's
1395
- // onlySomeReasons against a value the encoding rules forbid.
1396
883
  schema.assertMinimalNamedBits(bs.unusedBits, bs.bytes, function (msg) { throw ns.E(C, "DistributionPoint reasons: " + msg); });
1397
884
  out.reasons = { unusedBits: bs.unusedBits, bytes: bs.bytes };
1398
885
  } else if (f.tagNumber === 2) {
@@ -1408,15 +895,11 @@ function certExtensionDecoders(ns) {
1408
895
  });
1409
896
  }
1410
897
 
1411
- // qcStatements ::= SEQUENCE OF QCStatement (RFC 3739 sec. 3.2.6). QCStatement ::= SEQUENCE {
1412
- // statementId OBJECT IDENTIFIER, statementInfo ANY DEFINED BY statementId OPTIONAL }. Known statementIds
1413
- // (RFC 3739 id-qcs + the ETSI EN 319 412-5 esi4 catalogue) decode their statementInfo; an unknown statementId
1414
- // is preserved OPAQUE (the RFC 3739 open type), never rejected. The DEFINED-BY selection is an OID re-dispatch.
1415
898
  function _qcStr(node, tag, C, what) {
1416
899
  if (!node || node.tagClass !== "universal" || node.tagNumber !== tag) throw ns.E(C, what);
1417
900
  try { return asn1.read.string(node); } catch (e) { throw ns.E(C, what, e); }
1418
901
  }
1419
- function _qcOidSeq(node, C, what) { // a non-empty SEQUENCE OF OBJECT IDENTIFIER
902
+ function _qcOidSeq(node, C, what) {
1420
903
  if (!node || node.tagClass !== "universal" || node.tagNumber !== _T.SEQUENCE || !node.children || !node.children.length) throw ns.E(C, what + " must be a non-empty SEQUENCE OF OBJECT IDENTIFIER");
1421
904
  return _map(node.children, function (c) {
1422
905
  if (c.tagClass !== "universal" || c.tagNumber !== _T.OBJECT_IDENTIFIER) throw ns.E(C, what + " members must be OBJECT IDENTIFIERs");
@@ -1424,8 +907,8 @@ function certExtensionDecoders(ns) {
1424
907
  });
1425
908
  }
1426
909
  function _qcPresenceOnly(nm) { return function (node, C) { if (node) throw ns.E(C, nm + " carries no statementInfo (ETSI EN 319 412-5)"); return null; }; }
1427
- function _qcSemantics(node, C) { // SemanticsInformation, WITH COMPONENTS at least one field present
1428
- if (node === null) return null; // statementInfo OPTIONAL for id-qcs syntax statements
910
+ function _qcSemantics(node, C) {
911
+ if (node === null) return null;
1429
912
  if (node.tagClass !== "universal" || node.tagNumber !== _T.SEQUENCE || !node.children || !node.children.length) throw ns.E(C, "SemanticsInformation must be a SEQUENCE with at least one field (RFC 3739 sec. 3.2.6.1)");
1430
913
  var sid = null, i = 0;
1431
914
  if (node.children[0].tagClass === "universal" && node.children[0].tagNumber === _T.OBJECT_IDENTIFIER) {
@@ -1434,36 +917,29 @@ function certExtensionDecoders(ns) {
1434
917
  }
1435
918
  var nras = [];
1436
919
  if (node.children[i]) {
1437
- // nameRegistrationAuthorities ::= SEQUENCE SIZE (1..MAX) OF GeneralName. Decode and fully validate
1438
- // each element through the shared GeneralName decoder (context tag [0]..[8] + per-alternative
1439
- // structure); arbitrary bytes never surface as a name-registration authority.
1440
920
  nras = schema.walk(generalNames(ns, { decodeValue: true, code: C }), node.children[i], ns).result.names;
1441
921
  i++;
1442
922
  }
1443
923
  if (i !== node.children.length) throw ns.E(C, "SemanticsInformation has unexpected trailing fields");
1444
924
  return { semanticsIdentifier: sid, nameRegistrationAuthorities: nras };
1445
925
  }
1446
- // The per-statement decoder table answers off a dotted OID the encoded value supplies, so it holds
1447
- // no prototype: an inherited name would hand a statement a decoder nothing registered.
1448
926
  var qcInfoByOid = _create(null);
1449
927
  qcInfoByOid[O("qcCompliance")] = _qcPresenceOnly("QcCompliance");
1450
928
  qcInfoByOid[O("qcSSCD")] = _qcPresenceOnly("QcSSCD");
1451
929
  qcInfoByOid[O("qcsPkixQCSyntaxV1")] = _qcSemantics;
1452
930
  qcInfoByOid[O("qcsPkixQCSyntaxV2")] = _qcSemantics;
1453
931
  qcInfoByOid[O("qcType")] = function (node, C) {
1454
- var oids = _qcOidSeq(node, C, "QcType"); // SIZE(1) in EN 319 412-5 V2.5.1; accept >=1 for legacy certs
932
+ var oids = _qcOidSeq(node, C, "QcType");
1455
933
  return { types: oids, typeNames: _map(oids, function (d) { return ns.oid.name(d) || null; }) };
1456
934
  };
1457
935
  qcInfoByOid[O("qcRetentionPeriod")] = function (node, C) {
1458
936
  if (!node) throw ns.E(C, "QcRetentionPeriod requires an INTEGER statementInfo");
1459
937
  return { years: guard.range.uint31(readInt(node, C, "QcRetentionPeriod"), ns.E, C, "QcRetentionPeriod (years)") };
1460
938
  };
1461
- qcInfoByOid[O("qcLimitValue")] = function (node, C) { // MonetaryValue ::= SEQUENCE { currency, amount, exponent }
939
+ qcInfoByOid[O("qcLimitValue")] = function (node, C) {
1462
940
  if (!node || node.tagClass !== "universal" || node.tagNumber !== _T.SEQUENCE || !node.children || node.children.length !== 3) throw ns.E(C, "QcLimitValue must be a MonetaryValue SEQUENCE { currency, amount, exponent }");
1463
941
  var cur = node.children[0], currency;
1464
942
  if (cur.tagClass === "universal" && cur.tagNumber === _T.PRINTABLE_STRING) {
1465
- // Route through _qcStr so a disallowed PrintableString byte surfaces the decoder's typed
1466
- // bad-qc-statement error, not a leaked asn1/bad-printable-string from the raw leaf read.
1467
943
  currency = _qcStr(cur, _T.PRINTABLE_STRING, C, "QcLimitValue alphabetic currency must be a PrintableString");
1468
944
  if (currency.length !== 3) throw ns.E(C, "QcLimitValue alphabetic currency must be a 3-letter ISO 4217 code");
1469
945
  } else if (cur.tagClass === "universal" && cur.tagNumber === _T.INTEGER) {
@@ -1471,19 +947,15 @@ function certExtensionDecoders(ns) {
1471
947
  } else throw ns.E(C, "QcLimitValue currency must be a PrintableString(3) or INTEGER(1..999)");
1472
948
  return {
1473
949
  currency: currency,
1474
- // MonetaryValue amount and exponent are unconstrained INTEGERs (the amount non-negative in practice);
1475
- // bound each only to the safe-integer range so it narrows to a Number losslessly. An artificial
1476
- // tighter bound (uint31, or a small exponent window) would false-reject a valid MonetaryValue, and a
1477
- // wider one would round silently and mis-state the limit.
1478
950
  amount: guard.range.int(readInt(node.children[1], C, "QcLimitValue amount"), 0n, 9007199254740991n, ns.E, C, "QcLimitValue amount"),
1479
951
  exponent: guard.range.int(readInt(node.children[2], C, "QcLimitValue exponent"), -9007199254740991n, 9007199254740991n, ns.E, C, "QcLimitValue exponent"),
1480
952
  };
1481
953
  };
1482
- qcInfoByOid[O("qcCClegislation")] = function (node, C) { // SEQUENCE OF CountryName (PrintableString SIZE 2)
954
+ qcInfoByOid[O("qcCClegislation")] = function (node, C) {
1483
955
  if (!node || node.tagClass !== "universal" || node.tagNumber !== _T.SEQUENCE || !node.children || !node.children.length) throw ns.E(C, "QcCClegislation must be a non-empty SEQUENCE OF CountryName");
1484
956
  return { countries: _map(node.children, function (c) { var v = _qcStr(c, _T.PRINTABLE_STRING, C, "QcCClegislation CountryName must be a PrintableString"); if (v.length !== 2) throw ns.E(C, "QcCClegislation CountryName must be a 2-letter ISO 3166-1 code"); return v; }) };
1485
957
  };
1486
- qcInfoByOid[O("qcPDS")] = function (node, C) { // PdsLocations ::= SEQUENCE OF PdsLocation { url IA5, language Printable(2) }
958
+ qcInfoByOid[O("qcPDS")] = function (node, C) {
1487
959
  if (!node || node.tagClass !== "universal" || node.tagNumber !== _T.SEQUENCE || !node.children || !node.children.length) throw ns.E(C, "QcPDS must be a non-empty SEQUENCE OF PdsLocation");
1488
960
  return { locations: _map(node.children, function (loc) {
1489
961
  if (loc.tagClass !== "universal" || loc.tagNumber !== _T.SEQUENCE || !loc.children || loc.children.length !== 2) throw ns.E(C, "a QcPDS PdsLocation must be a SEQUENCE { url, language }");
@@ -1492,8 +964,6 @@ function certExtensionDecoders(ns) {
1492
964
  return { url: _qcStr(loc.children[0], _T.IA5_STRING, C, "a QcPDS url must be an IA5String"), language: lang };
1493
965
  }) };
1494
966
  };
1495
- // ETSI EN 319 412-5 V2.5.1 additions: QcIdentMethod (SEQUENCE OF OID, like QcType) and
1496
- // QcQSCDlegislation (SEQUENCE OF CountryName, like QcCClegislation). Both are registered ids, decoded.
1497
967
  qcInfoByOid[O("qcIdentMethod")] = function (node, C) {
1498
968
  var oids = _qcOidSeq(node, C, "QcIdentMethod");
1499
969
  return { methods: oids, methodNames: _map(oids, function (d) { return ns.oid.name(d) || null; }) };
@@ -1506,7 +976,7 @@ function certExtensionDecoders(ns) {
1506
976
  var C = ns.prefix + "/bad-qc-statements", CS = ns.prefix + "/bad-qc-statement";
1507
977
  var kids = seqChildren(buf, C, "QCStatements");
1508
978
  if (!kids.length) throw ns.E(C, "QCStatements must contain at least one QCStatement (RFC 3739 sec. 3.2.6)");
1509
- var out = []; // QCStatements ::= SEQUENCE OF QCStatement -- RFC 3739 imposes no statementId uniqueness; decode every statement in wire order (a linter may flag a repeat, the decoder must not drop it)
979
+ var out = [];
1510
980
  for (var i = 0; i < kids.length; i++) {
1511
981
  var st = kids[i];
1512
982
  if (st.tagClass !== "universal" || st.tagNumber !== _T.SEQUENCE || !st.children || st.children.length < 1 || st.children.length > 2) {
@@ -1522,10 +992,6 @@ function certExtensionDecoders(ns) {
1522
992
  return out;
1523
993
  }
1524
994
 
1525
- // [MS-WCCE] szOID-CERTIFICATE_TEMPLATE. CertificateTemplateOID ::= SEQUENCE {
1526
- // templateID OID, templateMajorVersion INTEGER OPTIONAL, templateMinorVersion INTEGER OPTIONAL }.
1527
- // The two versions are DWORDs (0..2^32-1) and not uint31: a CA key/template revision with bit 31
1528
- // set is a legal DWORD, so bound to the whole safe-integer range, never guard.range.uint31.
1529
995
  function msCertificateTemplate(buf) {
1530
996
  var C = ns.prefix + "/bad-ms-certificate-template";
1531
997
  var kids = seqChildren(buf, C, "CertificateTemplate");
@@ -1541,8 +1007,6 @@ function certExtensionDecoders(ns) {
1541
1007
  return { templateID: id, name: ns.oid.name(id) || null, templateMajorVersion: ver(1, "templateMajorVersion"), templateMinorVersion: ver(2, "templateMinorVersion") };
1542
1008
  }
1543
1009
 
1544
- // [MS-CRTD] szOID-ENROLL_CERTTYPE_EXTENSION carries the legacy v1 template name as a BARE BMPString.
1545
- // asn1.read.string accepts any universal string tag, so the explicit BMP_STRING assert is the guard.
1546
1010
  function msEnrollCertType(buf) {
1547
1011
  var C = ns.prefix + "/bad-ms-enroll-cert-type";
1548
1012
  var n = decodeTop(buf, C, "EnrollCertType");
@@ -1550,7 +1014,6 @@ function certExtensionDecoders(ns) {
1550
1014
  try { return asn1.read.string(n); } catch (e) { throw ns.E(C, "the enroll cert-type name must be a well-formed BMPString", e); }
1551
1015
  }
1552
1016
 
1553
- // [MS-WCCE] szOID-CERTSRV_CA_VERSION is an INTEGER (0..2^32-1) = (caKeyIndex << 16) | certIndex.
1554
1017
  function msCaVersion(buf) {
1555
1018
  var C = ns.prefix + "/bad-ms-ca-version";
1556
1019
  var n = decodeTop(buf, C, "CACertVersion");
@@ -1559,10 +1022,6 @@ function certExtensionDecoders(ns) {
1559
1022
  return { caVersion: v, caKeyIndex: v >>> 16, certIndex: v & 0xffff };
1560
1023
  }
1561
1024
 
1562
- // [MS-WCCE] szOID-CERTSRV_PREVIOUS_CERT_HASH holds the SHA-1 thumbprint of the prior CA certificate.
1563
- // AD CS stamps the Windows certificate THUMBPRINT here, which Windows computes with SHA-1 regardless
1564
- // of the certificate's signature algorithm, so the OCTET STRING is exactly 20 octets. An empty or
1565
- // other-length value is malformed renewal metadata and fails closed.
1566
1025
  function msPreviousCertHash(buf) {
1567
1026
  var C = ns.prefix + "/bad-ms-previous-cert-hash";
1568
1027
  var n = decodeTop(buf, C, "CAPrevCertHash");
@@ -1573,9 +1032,6 @@ function certExtensionDecoders(ns) {
1573
1032
  return hash;
1574
1033
  }
1575
1034
 
1576
- // The extension-decoder registry every consumer indexes with an OID read off the wire
1577
- // (path validation, inspection, the ACME certificate binding), so it holds no prototype: a
1578
- // planted dotted name would otherwise hand an extension a decoder nothing registered.
1579
1035
  var byOid = _create(null);
1580
1036
  byOid[O("qcStatements")] = qcStatements;
1581
1037
  byOid[O("basicConstraints")] = basicConstraints;
@@ -1599,21 +1055,12 @@ function certExtensionDecoders(ns) {
1599
1055
  byOid[O("msEnrollCertType")] = msEnrollCertType;
1600
1056
  byOid[O("msCaVersion")] = msCaVersion;
1601
1057
  byOid[O("msPreviousCertHash")] = msPreviousCertHash;
1602
- byOid[O("msApplicationPolicies")] = certificatePolicies; // [MS-WCCE] = RFC 5280 certificatePolicies, reused not re-implemented
1058
+ byOid[O("msApplicationPolicies")] = certificatePolicies;
1603
1059
  return { byOid: byOid };
1604
1060
  }
1605
1061
 
1606
- // Extension ::= SEQUENCE { extnID OID, critical BOOLEAN DEFAULT FALSE,
1607
- // extnValue OCTET STRING }. `critical` is a universal BOOLEAN present-by-count
1608
- // (not context-tagged), so the per-extension decode handles the 2-vs-3-child
1609
- // shape directly; the seqOf centralizes the SEQUENCE / SIZE(1..MAX) assertion
1610
- // and the RFC 5280 sec. 4.2 per-OID uniqueness.
1611
1062
  function extension(ns) {
1612
1063
  return schema.decode(function (ext) {
1613
- // Extension ::= SEQUENCE { extnID, critical DEFAULT FALSE, extnValue } is a
1614
- // UNIVERSAL SEQUENCE of exactly 2 (critical omitted) or 3 children. A
1615
- // context-tagged item (e.g. [5]{OID, OCTET STRING}) or a wrong child count
1616
- // is malformed; assert the tag, don't just count children (fail closed).
1617
1064
  if (!ext.children || ext.tagClass !== "universal" || ext.tagNumber !== asn1.TAGS.SEQUENCE ||
1618
1065
  ext.children.length < 2 || ext.children.length > 3) {
1619
1066
  throw ns.E(ns.prefix + "/bad-extension", "Extension must be a SEQUENCE of {extnID, critical?, extnValue}");
@@ -1622,8 +1069,6 @@ function extension(ns) {
1622
1069
  var critical = false, valueNode;
1623
1070
  if (ext.children.length === 3) {
1624
1071
  critical = asn1.read.boolean(ext.children[1]);
1625
- // critical is BOOLEAN DEFAULT FALSE -- DER omits the field when false, so an
1626
- // explicitly-encoded FALSE is a non-canonical form; reject it fail-closed.
1627
1072
  if (critical === false) throw ns.E(ns.prefix + "/bad-extension", "an explicit critical FALSE must be omitted (BOOLEAN DEFAULT FALSE)");
1628
1073
  valueNode = ext.children[2];
1629
1074
  } else {
@@ -1632,10 +1077,6 @@ function extension(ns) {
1632
1077
  return { oid: extnID, name: ns.oid.name(extnID) || null, critical: critical, value: asn1.read.octetString(valueNode) };
1633
1078
  });
1634
1079
  }
1635
- // opts.implicitTag (optional): read Extensions as a [tag] IMPLICIT SEQUENCE OF
1636
- // Extension, where the context tag REPLACES the universal SEQUENCE tag, for the CRMF
1637
- // CertTemplate extensions [9] (RFC 4211 sec. 5). With no opts the shape is a bare
1638
- // universal SEQUENCE OF, byte-identical to every existing caller.
1639
1080
  function extensions(ns, opts) {
1640
1081
  opts = opts || {};
1641
1082
  var EXT = extension(ns);
@@ -1648,15 +1089,6 @@ function extensions(ns, opts) {
1648
1089
  return schema.seqOf(EXT, _assign({ assert: "sequence" }, extOpts));
1649
1090
  }
1650
1091
 
1651
- // SubjectPublicKeyInfo ::= SEQUENCE { algorithm AlgorithmIdentifier,
1652
- // subjectPublicKey BIT STRING } (RFC 5280 sec. 4.1.2.7, RFC 2986 sec. 4.1). Asserted as a
1653
- // universal SEQUENCE, so a context-tagged or SET-tagged constructed node carrying
1654
- // two well-formed children is not a SubjectPublicKeyInfo. Shared by the
1655
- // certificate and CSR parsers.
1656
- // opts.implicitTag (optional): read the SubjectPublicKeyInfo as a [tag] IMPLICIT
1657
- // SEQUENCE (a context-class constructed node whose children are algorithm +
1658
- // subjectPublicKey), for the CRMF CertTemplate publicKey [6] (RFC 4211 sec. 5). With no
1659
- // opts the shape is a universal SEQUENCE, byte-identical to every existing caller.
1660
1092
  function spki(ns, opts) {
1661
1093
  opts = opts || {};
1662
1094
  return schema.seq([
@@ -1669,22 +1101,12 @@ function spki(ns, opts) {
1669
1101
  return {
1670
1102
  algorithm: m.fields.algorithm.value.result,
1671
1103
  publicKey: { unusedBits: m.fields.subjectPublicKey.value.unusedBits, bytes: m.fields.subjectPublicKey.value.bytes },
1672
- // `bytes` is the importable SubjectPublicKeyInfo DER. In IMPLICIT [tag]
1673
- // mode the wire node leads with the context tag, so recover the SEQUENCE
1674
- // encoding a consumer imports / hashes, and not the [tag] wire form.
1675
1104
  bytes: opts.implicitTag != null ? asn1.sequenceTlv(m.node) : m.node.bytes,
1676
1105
  };
1677
1106
  },
1678
1107
  });
1679
1108
  }
1680
1109
 
1681
- // Attribute ::= SEQUENCE { type OBJECT IDENTIFIER, values SET OF AttributeValue }.
1682
- // AttributeValue is ANY, kept as raw DER (node.bytes), so an unrecognized attribute
1683
- // type never fails the parse. values is SET SIZE (1..MAX) by default: an empty SET
1684
- // is rejected; there is NO SET-OF uniqueness. Shared by the CSR (requested
1685
- // attributes, RFC 2986 sec. 4.1) and PKCS#8 (private-key attributes, RFC 5958 sec. 2).
1686
- // opts.minValues (default 1) lowers the floor to 0 for the EST CsrAttrs key-type
1687
- // hints, whose empty values SET means "any key of this type" (RFC 9908 sec. 3.2).
1688
1110
  function attribute(ns, opts) {
1689
1111
  opts = opts || {};
1690
1112
  var minValues = opts.minValues === undefined ? 1 : opts.minValues;
@@ -1705,14 +1127,6 @@ function attribute(ns, opts) {
1705
1127
  });
1706
1128
  }
1707
1129
 
1708
- // Certificate, CertificateList, and CertificationRequest share one outer shape:
1709
- // SEQUENCE { toBeSigned SEQUENCE, signatureAlgorithm, signatureValue }. This
1710
- // returns the first element (the to-be-signed info) when `root` is that
1711
- // SEQUENCE-of-exactly-3 whose first child is itself a constructed universal
1712
- // SEQUENCE, or null otherwise. Every format's `matches` detector shares this
1713
- // preamble, so the signed-envelope shape is recognized in one place and the three
1714
- // detectors cannot drift on it (the CRL detector historically omitted the
1715
- // tbs-is-universal check this recovers).
1716
1130
  function signedEnvelopeTbs(root) {
1717
1131
  if (!schema.isUniversal(root, asn1.TAGS.SEQUENCE)) return null;
1718
1132
  if (!root.children || root.children.length !== 3) return null;
@@ -1721,10 +1135,6 @@ function signedEnvelopeTbs(root) {
1721
1135
  return tbs;
1722
1136
  }
1723
1137
 
1724
- // rootSequenceChildren(root, minLen, maxLen) -- the shared root-shape guard the
1725
- // format matches() detectors re-inline: returns root.children when root is a
1726
- // NON-EMPTY universal SEQUENCE whose child count is within [minLen, maxLen]
1727
- // (maxLen optional/Infinity), else null. Sibling of signedEnvelopeTbs.
1728
1138
  function rootSequenceChildren(root, minLen, maxLen) {
1729
1139
  if (!schema.isUniversal(root, asn1.TAGS.SEQUENCE) || !root.children) return null;
1730
1140
  var n = root.children.length;
@@ -1733,49 +1143,15 @@ function rootSequenceChildren(root, minLen, maxLen) {
1733
1143
  return root.children;
1734
1144
  }
1735
1145
 
1736
- // Every format's `parse` is the shared runParse bound to that format's identity
1737
- // (PEM label, error class, error-code prefix, top-level schema). This returns the
1738
- // bound parser so a format declares its configuration once and never re-writes the
1739
- // coerce -> decode -> walk wrapper. `opts`: { pemLabel, PemError, ErrorClass,
1740
- // prefix, what, topSchema, ns }.
1741
- // The top-level schema's reject code, the verdict for a DER that is not this
1742
- // structure at all, is `<prefix>/not-a-<structure>` (x509/not-a-certificate,
1743
- // cms/not-a-content-info, ...). Three codes predate that convention and stay
1744
- // frozen on the public error-code surface (ocsp/bad-ocsp-response,
1745
- // tsp/bad-response, crmf/bad-cert-req-messages); a new format follows the
1746
- // not-a-* form, never those precedents.
1747
1146
  function makeParser(opts) {
1748
1147
  return function (input) { return runParse(input, opts); };
1749
1148
  }
1750
1149
 
1751
- // makeRecordingParser(opts, kind) -> a parser that also records the bytes it read.
1752
- //
1753
- // The pairing lives here and is not repeated per format, because it is one decision, not four: a
1754
- // structure whose parse feeds a VERDICT needs its fields bound to the byte string they came from,
1755
- // since a parsed structure presents the signed range, the signature and the fields that range
1756
- // encodes as separate properties, and a caller can hand back a genuine range beside substituted
1757
- // fields. `kind` is the tag the matching door re-derives under, so a certificate cannot be presented
1758
- // where a CRL is expected. The record lives in guard-parsed, off the object, unreachable through it.
1759
1150
  function makeRecordingParser(opts, kind) {
1760
1151
  return guard.parsed.recordingParser(kind, makeParser(opts), opts.ErrorClass,
1761
1152
  opts.prefix + "/bad-input", "a " + opts.what);
1762
1153
  }
1763
1154
 
1764
- // The X.509 SIGNED{ToBeSigned} macro (RFC 5280 sec. 4.1.1.3): the outer
1765
- // SEQUENCE { toBeSigned, signatureAlgorithm AlgorithmIdentifier,
1766
- // signatureValue BIT STRING } shared by Certificate, CertificateList and
1767
- // CertificationRequest. `tbsSchema` parses the first element; the SEQUENCE-of-3
1768
- // shape, the arity, the signature extraction and the raw tbs / outer-signature
1769
- // bytes (for the cert/CRL outer==inner agreement check) are owned here once, and
1770
- // each format's `opts.build(envelope, ctx)` shapes its own object from the
1771
- // envelope. A CSR's build simply omits the agreement check, since its CRI has no inner
1772
- // signature AlgorithmIdentifier. The omission is structural, not a copy that
1773
- // forgot a guard.
1774
- // signatureValue is surfaced raw as { unusedBits, bytes }: for the envelope
1775
- // family, octet alignment is a verify-layer rule (path-validate rejects
1776
- // unusedBits != 0 for certification paths; an external verifier must do the
1777
- // same). A format with no in-tree verify layer (OCSP, CMP protection, CRMF
1778
- // POP) instead enforces alignment at parse.
1779
1155
  function signedEnvelope(ns, tbsSchema, opts) {
1780
1156
  return schema.seq([
1781
1157
  schema.field("toBeSigned", tbsSchema),
@@ -1787,8 +1163,8 @@ function signedEnvelope(ns, tbsSchema, opts) {
1787
1163
  var tbsMatch = m.fields.toBeSigned.value;
1788
1164
  var sigBits = m.fields.signatureValue.value;
1789
1165
  return opts.build({
1790
- tbsMatch: tbsMatch, // raw seq-match: .fields.* / .result / .node
1791
- tbsBytes: tbsMatch.node.bytes, // the exact signed region
1166
+ tbsMatch: tbsMatch,
1167
+ tbsBytes: tbsMatch.node.bytes,
1792
1168
  outerSignatureAlgorithmBytes: m.fields.signatureAlgorithm.node.bytes,
1793
1169
  signatureAlgorithm: m.fields.signatureAlgorithm.value.result,
1794
1170
  signatureValue: { unusedBits: sigBits.unusedBits, bytes: sigBits.bytes },
@@ -1797,13 +1173,6 @@ function signedEnvelope(ns, tbsSchema, opts) {
1797
1173
  });
1798
1174
  }
1799
1175
 
1800
- // dNSName syntax (RFC 5280 sec. 4.2.1.6 / RFC 1034 preferred name syntax, a representative CABF check): no
1801
- // whitespace, no leading/trailing dot, no empty label, no underscore, LDH labels (1-63 octets, no leading /
1802
- // trailing hyphen), <= 253 octets, an optional leftmost "*" wildcard. Returns a reason string, or null when
1803
- // well-formed. The shared home so the linter AND an identity comparator (case-fold only a valid dNSName) agree.
1804
- // One LDH label (RFC 1034 preferred syntax), decided character by character: every character an
1805
- // ASCII letter/digit/hyphen, and a hyphen neither first nor last. The caller has already rejected
1806
- // any label carrying a dot, whitespace, or underscore, so only the LDH shape remains to check.
1807
1176
  function _isLdhLabel(s) {
1808
1177
  var n = s.length;
1809
1178
  if (n === 0) return false;
@@ -1841,12 +1210,12 @@ module.exports = {
1841
1210
  pemDecodeAll: pemDecodeAll,
1842
1211
  pemDecodeLenient: pemDecodeLenient,
1843
1212
  pemEncode: pemEncode,
1844
- isJsWhitespace: _isJsWhitespace, // the shared JS regex \s code-point predicate for char-scan parsers
1845
- stripBase64Whitespace: _stripB64Whitespace, // shared CR/LF/TAB/SP strip for base64 bodies (PEM, MIME)
1846
- charTable: _charTable, // build a code-point membership table from a string of allowed characters
1847
- allCharsIn: _allCharsIn, // is a NON-EMPTY string composed only of table members (the /^[chars]+$/ family)
1213
+ isJsWhitespace: _isJsWhitespace,
1214
+ stripBase64Whitespace: _stripB64Whitespace,
1215
+ charTable: _charTable,
1216
+ allCharsIn: _allCharsIn,
1848
1217
  coerceToDer: coerceToDer,
1849
- isPemArmor: _isPemArmor, // does a byte source carry a PEM "-----BEGIN" armor (RFC 7468 leading text allowed)
1218
+ isPemArmor: _isPemArmor,
1850
1219
  decodeRoot: decodeRoot,
1851
1220
  runParse: runParse,
1852
1221
  makeNS: makeNS,