@blamejs/pki 0.6.14 → 0.6.16

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 (112) hide show
  1. package/CHANGELOG.md +27 -1
  2. package/lib/acme.js +59 -815
  3. package/lib/asn1-der.js +7 -245
  4. package/lib/attrcert-sign.js +12 -260
  5. package/lib/attrcert-verify.js +1 -7
  6. package/lib/byte-reader.js +0 -8
  7. package/lib/byte-writer.js +0 -11
  8. package/lib/cbor-det.js +0 -54
  9. package/lib/cmc-build.js +15 -357
  10. package/lib/cmc-verify.js +6 -239
  11. package/lib/cmp-build.js +41 -317
  12. package/lib/cmp-session.js +73 -760
  13. package/lib/cmp-verify.js +32 -488
  14. package/lib/cms-compress.js +3 -32
  15. package/lib/cms-decrypt.js +29 -294
  16. package/lib/cms-encrypt.js +14 -234
  17. package/lib/cms-sign.js +29 -324
  18. package/lib/cms-verify.js +9 -500
  19. package/lib/composite-kem.js +2 -102
  20. package/lib/composite-sig.js +1 -77
  21. package/lib/constants.js +0 -249
  22. package/lib/crl-sign.js +9 -237
  23. package/lib/crl-verify.js +1 -10
  24. package/lib/crmf-sign.js +19 -198
  25. package/lib/crmf-verify.js +1 -12
  26. package/lib/csr-sign.js +3 -34
  27. package/lib/csr-verify.js +1 -12
  28. package/lib/ct.js +28 -253
  29. package/lib/edwards-point.js +10 -44
  30. package/lib/est.js +28 -764
  31. package/lib/framework-error.js +4 -182
  32. package/lib/guard-all.js +1 -58
  33. package/lib/guard-async.js +1 -36
  34. package/lib/guard-bytes.js +6 -650
  35. package/lib/guard-compress.js +5 -102
  36. package/lib/guard-crypto.js +1 -40
  37. package/lib/guard-der.js +2 -93
  38. package/lib/guard-encoding.js +2 -60
  39. package/lib/guard-header.js +1 -36
  40. package/lib/guard-identifier.js +7 -531
  41. package/lib/guard-intrinsic.js +1 -150
  42. package/lib/guard-json.js +10 -73
  43. package/lib/guard-limits.js +1 -77
  44. package/lib/guard-list.js +1 -49
  45. package/lib/guard-name.js +3 -209
  46. package/lib/guard-parsed.js +7 -335
  47. package/lib/guard-range.js +1 -71
  48. package/lib/guard-secret.js +1 -102
  49. package/lib/guard-text.js +1 -64
  50. package/lib/guard-time.js +1 -97
  51. package/lib/hpke.js +3 -105
  52. package/lib/http-digest.js +14 -142
  53. package/lib/http-retry-after.js +1 -47
  54. package/lib/http-transport.js +21 -131
  55. package/lib/inspect.js +30 -284
  56. package/lib/ip-utils.js +6 -47
  57. package/lib/jose.js +7 -142
  58. package/lib/key.js +2 -67
  59. package/lib/lint.js +9 -194
  60. package/lib/merkle.js +1 -27
  61. package/lib/mime.js +9 -82
  62. package/lib/ocsp-request-verify.js +1 -14
  63. package/lib/ocsp-verify.js +1 -82
  64. package/lib/ocsp.js +11 -169
  65. package/lib/oid.js +5 -337
  66. package/lib/path-validate.js +103 -1492
  67. package/lib/pbes2.js +2 -89
  68. package/lib/pkcs12-build.js +31 -379
  69. package/lib/pki-build.js +64 -290
  70. package/lib/rc2.js +2 -18
  71. package/lib/rfc3339.js +4 -27
  72. package/lib/scep.js +140 -286
  73. package/lib/schema-all.js +0 -122
  74. package/lib/schema-attrcert.js +4 -146
  75. package/lib/schema-c509.js +153 -802
  76. package/lib/schema-cmc.js +5 -220
  77. package/lib/schema-cmp.js +2 -223
  78. package/lib/schema-cms.js +13 -427
  79. package/lib/schema-crl.js +5 -68
  80. package/lib/schema-crmf.js +6 -167
  81. package/lib/schema-csr.js +1 -54
  82. package/lib/schema-csrattrs.js +2 -87
  83. package/lib/schema-engine.js +0 -152
  84. package/lib/schema-ocsp.js +0 -117
  85. package/lib/schema-pkcs12.js +2 -93
  86. package/lib/schema-pkcs8.js +3 -35
  87. package/lib/schema-pkix.js +24 -655
  88. package/lib/schema-smime.js +0 -54
  89. package/lib/schema-tsp.js +3 -99
  90. package/lib/schema-x509.js +4 -63
  91. package/lib/shbs.js +11 -95
  92. package/lib/sign-scheme.js +2 -97
  93. package/lib/sigstore.js +9 -169
  94. package/lib/sleep.js +2 -8
  95. package/lib/smime.js +15 -408
  96. package/lib/tls-cert-compress.js +0 -62
  97. package/lib/trust.js +19 -185
  98. package/lib/tsp-sign.js +16 -270
  99. package/lib/validator-all.js +1 -30
  100. package/lib/validator-attcert.js +2 -46
  101. package/lib/validator-cose.js +2 -153
  102. package/lib/validator-keydesc.js +1 -37
  103. package/lib/validator-sig.js +1 -46
  104. package/lib/validator-tls.js +1 -88
  105. package/lib/validator-tpm.js +19 -122
  106. package/lib/verify-seam.js +1 -24
  107. package/lib/webauthn-mds.js +3 -400
  108. package/lib/webauthn.js +12 -833
  109. package/lib/webcrypto.js +5 -485
  110. package/lib/x509-sign.js +4 -108
  111. package/package.json +1 -1
  112. package/sbom.cdx.json +6 -6
package/lib/inspect.js CHANGED
@@ -35,23 +35,15 @@ var schemaAll = require("./schema-all");
35
35
  var pkix = require("./schema-pkix");
36
36
  var guard = require("./guard-all");
37
37
 
38
- // Display-naming conventions are centralized in pki.C.NAMES (shared with the strict
39
- // parsers so the labels can't drift); this module only composes them.
40
38
  var NAMES = constants.NAMES;
41
39
 
42
40
  var InspectError = frameworkError.InspectError;
43
41
  function _err(code, message, cause) { return new InspectError(code, message, cause); }
44
42
 
45
- // A dedicated namespace + decoder set: the shared RFC 5280 extension decoders,
46
- // composed exactly as path-validate / acme compose them. Decode failures here are
47
- // caught by the renderer and fall back to a hex dump (inspection is best-effort).
48
43
  var NS = pkix.makeNS("inspect", InspectError, oid);
49
44
  var EXT_DECODERS = pkix.certExtensionDecoders(NS).byOid;
50
- // Dispatch on the stable OID, not the display name: pki.oid.register() can override a built-in
51
- // name, which would silently skip the userNotice rendering and hex-dump the notice instead.
52
45
  var OID_UNOTICE = oid.byName("unotice");
53
46
 
54
- // ---- formatting helpers ------------------------------------------------------
55
47
 
56
48
  var HEX = "0123456789abcdef";
57
49
  function _hexColon(buf, opts) {
@@ -62,7 +54,6 @@ function _hexColon(buf, opts) {
62
54
  hex.push(opts.upper ? s.toUpperCase() : s);
63
55
  }
64
56
  if (!opts.wrap) return hex.join(":");
65
- // Wrap at `opts.wrap` bytes per line, each line indented by `opts.indent`.
66
57
  var pad = " ".repeat(opts.indent || 0), lines = [];
67
58
  for (var j = 0; j < hex.length; j += opts.wrap) {
68
59
  var chunk = hex.slice(j, j + opts.wrap).join(":");
@@ -71,31 +62,15 @@ function _hexColon(buf, opts) {
71
62
  return lines.join("\n");
72
63
  }
73
64
 
74
- // Coverage residual: two _hexColon default arms are unreachable through the public API.
75
- // * `opts = opts || {}`. All call sites pass an explicit opts object literal, so the
76
- // `|| {}` default never fires.
77
- // * `" ".repeat(opts.indent || 0)`. Every wrap-mode caller passes a positive indent
78
- // (pad.length + 8 >= 8, or inner.length == 16), so `opts.indent` is never falsy.
79
65
 
80
- // Control-byte neutralization for a GeneralName string value routes through the
81
- // guard family (the guard-shape-reinlined detector protects the shape).
82
66
  // @guard-via guard\.name\.escape
83
67
  var _clean = guard.name.escapeControlBytes;
84
68
 
85
- // The DN display string. pki.schema.pkix already assembles a fully RFC 4514-escaped
86
- // dn (short names from pki.C.NAMES.DN_SHORT, values escaped via guard.name.escapeDnValue,
87
- // with the '#'-hex / leading-'\' sentinel handled), so reuse it and never re-escape
88
- // the already-escaped values (which would double-escape a leading '#' / '\').
89
69
  function _dnString(name) { return (name && name.dn) || ""; }
90
70
 
91
71
  var MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
92
72
  function _two(n) { return (n < 10 ? "0" : "") + n; }
93
- // OpenSSL date: "Jul 4 07:00:27 2026 GMT" (month, space-padded day, time, year, GMT).
94
73
  function _date(iso) {
95
- // Rebuilt from the instant the value holds, so every field below is read off a Date of this
96
- // realm. The calendar methods are ordinary ones a subclass answers, and a parsed structure can
97
- // carry a Date a caller supplied, so reading them off that value let it decide the date printed
98
- // beside a certificate rather than the one the certificate carries.
99
74
  var held = guard.time.isDate(iso) ? guard.time.instantOf(iso) : Date.parse(String(iso));
100
75
  if (isNaN(held)) return String(iso);
101
76
  var d = new Date(held);
@@ -107,18 +82,12 @@ function _date(iso) {
107
82
 
108
83
  function _algName(a) { return (a && (a.name || a.oid)) || "unknown"; }
109
84
 
110
- // ---- serial + public key -----------------------------------------------------
111
85
 
112
86
  function _serial(cert, indent) {
113
87
  var hex = cert.serialNumberHex || "";
114
88
  if (hex.length % 2) hex = "0" + hex;
115
89
  var buf = Buffer.from(hex, "hex");
116
- // Strip a single DER positive-sign 00 byte (present when the value's high bit is
117
- // set) so the printed serial is the integer value, matching OpenSSL, and not the
118
- // encoding's leading octet.
119
90
  if (buf.length > 1 && buf[0] === 0x00 && (buf[1] & 0x80)) buf = buf.subarray(1);
120
- // Small non-negative serials render inline as decimal (0xhex), like OpenSSL;
121
- // anything larger renders as a colon-hex block.
122
91
  if (buf.length <= 6) {
123
92
  var n = parseInt(buf.toString("hex") || "0", 16);
124
93
  return "Serial Number: " + n + " (0x" + (_stripLeadingZeros(buf.toString("hex")) || "0") + ")";
@@ -128,9 +97,6 @@ function _serial(cert, indent) {
128
97
 
129
98
  var CURVE_BITS = { "P-256": 256, "P-384": 384, "P-521": 521, "prime256v1": 256, "secp384r1": 384, "secp521r1": 521 };
130
99
  var NIST_NAME = NAMES.NIST_CURVE;
131
- // Every RSA-family key algorithm carries the same SPKI subjectPublicKey, an
132
- // RSAPublicKey SEQUENCE { modulus, publicExponent } (RFC 4055 sec. 1.2 for
133
- // id-RSASSA-PSS / id-RSAES-OAEP), so all decode to modulus + exponent, not raw bytes.
134
100
  var RSA_KEY_ALGS = { rsaEncryption: 1, rsassaPss: 1, rsaesOaep: 1 };
135
101
  function _keyBlock(spki, pad) {
136
102
  var algName = _algName(spki.algorithm);
@@ -141,7 +107,7 @@ function _keyBlock(spki, pad) {
141
107
  if (algName === "ecPublicKey" || algName === "id-ecPublicKey") {
142
108
  var curveName = null;
143
109
  try { curveName = oid.name(asn1.read.oid(asn1.decode(spki.algorithm.parameters))); }
144
- catch (_e) { /* unknown curve */ }
110
+ catch (_e) { }
145
111
  var bits = CURVE_BITS[curveName] || (pub ? ((pub.length - 1) / 2) * 8 : 0);
146
112
  out.push(inner + "Public-Key: (" + bits + " bit)");
147
113
  if (pub) { out.push(inner + "pub:"); out.push(_hexColon(pub, { wrap: 16, indent: (pad.length + 8) })); }
@@ -150,39 +116,29 @@ function _keyBlock(spki, pad) {
150
116
  }
151
117
  if (RSA_KEY_ALGS[algName]) {
152
118
  try {
153
- var rsa = asn1.decode(pub); // RSAPublicKey ::= SEQUENCE { modulus INTEGER, publicExponent INTEGER }
119
+ var rsa = asn1.decode(pub);
154
120
  var modBig = asn1.read.integer(rsa.children[0]);
155
121
  var expBig = asn1.read.integer(rsa.children[1]);
156
122
  var modHex = modBig.toString(16); if (modHex.length % 2) modHex = "0" + modHex;
157
123
  var modBuf = Buffer.from(modHex, "hex");
158
- // Bit length is the value's, not the byte count's (a 0x7f.. modulus is 127-bit,
159
- // not 128), and the DER sign-padding 00 is present only when the top bit is set.
160
124
  out.push(inner + "Public-Key: (" + modBig.toString(2).length + " bit)");
161
125
  out.push(inner + "Modulus:");
162
126
  var modDisplay = (modBuf.length && (modBuf[0] & 0x80)) ? Buffer.concat([Buffer.from([0x00]), modBuf]) : modBuf;
163
127
  out.push(_hexColon(modDisplay, { wrap: 16, indent: (pad.length + 8) }));
164
128
  out.push(inner + "Exponent: " + expBig.toString(10) + " (0x" + expBig.toString(16) + ")");
165
129
  return out.join("\n");
166
- } catch (_e) { /* fall through to raw */ }
130
+ } catch (_e) { }
167
131
  }
168
- // EdDSA / ML-DSA / SLH-DSA / anything else: show the raw public-key bytes.
169
132
  if (pub) { out.push(inner + "Public-Key: (" + (pub.length * 8) + " bit)"); out.push(inner + "pub:"); out.push(_hexColon(pub, { wrap: 16, indent: (pad.length + 8) })); }
170
133
  return out.join("\n");
171
134
  }
172
135
 
173
- // ---- extensions --------------------------------------------------------------
174
136
 
175
- // Display-naming conventions from pki.C.NAMES (see constants.js); an entry absent
176
- // from a table falls back to the registered name / OID.
177
137
  var EXT_LABEL = NAMES.EXTENSION;
178
138
  var KU_LABEL = NAMES.KEY_USAGE;
179
139
  var EKU_LABEL = NAMES.EXT_KEY_USAGE;
180
140
  var GN_KIND = NAMES.GENERAL_NAME;
181
141
 
182
- // An iPAddress octet string -> a readable address, matching OpenSSL: 4 bytes as a
183
- // dotted-quad, 16 as (non-compressed, uppercase) IPv6, and the name-constraints
184
- // 8/32-byte address+mask forms as "addr/mask". Never emits a raw octet (a stray
185
- // 0x0a would inject a newline into the report); an odd length falls back to hex.
186
142
  function _ipString(buf) {
187
143
  if (!Buffer.isBuffer(buf)) return "";
188
144
  if (buf.length === 4) return buf[0] + "." + buf[1] + "." + buf[2] + "." + buf[3];
@@ -196,18 +152,6 @@ function _ipString(buf) {
196
152
  return _hexColon(buf, {});
197
153
  }
198
154
 
199
- // Format a decoded GeneralName ({tagClass, tagNumber, value, bytes}) the way
200
- // OpenSSL labels each choice. A directoryName carries a full DN (RFC 4514-escaped
201
- // via _dnString); an iPAddress is a Buffer rendered as an address (never raw bytes);
202
- // an otherName / unknown choice falls back to hex so a hostile value can never break
203
- // the report's line structure. The string choices (DNS / URI / email) are
204
- // control-byte-escaped -- which stops the severe case, a forged report line -- but
205
- // their structural separators are left as-is, matching OpenSSL (a GeneralName has no
206
- // RFC 4514-equivalent escaping profile, and escaping a legitimate comma in a URI
207
- // would misrepresent it).
208
- // The DN of a decoded directoryName GeneralName value. Shared so every renderer that meets this
209
- // form (the SAN/AKI GeneralName path and the AIA/SIA accessLocation path, which carry different
210
- // decoded shapes) prints the same DN, with neither falling back to a bare tag number.
211
155
  function _gnDn(value) {
212
156
  return (value && Array.isArray(value.rdns)) ? _dnString(value) : ((value && value.dn) || "");
213
157
  }
@@ -224,8 +168,6 @@ function _gn(g) {
224
168
  return kind + ":" + v;
225
169
  }
226
170
 
227
- // Format a GeneralName still in its raw DER TLV (a CRL distribution point leaves
228
- // its fullName entries undecoded): decode the context tag and render its content.
229
171
  function _gnRaw(buf) {
230
172
  if (!Buffer.isBuffer(buf)) return "";
231
173
  try {
@@ -233,35 +175,12 @@ function _gnRaw(buf) {
233
175
  var t = node.tagNumber;
234
176
  if (t === 1 || t === 2 || t === 6) return GN_KIND[t] + ":" + _clean(node.content.toString("latin1"));
235
177
  if (t === 7) return "IP Address:" + _ipString(node.content);
236
- return _hexColon(buf, {}); // dirName / otherName / registeredID -> best-effort hex
178
+ return _hexColon(buf, {});
237
179
  // allow:swallow-unverified drop-silent display fallback (tier-3): fullName GNs reach here as
238
- // schema-validated TLVs so asn1.decode cannot throw -- an unreachable defensive best-effort hex render.
239
180
  } catch (_e) { return _hexColon(buf, {}); }
240
181
  }
241
182
 
242
- // Coverage residual: the GeneralName render fallbacks are unreachable because the strict
243
- // generalName decoder and each caller already narrow the shape (the _gnRaw catch above is
244
- // separately documented).
245
- // * _ipString `if (!Buffer.isBuffer(buf)) return "";`. Every caller passes a Buffer (the
246
- // recursion slices a Buffer, _gn guards Buffer.isBuffer(g.value), _gnRaw passes
247
- // asn1.decode(...).content).
248
- // * _ipString trailing `return _hexColon(buf, {});`. generalName enforces iPAddress to
249
- // 4/16 octets (8/32 for a name-constraints subtree base), so length is only ever 4/8/16/32.
250
- // * _gn `if (!g || typeof g !== "object") return "";`. Every _gn call maps a decoder-produced
251
- // GeneralName object; the decoders never emit a null element.
252
- // * _gn directoryName `: ((g.value && g.value.dn) || "")`. A decoded directoryName [4] always
253
- // carries a Name with an rdns array, so the rdns arm is always taken.
254
- // * _gn otherName `: "<unsupported>"`. A decoded GeneralName always carries its raw bytes TLV.
255
- // * _gn `|| ("tag" + t)`. GeneralName CHOICE tags are 0..8, all mapped in NAMES.GENERAL_NAME;
256
- // generalName rejects a tag outside 0..8.
257
- // * _gn `? _hexColon(g.value, {})`. iPAddress [7] is the only choice whose decoded value is a
258
- // Buffer, handled at the t === 7 branch before this ternary; other tags' value is string or null.
259
- // * _gn trailing `: ""`. A decoded GeneralName always carries a bytes Buffer, so the
260
- // Buffer.isBuffer(g.bytes) arm is always taken.
261
- // * _gnRaw `if (!Buffer.isBuffer(buf)) return "";`. The sole caller (the CRL-DP fullName loop)
262
- // guards Buffer.isBuffer(nm) before calling _gnRaw.
263
183
 
264
- // Shared value renderers reused by more than one extension key.
265
184
  function _renderAltName(decoded, inner) {
266
185
  return inner + (decoded.names || []).map(_gn).join(", ");
267
186
  }
@@ -277,8 +196,6 @@ function _renderCrlDp(decoded, inner) {
277
196
  dpLines.push(inner + "Relative Name (to CRL issuer)");
278
197
  wrote = true;
279
198
  }
280
- // The reasons BIT STRING scopes which revocation reasons this DP covers; dropping
281
- // it would make a scoped revocation source look generally applicable.
282
199
  if (dp.reasons && Buffer.isBuffer(dp.reasons.bytes)) {
283
200
  var rf = [], rb = dp.reasons.bytes;
284
201
  for (var bit = 1; bit < rb.length * 8; bit++) {
@@ -286,8 +203,6 @@ function _renderCrlDp(decoded, inner) {
286
203
  }
287
204
  if (rf.length) { dpLines.push(inner + "Reasons: " + rf.join(", ")); wrote = true; }
288
205
  }
289
- // A DistributionPoint may carry only cRLIssuer (an indirect CRL, no
290
- // distributionPoint): render the issuer GeneralNames, never drop them.
291
206
  if (dp.cRLIssuer && Array.isArray(dp.cRLIssuer.names)) {
292
207
  dpLines.push(inner + "CRL Issuer:");
293
208
  dp.cRLIssuer.names.forEach(function (g) { dpLines.push(inner + " " + _gn(g)); });
@@ -298,24 +213,7 @@ function _renderCrlDp(decoded, inner) {
298
213
  return dpLines.join("\n");
299
214
  }
300
215
 
301
- // Coverage residual -- the shared alt-name / CRL-DP render fallbacks are unreachable because the
302
- // strict decoders already narrow the shape:
303
- // * _renderAltName `(decoded.names || [])` -- the subjectAltName/issuerAltName decoder always
304
- // yields a names array.
305
- // * _renderCrlDp `(decoded || [])`. The cRLDistributionPoints/freshestCRL decoder always
306
- // yields an array.
307
- // * _renderCrlDp `: _gn(nm)`. distributionPointName surfaces fullName entries as raw
308
- // GeneralName Buffers, so Buffer.isBuffer(nm) is always true.
309
- // * _renderCrlDp `if (!wrote) ... "(distribution point)"`. crlDistributionPoints throws unless
310
- // a distributionPoint (always fullName/rdn) or cRLIssuer (sets wrote) is present, so !wrote
311
- // never holds.
312
216
 
313
- // Declarative extension-value renderer registry: extension name -> (decoded, inner) ->
314
- // text. Data-driven dispatch (the schema family's "registry, not switch" shape) so a
315
- // new extension is a row, never another hand-coded branch, and the set an
316
- // operator sees rendered is visible in one place. An extension with no row here
317
- // hex-dumps its raw value (best-effort); each row's output is pinned by an
318
- // inspect.test.js conformance vector.
319
217
  var EXT_RENDERERS = {
320
218
  keyUsage: function (decoded, inner) {
321
219
  return inner + Object.keys(KU_LABEL).filter(function (k) { return decoded[k]; }).map(function (k) { return KU_LABEL[k]; }).join(", ");
@@ -324,7 +222,7 @@ var EXT_RENDERERS = {
324
222
  return inner + decoded.map(function (o) {
325
223
  var n = null;
326
224
  try { n = oid.name(o); }
327
- catch (_e) { /* unregistered EKU OID */ }
225
+ catch (_e) { }
328
226
  return EKU_LABEL[n] || n || o;
329
227
  }).join(", ");
330
228
  },
@@ -336,15 +234,15 @@ var EXT_RENDERERS = {
336
234
  qcStatements: function (decoded, inner) {
337
235
  return decoded.map(function (s) {
338
236
  var label = inner + (s.name || s.statementId), info = s.info;
339
- if (!info) return label; // presence-only (QcCompliance / QcSSCD)
237
+ if (!info) return label;
340
238
  if (info.opaque) return label + " (opaque)";
341
- if (typeof info.amount !== "undefined") return label + ": " + info.amount + " " + info.currency + (info.exponent ? " x10^" + info.exponent : ""); // QcLimitValue
342
- if (typeof info.years !== "undefined") return label + ": " + info.years + " years"; // QcRetentionPeriod
343
- if (info.typeNames) return label + ": " + info.typeNames.map(function (n, i) { return n || info.types[i]; }).join(", "); // QcType
344
- if (info.methodNames) return label + ": " + info.methodNames.map(function (n, i) { return n || info.methods[i]; }).join(", "); // QcIdentMethod
345
- if (info.locations) return label + ": " + info.locations.map(function (l) { return l.url + " (" + l.language + ")"; }).join(", "); // QcPDS
346
- if (info.countries) return label + ": " + info.countries.join(", "); // QcCClegislation / QcQSCDlegislation
347
- if (typeof info.semanticsIdentifier !== "undefined") { // SemanticsInformation (id-qcs)
239
+ if (typeof info.amount !== "undefined") return label + ": " + info.amount + " " + info.currency + (info.exponent ? " x10^" + info.exponent : "");
240
+ if (typeof info.years !== "undefined") return label + ": " + info.years + " years";
241
+ if (info.typeNames) return label + ": " + info.typeNames.map(function (n, i) { return n || info.types[i]; }).join(", ");
242
+ if (info.methodNames) return label + ": " + info.methodNames.map(function (n, i) { return n || info.methods[i]; }).join(", ");
243
+ if (info.locations) return label + ": " + info.locations.map(function (l) { return l.url + " (" + l.language + ")"; }).join(", ");
244
+ if (info.countries) return label + ": " + info.countries.join(", ");
245
+ if (typeof info.semanticsIdentifier !== "undefined") {
348
246
  var nra = info.nameRegistrationAuthorities && info.nameRegistrationAuthorities.length;
349
247
  return label + (info.semanticsIdentifier ? ": " + info.semanticsIdentifier : "") + (nra ? " (" + nra + " NRA)" : "");
350
248
  }
@@ -371,33 +269,18 @@ var EXT_RENDERERS = {
371
269
  decoded.forEach(function (p) {
372
270
  lines.push(inner + "Policy: " + p.policyIdentifier);
373
271
  if (!Buffer.isBuffer(p.qualifiersBytes)) return;
374
- // Render each PolicyQualifierInfo { policyQualifierId, qualifier }: a printable
375
- // qualifier (a CPS URI is an IA5String) shows as text, else a hex dump, and is never
376
- // dropped (which would make a qualified policy look unqualified).
377
272
  try {
378
273
  (asn1.decode(p.qualifiersBytes).children || []).forEach(function (pqi) {
379
274
  var qid = asn1.read.oid(pqi.children[0]), q = pqi.children[1];
380
275
  var label = null;
381
276
  try { label = oid.name(qid); }
382
- catch (_e) { /* unregistered qualifier */ }
383
- // A userNotice is a constructed SEQUENCE, so the printable-content test below can never
384
- // read it and it would hex-dump -- leaving the operator unable to read the very text the
385
- // qualifier exists to display. Render its DisplayText members through the shared pkix
386
- // reader (the same one pki.lint measures), so both agree on what the notice says.
277
+ catch (_e) { }
387
278
  if (qid === OID_UNOTICE) {
388
- // Render only when every member decoded under its declared string type. A null text means
389
- // the value did not, and showing the members that happened to decode would present a
390
- // partial notice as a complete one, so the whole qualifier falls through to the hex
391
- // dump, where the operator sees the bytes the certificate actually holds.
392
- // A null noticeNumbers means the reference did not fully decode, and is refused for the
393
- // same reason as a null text: a partially decoded notice must not be shown as a whole one.
394
279
  var texts = pkix.userNoticeTexts(q);
395
280
  if (texts.length && texts.every(function (t) {
396
281
  return t.text !== null && (t.field !== "organization" || t.noticeNumbers !== null);
397
282
  })) {
398
283
  texts.forEach(function (t) {
399
- // A NoticeReference is identified by organization AND number, so the numbers ride with
400
- // the organization -- printing the text alone would drop the key that names the notice.
401
284
  var nums = (t.noticeNumbers && t.noticeNumbers.length) ? " #" + t.noticeNumbers.join(", ") : "";
402
285
  lines.push(inner + " " + (label || qid) + " " + t.field + ": " + _clean(t.text) + nums);
403
286
  });
@@ -418,24 +301,15 @@ var EXT_RENDERERS = {
418
301
  cRLDistributionPoints: _renderCrlDp,
419
302
  freshestCRL: _renderCrlDp,
420
303
  authorityInfoAccess: function (decoded, inner) {
421
- // AccessDescription list: <accessMethod> - <accessLocation>. The method resolves to its name (caIssuers /
422
- // ocsp); the accessLocation is a GeneralName (a URI in the common case). An unregistered method / an
423
- // uncommon accessLocation tag falls back to the raw OID / bracketed tag, and the entry is never dropped.
424
304
  var LABEL = { caIssuers: "CA Issuers", ocsp: "OCSP" };
425
305
  return (decoded || []).map(function (ad) {
426
306
  var m = null;
427
307
  try { m = oid.name(ad.accessMethod); } catch (_e) { /* allow:swallow-unverified display best-effort: an unregistered accessMethod OID falls back to the raw dotted OID below (inspection is best-effort, never a verdict) */ }
428
308
  var loc = ad.accessLocation || {}, lv;
429
- // The string choices (URI/DNS/email) are IA5String values already control-byte-rejected at decode by the
430
- // CVE-2009-2408 guard, so they are safe to emit directly. The iPAddress choice is a raw 4/16-byte Buffer,
431
- // so render it through _ipString (never raw) and a byte such as 0x0a cannot inject a line and spoof a field.
432
309
  if (loc.tag === 6) lv = "URI:" + loc.value;
433
310
  else if (loc.tag === 2) lv = "DNS:" + loc.value;
434
311
  else if (loc.tag === 1) lv = "email:" + loc.value;
435
312
  else if (loc.tag === 7) lv = "IP:" + _ipString(loc.value);
436
- // A directoryName accessLocation carries a decoded Name, so print the DN. Without this it fell
437
- // to the bracketed-tag fallback and rendered a bare "[4]", hiding the responder/issuer identity
438
- // the entry exists to convey -- while the same form already printed as DirName elsewhere.
439
313
  else if (loc.tag === 4) lv = "DirName:" + _gnDn(loc.value);
440
314
  else lv = typeof loc.value === "string" ? loc.value : "[" + loc.tag + "]";
441
315
  return inner + (LABEL[m] || m || ad.accessMethod) + " - " + lv;
@@ -482,9 +356,6 @@ var EXT_RENDERERS = {
482
356
  return inner + _hexColon(Buffer.isBuffer(decoded) ? decoded : (decoded.bytes || Buffer.alloc(0)), { upper: true });
483
357
  },
484
358
  authorityKeyIdentifier: function (decoded, inner) {
485
- // Any of the three fields may be present; the issuer+serial form carries no
486
- // keyIdentifier, so render whichever the decoder populated, never claiming
487
- // "keyid:(none)" and dropping the certificate's real authority identifier.
488
359
  var akiLines = [];
489
360
  if (Buffer.isBuffer(decoded.keyIdentifier)) akiLines.push(inner + "keyid:" + _hexColon(decoded.keyIdentifier, { upper: true }));
490
361
  if (decoded.authorityCertIssuer && Array.isArray(decoded.authorityCertIssuer.names)) {
@@ -499,52 +370,15 @@ var EXT_RENDERERS = {
499
370
  return akiLines.length ? akiLines.join("\n") : inner + "keyid:(none)";
500
371
  },
501
372
  };
502
- // [MS-WCCE] szOID-APPLICATION_CERT_POLICIES decodes as certificatePolicies, so it renders identically.
503
373
  EXT_RENDERERS.msApplicationPolicies = EXT_RENDERERS.certificatePolicies;
504
374
 
505
- // Coverage residual -- the EXT_RENDERERS entry fallbacks are unreachable because each shared
506
- // decoder (and asn1.read.oid) already narrows the shape before the renderer runs:
507
- // * extKeyUsage `catch (_e) { /* unregistered EKU OID */ }` and certificatePolicies
508
- // `catch (_e) { /* unregistered qualifier */ }`. oid.name returns undefined (never throws)
509
- // for a well-formed unregistered OID; it throws only on a non-dotted argument, and both OIDs
510
- // come from asn1.read.oid (always a valid dotted OID).
511
- // * certificatePolicies `(asn1.decode(p.qualifiersBytes).children || [])`. asn1.decode of the
512
- // assertPolicyQualifiers-validated qualifiers SEQUENCE always yields a children array.
513
- // * certificatePolicies `: Buffer.alloc(0)`. assertPolicyQualifiers requires every
514
- // PolicyQualifierInfo to be a 2-child SEQUENCE, so pqi.children[1] is always a present node
515
- // carrying a bytes Buffer.
516
- // * certificatePolicies outer `catch (_e) { ... _hexColon(p.qualifiersBytes, {}) ... }`.
517
- // certificatePolicies already validated qualifiersBytes as a SEQUENCE of 2-child PQIs each
518
- // leading with a valid OID, so the re-decode + asn1.read.oid cannot throw.
519
- // * policyConstraints `: inner + "(empty)"`. policyConstraints rejects an empty SEQUENCE
520
- // (>= 1 context field), so requireExplicitPolicy or inhibitPolicyMapping is non-null;
521
- // pc.length is never 0.
522
- // * SCT `(decoded.scts || [])` / `(decoded.unknownScts || [])`. ct.parseSctList always returns
523
- // both arrays.
524
- // * SCT `: 0`. Every scts entry is a decoded v1 SCT with numeric version 0 (unknown-version
525
- // SCTs go to unknownScts and are not iterated here).
526
- // * SCT `: inner + "(empty SCT list)"`. ct.parseSctList rejects an empty list and routes every
527
- // SerializedSCT into scts/unknownScts, so at least one line is always emitted.
528
- // * subjectKeyIdentifier `: (decoded.bytes || Buffer.alloc(0))`. The subjectKeyIdentifier
529
- // decoder returns the KeyIdentifier as a Buffer, so Buffer.isBuffer(decoded) is always true.
530
- // * authorityKeyIdentifier `: BigInt(decoded.authorityCertSerialNumber)`. The AKI decoder reads
531
- // authorityCertSerialNumber via asn1.read.integerImplicit (a bigint), so typeof === "bigint"
532
- // is always true.
533
375
 
534
376
  function _renderExtValue(ext, decoded, inner) {
535
377
  var fn = EXT_RENDERERS[ext.name];
536
- return fn ? fn(decoded, inner) : null; // no registered renderer -> caller hex-dumps
378
+ return fn ? fn(decoded, inner) : null;
537
379
  }
538
380
 
539
- // Fallback for an extension with no decoder or a decode failure. Best-effort,
540
- // more useful than OpenSSL's raw-octet dump: a directly-printable value shows as a
541
- // string; a DER-wrapped character string is decoded and shown; otherwise a hex
542
- // dump. Never throws.
543
- var _STRING_TAGS = { 12: 1, 19: 1, 22: 1, 20: 1, 26: 1, 27: 1, 30: 1 }; // UTF8/Printable/IA5/Teletex/Visible/General/BMP
544
- // A value renders as text only when every byte is a printable, non-control ASCII
545
- // character. A control byte (a bare CR / LF / TAB, etc.) is rejected here so a
546
- // hostile private-extension value cannot forge or overwrite report lines in a
547
- // terminal or log -- such a value falls through to the hex dump instead.
381
+ var _STRING_TAGS = { 12: 1, 19: 1, 22: 1, 20: 1, 26: 1, 27: 1, 30: 1 };
548
382
  function _printable(buf) {
549
383
  return buf.length > 0 && buf.every(function (b) { return b >= 0x20 && b < 0x7f; });
550
384
  }
@@ -557,7 +391,7 @@ function _fallback(value, inner) {
557
391
  var s = asn1.read.string(n);
558
392
  if (_printable(Buffer.from(s, "utf8"))) return inner + s;
559
393
  }
560
- } catch (_e) { /* not DER / not a string -> hex */ }
394
+ } catch (_e) { }
561
395
  return _hexColon(value, { wrap: 16, indent: inner.length });
562
396
  }
563
397
 
@@ -570,18 +404,12 @@ function _extension(ext, pad) {
570
404
  try {
571
405
  var body = _renderExtValue(ext, decoder(ext.value), inner);
572
406
  if (body != null) return header + "\n" + body;
573
- } catch (_e) { /* fall through to the raw fallback */ }
407
+ } catch (_e) { }
574
408
  }
575
409
  return header + "\n" + _fallback(ext.value, inner);
576
410
  }
577
411
 
578
- // ---- input coercion ----------------------------------------------------------
579
412
 
580
- // A genuine pki.schema.x509.parse result carries this whole shape. The fast path
581
- // accepts a pre-parsed certificate to skip re-parsing, but a bare or partial object
582
- // with only a tbsBytes property is not a certificate: without this check the renderer
583
- // would dereference a missing field (c.validity.notBefore, ...) and throw a raw
584
- // TypeError instead of the documented typed inspect/bad-input (the API's error contract).
585
413
  function _looksParsed(o) {
586
414
  return typeof o.version === "number" && typeof o.serialNumberHex === "string" &&
587
415
  o.signatureAlgorithm && typeof o.signatureAlgorithm === "object" &&
@@ -593,7 +421,7 @@ function _looksParsed(o) {
593
421
  function _parse(input) {
594
422
  if (input && typeof input === "object" && !Buffer.isBuffer(input) && input.tbsBytes) {
595
423
  if (!_looksParsed(input)) throw _err("inspect/bad-input", "input has a tbsBytes property but is not a complete pki.schema.x509.parse result");
596
- return input; // a genuine already-parsed certificate
424
+ return input;
597
425
  }
598
426
  var der;
599
427
  if (Buffer.isBuffer(input)) der = input;
@@ -607,7 +435,6 @@ function _parse(input) {
607
435
  catch (e) { throw _err("inspect/bad-certificate", "input is not a well-formed X.509 certificate", e); }
608
436
  }
609
437
 
610
- // ---- public: certificate -----------------------------------------------------
611
438
 
612
439
  /**
613
440
  * @primitive pki.inspect.certificate
@@ -646,8 +473,6 @@ function certificate(input) {
646
473
  L.push(" Subject: " + _dnString(c.subject));
647
474
  L.push(" Subject Public Key Info:");
648
475
  L.push(_keyBlock(c.subjectPublicKeyInfo, " "));
649
- // Coverage residual -- `c.extensions || []` is unreachable: both input paths guarantee an array
650
- // (x509.parse yields one; the pre-parsed fast path requires Array.isArray(extensions) in _looksParsed).
651
476
  if ((c.extensions || []).length) {
652
477
  L.push(" X509v3 extensions:");
653
478
  c.extensions.forEach(function (ext) { L.push(_extension(ext, " ")); });
@@ -658,11 +483,7 @@ function certificate(input) {
658
483
  return L.join("\n") + "\n";
659
484
  }
660
485
 
661
- // ---- CRL / CSR / CMS coercion (the _parse model, per format) -----------------
662
486
 
663
- // Each fast-path completeness check mirrors _looksParsed: a bare/partial object
664
- // with only the marker field is NOT a parsed result, so it throws the documented
665
- // typed inspect/bad-input rather than dereferencing a missing field.
666
487
  function _looksParsedCrl(o) {
667
488
  return typeof o.version === "number" && o.issuer && typeof o.issuer === "object" &&
668
489
  o.thisUpdate != null && Array.isArray(o.revokedCertificates) && Array.isArray(o.crlExtensions) &&
@@ -675,29 +496,16 @@ function _looksParsedCsr(o) {
675
496
  }
676
497
  function _looksParsedCms(o) {
677
498
  if (typeof o.contentType !== "string" || typeof o.contentTypeName !== "string" || typeof o.version !== "number") return false;
678
- // A real parse result is one of the six dispatched content types AND carries that type's
679
- // required structural fields (_CMS_SHAPE). An unrecognized contentType, or a recognized one
680
- // missing its fields, is not a parse result: fail closed as inspect/bad-input, not a partial render.
681
499
  var shape = _CMS_SHAPE[o.contentType];
682
500
  return shape ? shape(o) : false;
683
501
  }
684
- // The shared inspect coercion boundary, composed by every direct inspector AND any():
685
- // pemLabel:null unwraps a PEM string OR a PEM-armored Buffer (a .pem read with
686
- // fs.readFileSync) under ANY block label -- so an aliased label (PKCS7 for CMS,
687
- // CRL for an X509 CRL) routes like any() instead of hitting a canonical-label
688
- // mismatch -- while a raw DER Buffer / Uint8Array passes through the detached-store guard.
689
502
  var _INSPECT_ENTRY = { pemLabel: null, PemError: InspectError, ErrorClass: InspectError, prefix: "inspect" };
690
- // A parameterized _parse clone: marker field, fast-path completeness check, then
691
- // the shared label-agnostic coercion to DER, then parse(der) inside try.
692
503
  function _coerce(input, spec) {
693
504
  if (input && typeof input === "object" && !Buffer.isBuffer(input) && !(input instanceof Uint8Array) && input[spec.marker] !== undefined) {
694
505
  if (!spec.looksParsed(input)) throw _err("inspect/bad-input", "input has a " + spec.marker + " property but is not a complete " + spec.parsedName + " result");
695
506
  return input;
696
507
  }
697
508
  var der;
698
- // Compose the ONE coercion every format parser + any() use, so a PEM string and a
699
- // PEM-armored Buffer unwrap under any label (not just the format's canonical one)
700
- // and a detached-store Buffer fails closed here, not divergently per inspector.
701
509
  try { der = pkix.coerceToDer(input, _INSPECT_ENTRY); }
702
510
  catch (e) { throw _err("inspect/bad-input", "input must be a parsed " + spec.what + ", a DER Buffer, or a PEM block", e); }
703
511
  try { return spec.parse(der); }
@@ -707,40 +515,22 @@ function _parseCrl(input) { return _coerce(input, { marker: "thisUpdate", looksP
707
515
  function _parseCsr(input) { return _coerce(input, { marker: "certificationRequestInfoBytes", looksParsed: _looksParsedCsr, parsedName: "pki.schema.csr.parse", parse: csr.parse, badCode: "inspect/bad-csr", what: "PKCS#10 certification request" }); }
708
516
  function _parseCms(input) { return _coerce(input, { marker: "contentType", looksParsed: _looksParsedCms, parsedName: "pki.schema.cms.parse", parse: cms.parse, badCode: "inspect/bad-cms", what: "CMS message" }); }
709
517
 
710
- // ---- shared attribute-value renderer (CSR attributes + CMS signed/unsigned) ---
711
518
 
712
- // Attribute/extension dispatch keys on the STABLE OID, never the display name a
713
- // pki.oid.register() override can change (the schema stores the overridden name).
714
519
  var OID_EXTENSION_REQUEST = oid.byName("extensionRequest");
715
520
  var OID_CONTENT_TYPE = oid.byName("contentType");
716
521
  var OID_MESSAGE_DIGEST = oid.byName("messageDigest");
717
522
  var OID_SIGNING_TIME = oid.byName("signingTime");
718
523
 
719
- // Decode the known content-binding attribute types for display; anything else, or
720
- // a decode failure, falls back to printable-or-hex. Never throws once parsed.
721
- // Keyed on the attribute's OID (stable), not its display name (register-mutable).
722
524
  function _attrValue(typeOid, rawDer, inner) {
723
525
  try {
724
526
  if (typeOid === OID_CONTENT_TYPE) { var ct = asn1.read.oid(asn1.decode(rawDer)); return inner + (oid.name(ct) || ct); }
725
527
  if (typeOid === OID_MESSAGE_DIGEST) return _hexColon(asn1.read.octetString(asn1.decode(rawDer)), { wrap: 16, indent: inner.length });
726
528
  if (typeOid === OID_SIGNING_TIME) return inner + _date(asn1.read.time(asn1.decode(rawDer)));
727
- } catch (_e) { /* fall through to the raw fallback */ }
529
+ } catch (_e) { }
728
530
  return _fallback(rawDer, inner);
729
531
  }
730
532
 
731
- // ---- CRL report --------------------------------------------------------------
732
533
 
733
- // The CRL decoder PRE-DECODES three extension values to a JS type (not the raw
734
- // Buffer the shared _extension consumes): cRLNumber -> BigInt, reasonCode ->
735
- // Number, invalidityDate -> Date. Dispatch on the STABLE OID exactly as the decoder
736
- // does (schema-crl decodeExt), NOT the display name -- pki.oid.register() can override
737
- // a built-in name, which would skip these branches and hand _extension a non-Buffer
738
- // value (rendering "(empty)" for the CRL number or reason). deltaCRLIndicator is a
739
- // raw-Buffer extension the decoder does NOT pre-decode, but its value is a bare INTEGER
740
- // (BaseCRLNumber) the operator needs in decimal to relate a delta CRL to its base -- so
741
- // decode it here for display (best-effort: a malformed value falls through to hex).
742
- // Delegate every other raw-value extension (AKI / IDP / freshestCRL / certificateIssuer)
743
- // to the shared _extension verbatim.
744
534
  var OID_CRL_NUMBER = oid.byName("cRLNumber");
745
535
  var OID_REASON_CODE = oid.byName("reasonCode");
746
536
  var OID_INVALIDITY_DATE = oid.byName("invalidityDate");
@@ -754,7 +544,7 @@ function _crlExtension(ext, pad) {
754
544
  if (ext.oid === OID_INVALIDITY_DATE && guard.time.isDate(ext.value)) return header + "\n" + inner + _date(ext.value);
755
545
  if (ext.oid === OID_DELTA_CRL_INDICATOR && Buffer.isBuffer(ext.value)) {
756
546
  try { return header + "\n" + inner + "BaseCRLNumber: " + String(asn1.read.integer(asn1.decode(ext.value))); }
757
- catch (_e) { /* malformed BaseCRLNumber -> fall through to the shared _extension (hex) */ }
547
+ catch (_e) { }
758
548
  }
759
549
  return _extension(ext, pad);
760
550
  }
@@ -815,12 +605,9 @@ function crlReport(input) {
815
605
  return L.join("\n") + "\n";
816
606
  }
817
607
 
818
- // ---- CSR report --------------------------------------------------------------
819
608
 
820
609
  function _attribute(attr, pad) {
821
610
  var inner = pad + " ";
822
- // extensionRequest carries decoded RFC 5280 extensions (the cert-extension shape)
823
- // -> render each through the shared _extension, identically to a certificate's.
824
611
  if (attr.type === OID_EXTENSION_REQUEST && Array.isArray(attr.extensions)) {
825
612
  var lines = [pad + "Requested Extensions:"];
826
613
  attr.extensions.forEach(function (ext) { lines.push(_extension(ext, inner)); });
@@ -867,46 +654,18 @@ function csrReport(input) {
867
654
  return L.join("\n") + "\n";
868
655
  }
869
656
 
870
- // ---- CMS report --------------------------------------------------------------
871
657
 
872
- // Dispatch on the STABLE contentType OID, never the display name: pki.oid.register()
873
- // lets an application override the built-in "signedData" name, and dispatching on the
874
- // mutable name would then misroute a valid SignedData to the generic summary.
875
658
  var OID_SIGNED_DATA = oid.byName("signedData");
876
- // pki.schema.cms.parse dispatches ONLY these six RFC 5652 content types (every other
877
- // OID throws cms/unsupported- or cms/unknown-content-type -- it never yields an object).
878
- // Each maps to a predicate over the REQUIRED (non-OPTIONAL) fields of its top-level
879
- // SEQUENCE, so the parsed-object fast path in _looksParsedCms accepts a genuine parse
880
- // result but a partial/hand-built object (the type OID + name + version, none of the
881
- // structural fields) fails closed as inspect/bad-input -- the documented boundary.
882
659
  function _isObj(x) { return !!x && typeof x === "object"; }
883
660
  var _CMS_SHAPE = {};
884
- _CMS_SHAPE[OID_SIGNED_DATA] = function (o) { return Array.isArray(o.digestAlgorithms) && _isObj(o.encapContentInfo) && Array.isArray(o.signerInfos); }; // sec. 5.1
885
- _CMS_SHAPE[oid.byName("envelopedData")] = function (o) { return Array.isArray(o.recipientInfos) && _isObj(o.encryptedContentInfo); }; // sec. 6.1
886
- _CMS_SHAPE[oid.byName("encryptedData")] = function (o) { return _isObj(o.encryptedContentInfo); }; // sec. 8
887
- _CMS_SHAPE[oid.byName("authData")] = function (o) { return Array.isArray(o.recipientInfos) && _isObj(o.macAlgorithm) && _isObj(o.encapContentInfo) && Buffer.isBuffer(o.mac); }; // sec. 9.1
888
- _CMS_SHAPE[oid.byName("authEnvelopedData")] = function (o) { return Array.isArray(o.recipientInfos) && _isObj(o.encryptedContentInfo) && Buffer.isBuffer(o.mac); }; // RFC 5083 (build surfaces encryptedContentInfo)
889
- _CMS_SHAPE[oid.byName("compressedData")] = function (o) { return _isObj(o.compressionAlgorithm) && _isObj(o.encapContentInfo); }; // RFC 3274
661
+ _CMS_SHAPE[OID_SIGNED_DATA] = function (o) { return Array.isArray(o.digestAlgorithms) && _isObj(o.encapContentInfo) && Array.isArray(o.signerInfos); };
662
+ _CMS_SHAPE[oid.byName("envelopedData")] = function (o) { return Array.isArray(o.recipientInfos) && _isObj(o.encryptedContentInfo); };
663
+ _CMS_SHAPE[oid.byName("encryptedData")] = function (o) { return _isObj(o.encryptedContentInfo); };
664
+ _CMS_SHAPE[oid.byName("authData")] = function (o) { return Array.isArray(o.recipientInfos) && _isObj(o.macAlgorithm) && _isObj(o.encapContentInfo) && Buffer.isBuffer(o.mac); };
665
+ _CMS_SHAPE[oid.byName("authEnvelopedData")] = function (o) { return Array.isArray(o.recipientInfos) && _isObj(o.encryptedContentInfo) && Buffer.isBuffer(o.mac); };
666
+ _CMS_SHAPE[oid.byName("compressedData")] = function (o) { return _isObj(o.compressionAlgorithm) && _isObj(o.encapContentInfo); };
890
667
 
891
- // Coverage residuals in the report assemblers -- verified-hard-to-reach, not gaps:
892
- // * The `x.bytes || x` / `oid.name(o) || o` / `a.name || oid.name || a.type` fallbacks are
893
- // belts for shapes the strict parsers never produce (a parsed CSR always carries
894
- // signatureValue.bytes; a decoded algorithm always names its OID) -- they mirror
895
- // certificate()'s own defensive fallbacks.
896
- // * The embedded-CRL delegation and the CONTEXT-tagged embedded-element summary need a CMS
897
- // carrying a crls element or an attribute-certificate CHOICE alternative, and the
898
- // AuthenticatedData macAlgorithm / countersignature-unsignedAttrs arms need CMS shapes the
899
- // toolkit's own producers (cms.sign / encrypt / compress) do not emit; every such arm is
900
- // driven best-effort (never throws) and its structural sibling (embedded Certificate,
901
- // envelopedData recipientInfo, CompressedData compressionAlgorithm, signed attributes) is
902
- // covered.
903
668
 
904
- // An embedded certificate/CRL element {bytes,tagClass,tagNumber}: a UNIVERSAL
905
- // SEQUENCE is a real Certificate/CertificateList -> delegate to the full sub-report
906
- // (guarded; a one-line summary on any failure); a CONTEXT-tagged CHOICE alternative
907
- // (attribute certificate / other) renders a one-line tag+size summary, never parsed.
908
- // Leading "0" characters removed, and a single trailing "\n" removed -- both by character scan
909
- // (no regex): display helpers, matching the /^0+/ and /\n$/ replacements they succeed.
910
669
  function _stripLeadingZeros(s) {
911
670
  var i = 0;
912
671
  while (i < s.length && s.charAt(i) === "0") i += 1;
@@ -921,7 +680,7 @@ function _cmsEmbedded(kind, el, pad) {
921
680
  try {
922
681
  var sub = (kind === "CRL") ? crlReport(el.bytes) : certificate(el.bytes);
923
682
  return pad + kind + ":\n" + _stripTrailingNewline(sub).split("\n").map(function (l) { return pad + " " + l; }).join("\n");
924
- } catch (_e) { /* fall through to the summary */ }
683
+ } catch (_e) { }
925
684
  }
926
685
  return pad + kind + " [" + el.tagClass + " " + el.tagNumber + "] (" + el.bytes.length + " bytes)";
927
686
  }
@@ -977,15 +736,12 @@ function _signerInfo(si, pad) {
977
736
  * var cmsDer = await pki.cms.sign(Buffer.from("hello"), { cert: cert, key: key });
978
737
  * pki.inspect.cms(cmsDer).split("\n")[0]; // "CMS ContentInfo:"
979
738
  */
980
- // A ContentInfo whose content type pki.schema.cms.parse does not dispatch (id-data,
981
- // digestedData, ...) is a VALID CMS the parser defers, not a malformed message -- render an
982
- // outer-only summary (the named content type) rather than failing the report.
983
739
  function _cmsOuterSummary(input) {
984
740
  var ct = null;
985
741
  try {
986
742
  var der = pkix.coerceToDer(input, _INSPECT_ENTRY);
987
743
  ct = asn1.read.oid(asn1.decode(der).children[0]);
988
- } catch (_e) { /* best-effort: name unknown */ }
744
+ } catch (_e) { }
989
745
  return "CMS ContentInfo:\n Content Type: " + (ct ? (oid.name(ct) || ct) + " (" + ct + ")" : "unknown") +
990
746
  "\n (content type not further parsed; outer ContentInfo only)\n";
991
747
  }
@@ -994,9 +750,6 @@ function cmsReport(input) {
994
750
  var m;
995
751
  try { m = _parseCms(input); }
996
752
  catch (e) {
997
- // A ContentInfo whose contentType the parser does not dispatch is a valid CMS, not a malformed
998
- // message: a known-but-deferred type (cms/unsupported-content-type, e.g. id-data) or a private/
999
- // unregistered OID (cms/unknown-content-type). Either renders the outer-only summary.
1000
753
  var cc = e && e.cause && e.cause.code;
1001
754
  if (e && e.code === "inspect/bad-cms" && (cc === "cms/unsupported-content-type" || cc === "cms/unknown-content-type")) return _cmsOuterSummary(input);
1002
755
  throw e;
@@ -1017,7 +770,6 @@ function cmsReport(input) {
1017
770
  (m.crls || []).forEach(function (el) { L.push(_cmsEmbedded("CRL", el, " ")); });
1018
771
  (m.signerInfos || []).forEach(function (si) { L.push(_signerInfo(si, " ")); });
1019
772
  } else {
1020
- // Non-SignedData: a stable top-field summary (no plaintext to show); never throws.
1021
773
  L.push(" " + (m.contentTypeName || "content") + ":");
1022
774
  if (m.version != null) L.push(" Version: " + m.version);
1023
775
  (m.recipientInfos || []).forEach(function (ri) { L.push(" RecipientInfo: " + (ri.type || "?") + (ri.ridType ? " (" + ri.ridType + ")" : "")); });
@@ -1031,7 +783,6 @@ function cmsReport(input) {
1031
783
  return L.join("\n") + "\n";
1032
784
  }
1033
785
 
1034
- // ---- unified detect-and-dispatch ---------------------------------------------
1035
786
 
1036
787
  var _INSPECT_BY_FORMAT = { x509: certificate, crl: crlReport, csr: csrReport, cms: cmsReport };
1037
788
 
@@ -1058,12 +809,10 @@ var _INSPECT_BY_FORMAT = { x509: certificate, crl: crlReport, csr: csrReport, cm
1058
809
  * pki.inspect.any(der); // routes to the right report by detected format
1059
810
  */
1060
811
  function any(input) {
1061
- // Unwrap to DER ONCE (any PEM label), detect from the DER, then route the DER Buffer -- the
1062
- // renderer parses a Buffer directly and never re-applies its own strict PEM label.
1063
812
  var der, fmt;
1064
813
  try {
1065
814
  der = pkix.coerceToDer(input, _INSPECT_ENTRY);
1066
- fmt = schemaAll.detectFormat(der); // decodes the root -- a non-DER Buffer throws here, not at coerce
815
+ fmt = schemaAll.detectFormat(der);
1067
816
  } catch (e) { throw _err("inspect/bad-input", "input is not a decodable DER Buffer or PEM string", e); }
1068
817
  if (fmt === null) throw _err("inspect/bad-input", "input does not match any registered PKI format");
1069
818
  var render = _INSPECT_BY_FORMAT[fmt];
@@ -1077,8 +826,5 @@ module.exports = {
1077
826
  csr: csrReport,
1078
827
  cms: cmsReport,
1079
828
  any: any,
1080
- // The extension names certificate() renders to their decoded values (vs the raw
1081
- // hex fallback). The inspect test asserts this covers every extension the shared
1082
- // decoders decode, so a newly-decodable extension cannot silently hex-dump.
1083
829
  renderedExtensions: Object.keys(EXT_RENDERERS),
1084
830
  };