Define strict TLS trust plugin API

This commit is contained in:
sergeych committed 2026-08-16 17:49:42 +04:00
1 parent f1fcef9b85
commit 1eeb8bac05
17 files changed
+834 -194

No files matched your search

+39 -26
View File
@@ -3,40 +3,53 @@ import { TrustRunner } from "../src/index.js";
const villagePlugin = {
manifest: {
manifestVersion: 1,
trustApiVersion: "0.1",
id: "community.village.trust",
name: "Village community trust",
role: "decision-authority",
version: "0.0.0",
supportedModes: ["decision-authority"],
capabilities: {},
},
collectEvidence({ facts }) {
if (facts.hostname !== "library.village") return;
return {
entries: [
{
kind: "evidence",
code: "known-community-key",
message: "The community has previously observed this certificate.",
hooks: {
collectEvidence({ facts }) {
if (facts.hostname !== "library.village") return;
return {
entries: [
{
kind: "evidence",
code: "known-community-key",
message: "The community has previously observed this certificate.",
},
],
};
},
async onTlsFailure({ facts, journal }) {
const known = journal.entries.some(
(entry) => entry.code === "known-community-key",
);
if (!known) return;
return {
trusted: true,
scope: {
kind: "certificate-for-host",
hostname: facts.hostname,
port: facts.port,
certificateSha256: facts.presentedChain[0].sha256,
},
],
};
},
decide({ facts, journal }) {
const known = journal.entries.some(
(entry) => entry.code === "known-community-key",
);
if (!known) return;
return {
trusted: true,
scope: { hostname: facts.hostname, port: facts.port },
reasonEntryIds: journal.entries
.filter((entry) => entry.code === "known-community-key")
.map((entry) => entry.id),
};
lifetime: { kind: "connection" },
reasonEntryIds: journal.entries
.filter((entry) => entry.code === "known-community-key")
.map((entry) => entry.id),
};
},
},
};
const result = await new TrustRunner({ plugins: [villagePlugin] }).evaluate(
const result = await new TrustRunner({
plugins: [{ plugin: villagePlugin, mode: "decision-authority" }],
}).evaluate(
unknownLocalAuthority,
);
console.log(JSON.stringify(result, null, 2));
+49
View File
@@ -0,0 +1,49 @@
import {
defineTrustPlugin,
exactCertificateScope,
} from "../sdk/plugin-api.js";
export default defineTrustPlugin({
manifest: {
manifestVersion: 1,
trustApiVersion: "0.1",
id: "community.village.strict-example",
name: "Village strict example",
version: "1.0.0",
supportedModes: ["advisor", "decision-authority"],
capabilities: { interactiveUi: true },
},
hooks: {
collectEvidence({ facts }) {
if (facts.hostname !== "library.village") return undefined;
return {
entries: [
{
kind: "evidence",
code: "known-community-certificate",
message: "The configured community recognizes this certificate.",
},
],
};
},
async onTlsFailure({ facts, journal, ui }) {
const trusted = await ui.requestDecision({
title: "Unknown village authority",
summary: "Firefox does not recognize this authority. The community recognizes the exact certificate.",
proposedScope: exactCertificateScope(facts),
evidenceEntryIds: journal.entries.map((entry) => entry.id),
});
if (trusted === undefined) return undefined;
return {
trusted,
scope: exactCertificateScope(facts),
lifetime: { kind: "session" },
reasonEntryIds: journal.entries.map((entry) => entry.id),
};
},
},
});
+43
View File
@@ -0,0 +1,43 @@
import type {
PluginContribution,
TrustPluginHooks,
TrustVerdict,
} from "../sdk/plugin-api.js";
// These intentionally invalid declarations are compiler assertions: the build
// fails if TypeScript ever stops rejecting them.
const asynchronousFastHook: TrustPluginHooks = {
// @ts-expect-error onBeforeTlsAccept must never return a Promise.
async onBeforeTlsAccept() {
return undefined;
},
};
// @ts-expect-error Every terminal verdict requires an explicit lifetime.
const verdictWithoutLifetime: TrustVerdict = {
trusted: true,
scope: {
kind: "certificate-for-host",
hostname: "example.test",
port: 443,
certificateSha256: "leaf-sha256",
},
reasonEntryIds: [],
};
const voteWithoutBoolean: PluginContribution = {
entries: [
// @ts-expect-error A vote must state its Boolean recommendation.
{
kind: "vote",
code: "ambiguous-vote",
message: "This vote deliberately omits data.trusted.",
},
],
};
void asynchronousFastHook;
void verdictWithoutLifetime;
void voteWithoutBoolean;