Add explainable certificate path construction
This commit is contained in:
1 parent
2d02590214
commit
f0dbc19807
12 files changed
+319
-2
No files matched your search
@@ -85,6 +85,8 @@ const elements = {
|
||||
status: document.querySelector("#status"),
|
||||
identity: document.querySelector("#identity"),
|
||||
chain: document.querySelector("#chain"),
|
||||
pathPanel: document.querySelector("#path-panel"),
|
||||
paths: document.querySelector("#paths"),
|
||||
journal: document.querySelector("#journal"),
|
||||
rules: document.querySelector("#rules"),
|
||||
trust: document.querySelector("#trust"),
|
||||
@@ -154,6 +156,7 @@ elements.importBundle.addEventListener("change", async () => {
|
||||
facts: bundle.evidence.facts,
|
||||
findings: bundle.evidence.findings,
|
||||
caveats: bundle.evidence.caveats,
|
||||
pathAnalysis: bundle.evidence.pathAnalysis,
|
||||
};
|
||||
policyOverlay.clear();
|
||||
activateReport(report, "offline");
|
||||
@@ -246,6 +249,7 @@ async function render() {
|
||||
renderStatus(result, decisionApplied || decidedByOverlay);
|
||||
renderIdentity(result);
|
||||
renderChain(result);
|
||||
renderPathAnalysis(state.scenario === "live" ? state.liveReport?.pathAnalysis : undefined, facts);
|
||||
renderJournal(result);
|
||||
renderRules(policyOverlay.snapshot());
|
||||
elements.clear.hidden = policyOverlay.snapshot().rules.length === 0;
|
||||
@@ -255,6 +259,47 @@ async function render() {
|
||||
elements.exportBundle.hidden = state.scenario !== "live";
|
||||
}
|
||||
|
||||
function renderPathAnalysis(analysis, facts) {
|
||||
elements.pathPanel.hidden = !analysis;
|
||||
if (!analysis) return;
|
||||
const certificates = new Map(
|
||||
[...facts.presentedChain, ...facts.constructedChain].map((certificate) => [certificate.sha256, certificate]),
|
||||
);
|
||||
const pathCards = analysis.paths.map((path, index) => {
|
||||
const article = document.createElement("article");
|
||||
article.className = `path-card status-${path.status}`;
|
||||
article.append(
|
||||
node("span", path.status, "entry-kind"),
|
||||
node("h3", `Candidate path ${index + 1}`),
|
||||
node("p", path.certificateSha256
|
||||
.map((fingerprint) => subjectName(certificates.get(fingerprint)?.subject))
|
||||
.join(" → ")),
|
||||
node("code", [formatCode(path.terminalReason), path.structuralTerminalReason && path.structuralTerminalReason !== path.terminalReason ? `ends at: ${formatCode(path.structuralTerminalReason)}` : undefined, path.trustSource].filter(Boolean).join(" · ")),
|
||||
);
|
||||
if (path.validationFailures?.length) {
|
||||
article.append(node("p", `Validation: ${path.validationFailures.map((failure) => formatCode(failure.code)).join("; ")}`));
|
||||
}
|
||||
return article;
|
||||
});
|
||||
const rejected = analysis.edges.filter((edge) =>
|
||||
!edge.accepted && (edge.issuerNameMatches || edge.authorityKeyMatches === true || edge.signatureValid),
|
||||
);
|
||||
if (rejected.length) {
|
||||
const heading = node("h3", "Rejected issuer edges", "path-subheading");
|
||||
pathCards.push(heading, ...rejected.map((edge) => {
|
||||
const article = document.createElement("article");
|
||||
article.className = "path-card status-rejected";
|
||||
article.append(
|
||||
node("span", "rejected", "entry-kind"),
|
||||
node("h3", `${subjectName(certificates.get(edge.childSha256)?.subject)} → ${subjectName(certificates.get(edge.issuerSha256)?.subject)}`),
|
||||
node("p", edge.reasons.map(formatCode).join("; ")),
|
||||
);
|
||||
return article;
|
||||
}));
|
||||
}
|
||||
elements.paths.replaceChildren(...pathCards);
|
||||
}
|
||||
|
||||
function activateReport(report, source) {
|
||||
state.liveReport = report;
|
||||
state.source = source;
|
||||
|
||||
@@ -86,6 +86,14 @@
|
||||
<ol id="chain" class="chain"></ol>
|
||||
</section>
|
||||
|
||||
<section id="path-panel" class="panel" hidden>
|
||||
<div class="section-heading">
|
||||
<p class="eyebrow">Candidate construction</p>
|
||||
<h2>Every route to trust—and where it ends</h2>
|
||||
</div>
|
||||
<div id="paths" class="path-analysis"></div>
|
||||
</section>
|
||||
|
||||
<section class="panel">
|
||||
<div class="section-heading">
|
||||
<p class="eyebrow">Append-only journal</p>
|
||||
|
||||
@@ -296,6 +296,16 @@ select {
|
||||
.entry-kind { color: var(--cyan); font: 700 0.62rem ui-monospace, monospace; text-transform: uppercase; }
|
||||
.empty { color: var(--ink-muted); }
|
||||
|
||||
.path-analysis { display: grid; gap: 0.65rem; }
|
||||
.path-card { display: grid; grid-template-columns: 5rem 1fr auto; gap: 0.2rem 0.8rem; padding: 0.9rem 1rem; border-left: 3px solid var(--amber); background: #0c161b; }
|
||||
.path-card.status-trusted { border-color: var(--green); }
|
||||
.path-card.status-untrusted, .path-card.status-rejected { border-color: var(--red); }
|
||||
.path-card h3, .path-card p { margin: 0; }
|
||||
.path-card h3 { font-size: 0.86rem; }
|
||||
.path-card p { grid-column: 2 / -1; color: var(--ink-muted); font-size: 0.78rem; overflow-wrap: anywhere; }
|
||||
.path-card code { color: #78919b; font-size: 0.68rem; }
|
||||
.path-subheading { margin: 1rem 0 0.2rem; font-size: 0.85rem; }
|
||||
|
||||
.decision-bar {
|
||||
position: fixed;
|
||||
z-index: 20;
|
||||
|
||||
Reference in new issue
Block a user