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