@blamejs/pki 0.8.28 → 0.8.30
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 +42 -0
- package/README.md +1 -1
- package/lib/inspect.js +1156 -10
- package/lib/ocsp.js +10 -3
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/inspect.js
CHANGED
|
@@ -31,6 +31,15 @@ var x509 = require("./schema-x509");
|
|
|
31
31
|
var crl = require("./schema-crl");
|
|
32
32
|
var csr = require("./schema-csr");
|
|
33
33
|
var cms = require("./schema-cms");
|
|
34
|
+
var pkcs8 = require("./schema-pkcs8");
|
|
35
|
+
var pkcs12 = require("./schema-pkcs12");
|
|
36
|
+
var crmf = require("./schema-crmf");
|
|
37
|
+
var cmp = require("./schema-cmp");
|
|
38
|
+
var csrattrs = require("./schema-csrattrs");
|
|
39
|
+
var trustanchor = require("./schema-trustanchor");
|
|
40
|
+
var ocsp = require("./schema-ocsp");
|
|
41
|
+
var tsp = require("./schema-tsp");
|
|
42
|
+
var attrcert = require("./schema-attrcert");
|
|
34
43
|
var schemaAll = require("./schema-all");
|
|
35
44
|
var pkix = require("./schema-pkix");
|
|
36
45
|
var guard = require("./guard-all");
|
|
@@ -84,7 +93,10 @@ function _algName(a) { return (a && (a.name || a.oid)) || "unknown"; }
|
|
|
84
93
|
|
|
85
94
|
|
|
86
95
|
function _serial(cert, indent) {
|
|
87
|
-
|
|
96
|
+
/** @internal Only a hex string is read as one. A caller's pre-parsed object can carry anything here, and
|
|
97
|
+
* the byte reader below faults on a value that is not a string rather than reporting an unreadable one. */
|
|
98
|
+
var hex = typeof cert.serialNumberHex === "string" && _isHexDigits(cert.serialNumberHex)
|
|
99
|
+
? cert.serialNumberHex : "";
|
|
88
100
|
if (hex.length % 2) hex = "0" + hex;
|
|
89
101
|
var buf = Buffer.from(hex, "hex");
|
|
90
102
|
if (buf.length > 1 && buf[0] === 0x00 && (buf[1] & 0x80)) buf = buf.subarray(1);
|
|
@@ -485,6 +497,15 @@ function _extension(ext, pad) {
|
|
|
485
497
|
if (body != null) return header + "\n" + body;
|
|
486
498
|
} catch (_e) { }
|
|
487
499
|
}
|
|
500
|
+
/** @internal An extension whose own parser decoded it, which is how the attribute-certificate and
|
|
501
|
+
* revocation-list tables reach here: the certificate decoders above do not know those OIDs, and without
|
|
502
|
+
* this the decoded value fell through to a hex dump of the bytes it was decoded from. */
|
|
503
|
+
if (ext.decoded && typeof ext.decoded === "object" && ext.decoded.opaque !== true) {
|
|
504
|
+
var fromParser = _decodedRecord(ext.decoded, inner);
|
|
505
|
+
if (fromParser) return header + "\n" + fromParser;
|
|
506
|
+
}
|
|
507
|
+
var enriched = _extraFields(ext, _ROW_PLUMBING, inner);
|
|
508
|
+
if (enriched) return header + "\n" + enriched;
|
|
488
509
|
return header + "\n" + _fallback(ext.value, inner);
|
|
489
510
|
}
|
|
490
511
|
|
|
@@ -596,6 +617,7 @@ function _parseCms(input) { return _coerce(input, { marker: "contentType", looks
|
|
|
596
617
|
|
|
597
618
|
|
|
598
619
|
var OID_EXTENSION_REQUEST = oid.byName("extensionRequest");
|
|
620
|
+
var OID_SUBJECT_KEY_ID = oid.byName("subjectKeyIdentifier");
|
|
599
621
|
var OID_CONTENT_TYPE = oid.byName("contentType");
|
|
600
622
|
var OID_MESSAGE_DIGEST = oid.byName("messageDigest");
|
|
601
623
|
var OID_SIGNING_TIME = oid.byName("signingTime");
|
|
@@ -686,6 +708,75 @@ function crlReport(input) {
|
|
|
686
708
|
}
|
|
687
709
|
|
|
688
710
|
|
|
711
|
+
/** @internal Whether a value is one of the four shapes the name renderer reads: a parsed Name, a
|
|
712
|
+
* GeneralNames list, a decoded GeneralName, or a raw one still carrying its context tag. Anything else is a
|
|
713
|
+
* record and is rendered as its fields. */
|
|
714
|
+
function _looksLikeName(v) {
|
|
715
|
+
if (!v || typeof v !== "object" || Array.isArray(v)) return false;
|
|
716
|
+
if (v.dn !== undefined || Array.isArray(v.rdns) || Array.isArray(v.names)) return true;
|
|
717
|
+
return typeof v.tagNumber === "number" && (v.value !== undefined || Buffer.isBuffer(v.bytes));
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
/** @internal A decoded record as label/value lines. Each field is rendered by the vocabulary that already
|
|
721
|
+
* exists for its kind, because the alternative is string conversion, which prints a decoded value as its
|
|
722
|
+
* object notation. Depth is bounded so a record carrying a parsed structure cannot walk the whole of it. */
|
|
723
|
+
function _decodedRecord(rec, pad, depth) {
|
|
724
|
+
if (depth === undefined) depth = 3;
|
|
725
|
+
var lines = [];
|
|
726
|
+
Object.keys(rec).forEach(function (k) {
|
|
727
|
+
var v = rec[k];
|
|
728
|
+
if (v === null || v === undefined || k === "opaque") return;
|
|
729
|
+
if (Buffer.isBuffer(v)) { lines.push(pad + k + ": " + _hexColon(v, {})); return; }
|
|
730
|
+
if (v instanceof Date) { lines.push(pad + k + ": " + _date(v)); return; }
|
|
731
|
+
if (typeof v !== "object") { lines.push(pad + k + ": " + _clean(String(v))); return; }
|
|
732
|
+
/** @internal Only a value that LOOKS like a name is rendered as one. Asked of every record, the name
|
|
733
|
+
* renderer answers a non-name with the truthy `tag<undefined>:`, and that answer then stood in for the
|
|
734
|
+
* record's own fields: a group attribute rendered as that string instead of its memberships. */
|
|
735
|
+
if (_looksLikeName(v)) {
|
|
736
|
+
var asName = _nameOrGn(v);
|
|
737
|
+
if (asName && asName !== "(none)" && asName !== "(empty)" && asName !== "(unrecognized name form)") {
|
|
738
|
+
lines.push(pad + k + ": " + asName);
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
if (depth <= 0) return;
|
|
743
|
+
if (Array.isArray(v)) {
|
|
744
|
+
if (!v.length) return;
|
|
745
|
+
lines.push(pad + k + ":");
|
|
746
|
+
v.forEach(function (m) {
|
|
747
|
+
if (m === null || typeof m !== "object" || Buffer.isBuffer(m)) {
|
|
748
|
+
lines.push(pad + " " + (Buffer.isBuffer(m) ? _hexColon(m, {}) : _clean(String(m))));
|
|
749
|
+
} else {
|
|
750
|
+
var sub = _decodedRecord(m, pad + " ", depth - 1);
|
|
751
|
+
if (sub) lines.push(sub);
|
|
752
|
+
}
|
|
753
|
+
});
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
var nested = _decodedRecord(v, pad + " ", depth - 1);
|
|
757
|
+
if (nested) { lines.push(pad + k + ":"); lines.push(nested); }
|
|
758
|
+
});
|
|
759
|
+
return lines.join("\n");
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
/** @internal The fields a parser ENRICHED a row with, which are whatever it carries beyond the plumbing every
|
|
763
|
+
* row has. A table that decodes its own values attaches them here rather than under `decoded`, and routing
|
|
764
|
+
* such a row through a renderer that knows only the plumbing printed the bytes the parser had already read:
|
|
765
|
+
* an EST key-size constraint as its DER, an OCSP archive cutoff as a GeneralizedTime's octets. */
|
|
766
|
+
function _extraFields(row, skip, pad) {
|
|
767
|
+
var extra = Object.create(null), any = false;
|
|
768
|
+
Object.keys(row).forEach(function (k) {
|
|
769
|
+
if (skip.indexOf(k) !== -1) return;
|
|
770
|
+
var v = row[k];
|
|
771
|
+
if (v === null || v === undefined || v === false) return;
|
|
772
|
+
extra[k] = v;
|
|
773
|
+
any = true;
|
|
774
|
+
});
|
|
775
|
+
return any ? _decodedRecord(extra, pad) : "";
|
|
776
|
+
}
|
|
777
|
+
var _ROW_PLUMBING = ["oid", "name", "critical", "value", "valueBytes", "bytes", "scope", "index",
|
|
778
|
+
"containerIndex", "state", "code", "decoded", "kind", "values", "type", "extensions"];
|
|
779
|
+
|
|
689
780
|
function _attribute(attr, pad) {
|
|
690
781
|
var inner = pad + " ";
|
|
691
782
|
if (attr.type === OID_EXTENSION_REQUEST && Array.isArray(attr.extensions)) {
|
|
@@ -693,8 +784,27 @@ function _attribute(attr, pad) {
|
|
|
693
784
|
attr.extensions.forEach(function (ext) { lines.push(_extension(ext, inner)); });
|
|
694
785
|
return lines.join("\n");
|
|
695
786
|
}
|
|
696
|
-
|
|
697
|
-
|
|
787
|
+
/** @internal The registry lookup takes a dotted identifier, and a caller's object can carry anything in
|
|
788
|
+
* `type`: asked about a number it refuses with its own code, which is not this verb's contract. */
|
|
789
|
+
var registryName = null;
|
|
790
|
+
if (!attr.name && typeof attr.type === "string") {
|
|
791
|
+
try { registryName = oid.name(attr.type); }
|
|
792
|
+
// allow:swallow-unverified drop-silent display fallback: an unreadable type prints as itself
|
|
793
|
+
catch (_notAnIdentifier) { registryName = null; }
|
|
794
|
+
}
|
|
795
|
+
var header = pad + (attr.name || registryName || String(attr.type)) + ":";
|
|
796
|
+
/** @internal Where the parser DECODED the value, render that rather than the bytes it decoded from: an
|
|
797
|
+
* attribute certificate's whole content is the attributes it asserts, and a role naming a URI printed as
|
|
798
|
+
* a hex run said nothing a reader could act on. An entry the decoder left opaque still prints as bytes. */
|
|
799
|
+
var vals;
|
|
800
|
+
if (Array.isArray(attr.decoded) && attr.decoded.length === _list(attr.values).length) {
|
|
801
|
+
vals = attr.decoded.map(function (d, i) {
|
|
802
|
+
if (!d || typeof d !== "object" || d.opaque === true) return _attrValue(attr.type, attr.values[i], inner);
|
|
803
|
+
return _decodedRecord(d, inner) || _attrValue(attr.type, attr.values[i], inner);
|
|
804
|
+
});
|
|
805
|
+
} else {
|
|
806
|
+
vals = _list(attr.values).map(function (v) { return _attrValue(attr.type, v, inner); });
|
|
807
|
+
}
|
|
698
808
|
return vals.length ? header + "\n" + vals.join("\n") : header;
|
|
699
809
|
}
|
|
700
810
|
|
|
@@ -864,7 +974,1022 @@ function cmsReport(input) {
|
|
|
864
974
|
}
|
|
865
975
|
|
|
866
976
|
|
|
867
|
-
|
|
977
|
+
/** @internal The remaining detected formats, each composing the vocabulary above and each a named verb as
|
|
978
|
+
* well as a route from `any`.
|
|
979
|
+
*
|
|
980
|
+
* NO REPORT PRINTS PRIVATE KEY MATERIAL. A PKCS#8 and a PKCS#12 both carry a private key, and a renderer
|
|
981
|
+
* that wrote one out would make an inspection tool a way of reading a key out of a file somebody opened
|
|
982
|
+
* only to look at it. Each shows the algorithm, the public half where the structure carries one, and that
|
|
983
|
+
* a private key is present, never its bytes. `inspect-formats.test.js` searches each report for byte runs
|
|
984
|
+
* of the key in hex, base64 and colon-hex, so the rule is measured rather than stated here. */
|
|
985
|
+
|
|
986
|
+
/** @internal Each predicate asks whether the object has the STRUCTURE its parser returns, not whether it
|
|
987
|
+
* carries the marker property, because an object that merely has the marker is a caller's own object and
|
|
988
|
+
* a report rendered from one says what the caller wrote rather than what any bytes encode. The marker
|
|
989
|
+
* selects the predicate; the predicate is what admits the object. */
|
|
990
|
+
function _looksParsedPkcs8(o) {
|
|
991
|
+
return typeof o.version === "number" && o.privateKeyAlgorithm && typeof o.privateKeyAlgorithm === "object" &&
|
|
992
|
+
Buffer.isBuffer(o.privateKey) && Array.isArray(o.attributes);
|
|
993
|
+
}
|
|
994
|
+
function _looksParsedPkcs12(o) {
|
|
995
|
+
return typeof o.version === "number" && typeof o.integrityMode === "string" &&
|
|
996
|
+
Array.isArray(o.safeBags) && Array.isArray(o.encryptedSafes);
|
|
997
|
+
}
|
|
998
|
+
/** @internal Every message has to carry the structure the renderer reads. Checking only that `messages` is
|
|
999
|
+
* an array admitted `{ messages: [{}] }`, and the renderer then dereferenced a certTemplate that was not
|
|
1000
|
+
* there, so a caller's own object left an untyped TypeError where this door promises inspect/bad-input. */
|
|
1001
|
+
function _looksParsedCrmf(o) {
|
|
1002
|
+
if (!Array.isArray(o.messages) || !o.messages.length) return false;
|
|
1003
|
+
return o.messages.every(function (m) {
|
|
1004
|
+
return !!m && typeof m === "object" && !!m.certReq && typeof m.certReq === "object" &&
|
|
1005
|
+
!!m.certReq.certTemplate && typeof m.certReq.certTemplate === "object";
|
|
1006
|
+
});
|
|
1007
|
+
}
|
|
1008
|
+
function _looksParsedCmp(o) {
|
|
1009
|
+
return o.header && typeof o.header === "object" && o.body && typeof o.body === "object" &&
|
|
1010
|
+
typeof o.body.arm === "string";
|
|
1011
|
+
}
|
|
1012
|
+
/** @internal Every item has to be one of the two shapes the parser produces, a bare object identifier or an
|
|
1013
|
+
* attribute carrying a values array, because the report reads both: an array of nullish or wrong-typed
|
|
1014
|
+
* members faulted the renderer where this door promises a typed refusal. */
|
|
1015
|
+
function _looksParsedCsrattrs(o) {
|
|
1016
|
+
if (!Array.isArray(o.items)) return false;
|
|
1017
|
+
return o.items.every(function (it) {
|
|
1018
|
+
if (!it || typeof it !== "object") return false;
|
|
1019
|
+
if (it.kind === "oid") return true;
|
|
1020
|
+
return it.kind === "attribute" && (it.values === undefined || it.values === null || Array.isArray(it.values));
|
|
1021
|
+
});
|
|
1022
|
+
}
|
|
1023
|
+
/** @internal Each anchor has to carry the member its own kind names, because that is what the report reads:
|
|
1024
|
+
* an anchor with a kind and nothing under it faulted the renderer where this door promises a typed
|
|
1025
|
+
* refusal. */
|
|
1026
|
+
function _looksParsedTrustAnchor(o) {
|
|
1027
|
+
if (!Array.isArray(o.anchors) || !o.anchors.length) return false;
|
|
1028
|
+
return o.anchors.every(function (a) {
|
|
1029
|
+
if (!a || typeof a !== "object" || typeof a.kind !== "string") return false;
|
|
1030
|
+
var held = a.kind === "certificate" ? a.certificate : (a.kind === "tbsCert" ? a.tbsCert : a.taInfo);
|
|
1031
|
+
return !!held && typeof held === "object";
|
|
1032
|
+
});
|
|
1033
|
+
}
|
|
1034
|
+
function _looksParsedOcspReq(o) {
|
|
1035
|
+
if (typeof o.version !== "number" || !Array.isArray(o.requestList) || !Buffer.isBuffer(o.tbsRequestBytes)) return false;
|
|
1036
|
+
return o.requestList.every(function (r) { return !!r && typeof r === "object" && !!r.certID && typeof r.certID === "object"; });
|
|
1037
|
+
}
|
|
1038
|
+
function _looksParsedOcspResp(o) {
|
|
1039
|
+
var st = o.responseStatus;
|
|
1040
|
+
if (!st || typeof st !== "object" || typeof st.code !== "number" || typeof st.name !== "string") return false;
|
|
1041
|
+
var br = o.basicResponse;
|
|
1042
|
+
/** @internal A response with no basic response is a status-only answer the report handles, but one that
|
|
1043
|
+
* HAS a basic response has to carry the answers the report walks: a responderID and a responses list whose
|
|
1044
|
+
* entries name a CertID and a status. */
|
|
1045
|
+
if (br === null || br === undefined) return true;
|
|
1046
|
+
if (typeof br !== "object" || !br.responderID || typeof br.responderID !== "object") return false;
|
|
1047
|
+
if (!Array.isArray(br.responses)) return false;
|
|
1048
|
+
return br.responses.every(function (r) {
|
|
1049
|
+
return !!r && typeof r === "object" && !!r.certID && typeof r.certID === "object" &&
|
|
1050
|
+
!!r.certStatus && typeof r.certStatus === "object";
|
|
1051
|
+
});
|
|
1052
|
+
}
|
|
1053
|
+
/** @internal A rejected or waiting response carries `timeStampToken: null`, which is the shape RFC 3161
|
|
1054
|
+
* sec. 2.4.2 gives a non-granted status, so the token is required to be PRESENT as a key and not to be an
|
|
1055
|
+
* object. Requiring an object here refused a parsed rejection that the same bytes rendered fine. */
|
|
1056
|
+
/** @internal RFC 3161 sec. 2.4.2's PKIStatus. */
|
|
1057
|
+
var TSP_STATUS_NAMES = Object.assign(Object.create(null), {
|
|
1058
|
+
0: "granted", 1: "grantedWithMods", 2: "rejection", 3: "waiting",
|
|
1059
|
+
4: "revocationWarning", 5: "revocationNotification",
|
|
1060
|
+
});
|
|
1061
|
+
|
|
1062
|
+
function _looksParsedTsp(o) {
|
|
1063
|
+
if (typeof o.status !== "number" || !("timeStampToken" in o)) return false;
|
|
1064
|
+
/** @internal PKIFailureInfo is a bit record whose asserted names are an array, so a present failInfo of any
|
|
1065
|
+
* other shape is a caller's own object and not this parser's output. The readers below still fault on
|
|
1066
|
+
* nothing, and this makes an obviously wrong shape a typed refusal rather than an empty line. */
|
|
1067
|
+
if (o.failInfo !== null && o.failInfo !== undefined) {
|
|
1068
|
+
if (typeof o.failInfo !== "object" || !Array.isArray(o.failInfo.bits)) return false;
|
|
1069
|
+
}
|
|
1070
|
+
var tok = o.timeStampToken;
|
|
1071
|
+
return tok === null || (!!tok && typeof tok === "object" && !!tok.tstInfo && typeof tok.tstInfo === "object");
|
|
1072
|
+
}
|
|
1073
|
+
function _looksParsedAttrCert(o) {
|
|
1074
|
+
return typeof o.version === "number" && o.holder && typeof o.holder === "object" &&
|
|
1075
|
+
!!o.issuer && typeof o.issuer === "object" &&
|
|
1076
|
+
Array.isArray(o.attributes) && o.signatureAlgorithm && typeof o.signatureAlgorithm === "object";
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
function _parsePkcs8(input) { return _coerce(input, { marker: "privateKeyAlgorithm", looksParsed: _looksParsedPkcs8, parsedName: "pki.schema.pkcs8.parse", parse: pkcs8.parse, badCode: "inspect/bad-pkcs8", what: "PKCS#8 private key" }); }
|
|
1080
|
+
function _parsePkcs12(input) { return _coerce(input, { marker: "safeBags", looksParsed: _looksParsedPkcs12, parsedName: "pki.schema.pkcs12.parse", parse: pkcs12.parse, badCode: "inspect/bad-pkcs12", what: "PKCS#12 store" }); }
|
|
1081
|
+
function _parseCrmf(input) { return _coerce(input, { marker: "messages", looksParsed: _looksParsedCrmf, parsedName: "pki.schema.crmf.parse", parse: crmf.parse, badCode: "inspect/bad-crmf", what: "CRMF certificate request message" }); }
|
|
1082
|
+
function _parseCmp(input) { return _coerce(input, { marker: "header", looksParsed: _looksParsedCmp, parsedName: "pki.schema.cmp.parse", parse: cmp.parse, badCode: "inspect/bad-cmp", what: "CMP message" }); }
|
|
1083
|
+
function _parseCsrattrs(input) { return _coerce(input, { marker: "items", looksParsed: _looksParsedCsrattrs, parsedName: "pki.schema.csrattrs.parse", parse: csrattrs.parse, badCode: "inspect/bad-csrattrs", what: "CSR attributes response" }); }
|
|
1084
|
+
function _parseTrustAnchor(input) { return _coerce(input, { marker: "anchors", looksParsed: _looksParsedTrustAnchor, parsedName: "pki.schema.trustanchor.parse", parse: trustanchor.parse, badCode: "inspect/bad-trustanchor", what: "trust anchor list" }); }
|
|
1085
|
+
function _parseOcspReq(input) { return _coerce(input, { marker: "requestList", looksParsed: _looksParsedOcspReq, parsedName: "pki.schema.ocsp.parseRequest", parse: ocsp.parseRequest, badCode: "inspect/bad-ocsp-request", what: "OCSP request" }); }
|
|
1086
|
+
function _parseOcspResp(input) { return _coerce(input, { marker: "responseStatus", looksParsed: _looksParsedOcspResp, parsedName: "pki.schema.ocsp.parseResponse", parse: ocsp.parseResponse, badCode: "inspect/bad-ocsp-response", what: "OCSP response" }); }
|
|
1087
|
+
function _parseTsp(input) { return _coerce(input, { marker: "timeStampToken", looksParsed: _looksParsedTsp, parsedName: "pki.schema.tsp.parseResponse", parse: tsp.parseResponse, badCode: "inspect/bad-tsp", what: "timestamp response" }); }
|
|
1088
|
+
function _parseAttrCert(input) { return _coerce(input, { marker: "holder", looksParsed: _looksParsedAttrCert, parsedName: "pki.schema.attrcert.parse", parse: attrcert.parse, badCode: "inspect/bad-attrcert", what: "attribute certificate" }); }
|
|
1089
|
+
|
|
1090
|
+
/** @internal A GeneralName, a distinguished name, a GeneralNames list, or nothing, whichever the field
|
|
1091
|
+
* carries. These structures reach a name through four different shapes and a report reads one the same
|
|
1092
|
+
* way wherever it sits. The list form arrives with each member still raw, carrying its context tag and
|
|
1093
|
+
* its bytes and no decoded value, so each is read through the attribute-certificate module's own
|
|
1094
|
+
* GeneralName reader rather than rendered from the wrapper, which is what printed the whole list as one
|
|
1095
|
+
* hex run under a tag number the wrapper does not have. */
|
|
1096
|
+
function _nameOrGn(v) {
|
|
1097
|
+
if (!v || typeof v !== "object") return "(none)";
|
|
1098
|
+
if (v.dn !== undefined || Array.isArray(v.rdns)) return _dnString(v) || "(empty)";
|
|
1099
|
+
if (Array.isArray(v.names)) {
|
|
1100
|
+
var parts = v.names.map(function (n) {
|
|
1101
|
+
if (n && n.value !== undefined) return _gn(n);
|
|
1102
|
+
if (n && Buffer.isBuffer(n.bytes)) {
|
|
1103
|
+
try { return _gn(attrcert.readGeneralName(n.bytes)); }
|
|
1104
|
+
// allow:swallow-unverified drop-silent display fallback: an unreadable name prints as its bytes
|
|
1105
|
+
catch (_unreadable) { return _gnRaw(n.bytes); }
|
|
1106
|
+
}
|
|
1107
|
+
return "";
|
|
1108
|
+
}).filter(function (s) { return s !== ""; });
|
|
1109
|
+
return parts.length ? parts.join(", ") : "(empty)";
|
|
1110
|
+
}
|
|
1111
|
+
/** @internal A STANDALONE GeneralName arrives raw from some parsers exactly as a list member does, and it
|
|
1112
|
+
* needs the same decode: passed straight to the renderer, a directoryName printed as its label with no
|
|
1113
|
+
* name after it and every other form printed its bytes. The list path had this and the single path did
|
|
1114
|
+
* not, which is one rule applied to one of its two callers. */
|
|
1115
|
+
if (v.value === undefined && Buffer.isBuffer(v.bytes) && typeof v.tagNumber === "number") {
|
|
1116
|
+
try { return _gn(attrcert.readGeneralName(v.bytes)); }
|
|
1117
|
+
// allow:swallow-unverified drop-silent display fallback: an unreadable name prints as its bytes
|
|
1118
|
+
catch (_unreadableSingle) { return _gnRaw(v.bytes); }
|
|
1119
|
+
}
|
|
1120
|
+
var g = _gn(v);
|
|
1121
|
+
return g || "(unrecognized name form)";
|
|
1122
|
+
}
|
|
1123
|
+
function _optLine(L, pad, label, v) { if (v !== null && v !== undefined) L.push(pad + label + ": " + v); }
|
|
1124
|
+
|
|
1125
|
+
/** @internal Join a short list the way a sentence does. */
|
|
1126
|
+
function _joinAnd(list) {
|
|
1127
|
+
if (list.length <= 1) return list.join("");
|
|
1128
|
+
return list.slice(0, -1).join(", ") + " and " + list[list.length - 1];
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
/** @internal A list is a list only when it IS one. `x && x.length` is true of a string and of anything else
|
|
1132
|
+
* carrying a length, and `(x || [])` passes a truthy non-array straight through, so both admitted a value
|
|
1133
|
+
* the following forEach then faulted on. These reports take a caller's pre-parsed object, whose members no
|
|
1134
|
+
* door can validate to arbitrary depth, so a report must not fault on any shape it is handed. */
|
|
1135
|
+
function _list(v) { return Array.isArray(v) ? v : []; }
|
|
1136
|
+
/** @internal The same for a member a loop body dereferences. */
|
|
1137
|
+
function _obj(v) { return (v !== null && typeof v === "object") ? v : {}; }
|
|
1138
|
+
|
|
1139
|
+
/** @internal A serial reads the same wherever a report carries one, through the certificate report's own
|
|
1140
|
+
* presentation: small values as decimal with the hex beside them, long ones as wrapped colon-hex. Printed
|
|
1141
|
+
* as bare hex, the serial 99 renders "63" and reads as the decimal 63. */
|
|
1142
|
+
function _serialLine(hexOrValue, pad) {
|
|
1143
|
+
if (hexOrValue === null || hexOrValue === undefined) return null;
|
|
1144
|
+
/** @internal Only the two forms a parser produces, a hex string or a bigint. Anything else is a caller's
|
|
1145
|
+
* own value, and `toString(16)` on it reaches the hex reader with something that is not hex. */
|
|
1146
|
+
var hex;
|
|
1147
|
+
if (typeof hexOrValue === "string") hex = hexOrValue;
|
|
1148
|
+
else if (typeof hexOrValue === "bigint" || typeof hexOrValue === "number") hex = hexOrValue.toString(16);
|
|
1149
|
+
else return null;
|
|
1150
|
+
if (!_isHexDigits(hex)) return null;
|
|
1151
|
+
return pad + _serial({ serialNumberHex: hex }, pad.length + 4);
|
|
1152
|
+
}
|
|
1153
|
+
/** @internal Whether every character is a hex digit, by character code so no regex is involved. */
|
|
1154
|
+
function _isHexDigits(s) {
|
|
1155
|
+
if (typeof s !== "string" || s.length === 0) return false;
|
|
1156
|
+
for (var i = 0; i < s.length; i++) {
|
|
1157
|
+
var c = s.charCodeAt(i);
|
|
1158
|
+
var ok = (c >= 0x30 && c <= 0x39) || (c >= 0x61 && c <= 0x66) || (c >= 0x41 && c <= 0x46);
|
|
1159
|
+
if (!ok) return false;
|
|
1160
|
+
}
|
|
1161
|
+
return true;
|
|
1162
|
+
}
|
|
1163
|
+
function _serialInto(L, pad, hexOrValue) {
|
|
1164
|
+
var line = _serialLine(hexOrValue, pad);
|
|
1165
|
+
if (line !== null) L.push(line);
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
/**
|
|
1169
|
+
* @primitive pki.inspect.pkcs8
|
|
1170
|
+
* @signature pki.inspect.pkcs8(input) -> string
|
|
1171
|
+
* @since 0.8.29
|
|
1172
|
+
* @status stable
|
|
1173
|
+
* @spec RFC 5958
|
|
1174
|
+
* @related pki.schema.pkcs8.parse, pki.inspect.pkcs12
|
|
1175
|
+
*
|
|
1176
|
+
* Render a PKCS#8 private key as a text report: the version, the key algorithm and
|
|
1177
|
+
* its parameters, the presence and length of the private key and of a public half,
|
|
1178
|
+
* and the type of each attribute. No byte string from inside the file is rendered.
|
|
1179
|
+
* That is structural rather than a screen on the private key, because RFC 5958 has
|
|
1180
|
+
* the parser check that a v2 `OneAsymmetricKey` HAS a public half and well formed
|
|
1181
|
+
* attributes, never that either is unrelated to the private key: private bytes can
|
|
1182
|
+
* sit in the public `BIT STRING` or in any attribute value, so a report that wrote
|
|
1183
|
+
* either out could be made to write the key out. `pki.key.export` is the verb for
|
|
1184
|
+
* the bytes themselves.
|
|
1185
|
+
* `input` is a PEM string, a DER Buffer, or a `pki.schema.pkcs8.parse` result; a
|
|
1186
|
+
* non-PKCS#8 throws `inspect/bad-pkcs8`, a wrong-type input `inspect/bad-input`.
|
|
1187
|
+
*
|
|
1188
|
+
* @example
|
|
1189
|
+
* var pair = await pki.key.generate("Ed25519");
|
|
1190
|
+
* pki.inspect.pkcs8(await pki.key.export(pair.privateKey)).split("\n")[0]; // "PKCS#8 Private Key:"
|
|
1191
|
+
*/
|
|
1192
|
+
function pkcs8Report(input) {
|
|
1193
|
+
var k = _parsePkcs8(input);
|
|
1194
|
+
var L = ["PKCS#8 Private Key:", " Version: " + k.version];
|
|
1195
|
+
L.push(" Private Key Algorithm: " + _algName(k.privateKeyAlgorithm));
|
|
1196
|
+
/** @internal The algorithm's PARAMETERS say which curve an EC key is on, and without them an EC report
|
|
1197
|
+
* named only `ecPublicKey` and identified nothing. They are public: an AlgorithmIdentifier's parameters
|
|
1198
|
+
* describe the algorithm, not the key, so rendering them discloses nothing the key itself holds. */
|
|
1199
|
+
var alg = k.privateKeyAlgorithm;
|
|
1200
|
+
if (alg && Buffer.isBuffer(alg.parameters)) {
|
|
1201
|
+
var paramName;
|
|
1202
|
+
try { paramName = oid.name(asn1.read.oid(asn1.decode(alg.parameters))); }
|
|
1203
|
+
// allow:swallow-unverified drop-silent display fallback: parameters that are not an OID are not named
|
|
1204
|
+
catch (_notAnOid) { paramName = null; }
|
|
1205
|
+
/** @internal Named or not rendered. An AlgorithmIdentifier's parameters are whatever the file's author
|
|
1206
|
+
* put there, so a hex fallback here is another way to write the private key out: the parameters of an
|
|
1207
|
+
* unknown algorithm can hold a copy of it and the parser has no reason to refuse that. */
|
|
1208
|
+
L.push(" Algorithm Parameters: " + (paramName ||
|
|
1209
|
+
("present, " + alg.parameters.length + " bytes (not rendered)")));
|
|
1210
|
+
}
|
|
1211
|
+
L.push(" Private Key: present, " + k.privateKey.length + " bytes (not rendered)");
|
|
1212
|
+
/** @internal The public half is rendered only when the bytes are actually there: an RFC 5958 OneAsymmetricKey
|
|
1213
|
+
* carries it as a BIT STRING record and a caller's object can carry anything, and the byte renderer faults
|
|
1214
|
+
* on a value that is not a buffer rather than reporting one it cannot read. */
|
|
1215
|
+
/** @internal THIS REPORT RENDERS NO BYTES FROM THE FILE, and the rule is structural rather than a
|
|
1216
|
+
* comparison because a comparison loses. RFC 5958's parser checks that a v2 OneAsymmetricKey HAS a public
|
|
1217
|
+
* half and that its attributes are well formed, never that either is unrelated to the private key, so
|
|
1218
|
+
* private bytes can sit in the public BIT STRING or in any attribute value. Screening each against the
|
|
1219
|
+
* private key needs the key's algorithm-specific inner encoding, since an Ed25519 privateKey is
|
|
1220
|
+
* `04 20 || seed` and a crafted public half carrying the seed alone matches neither direction of a
|
|
1221
|
+
* whole-buffer compare; deriving the public half instead would be a key operation, which this synchronous
|
|
1222
|
+
* renderer cannot do. So nothing from inside the file is written out: the report names the algorithm, its
|
|
1223
|
+
* parameters, what is present and how long, and the attribute types, and there is nothing left to screen.
|
|
1224
|
+
* `pki.key.export` is the verb for the bytes themselves. */
|
|
1225
|
+
var pubBytes = Buffer.isBuffer(k.publicKey) ? k.publicKey : _obj(k.publicKey).bytes;
|
|
1226
|
+
if (Buffer.isBuffer(pubBytes)) {
|
|
1227
|
+
L.push(" Public Key: present, " + pubBytes.length + " bytes (not rendered)");
|
|
1228
|
+
} else if (k.publicKey) {
|
|
1229
|
+
L.push(" Public Key: present, not readable as bytes");
|
|
1230
|
+
}
|
|
1231
|
+
L.push(" Attributes:");
|
|
1232
|
+
if (_list(k.attributes).length) {
|
|
1233
|
+
_list(k.attributes).forEach(function (raw) {
|
|
1234
|
+
var a = _obj(raw);
|
|
1235
|
+
/** @internal The registry takes a dotted identifier and refuses anything else with its own code, which
|
|
1236
|
+
* is not this verb's contract: a caller's object can carry any `type`. */
|
|
1237
|
+
var known = null;
|
|
1238
|
+
if (!a.name && typeof a.type === "string") {
|
|
1239
|
+
try { known = oid.name(a.type); }
|
|
1240
|
+
// allow:swallow-unverified drop-silent display fallback: an unreadable type prints as itself
|
|
1241
|
+
catch (_notAnIdentifier) { known = null; }
|
|
1242
|
+
}
|
|
1243
|
+
L.push(" " + (a.name || known || String(a.type)) + ": " +
|
|
1244
|
+
_list(a.values).length + " value(s) (not rendered)");
|
|
1245
|
+
});
|
|
1246
|
+
} else {
|
|
1247
|
+
L.push(" (none)");
|
|
1248
|
+
}
|
|
1249
|
+
return L.join("\n") + "\n";
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
/**
|
|
1253
|
+
* @primitive pki.inspect.pkcs12
|
|
1254
|
+
* @signature pki.inspect.pkcs12(input) -> string
|
|
1255
|
+
* @since 0.8.29
|
|
1256
|
+
* @status stable
|
|
1257
|
+
* @spec RFC 7292
|
|
1258
|
+
* @related pki.schema.pkcs12.parse, pki.pkcs12.open
|
|
1259
|
+
*
|
|
1260
|
+
* Render a PKCS#12 store's outer structure as a text report: the version, the
|
|
1261
|
+
* integrity mode, the MAC algorithm and its iteration count, and one line per safe
|
|
1262
|
+
* bag giving its type. No private key is rendered, and an encrypted safe's contents
|
|
1263
|
+
* are not decrypted, because this verb takes no password: the report names
|
|
1264
|
+
* `pki.pkcs12.open` as what reads the contents. `input` is a PEM string, a DER
|
|
1265
|
+
* Buffer, or a `pki.schema.pkcs12.parse` result; a non-PKCS#12 throws
|
|
1266
|
+
* `inspect/bad-pkcs12`, a wrong-type input `inspect/bad-input`.
|
|
1267
|
+
*
|
|
1268
|
+
* @example
|
|
1269
|
+
* var pair = await pki.key.generate("Ed25519");
|
|
1270
|
+
* var key = await pki.key.export(pair.privateKey);
|
|
1271
|
+
* var cert = await pki.x509.sign({ subject: "p12.example", subjectPublicKey: await pki.key.export(pair.publicKey),
|
|
1272
|
+
* notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") }, { key: key });
|
|
1273
|
+
* var store = await pki.pkcs12.build({ safeContents: [{ bags: [{ type: "cert", cert: cert }] }] }, { password: "1234" });
|
|
1274
|
+
* pki.inspect.pkcs12(store).split("\n")[0]; // "PKCS#12 Store:"
|
|
1275
|
+
*/
|
|
1276
|
+
/** @internal A salt's LENGTH is what says anything about the protection; its value says nothing a reader acts
|
|
1277
|
+
* on. And `pki.pkcs12.build` takes a caller-supplied salt, so a store can carry the private key bytes there
|
|
1278
|
+
* and a report printing the value would write the key out. Length only, for the same reason the key report
|
|
1279
|
+
* renders no bytes at all. */
|
|
1280
|
+
function _saltLine(L, salt) {
|
|
1281
|
+
if (Buffer.isBuffer(salt)) L.push(" Salt: " + salt.length + " bytes (not rendered)");
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
/** @internal Whether any signer of a signed authSafe has NO certificate embedded beside it. A store can embed
|
|
1285
|
+
* a chain certificate while leaving the signer's own out, so counting the certificates answered the wrong
|
|
1286
|
+
* question: verification needs the certificate that MATCHES the signer. A SignerInfo names itself by issuer
|
|
1287
|
+
* and serial or by subject key identifier, and an embedded certificate arrives raw, so each is parsed to
|
|
1288
|
+
* compare. A certificate this parser refuses simply matches nothing. */
|
|
1289
|
+
function _someSignerUnmatched(signed) {
|
|
1290
|
+
var signers = _list(signed.signerInfos);
|
|
1291
|
+
if (!signers.length) return false;
|
|
1292
|
+
var parsed = [];
|
|
1293
|
+
_list(signed.certificates).forEach(function (raw) {
|
|
1294
|
+
var bytes = Buffer.isBuffer(raw) ? raw : _obj(raw).bytes;
|
|
1295
|
+
if (!Buffer.isBuffer(bytes)) return;
|
|
1296
|
+
// allow:swallow-unverified drop-silent display fallback: a certificate this parser refuses matches nothing
|
|
1297
|
+
try { parsed.push(x509.parse(bytes)); } catch (_unreadableCert) { return; }
|
|
1298
|
+
});
|
|
1299
|
+
return signers.some(function (rawSigner) {
|
|
1300
|
+
var sid = _obj(_obj(rawSigner).sid);
|
|
1301
|
+
return !parsed.some(function (c) { return _certMatchesSid(c, sid); });
|
|
1302
|
+
});
|
|
1303
|
+
}
|
|
1304
|
+
function _certMatchesSid(cert, sid) {
|
|
1305
|
+
if (Buffer.isBuffer(sid.subjectKeyIdentifier)) {
|
|
1306
|
+
var ski = _list(cert.extensions).filter(function (e) { return _obj(e).oid === OID_SUBJECT_KEY_ID; })[0];
|
|
1307
|
+
if (!ski) return false;
|
|
1308
|
+
// allow:swallow-unverified drop-silent display fallback: an unreadable key id matches nothing
|
|
1309
|
+
try { return asn1.read.octetString(asn1.decode(_obj(ski).value)).equals(sid.subjectKeyIdentifier); }
|
|
1310
|
+
catch (_unreadableSki) { return false; }
|
|
1311
|
+
}
|
|
1312
|
+
if (!sid.issuer || sid.serialNumberHex === undefined) return false;
|
|
1313
|
+
if (cert.serialNumberHex !== sid.serialNumberHex) return false;
|
|
1314
|
+
/** @internal The RFC 5280 sec. 7.1 canonical comparison, through the shared home, because two encodings of
|
|
1315
|
+
* one name are one name: compared as bytes, a certificate whose issuer DN is encoded differently from the
|
|
1316
|
+
* SignerInfo's matched nothing and the report asked for a certificate the store already had. */
|
|
1317
|
+
var a = _obj(cert.issuer).rdns, b2 = _obj(sid.issuer).rdns;
|
|
1318
|
+
if (Array.isArray(a) && Array.isArray(b2)) {
|
|
1319
|
+
// allow:swallow-unverified drop-silent display fallback: a name the comparison refuses falls back to bytes
|
|
1320
|
+
try { return guard.name.dnEqual(a, b2, _err, "inspect/bad-input", "the signer name") === true; }
|
|
1321
|
+
catch (_notComparable) { return false; }
|
|
1322
|
+
}
|
|
1323
|
+
var issuerBytes = _obj(sid.issuer).bytes;
|
|
1324
|
+
return Buffer.isBuffer(issuerBytes) && Buffer.isBuffer(_obj(cert.issuer).bytes) &&
|
|
1325
|
+
_obj(cert.issuer).bytes.equals(issuerBytes);
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
function pkcs12Report(input) {
|
|
1329
|
+
var p = _parsePkcs12(input);
|
|
1330
|
+
var L = ["PKCS#12 Store:", " Version: " + p.version];
|
|
1331
|
+
L.push(" Integrity Mode: " + p.integrityMode);
|
|
1332
|
+
if (p.mac) {
|
|
1333
|
+
L.push(" MAC:");
|
|
1334
|
+
L.push(" Kind: " + p.mac.kind);
|
|
1335
|
+
/** @internal RFC 9579's PBMAC1 carries the parameters that actually protect the store inside its own
|
|
1336
|
+
* structure, and the outer MacData fields then describe nothing: read from there, a store with 2048
|
|
1337
|
+
* PBKDF2 iterations reported one iteration, an empty salt, and "pbmac1" as its digest. */
|
|
1338
|
+
if (p.mac.pbmac1) {
|
|
1339
|
+
var kdf = p.mac.pbmac1.kdf || {};
|
|
1340
|
+
_optLine(L, " ", "Digest", p.mac.pbmac1.schemeName || p.mac.pbmac1.schemeOid);
|
|
1341
|
+
_optLine(L, " ", "Key Derivation", kdf.prfName || kdf.prfOid);
|
|
1342
|
+
_optLine(L, " ", "Iterations", kdf.iterationCount);
|
|
1343
|
+
_optLine(L, " ", "Key Length", kdf.keyLength);
|
|
1344
|
+
_saltLine(L, kdf.salt);
|
|
1345
|
+
} else {
|
|
1346
|
+
_optLine(L, " ", "Digest", p.mac.hashName || p.mac.hashOid);
|
|
1347
|
+
_optLine(L, " ", "Iterations", p.mac.iterations);
|
|
1348
|
+
_saltLine(L, p.mac.macSalt);
|
|
1349
|
+
}
|
|
1350
|
+
} else {
|
|
1351
|
+
L.push(" MAC: (none)");
|
|
1352
|
+
}
|
|
1353
|
+
L.push(" Safe Bags: " + _list(p.safeBags).length);
|
|
1354
|
+
/** @internal RFC 7292 sec. 4.2.6's safeContentsBag holds further bags, and reporting only the container
|
|
1355
|
+
* made a container of keys and certificates read the same as an empty one. The no-key-material rule
|
|
1356
|
+
* carries into the nested level, because a key inside a container is still a key. */
|
|
1357
|
+
(function walkBags(bags, pad) {
|
|
1358
|
+
_list(bags).forEach(function (rawBag, i) {
|
|
1359
|
+
var bag = _obj(rawBag);
|
|
1360
|
+
/** @internal The PARSER's vocabulary, which is the ASN.1 bag-type name, not the builder's shorthand:
|
|
1361
|
+
* keyed on `shroudedKey` and `key` the marker never once fired, because a parsed bag reports itself as
|
|
1362
|
+
* `pkcs8ShroudedKeyBag` or `keyBag`. */
|
|
1363
|
+
var isKey = bag.type === "pkcs8ShroudedKeyBag" || bag.type === "keyBag";
|
|
1364
|
+
L.push(pad + "[" + i + "] " + bag.type + (isKey ? " (private key, not rendered)" : "") +
|
|
1365
|
+
(Array.isArray(bag.nested) ? " (" + bag.nested.length + " nested)" : ""));
|
|
1366
|
+
if (Array.isArray(bag.nested) && bag.nested.length) walkBags(bag.nested, pad + " ");
|
|
1367
|
+
});
|
|
1368
|
+
})(p.safeBags, " ");
|
|
1369
|
+
L.push(" Encrypted Safes: " + _list(p.encryptedSafes).length);
|
|
1370
|
+
var needsRecipientKey = false, needsSafePassword = false;
|
|
1371
|
+
_list(p.encryptedSafes).forEach(function (rawSafe, i) {
|
|
1372
|
+
var safe = _obj(rawSafe);
|
|
1373
|
+
if (safe.type === "envelopedData") needsRecipientKey = true;
|
|
1374
|
+
else needsSafePassword = true;
|
|
1375
|
+
/** @internal An encrypted safe is a CMS EncryptedData, so the algorithm that protects it sits on that
|
|
1376
|
+
* structure's encryptedContentInfo rather than on the safe. Read off the safe it was absent and every
|
|
1377
|
+
* encrypted store reported its algorithm as unknown. */
|
|
1378
|
+
var eci = _obj(safe.content).encryptedContentInfo;
|
|
1379
|
+
/** @internal The safe's TYPE says which credential opens it, so the row names it: RFC 7292 sec. 4.1's
|
|
1380
|
+
* password privacy is an EncryptedData and its public-key privacy an EnvelopedData, and the two are
|
|
1381
|
+
* opened by different things. */
|
|
1382
|
+
L.push(" [" + i + "] " + (safe.type ? safe.type + ": " : "") +
|
|
1383
|
+
_algName(_obj(eci).contentEncryptionAlgorithm) + " (not decrypted)");
|
|
1384
|
+
});
|
|
1385
|
+
/** @internal WHICH credential opens the contents, because naming the wrong one sends an operator to look
|
|
1386
|
+
* for something the store does not want: RFC 7292 sec. 4.1's public-key privacy is opened with a recipient
|
|
1387
|
+
* key and `pki.pkcs12.open` refuses it with pkcs12/no-recipient-key, not with a password prompt. */
|
|
1388
|
+
var needs = [];
|
|
1389
|
+
/** @internal Derived from what the store IS, not from whether it has encrypted safes: a public-key-integrity
|
|
1390
|
+
* store of plaintext bags is opened by its signature, a MAC-less one needs opts.allowUnauthenticated, and a
|
|
1391
|
+
* password is wanted for the MAC, for a password-encrypted safe, or for a shrouded key bag. Read off an
|
|
1392
|
+
* empty encryptedSafes list, the report asked for a password from stores that do not want one. */
|
|
1393
|
+
var hasShroudedKey = (function anyShrouded(bags) {
|
|
1394
|
+
return _list(bags).some(function (raw) {
|
|
1395
|
+
var bag = _obj(raw);
|
|
1396
|
+
return bag.type === "pkcs8ShroudedKeyBag" || anyShrouded(bag.nested);
|
|
1397
|
+
});
|
|
1398
|
+
})(p.safeBags);
|
|
1399
|
+
if (p.integrityMode === "password" || needsSafePassword || hasShroudedKey) needs.push("the password");
|
|
1400
|
+
if (needsRecipientKey) needs.push("opts.recipientKey for the enveloped safe");
|
|
1401
|
+
/** @internal A store can want a credential AND be unauthenticated, and `pki.pkcs12.open` checks both: a
|
|
1402
|
+
* MAC-less store holding a shrouded key needs the password for the key and the opt for the missing MAC.
|
|
1403
|
+
* Offered as an alternative to the others, the opt went unmentioned for exactly those stores. */
|
|
1404
|
+
if (p.integrityMode === "none") needs.push("opts.allowUnauthenticated, since the store carries no MAC");
|
|
1405
|
+
/** @internal A public-key-integrity store is verified against the signer's certificate, and its SignedData
|
|
1406
|
+
* may carry none: then the certificate has to be supplied, and a report naming only the privacy credentials
|
|
1407
|
+
* left out an input without which opening fails. */
|
|
1408
|
+
if (p.integrityMode === "public-key" && _someSignerUnmatched(_obj(p.authSafeSigned))) {
|
|
1409
|
+
needs.push("opts.signerCerts, since the signed authSafe carries no certificate matching its signer");
|
|
1410
|
+
}
|
|
1411
|
+
if (needs.length) {
|
|
1412
|
+
L.push(" The bag contents are read with pki.pkcs12.open, which needs " + _joinAnd(needs) + ".");
|
|
1413
|
+
} else {
|
|
1414
|
+
L.push(" The bag contents are read with pki.pkcs12.open. This store's integrity is its signature and its bags are not encrypted, so it needs no password.");
|
|
1415
|
+
}
|
|
1416
|
+
return L.join("\n") + "\n";
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
/**
|
|
1420
|
+
* @primitive pki.inspect.crmf
|
|
1421
|
+
* @signature pki.inspect.crmf(input) -> string
|
|
1422
|
+
* @since 0.8.29
|
|
1423
|
+
* @status stable
|
|
1424
|
+
* @spec RFC 4211
|
|
1425
|
+
* @related pki.schema.crmf.parse, pki.inspect.csr
|
|
1426
|
+
*
|
|
1427
|
+
* Render CRMF certificate request messages as a text report: one block per message
|
|
1428
|
+
* giving its request id, the certificate template's subject, issuer, validity and
|
|
1429
|
+
* public key, the requested extensions, and whether a proof of possession and
|
|
1430
|
+
* registration controls are present. `input` is a PEM string, a DER Buffer, or a
|
|
1431
|
+
* `pki.schema.crmf.parse` result; a non-CRMF throws `inspect/bad-crmf`, a
|
|
1432
|
+
* wrong-type input `inspect/bad-input`.
|
|
1433
|
+
*
|
|
1434
|
+
* @example
|
|
1435
|
+
* var pair = await pki.key.generate("Ed25519");
|
|
1436
|
+
* var der = await pki.crmf.build({ certReqId: 0, certTemplate: { subject: "crmf.example",
|
|
1437
|
+
* publicKey: await pki.key.export(pair.publicKey) } }, { key: await pki.key.export(pair.privateKey) });
|
|
1438
|
+
* pki.inspect.crmf(der).split("\n")[0]; // "CRMF Certificate Request Messages:"
|
|
1439
|
+
*/
|
|
1440
|
+
function crmfReport(input) {
|
|
1441
|
+
var m = _parseCrmf(input);
|
|
1442
|
+
var L = ["CRMF Certificate Request Messages:", " Messages: " + m.messages.length];
|
|
1443
|
+
m.messages.forEach(function (msg, i) {
|
|
1444
|
+
var req = msg.certReq, t = req.certTemplate;
|
|
1445
|
+
L.push(" [" + i + "] Certificate Request:");
|
|
1446
|
+
_optLine(L, " ", "Request ID", req.certReqId === undefined ? null : String(req.certReqId));
|
|
1447
|
+
/** @internal RFC 4211 sec. 5 carries the DER value, so 2 names v3, exactly as a certificate's does. The
|
|
1448
|
+
* raw value read as a request for v2, which is the opposite of what the template asks for, and the
|
|
1449
|
+
* certificate report beside it writes the same number the other way. */
|
|
1450
|
+
_optLine(L, " ", "Version", t.version === undefined || t.version === null
|
|
1451
|
+
? null : (Number(t.version) + 1) + " (0x" + Number(t.version).toString(16) + ")");
|
|
1452
|
+
if (t.serialNumber !== undefined && t.serialNumber !== null) L.push(" Serial Number: " + t.serialNumber);
|
|
1453
|
+
if (t.issuer) L.push(" Issuer: " + _nameOrGn(t.issuer));
|
|
1454
|
+
if (t.subject) L.push(" Subject: " + _nameOrGn(t.subject));
|
|
1455
|
+
if (t.signingAlg) L.push(" Signing Algorithm: " + _algName(t.signingAlg));
|
|
1456
|
+
if (t.validity) {
|
|
1457
|
+
L.push(" Validity:");
|
|
1458
|
+
_optLine(L, " ", "Not Before", t.validity.notBefore ? _date(t.validity.notBefore) : null);
|
|
1459
|
+
_optLine(L, " ", "Not After", t.validity.notAfter ? _date(t.validity.notAfter) : null);
|
|
1460
|
+
}
|
|
1461
|
+
if (t.publicKey) { L.push(" Subject Public Key Info:"); L.push(_keyBlock(t.publicKey, " ")); }
|
|
1462
|
+
if (_list(t.extensions).length) {
|
|
1463
|
+
L.push(" Requested Extensions:");
|
|
1464
|
+
_list(t.extensions).forEach(function (e) { L.push(_extension(_obj(e), " ")); });
|
|
1465
|
+
}
|
|
1466
|
+
/** @internal WHICH proof of possession a request carries is the difference between a key whose holder
|
|
1467
|
+
* proved possession by signing and one an RA vouched for, so the form is named rather than reported as
|
|
1468
|
+
* merely present. */
|
|
1469
|
+
L.push(" Proof of Possession: " + (msg.popo ? (msg.popo.type || "present") : "(none)"));
|
|
1470
|
+
L.push(" Registration Controls: " + ((req.controls && req.controls.length) || 0));
|
|
1471
|
+
L.push(" Registration Info: " + ((msg.regInfo && msg.regInfo.length) || 0));
|
|
1472
|
+
});
|
|
1473
|
+
return L.join("\n") + "\n";
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
/**
|
|
1477
|
+
* @primitive pki.inspect.cmp
|
|
1478
|
+
* @signature pki.inspect.cmp(input) -> string
|
|
1479
|
+
* @since 0.8.29
|
|
1480
|
+
* @status stable
|
|
1481
|
+
* @spec RFC 9810
|
|
1482
|
+
* @related pki.schema.cmp.parse, pki.inspect.crmf
|
|
1483
|
+
*
|
|
1484
|
+
* Render a CMP message as a text report: the header's protocol version, sender and
|
|
1485
|
+
* recipient, transaction id and nonces, the body arm the message carries, and
|
|
1486
|
+
* whether protection and extra certificates are present. The body's inner content
|
|
1487
|
+
* is named rather than expanded, since each arm is its own structure that its own
|
|
1488
|
+
* verb reads. `input` is a PEM string, a DER Buffer, or a `pki.schema.cmp.parse`
|
|
1489
|
+
* result; a non-CMP throws `inspect/bad-cmp`, a wrong-type input
|
|
1490
|
+
* `inspect/bad-input`.
|
|
1491
|
+
*
|
|
1492
|
+
* @example
|
|
1493
|
+
* var pair = await pki.key.generate("Ed25519");
|
|
1494
|
+
* var key = await pki.key.export(pair.privateKey);
|
|
1495
|
+
* var cert = await pki.x509.sign({ subject: "cmp.example", subjectPublicKey: await pki.key.export(pair.publicKey),
|
|
1496
|
+
* notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") }, { key: key });
|
|
1497
|
+
* var der = await pki.cmp.build({ header: { sender: { directoryName: "CN=cmp.example" },
|
|
1498
|
+
* recipient: { directoryName: "CN=A CA" } },
|
|
1499
|
+
* body: { ir: { certTemplate: { subject: [{ commonName: "cmp.example" }], publicKey: await pki.key.export(pair.publicKey) } } } },
|
|
1500
|
+
* { cert: cert, key: key });
|
|
1501
|
+
* pki.inspect.cmp(der).split("\n")[0]; // "CMP Message:"
|
|
1502
|
+
*/
|
|
1503
|
+
function cmpReport(input) {
|
|
1504
|
+
var m = _parseCmp(input);
|
|
1505
|
+
var h = m.header, body = m.body;
|
|
1506
|
+
var L = ["CMP Message:", " Header:"];
|
|
1507
|
+
_optLine(L, " ", "Protocol Version", h.pvno);
|
|
1508
|
+
L.push(" Sender: " + _nameOrGn(h.sender));
|
|
1509
|
+
L.push(" Recipient: " + _nameOrGn(h.recipient));
|
|
1510
|
+
if (h.messageTime) L.push(" Message Time: " + _date(h.messageTime));
|
|
1511
|
+
if (h.protectionAlg) L.push(" Protection Algorithm: " + _algName(h.protectionAlg));
|
|
1512
|
+
if (Buffer.isBuffer(h.transactionID)) L.push(" Transaction ID: " + _hexColon(h.transactionID, {}));
|
|
1513
|
+
if (Buffer.isBuffer(h.senderNonce)) L.push(" Sender Nonce: " + _hexColon(h.senderNonce, {}));
|
|
1514
|
+
if (Buffer.isBuffer(h.recipNonce)) L.push(" Recipient Nonce: " + _hexColon(h.recipNonce, {}));
|
|
1515
|
+
if (Buffer.isBuffer(h.senderKID)) L.push(" Sender Key ID: " + _hexColon(h.senderKID, {}));
|
|
1516
|
+
L.push(" General Info: " + ((h.generalInfo && h.generalInfo.length) || 0));
|
|
1517
|
+
L.push(" Body:");
|
|
1518
|
+
L.push(" Arm: " + body.arm);
|
|
1519
|
+
/** @internal The error arm is expanded where the others are only named, because its CONTENT is the whole
|
|
1520
|
+
* diagnostic and no other verb renders it: a reader of an error message wants the status, the failure bits
|
|
1521
|
+
* and the code, and naming the arm alone told them only that something went wrong. */
|
|
1522
|
+
if (body.arm === "error" && body.decoded && typeof body.decoded === "object") {
|
|
1523
|
+
var errText = _decodedRecord(body.decoded, " ");
|
|
1524
|
+
if (errText) L.push(errText);
|
|
1525
|
+
}
|
|
1526
|
+
L.push(" Protection: " + (m.protection ? "present" : "(none)"));
|
|
1527
|
+
L.push(" Extra Certificates: " + ((m.extraCerts && m.extraCerts.length) || 0));
|
|
1528
|
+
return L.join("\n") + "\n";
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
/**
|
|
1532
|
+
* @primitive pki.inspect.csrattrs
|
|
1533
|
+
* @signature pki.inspect.csrattrs(input) -> string
|
|
1534
|
+
* @since 0.8.29
|
|
1535
|
+
* @status stable
|
|
1536
|
+
* @spec RFC 7030, RFC 8951
|
|
1537
|
+
* @related pki.schema.csrattrs.parse, pki.inspect.csr
|
|
1538
|
+
*
|
|
1539
|
+
* Render an EST CSR Attributes response as a text report: one line per item, each
|
|
1540
|
+
* either a bare object identifier the server asks the client to supply or an
|
|
1541
|
+
* attribute with its type and values. `input` is a PEM string, a DER Buffer, or a
|
|
1542
|
+
* `pki.schema.csrattrs.parse` result; a non-CsrAttrs throws
|
|
1543
|
+
* `inspect/bad-csrattrs`, a wrong-type input `inspect/bad-input`.
|
|
1544
|
+
*
|
|
1545
|
+
* @example
|
|
1546
|
+
* var b = pki.asn1.build;
|
|
1547
|
+
* var der = b.sequence([b.oid("1.2.840.113549.1.9.7")]);
|
|
1548
|
+
* pki.inspect.csrattrs(der).split("\n")[0]; // "EST CSR Attributes:"
|
|
1549
|
+
*/
|
|
1550
|
+
function csrattrsReport(input) {
|
|
1551
|
+
var a = _parseCsrattrs(input);
|
|
1552
|
+
var L = ["EST CSR Attributes:", " Items: " + a.items.length];
|
|
1553
|
+
a.items.forEach(function (item, i) {
|
|
1554
|
+
var label = item.kind === "oid" ? "OID" : "Attribute";
|
|
1555
|
+
var name = item.name || item.oid || "unknown";
|
|
1556
|
+
L.push(" [" + i + "] " + label + ": " + name + (item.name && item.oid ? " (" + item.oid + ")" : ""));
|
|
1557
|
+
/** @internal The CONSTRAINT a client has to act on is what the parser added beside the raw value: a key
|
|
1558
|
+
* size, a curve set, a template. Printed as its DER, a request for a 2048-bit key read as four bytes. */
|
|
1559
|
+
var constraint = _extraFields(item, _ROW_PLUMBING, " ");
|
|
1560
|
+
if (constraint) L.push(constraint);
|
|
1561
|
+
if (item.kind !== "oid" && _list(item.values).length) {
|
|
1562
|
+
L.push(" Values: " + _list(item.values).length);
|
|
1563
|
+
_list(item.values).forEach(function (v) {
|
|
1564
|
+
/** @internal An attribute value a server chose is escaped for the reason a distinguished name is. */
|
|
1565
|
+
L.push(" " + (Buffer.isBuffer(v) ? _hexColon(v, {}) : _clean(String(v))));
|
|
1566
|
+
});
|
|
1567
|
+
}
|
|
1568
|
+
});
|
|
1569
|
+
return L.join("\n") + "\n";
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
/**
|
|
1573
|
+
* @primitive pki.inspect.trustanchor
|
|
1574
|
+
* @signature pki.inspect.trustanchor(input) -> string
|
|
1575
|
+
* @since 0.8.29
|
|
1576
|
+
* @status stable
|
|
1577
|
+
* @spec RFC 5914
|
|
1578
|
+
* @related pki.schema.trustanchor.parse, pki.inspect.certificate
|
|
1579
|
+
*
|
|
1580
|
+
* Render an RFC 5914 trust anchor list as a text report: one block per anchor
|
|
1581
|
+
* giving which of the three forms it takes, its public key, key identifier and
|
|
1582
|
+
* title where present, and the certificate path controls that bound what it may
|
|
1583
|
+
* anchor. `input` is a PEM string, a DER Buffer, or a
|
|
1584
|
+
* `pki.schema.trustanchor.parse` result; a non-list throws
|
|
1585
|
+
* `inspect/bad-trustanchor`, a wrong-type input `inspect/bad-input`.
|
|
1586
|
+
*
|
|
1587
|
+
* @example
|
|
1588
|
+
* var pair = await pki.key.generate("Ed25519");
|
|
1589
|
+
* var b = pki.asn1.build;
|
|
1590
|
+
* var spki = await pki.key.export(pair.publicKey);
|
|
1591
|
+
* var der = b.sequence([b.explicit(2, b.sequence([b.raw(spki), b.octetString(Buffer.alloc(20, 1))]))]);
|
|
1592
|
+
* pki.inspect.trustanchor(der).split("\n")[0]; // "Trust Anchor List:"
|
|
1593
|
+
*/
|
|
1594
|
+
function trustAnchorReport(input) {
|
|
1595
|
+
var t = _parseTrustAnchor(input);
|
|
1596
|
+
var L = ["Trust Anchor List:", " Anchors: " + t.anchors.length];
|
|
1597
|
+
t.anchors.forEach(function (a, i) {
|
|
1598
|
+
L.push(" [" + i + "] Form: " + a.kind);
|
|
1599
|
+
/** @internal RFC 5914 sec. 3 gives three forms, and only the TrustAnchorInfo one carries its key and
|
|
1600
|
+
* name at the top: the other two hold a certificate, whose subject and subjectPublicKeyInfo are what
|
|
1601
|
+
* name the anchor. Read as though every form were TrustAnchorInfo, a certificate anchor rendered its
|
|
1602
|
+
* form and nothing else, so two different anchors produced the same report. */
|
|
1603
|
+
var held = a.kind === "certificate" ? a.certificate : (a.kind === "tbsCert" ? a.tbsCert : null);
|
|
1604
|
+
if (held) {
|
|
1605
|
+
if (held.subject) L.push(" Subject: " + _dnString(held.subject));
|
|
1606
|
+
if (held.issuer) L.push(" Issuer: " + _dnString(held.issuer));
|
|
1607
|
+
if (held.serialNumberHex) _serialInto(L, " ", held.serialNumberHex);
|
|
1608
|
+
if (held.subjectPublicKeyInfo) {
|
|
1609
|
+
L.push(" Subject Public Key Info:");
|
|
1610
|
+
L.push(_keyBlock(held.subjectPublicKeyInfo, " "));
|
|
1611
|
+
}
|
|
1612
|
+
/** @internal Rendered, not counted, and for the same reason the TrustAnchorInfo arm renders its own:
|
|
1613
|
+
* the extensions bound what trusting the anchor means, so a count made a path-limited anchor read the
|
|
1614
|
+
* same as an unlimited one. One rule, both arms. */
|
|
1615
|
+
if (_list(held.extensions).length) {
|
|
1616
|
+
L.push(" Extensions:");
|
|
1617
|
+
_list(held.extensions).forEach(function (e) { L.push(_extension(_obj(e), " ")); });
|
|
1618
|
+
}
|
|
1619
|
+
return;
|
|
1620
|
+
}
|
|
1621
|
+
var info = a.taInfo;
|
|
1622
|
+
if (info.pubKey) { L.push(" Subject Public Key Info:"); L.push(_keyBlock(info.pubKey, " ")); }
|
|
1623
|
+
if (Buffer.isBuffer(info.keyId)) L.push(" Key Identifier: " + _hexColon(info.keyId, {}));
|
|
1624
|
+
/** @internal A taTitle is text whoever wrote the anchor list chose, so it is escaped as the rest are. */
|
|
1625
|
+
_optLine(L, " ", "Title", info.taTitle == null ? null : _clean(String(info.taTitle)));
|
|
1626
|
+
/** @internal RFC 5914 sec. 3's exts carry certificate extensions that bound what trusting the anchor
|
|
1627
|
+
* means, a basicConstraints path length among them. Skipped, an anchor carrying one read the same as one
|
|
1628
|
+
* carrying none. */
|
|
1629
|
+
if (_list(info.exts).length) {
|
|
1630
|
+
L.push(" Extensions:");
|
|
1631
|
+
_list(info.exts).forEach(function (e) { L.push(_extension(_obj(e), " ")); });
|
|
1632
|
+
}
|
|
1633
|
+
/** @internal RFC 5914 sec. 3 excludes some extension types from `exts`, and the parser drops one it finds
|
|
1634
|
+
* there while recording its identifier. Unmentioned, an anchor that tried to carry a name constraint in
|
|
1635
|
+
* that field read the same as one that carried nothing, and the constraint it named is not in force. */
|
|
1636
|
+
if (_list(info.excludedExtensions).length) {
|
|
1637
|
+
L.push(" Extensions the profile excludes here, dropped and not in force:");
|
|
1638
|
+
_list(info.excludedExtensions).forEach(function (o) {
|
|
1639
|
+
L.push(" " + (oid.name(String(o)) || String(o)) + " (" + String(o) + ")");
|
|
1640
|
+
});
|
|
1641
|
+
}
|
|
1642
|
+
var cpc = info.certPath;
|
|
1643
|
+
if (cpc) {
|
|
1644
|
+
L.push(" Certificate Path Controls:");
|
|
1645
|
+
if (cpc.taName) L.push(" Name: " + _nameOrGn(cpc.taName));
|
|
1646
|
+
_optLine(L, " ", "Path Length", cpc.pathLenConstraint);
|
|
1647
|
+
/** @internal The policy IDENTIFIERS, because a count made two anchors restricted to different single
|
|
1648
|
+
* policies read the same, and which policies an anchor is restricted to is what a reader asks. */
|
|
1649
|
+
if (_list(cpc.policySet).length) {
|
|
1650
|
+
L.push(" Policies:");
|
|
1651
|
+
_list(cpc.policySet).forEach(function (pol) {
|
|
1652
|
+
var p = _obj(pol);
|
|
1653
|
+
L.push(" " + (p.name ? p.name + " (" + p.policyIdentifier + ")" : String(p.policyIdentifier)));
|
|
1654
|
+
});
|
|
1655
|
+
}
|
|
1656
|
+
/** @internal RFC 5914 sec. 3's policyFlags is a BIT STRING the parser decodes to a record of named
|
|
1657
|
+
* booleans, so it has no length: read as an array the line never ran and a trust anchor's active
|
|
1658
|
+
* policy restrictions were absent from the report rather than shown as off. */
|
|
1659
|
+
if (cpc.policyFlags) {
|
|
1660
|
+
var onFlags = Object.keys(cpc.policyFlags).filter(function (n) { return cpc.policyFlags[n] === true; });
|
|
1661
|
+
L.push(" Policy Flags: " + (onFlags.length ? onFlags.join(", ") : "(none set)"));
|
|
1662
|
+
}
|
|
1663
|
+
/** @internal The subtrees an anchor may certify within are what a reader inspects an anchor to learn,
|
|
1664
|
+
* and omitted they made a restricted anchor read identically to an unrestricted one. The shape is the
|
|
1665
|
+
* certificate extension's own, so the extension renderer formats it rather than a second copy. */
|
|
1666
|
+
if (cpc.nameConstr) {
|
|
1667
|
+
var ncText = EXT_RENDERERS.nameConstraints(cpc.nameConstr, " ");
|
|
1668
|
+
if (ncText) { L.push(" Name Constraints:"); L.push(ncText); }
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
});
|
|
1672
|
+
return L.join("\n") + "\n";
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
/**
|
|
1676
|
+
* @primitive pki.inspect.ocspRequest
|
|
1677
|
+
* @signature pki.inspect.ocspRequest(input) -> string
|
|
1678
|
+
* @since 0.8.29
|
|
1679
|
+
* @status stable
|
|
1680
|
+
* @spec RFC 6960
|
|
1681
|
+
* @related pki.schema.ocsp.parseRequest, pki.inspect.ocspResponse
|
|
1682
|
+
*
|
|
1683
|
+
* Render an OCSP request as a text report: the version, the requestor name where
|
|
1684
|
+
* one is given, one block per requested certificate giving the CertID's hash
|
|
1685
|
+
* algorithm and the issuer name and key hashes with the serial asked about, the
|
|
1686
|
+
* request extensions, and whether the request is signed. `input` is a PEM string, a
|
|
1687
|
+
* DER Buffer, or a `pki.schema.ocsp.parseRequest` result; a non-request throws
|
|
1688
|
+
* `inspect/bad-ocsp-request`, a wrong-type input `inspect/bad-input`.
|
|
1689
|
+
*
|
|
1690
|
+
* @example
|
|
1691
|
+
* var pair = await pki.key.generate("Ed25519");
|
|
1692
|
+
* var key = await pki.key.export(pair.privateKey);
|
|
1693
|
+
* var cert = await pki.x509.sign({ subject: "ocsp.example", subjectPublicKey: await pki.key.export(pair.publicKey),
|
|
1694
|
+
* notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") }, { key: key });
|
|
1695
|
+
* var der = await pki.ocsp.buildRequest({ cert: cert, issuer: cert });
|
|
1696
|
+
* pki.inspect.ocspRequest(der).split("\n")[0]; // "OCSP Request:"
|
|
1697
|
+
*/
|
|
1698
|
+
function ocspRequestReport(input) {
|
|
1699
|
+
var r = _parseOcspReq(input);
|
|
1700
|
+
var L = ["OCSP Request:", " Version: " + r.version];
|
|
1701
|
+
L.push(" Requestor Name: " + (r.requestorName ? _nameOrGn(r.requestorName) : "(none)"));
|
|
1702
|
+
L.push(" Requests: " + r.requestList.length);
|
|
1703
|
+
r.requestList.forEach(function (req, i) {
|
|
1704
|
+
var id = req.certID;
|
|
1705
|
+
L.push(" [" + i + "] Certificate ID:");
|
|
1706
|
+
L.push(" Hash Algorithm: " + _algName(id.hashAlgorithm));
|
|
1707
|
+
if (Buffer.isBuffer(id.issuerNameHash)) L.push(" Issuer Name Hash: " + _hexColon(id.issuerNameHash, {}));
|
|
1708
|
+
if (Buffer.isBuffer(id.issuerKeyHash)) L.push(" Issuer Key Hash: " + _hexColon(id.issuerKeyHash, {}));
|
|
1709
|
+
_serialInto(L, " ", id.serialNumberHex || id.serialNumber);
|
|
1710
|
+
if (_list(req.singleRequestExtensions).length) {
|
|
1711
|
+
L.push(" Single Request Extensions:");
|
|
1712
|
+
_list(req.singleRequestExtensions).forEach(function (e) { L.push(_extension(_obj(e), " ")); });
|
|
1713
|
+
}
|
|
1714
|
+
});
|
|
1715
|
+
if (_list(r.requestExtensions).length) {
|
|
1716
|
+
L.push(" Request Extensions:");
|
|
1717
|
+
_list(r.requestExtensions).forEach(function (e) { L.push(_extension(_obj(e), " ")); });
|
|
1718
|
+
} else {
|
|
1719
|
+
L.push(" Request Extensions: (none)");
|
|
1720
|
+
}
|
|
1721
|
+
L.push(" Signature: " + (r.optionalSignature ? "present" : "(unsigned)"));
|
|
1722
|
+
return L.join("\n") + "\n";
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1725
|
+
/**
|
|
1726
|
+
* @primitive pki.inspect.ocspResponse
|
|
1727
|
+
* @signature pki.inspect.ocspResponse(input) -> string
|
|
1728
|
+
* @since 0.8.29
|
|
1729
|
+
* @status stable
|
|
1730
|
+
* @spec RFC 6960
|
|
1731
|
+
* @related pki.schema.ocsp.parseResponse, pki.inspect.ocspRequest
|
|
1732
|
+
*
|
|
1733
|
+
* Render an OCSP response as a text report: the response status, and for a basic
|
|
1734
|
+
* response the responder id, the time it was produced, one block per answer giving
|
|
1735
|
+
* the CertID, the certificate's status with its revocation time and reason where it
|
|
1736
|
+
* is revoked, and the validity window of the answer, plus the response extensions
|
|
1737
|
+
* and the certificates carried for the relying party to chain. `input` is a PEM
|
|
1738
|
+
* string, a DER Buffer, or a `pki.schema.ocsp.parseResponse` result; a non-response
|
|
1739
|
+
* throws `inspect/bad-ocsp-response`, a wrong-type input `inspect/bad-input`.
|
|
1740
|
+
*
|
|
1741
|
+
* @example
|
|
1742
|
+
* var pair = await pki.key.generate("Ed25519");
|
|
1743
|
+
* var key = await pki.key.export(pair.privateKey);
|
|
1744
|
+
* var cert = await pki.x509.sign({ subject: "ocsp.example", subjectPublicKey: await pki.key.export(pair.publicKey),
|
|
1745
|
+
* notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") }, { key: key });
|
|
1746
|
+
* var der = await pki.ocsp.sign({ responderID: "byName",
|
|
1747
|
+
* responses: [{ cert: cert, issuer: cert, status: "good", thisUpdate: new Date("2026-01-02T00:00:00Z") }] },
|
|
1748
|
+
* { cert: cert, key: key });
|
|
1749
|
+
* pki.inspect.ocspResponse(der).split("\n")[0]; // "OCSP Response:"
|
|
1750
|
+
*/
|
|
1751
|
+
function ocspResponseReport(input) {
|
|
1752
|
+
var r = _parseOcspResp(input);
|
|
1753
|
+
var st = r.responseStatus;
|
|
1754
|
+
var L = ["OCSP Response:", " Response Status: " + st.name];
|
|
1755
|
+
if (r.responseBytes) L.push(" Response Type: " + (r.responseBytes.responseTypeName || r.responseBytes.responseType));
|
|
1756
|
+
var br = r.basicResponse;
|
|
1757
|
+
if (!br) { L.push(" (no basic response to render)"); return L.join("\n") + "\n"; }
|
|
1758
|
+
L.push(" Version: " + br.version);
|
|
1759
|
+
/** @internal RFC 6960 sec. 4.2.1 gives ResponderID two forms, and the key form is a hash rather than a
|
|
1760
|
+
* name: passed to the name renderer it printed neither, so a responder that identifies itself by key was
|
|
1761
|
+
* not identified in the report at all. */
|
|
1762
|
+
L.push(" Responder ID: " + (Buffer.isBuffer(br.responderID.byKey)
|
|
1763
|
+
? "keyHash:" + _hexColon(br.responderID.byKey, {})
|
|
1764
|
+
: _nameOrGn(br.responderID.byName)));
|
|
1765
|
+
if (br.producedAt) L.push(" Produced At: " + _date(br.producedAt));
|
|
1766
|
+
L.push(" Responses: " + br.responses.length);
|
|
1767
|
+
br.responses.forEach(function (one, i) {
|
|
1768
|
+
var id = one.certID;
|
|
1769
|
+
L.push(" [" + i + "] Certificate ID:");
|
|
1770
|
+
L.push(" Hash Algorithm: " + _algName(id.hashAlgorithm));
|
|
1771
|
+
if (Buffer.isBuffer(id.issuerNameHash)) L.push(" Issuer Name Hash: " + _hexColon(id.issuerNameHash, {}));
|
|
1772
|
+
if (Buffer.isBuffer(id.issuerKeyHash)) L.push(" Issuer Key Hash: " + _hexColon(id.issuerKeyHash, {}));
|
|
1773
|
+
_serialInto(L, " ", id.serialNumberHex || id.serialNumber);
|
|
1774
|
+
var status = one.certStatus;
|
|
1775
|
+
L.push(" Cert Status: " + status.type);
|
|
1776
|
+
if (status.revocationTime) L.push(" Revocation Time: " + _date(status.revocationTime));
|
|
1777
|
+
_optLine(L, " ", "Revocation Reason", status.revocationReason);
|
|
1778
|
+
if (one.thisUpdate) L.push(" This Update: " + _date(one.thisUpdate));
|
|
1779
|
+
L.push(" Next Update: " + (one.nextUpdate ? _date(one.nextUpdate) : "NONE"));
|
|
1780
|
+
if (_list(one.singleExtensions).length) {
|
|
1781
|
+
L.push(" Single Extensions:");
|
|
1782
|
+
_list(one.singleExtensions).forEach(function (e) { L.push(_extension(_obj(e), " ")); });
|
|
1783
|
+
}
|
|
1784
|
+
});
|
|
1785
|
+
if (_list(br.responseExtensions).length) {
|
|
1786
|
+
L.push(" Response Extensions:");
|
|
1787
|
+
_list(br.responseExtensions).forEach(function (e) { L.push(_extension(_obj(e), " ")); });
|
|
1788
|
+
}
|
|
1789
|
+
if (br.signatureAlgorithm) L.push(" Signature Algorithm: " + _algName(br.signatureAlgorithm));
|
|
1790
|
+
L.push(" Certificates: " + ((br.certs && br.certs.length) || 0));
|
|
1791
|
+
return L.join("\n") + "\n";
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1794
|
+
/**
|
|
1795
|
+
* @primitive pki.inspect.tsp
|
|
1796
|
+
* @signature pki.inspect.tsp(input) -> string
|
|
1797
|
+
* @since 0.8.29
|
|
1798
|
+
* @status stable
|
|
1799
|
+
* @spec RFC 3161
|
|
1800
|
+
* @related pki.schema.tsp.parseResponse, pki.inspect.cms
|
|
1801
|
+
*
|
|
1802
|
+
* Render an RFC 3161 timestamp response as a text report: the PKI status with its
|
|
1803
|
+
* status strings and failure info, and the TSTInfo inside the token, giving the
|
|
1804
|
+
* policy, the message imprint, the serial, the time it was stamped with its
|
|
1805
|
+
* accuracy and ordering, the nonce, and the TSA name. The token's own CMS structure,
|
|
1806
|
+
* its signer identifier and its signed attributes, is what `pki.inspect.cms`
|
|
1807
|
+
* renders, and a bare timestamp TOKEN is a CMS ContentInfo rather than a response,
|
|
1808
|
+
* so that verb reads one of those directly. `input` is a PEM string, a DER Buffer, or a
|
|
1809
|
+
* `pki.schema.tsp.parseResponse` result; a non-response throws `inspect/bad-tsp`,
|
|
1810
|
+
* a wrong-type input `inspect/bad-input`.
|
|
1811
|
+
*
|
|
1812
|
+
* @example
|
|
1813
|
+
* var pair = await pki.key.generate("Ed25519");
|
|
1814
|
+
* var key = await pki.key.export(pair.privateKey), spki = await pki.key.export(pair.publicKey);
|
|
1815
|
+
* var ca = await pki.x509.sign({ subject: "tsa.example", subjectPublicKey: spki,
|
|
1816
|
+
* notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z"),
|
|
1817
|
+
* extensions: { keyUsage: ["digitalSignature"], extendedKeyUsage: ["timeStamping"], extendedKeyUsageCritical: true } },
|
|
1818
|
+
* { key: key });
|
|
1819
|
+
* var imprint = { hashAlgorithm: "sha256", hashedMessage: Buffer.alloc(32, 7) };
|
|
1820
|
+
* var token = await pki.tsp.sign(imprint, { cert: ca, key: key }, { policy: "1.2.3", serialNumber: 1 });
|
|
1821
|
+
* pki.inspect.tsp(pki.tsp.response(token, {})).split("\n")[0]; // "Timestamp Response:"
|
|
1822
|
+
*/
|
|
1823
|
+
function tspReport(input) {
|
|
1824
|
+
var r = _parseTsp(input);
|
|
1825
|
+
/** @internal RFC 3161 sec. 2.4.2's PKIStatus values need different handling from each other, and a bare
|
|
1826
|
+
* integer said which only to a reader who knows the table: a rejection and a response still being waited
|
|
1827
|
+
* on both read as a number. */
|
|
1828
|
+
var statusName = TSP_STATUS_NAMES[r.status];
|
|
1829
|
+
var L = ["Timestamp Response:",
|
|
1830
|
+
" Status: " + r.status + (statusName ? " (" + statusName + ")" : "")];
|
|
1831
|
+
/** @internal A statusString is text a responder chose, so it reaches the report through the same escape
|
|
1832
|
+
* every other caller-controlled string does: copied raw, a newline forges a report line and a terminal
|
|
1833
|
+
* escape rewrites the screen around it. */
|
|
1834
|
+
if (r.statusString) {
|
|
1835
|
+
var strings = Array.isArray(r.statusString) ? r.statusString : [r.statusString];
|
|
1836
|
+
L.push(" Status String: " + strings.map(function (s) { return _clean(String(s)); }).join("; "));
|
|
1837
|
+
}
|
|
1838
|
+
/** @internal PKIFailureInfo is a BIT STRING, and the parser decodes it to a record carrying the named
|
|
1839
|
+
* bits it asserts in `bits`. Read as an array or a string it rendered as its own object notation and the
|
|
1840
|
+
* reason a request was rejected, which is the one thing this line is for, was lost. */
|
|
1841
|
+
if (r.failInfo) L.push(" Failure Info: " + _list(_obj(r.failInfo).bits).join(", "));
|
|
1842
|
+
var tok = r.timeStampToken;
|
|
1843
|
+
if (!tok) { L.push(" Token: (none)"); return L.join("\n") + "\n"; }
|
|
1844
|
+
var t = tok.tstInfo;
|
|
1845
|
+
L.push(" TSTInfo:");
|
|
1846
|
+
_optLine(L, " ", "Version", t.version);
|
|
1847
|
+
_optLine(L, " ", "Policy", t.policyName ? t.policyName + " (" + t.policy + ")" : t.policy);
|
|
1848
|
+
if (t.messageImprint) {
|
|
1849
|
+
L.push(" Message Imprint:");
|
|
1850
|
+
L.push(" Hash Algorithm: " + _algName(t.messageImprint.hashAlgorithm));
|
|
1851
|
+
if (Buffer.isBuffer(t.messageImprint.hashedMessage)) {
|
|
1852
|
+
L.push(" Hashed Message:");
|
|
1853
|
+
L.push(_hexColon(t.messageImprint.hashedMessage, { wrap: 16, indent: 16 }));
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
_serialInto(L, " ", t.serialNumberHex || t.serialNumber);
|
|
1857
|
+
/** @internal A timestamp's value is the instant it names, and RFC 3161 sec. 2.4.2 puts the sub-second part
|
|
1858
|
+
* in the GeneralizedTime's fraction, which the shared date renderer does not carry. Dropped, two stamps a
|
|
1859
|
+
* fraction of a second apart read as the same instant. */
|
|
1860
|
+
if (t.genTime) {
|
|
1861
|
+
L.push(" Gen Time: " + _date(t.genTime) +
|
|
1862
|
+
(t.genTimeFraction ? " (fraction: ." + t.genTimeFraction + ")" : ""));
|
|
1863
|
+
}
|
|
1864
|
+
if (t.accuracy) {
|
|
1865
|
+
var acc = [];
|
|
1866
|
+
if (t.accuracy.seconds != null) acc.push(t.accuracy.seconds + "s");
|
|
1867
|
+
if (t.accuracy.millis != null) acc.push(t.accuracy.millis + "ms");
|
|
1868
|
+
if (t.accuracy.micros != null) acc.push(t.accuracy.micros + "us");
|
|
1869
|
+
L.push(" Accuracy: " + (acc.length ? acc.join(" ") : "(empty)"));
|
|
1870
|
+
} else {
|
|
1871
|
+
L.push(" Accuracy: (none stated)");
|
|
1872
|
+
}
|
|
1873
|
+
L.push(" Ordering: " + (t.ordering === true));
|
|
1874
|
+
if (t.nonceHex) L.push(" Nonce: " + t.nonceHex);
|
|
1875
|
+
if (t.tsa) L.push(" TSA: " + _nameOrGn(t.tsa));
|
|
1876
|
+
if (_list(t.extensions).length) {
|
|
1877
|
+
L.push(" Extensions:");
|
|
1878
|
+
_list(t.extensions).forEach(function (e) { L.push(_extension(_obj(e), " ")); });
|
|
1879
|
+
}
|
|
1880
|
+
L.push(" Certificates: " + _list(tok.certificates).length);
|
|
1881
|
+
return L.join("\n") + "\n";
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
/**
|
|
1885
|
+
* @primitive pki.inspect.attrcert
|
|
1886
|
+
* @signature pki.inspect.attrcert(input) -> string
|
|
1887
|
+
* @since 0.8.29
|
|
1888
|
+
* @status stable
|
|
1889
|
+
* @spec RFC 5755
|
|
1890
|
+
* @related pki.schema.attrcert.parse, pki.inspect.certificate
|
|
1891
|
+
*
|
|
1892
|
+
* Render an RFC 5755 attribute certificate as a text report: the version, the
|
|
1893
|
+
* holder and the issuer in whichever form each takes, the serial, the validity
|
|
1894
|
+
* period, the attributes the certificate asserts, its extensions, and the signature
|
|
1895
|
+
* algorithm. `input` is a PEM string, a DER Buffer, or a
|
|
1896
|
+
* `pki.schema.attrcert.parse` result; a non-attribute-certificate throws
|
|
1897
|
+
* `inspect/bad-attrcert`, a wrong-type input `inspect/bad-input`. An
|
|
1898
|
+
* X.509-1997 v1 attribute certificate is not parsed by this build, and the report
|
|
1899
|
+
* surfaces that refusal rather than rendering a partial one.
|
|
1900
|
+
*
|
|
1901
|
+
* @example
|
|
1902
|
+
* var pair = await pki.key.generate("Ed25519");
|
|
1903
|
+
* var der = await pki.attrcert.sign({ holder: { entityName: { directoryName: "CN=Alice" } },
|
|
1904
|
+
* notBeforeTime: new Date("2026-01-01T00:00:00Z"), notAfterTime: new Date("2027-01-01T00:00:00Z"),
|
|
1905
|
+
* attributes: { role: { roleName: { uniformResourceIdentifier: "urn:role:admin" } } } },
|
|
1906
|
+
* { name: "CN=An AA", publicKey: await pki.key.export(pair.publicKey), key: await pki.key.export(pair.privateKey) });
|
|
1907
|
+
* pki.inspect.attrcert(der).split("\n")[0]; // "Attribute Certificate:"
|
|
1908
|
+
*/
|
|
1909
|
+
function attrCertReport(input) {
|
|
1910
|
+
var a = _parseAttrCert(input);
|
|
1911
|
+
var L = ["Attribute Certificate:", " Data:"];
|
|
1912
|
+
L.push(" Version: " + a.version);
|
|
1913
|
+
var holder = a.holder;
|
|
1914
|
+
L.push(" Holder:");
|
|
1915
|
+
if (holder.entityName) L.push(" Entity Name: " + _nameOrGn(holder.entityName));
|
|
1916
|
+
if (holder.baseCertificateID) {
|
|
1917
|
+
var bid = holder.baseCertificateID;
|
|
1918
|
+
L.push(" Base Certificate ID:");
|
|
1919
|
+
if (bid.issuer) L.push(" Issuer: " + _nameOrGn(bid.issuer));
|
|
1920
|
+
/** @internal RFC 5755's IssuerSerial carries an optional issuerUID, which distinguishes two issuers that
|
|
1921
|
+
* share a name, so a holder identified with one was reported without the field that identifies it. */
|
|
1922
|
+
if (bid.issuerUID) {
|
|
1923
|
+
var uid = Buffer.isBuffer(bid.issuerUID) ? bid.issuerUID : _obj(bid.issuerUID).bytes;
|
|
1924
|
+
if (Buffer.isBuffer(uid)) L.push(" Issuer Unique ID: " + _hexColon(uid, {}));
|
|
1925
|
+
}
|
|
1926
|
+
/** @internal RFC 5755's IssuerSerial names its field `serial`, which the parser surfaces as `serial`
|
|
1927
|
+
* and `serialHex`, not as the `serialNumber` a certificate carries. Read by the certificate's names
|
|
1928
|
+
* both operands were undefined and the holder's serial was dropped, leaving two holders under one
|
|
1929
|
+
* issuer indistinguishable in the report. */
|
|
1930
|
+
_serialInto(L, " ", bid.serialHex || bid.serial);
|
|
1931
|
+
}
|
|
1932
|
+
/** @internal The DIGEST is what identifies a holder named this way, so the algorithm alone left every
|
|
1933
|
+
* holder identified by one digest algorithm reading the same. The object type says what was hashed. */
|
|
1934
|
+
if (holder.objectDigestInfo) {
|
|
1935
|
+
var odi = holder.objectDigestInfo;
|
|
1936
|
+
L.push(" Object Digest Info:");
|
|
1937
|
+
L.push(" Digested Object Type: " +
|
|
1938
|
+
((odi.digestedObjectType && odi.digestedObjectType.name) || "unspecified"));
|
|
1939
|
+
if (odi.otherObjectTypeID) L.push(" Other Object Type: " + odi.otherObjectTypeID);
|
|
1940
|
+
L.push(" Digest Algorithm: " + _algName(odi.digestAlgorithm));
|
|
1941
|
+
var digestBytes = odi.objectDigest && (Buffer.isBuffer(odi.objectDigest) ? odi.objectDigest : odi.objectDigest.bytes);
|
|
1942
|
+
if (Buffer.isBuffer(digestBytes)) {
|
|
1943
|
+
L.push(" Digest:");
|
|
1944
|
+
L.push(_hexColon(digestBytes, { wrap: 16, indent: 20 }));
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
/** @internal RFC 5755 sec. 4.2.3's V2Form names the issuer by name, by a base certificate id, or by an
|
|
1948
|
+
* object digest, and only the first was rendered: an issuer identified either other way appeared as
|
|
1949
|
+
* nothing at all. The v1Form is a GeneralNames list, which the name renderer reads. */
|
|
1950
|
+
var iss = a.issuer;
|
|
1951
|
+
var issuedBy = iss.v2Form ? _nameOrGn(iss.v2Form.issuerName) : _nameOrGn(iss.v1Form || iss);
|
|
1952
|
+
if (issuedBy === "(none)" && iss.v2Form) {
|
|
1953
|
+
var v2 = iss.v2Form;
|
|
1954
|
+
if (v2.baseCertificateID) {
|
|
1955
|
+
issuedBy = "baseCertificateID " + _nameOrGn(v2.baseCertificateID.issuer) +
|
|
1956
|
+
(v2.baseCertificateID.serialHex ? " serial " + v2.baseCertificateID.serialHex : "");
|
|
1957
|
+
} else if (v2.objectDigestInfo) {
|
|
1958
|
+
issuedBy = "objectDigest " + _algName(v2.objectDigestInfo.digestAlgorithm);
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
L.push(" Issuer: " + issuedBy);
|
|
1962
|
+
if (iss.form) L.push(" Issuer Form: " + iss.form);
|
|
1963
|
+
L.push(" " + _serial(a, 12));
|
|
1964
|
+
if (a.validity) {
|
|
1965
|
+
L.push(" Validity:");
|
|
1966
|
+
if (a.validity.notBeforeTime) L.push(" Not Before: " + _date(a.validity.notBeforeTime));
|
|
1967
|
+
if (a.validity.notAfterTime) L.push(" Not After : " + _date(a.validity.notAfterTime));
|
|
1968
|
+
}
|
|
1969
|
+
/** @internal RFC 5755's optional top-level issuerUniqueID, which tells two issuers sharing a name apart the
|
|
1970
|
+
* way the holder's own does, and which the report left out entirely. */
|
|
1971
|
+
if (a.issuerUniqueID) {
|
|
1972
|
+
var acUid = Buffer.isBuffer(a.issuerUniqueID) ? a.issuerUniqueID : _obj(a.issuerUniqueID).bytes;
|
|
1973
|
+
if (Buffer.isBuffer(acUid)) L.push(" Issuer Unique ID: " + _hexColon(acUid, {}));
|
|
1974
|
+
}
|
|
1975
|
+
L.push(" Attributes:");
|
|
1976
|
+
if (_list(a.attributes).length) _list(a.attributes).forEach(function (at) { L.push(_attribute(_obj(at), " ")); });
|
|
1977
|
+
else L.push(" (none)");
|
|
1978
|
+
if (_list(a.extensions).length) {
|
|
1979
|
+
L.push(" Extensions:");
|
|
1980
|
+
_list(a.extensions).forEach(function (e) { L.push(_extension(_obj(e), " ")); });
|
|
1981
|
+
}
|
|
1982
|
+
L.push(" Signature Algorithm: " + _algName(a.signatureAlgorithm));
|
|
1983
|
+
return L.join("\n") + "\n";
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1986
|
+
var _INSPECT_BY_FORMAT = Object.assign(Object.create(null), {
|
|
1987
|
+
x509: certificate, crl: crlReport, csr: csrReport, cms: cmsReport,
|
|
1988
|
+
pkcs8: pkcs8Report, pkcs12: pkcs12Report, crmf: crmfReport, cmp: cmpReport,
|
|
1989
|
+
csrattrs: csrattrsReport, trustanchor: trustAnchorReport,
|
|
1990
|
+
"ocsp-request": ocspRequestReport, "ocsp-response": ocspResponseReport,
|
|
1991
|
+
tsp: tspReport, attrcert: attrCertReport,
|
|
1992
|
+
});
|
|
868
1993
|
|
|
869
1994
|
/**
|
|
870
1995
|
* @primitive pki.inspect.any
|
|
@@ -875,11 +2000,12 @@ var _INSPECT_BY_FORMAT = Object.assign(Object.create(null), { x509: certificate,
|
|
|
875
2000
|
* @related pki.schema.detectFormat, pki.inspect.certificate
|
|
876
2001
|
*
|
|
877
2002
|
* Detect which PKI format `input` (a PEM string or DER Buffer) encodes and render
|
|
878
|
-
* it with the matching report, the inspect analogue of `pki.schema.parse`.
|
|
879
|
-
*
|
|
880
|
-
*
|
|
881
|
-
*
|
|
882
|
-
*
|
|
2003
|
+
* it with the matching report, the inspect analogue of `pki.schema.parse`. Every
|
|
2004
|
+
* format `pki.schema.all()` detects has a report and its own named verb, so the
|
|
2005
|
+
* same bytes render the same whether the format is detected or asked for by name.
|
|
2006
|
+
* An X.509-1997 v1 attribute certificate is detected so it can be named, and it is
|
|
2007
|
+
* refused with the reason its own parser gives rather than as an unsupported
|
|
2008
|
+
* format. An unrecognized input throws `inspect/bad-input`.
|
|
883
2009
|
*
|
|
884
2010
|
* @example
|
|
885
2011
|
* var pair = await pki.key.generate("Ed25519");
|
|
@@ -896,7 +2022,17 @@ function any(input) {
|
|
|
896
2022
|
} catch (e) { throw _err("inspect/bad-input", "input is not a decodable DER Buffer or PEM string", e); }
|
|
897
2023
|
if (fmt === null) throw _err("inspect/bad-input", "input does not match any registered PKI format");
|
|
898
2024
|
var render = _INSPECT_BY_FORMAT[fmt];
|
|
899
|
-
|
|
2025
|
+
/** @internal A format with no renderer is one whose parser refuses the bytes outright, and the reason
|
|
2026
|
+
* it refuses is what a reader of that file needs. `attrcert-v1` is the case: an X.509-1997 attribute
|
|
2027
|
+
* certificate is detected so it can be named rather than misread as the v2 form, and its parser
|
|
2028
|
+
* throws `attrcert/legacy-v1-not-supported`. Reporting "inspect does not support this format" would
|
|
2029
|
+
* send an operator looking for a missing feature instead of telling them which form they hold, so the
|
|
2030
|
+
* format's own parse runs and its error propagates. */
|
|
2031
|
+
if (!render) {
|
|
2032
|
+
schemaAll.parse(der);
|
|
2033
|
+
throw _err("inspect/unsupported-format", "inspect does not support the detected format \"" + fmt +
|
|
2034
|
+
"\" (supported: " + Object.keys(_INSPECT_BY_FORMAT).sort().join(", ") + ")");
|
|
2035
|
+
}
|
|
900
2036
|
return render(der);
|
|
901
2037
|
}
|
|
902
2038
|
|
|
@@ -905,6 +2041,16 @@ module.exports = {
|
|
|
905
2041
|
crl: crlReport,
|
|
906
2042
|
csr: csrReport,
|
|
907
2043
|
cms: cmsReport,
|
|
2044
|
+
pkcs8: pkcs8Report,
|
|
2045
|
+
pkcs12: pkcs12Report,
|
|
2046
|
+
crmf: crmfReport,
|
|
2047
|
+
cmp: cmpReport,
|
|
2048
|
+
csrattrs: csrattrsReport,
|
|
2049
|
+
trustanchor: trustAnchorReport,
|
|
2050
|
+
ocspRequest: ocspRequestReport,
|
|
2051
|
+
ocspResponse: ocspResponseReport,
|
|
2052
|
+
tsp: tspReport,
|
|
2053
|
+
attrcert: attrCertReport,
|
|
908
2054
|
any: any,
|
|
909
2055
|
renderedExtensions: Object.keys(EXT_RENDERERS),
|
|
910
2056
|
};
|