Pier Angelo Vendrame pushed to branch tor-browser-102.11.0esr-13.0-1 at The Tor Project / Applications / Tor Browser
Commits: 98e75e48 by Pier Angelo Vendrame at 2023-05-17T10:25:44+02:00 fixup! Bug 40933: Add tor-launcher functionality
Added a newnym function
- - - - - 74e39196 by Pier Angelo Vendrame at 2023-05-17T10:25:52+02:00 fixup! Bug 10760: Integrate TorButton to TorBrowser core
Bug 40938: Moving the domain isolator out of torbutton
- - - - - 0f9ea290 by Arthur Edelstein at 2023-05-17T10:25:53+02:00 Bug 3455: Add DomainIsolator, for isolating circuit by domain.
Add an XPCOM component that registers a ProtocolProxyChannelFilter which sets the username/password for each web request according to url bar domain.
Bug 9442: Add New Circuit button
Bug 13766: Set a 10 minute circuit dirty timeout for the catch-all circ.
Bug 19206: Include a 128 bit random tag as part of the domain isolator nonce.
Bug 19206: Clear out the domain isolator state on `New Identity`.
Bug 21201.2: Isolate by firstPartyDomain from OriginAttributes
Bug 21745: Fix handling of catch-all circuit
Bug 41741: Refactor the domain isolator and new circuit
- - - - - 14058280 by Pier Angelo Vendrame at 2023-05-17T10:25:53+02:00 fixup! Bug 3455: Add DomainIsolator, for isolating circuit by domain.
Refactors to the old JS code.
- - - - - c94c9662 by Pier Angelo Vendrame at 2023-05-17T10:25:53+02:00 fixup! Bug 3455: Add DomainIsolator, for isolating circuit by domain.
Manage NEWNYM here.
- - - - - 994e4ce2 by Pier Angelo Vendrame at 2023-05-17T10:25:54+02:00 fixup! Bug 3455: Add DomainIsolator, for isolating circuit by domain.
Removed the XPCOM definition of the domain isolator.
- - - - - bcabee6b by Pier Angelo Vendrame at 2023-05-17T10:25:54+02:00 fixup! Bug 10760: Integrate TorButton to TorBrowser core
Extract the new identity button from torbutton
- - - - - 16cf26ff by Pier Angelo Vendrame at 2023-05-17T10:36:36+02:00 fixup! Bug 3455: Add DomainIsolator, for isolating circuit by domain.
Actually added the new circuit button.
- - - - - 39b5273c by Pier Angelo Vendrame at 2023-05-17T10:37:31+02:00 fixup! Bug 41600: Add a tor circuit display panel.
Use the new domain isolator interface.
- - - - - 9cd44b23 by Pier Angelo Vendrame at 2023-05-17T10:38:45+02:00 fixup! Bug 40209: Implement Basic Crypto Safety
Use the new domain isolator interface
- - - - - b27d2320 by Pier Angelo Vendrame at 2023-05-17T10:38:48+02:00 fixup! Bug 10760: Integrate TorButton to TorBrowser core
Remove string changes from Torbutton. We will add them back in the TorStrings commit.
- - - - - 5b547f81 by Pier Angelo Vendrame at 2023-05-17T10:38:49+02:00 fixup! Add TorStrings module for localization
Add our DTDs where needed.
These changes were originally in the torbutton commit, but I think they are better fit here, with all the strings files.
- - - - -
15 changed files:
- browser/actors/CryptoSafetyParent.jsm - browser/base/content/appmenu-viewcache.inc.xhtml - browser/base/content/browser-menubar.inc - browser/base/content/browser-sets.inc - browser/base/content/browser.js - browser/base/content/navigator-toolbox.inc.xhtml - browser/components/torcircuit/content/torCircuitPanel.js - + toolkit/components/tor-launcher/TorDomainIsolator.jsm - toolkit/components/tor-launcher/TorProtocolService.jsm - toolkit/components/tor-launcher/TorStartupService.jsm - toolkit/components/tor-launcher/moz.build - toolkit/torbutton/chrome/content/torbutton.js - − toolkit/torbutton/components/domain-isolator.js - toolkit/torbutton/jar.mn - toolkit/torbutton/modules/utils.js
Changes:
===================================== browser/actors/CryptoSafetyParent.jsm ===================================== @@ -12,6 +12,12 @@ const { XPCOMUtils } = ChromeUtils.import( "resource://gre/modules/XPCOMUtils.jsm" );
+ChromeUtils.defineModuleGetter( + this, + "TorDomainIsolator", + "resource://gre/modules/TorDomainIsolator.jsm" +); + XPCOMUtils.defineLazyGetter(this, "cryptoSafetyBundle", () => { return Services.strings.createBundle( "chrome://browser/locale/cryptoSafetyPrompt.properties" @@ -75,7 +81,11 @@ class CryptoSafetyParent extends JSWindowActorParent { );
if (buttonPressed === 0) { - this.browsingContext.topChromeWindow.torbutton_new_circuit(); + const { browsingContext } = this.manager; + const browser = browsingContext.embedderElement; + if (browser) { + TorDomainIsolator.newCircuitForBrowser(browser.ownerGlobal.gBrowser); + } } } }
===================================== browser/base/content/appmenu-viewcache.inc.xhtml ===================================== @@ -63,9 +63,9 @@ key="new-identity-key"/> <toolbarbutton id="appMenuNewCircuit" class="subviewbutton" - key="torbutton-new-circuit-key" + key="new-circuit-key" label="&torbutton.context_menu.new_circuit_sentence_case;" - oncommand="torbutton_new_circuit();"/> + oncommand="TorDomainIsolator.newCircuitForBrowser(gBrowser);"/> <toolbarseparator/> <toolbarbutton id="appMenu-bookmarks-button" class="subviewbutton subviewbutton-nav"
===================================== browser/base/content/browser-menubar.inc ===================================== @@ -33,9 +33,9 @@ key="new-identity-key"/> <menuitem id="menu_newCircuit" accesskey="&torbutton.context_menu.new_circuit_key;" - key="torbutton-new-circuit-key" + key="new-circuit-key" label="&torbutton.context_menu.new_circuit;" - oncommand="torbutton_new_circuit();"/> + oncommand="TorDomainIsolator.newCircuitForBrowser(gBrowser);"/> <menuseparator/> <menuitem id="menu_openLocation" hidden="true"
===================================== browser/base/content/browser-sets.inc ===================================== @@ -389,5 +389,5 @@ internal="true"/> #endif <key id="new-identity-key" modifiers="accel shift" key="U" oncommand="NewIdentityButton.onCommand(event)"/> - <key id="torbutton-new-circuit-key" modifiers="accel shift" key="L" oncommand="torbutton_new_circuit()"/> + <key id="new-circuit-key" modifiers="accel shift" key="L" oncommand="TorDomainIsolator.newCircuitForBrowser(gBrowser)"/> </keyset>
===================================== browser/base/content/browser.js ===================================== @@ -80,6 +80,7 @@ XPCOMUtils.defineLazyModuleGetters(this, { TabCrashHandler: "resource:///modules/ContentCrashHandlers.jsm", TelemetryEnvironment: "resource://gre/modules/TelemetryEnvironment.jsm", TorConnect: "resource:///modules/TorConnect.jsm", + TorDomainIsolator: "resource://gre/modules/TorDomainIsolator.jsm", Translation: "resource:///modules/translation/TranslationParent.jsm", UITour: "resource:///modules/UITour.jsm", UpdateUtils: "resource://gre/modules/UpdateUtils.jsm",
===================================== browser/base/content/navigator-toolbox.inc.xhtml ===================================== @@ -557,7 +557,7 @@
<toolbarbutton id="new-circuit-button" class="toolbarbutton-1 chromeclass-toolbar-additional" label="&torbutton.context_menu.new_circuit;" - oncommand="torbutton_new_circuit();" + oncommand="TorDomainIsolator.newCircuitForBrowser(gBrowser);" tooltiptext="&torbutton.context_menu.new_circuit;"/>
<toolbarbutton id="fullscreen-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
===================================== browser/components/torcircuit/content/torCircuitPanel.js ===================================== @@ -193,7 +193,7 @@ var gTorCircuitPanel = { document .getElementById("tor-circuit-new-circuit") .addEventListener("command", () => { - torbutton_new_circuit(); + TorDomainIsolator.newCircuitForBrowser(gBrowser); // And hide. // NOTE: focus should return to the toolbar button, which we expect to // remain visible during reload. @@ -415,20 +415,14 @@ var gTorCircuitPanel = { */ _updateCurrentBrowser(matchingCredentials = null) { const browser = gBrowser.selectedBrowser; - const { getDomainForBrowser } = ChromeUtils.import( - "resource://torbutton/modules/utils.js" - ); - const domain = getDomainForBrowser(browser); + const domain = TorDomainIsolator.getDomainForBrowser(browser); // We choose the currentURI, which matches what is shown in the URL bar and // will match up with the domain. // In contrast, documentURI corresponds to the shown page. E.g. it could // point to "about:certerror". const scheme = browser.currentURI?.scheme;
- const domainIsolator = Cc["@torproject.org/domain-isolator;1"].getService( - Ci.nsISupports - ).wrappedJSObject; - let credentials = domainIsolator.getSocksProxyCredentials( + let credentials = TorDomainIsolator.getSocksProxyCredentials( domain, browser.contentPrincipal.originAttributes.userContextId );
===================================== toolkit/components/tor-launcher/TorDomainIsolator.jsm ===================================== @@ -0,0 +1,362 @@ +// A component for Tor Browser that puts requests from different +// first party domains on separate Tor circuits. + +var EXPORTED_SYMBOLS = ["TorDomainIsolator"]; + +const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); +const { XPCOMUtils } = ChromeUtils.import( + "resource://gre/modules/XPCOMUtils.jsm" +); +const { ConsoleAPI } = ChromeUtils.import("resource://gre/modules/Console.jsm"); + +Cu.importGlobalProperties(["crypto"]); + +XPCOMUtils.defineLazyServiceGetters(this, { + ProtocolProxyService: [ + "@mozilla.org/network/protocol-proxy-service;1", + "nsIProtocolProxyService", + ], +}); + +ChromeUtils.defineModuleGetter( + this, + "TorProtocolService", + "resource://gre/modules/TorProtocolService.jsm" +); + +const logger = new ConsoleAPI({ + prefix: "TorDomainIsolator", + maxLogLevel: "warn", + maxLogLevelPref: "browser.tordomainisolator.loglevel", +}); + +// The string to use instead of the domain when it is not known. +const CATCHALL_DOMAIN = "--unknown--"; + +// The preference to observe, to know whether isolation should be enabled or +// disabled. +const NON_TOR_PROXY_PREF = "extensions.torbutton.use_nontor_proxy"; + +// The topic of new identity, to observe to cleanup all the nonces. +const NEW_IDENTITY_TOPIC = "new-identity-requested"; + +class TorDomainIsolatorImpl { + // A mutable map that records what nonce we are using for each domain. + #noncesForDomains = new Map(); + + // A mutable map that records what nonce we are using for each tab container. + #noncesForUserContextId = new Map(); + + // A bool that controls if we use SOCKS auth for isolation or not. + #isolationEnabled = true; + + // Specifies when the current catch-all circuit was first used + #catchallDirtySince = Date.now(); + + /** + * Initialize the domain isolator. + * This function will setup the proxy filter that injects the credentials and + * register some observers. + */ + init() { + logger.info("Setup circuit isolation by domain and user context"); + + if (Services.prefs.getBoolPref(NON_TOR_PROXY_PREF)) { + this.#isolationEnabled = false; + } + this.#setupProxyFilter(); + + Services.prefs.addObserver(NON_TOR_PROXY_PREF, this); + Services.obs.addObserver(this, NEW_IDENTITY_TOPIC); + } + + /** + * Removes the observers added in the initialization. + */ + uninit() { + Services.prefs.removeObserver(NON_TOR_PROXY_PREF, this); + Services.obs.removeObserver(this, NEW_IDENTITY_TOPIC); + } + + enable() { + logger.trace("Domain isolation enabled"); + this.#isolationEnabled = true; + } + + disable() { + logger.trace("Domain isolation disabled"); + this.#isolationEnabled = false; + } + + /** + * Return the credentials to use as username and password for the SOCKS proxy, + * given a certain domain and userContextId. Optionally, create them. + * + * @param firstPartyDomain The first party domain associated to the requests + * @param userContextId The context ID associated to the request + * @param create Whether to create the nonce, if it is not available + * @return Either the credential, or null if we do not have them and create is + * false. + */ + getSocksProxyCredentials(firstPartyDomain, userContextId, create = false) { + if (!this.#noncesForDomains.has(firstPartyDomain)) { + if (!create) { + return null; + } + const nonce = this.#nonce(); + logger.info(`New nonce for first party ${firstPartyDomain}: ${nonce}`); + this.#noncesForDomains.set(firstPartyDomain, nonce); + } + if (!this.#noncesForUserContextId.has(userContextId)) { + if (!create) { + return null; + } + const nonce = this.#nonce(); + logger.info(`New nonce for userContextId ${userContextId}: ${nonce}`); + this.#noncesForUserContextId.set(userContextId, nonce); + } + return { + username: this.#makeUsername(firstPartyDomain, userContextId), + password: + this.#noncesForDomains.get(firstPartyDomain) + + this.#noncesForUserContextId.get(userContextId), + }; + } + + /** + * Create a new nonce for the FP domain of the selected browser and reload the + * tab with a new circuit. + * + * @param browser Should be the gBrowser from the context of the caller + */ + newCircuitForBrowser(browser) { + const firstPartyDomain = getDomainForBrowser(browser.selectedBrowser); + this.#newCircuitForDomain(firstPartyDomain); + // TODO: How to properly handle the user context? Should we use + // (domain, userContextId) pairs, instead of concatenating nonces? + browser.reloadWithFlags(Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE); + } + + /** + * Clear the isolation state cache, forcing new circuits to be used for all + * subsequent requests. + */ + clearIsolation() { + logger.trace("Clearing isolation nonces."); + + // Per-domain and per contextId nonces are stored in maps, so simply clear + // them. + this.#noncesForDomains.clear(); + this.#noncesForUserContextId.clear(); + + // Force a rotation on the next catch-all circuit use by setting the + // creation time to the epoch. + this.#catchallDirtySince = 0; + } + + async observe(subject, topic, data) { + if (topic === "nsPref:changed" && data === NON_TOR_PROXY_PREF) { + if (Services.prefs.getBoolPref(NON_TOR_PROXY_PREF)) { + this.disable(); + } else { + this.enable(); + } + } else if (topic === NEW_IDENTITY_TOPIC) { + logger.info( + "New identity has been requested, clearing isolation tokens." + ); + this.clearIsolation(); + try { + await TorProtocolService.newnym(); + } catch (e) { + logger.error("Could not send the newnym command", e); + // TODO: What UX to use here? See tor-browser#41708 + } + } + } + + /** + * Setup a filter that 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 userContextId) and a nonce + * password. + * Tor provides a separate circuit for each username+password combination. + */ + #setupProxyFilter() { + const filterFunction = (aChannel, aProxy) => { + if (!this.#isolationEnabled) { + return aProxy; + } + try { + const channel = aChannel.QueryInterface(Ci.nsIChannel); + let firstPartyDomain = + channel.loadInfo.originAttributes.firstPartyDomain; + const userContextId = channel.loadInfo.originAttributes.userContextId; + if (firstPartyDomain === "") { + firstPartyDomain = CATCHALL_DOMAIN; + if (Date.now() - this.#catchallDirtySince > 1000 * 10 * 60) { + logger.info( + "tor catchall circuit has been dirty for over 10 minutes. Rotating." + ); + this.#newCircuitForDomain(CATCHALL_DOMAIN); + this.#catchallDirtySince = Date.now(); + } + } + const replacementProxy = this.#applySocksProxyCredentials( + aProxy, + firstPartyDomain, + userContextId + ); + logger.debug( + `Requested ${channel.URI.spec} via ${replacementProxy.username}:${replacementProxy.password}` + ); + return replacementProxy; + } catch (e) { + logger.error("Error while setting a new proxy", e); + return null; + } + }; + + ProtocolProxyService.registerChannelFilter( + { + applyFilter(aChannel, aProxy, aCallback) { + aCallback.onProxyFilterResult(filterFunction(aChannel, aProxy)); + }, + }, + 0 + ); + } + + /** + * 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 userContextId, and the password is a nonce. + */ + #applySocksProxyCredentials(originalProxy, domain, userContextId) { + const proxy = originalProxy.QueryInterface(Ci.nsIProxyInfo); + const { username, password } = this.getSocksProxyCredentials( + domain, + userContextId, + true + ); + return ProtocolProxyService.newProxyInfoWithAuth( + "socks", + proxy.host, + proxy.port, + username, + password, + "", // aProxyAuthorizationHeader + "", // aConnectionIsolationKey + proxy.flags, + proxy.failoverTimeout, + proxy.failoverProxy + ); + } + + /** + * Combine the needed data into a username for the proxy. + */ + #makeUsername(domain, userContextId) { + if (!domain) { + domain = CATCHALL_DOMAIN; + } + return `${domain}:${userContextId}`; + } + + /** + * 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. + */ + #nonce() { + return Array.from(crypto.getRandomValues(new Uint8Array(16)), byte => + byte.toString(16).padStart(2, "0") + ).join(""); + } + + /** + * Re-generate the nonce for a certain domain. + */ + #newCircuitForDomain(domain) { + if (!domain) { + domain = CATCHALL_DOMAIN; + } + this.#noncesForDomains.set(domain, this.#nonce()); + logger.info( + `New domain isolation for ${domain}: ${this.#noncesForDomains.get( + domain + )}` + ); + } + + /** + * Re-generate the nonce for a userContextId. + * + * Currently, this function is not hooked to anything. + */ + #newCircuitForUserContextId(userContextId) { + this.#noncesForUserContextId.set(userContextId, this.#nonce()); + logger.info( + `New container isolation for ${userContextId}: ${this.#noncesForUserContextId.get( + userContextId + )}` + ); + } +} + +/** + * Get the first party domain for a certain browser. + * + * @param browser The browser to get the FP-domain for. + * + * Please notice that it should be gBrowser.selectedBrowser, because + * browser.documentURI is the actual shown page, and might be an error page. + * In this case, we rely on currentURI, which for gBrowser is an alias of + * gBrowser.selectedBrowser.currentURI. + * See browser/base/content/tabbrowser.js and tor-browser#31562. + */ +function getDomainForBrowser(browser) { + let fpd = browser.contentPrincipal.originAttributes.firstPartyDomain; + + // Bug 31562: For neterror or certerror, get the original URL from + // browser.currentURI and use it to calculate the firstPartyDomain. + const knownErrors = [ + "about:neterror", + "about:certerror", + "about:httpsonlyerror", + ]; + const { documentURI } = browser; + if ( + documentURI && + documentURI.schemeIs("about") && + knownErrors.some(x => documentURI.spec.startsWith(x)) + ) { + const knownSchemes = ["http", "https"]; + const currentURI = browser.currentURI; + if (currentURI && knownSchemes.some(x => currentURI.schemeIs(x))) { + try { + fpd = Services.eTLD.getBaseDomainFromHost(currentURI.host); + } catch (e) { + if ( + e.result === Cr.NS_ERROR_HOST_IS_IP_ADDRESS || + e.result === Cr.NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS + ) { + fpd = currentURI.host; + } else { + logger.error( + `Failed to get first party domain for host ${currentURI.host}`, + e + ); + } + } + } + } + + return fpd; +} + +const TorDomainIsolator = new TorDomainIsolatorImpl(); +// Reduce global vars pollution +TorDomainIsolator.getDomainForBrowser = getDomainForBrowser;
===================================== toolkit/components/tor-launcher/TorProtocolService.jsm ===================================== @@ -4,6 +4,7 @@
var EXPORTED_SYMBOLS = ["TorProtocolService"];
+const { ConsoleAPI } = ChromeUtils.import("resource://gre/modules/Console.jsm"); const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); const { setTimeout } = ChromeUtils.import("resource://gre/modules/Timer.jsm"); ChromeUtils.defineModuleGetter( @@ -11,9 +12,6 @@ ChromeUtils.defineModuleGetter( "FileUtils", "resource://gre/modules/FileUtils.jsm" ); -const { XPCOMUtils } = ChromeUtils.import( - "resource://gre/modules/XPCOMUtils.jsm" -);
Cu.importGlobalProperties(["crypto"]);
@@ -45,18 +43,9 @@ const TorTopics = Object.freeze({ ProcessRestarted: "TorProcessRestarted", });
-// Logger adapted from CustomizableUI.jsm -XPCOMUtils.defineLazyGetter(this, "logger", () => { - const { ConsoleAPI } = ChromeUtils.import( - "resource://gre/modules/Console.jsm" - ); - // TODO: Use a preference to set the log level. - const consoleOptions = { - // maxLogLevel: "warn", - maxLogLevel: "all", - prefix: "TorProtocolService", - }; - return new ConsoleAPI(consoleOptions); +const logger = new ConsoleAPI({ + maxLogLevel: "warn", + prefix: "TorProtocolService", });
// Manage the connection to tor's control port, to update its settings and query @@ -194,6 +183,10 @@ const TorProtocolService = { TorMonitorService.retrieveBootstrapStatus(); },
+ async newnym() { + return this.sendCommand("SIGNAL NEWNYM"); + }, + // TODO: transform the following 4 functions in getters. At the moment they // are also used in torbutton.
===================================== toolkit/components/tor-launcher/TorStartupService.jsm ===================================== @@ -33,6 +33,12 @@ ChromeUtils.defineModuleGetter( "resource:///modules/TorSettings.jsm" );
+ChromeUtils.defineModuleGetter( + this, + "TorDomainIsolator", + "resource://gre/modules/TorDomainIsolator.jsm" +); + /* Browser observer topis */ const BrowserTopics = Object.freeze({ ProfileAfterChange: "profile-after-change", @@ -67,12 +73,16 @@ class TorStartupService { TorSettings.init(); TorConnect.init();
+ TorDomainIsolator.init(); + gInited = true; }
_uninit() { Services.obs.removeObserver(this, BrowserTopics.QuitApplicationGranted);
+ TorDomainIsolator.uninit(); + // Close any helper connection first... TorProtocolService.uninit(); // ... and only then closes the event monitor connection, which will cause
===================================== toolkit/components/tor-launcher/moz.build ===================================== @@ -1,5 +1,6 @@ EXTRA_JS_MODULES += [ "TorBootstrapRequest.jsm", + "TorDomainIsolator.jsm", "TorLauncherUtil.jsm", "TorMonitorService.jsm", "TorParsers.jsm",
===================================== toolkit/torbutton/chrome/content/torbutton.js ===================================== @@ -1,6 +1,5 @@ // window globals var torbutton_init; -var torbutton_new_circuit;
(() => { // Bug 1506 P1-P5: This is the main Torbutton overlay file. Much needs to be @@ -16,9 +15,7 @@ var torbutton_new_circuit;
let { unescapeTorString, - getDomainForBrowser, torbutton_log, - torbutton_get_property_string, } = ChromeUtils.import("resource://torbutton/modules/utils.js"); let { configureControlPortModule, wait_for_controller } = ChromeUtils.import( "resource://torbutton/modules/tor-control-port.js" @@ -46,32 +43,22 @@ var torbutton_new_circuit; // in a component, not the XUL overlay. var torbutton_unique_pref_observer = { register() { - this.forced_ua = false; - m_tb_prefs.addObserver("extensions.torbutton", this); - m_tb_prefs.addObserver("browser.privatebrowsing.autostart", this); - m_tb_prefs.addObserver("javascript", this); + Services.prefs.addObserver("browser.privatebrowsing.autostart", this); },
unregister() { - m_tb_prefs.removeObserver("extensions.torbutton", this); - m_tb_prefs.removeObserver("browser.privatebrowsing.autostart", this); - m_tb_prefs.removeObserver("javascript", this); + Services.prefs.removeObserver("browser.privatebrowsing.autostart", this); },
// topic: what event occurred // subject: what nsIPrefBranch we're observing // data: which pref has been changed (relative to subject) observe(subject, topic, data) { - if (topic !== "nsPref:changed") { - return; - } - switch (data) { - case "browser.privatebrowsing.autostart": - torbutton_update_disk_prefs(); - break; - case "extensions.torbutton.use_nontor_proxy": - torbutton_use_nontor_proxy(); - break; + if ( + topic === "nsPref:changed" && + data === "browser.privatebrowsing.autostart" + ) { + torbutton_update_disk_prefs(); } }, }; @@ -113,62 +100,6 @@ var torbutton_new_circuit; }, };
- var torbutton_new_identity_observers = { - register() { - Services.obs.addObserver(this, "new-identity-requested"); - }, - - observe(aSubject, aTopic, aData) { - if (aTopic !== "new-identity-requested") { - return; - } - - // Clear the domain isolation state. - torbutton_log(3, "Clearing domain isolator"); - const domainIsolator = Cc["@torproject.org/domain-isolator;1"].getService( - Ci.nsISupports - ).wrappedJSObject; - domainIsolator.clearIsolation(); - - torbutton_log(3, "New Identity: Sending NEWNYM"); - // We only support TBB for newnym. - if ( - !m_tb_control_pass || - (!m_tb_control_ipc_file && !m_tb_control_port) - ) { - const warning = torbutton_get_property_string( - "torbutton.popup.no_newnym" - ); - torbutton_log( - 5, - "Torbutton cannot safely newnym. It does not have access to the Tor Control Port." - ); - window.alert(warning); - } else { - const warning = torbutton_get_property_string( - "torbutton.popup.no_newnym" - ); - torbutton_send_ctrl_cmd("SIGNAL NEWNYM") - .then(res => { - if (!res) { - torbutton_log( - 5, - "Torbutton was unable to request a new circuit from Tor" - ); - window.alert(warning); - } - }) - .catch(e => { - torbutton_log( - 5, - "Torbutton was unable to request a new circuit from Tor " + e - ); - window.alert(warning); - }); - } - }, - }; - // Bug 1506 P2-P4: This code sets some version variables that are irrelevant. // It does read out some important environment variables, though. It is // called once per browser window.. This might belong in a component. @@ -258,8 +189,6 @@ var torbutton_new_circuit; true );
- torbutton_new_identity_observers.register(); - torbutton_log(3, "init completed"); };
@@ -374,36 +303,6 @@ var torbutton_new_circuit; return response; }
- // Bug 1506 P4: Needed for New IP Address - torbutton_new_circuit = function() { - let firstPartyDomain = getDomainForBrowser(gBrowser.selectedBrowser); - - let domainIsolator = Cc["@torproject.org/domain-isolator;1"].getService( - Ci.nsISupports - ).wrappedJSObject; - - domainIsolator.newCircuitForDomain(firstPartyDomain); - - gBrowser.reloadWithFlags(Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE); - }; - - /* Called when we switch the use_nontor_proxy pref in either direction. - * - * Enables/disables domain isolation and then does new identity - */ - function torbutton_use_nontor_proxy() { - let domainIsolator = Cc["@torproject.org/domain-isolator;1"].getService( - Ci.nsISupports - ).wrappedJSObject; - - if (m_tb_prefs.getBoolPref("extensions.torbutton.use_nontor_proxy")) { - // Disable domain isolation - domainIsolator.disableIsolation(); - } else { - domainIsolator.enableIsolation(); - } - } - async function torbutton_do_tor_check() { let checkSvc = Cc["@torproject.org/torbutton-torCheckService;1"].getService( Ci.nsISupports
===================================== toolkit/torbutton/components/domain-isolator.js deleted ===================================== @@ -1,312 +0,0 @@ -// # 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"); -const { XPCOMUtils } = ChromeUtils.import( - "resource://gre/modules/XPCOMUtils.jsm" -); - -XPCOMUtils.defineLazyModuleGetters(this, { - ComponentUtils: "resource://gre/modules/ComponentUtils.jsm", -}); - -// Make the logger available. -let logger = Cc["@torproject.org/torbutton-logger;1"].getService(Ci.nsISupports) - .wrappedJSObject; - -// 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(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 = new Map(); - -// __tor.noncesForUserContextId__. -// A mutable map that records what nonce we are using for each tab container. -tor.noncesForUserContextId = new Map(); - -// __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.passwordForDomainAndUserContextId = function( - domain, - userContextId, - create -) { - // Check if we already have a nonce. If not, possibly create one for this - // domain and userContextId. - if (!tor.noncesForDomains.has(domain)) { - if (!create) { - return null; - } - tor.noncesForDomains.set(domain, tor.nonce()); - } - if (!tor.noncesForUserContextId.has(userContextId)) { - if (!create) { - return null; - } - tor.noncesForUserContextId.set(userContextId, tor.nonce()); - } - return ( - tor.noncesForDomains.get(domain) + - tor.noncesForUserContextId.get(userContextId) - ); -}; - -tor.usernameForDomainAndUserContextId = function(domain, userContextId) { - return `${domain}:${userContextId}`; -}; - -// __tor.socksProxyCredentials(originalProxy, domain, userContextId)__. -// 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 userContextId, and the password is a nonce. -tor.socksProxyCredentials = function(originalProxy, domain, userContextId) { - let proxy = originalProxy.QueryInterface(Ci.nsIProxyInfo); - let proxyUsername = tor.usernameForDomainAndUserContextId( - domain, - userContextId - ); - let proxyPassword = tor.passwordForDomainAndUserContextId( - domain, - userContextId, - true - ); - return mozilla.protocolProxyService.newProxyInfoWithAuth( - "socks", - proxy.host, - proxy.port, - proxyUsername, - proxyPassword, - "", // 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.set(domain, tor.nonce()); - logger.eclog( - 3, - `New domain isolation for ${domain}: ${tor.noncesForDomains.get(domain)}` - ); -}; - -tor.newCircuitForUserContextId = function(userContextId) { - // Re-generate the nonce for the context. - tor.noncesForUserContextId.set(userContextId, tor.nonce()); - logger.eclog( - 3, - `New container isolation for ${userContextId}: ${tor.noncesForUserContextId.get( - userContextId - )}` - ); -}; - -// __tor.clearIsolation()_. -// Clear the isolation state cache, forcing new circuits to be used for all -// subsequent requests. -tor.clearIsolation = function() { - // Per-domain and per contextId nonces are stored in maps, so simply clear them. - tor.noncesForDomains.clear(); - tor.noncesForUserContextId.clear(); - - // 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 userContextId) 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, - userContextId = channel.loadInfo.originAttributes.userContextId; - 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, - userContextId - ); - 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}`); - return null; - } - }, 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"); - -// 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(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(domain) { - tor.newCircuitForDomain(domain); - }, - - /** - * Return the stored SOCKS proxy username and password for the given domain - * and user context ID. - * - * @param {string} firstPartyDomain - The domain to lookup credentials for. - * @param {integer} userContextId - The ID for the user context. - * - * @return {{ username: string, password: string }?} - The SOCKS credentials, - * or null if none are found. - */ - getSocksProxyCredentials(firstPartyDomain, userContextId) { - if (firstPartyDomain == "") { - firstPartyDomain = "--unknown--"; - } - let proxyPassword = tor.passwordForDomainAndUserContextId( - firstPartyDomain, - userContextId, - // Do not create a new entry if it does not exist. - false - ); - if (!proxyPassword) { - return null; - } - return { - username: tor.usernameForDomainAndUserContextId( - firstPartyDomain, - userContextId - ), - password: proxyPassword, - }; - }, - - enableIsolation() { - tor.isolationEnabled = true; - }, - - disableIsolation() { - tor.isolationEnabled = false; - }, - - clearIsolation() { - tor.clearIsolation(); - }, - - wrappedJSObject: null, -}; - -// Assign factory to global object. -const NSGetFactory = XPCOMUtils.generateNSGetFactory - ? XPCOMUtils.generateNSGetFactory([DomainIsolator]) - : ComponentUtils.generateNSGetFactory([DomainIsolator]);
===================================== toolkit/torbutton/jar.mn ===================================== @@ -43,9 +43,5 @@ torbutton.jar: % component {f36d72c9-9718-4134-b550-e109638331d7} %components/torbutton-logger.js % contract @torproject.org/torbutton-logger;1 {f36d72c9-9718-4134-b550-e109638331d7}
-% component {e33fd6d4-270f-475f-a96f-ff3140279f68} %components/domain-isolator.js -% contract @torproject.org/domain-isolator;1 {e33fd6d4-270f-475f-a96f-ff3140279f68} - % category profile-after-change StartupObserver @torproject.org/startup-observer;1 -% category profile-after-change DomainIsolator @torproject.org/domain-isolator;1 % category profile-after-change DragDropFilter @torproject.org/torbutton-dragDropFilter;1
===================================== toolkit/torbutton/modules/utils.js ===================================== @@ -213,45 +213,6 @@ var unescapeTorString = function(str) { return _torControl._strUnescape(str); };
-var getFPDFromHost = hostname => { - try { - return Services.eTLD.getBaseDomainFromHost(hostname); - } catch (e) { - if ( - e.result == Cr.NS_ERROR_HOST_IS_IP_ADDRESS || - e.result == Cr.NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS - ) { - return hostname; - } - } - return null; -}; - -// Assuming this is called with gBrowser.selectedBrowser -var getDomainForBrowser = browser => { - let fpd = browser.contentPrincipal.originAttributes.firstPartyDomain; - // Bug 31562: For neterror or certerror, get the original URL from - // browser.currentURI and use it to calculate the firstPartyDomain. - let knownErrors = [ - "about:neterror", - "about:certerror", - "about:httpsonlyerror", - ]; - let documentURI = browser.documentURI; - if ( - documentURI && - documentURI.schemeIs("about") && - knownErrors.some(x => documentURI.spec.startsWith(x)) - ) { - let knownSchemes = ["http", "https", "ftp"]; - let currentURI = browser.currentURI; - if (currentURI && knownSchemes.some(x => currentURI.schemeIs(x))) { - fpd = getFPDFromHost(currentURI.host) || fpd; - } - } - return fpd; -}; - var m_tb_torlog = Cc["@torproject.org/torbutton-logger;1"].getService( Ci.nsISupports ).wrappedJSObject; @@ -310,7 +271,6 @@ let EXPORTED_SYMBOLS = [ "bindPrefAndInit", "getEnv", "getLocale", - "getDomainForBrowser", "getPrefValue", "observe", "showDialog",
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/ff98f1b...
tor-commits@lists.torproject.org