@blamejs/pki 0.6.14 → 0.6.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -1
- package/lib/acme.js +59 -815
- package/lib/asn1-der.js +7 -245
- package/lib/attrcert-sign.js +12 -260
- package/lib/attrcert-verify.js +1 -7
- package/lib/byte-reader.js +0 -8
- package/lib/byte-writer.js +0 -11
- package/lib/cbor-det.js +0 -54
- package/lib/cmc-build.js +15 -357
- package/lib/cmc-verify.js +6 -239
- package/lib/cmp-build.js +41 -317
- package/lib/cmp-session.js +73 -760
- package/lib/cmp-verify.js +32 -488
- package/lib/cms-compress.js +3 -32
- package/lib/cms-decrypt.js +29 -294
- package/lib/cms-encrypt.js +14 -234
- package/lib/cms-sign.js +29 -324
- package/lib/cms-verify.js +9 -500
- package/lib/composite-kem.js +2 -102
- package/lib/composite-sig.js +1 -77
- package/lib/constants.js +0 -249
- package/lib/crl-sign.js +9 -237
- package/lib/crl-verify.js +1 -10
- package/lib/crmf-sign.js +19 -198
- package/lib/crmf-verify.js +1 -12
- package/lib/csr-sign.js +3 -34
- package/lib/csr-verify.js +1 -12
- package/lib/ct.js +28 -253
- package/lib/edwards-point.js +10 -44
- package/lib/est.js +28 -764
- package/lib/framework-error.js +4 -182
- package/lib/guard-all.js +1 -58
- package/lib/guard-async.js +1 -36
- package/lib/guard-bytes.js +6 -650
- package/lib/guard-compress.js +5 -102
- package/lib/guard-crypto.js +1 -40
- package/lib/guard-der.js +2 -93
- package/lib/guard-encoding.js +2 -60
- package/lib/guard-header.js +1 -36
- package/lib/guard-identifier.js +7 -531
- package/lib/guard-intrinsic.js +1 -150
- package/lib/guard-json.js +10 -73
- package/lib/guard-limits.js +1 -77
- package/lib/guard-list.js +1 -49
- package/lib/guard-name.js +3 -209
- package/lib/guard-parsed.js +7 -335
- package/lib/guard-range.js +1 -71
- package/lib/guard-secret.js +1 -102
- package/lib/guard-text.js +1 -64
- package/lib/guard-time.js +1 -97
- package/lib/hpke.js +3 -105
- package/lib/http-digest.js +14 -142
- package/lib/http-retry-after.js +1 -47
- package/lib/http-transport.js +21 -131
- package/lib/inspect.js +30 -284
- package/lib/ip-utils.js +6 -47
- package/lib/jose.js +7 -142
- package/lib/key.js +2 -67
- package/lib/lint.js +9 -194
- package/lib/merkle.js +1 -27
- package/lib/mime.js +9 -82
- package/lib/ocsp-request-verify.js +1 -14
- package/lib/ocsp-verify.js +1 -82
- package/lib/ocsp.js +11 -169
- package/lib/oid.js +5 -337
- package/lib/path-validate.js +103 -1492
- package/lib/pbes2.js +2 -89
- package/lib/pkcs12-build.js +31 -379
- package/lib/pki-build.js +64 -290
- package/lib/rc2.js +2 -18
- package/lib/rfc3339.js +4 -27
- package/lib/scep.js +8 -252
- package/lib/schema-all.js +0 -122
- package/lib/schema-attrcert.js +4 -146
- package/lib/schema-c509.js +153 -802
- package/lib/schema-cmc.js +5 -220
- package/lib/schema-cmp.js +2 -223
- package/lib/schema-cms.js +13 -427
- package/lib/schema-crl.js +5 -68
- package/lib/schema-crmf.js +6 -167
- package/lib/schema-csr.js +1 -54
- package/lib/schema-csrattrs.js +2 -87
- package/lib/schema-engine.js +0 -152
- package/lib/schema-ocsp.js +0 -117
- package/lib/schema-pkcs12.js +2 -93
- package/lib/schema-pkcs8.js +3 -35
- package/lib/schema-pkix.js +24 -655
- package/lib/schema-smime.js +0 -54
- package/lib/schema-tsp.js +3 -99
- package/lib/schema-x509.js +4 -63
- package/lib/shbs.js +11 -95
- package/lib/sign-scheme.js +2 -97
- package/lib/sigstore.js +9 -169
- package/lib/sleep.js +2 -8
- package/lib/smime.js +15 -408
- package/lib/tls-cert-compress.js +0 -62
- package/lib/trust.js +19 -185
- package/lib/tsp-sign.js +16 -270
- package/lib/validator-all.js +1 -30
- package/lib/validator-attcert.js +2 -46
- package/lib/validator-cose.js +2 -153
- package/lib/validator-keydesc.js +1 -37
- package/lib/validator-sig.js +1 -46
- package/lib/validator-tls.js +1 -88
- package/lib/validator-tpm.js +19 -122
- package/lib/verify-seam.js +1 -24
- package/lib/webauthn-mds.js +3 -400
- package/lib/webauthn.js +12 -833
- package/lib/webcrypto.js +5 -485
- package/lib/x509-sign.js +4 -108
- package/package.json +1 -1
- 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) {
|
|
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);
|
|
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) {
|
|
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, {});
|
|
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) {
|
|
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;
|
|
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 : "");
|
|
342
|
-
if (typeof info.years !== "undefined") return label + ": " + info.years + " years";
|
|
343
|
-
if (info.typeNames) return label + ": " + info.typeNames.map(function (n, i) { return n || info.types[i]; }).join(", ");
|
|
344
|
-
if (info.methodNames) return label + ": " + info.methodNames.map(function (n, i) { return n || info.methods[i]; }).join(", ");
|
|
345
|
-
if (info.locations) return label + ": " + info.locations.map(function (l) { return l.url + " (" + l.language + ")"; }).join(", ");
|
|
346
|
-
if (info.countries) return label + ": " + info.countries.join(", ");
|
|
347
|
-
if (typeof info.semanticsIdentifier !== "undefined") {
|
|
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) {
|
|
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;
|
|
378
|
+
return fn ? fn(decoded, inner) : null;
|
|
537
379
|
}
|
|
538
380
|
|
|
539
|
-
|
|
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) {
|
|
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) {
|
|
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;
|
|
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) {
|
|
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) {
|
|
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); };
|
|
885
|
-
_CMS_SHAPE[oid.byName("envelopedData")] = function (o) { return Array.isArray(o.recipientInfos) && _isObj(o.encryptedContentInfo); };
|
|
886
|
-
_CMS_SHAPE[oid.byName("encryptedData")] = function (o) { return _isObj(o.encryptedContentInfo); };
|
|
887
|
-
_CMS_SHAPE[oid.byName("authData")] = function (o) { return Array.isArray(o.recipientInfos) && _isObj(o.macAlgorithm) && _isObj(o.encapContentInfo) && Buffer.isBuffer(o.mac); };
|
|
888
|
-
_CMS_SHAPE[oid.byName("authEnvelopedData")] = function (o) { return Array.isArray(o.recipientInfos) && _isObj(o.encryptedContentInfo) && Buffer.isBuffer(o.mac); };
|
|
889
|
-
_CMS_SHAPE[oid.byName("compressedData")] = function (o) { return _isObj(o.compressionAlgorithm) && _isObj(o.encapContentInfo); };
|
|
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) {
|
|
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) {
|
|
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);
|
|
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
|
};
|