Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion extension/src/background.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { endpoint } from "./config";
import { bundle_name, bundle_prev_name, endpoint } from "./config";
import { db } from "./globals";
import {
headersListener,
installListener,
requestListener,
startupListener,
tabCloseListener,
torCircuitListener,
} from "./webcat/listeners";
import { FRAME_TYPES } from "./webcat/resources";
import { setErrorIcon } from "./webcat/ui";
Expand Down Expand Up @@ -82,6 +83,22 @@ browser.webRequest.onHeadersReceived.addListener(
["blocking", "responseHeaders"],
);

// In TBB we send circuit hints from the BundleFetcher; this listener maps the hints from the internal
// format to the secure header expected by TBB that cannot be set directly via fetch
browser.webRequest.onBeforeSendHeaders.addListener(
torCircuitListener,
{
urls: [
`http://*${bundle_name}`,
`https://*${bundle_name}`,
`http://*${bundle_prev_name}`,
`https://*${bundle_prev_name}`,
],
types: ["xmlhttprequest"],
},
["blocking", "requestHeaders"],
);

// Not the best performance idea to act on all tab just for this
browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
const errorUrl = browser.runtime.getURL("pages/error.html");
Expand Down
12 changes: 11 additions & 1 deletion extension/src/webcat/interfaces/originstate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,29 @@ type BundleFetch = {
};

export class BundleFetcher implements Iterable<BundleFetch> {
public static readonly CircuitHeader = "X-WEBCAT-Circuit-Hint";
public static readonly SecureCircuitHeader = "Sec-Tor-Circuit-Hint";

public readonly current: BundleFetch;
public readonly previous: BundleFetch;

constructor(base: string) {
constructor(base: string, originUrl: string | undefined) {
const origin = new URL(originUrl || "").origin;
this.current = {
promise: fetch(`${base}${bundle_name}`, {
cache: "no-store",
headers: {
[BundleFetcher.CircuitHeader]: origin,
},
}),
};

this.previous = {
promise: fetch(`${base}${bundle_prev_name}`, {
cache: "no-store",
headers: {
[BundleFetcher.CircuitHeader]: origin,
},
}),
};
}
Expand Down
32 changes: 32 additions & 0 deletions extension/src/webcat/listeners.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { db, origins, tabs } from "../globals";
import { getHooks } from "./genhooks";
import { hooksType, metadataRequestSource } from "./interfaces/base";
import { WebcatError } from "./interfaces/errors";
import { BundleFetcher } from "./interfaces/originstate";
import { logger } from "./logger";
import { validateOrigin } from "./request";
import { FRAME_TYPES } from "./resources";
Expand Down Expand Up @@ -94,6 +95,7 @@ export async function headersListener(
details.url,
details.tabId,
metadataRequestSource.worker,
details.originUrl,
);
if (result instanceof WebcatError) {
origins.delete(fqdn);
Expand Down Expand Up @@ -230,6 +232,7 @@ export async function requestListener(
details.url,
details.tabId,
metadataRequestSource.main_frame,
details.originUrl,
);
if (result instanceof WebcatError) {
origins.delete(fqdn);
Expand Down Expand Up @@ -263,3 +266,32 @@ export async function requestListener(
// Returning a response here is a very powerful tool, let's think about it later
return {};
}

export async function torCircuitListener(
details: browser.webRequest._OnBeforeSendHeadersDetails,
): Promise<browser.webRequest.BlockingResponse> {
if (!details.requestHeaders) {
console.error("FATAL: request headers not available");
return { cancel: true };
}
if (isExtensionRequest(details)) {
let headers: browser.webRequest.HttpHeaders;
if ((await browser.runtime.getBrowserInfo()).vendor === "Tor Project") {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This limits sending the circuit hints to TBB; on Firefox, no hint will be sent. Before merging, we may want to limit it further to specific versions of TBB, because until TBB implements its part, there's a risk of leaking information across origins.

headers = details.requestHeaders.map((header) => {
if (header.name === BundleFetcher.CircuitHeader) {
return {
name: BundleFetcher.SecureCircuitHeader,
value: header.value,
};
}
return header;
});
} else {
headers = details.requestHeaders.filter((header) => {
return header.name !== BundleFetcher.CircuitHeader;
});
}
return { requestHeaders: headers };
}
return {};
}
22 changes: 22 additions & 0 deletions extension/src/webcat/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export async function validateOrigin(
url: string,
tabId: number,
type: metadataRequestSource,
originUrl: string | undefined,
) {
const enrollment_hash = await db.getFQDNEnrollment(fqdn);
if (enrollment_hash.length === 0) {
Expand Down Expand Up @@ -63,9 +64,30 @@ export async function validateOrigin(
fqdn,
);

// Determine the origin URL to pass to the BundleFetcher, i.e.
// the (scheme, hostname, port) triple of the associated top level frame
switch (type) {
case metadataRequestSource.main_frame:
// If this is main frame navigation, the origin string is the origin of
// the target URL
originUrl = urlobj.origin;
break;
case metadataRequestSource.sub_frame:
// If this is sub frame navigation, the origin string is the origin
// of the tab's top frame
originUrl = new URL((await browser.tabs.get(tabId)).url || "").origin;
break;
case metadataRequestSource.worker:
// If this is a request associated with a worker, the origin is the
// origin of the worker
originUrl = new URL(originUrl || "").origin;
break;
}

// Policy hash is checked at the top and then later again
const newFetcher = new BundleFetcher(
`${urlobj.protocol}//${fqdn}:${urlobj.port}`,
originUrl,
);
const newOriginState = new OriginStateInitial(
newFetcher,
Expand Down
2 changes: 1 addition & 1 deletion extension/tests/webcat/response.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { SHA256 } from "../../src/webcat/utils";

function makeDummyFetcher(): BundleFetcher {
// base URL is irrelevant, fetch will never be awaited in these tests
return new BundleFetcher("https://example.com");
return new BundleFetcher("https://example.com", "https://example.com");
}

// --- Mocks ---
Expand Down
Loading