/
domain-isolator.js
210 lines (178 loc) · 7.4 KB
/
domain-isolator.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
// # domain-isolator.js
// A component for TorBrowser that puts requests from different
// first party domains on separate tor circuits.
// This file is written in call stack order (later functions
// call earlier functions). The code file can be processed
// with docco.js to provide clear documentation.
// ### Abbreviations
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
// Make the logger available.
let logger = Cc["@torproject.org/torbutton-logger;1"]
.getService(Ci.nsISupports).wrappedJSObject;
let { ensureDefaultPrefs } = ChromeUtils.import("resource://torbutton/modules/default-prefs.js", {});
ensureDefaultPrefs();
// Import Services object
ChromeUtils.import("resource://gre/modules/Services.jsm");
// Import crypto object (FF 37+).
Cu.importGlobalProperties(["crypto"]);
// ## mozilla namespace.
// Useful functionality for interacting with Mozilla services.
let mozilla = {};
// __mozilla.protocolProxyService__.
// Mozilla's protocol proxy service, useful for managing proxy connections made
// by the browser.
mozilla.protocolProxyService = Cc["@mozilla.org/network/protocol-proxy-service;1"]
.getService(Ci.nsIProtocolProxyService);
// __mozilla.registerProxyChannelFilter(filterFunction, positionIndex)__.
// Registers a proxy channel filter with the Mozilla Protocol Proxy Service,
// which will help to decide the proxy to be used for a given channel.
// The filterFunction should expect two arguments, (aChannel, aProxy),
// where aProxy is the proxy or list of proxies that would be used by default
// for the given channel, and should return a new Proxy or list of Proxies.
mozilla.registerProxyChannelFilter = function (filterFunction, positionIndex) {
let proxyFilter = {
applyFilter : function (aProxyService, aChannel, aProxy, aCallback) {
aCallback.onProxyFilterResult(filterFunction(aChannel, aProxy));
}
};
mozilla.protocolProxyService.registerChannelFilter(proxyFilter, positionIndex);
};
// ## tor functionality.
let tor = {};
// __tor.noncesForDomains__.
// A mutable map that records what nonce we are using for each domain.
tor.noncesForDomains = {};
// __tor.isolationEabled__.
// A bool that controls if we use SOCKS auth for isolation or not.
tor.isolationEnabled = true;
// __tor.unknownDirtySince__.
// Specifies when the current catch-all circuit was first used
tor.unknownDirtySince = Date.now();
// __tor.socksProxyCredentials(originalProxy, domain)__.
// Takes a proxyInfo object (originalProxy) and returns a new proxyInfo
// object with the same properties, except the username is set to the
// the domain, and the password is a nonce.
tor.socksProxyCredentials = function (originalProxy, domain) {
// Check if we already have a nonce. If not, create
// one for this domain.
if (!tor.noncesForDomains.hasOwnProperty(domain)) {
tor.noncesForDomains[domain] = tor.nonce();
}
let proxy = originalProxy.QueryInterface(Ci.nsIProxyInfo);
return mozilla.protocolProxyService
.newProxyInfoWithAuth("socks",
proxy.host,
proxy.port,
domain, // username
tor.noncesForDomains[domain], // password
"", // aProxyAuthorizationHeader
"", // aConnectionIsolationKey
proxy.flags,
proxy.failoverTimeout,
proxy.failoverProxy);
};
tor.nonce = function() {
// Generate a new 128 bit random tag. Strictly speaking both using a
// cryptographic entropy source and using 128 bits of entropy for the
// tag are likely overkill, as correct behavior only depends on how
// unlikely it is for there to be a collision.
let tag = new Uint8Array(16);
crypto.getRandomValues(tag);
// Convert the tag to a hex string.
let tagStr = "";
for (let i = 0; i < tag.length; i++) {
tagStr += (tag[i] >>> 4).toString(16);
tagStr += (tag[i] & 0x0F).toString(16);
}
return tagStr;
};
tor.newCircuitForDomain = function(domain) {
// Re-generate the nonce for the domain.
if (domain === "") {
domain = "--unknown--";
}
tor.noncesForDomains[domain] = tor.nonce();
logger.eclog(3, "New domain isolation for " + domain + ": " + tor.noncesForDomains[domain]);
};
// __tor.clearIsolation()_.
// Clear the isolation state cache, forcing new circuits to be used for all
// subsequent requests.
tor.clearIsolation = function () {
// Per-domain nonces are stored in a map, so simply re-initialize the map.
tor.noncesForDomains = {};
// Force a rotation on the next catch-all circuit use by setting the creation
// time to the epoch.
tor.unknownDirtySince = 0;
};
// __tor.isolateCircuitsByDomain()__.
// For every HTTPChannel, replaces the default SOCKS proxy with one that authenticates
// to the SOCKS server (the tor client process) with a username (the first party domain)
// and a nonce password. Tor provides a separate circuit for each username+password
// combination.
tor.isolateCircuitsByDomain = function () {
mozilla.registerProxyChannelFilter(function (aChannel, aProxy) {
if (!tor.isolationEnabled) {
return aProxy;
}
try {
let channel = aChannel.QueryInterface(Ci.nsIChannel),
firstPartyDomain = channel.loadInfo.originAttributes.firstPartyDomain;
if (firstPartyDomain === "") {
firstPartyDomain = "--unknown--";
if (Date.now() - tor.unknownDirtySince > 1000*10*60) {
logger.eclog(3, "tor catchall circuit has been dirty for over 10 minutes. Rotating.");
tor.newCircuitForDomain("--unknown--");
tor.unknownDirtySince = Date.now();
}
}
let replacementProxy = tor.socksProxyCredentials(aProxy, firstPartyDomain);
logger.eclog(3, `tor SOCKS: ${channel.URI.spec} via
${replacementProxy.username}:${replacementProxy.password}`);
return replacementProxy;
} catch (e) {
logger.eclog(4, `tor domain isolator error: ${e.message}`);
}
}, 0);
};
// ## XPCOM component construction.
// Module specific constants
const kMODULE_NAME = "TorBrowser Domain Isolator";
const kMODULE_CONTRACTID = "@torproject.org/domain-isolator;1";
const kMODULE_CID = Components.ID("e33fd6d4-270f-475f-a96f-ff3140279f68");
// Import XPCOMUtils object.
const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
// DomainIsolator object.
function DomainIsolator() {
this.wrappedJSObject = this;
}
// Firefox component requirements
DomainIsolator.prototype = {
QueryInterface: ChromeUtils.generateQI([Ci.nsIObserver]),
classDescription: kMODULE_NAME,
classID: kMODULE_CID,
contractID: kMODULE_CONTRACTID,
observe: function (subject, topic, data) {
if (topic === "profile-after-change") {
logger.eclog(3, "domain isolator: set up isolating circuits by domain");
if (Services.prefs.getBoolPref("extensions.torbutton.use_nontor_proxy")) {
tor.isolationEnabled = false;
}
tor.isolateCircuitsByDomain();
}
},
newCircuitForDomain: function (domain) {
tor.newCircuitForDomain(domain);
},
enableIsolation: function() {
tor.isolationEnabled = true;
},
disableIsolation: function() {
tor.isolationEnabled = false;
},
clearIsolation: function() {
tor.clearIsolation();
},
wrappedJSObject: null
};
// Assign factory to global object.
const NSGetFactory = XPCOMUtils.generateNSGetFactory([DomainIsolator]);