Implement scoped authority trust overlay

This commit is contained in:
sergeych committed 2026-08-16 19:42:53 +04:00
1 parent 1eeb8bac05
commit d479c459fc
17 files changed
+712 -65

No files matched your search

+28 -3
View File
@@ -88,15 +88,19 @@ export function createCommunityAdvicePlugin({
};
}
export function createUserDecisionPlugin(decision) {
export function createUserDecisionPlugin(decision, options = {}) {
if (decision !== true && decision !== false) return undefined;
const decide = ({ facts, journal }) => ({
trusted: decision,
scope: exactCertificateScope(facts),
lifetime: { kind: "connection" },
scope:
options.target === "authority"
? authorityForHostScope(facts, options)
: exactCertificateScope(facts),
lifetime: { kind: options.lifetime ?? "connection" },
reasonEntryIds: journal.entries
.filter((entry) => entry.kind === "evidence" || entry.kind === "warning")
.map((entry) => entry.id),
overriddenErrors: decision ? [...facts.errors] : [],
});
return {
manifest: pluginManifest(
@@ -113,6 +117,27 @@ export function createUserDecisionPlugin(decision) {
};
}
function authorityForHostScope(facts, options) {
const authorities = [...facts.constructedChain].filter(
(certificate) =>
certificate.isCa && certificate.keyUsages.includes("keyCertSign"),
);
const authority = options.authorityCertificateSha256
? authorities.find(
(certificate) =>
certificate.sha256 === options.authorityCertificateSha256,
)
: authorities.at(-1);
if (!authority) throw new TypeError("No usable CA certificate is present");
return {
kind: "authority-for-host",
hostname: facts.hostname,
port: "any",
authorityCertificateSha256: authority.sha256,
includeSubdomains: Boolean(options.includeSubdomains),
};
}
function exactCertificateScope(facts) {
return {
kind: "certificate-for-host",
+61
View File
@@ -0,0 +1,61 @@
export function createPolicyOverlayPlugin(overlay) {
const evidenceByConnection = new Map();
return {
manifest: {
manifestVersion: 1,
trustApiVersion: "0.1",
id: "org.browsec.local-policy-overlay",
name: "Browsec local trust policy",
version: "0.1.0",
supportedModes: ["decision-authority"],
capabilities: {},
},
hooks: {
collectEvidence({ facts }) {
const rule = overlay.match(facts);
if (!rule) return undefined;
evidenceByConnection.set(facts.connectionId, rule.id);
return {
entries: [
{
kind: "evidence",
code: "local-policy-rule-matched",
message: `${rule.sourcePluginName} stored a matching ${rule.trusted ? "trusted" : "not-trusted"} rule.`,
data: { ruleId: rule.id, trusted: rule.trusted },
},
],
};
},
onBeforeTlsAccept(context) {
return decideFromOverlay(context);
},
async onTlsFailure(context) {
return decideFromOverlay(context);
},
},
};
function decideFromOverlay({ facts, journal }) {
const expectedRuleId = evidenceByConnection.get(facts.connectionId);
const rule = overlay.take(facts);
evidenceByConnection.delete(facts.connectionId);
if (!rule || rule.id !== expectedRuleId) return undefined;
return {
trusted: rule.trusted,
scope: rule.scope,
lifetime: rule.lifetime,
overriddenErrors: rule.overriddenErrors,
reasonEntryIds: journal.entries
.filter(
(entry) =>
entry.pluginId === "org.browsec.local-policy-overlay" &&
entry.data?.ruleId === rule.id,
)
.map((entry) => entry.id),
};
}
}