Implement scoped authority trust overlay
This commit is contained in:
1 parent
1eeb8bac05
commit
d479c459fc
17 files changed
+712
-65
No files matched your search
@@ -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",
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user