Add explainable certificate path construction

This commit is contained in:
sergeych committed 2026-08-16 23:58:03 +04:00
1 parent 2d02590214
commit f0dbc19807
12 files changed
+319 -2

No files matched your search

+14
View File
@@ -38,6 +38,20 @@ test("an understood name does not make an unsupported critical extension safe",
assert.deepEqual(result.derived.unsupportedCriticalExtensions, ["2.5.29.32"]);
});
test("DER explorer decodes SAN and Authority Key Identifier relationships", () => {
const sanNames = tlv("30", "820b6578616d706c652e636f6d8704c0000201");
const authorityKey = tlv("30", "800401020304");
const extensions = tlv("30", extension("551d11", sanNames) + extension("551d23", authorityKey));
const certificate = bytes(tlv("30", tlv("30", tlv("a3", extensions))));
const result = exploreCertificateDer(certificate);
assert.deepEqual(result.derived.subjectAlternativeNames, [
{ type: "dns", value: "example.com" },
{ type: "ip", value: "192.0.2.1", valueHex: "c0000201" },
]);
assert.equal(result.derived.authorityKeyIdentifier, "01020304");
});
function extension(oidHex, valueHex, critical = false) {
return tlv("30", tlv("06", oidHex) + (critical ? "0101ff" : "") + tlv("04", valueHex));
}
+55
View File
@@ -0,0 +1,55 @@
import assert from "node:assert/strict";
import test from "node:test";
import { analyzeCertificatePaths } from "../src/path-analysis.js";
test("path analysis enumerates two valid issuer alternatives with attributed trust", () => {
const certificates = [cert("leaf", false), cert("intermediate-a"), cert("intermediate-b"), cert("root-a", true), cert("root-b", true)];
const relationships = [
link("leaf", "intermediate-a"), link("leaf", "intermediate-b"),
link("intermediate-a", "root-a"), link("intermediate-b", "root-b"),
];
const result = analyzeCertificatePaths(certificates, relationships, {
trustedCertificateSha256: ["root-a"],
});
assert.equal(result.paths.length, 2);
assert.equal(result.paths.find((path) => path.certificateSha256.includes("root-a")).status, "trusted");
assert.equal(result.paths.find((path) => path.certificateSha256.includes("root-b")).status, "untrusted");
});
test("path analysis explains rejected and missing issuer paths", () => {
const certificates = [cert("leaf", false), cert("wrong-issuer")];
const relationships = [{ ...link("leaf", "wrong-issuer"), signatureValid: false }];
const result = analyzeCertificatePaths(certificates, relationships);
assert.equal(result.paths[0].status, "incomplete");
assert.equal(result.paths[0].terminalReason, "no-acceptable-issuer");
assert.deepEqual(result.edges[0].reasons, ["signature-invalid"]);
});
test("path analysis rejects issuer certificates that cannot sign certificates", () => {
const issuer = { ...cert("issuer"), keyUsages: ["digitalSignature"] };
const result = analyzeCertificatePaths([cert("leaf", false), issuer], [link("leaf", "issuer")]);
assert.equal(result.edges[0].accepted, false);
assert.match(result.edges[0].reasons.join(" "), /key-usage/);
});
test("path validation explains an exceeded CA path-length constraint", () => {
const intermediate = cert("intermediate");
const root = { ...cert("root", true), pathLengthConstraint: 0 };
const result = analyzeCertificatePaths(
[cert("leaf", false), intermediate, root],
[link("leaf", "intermediate"), link("intermediate", "root")],
{ trustedCertificateSha256: ["root"] },
);
assert.equal(result.paths[0].status, "invalid");
assert.deepEqual(result.paths[0].validationFailures.map((failure) => failure.code), ["path-length-constraint-exceeded"]);
});
function cert(sha256, selfSigned = false) {
return { sha256, subject: sha256, isCa: sha256 !== "leaf", keyUsages: ["keyCertSign"], selfSigned, unsupportedCriticalExtensions: [] };
}
function link(childSha256, issuerSha256) {
return { childSha256, issuerSha256, issuerNameMatches: true, signatureValid: true, authorityKeyMatches: true };
}