|
1
|
+/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
4
|
+
|
|
5
|
+/*
|
|
6
|
+ * This module implements Tor-specific Web Request policies, such as
|
|
7
|
+ * preventing observable cross-site requests to .tor.onion and .bit.onion sites.
|
|
8
|
+ */
|
|
9
|
+
|
|
10
|
+import { ConsoleAPI } from "resource://gre/modules/Console.sys.mjs";
|
|
11
|
+
|
|
12
|
+const log = new ConsoleAPI({
|
|
13
|
+ maxLogLevel: "warn",
|
|
14
|
+ maxLogLevelPref: "browser.torRequestWatch.log_level",
|
|
15
|
+ prefix: "TorRequestWatch",
|
|
16
|
+});
|
|
17
|
+
|
|
18
|
+class RequestObserver {
|
|
19
|
+ static #topics = [
|
|
20
|
+ "http-on-modify-request",
|
|
21
|
+ "http-on-examine-response",
|
|
22
|
+ "http-on-examine-cached-response",
|
|
23
|
+ "http-on-examine-merged-response",
|
|
24
|
+ ];
|
|
25
|
+ #asObserver(addOrRemove) {
|
|
26
|
+ const action = Services.obs[`${addOrRemove}Observer`].bind(Services.obs);
|
|
27
|
+ for (const topic of RequestObserver.#topics) {
|
|
28
|
+ action(this, topic);
|
|
29
|
+ }
|
|
30
|
+ }
|
|
31
|
+
|
|
32
|
+ start() {
|
|
33
|
+ this.#asObserver("add");
|
|
34
|
+ log.debug("Started");
|
|
35
|
+ }
|
|
36
|
+ stop() {
|
|
37
|
+ this.#asObserver("remove");
|
|
38
|
+ log.debug("Stopped");
|
|
39
|
+ }
|
|
40
|
+
|
|
41
|
+ // nsIObserver implementation
|
|
42
|
+ observe(subject, topic, data) {
|
|
43
|
+ try {
|
|
44
|
+ let channel = ChannelWrapper.get(
|
|
45
|
+ subject.QueryInterface(Ci.nsIHttpChannel)
|
|
46
|
+ );
|
|
47
|
+ switch (topic) {
|
|
48
|
+ case "http-on-modify-request":
|
|
49
|
+ this.onRequest(channel);
|
|
50
|
+ break;
|
|
51
|
+ case "http-on-examine-cached-response":
|
|
52
|
+ case "http-on-examine-merged-response":
|
|
53
|
+ channel.isCached = true;
|
|
54
|
+ // falls through
|
|
55
|
+ case "http-on-examine-response":
|
|
56
|
+ this.onResponse(channel);
|
|
57
|
+ break;
|
|
58
|
+ }
|
|
59
|
+ } catch (e) {
|
|
60
|
+ log.error(e);
|
|
61
|
+ }
|
|
62
|
+ }
|
|
63
|
+
|
|
64
|
+ onRequest(channel) {
|
|
65
|
+ if (this.shouldBlind(channel, channel.documentURL)) {
|
|
66
|
+ log.warn(`Blocking cross-site ${channel.finalURL} ${channel.type} load.`);
|
|
67
|
+ channel.cancel(Cr.NS_ERROR_ABORT);
|
|
68
|
+ }
|
|
69
|
+ }
|
|
70
|
+ onResponse(channel) {
|
|
71
|
+ if (!channel.documentURL && this.shouldBlind(channel, channel.originURL)) {
|
|
72
|
+ const COOP = "cross-origin-opener-policy";
|
|
73
|
+ // we break window.opener references if needed to mitigate XS-Leaks
|
|
74
|
+ for (let h of channel.getResponseHeaders()) {
|
|
75
|
+ if (h.name.toLowerCase() === COOP && h.value === "same-origin") {
|
|
76
|
+ log.debug(`${COOP} is already same-origin, nothing to do.`);
|
|
77
|
+ return;
|
|
78
|
+ }
|
|
79
|
+ }
|
|
80
|
+ log.warn(`Blinding cross-site ${channel.finalURL} load.`);
|
|
81
|
+ channel.setResponseHeader(COOP, "same-origin-allow-popups");
|
|
82
|
+ }
|
|
83
|
+ }
|
|
84
|
+
|
|
85
|
+ isCrossOrigin(url1, url2) {
|
|
86
|
+ return new URL(url1).origin !== new URL(url2).origin;
|
|
87
|
+ }
|
|
88
|
+ shouldBlindCrossOrigin(uri) {
|
|
89
|
+ try {
|
|
90
|
+ let { host } = uri;
|
|
91
|
+ if (host.endsWith(".onion")) {
|
|
92
|
+ const previousPart = host.slice(-10, -6);
|
|
93
|
+ return (
|
|
94
|
+ previousPart && (previousPart === ".tor" || previousPart === ".bit")
|
|
95
|
+ );
|
|
96
|
+ }
|
|
97
|
+ } catch (e) {
|
|
98
|
+ // no host
|
|
99
|
+ }
|
|
100
|
+ return false;
|
|
101
|
+ }
|
|
102
|
+ shouldBlind(channel, sourceURL) {
|
|
103
|
+ return (
|
|
104
|
+ sourceURL &&
|
|
105
|
+ this.shouldBlindCrossOrigin(channel.finalURI) &&
|
|
106
|
+ this.isCrossOrigin(channel.finalURL, sourceURL)
|
|
107
|
+ );
|
|
108
|
+ }
|
|
109
|
+}
|
|
110
|
+
|
|
111
|
+let observer;
|
|
112
|
+export const TorRequestWatch = {
|
|
113
|
+ start() {
|
|
114
|
+ if (!observer) {
|
|
115
|
+ (observer = new RequestObserver()).start();
|
|
116
|
+ }
|
|
117
|
+ },
|
|
118
|
+ stop() {
|
|
119
|
+ if (observer) {
|
|
120
|
+ observer.stop();
|
|
121
|
+ observer = null;
|
|
122
|
+ }
|
|
123
|
+ },
|
|
124
|
+}; |