browsec/trustlab/plugins/demo-plugins.js

174 lines
4.7 KiB
JavaScript

export function createFirefoxValidationPlugin() {
return {
manifest: pluginManifest(
"org.browsec.firefox-validation",
"Firefox validation",
["advisor"],
),
hooks: {
collectEvidence({ facts }) {
if (facts.validation === "success") {
return {
entries: [
{
kind: "vote",
code: "firefox-validation-succeeded",
message: "Firefox constructed a valid path to a configured trust anchor.",
data: { trusted: true },
},
],
};
}
return {
entries: facts.errors.map((error) => ({
kind: "warning",
code: error,
message: explainValidationError(error),
})),
};
},
},
};
}
export function createVillageCommunityPlugin() {
return {
manifest: pluginManifest(
"community.village.observer",
"Village community observer",
["advisor"],
),
hooks: {
collectEvidence({ facts }) {
if (facts.hostname !== "library.village") return;
return {
entries: [
{
kind: "evidence",
code: "community-key-continuity",
message: "This certificate has appeared in the synthetic community record for 184 days.",
data: { observers: 7, independentOperators: 3, ageDays: 184 },
},
{
kind: "vote",
code: "community-recommends-trust",
message: "The configured community recommends trusting this exact host certificate.",
data: { trusted: true },
},
],
};
},
},
};
}
export function createCommunityAdvicePlugin({
id,
name,
trusted,
message,
}) {
return {
manifest: pluginManifest(id, name, ["advisor"]),
hooks: {
collectEvidence() {
return {
entries: [
{
kind: "vote",
code: trusted ? "community-votes-trusted" : "community-votes-not-trusted",
message,
data: { trusted },
},
],
};
},
},
};
}
export function createUserDecisionPlugin(decision, options = {}) {
if (decision !== true && decision !== false) return undefined;
const decide = ({ facts, journal }) => ({
trusted: decision,
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(
"local.user.decision",
"Local user decision",
["decision-authority"],
),
hooks: {
onBeforeTlsAccept: decide,
async onTlsFailure(context) {
return decide(context);
},
},
};
}
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",
hostname: facts.hostname,
port: facts.port,
certificateSha256: facts.presentedChain[0].sha256,
};
}
function pluginManifest(id, name, supportedModes) {
return {
manifestVersion: 1,
trustApiVersion: "0.1",
id,
name,
version: "0.0.0",
supportedModes,
capabilities: {},
};
}
function explainValidationError(error) {
const explanations = {
"unknown-issuer":
"Firefox cannot construct a path from this certificate to a configured trust anchor.",
expired: "At least one certificate in the validation path is outside its validity period.",
"hostname-mismatch":
"The leaf certificate does not identify the requested hostname.",
"explicitly-distrusted-authority":
"The chain reaches an authority rejected by an explicit local Browsec rule.",
};
return explanations[error] ?? `Firefox reported certificate error: ${error}.`;
}