Add bounded DER certificate explorer

This commit is contained in:
sergeych committed 2026-08-16 20:42:11 +04:00
1 parent be751f4b51
commit 216afb6c18
10 files changed
+384 -12

No files matched your search

+61
View File
@@ -333,6 +333,7 @@ function renderChain(result) {
node("code", certificate.sha256 ?? "No fingerprint"),
node("span", certificate.edge, "edge-label"),
);
if (certificate.der) item.append(renderDerExplorer(certificate));
item.setAttribute("aria-label", `${certificate.role}: ${certificate.name}. ${certificate.edge}`);
if (index < all.length - 1) item.dataset.linked = "true";
return item;
@@ -340,6 +341,65 @@ function renderChain(result) {
);
}
function renderDerExplorer(certificate) {
const details = document.createElement("details");
details.className = "der-explorer";
const summary = document.createElement("summary");
summary.textContent = `Certificate internals · ${certificate.der.byteLength} DER bytes · ${certificate.der.extensions.length} extensions`;
details.append(summary);
const actions = document.createElement("div");
actions.className = "der-actions";
const download = document.createElement("a");
download.className = "button quiet";
download.textContent = "Save original .der";
download.download = `${certificate.sha256}.der`;
download.href = `data:application/pkix-cert;base64,${certificate.derBase64}`;
actions.append(download);
details.append(actions);
const derived = document.createElement("dl");
derived.className = "der-derived";
derived.append(
definition("Basic Constraints", certificate.der.derived.isCa
? `CA: true${certificate.der.derived.pathLength === undefined ? "" : `, path length: ${certificate.der.derived.pathLength}`}`
: "CA: false"),
definition("Key Usage", certificate.der.derived.keyUsages.join(", ") || "Not asserted"),
definition("Unsupported critical extensions", certificate.der.derived.unsupportedCriticalExtensions.join(", ") || "None"),
);
details.append(derived);
const extensions = document.createElement("div");
extensions.className = "der-extensions";
for (const extension of certificate.der.extensions) {
const row = document.createElement("article");
if (extension.critical) row.className = extension.supported ? "critical" : "critical unknown";
row.append(
node("strong", extension.name),
node("code", extension.oid),
node("span", `${extension.critical ? "critical" : "non-critical"} · bytes ${extension.offset}–${extension.offset + extension.length - 1}`),
node("pre", extension.decoded ? JSON.stringify(extension.decoded, null, 2) : extension.valueHex),
);
extensions.append(row);
}
details.append(extensions);
const tree = document.createElement("details");
tree.className = "der-tree";
tree.append(node("summary", "ASN.1 structure and byte ranges"), renderDerNode(certificate.der.tree));
details.append(tree);
return details;
}
function renderDerNode(item) {
const list = document.createElement("ul");
const entry = document.createElement("li");
entry.append(node("code", `${item.type} · ${item.offset}–${item.endOffset - 1} · value ${item.valueLength} B`));
if (item.children.length) entry.append(...item.children.map(renderDerNode));
list.append(entry);
return list;
}
function renderJournal(result) {
if (result.journal.entries.length === 0) {
elements.journal.replaceChildren(node("p", "No plugin findings.", "empty"));
@@ -390,6 +450,7 @@ function renderTrustTargets(facts) {
if (
!certificate.isCa ||
!certificate.keyUsages.includes("keyCertSign") ||
certificate.unsupportedCriticalExtensions?.length > 0 ||
seen.has(certificate.sha256)
) {
continue;
+20
View File
@@ -232,6 +232,25 @@ select {
.edge-label { grid-column: 3; grid-row: 1 / span 2; align-self: center; color: var(--green); font-size: 0.78rem; }
.failed .edge-label { color: var(--red); }
.der-explorer { grid-column: 1 / -1; margin-top: 0.8rem; border-top: 1px solid var(--line); padding-top: 0.8rem; }
.der-explorer > summary, .der-tree > summary { color: var(--cyan); cursor: pointer; font-size: 0.78rem; }
.der-actions { margin: 0.8rem 0; }
.der-actions a { display: inline-block; color: inherit; text-decoration: none; font-size: 0.72rem; }
.der-derived { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0.7rem; margin: 0.8rem 0; }
.der-derived div { padding: 0.65rem; background: #0b151a; }
.der-derived dt { color: var(--ink-muted); font-size: 0.68rem; }
.der-derived dd { margin: 0.25rem 0 0; font-size: 0.76rem; overflow-wrap: anywhere; }
.der-extensions { display: grid; gap: 0.45rem; }
.der-extensions article { display: grid; grid-template-columns: minmax(10rem, 1fr) auto; gap: 0.25rem 0.8rem; padding: 0.65rem; border-left: 2px solid #46616c; background: #0b151a; }
.der-extensions article.critical { border-color: var(--amber); }
.der-extensions article.unknown { border-color: var(--red); }
.der-extensions span, .der-extensions pre { grid-column: 1 / -1; }
.der-extensions span { color: var(--ink-muted); font-size: 0.68rem; }
.der-extensions pre { max-height: 12rem; margin: 0.25rem 0 0; overflow: auto; color: #b8cad2; font-size: 0.68rem; white-space: pre-wrap; overflow-wrap: anywhere; }
.der-tree { margin-top: 0.8rem; }
.der-tree ul { margin: 0.3rem 0 0; padding-left: 1.15rem; list-style: none; border-left: 1px solid #304751; }
.der-tree code { color: #9db1ba; font-size: 0.66rem; }
.journal { display: grid; gap: 0.65rem; }
.journal-entry {
display: grid;
@@ -293,6 +312,7 @@ select {
.identity-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.certificate { grid-template-columns: 1fr; }
.certificate code, .edge-label { grid-column: 1; grid-row: auto; }
.der-derived { grid-template-columns: 1fr; }
.journal-entry { grid-template-columns: 4.5rem 1fr; }
.journal-entry code { display: none; }
.decision-bar { flex-wrap: wrap; }