Add synchronized DER view and investigation bundles
This commit is contained in:
1 parent
216afb6c18
commit
f5e4ece6e7
10 files changed
+372
-31
No files matched your search
@@ -0,0 +1,65 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { createHash } from "node:crypto";
|
||||
import test from "node:test";
|
||||
|
||||
import { createInvestigationBundle, parseInvestigationBundle } from "../src/investigation-bundle.js";
|
||||
import { exploreCertificateDer } from "../src/der-explorer.js";
|
||||
|
||||
test("investigation bundles round-trip original DER and normalized evidence", async () => {
|
||||
const derBytes = bytes("30023000");
|
||||
const der = exploreCertificateDer(derBytes);
|
||||
const certificate = {
|
||||
subject: "CN=Offline Test",
|
||||
sha256: sha256(derBytes),
|
||||
isCa: false,
|
||||
keyUsages: [],
|
||||
selfSigned: false,
|
||||
derBase64: Buffer.from(derBytes).toString("base64"),
|
||||
der,
|
||||
};
|
||||
const report = {
|
||||
observedAt: "2026-08-16T00:00:00.000Z",
|
||||
facts: {
|
||||
connectionId: "offline-test",
|
||||
hostname: "offline.test",
|
||||
port: 443,
|
||||
validation: "failure",
|
||||
errors: ["unknown-issuer"],
|
||||
failure: { code: "unknown-issuer", check: "trust-anchor", summary: "Unknown", certificateSha256: certificate.sha256 },
|
||||
presentedChain: [certificate],
|
||||
constructedChain: [certificate],
|
||||
tls: {},
|
||||
},
|
||||
findings: [],
|
||||
caveats: [],
|
||||
};
|
||||
const bundle = createInvestigationBundle(report);
|
||||
const parsed = await parseInvestigationBundle(JSON.stringify(bundle));
|
||||
assert.equal(parsed.evidence.facts.hostname, "offline.test");
|
||||
assert.equal(parsed.evidence.facts.presentedChain[0].derBase64, certificate.derBase64);
|
||||
assert.equal(parsed.privacy.containsPrivateKeys, false);
|
||||
});
|
||||
|
||||
test("bundle import rejects altered DER-derived authority facts and secret-bearing declarations", async () => {
|
||||
const derBytes = bytes("30023000");
|
||||
const der = exploreCertificateDer(derBytes);
|
||||
const base = {
|
||||
format: "org.browsec.investigation",
|
||||
version: 1,
|
||||
privacy: { containsPrivateKeys: false, containsTlsSessionSecrets: false },
|
||||
evidence: {
|
||||
facts: {
|
||||
connectionId: "tampered", hostname: "offline.test", port: 443, validation: "failure", errors: [],
|
||||
presentedChain: [{ subject: "x", sha256: sha256(derBytes), isCa: true, keyUsages: [], selfSigned: false, derBase64: Buffer.from(derBytes).toString("base64"), der }],
|
||||
constructedChain: [], tls: {},
|
||||
},
|
||||
findings: [], caveats: [],
|
||||
},
|
||||
};
|
||||
await assert.rejects(parseInvestigationBundle(JSON.stringify(base)), /CA assertion/);
|
||||
base.privacy.containsPrivateKeys = true;
|
||||
await assert.rejects(parseInvestigationBundle(JSON.stringify(base)), /no-secrets declaration/);
|
||||
});
|
||||
|
||||
function bytes(hex) { return Uint8Array.from(hex.match(/../g).map((pair) => Number.parseInt(pair, 16))); }
|
||||
function sha256(value) { return createHash("sha256").update(value).digest("hex"); }
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
createVillageCommunityPlugin,
|
||||
} from "../plugins/demo-plugins.js";
|
||||
import { TrustRunner } from "../src/index.js";
|
||||
import { chainRows, subjectName, verdictCopy } from "../ui/model.js";
|
||||
import { chainRows, flattenDerTree, smallestDerNodeAt, subjectName, verdictCopy } from "../ui/model.js";
|
||||
|
||||
test("UI model identifies the failed end of an unknown-authority chain", () => {
|
||||
const rows = chainRows(unknownLocalAuthority);
|
||||
@@ -46,6 +46,19 @@ test("certificate display names prefer the common name", () => {
|
||||
assert.equal(subjectName("O=Nameless"), "O=Nameless");
|
||||
});
|
||||
|
||||
test("DER byte selection resolves to the narrowest ASN.1 node", () => {
|
||||
const tree = {
|
||||
type: "SEQUENCE", offset: 0, endOffset: 12, children: [
|
||||
{ type: "INTEGER", offset: 2, endOffset: 5, children: [] },
|
||||
{ type: "OCTET STRING", offset: 5, endOffset: 12, children: [] },
|
||||
],
|
||||
};
|
||||
assert.deepEqual(flattenDerTree(tree).map((item) => item.depth), [0, 1, 1]);
|
||||
assert.equal(smallestDerNodeAt(tree, 3).type, "INTEGER");
|
||||
assert.equal(smallestDerNodeAt(tree, 8).type, "OCTET STRING");
|
||||
assert.equal(smallestDerNodeAt(tree, 12), undefined);
|
||||
});
|
||||
|
||||
test("UI plugins produce attributed evidence and a local Boolean verdict", async () => {
|
||||
const plugins = [
|
||||
configure(createFirefoxValidationPlugin(), "advisor"),
|
||||
@@ -98,6 +111,8 @@ test("security surface contains immutable-frame and simulation labels", async ()
|
||||
assert.match(html, /TRUSTLAB · SYNTHETIC/);
|
||||
assert.match(html, /cannot alter browser or system trust/);
|
||||
assert.match(html, /Live TLS target/);
|
||||
assert.match(html, /Export investigation/);
|
||||
assert.match(html, /Import investigation/);
|
||||
assert.match(html, /Decision target/);
|
||||
assert.match(html, /Effective and consumed local rules/);
|
||||
});
|
||||
Reference in new issue
Block a user