174 lines
4.7 KiB
JavaScript
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}.`;
|
|
}
|