@blamejs/pki 0.5.7 → 0.5.8

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 (104) hide show
  1. package/CHANGELOG.md +405 -386
  2. package/MIGRATING.md +43 -0
  3. package/README.md +12 -12
  4. package/lib/acme.js +31 -31
  5. package/lib/asn1-der.js +10 -10
  6. package/lib/attrcert-sign.js +19 -19
  7. package/lib/byte-reader.js +6 -6
  8. package/lib/byte-writer.js +5 -5
  9. package/lib/cbor-det.js +27 -24
  10. package/lib/cmc-build.js +97 -92
  11. package/lib/cmc-verify.js +106 -30
  12. package/lib/cmp-build.js +25 -25
  13. package/lib/cmp-session.js +70 -70
  14. package/lib/cmp-verify.js +71 -57
  15. package/lib/cms-compress.js +7 -7
  16. package/lib/cms-decrypt.js +90 -72
  17. package/lib/cms-encrypt.js +32 -32
  18. package/lib/cms-sign.js +74 -55
  19. package/lib/cms-verify.js +97 -75
  20. package/lib/composite-sig.js +13 -13
  21. package/lib/constants.js +4 -4
  22. package/lib/crl-sign.js +22 -22
  23. package/lib/crl-verify.js +7 -6
  24. package/lib/crmf-sign.js +14 -14
  25. package/lib/csr-sign.js +8 -8
  26. package/lib/ct.js +37 -37
  27. package/lib/edwards-point.js +7 -7
  28. package/lib/est.js +98 -55
  29. package/lib/framework-error.js +5 -5
  30. package/lib/guard-all.js +3 -3
  31. package/lib/guard-async.js +4 -4
  32. package/lib/guard-bytes.js +79 -79
  33. package/lib/guard-compress.js +17 -17
  34. package/lib/guard-crypto.js +1 -1
  35. package/lib/guard-encoding.js +15 -15
  36. package/lib/guard-header.js +3 -3
  37. package/lib/guard-identifier.js +16 -16
  38. package/lib/guard-json.js +15 -15
  39. package/lib/guard-limits.js +7 -7
  40. package/lib/guard-name.js +81 -16
  41. package/lib/guard-parsed.js +80 -80
  42. package/lib/guard-range.js +19 -19
  43. package/lib/guard-secret.js +11 -10
  44. package/lib/guard-text.js +6 -6
  45. package/lib/guard-time.js +10 -10
  46. package/lib/hpke.js +18 -17
  47. package/lib/http-digest.js +35 -35
  48. package/lib/http-retry-after.js +13 -13
  49. package/lib/http-transport.js +20 -19
  50. package/lib/inspect.js +53 -53
  51. package/lib/ip-utils.js +2 -2
  52. package/lib/jose.js +13 -13
  53. package/lib/key.js +16 -16
  54. package/lib/lint.js +51 -51
  55. package/lib/merkle.js +51 -36
  56. package/lib/mime.js +18 -18
  57. package/lib/ocsp-verify.js +10 -10
  58. package/lib/ocsp.js +13 -13
  59. package/lib/oid.js +29 -29
  60. package/lib/path-validate.js +114 -113
  61. package/lib/pbes2.js +16 -16
  62. package/lib/pkcs12-build.js +53 -53
  63. package/lib/pki-build.js +21 -19
  64. package/lib/rc2.js +1 -1
  65. package/lib/rfc3339.js +5 -5
  66. package/lib/schema-all.js +31 -31
  67. package/lib/schema-attrcert.js +12 -12
  68. package/lib/schema-c509.js +144 -142
  69. package/lib/schema-cmc.js +58 -58
  70. package/lib/schema-cmp.js +43 -43
  71. package/lib/schema-cms.js +45 -45
  72. package/lib/schema-crl.js +7 -7
  73. package/lib/schema-crmf.js +28 -28
  74. package/lib/schema-csr.js +12 -12
  75. package/lib/schema-csrattrs.js +16 -16
  76. package/lib/schema-engine.js +18 -18
  77. package/lib/schema-ocsp.js +15 -15
  78. package/lib/schema-pkcs12.js +20 -20
  79. package/lib/schema-pkcs8.js +2 -2
  80. package/lib/schema-pkix.js +131 -126
  81. package/lib/schema-smime.js +19 -19
  82. package/lib/schema-tsp.js +12 -12
  83. package/lib/schema-x509.js +3 -3
  84. package/lib/shbs.js +18 -18
  85. package/lib/sign-scheme.js +13 -13
  86. package/lib/sigstore.js +10 -11
  87. package/lib/sleep.js +1 -1
  88. package/lib/smime.js +308 -96
  89. package/lib/tls-cert-compress.js +18 -18
  90. package/lib/trust.js +27 -27
  91. package/lib/tsp-sign.js +17 -17
  92. package/lib/validator-all.js +1 -1
  93. package/lib/validator-attcert.js +1 -1
  94. package/lib/validator-cose.js +43 -44
  95. package/lib/validator-keydesc.js +3 -3
  96. package/lib/validator-sig.js +13 -13
  97. package/lib/validator-tls.js +11 -11
  98. package/lib/validator-tpm.js +20 -19
  99. package/lib/webauthn-mds.js +66 -66
  100. package/lib/webauthn.js +33 -33
  101. package/lib/webcrypto.js +15 -15
  102. package/lib/x509-sign.js +13 -13
  103. package/package.json +3 -2
  104. package/sbom.cdx.json +6 -6
@@ -10,7 +10,7 @@
10
10
  // returns an asn1-schema that walks the corresponding ASN.1 structure and emits
11
11
  // the caller's own <prefix>/* error codes. x509.js, crl.js, and future CMS/CSR
12
12
  // parsers compose these so AlgorithmIdentifier / Name / Extension are defined
13
- // once, not re-derived per format. This module is internal infrastructure -- the
13
+ // once, not re-derived per format. This module is internal infrastructure: the
14
14
  // operator-facing surface is the parsers that consume it.
15
15
 
16
16
  var asn1 = require("./asn1-der");
@@ -36,7 +36,7 @@ var PEM_RE = /-----BEGIN ([A-Z0-9 ]+)-----([\s\S]*?)-----END \1-----/;
36
36
  // lenient Buffer.from would otherwise let several distinct PEM texts alias one
37
37
  // DER (PEM-layer malleability) and silently drop trailing content bits.
38
38
  function pemDecode(text, label, PemError) {
39
- // guard.text.decode caps the raw byte length BEFORE the latin1 string copy:
39
+ // guard.text.decode caps the raw byte length before the latin1 string copy:
40
40
  // converting first would allocate a full-size string for input the cap is
41
41
  // about to reject, and a buffer above Node's max string length would escape as
42
42
  // an untyped ERR_STRING_TOO_LONG instead of pem/too-large.
@@ -51,7 +51,7 @@ function pemDecode(text, label, PemError) {
51
51
  return guard.encoding.base64(b64, null, function (c, msg) { return new PemError(c, msg); }, "pem/bad-base64", "PEM base64 body");
52
52
  }
53
53
 
54
- // pemDecodeAll(text, label, PemError): decode EVERY PEM block under the STRICT
54
+ // pemDecodeAll(text, label, PemError): decode every PEM block under the strict
55
55
  // RFC 7468 profile for application/pem-certificate-chain (RFC 8555 sec. 9.1) --
56
56
  // each block must carry `label` (default CERTIFICATE), explanatory text MUST NOT
57
57
  // appear before/between/after blocks (errata 5983: certchain = stricttextualmsg
@@ -81,13 +81,13 @@ function pemDecodeAll(text, label, PemError) {
81
81
  // alphanumeric words separated by single spaces, a subset of the RFC 7468
82
82
  // sec. 3 label grammar). Anything else (lowercase, a leading/trailing space,
83
83
  // an embedded "-----" or newline) would emit armor pemDecode itself rejects
84
- // or corrupt the armor grammar -- a config-time authoring fault, thrown here.
84
+ // or corrupt the armor grammar: a config-time authoring fault, thrown here.
85
85
  function pemEncode(der, label, PemError) {
86
86
  if (typeof label !== "string" || !/^[A-Z0-9]+( [A-Z0-9]+)*$/.test(label)) {
87
87
  throw new PemError("pem/bad-label", "pemEncode requires an uppercase A-Z0-9 label with single spaces");
88
88
  }
89
89
  // Re-view through the byte guard: a string input would silently utf8-armor into
90
- // a bogus PEM, and a detached-backed Buffer would armor an empty body -- both
90
+ // a bogus PEM, and a detached-backed Buffer would armor an empty body; both
91
91
  // fail closed (a Uint8Array / Buffer of real DER re-views cleanly).
92
92
  var buf = guard.bytes.view(der, PemError, "pem/bad-input", "pemEncode DER input");
93
93
  var b64 = buf.toString("base64").replace(/(.{64})/g, "$1\n").replace(/\n$/, "");
@@ -101,7 +101,7 @@ function coerceToDer(input, opts) {
101
101
  if (typeof input === "string") return pemDecode(input, opts.pemLabel, opts.PemError);
102
102
  if (Buffer.isBuffer(input) || input instanceof Uint8Array) {
103
103
  // Route the DER Buffer / Uint8Array through the shared byte guard so a
104
- // detached backing store fails closed here for EVERY format that composes
104
+ // detached backing store fails closed here for every format that composes
105
105
  // this boundary (x509/crl/csr/pkcs8/cms/pkcs12), not per-format.
106
106
  var buf = guard.bytes.view(input, opts.ErrorClass, opts.prefix + "/bad-input", "parse");
107
107
  return _isPemArmor(buf) ? pemDecode(buf, opts.pemLabel, opts.PemError) : buf;
@@ -109,11 +109,11 @@ function coerceToDer(input, opts) {
109
109
  throw new opts.ErrorClass(opts.prefix + "/bad-input", "parse expects a DER Buffer or a PEM string");
110
110
  }
111
111
 
112
- // Does a Buffer carry PEM armor (a .pem read with fs.readFileSync) rather than
112
+ // Does a Buffer carry PEM armor (a .pem read with fs.readFileSync) instead of
113
113
  // raw DER? It does iff "-----BEGIN" appears and everything before it is TEXT
114
- // (UTF-8 BOM / whitespace / RFC 7468 explanatory preamble). DER is binary -- its
115
- // leading tag+length bytes are non-printable -- so a non-SEQUENCE DER (a bare
116
- // SET / INTEGER) is NOT misrouted here; it decodes and fails closed structurally.
114
+ // (UTF-8 BOM / whitespace / RFC 7468 explanatory preamble). DER is binary and its
115
+ // leading tag+length bytes are non-printable, so a non-SEQUENCE DER (a bare
116
+ // SET / INTEGER) is not misrouted here; it decodes and fails closed structurally.
117
117
  function _isPemArmor(buf) {
118
118
  var head = buf.slice(0, 4096).toString("latin1");
119
119
  var idx = head.indexOf("-----BEGIN");
@@ -130,9 +130,9 @@ function _isPemArmor(buf) {
130
130
  // Decode the DER root, wrapping a codec fault in the caller's <prefix>/bad-der.
131
131
  function decodeRoot(der, opts) {
132
132
  // A format whose wire encoding is normatively BER (RFC 7292 PKCS#12) opts
133
- // in to the codec's ber mode for the WHOLE decode. BER-vs-DER divergence is
134
- // not always a decode-time throw -- a definite-length constructed IMPLICIT
135
- // OCTET STRING decodes strictly and only diverges at the typed reader -- so
133
+ // in to the codec's ber mode for the whole decode. BER-vs-DER divergence is
134
+ // not always a decode-time throw: a definite-length constructed IMPLICIT
135
+ // OCTET STRING decodes strictly and only diverges at the typed reader, so
136
136
  // a strict-first-retry-on-throw boundary would miss legal BER shapes. A
137
137
  // strict-DER input decodes identically under the ber mode, every other
138
138
  // strictness verdict included.
@@ -150,7 +150,7 @@ function runParse(input, opts) {
150
150
  }
151
151
 
152
152
  // Distinguished-name attribute short labels (RFC 4514 sec. 3 + common use).
153
- // RFC 4514 short names for the DN string representation -- the single source is
153
+ // RFC 4514 short names for the DN string representation; the single source is
154
154
  // pki.C.NAMES.DN_SHORT (shared with the human-readable renderer so the two can't drift).
155
155
  var DN_SHORT = constants.NAMES.DN_SHORT;
156
156
 
@@ -158,7 +158,7 @@ var DN_SHORT = constants.NAMES.DN_SHORT;
158
158
  // The error/OID namespace every format module walks its schema under:
159
159
  // { prefix, E:(code,message,cause)=>new ErrorClass(...), oid }. Factored so a
160
160
  // format declares it in one line instead of repeating the error-constructor
161
- // closure (a caller that never passes a cause is unaffected -- withCause ignores
161
+ // closure (a caller that never passes a cause is unaffected, since withCause ignores
162
162
  // an undefined third arg).
163
163
  function makeNS(prefix, ErrorClass, oidModule) {
164
164
  return { prefix: prefix, E: function (code, message, cause) { return new ErrorClass(code, message, cause); }, oid: oidModule };
@@ -166,9 +166,9 @@ function makeNS(prefix, ErrorClass, oidModule) {
166
166
 
167
167
  // A bounded universal-INTEGER version reader. `accept` maps each legal wire value
168
168
  // (as a decimal string) to its surfaced version number; any other value is a
169
- // <prefix>/bad-version fault. The one genuine per-format divergence -- the cert
169
+ // <prefix>/bad-version fault. The one genuine per-format divergence, the cert
170
170
  // rejects 0 and maps 1->2/2->3, a CRL accepts only 1->2, a CSR only 0->1, a PKCS#8
171
- // 0->1/1->2 -- is expressed purely as the accept map (RFC 5280 sec. 4.1.2.1 / sec. 5.1.2.1,
171
+ // 0->1/1->2, is expressed purely as the accept map (RFC 5280 sec. 4.1.2.1 / sec. 5.1.2.1,
172
172
  // RFC 2986 sec. 4.1, RFC 5958 sec. 2). read.integer is strict, so an ENUMERATED-tagged
173
173
  // version is rejected at the leaf (asn1/*).
174
174
  function versionReader(ns, accept) {
@@ -185,11 +185,11 @@ function versionReader(ns, accept) {
185
185
  // universal SEQUENCE, byte-identical to every existing caller.
186
186
  //
187
187
  // Parameter-rule tiering: only the params-MUST-BE-ABSENT families (ML-DSA,
188
- // SLH-DSA, the Edwards/Montgomery curves -- new registrations with no legacy
188
+ // SLH-DSA, the Edwards/Montgomery curves), new registrations with no legacy
189
189
  // corpus) are enforced at parse time, in the build below. The CLASSICAL
190
190
  // per-OID rules (RSA parameters MUST be NULL, RFC 3279 sec. 2.2.1/2.3.1;
191
191
  // ecdsa-with-SHA2 parameters MUST be omitted, RFC 5758 sec. 3.2) are
192
- // deliberately NOT parse-time rejects: a long tail of deployed certificates
192
+ // deliberately not parse-time rejects: a long tail of deployed certificates
193
193
  // omits the RSA NULL, so the parse tier stays interop-lenient for those
194
194
  // families and the strict classical check runs on the signature-verification
195
195
  // path (path-validate's signature-algorithm registry), where the verdict
@@ -205,11 +205,11 @@ function algorithmIdentifier(ns, opts) {
205
205
  arity: { min: 1 }, code: ns.prefix + "/bad-algorithm-identifier", what: "AlgorithmIdentifier",
206
206
  build: function (m, ctx) {
207
207
  var dotted = m.fields.algorithm.value;
208
- // For every algorithm the registry marks parameters-absent -- the PQC
208
+ // The registry marks several algorithm families parameters-absent: the PQC
209
209
  // signature families (ML-DSA RFC 9881, SLH-DSA RFC 9909/9814), the
210
210
  // Edwards/Montgomery curves (RFC 8410), ML-KEM (RFC 9936), and the HKDF
211
- // identifiers (RFC 8619) -- the `parameters` field MUST be absent: no
212
- // explicit NULL, no bytes. Enforced ONCE here so every consumer of the
211
+ // identifiers (RFC 8619). For all of them the `parameters` field MUST be
212
+ // absent: no explicit NULL, no bytes. Enforced ONCE here so every consumer of the
213
213
  // shared AlgorithmIdentifier inherits the rule; the guard-parity bug
214
214
  // class is structurally impossible.
215
215
  if (m.fields.parameters.present && ctx.oid.paramsMustBeAbsent(dotted)) {
@@ -221,7 +221,7 @@ function algorithmIdentifier(ns, opts) {
221
221
  });
222
222
  }
223
223
 
224
- // pbkdf2Params(ns): PBKDF2-params (RFC 8018 sec. 5.2) constrained to the RFC 9579 PBMAC1 profile -- the
224
+ // pbkdf2Params(ns): PBKDF2-params (RFC 8018 sec. 5.2) constrained to the RFC 9579 PBMAC1 profile, where the
225
225
  // salt uses the OCTET STRING choice and keyLength MUST be present (a MacData / PKIProtection consumer
226
226
  // cannot infer the derived MAC key size, RFC 9579 sec. 4.b/5). Shared by the PKCS#12 MacData reader and
227
227
  // the CMP PBMAC1 protection reader: one ns-parameterized decoder so neither format re-derives the shape.
@@ -238,26 +238,28 @@ function pbkdf2Params(ns) {
238
238
  if (!m.fields.keyLength.present) {
239
239
  throw ctx.E(ctx.prefix + "/bad-mac-data", "PBMAC1 PBKDF2-params must carry keyLength (RFC 9579 sec. 5)");
240
240
  }
241
- // guard.range.positiveInt31 bounds + narrows each counter atomically -- a value past the bound
241
+ // guard.range.positiveInt31 bounds + narrows each counter atomically, so a value past the bound
242
242
  // would round silently and hand a verifier wrong inputs.
243
243
  var iterationCount = guard.range.positiveInt31(m.fields.iterationCount.value, ctx.E, ctx.prefix + "/bad-mac-data", "PBKDF2 iterationCount");
244
244
  var keyLength = guard.range.positiveInt31(m.fields.keyLength.value, ctx.E, ctx.prefix + "/bad-mac-data", "PBKDF2 keyLength");
245
245
  var prf = m.fields.prf.present ? m.fields.prf.value.result : null;
246
- // X.690 sec. 11.5 -- the prf DEFAULT is algid-hmacWithSHA1 (hmacWithSHA1 with NULL parameters,
246
+ // X.690 sec. 11.5: the prf DEFAULT is algid-hmacWithSHA1 (hmacWithSHA1 with NULL parameters,
247
247
  // i.e. the 2-byte DER 05 00, RFC 8018 sec. 5.2); an explicit prf byte-equal to that default is
248
248
  // non-canonical and rejects. hmacWithSHA1 with ABSENT parameters is a different value and decodes.
249
- // This is a public structural check on the algorithm parameters -- not a secret compare -- so a
249
+ // This is a public structural check on the algorithm parameters, not a secret compare, so a
250
250
  // direct byte test on the (fixed 2-octet) NULL encoding, not a timing-safe comparison.
251
251
  var pp = prf ? prf.parameters : null;
252
- // nosemgrep: pki-non-constant-time-secret-compare -- pp is the PUBLIC algorithm-identifier parameters
253
- // field (a fixed 2-octet NULL encoding), not a MAC / tag / secret; a timing-safe compare is inapplicable.
252
+ // pp is the public algorithm-identifier parameters field, a fixed 2-octet NULL encoding.
253
+ // No MAC, tag or secret is involved, so a timing-safe compare has nothing to protect.
254
+ // nosemgrep: pki-non-constant-time-secret-compare
254
255
  if (prf && prf.oid === hmacSha1 && pp !== null && pp.length === 2 && pp[0] === 0x05 && pp[1] === 0x00) {
255
256
  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)");
256
257
  }
257
258
  // The PBKDF2 prf HMAC AlgorithmIdentifier likewise carries NULL (or absent) parameters (RFC 8018 App. B.1):
258
259
  // reject a present-but-non-NULL prf parameter (e.g. an INTEGER) instead of discarding it, since the prf
259
260
  // hash is dispatched by OID alone. (An absent prf, and hmacWithSHA1 with absent params, remain valid above.)
260
- // nosemgrep: pki-non-constant-time-secret-compare -- pp is the PUBLIC algorithm-identifier parameters node.
261
+ // pp is the public algorithm-identifier parameters node.
262
+ // nosemgrep: pki-non-constant-time-secret-compare
261
263
  if (prf && pp !== null && !(pp.length === 2 && pp[0] === 0x05 && pp[1] === 0x00)) {
262
264
  throw ctx.E(ctx.prefix + "/bad-mac-data", "the PBKDF2 prf parameters must be absent or NULL (RFC 8018 App. B.1)");
263
265
  }
@@ -293,12 +295,13 @@ function pbmac1Params(ns) {
293
295
  }
294
296
  var scheme = m.fields.messageAuthScheme.value.result;
295
297
  // RFC 8018 App. B.1: an HMAC messageAuthScheme AlgorithmIdentifier carries NULL (or absent) parameters,
296
- // matching the builder (_hmacAlgId emits the 2-byte 05 00). Reject any OTHER parameter encoding rather than
297
- // silently discarding it -- _verifyMac dispatches by the scheme OID alone, so a mismatched / malformed
298
+ // matching the builder (_hmacAlgId emits the 2-byte 05 00). Any other parameter encoding is
299
+ // rejected: _verifyMac dispatches by the scheme OID alone, so a mismatched / malformed
298
300
  // parameter (e.g. an INTEGER) must not slip through unvalidated. A direct byte test on the fixed 2-octet
299
301
  // NULL encoding of the PUBLIC parameters, not a secret compare.
300
302
  var sp = scheme.parameters;
301
- // nosemgrep: pki-non-constant-time-secret-compare -- sp is the PUBLIC algorithm-identifier parameters node.
303
+ // sp is the public algorithm-identifier parameters node.
304
+ // nosemgrep: pki-non-constant-time-secret-compare
302
305
  if (sp !== null && !(sp.length === 2 && sp[0] === 0x05 && sp[1] === 0x00)) {
303
306
  throw ctx.E(ctx.prefix + "/bad-mac-data", "the PBMAC1 messageAuthScheme parameters must be absent or NULL (RFC 8018 App. B.1)");
304
307
  }
@@ -314,12 +317,12 @@ function pbmac1Params(ns) {
314
317
  // attrValueToString(ns): the AttributeValue decode-leaf. A malformed KNOWN
315
318
  // string type (invalid UTF-8, a non-IA5 byte, a PrintableString character
316
319
  // outside its set, ...) surfaces as an asn1/bad-* content error and must fail
317
- // closed -- do NOT hex-encode it away, or the decoder's strict string validation
320
+ // closed. Do not hex-encode it away, or the decoder's strict string validation
318
321
  // is silently bypassed on the DN path. A value that is simply not a decodable
319
- // primitive string is NOT malformed and stays representable: an ANY-typed
322
+ // primitive string is not malformed and stays representable: an ANY-typed
320
323
  // non-string tag (asn1/expected-string) or a constructed universal type such as
321
324
  // a SEQUENCE (asn1/expected-primitive) renders per RFC 4514 sec. 2.4 as "#" plus the
322
- // hex of its FULL DER encoding (node.bytes), round-tripping intact.
325
+ // hex of its complete DER encoding (node.bytes), round-tripping intact.
323
326
  // A GENUINE string whose first character is '#' or '\' is surfaced with a
324
327
  // leading '\' escape (RFC 4514 sec. 2.4) so it can never collide with the
325
328
  // '#'+hex form; the encode direction strips that one leading escape back off.
@@ -331,8 +334,9 @@ function attrValueToString(ns) {
331
334
  if (!e || (e.code !== "asn1/expected-string" && e.code !== "asn1/expected-primitive")) {
332
335
  throw ns.E(ns.prefix + "/bad-atv", "malformed string in attribute value: " + ((e && e.message) || String(e)));
333
336
  }
334
- // NumericString is deliberately NOT read by asn1.read.string -- a value that returns a plain string joins
335
- // the RFC 5280 sec. 7.1 name-comparison identity class, and NumericString is not a DirectoryString type.
337
+ // asn1.read.string does not read NumericString, and that is deliberate: a value that returns a plain
338
+ // string joins the RFC 5280 sec. 7.1 name-comparison identity class, and NumericString is not a
339
+ // DirectoryString type.
336
340
  // It does have its own strict reader though, so VALIDATE the content here (an out-of-alphabet value is
337
341
  // malformed DER and must not be accepted merely because it falls to the opaque form) while still
338
342
  // surfacing it in the type-distinct RFC 4514 hex form, which keeps it out of that identity class.
@@ -347,9 +351,9 @@ function attrValueToString(ns) {
347
351
  }, function (value) {
348
352
  // encode: a leading '\' escapes the next character (the decode's escape of a
349
353
  // literal '#'/'\'); a "#hex" form (RFC 4514 sec. 2.4) round-trips its raw DER
350
- // verbatim -- the hex is validated (even-length hex digits encoding exactly one
351
- // DER TLV) and throws rather than silently emitting truncated bytes, since
352
- // Buffer.from(str, "hex") stops at the first invalid character. Any other
354
+ // verbatim. The hex is validated (even-length hex digits encoding exactly one
355
+ // DER TLV) and throws on anything else, since Buffer.from(str, "hex") stops at
356
+ // the first invalid character and would emit truncated bytes. Any other
353
357
  // string encodes as UTF8String (the decode does not preserve the original
354
358
  // string type, so this is the canonical re-encoding, not a byte-exact one).
355
359
  if (typeof value === "string" && value.charAt(0) === "\\") return asn1.build.utf8(value.slice(1));
@@ -371,17 +375,17 @@ function attrValueToString(ns) {
371
375
 
372
376
  // The always-escape specials of RFC 4514 sec. 2.4, plus its positional rules:
373
377
  // NUL escapes as \00, a trailing space as '\ ', and a leading '#' or space as
374
- // '\#' / '\ ' -- without them the rendered dn is ambiguous (a literal '#0500'
378
+ // '\#' / '\ '. Without them the rendered dn is ambiguous (a literal '#0500'
375
379
  // value would be indistinguishable from the hexstring form) and leading /
376
380
  // trailing spaces would be silently significant.
377
381
  // RFC 4514 sec. 2.4 attribute-value escaping is the single guard-name primitive
378
- // (pki.inspect reuses the dn this produces rather than re-escaping the already-
382
+ // (pki.inspect reuses the dn this produces and does not re-escape the already-
379
383
  // escaped value), so the display escaper can't diverge from a re-inline elsewhere.
380
384
  var _escapeDnValue = guard.name.escapeDnValue;
381
385
 
382
386
  // The dn RENDERING of a surfaced AttributeValue: the '#'-leading hex form (a
383
- // non-string value) renders bare -- escaping its '#' would turn the RFC 4514
384
- // hexstring form into an escaped literal -- while a genuine string (surfaced
387
+ // non-string value) renders bare, since escaping its '#' would turn the RFC 4514
388
+ // hexstring form into an escaped literal. A genuine string (surfaced
385
389
  // with its leading '#'/'\' escape, see attrValueToString) is unescaped back to
386
390
  // the semantic string and then rendered with the full escape set.
387
391
  function _dnDisplayValue(v) {
@@ -395,12 +399,12 @@ function _dnDisplayValue(v) {
395
399
  // sec. 5.1.2.4-5.1.2.6): a date through the year 2049 MUST be UTCTime and
396
400
  // GeneralizedTime is reserved for 2050 onward, so a GeneralizedTime carrying a
397
401
  // 1950..2049 date is a non-conforming second encoding of a representable value
398
- // and is rejected (<prefix>/bad-time) -- accepting it would let two wire forms
402
+ // and is rejected (<prefix>/bad-time); accepting it would let two wire forms
399
403
  // alias one abstract time (a parser-differential channel). The engine's
400
404
  // schema.time already applies the cutover on ENCODE; this leaf composes it and
401
405
  // enforces the same rule on DECODE. A GeneralizedTime-only field (an RFC 3161
402
- // genTime, a CRL invalidityDate, an RFC 5755 AttCertValidityPeriod) must NOT
403
- // take this leaf -- its syntax has no UTCTime alternative to cut over to.
406
+ // genTime, a CRL invalidityDate, an RFC 5755 AttCertValidityPeriod) must not
407
+ // take this leaf, whose syntax has no UTCTime alternative to cut over to.
404
408
  function time(ns) {
405
409
  var base = schema.time(ns);
406
410
  return schema.decode(function (n, ctx) {
@@ -432,10 +436,10 @@ function attributeTypeAndValue(ns) {
432
436
  });
433
437
  }
434
438
  function relativeDistinguishedName(ns) {
435
- // RelativeDistinguishedName ::= SET SIZE (1..MAX) -- an empty SET {} is malformed.
439
+ // RelativeDistinguishedName ::= SET SIZE (1..MAX), so an empty SET {} is malformed.
436
440
  return schema.setOf(attributeTypeAndValue(ns), { assert: "set", min: 1, code: ns.prefix + "/bad-rdn", what: "RelativeDistinguishedName" });
437
441
  }
438
- // opts.implicitTag (optional): read the Name as a [tag] IMPLICIT RDNSequence -- the
442
+ // opts.implicitTag (optional): read the Name as a [tag] IMPLICIT RDNSequence. The
439
443
  // context tag REPLACES the universal SEQUENCE tag, so the node is a context-class
440
444
  // constructed [tag] whose children ARE the RDN SETs (the CRMF CertTemplate issuer
441
445
  // [3] / subject [5] IMPLICIT arm, RFC 4211 sec. 5). With no opts the shape is a bare
@@ -456,9 +460,9 @@ function name(ns, opts) {
456
460
  parts.push(atvParts.join("+"));
457
461
  });
458
462
  // `dn` is a DISPLAY string, and its spelling is `openssl x509 -subject`: the RDNs in the
459
- // certificate's own order, joined by ", ". That is deliberately NOT the RFC 4514 / LDAP
463
+ // certificate's own order, joined by ", ". That is deliberately not the RFC 4514 / LDAP
460
464
  // distinguishedName, which reverses the RDNs and joins them with a bare comma (`openssl -nameopt
461
- // RFC2253`) -- so this string must never be handed to an LDAP client. What IS taken from RFC 4514
465
+ // RFC2253`), so this string must never be handed to an LDAP client. What IS taken from RFC 4514
462
466
  // is sec. 2.4 value escaping, so an attribute value holding a comma cannot read as two RDNs. The
463
467
  // interop gate pins this against openssl's own output. Identity never flows through this string:
464
468
  // guard.name.dnEqual compares the DER under RFC 5280 sec. 7.1, and `bytes` below carries the raw
@@ -475,28 +479,29 @@ function name(ns, opts) {
475
479
  }
476
480
 
477
481
  // GeneralName ::= CHOICE (RFC 5280 sec. 4.2.1.6). A validate-and-surface-raw leaf for a
478
- // caller that keeps the value RAW but needs it to be a well-formed GeneralName: it
482
+ // caller that keeps the value raw but needs it to be a well-formed GeneralName: it
479
483
  // checks the chosen alternative's tag, form (constructed vs primitive per X.690
480
- // sec. 10.2), and content -- otherName [0] is a SEQUENCE { type-id OID, value [0] EXPLICIT
484
+ // sec. 10.2), and content, then surfaces the value raw ({ bytes, tagClass, tagNumber }).
485
+ // The content rule per alternative: otherName [0] is a SEQUENCE { type-id OID, value [0] EXPLICIT
481
486
  // }; x400Address [3] / ediPartyName [5] are non-empty constructed; directoryName [4]
482
487
  // EXPLICIT wraps a valid Name; rfc822Name [1] / dNSName [2] / uniformResourceIdentifier
483
488
  // [6] are primitive non-empty IA5String (7-bit); iPAddress [7] is a 4- or 16-octet
484
- // OCTET STRING; registeredID [8] is a primitive OBJECT IDENTIFIER -- then surfaces the
485
- // value RAW ({ bytes, tagClass, tagNumber }). `opts.code` is the caller's error code
489
+ // OCTET STRING; registeredID [8] is a primitive OBJECT IDENTIFIER.
490
+ // `opts.code` is the caller's error code
486
491
  // (e.g. ocsp/bad-requestor-name, tsp/bad-tsa). Shared so the two parsers cannot drift.
487
492
  // opts.decodeValue (default false): in addition to the raw { bytes, tagClass,
488
- // tagNumber }, surface the DECODED `value` per arm -- IA5 text (string), an
493
+ // tagNumber }, surface the DECODED `value` per arm: IA5 text (string), an
489
494
  // iPAddress Buffer, a directoryName { rdns, dn }, a registeredID OID string,
490
495
  // or an otherName { typeId, valueBytes }. The path validator's name-constraint
491
496
  // matcher needs the decoded value; the tsp/ocsp/attrcert consumers pass no
492
497
  // flag and get the byte-identical raw-only shape.
493
498
  // opts.subtreeBase (default false): the GeneralSubtree.base form (RFC 5280
494
- // sec. 4.2.1.10) -- an iPAddress base is an address+mask (8 octets IPv4 / 32 IPv6),
495
- // NOT the 4/16-octet SAN address form. Only the iPAddress size rule changes.
499
+ // sec. 4.2.1.10). An iPAddress base is an address+mask (8 octets IPv4 / 32 IPv6),
500
+ // and not the 4/16-octet SAN address form. Only the iPAddress size rule changes.
496
501
  // A GeneralizedTime-only leaf. Several formats fix their times to
497
502
  // GeneralizedTime and reject UTCTime (OCSP RFC 6960, TSP RFC 3161 sec. 2.4.2,
498
503
  // CMP RFC 9810 sec. 5.1.1, attribute-certificate validity RFC 5755 sec. 4.2.6);
499
- // ONE ns-parameterized factory owns the tag assert so the per-format code and
504
+ // a single ns-parameterized factory owns the tag assert, so the per-format code and
500
505
  // message differ but the check cannot drift. opts.allowFractional enables the
501
506
  // X.690 sec. 11.7 fractional-seconds profile (the RFC 3161 genTime).
502
507
  function generalizedTime(ns, opts) {
@@ -529,8 +534,8 @@ function utf8Text(ns, opts) {
529
534
  // A raw NON-EMPTY universal SEQUENCE leaf -- surfaces the exact TLV bytes of an
530
535
  // embedded element (a Certificate, a CertificateList, a PKIPublicationInfo)
531
536
  // after asserting the SEQUENCE tag and at least one child. An empty SEQUENCE
532
- // (30 00) has the right tag but is none of those structures, so it rejects
533
- // rather than surfacing degenerate bytes to certificate processing.
537
+ // (30 00) has the right tag but is none of those structures, so it rejects and
538
+ // no degenerate bytes reach certificate processing.
534
539
  function rawNonEmptySequence(ns, opts) {
535
540
  opts = opts || {};
536
541
  var code = opts.code || (ns.prefix + "/bad-sequence");
@@ -543,8 +548,8 @@ function rawNonEmptySequence(ns, opts) {
543
548
  });
544
549
  }
545
550
 
546
- // CRLReason ::= ENUMERATED (RFC 5280 sec. 5.3.1) -- value 7 is unused/reserved.
547
- // The ONE canonical value->name table every consumer validates against: the
551
+ // CRLReason ::= ENUMERATED (RFC 5280 sec. 5.3.1). Value 7 is unused/reserved.
552
+ // The single canonical value->name table every consumer validates against: the
548
553
  // CRL reasonCode decoder surfaces the numeric code, the OCSP RevokedInfo
549
554
  // decoder the name; both draw the legal set from here so they cannot drift.
550
555
  // CRLReason ENUMERATED value names -- the single source is pki.C.NAMES.CRL_REASON
@@ -580,7 +585,7 @@ function generalName(ns, opts) {
580
585
  }
581
586
  if (decodeValue) value = { typeId: typeId, valueBytes: ov.children[0].bytes };
582
587
  } else if (t === 4) {
583
- // directoryName [4] EXPLICIT Name -- validate the wrapped RDNSequence.
588
+ // directoryName [4] EXPLICIT Name: validate the wrapped RDNSequence.
584
589
  if (n.children.length !== 1) throw ctx.E(code, "GeneralName directoryName [4] must wrap exactly one Name");
585
590
  var dnMatch = schema.walk(NAME, n.children[0], ctx);
586
591
  if (decodeValue) value = dnMatch.result;
@@ -589,7 +594,7 @@ function generalName(ns, opts) {
589
594
  if (constructed) throw ctx.E(code, "GeneralName [" + t + "] must be primitive (X.690 sec. 10.2)");
590
595
  if (GN_IA5[t]) {
591
596
  if (n.content.length === 0) throw ctx.E(code, "GeneralName [" + t + "] must be a non-empty IA5String");
592
- // 7-bit IA5, no C0/DEL control byte -- an embedded NUL/control in a
597
+ // 7-bit IA5, no C0/DEL control byte. An embedded NUL/control in a
593
598
  // dNSName/rfc822Name/URI enables a name-truncation bypass (CVE-2009-2408).
594
599
  guard.name.assertPrintableIa5(n.content, ctx.E, code, "GeneralName [" + t + "]");
595
600
  if (decodeValue) value = n.content.toString("latin1");
@@ -619,10 +624,10 @@ function generalName(ns, opts) {
619
624
  // element is validated by generalName (its CHOICE alternative's form + content) and
620
625
  // surfaced raw, so a caller carrying a GeneralNames field cannot accept a malformed
621
626
  // element by treating the whole sequence as opaque bytes. Returns { names, bytes }
622
- // where each `names[i]` is the generalName leaf's return -- { bytes, tagClass,
623
- // tagNumber } and, when opts.decodeValue is set, the decoded `value` -- and `bytes`
627
+ // where each `names[i]` is the generalName leaf's return: { bytes, tagClass,
628
+ // tagNumber } plus, when opts.decodeValue is set, the decoded `value`. `bytes`
624
629
  // is the raw outer DER.
625
- // `opts.implicitTag` handles a [tag] IMPLICIT GeneralNames -- the context tag REPLACES
630
+ // `opts.implicitTag` handles a [tag] IMPLICIT GeneralNames, where the context tag REPLACES
626
631
  // the universal SEQUENCE tag (RFC 5755 Holder.entityName [1]); otherwise it is a bare
627
632
  // universal SEQUENCE OF. `opts.code` is the caller's error code. Shared so the x509 /
628
633
  // attribute-certificate / (future) CRMF parsers validate a GeneralNames identically.
@@ -641,11 +646,11 @@ function generalNames(ns, opts) {
641
646
 
642
647
  // DistributionPointName ::= CHOICE { fullName [0] IMPLICIT GeneralNames,
643
648
  // nameRelativeToCRLIssuer [1] IMPLICIT RelativeDistinguishedName } (RFC 5280
644
- // sec. 4.2.1.13). The caller hands the CHOICE node itself -- the alternative
645
- // INSIDE the [0]-tagged distributionPoint field wrapper (a context tag on a
649
+ // sec. 4.2.1.13). The caller hands the CHOICE node itself: the alternative
650
+ // inside the [0]-tagged distributionPoint field wrapper (a context tag on a
646
651
  // CHOICE-typed field is always EXPLICIT, so the wire nests [0]{ [0]|[1] ... }).
647
- // fullName surfaces each element's RAW GeneralName DER as { kind: "fullName",
648
- // names: [Buffer, ...] } -- the byte-exact comparison key the sec. 5.2.5
652
+ // fullName surfaces each element's raw GeneralName DER as { kind: "fullName",
653
+ // names: [Buffer, ...] }, the byte-exact comparison key the sec. 5.2.5
649
654
  // "identical encoding MUST be used" correspondence rule requires;
650
655
  // nameRelativeToCRLIssuer surfaces its full [1]-tagged TLV as { kind: "rdn",
651
656
  // bytes: Buffer } (byte-identical [1] TLVs are byte-identical RDN fragments).
@@ -660,7 +665,7 @@ function distributionPointName(ns, node, code) {
660
665
  return { kind: "fullName", names: gns.names.map(function (n) { return n.bytes; }) };
661
666
  }
662
667
  if (node && node.tagClass === "context" && node.tagNumber === 1) {
663
- // [1] IMPLICIT RelativeDistinguishedName -- the context tag replaces the
668
+ // In [1] IMPLICIT RelativeDistinguishedName the context tag replaces the
664
669
  // SET tag, so the node's direct children are the AttributeTypeAndValues.
665
670
  schema.walk(schema.implicitSetOf(1, attributeTypeAndValue(ns), {
666
671
  min: 1, code: code, what: "DistributionPointName nameRelativeToCRLIssuer" }), node, ns);
@@ -670,15 +675,15 @@ function distributionPointName(ns, node, code) {
670
675
  }
671
676
 
672
677
  // A certificate's keyUsage as the named booleans the shared sec. 4.2.1 decoder produces, or null
673
- // when the certificate carries no keyUsage extension -- which places no restriction (sec. 4.2.1.3),
674
- // a distinct answer from "carries one that permits nothing".
678
+ // when the certificate carries no keyUsage extension. An absent extension places no restriction
679
+ // (sec. 4.2.1.3), a distinct answer from "carries one that permits nothing".
675
680
  //
676
681
  // Declared here because "may this certificate do X" is asked at five boundaries and the answer has
677
682
  // to be the same at all of them. keyUsage is a NamedBitList: DER drops its trailing zero bits
678
683
  // (X.690 sec. 11.2.2) and sec. 4.2.1.3 requires at least one bit set. A boundary that reads the
679
684
  // bits with a plain BIT STRING read applies neither, so the same certificate is authorized there
680
- // and rejected as malformed by the issuing side and the path validator -- one extension with two
681
- // readings, and the permissive one deciding.
685
+ // and rejected as malformed by the issuing side and the path validator. One extension gets two
686
+ // readings, and the permissive one decides.
682
687
  //
683
688
  // `E(code, message, cause)` is the caller's typed error factory, so each boundary keeps its own
684
689
  // domain while the RULE stays single-homed.
@@ -701,8 +706,8 @@ function keyUsageOf(ns, cert, E, code, label) {
701
706
  // onlySomeReasons [3] ReasonFlags OPTIONAL, indirectCRL [4] DEFAULT FALSE,
702
707
  // onlyContainsAttributeCerts [5] DEFAULT FALSE } (RFC 5280 sec. 5.2.5).
703
708
  //
704
- // Declared here rather than in a consumer because the extension answers a
705
- // question -- which certificates does this CRL speak for -- that both the path
709
+ // It is declared here and not in a consumer because the extension answers the
710
+ // question "which certificates does this CRL speak for", which both the path
706
711
  // validator and the standalone CRL verbs act on, and a scope flag read by a hand
707
712
  // walk over the raw children is read by the reader's own rules instead of the
708
713
  // encoding's. Each flag is an IMPLICIT BOOLEAN, so `implicitBoolean` is what
@@ -714,7 +719,7 @@ function keyUsageOf(ns, cert, E, code, label) {
714
719
  //
715
720
  // A present DEFAULT-FALSE flag encoding FALSE is well-formed at the leaf but is
716
721
  // still an encoding DER forbids (X.690 sec. 11.5 omits the default), so the
717
- // caller decides what that means for it -- reported as a value of `false`
722
+ // caller decides what that means for it. It is reported as a value of `false`
718
723
  // against a `present` of true, never silently normalized away.
719
724
  function issuingDistributionPoint(code) {
720
725
  return schema.seq([
@@ -744,7 +749,7 @@ var _T = asn1.TAGS;
744
749
 
745
750
  // assertPolicyQualifiers(qNode, fail) -- RFC 5280 sec. 4.2.1.4: a PolicyInformation's
746
751
  // policyQualifiers is a non-empty SEQUENCE OF PolicyQualifierInfo, each a SEQUENCE
747
- // { policyQualifierId OID, qualifier } of EXACTLY two members led by an OID. The
752
+ // { policyQualifierId OID, qualifier } of exactly two members led by an OID. The
748
753
  // structure is validated fail-closed; the qualifier body stays opaque (surfaced
749
754
  // raw by the caller). `fail(msg)` throws the caller's typed code. Shared by the
750
755
  // certificatePolicies extension decoder and the RFC 5035 ESS SigningCertificate
@@ -764,8 +769,8 @@ function assertPolicyQualifiers(qNode, fail) {
764
769
  }
765
770
 
766
771
  // DisplayText ::= CHOICE { ia5String, visibleString, bmpString, utf8String } each SIZE (1..200)
767
- // -- RFC 5280 sec. 4.2.1.4. The bound is deliberately NOT enforced here, and that is a normative
768
- // requirement rather than a gap: the same section closes with "While the explicitText has a maximum
772
+ // -- RFC 5280 sec. 4.2.1.4. The bound is deliberately not enforced here. That is a normative
773
+ // requirement and not a gap: the same section closes with "While the explicitText has a maximum
769
774
  // size of 200 characters, some non-conforming CAs exceed this limit. Therefore, certificate users
770
775
  // SHOULD gracefully handle explicitText with more than 200 characters." A decoder IS the certificate
771
776
  // user that note addresses, so refusing an over-long notice would violate a SHOULD and reject
@@ -804,11 +809,11 @@ function _isDisplayTextNode(n) {
804
809
  // Reading the content bytes directly would repair the value instead: `toString("utf8")` substitutes
805
810
  // U+FFFD for invalid sequences and a hand-rolled UCS-2 loop drops a trailing odd octet, so a caller
806
811
  // would render or measure text the certificate does not contain. Returns null when the value does
807
- // not decode, so the caller can take its own fallback rather than trust a repair.
812
+ // not decode, leaving the fallback to the caller.
808
813
  // An undecodable value yields an entry whose `text` and `chars` are null but whose `field` and
809
814
  // `tagNumber` are still present. The two are separable facts: which ASN.1 string type was used is
810
815
  // established by the tag alone, so a rule about the ENCODING stays answerable even when the contents
811
- // do not decode, while every rule that reads the TEXT must skip the entry rather than analyze a
816
+ // do not decode, while every rule that reads the TEXT must skip the entry and not analyze a
812
817
  // repair. Collapsing both into "not analyzable" would let a prohibited encoding escape its finding
813
818
  // by also being malformed inside.
814
819
  function _dtEntry(field, node) {
@@ -817,29 +822,29 @@ function _dtEntry(field, node) {
817
822
  try { text = asn1.read.string(node); } catch (_e) { text = null; }
818
823
  return { field: field, tagNumber: node.tagNumber, text: text, chars: text === null ? null : displayTextChars(text) };
819
824
  }
820
- // A NoticeReference names its notice by ORGANIZATION **and** NUMBER -- the organization alone does not
825
+ // A NoticeReference names its notice by ORGANIZATION **and** NUMBER. The organization alone does not
821
826
  // identify which notice is meant, so a consumer that surfaced only the text would lose the lookup key.
822
827
  // Returns null when the numbers are absent, wrongly shaped, or contain a member that is not a readable
823
- // INTEGER: an incompletely decoded reference must NOT be presentable, because dropping the members that
824
- // failed would render a partial reference indistinguishable from a whole one -- the same defect as
828
+ // INTEGER: an incompletely decoded reference must not be presentable, because dropping the members that
829
+ // failed would render a partial reference indistinguishable from a whole one. That is the same defect as
825
830
  // omitting the numbers entirely, in a form that is harder to notice. Numbers are decimal strings.
826
831
  function _noticeNumbers(node) {
827
832
  if (!node || node.tagClass !== "universal" || node.tagNumber !== _T.SEQUENCE || !node.children) return null;
828
833
  var nums = [], ok = true;
829
834
  node.children.forEach(function (n) {
830
- // allow:swallow-unverified a non-INTEGER member makes the whole reference undecodable; the caller falls back rather than rendering part of it
835
+ // allow:swallow-unverified a non-INTEGER member makes the whole reference undecodable, and the caller falls back
831
836
  try { nums.push(String(asn1.read.integer(n))); } catch (_e) { ok = false; }
832
837
  });
833
838
  return ok ? nums : null;
834
839
  }
835
- // Every DisplayText member is returned, including one whose contents did not decode -- that entry
840
+ // Every DisplayText member is returned, including one whose contents did not decode; that entry
836
841
  // carries its tag with a null `text`. A caller that RENDERS the notice must check that every entry
837
842
  // decoded before showing any of it (a partial notice is indistinguishable from a complete one); a
838
843
  // caller that only classifies the encoding can read the tags regardless.
839
844
  // The LAYOUT is validated before any member is read: UserNotice fixes the order (noticeRef first) and
840
845
  // the cardinality (each member at most once, nothing else present). Collecting every recognized member
841
- // and ignoring the rest would accept a duplicated explicitText, a reversed order, or an extra member,
842
- // and the caller -- which can only see whether the entries it got look complete -- would then present
846
+ // and ignoring the rest would accept a duplicated explicitText, a reversed order, or an extra member.
847
+ // The caller can only see whether the entries it got look complete, so it would then present
843
848
  // a structurally invalid notice as a whole one. A malformed layout yields NO entries, so a renderer
844
849
  // falls back to hex and a reporter measures nothing.
845
850
  function userNoticeTexts(qualifier) {
@@ -914,7 +919,7 @@ function certExtensionDecoders(ns) {
914
919
  if (kids[i] && kids[i].tagClass === "universal" && kids[i].tagNumber === _T.INTEGER) {
915
920
  if (!cA) throw ns.E(C, "BasicConstraints pathLenConstraint is only permitted when cA is TRUE (RFC 5280 sec. 4.2.1.9)");
916
921
  var pl = readInt(kids[i], C, "pathLenConstraint");
917
- // guard.range.uint31 bounds the decoded BigInt and narrows atomically -- a
922
+ // guard.range.uint31 bounds the decoded BigInt and narrows atomically. A
918
923
  // value past the safe-integer range would otherwise round silently and be
919
924
  // compared as a path-length ceiling.
920
925
  pathLen = guard.range.uint31(pl, ns.E, C, "BasicConstraints pathLenConstraint (RFC 5280 sec. 4.2.1.9)"); i++;
@@ -932,7 +937,7 @@ function certExtensionDecoders(ns) {
932
937
  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)");
933
938
  var bs;
934
939
  try { bs = asn1.read.bitString(n); } catch (e) { throw ns.E(C, "KeyUsage must be a well-formed BIT STRING", e); }
935
- // At least one bit MUST be set (RFC 5280 sec. 4.2.1.3) -- an all-zero value (a
940
+ // At least one bit MUST be set (RFC 5280 sec. 4.2.1.3), so an all-zero value (a
936
941
  // non-empty byte run of only zero bits, e.g. 03 02 07 00) is malformed too.
937
942
  var anyBit = false;
938
943
  for (var z = 0; z < bs.bytes.length; z++) { if (bs.bytes[z] !== 0) { anyBit = true; break; } }
@@ -946,7 +951,7 @@ function certExtensionDecoders(ns) {
946
951
  });
947
952
  // Any bit beyond the 9 named positions is a reserved/unknown usage. RFC 5280 tolerates it,
948
953
  // but a "MUST be the ONLY bit" rule (e.g. RFC 9935 sec. 5 ML-KEM keyEncipherment-only) needs
949
- // to see it -- so surface it rather than silently drop bits >= 9.
954
+ // to see it, so bits >= 9 are surfaced on `reservedBitsSet`.
950
955
  var reserved = false;
951
956
  for (var bit2 = KU_BITS.length; bit2 < bs.bytes.length * 8 && !reserved; bit2++) {
952
957
  if ((bs.bytes[bit2 >> 3] & (0x80 >> (bit2 & 7))) !== 0) reserved = true;
@@ -999,7 +1004,7 @@ function certExtensionDecoders(ns) {
999
1004
  var seen = {};
1000
1005
  return kids.map(function (pi) {
1001
1006
  // PolicyInformation ::= SEQUENCE { policyIdentifier, policyQualifiers
1002
- // SEQUENCE SIZE(1..MAX) OPTIONAL } -- exactly one or two fields; a second
1007
+ // SEQUENCE SIZE(1..MAX) OPTIONAL }. Exactly one or two fields; a second
1003
1008
  // field, if present, MUST be a SEQUENCE. Extra/mis-typed fields are malformed.
1004
1009
  if (pi.tagClass !== "universal" || pi.tagNumber !== _T.SEQUENCE || !pi.children || pi.children.length < 1 || pi.children.length > 2) {
1005
1010
  throw ns.E(C, "PolicyInformation must be a SEQUENCE { policyIdentifier, policyQualifiers? }");
@@ -1069,7 +1074,7 @@ function certExtensionDecoders(ns) {
1069
1074
  return guard.range.uint31(v, ns.E, C, "InhibitAnyPolicy skip count (RFC 5280 sec. 4.2.1.14)");
1070
1075
  }
1071
1076
 
1072
- // subjectAltName / issuerAltName ::= GeneralNames -- decoded values surfaced.
1077
+ // subjectAltName / issuerAltName ::= GeneralNames, with decoded values surfaced.
1073
1078
  function altName(buf) {
1074
1079
  var C = ns.prefix + "/bad-extension-value";
1075
1080
  var n = decodeTop(buf, C, "GeneralNames");
@@ -1080,7 +1085,7 @@ function certExtensionDecoders(ns) {
1080
1085
  // (RFC 5280 sec. 4.2.2.1); AccessDescription ::= SEQUENCE { accessMethod OBJECT IDENTIFIER, accessLocation
1081
1086
  // GeneralName }. Surfaces [{ accessMethod: <dotted OID>, accessLocation: { tag, value } }] in wire order --
1082
1087
  // accessLocation is the shared generalName leaf (its context tag number + decoded value, so a control-byte
1083
- // URI is rejected by the CVE-2009-2408 guard). BOTH accessMethods surface (id-ad-caIssuers AND id-ad-ocsp):
1088
+ // URI is rejected by the CVE-2009-2408 guard). Both accessMethods surface (id-ad-caIssuers AND id-ad-ocsp):
1084
1089
  // a consumer filters by accessMethod (caIssuers for issuer fetching, ocsp for responder discovery). An empty
1085
1090
  // SEQUENCE violates SIZE(1..MAX) and is malformed. A composed decoder + registry row, not a hand-roll.
1086
1091
  function authorityInfoAccess(buf) {
@@ -1149,7 +1154,7 @@ function certExtensionDecoders(ns) {
1149
1154
  // which owns the inner peel + the bounded TLS decode; structure is decoded, the
1150
1155
  // signature stays raw and unverified. The registry contract is that a decoder
1151
1156
  // throws a `<prefix>/bad-*` code, so the CT module's own `ct/*` fault is wrapped
1152
- // and carried as `.cause` rather than leaking a foreign namespace here.
1157
+ // and carried as `.cause`, so a foreign namespace never leaks out here.
1153
1158
  function sctList(buf) {
1154
1159
  var C = ns.prefix + "/bad-extension-value";
1155
1160
  try { return ct.parseSctList(buf); }
@@ -1172,13 +1177,13 @@ function certExtensionDecoders(ns) {
1172
1177
  // distributionPoint [0] DistributionPointName OPTIONAL, reasons [1]
1173
1178
  // ReasonFlags OPTIONAL, cRLIssuer [2] GeneralNames OPTIONAL }. "a
1174
1179
  // DistributionPoint MUST NOT consist of only the reasons field; either
1175
- // distributionPoint or cRLIssuer MUST be present" -- a reasons-only (or
1180
+ // distributionPoint or cRLIssuer MUST be present". A reasons-only (or
1176
1181
  // empty) DistributionPoint is malformed. The DistributionPointName is
1177
- // surfaced through the shared helper (raw name encodings -- the sec. 5.2.5
1182
+ // surfaced through the shared helper (raw name encodings, the sec. 5.2.5
1178
1183
  // correspondence key); `reasons` stays a raw BIT STRING ({ unusedBits,
1179
1184
  // bytes }); cRLIssuer is a validated GeneralNames with decoded values.
1180
- // FreshestCRL ::= CRLDistributionPoints (sec. 4.2.1.15), so the SAME decoder
1181
- // serves both OIDs -- one codec, both registry rows.
1185
+ // FreshestCRL ::= CRLDistributionPoints (sec. 4.2.1.15), so the same decoder
1186
+ // serves both OIDs: one codec, both registry rows.
1182
1187
  function crlDistributionPoints(buf) {
1183
1188
  var C = ns.prefix + "/bad-crl-distribution-points";
1184
1189
  var kids = seqChildren(buf, C, "CRLDistributionPoints");
@@ -1202,7 +1207,7 @@ function certExtensionDecoders(ns) {
1202
1207
  try { bs = asn1.read.bitStringImplicit(f, 1); }
1203
1208
  catch (e) { throw ns.E(C, "DistributionPoint reasons [1] must be an IMPLICIT ReasonFlags BIT STRING", e); }
1204
1209
  // ReasonFlags is a NamedBitList, so DER requires trailing zero bits be
1205
- // dropped (X.690 sec. 11.2.2) -- the same rule the keyUsage decoder
1210
+ // dropped (X.690 sec. 11.2.2), the same rule the keyUsage decoder
1206
1211
  // enforces above. Without it two different encodings of one reason set
1207
1212
  // both parse, and RFC 5280 sec. 6.3.3(d)(1) would intersect the CRL's
1208
1213
  // onlySomeReasons against a value the encoding rules forbid.
@@ -1247,9 +1252,9 @@ function certExtensionDecoders(ns) {
1247
1252
  }
1248
1253
  var nras = [];
1249
1254
  if (node.children[i]) {
1250
- // nameRegistrationAuthorities ::= SEQUENCE SIZE (1..MAX) OF GeneralName -- decode + fully validate
1255
+ // nameRegistrationAuthorities ::= SEQUENCE SIZE (1..MAX) OF GeneralName. Decode and fully validate
1251
1256
  // each element through the shared GeneralName decoder (context tag [0]..[8] + per-alternative
1252
- // structure), rather than surfacing arbitrary bytes as a name-registration authority.
1257
+ // structure); arbitrary bytes never surface as a name-registration authority.
1253
1258
  nras = schema.walk(generalNames(ns, { decodeValue: true, code: C }), node.children[i], ns).result.names;
1254
1259
  i++;
1255
1260
  }
@@ -1283,7 +1288,7 @@ function certExtensionDecoders(ns) {
1283
1288
  return {
1284
1289
  currency: currency,
1285
1290
  // MonetaryValue amount and exponent are unconstrained INTEGERs (the amount non-negative in practice);
1286
- // bound each only to the safe-integer range so it narrows to a Number losslessly -- an artificial
1291
+ // bound each only to the safe-integer range so it narrows to a Number losslessly. An artificial
1287
1292
  // tighter bound (uint31, or a small exponent window) would false-reject a valid MonetaryValue, and a
1288
1293
  // wider one would round silently and mis-state the limit.
1289
1294
  amount: guard.range.int(readInt(node.children[1], C, "QcLimitValue amount"), 0n, 9007199254740991n, ns.E, C, "QcLimitValue amount"),
@@ -1304,7 +1309,7 @@ function certExtensionDecoders(ns) {
1304
1309
  }) };
1305
1310
  };
1306
1311
  // ETSI EN 319 412-5 V2.5.1 additions: QcIdentMethod (SEQUENCE OF OID, like QcType) and
1307
- // QcQSCDlegislation (SEQUENCE OF CountryName, like QcCClegislation) -- registered ids, decoded.
1312
+ // QcQSCDlegislation (SEQUENCE OF CountryName, like QcCClegislation). Both are registered ids, decoded.
1308
1313
  qcInfoByOid[O("qcIdentMethod")] = function (node, C) {
1309
1314
  var oids = _qcOidSeq(node, C, "QcIdentMethod");
1310
1315
  return { methods: oids, methodNames: oids.map(function (d) { return ns.oid.name(d) || null; }) };
@@ -1333,9 +1338,9 @@ function certExtensionDecoders(ns) {
1333
1338
  return out;
1334
1339
  }
1335
1340
 
1336
- // [MS-WCCE] szOID-CERTIFICATE_TEMPLATE -- CertificateTemplateOID ::= SEQUENCE {
1341
+ // [MS-WCCE] szOID-CERTIFICATE_TEMPLATE. CertificateTemplateOID ::= SEQUENCE {
1337
1342
  // templateID OID, templateMajorVersion INTEGER OPTIONAL, templateMinorVersion INTEGER OPTIONAL }.
1338
- // The two versions are DWORDs (0..2^32-1), NOT uint31 -- a CA key/template revision with bit 31
1343
+ // The two versions are DWORDs (0..2^32-1) and not uint31: a CA key/template revision with bit 31
1339
1344
  // set is a legal DWORD, so bound to the whole safe-integer range, never guard.range.uint31.
1340
1345
  function msCertificateTemplate(buf) {
1341
1346
  var C = ns.prefix + "/bad-ms-certificate-template";
@@ -1352,7 +1357,7 @@ function certExtensionDecoders(ns) {
1352
1357
  return { templateID: id, name: ns.oid.name(id) || null, templateMajorVersion: ver(1, "templateMajorVersion"), templateMinorVersion: ver(2, "templateMinorVersion") };
1353
1358
  }
1354
1359
 
1355
- // [MS-CRTD] szOID-ENROLL_CERTTYPE_EXTENSION -- the legacy v1 template name as a BARE BMPString.
1360
+ // [MS-CRTD] szOID-ENROLL_CERTTYPE_EXTENSION carries the legacy v1 template name as a BARE BMPString.
1356
1361
  // asn1.read.string accepts any universal string tag, so the explicit BMP_STRING assert is the guard.
1357
1362
  function msEnrollCertType(buf) {
1358
1363
  var C = ns.prefix + "/bad-ms-enroll-cert-type";
@@ -1361,7 +1366,7 @@ function certExtensionDecoders(ns) {
1361
1366
  try { return asn1.read.string(n); } catch (e) { throw ns.E(C, "the enroll cert-type name must be a well-formed BMPString", e); }
1362
1367
  }
1363
1368
 
1364
- // [MS-WCCE] szOID-CERTSRV_CA_VERSION -- INTEGER (0..2^32-1) = (caKeyIndex << 16) | certIndex.
1369
+ // [MS-WCCE] szOID-CERTSRV_CA_VERSION is an INTEGER (0..2^32-1) = (caKeyIndex << 16) | certIndex.
1365
1370
  function msCaVersion(buf) {
1366
1371
  var C = ns.prefix + "/bad-ms-ca-version";
1367
1372
  var n = decodeTop(buf, C, "CACertVersion");
@@ -1370,7 +1375,7 @@ function certExtensionDecoders(ns) {
1370
1375
  return { caVersion: v, caKeyIndex: v >>> 16, certIndex: v & 0xffff };
1371
1376
  }
1372
1377
 
1373
- // [MS-WCCE] szOID-CERTSRV_PREVIOUS_CERT_HASH -- the SHA-1 thumbprint of the prior CA certificate.
1378
+ // [MS-WCCE] szOID-CERTSRV_PREVIOUS_CERT_HASH holds the SHA-1 thumbprint of the prior CA certificate.
1374
1379
  // AD CS stamps the Windows certificate THUMBPRINT here, which Windows computes with SHA-1 regardless
1375
1380
  // of the certificate's signature algorithm, so the OCTET STRING is exactly 20 octets. An empty or
1376
1381
  // other-length value is malformed renewal metadata and fails closed.
@@ -1418,7 +1423,7 @@ function certExtensionDecoders(ns) {
1418
1423
  // and the RFC 5280 sec. 4.2 per-OID uniqueness.
1419
1424
  function extension(ns) {
1420
1425
  return schema.decode(function (ext) {
1421
- // Extension ::= SEQUENCE { extnID, critical DEFAULT FALSE, extnValue } -- a
1426
+ // Extension ::= SEQUENCE { extnID, critical DEFAULT FALSE, extnValue } is a
1422
1427
  // UNIVERSAL SEQUENCE of exactly 2 (critical omitted) or 3 children. A
1423
1428
  // context-tagged item (e.g. [5]{OID, OCTET STRING}) or a wrong child count
1424
1429
  // is malformed; assert the tag, don't just count children (fail closed).
@@ -1441,7 +1446,7 @@ function extension(ns) {
1441
1446
  });
1442
1447
  }
1443
1448
  // opts.implicitTag (optional): read Extensions as a [tag] IMPLICIT SEQUENCE OF
1444
- // Extension -- the context tag REPLACES the universal SEQUENCE tag, for the CRMF
1449
+ // Extension, where the context tag REPLACES the universal SEQUENCE tag, for the CRMF
1445
1450
  // CertTemplate extensions [9] (RFC 4211 sec. 5). With no opts the shape is a bare
1446
1451
  // universal SEQUENCE OF, byte-identical to every existing caller.
1447
1452
  function extensions(ns, opts) {
@@ -1458,8 +1463,8 @@ function extensions(ns, opts) {
1458
1463
 
1459
1464
  // SubjectPublicKeyInfo ::= SEQUENCE { algorithm AlgorithmIdentifier,
1460
1465
  // subjectPublicKey BIT STRING } (RFC 5280 sec. 4.1.2.7, RFC 2986 sec. 4.1). Asserted as a
1461
- // universal SEQUENCE -- a context-tagged or SET-tagged constructed node carrying
1462
- // two well-formed children is NOT a SubjectPublicKeyInfo. Shared by the
1466
+ // universal SEQUENCE, so a context-tagged or SET-tagged constructed node carrying
1467
+ // two well-formed children is not a SubjectPublicKeyInfo. Shared by the
1463
1468
  // certificate and CSR parsers.
1464
1469
  // opts.implicitTag (optional): read the SubjectPublicKeyInfo as a [tag] IMPLICIT
1465
1470
  // SEQUENCE (a context-class constructed node whose children are algorithm +
@@ -1479,7 +1484,7 @@ function spki(ns, opts) {
1479
1484
  publicKey: { unusedBits: m.fields.subjectPublicKey.value.unusedBits, bytes: m.fields.subjectPublicKey.value.bytes },
1480
1485
  // `bytes` is the importable SubjectPublicKeyInfo DER. In IMPLICIT [tag]
1481
1486
  // mode the wire node leads with the context tag, so recover the SEQUENCE
1482
- // encoding a consumer imports / hashes rather than the [tag] wire form.
1487
+ // encoding a consumer imports / hashes, and not the [tag] wire form.
1483
1488
  bytes: opts.implicitTag != null ? asn1.sequenceTlv(m.node) : m.node.bytes,
1484
1489
  };
1485
1490
  },
@@ -1487,7 +1492,7 @@ function spki(ns, opts) {
1487
1492
  }
1488
1493
 
1489
1494
  // Attribute ::= SEQUENCE { type OBJECT IDENTIFIER, values SET OF AttributeValue }.
1490
- // AttributeValue is ANY, kept as raw DER (node.bytes) -- an unrecognized attribute
1495
+ // AttributeValue is ANY, kept as raw DER (node.bytes), so an unrecognized attribute
1491
1496
  // type never fails the parse. values is SET SIZE (1..MAX) by default: an empty SET
1492
1497
  // is rejected; there is NO SET-OF uniqueness. Shared by the CSR (requested
1493
1498
  // attributes, RFC 2986 sec. 4.1) and PKCS#8 (private-key attributes, RFC 5958 sec. 2).
@@ -1546,8 +1551,8 @@ function rootSequenceChildren(root, minLen, maxLen) {
1546
1551
  // bound parser so a format declares its configuration once and never re-writes the
1547
1552
  // coerce -> decode -> walk wrapper. `opts`: { pemLabel, PemError, ErrorClass,
1548
1553
  // prefix, what, topSchema, ns }.
1549
- // The top-level schema's reject code -- the verdict for a DER that is not this
1550
- // structure at all -- is `<prefix>/not-a-<structure>` (x509/not-a-certificate,
1554
+ // The top-level schema's reject code, the verdict for a DER that is not this
1555
+ // structure at all, is `<prefix>/not-a-<structure>` (x509/not-a-certificate,
1551
1556
  // cms/not-a-content-info, ...). Three codes predate that convention and stay
1552
1557
  // frozen on the public error-code surface (ocsp/bad-ocsp-response,
1553
1558
  // tsp/bad-response, crmf/bad-cert-req-messages); a new format follows the
@@ -1558,10 +1563,10 @@ function makeParser(opts) {
1558
1563
 
1559
1564
  // makeRecordingParser(opts, kind) -> a parser that also records the bytes it read.
1560
1565
  //
1561
- // The pairing is here rather than repeated per format because it is one decision, not four: a
1566
+ // The pairing lives here and is not repeated per format, because it is one decision, not four: a
1562
1567
  // structure whose parse feeds a VERDICT needs its fields bound to the byte string they came from,
1563
1568
  // since a parsed structure presents the signed range, the signature and the fields that range
1564
- // encodes as separate properties -- and a caller can hand back a genuine range beside substituted
1569
+ // encodes as separate properties, and a caller can hand back a genuine range beside substituted
1565
1570
  // fields. `kind` is the tag the matching door re-derives under, so a certificate cannot be presented
1566
1571
  // where a CRL is expected. The record lives in guard-parsed, off the object, unreachable through it.
1567
1572
  function makeRecordingParser(opts, kind) {
@@ -1576,10 +1581,10 @@ function makeRecordingParser(opts, kind) {
1576
1581
  // shape, the arity, the signature extraction and the raw tbs / outer-signature
1577
1582
  // bytes (for the cert/CRL outer==inner agreement check) are owned here once, and
1578
1583
  // each format's `opts.build(envelope, ctx)` shapes its own object from the
1579
- // envelope. A CSR's build simply omits the agreement check -- its CRI has no inner
1580
- // signature AlgorithmIdentifier -- so the omission is structural, not a copy that
1584
+ // envelope. A CSR's build simply omits the agreement check, since its CRI has no inner
1585
+ // signature AlgorithmIdentifier. The omission is structural, not a copy that
1581
1586
  // forgot a guard.
1582
- // signatureValue is surfaced RAW as { unusedBits, bytes }: for the envelope
1587
+ // signatureValue is surfaced raw as { unusedBits, bytes }: for the envelope
1583
1588
  // family, octet alignment is a verify-layer rule (path-validate rejects
1584
1589
  // unusedBits != 0 for certification paths; an external verifier must do the
1585
1590
  // same). A format with no in-tree verify layer (OCSP, CMP protection, CRMF