| 
Commits:
2bf330d2
 by Pier Angelo Vendrame   at 2023-06-08T14:56:19+02:00 
 fixup! Bug 40933: Add tor-launcher functionality
Added a newnym function
ed24f026
 by Pier Angelo Vendrame   at 2023-06-08T14:56:20+02:00 
 fixup! Bug 10760: Integrate TorButton to TorBrowser core
Bug 40938: Moving the domain isolator out of torbutton
4e063afe
 by Arthur Edelstein   at 2023-06-08T14:56:20+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
9018b2cd
 by Pier Angelo Vendrame   at 2023-06-08T14:56:20+02:00 
 fixup! Bug 3455: Add DomainIsolator, for isolating circuit by domain.
Refactors to the old JS code.
47c03bfc
 by Pier Angelo Vendrame   at 2023-06-08T14:56:21+02:00 
 fixup! Bug 3455: Add DomainIsolator, for isolating circuit by domain.
Manage NEWNYM here.
d33564dd
 by Pier Angelo Vendrame   at 2023-06-08T14:56:21+02:00 
 fixup! Bug 3455: Add DomainIsolator, for isolating circuit by domain.
Removed the XPCOM definition of the domain isolator.
ae68e4b5
 by Pier Angelo Vendrame   at 2023-06-08T14:56:22+02:00 
 fixup! Bug 10760: Integrate TorButton to TorBrowser core
Extract the new identity button from torbutton
322c6af1
 by Pier Angelo Vendrame   at 2023-06-08T14:56:22+02:00 
 fixup! Bug 3455: Add DomainIsolator, for isolating circuit by domain.
Actually added the new circuit button.
1ba65aa4
 by Pier Angelo Vendrame   at 2023-06-08T14:56:22+02:00 
 fixup! Bug 41600: Add a tor circuit display panel.
Use the new domain isolator interface.
beec825e
 by Pier Angelo Vendrame   at 2023-06-08T14:56:23+02:00 
 fixup! Bug 40209: Implement Basic Crypto Safety
Use the new domain isolator interface
e3478fc9
 by Pier Angelo Vendrame   at 2023-06-08T14:56:23+02:00 
 fixup! Bug 10760: Integrate TorButton to TorBrowser core
Remove string changes from Torbutton.
We will add them back in the TorStrings commit.
cf8bb1df
 by Pier Angelo Vendrame   at 2023-06-08T14:56:24+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:
Changes:
browser/actors/CryptoSafetyParent.jsm
 
| ... | ... | @@ -12,6 +12,12 @@ const { XPCOMUtils } = ChromeUtils.import( |  
| 12 | 12 |    "resource://gre/modules/XPCOMUtils.jsm"
 |  
| 13 | 13 |  );
 |  
| 14 | 14 |  
 |  
|  | 15 | +ChromeUtils.defineModuleGetter(
 |  
|  | 16 | +  this,
 |  
|  | 17 | +  "TorDomainIsolator",
 |  
|  | 18 | +  "resource://gre/modules/TorDomainIsolator.jsm"
 |  
|  | 19 | +);
 |  
|  | 20 | +
 |  
| 15 | 21 |  XPCOMUtils.defineLazyGetter(this, "cryptoSafetyBundle", () => {
 |  
| 16 | 22 |    return Services.strings.createBundle(
 |  
| 17 | 23 |      "chrome://browser/locale/cryptoSafetyPrompt.properties"
 |  
| ... | ... | @@ -75,7 +81,11 @@ class CryptoSafetyParent extends JSWindowActorParent { |  
| 75 | 81 |      );
 |  
| 76 | 82 |  
 |  
| 77 | 83 |      if (buttonPressed === 0) {
 |  
| 78 |  | -      this.browsingContext.topChromeWindow.torbutton_new_circuit();
 |  
|  | 84 | +      const { browsingContext } = this.manager;
 |  
|  | 85 | +      const browser = browsingContext.embedderElement;
 |  
|  | 86 | +      if (browser) {
 |  
|  | 87 | +        TorDomainIsolator.newCircuitForBrowser(browser.ownerGlobal.gBrowser);
 |  
|  | 88 | +      }
 |  
| 79 | 89 |      }
 |  
| 80 | 90 |    }
 |  
| 81 | 91 |  } |  browser/base/content/appmenu-viewcache.inc.xhtml
 
 
| ... | ... | @@ -63,9 +63,9 @@ |  
| 63 | 63 |                       key="new-identity-key"/>
 |  
| 64 | 64 |        <toolbarbutton id="appMenuNewCircuit"
 |  
| 65 | 65 |                       class="subviewbutton"
 |  
| 66 |  | -                     key="torbutton-new-circuit-key"
 |  
|  | 66 | +                     key="new-circuit-key"
 |  
| 67 | 67 |                       label="&torbutton.context_menu.new_circuit_sentence_case;"
 |  
| 68 |  | -                     _oncommand_="torbutton_new_circuit();"/>
 |  
|  | 68 | +                     _oncommand_="TorDomainIsolator.newCircuitForBrowser(gBrowser);"/>
 |  
| 69 | 69 |        <toolbarseparator/>
 |  
| 70 | 70 |        <toolbarbutton id="appMenu-bookmarks-button"
 |  
| 71 | 71 |                       class="subviewbutton subviewbutton-nav"
 |  browser/base/content/browser-menubar.inc
 
 
| ... | ... | @@ -33,9 +33,9 @@ |  
| 33 | 33 |                            key="new-identity-key"/>
 |  
| 34 | 34 |                  <menuitem id="menu_newCircuit"
 |  
| 35 | 35 |                            accesskey="&torbutton.context_menu.new_circuit_key;"
 |  
| 36 |  | -                          key="torbutton-new-circuit-key"
 |  
|  | 36 | +                          key="new-circuit-key"
 |  
| 37 | 37 |                            label="&torbutton.context_menu.new_circuit;"
 |  
| 38 |  | -                          oncommand="torbutton_new_circuit();"/>
 |  
|  | 38 | +                          oncommand="TorDomainIsolator.newCircuitForBrowser(gBrowser);"/>
 |  
| 39 | 39 |                  <menuseparator/>
 |  
| 40 | 40 |                  <menuitem id="menu_openLocation"
 |  
| 41 | 41 |                            hidden="true"
 |  browser/base/content/browser-sets.inc
 
 
| ... | ... | @@ -389,5 +389,5 @@ |  
| 389 | 389 |           internal="true"/>
 |  
| 390 | 390 |  #endif
 |  
| 391 | 391 |      <key id="new-identity-key" modifiers="accel shift" key="U" oncommand="NewIdentityButton.onCommand(event)"/>
 |  
| 392 |  | -    <key id="torbutton-new-circuit-key" modifiers="accel shift" key="L" oncommand="torbutton_new_circuit()"/>
 |  
|  | 392 | +    <key id="new-circuit-key" modifiers="accel shift" key="L" oncommand="TorDomainIsolator.newCircuitForBrowser(gBrowser)"/>
 |  
| 393 | 393 |    </keyset> |  browser/base/content/browser.js
 
 
| ... | ... | @@ -82,6 +82,7 @@ XPCOMUtils.defineLazyModuleGetters(this, { |  
| 82 | 82 |    TorConnect: "resource:///modules/TorConnect.jsm",
 |  
| 83 | 83 |    TorConnectState: "resource:///modules/TorConnect.jsm",
 |  
| 84 | 84 |    TorConnectTopics: "resource:///modules/TorConnect.jsm",
 |  
|  | 85 | +  TorDomainIsolator: "resource://gre/modules/TorDomainIsolator.jsm",
 |  
| 85 | 86 |    Translation: "resource:///modules/translation/TranslationParent.jsm",
 |  
| 86 | 87 |    UITour: "resource:///modules/UITour.jsm",
 |  
| 87 | 88 |    UpdateUtils: "resource://gre/modules/UpdateUtils.jsm",
 |  browser/base/content/navigator-toolbox.inc.xhtml
 
 
| ... | ... | @@ -569,7 +569,7 @@ |  
| 569 | 569 |  
 |  
| 570 | 570 |      <toolbarbutton id="new-circuit-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
 |  
| 571 | 571 |                     label="&torbutton.context_menu.new_circuit;"
 |  
| 572 |  | -                   _oncommand_="torbutton_new_circuit();"
 |  
|  | 572 | +                   _oncommand_="TorDomainIsolator.newCircuitForBrowser(gBrowser);"
 |  
| 573 | 573 |                     tooltiptext="&torbutton.context_menu.new_circuit;"/>
 |  
| 574 | 574 |  
 |  
| 575 | 575 |      <toolbarbutton id="fullscreen-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
 |  browser/components/torcircuit/content/torCircuitPanel.js
 
 
| ... | ... | @@ -193,7 +193,7 @@ var gTorCircuitPanel = { |  
| 193 | 193 |      document
 |  
| 194 | 194 |        .getElementById("tor-circuit-new-circuit")
 |  
| 195 | 195 |        .addEventListener("command", () => {
 |  
| 196 |  | -        torbutton_new_circuit();
 |  
|  | 196 | +        TorDomainIsolator.newCircuitForBrowser(gBrowser);
 |  
| 197 | 197 |          // And hide.
 |  
| 198 | 198 |          // NOTE: focus should return to the toolbar button, which we expect to
 |  
| 199 | 199 |          // remain visible during reload.
 |  
| ... | ... | @@ -415,20 +415,14 @@ var gTorCircuitPanel = { |  
| 415 | 415 |     */
 |  
| 416 | 416 |    _updateCurrentBrowser(matchingCredentials = null) {
 |  
| 417 | 417 |      const browser = gBrowser.selectedBrowser;
 |  
| 418 |  | -    const { getDomainForBrowser } = ChromeUtils.import(
 |  
| 419 |  | -      "resource://torbutton/modules/utils.js"
 |  
| 420 |  | -    );
 |  
| 421 |  | -    const domain = getDomainForBrowser(browser);
 |  
|  | 418 | +    const domain = TorDomainIsolator.getDomainForBrowser(browser);
 |  
| 422 | 419 |      // We choose the currentURI, which matches what is shown in the URL bar and
 |  
| 423 | 420 |      // will match up with the domain.
 |  
| 424 | 421 |      // In contrast, documentURI corresponds to the shown page. E.g. it could
 |  
| 425 | 422 |      // point to "about:certerror".
 |  
| 426 | 423 |      const scheme = browser.currentURI?.scheme;
 |  
| 427 | 424 |  
 |  
| 428 |  | -    const domainIsolator = Cc["@torproject.org/domain-isolator;1"].getService(
 |  
| 429 |  | -      Ci.nsISupports
 |  
| 430 |  | -    ).wrappedJSObject;
 |  
| 431 |  | -    let credentials = domainIsolator.getSocksProxyCredentials(
 |  
|  | 425 | +    let credentials = TorDomainIsolator.getSocksProxyCredentials(
 |  
| 432 | 426 |        domain,
 |  
| 433 | 427 |        browser.contentPrincipal.originAttributes.userContextId
 |  
| 434 | 428 |      );
 |  toolkit/components/tor-launcher/TorDomainIsolator.jsm
 
 
|  | 1 | +// A component for Tor Browser that puts requests from different
 |  
|  | 2 | +// first party domains on separate Tor circuits.
 |  
|  | 3 | +
 |  
|  | 4 | +var EXPORTED_SYMBOLS = ["TorDomainIsolator"];
 |  
|  | 5 | +
 |  
|  | 6 | +const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
 |  
|  | 7 | +const { XPCOMUtils } = ChromeUtils.import(
 |  
|  | 8 | +  "resource://gre/modules/XPCOMUtils.jsm"
 |  
|  | 9 | +);
 |  
|  | 10 | +const { ConsoleAPI } = ChromeUtils.import("resource://gre/modules/Console.jsm");
 |  
|  | 11 | +
 |  
|  | 12 | +Cu.importGlobalProperties(["crypto"]);
 |  
|  | 13 | +
 |  
|  | 14 | +XPCOMUtils.defineLazyServiceGetters(this, {
 |  
|  | 15 | +  ProtocolProxyService: [
 |  
|  | 16 | +    "@mozilla.org/network/protocol-proxy-service;1",
 |  
|  | 17 | +    "nsIProtocolProxyService",
 |  
|  | 18 | +  ],
 |  
|  | 19 | +});
 |  
|  | 20 | +
 |  
|  | 21 | +ChromeUtils.defineModuleGetter(
 |  
|  | 22 | +  this,
 |  
|  | 23 | +  "TorProtocolService",
 |  
|  | 24 | +  "resource://gre/modules/TorProtocolService.jsm"
 |  
|  | 25 | +);
 |  
|  | 26 | +
 |  
|  | 27 | +const logger = new ConsoleAPI({
 |  
|  | 28 | +  prefix: "TorDomainIsolator",
 |  
|  | 29 | +  maxLogLevel: "warn",
 |  
|  | 30 | +  maxLogLevelPref: "browser.tordomainisolator.loglevel",
 |  
|  | 31 | +});
 |  
|  | 32 | +
 |  
|  | 33 | +// The string to use instead of the domain when it is not known.
 |  
|  | 34 | +const CATCHALL_DOMAIN = "--unknown--";
 |  
|  | 35 | +
 |  
|  | 36 | +// The preference to observe, to know whether isolation should be enabled or
 |  
|  | 37 | +// disabled.
 |  
|  | 38 | +const NON_TOR_PROXY_PREF = "extensions.torbutton.use_nontor_proxy";
 |  
|  | 39 | +
 |  
|  | 40 | +// The topic of new identity, to observe to cleanup all the nonces.
 |  
|  | 41 | +const NEW_IDENTITY_TOPIC = "new-identity-requested";
 |  
|  | 42 | +
 |  
|  | 43 | +class TorDomainIsolatorImpl {
 |  
|  | 44 | +  // A mutable map that records what nonce we are using for each domain.
 |  
|  | 45 | +  #noncesForDomains = new Map();
 |  
|  | 46 | +
 |  
|  | 47 | +  // A mutable map that records what nonce we are using for each tab container.
 |  
|  | 48 | +  #noncesForUserContextId = new Map();
 |  
|  | 49 | +
 |  
|  | 50 | +  // A bool that controls if we use SOCKS auth for isolation or not.
 |  
|  | 51 | +  #isolationEnabled = true;
 |  
|  | 52 | +
 |  
|  | 53 | +  // Specifies when the current catch-all circuit was first used
 |  
|  | 54 | +  #catchallDirtySince = Date.now();
 |  
|  | 55 | +
 |  
|  | 56 | +  /**
 |  
|  | 57 | +   * Initialize the domain isolator.
 |  
|  | 58 | +   * This function will setup the proxy filter that injects the credentials and
 |  
|  | 59 | +   * register some observers.
 |  
|  | 60 | +   */
 |  
|  | 61 | +  init() {
 |  
|  | 62 | +    logger.info("Setup circuit isolation by domain and user context");
 |  
|  | 63 | +
 |  
|  | 64 | +    if (Services.prefs.getBoolPref(NON_TOR_PROXY_PREF)) {
 |  
|  | 65 | +      this.#isolationEnabled = false;
 |  
|  | 66 | +    }
 |  
|  | 67 | +    this.#setupProxyFilter();
 |  
|  | 68 | +
 |  
|  | 69 | +    Services.prefs.addObserver(NON_TOR_PROXY_PREF, this);
 |  
|  | 70 | +    Services.obs.addObserver(this, NEW_IDENTITY_TOPIC);
 |  
|  | 71 | +  }
 |  
|  | 72 | +
 |  
|  | 73 | +  /**
 |  
|  | 74 | +   * Removes the observers added in the initialization.
 |  
|  | 75 | +   */
 |  
|  | 76 | +  uninit() {
 |  
|  | 77 | +    Services.prefs.removeObserver(NON_TOR_PROXY_PREF, this);
 |  
|  | 78 | +    Services.obs.removeObserver(this, NEW_IDENTITY_TOPIC);
 |  
|  | 79 | +  }
 |  
|  | 80 | +
 |  
|  | 81 | +  enable() {
 |  
|  | 82 | +    logger.trace("Domain isolation enabled");
 |  
|  | 83 | +    this.#isolationEnabled = true;
 |  
|  | 84 | +  }
 |  
|  | 85 | +
 |  
|  | 86 | +  disable() {
 |  
|  | 87 | +    logger.trace("Domain isolation disabled");
 |  
|  | 88 | +    this.#isolationEnabled = false;
 |  
|  | 89 | +  }
 |  
|  | 90 | +
 |  
|  | 91 | +  /**
 |  
|  | 92 | +   * Return the credentials to use as username and password for the SOCKS proxy,
 |  
|  | 93 | +   * given a certain domain and userContextId. Optionally, create them.
 |  
|  | 94 | +   *
 |  
|  | 95 | +   * @param firstPartyDomain The first party domain associated to the requests
 |  
|  | 96 | +   * @param userContextId The context ID associated to the request
 |  
|  | 97 | +   * @param create Whether to create the nonce, if it is not available
 |  
|  | 98 | +   * @return Either the credential, or null if we do not have them and create is
 |  
|  | 99 | +   * false.
 |  
|  | 100 | +   */
 |  
|  | 101 | +  getSocksProxyCredentials(firstPartyDomain, userContextId, create = false) {
 |  
|  | 102 | +    if (!this.#noncesForDomains.has(firstPartyDomain)) {
 |  
|  | 103 | +      if (!create) {
 |  
|  | 104 | +        return null;
 |  
|  | 105 | +      }
 |  
|  | 106 | +      const nonce = this.#nonce();
 |  
|  | 107 | +      logger.info(`New nonce for first party ${firstPartyDomain}: ${nonce}`);
 |  
|  | 108 | +      this.#noncesForDomains.set(firstPartyDomain, nonce);
 |  
|  | 109 | +    }
 |  
|  | 110 | +    if (!this.#noncesForUserContextId.has(userContextId)) {
 |  
|  | 111 | +      if (!create) {
 |  
|  | 112 | +        return null;
 |  
|  | 113 | +      }
 |  
|  | 114 | +      const nonce = this.#nonce();
 |  
|  | 115 | +      logger.info(`New nonce for userContextId ${userContextId}: ${nonce}`);
 |  
|  | 116 | +      this.#noncesForUserContextId.set(userContextId, nonce);
 |  
|  | 117 | +    }
 |  
|  | 118 | +    return {
 |  
|  | 119 | +      username: this.#makeUsername(firstPartyDomain, userContextId),
 |  
|  | 120 | +      password:
 |  
|  | 121 | +        this.#noncesForDomains.get(firstPartyDomain) +
 |  
|  | 122 | +        this.#noncesForUserContextId.get(userContextId),
 |  
|  | 123 | +    };
 |  
|  | 124 | +  }
 |  
|  | 125 | +
 |  
|  | 126 | +  /**
 |  
|  | 127 | +   * Create a new nonce for the FP domain of the selected browser and reload the
 |  
|  | 128 | +   * tab with a new circuit.
 |  
|  | 129 | +   *
 |  
|  | 130 | +   * @param browser Should be the gBrowser from the context of the caller
 |  
|  | 131 | +   */
 |  
|  | 132 | +  newCircuitForBrowser(browser) {
 |  
|  | 133 | +    const firstPartyDomain = getDomainForBrowser(browser.selectedBrowser);
 |  
|  | 134 | +    this.#newCircuitForDomain(firstPartyDomain);
 |  
|  | 135 | +    // TODO: How to properly handle the user context? Should we use
 |  
|  | 136 | +    // (domain, userContextId) pairs, instead of concatenating nonces?
 |  
|  | 137 | +    browser.reloadWithFlags(Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE);
 |  
|  | 138 | +  }
 |  
|  | 139 | +
 |  
|  | 140 | +  /**
 |  
|  | 141 | +   * Clear the isolation state cache, forcing new circuits to be used for all
 |  
|  | 142 | +   * subsequent requests.
 |  
|  | 143 | +   */
 |  
|  | 144 | +  clearIsolation() {
 |  
|  | 145 | +    logger.trace("Clearing isolation nonces.");
 |  
|  | 146 | +
 |  
|  | 147 | +    // Per-domain and per contextId nonces are stored in maps, so simply clear
 |  
|  | 148 | +    // them.
 |  
|  | 149 | +    this.#noncesForDomains.clear();
 |  
|  | 150 | +    this.#noncesForUserContextId.clear();
 |  
|  | 151 | +
 |  
|  | 152 | +    // Force a rotation on the next catch-all circuit use by setting the
 |  
|  | 153 | +    // creation time to the epoch.
 |  
|  | 154 | +    this.#catchallDirtySince = 0;
 |  
|  | 155 | +  }
 |  
|  | 156 | +
 |  
|  | 157 | +  async observe(subject, topic, data) {
 |  
|  | 158 | +    if (topic === "nsPref:changed" && data ="" NON_TOR_PROXY_PREF) {
 |  
|  | 159 | +      if (Services.prefs.getBoolPref(NON_TOR_PROXY_PREF)) {
 |  
|  | 160 | +        this.disable();
 |  
|  | 161 | +      } else {
 |  
|  | 162 | +        this.enable();
 |  
|  | 163 | +      }
 |  
|  | 164 | +    } else if (topic === NEW_IDENTITY_TOPIC) {
 |  
|  | 165 | +      logger.info(
 |  
|  | 166 | +        "New identity has been requested, clearing isolation tokens."
 |  
|  | 167 | +      );
 |  
|  | 168 | +      this.clearIsolation();
 |  
|  | 169 | +      try {
 |  
|  | 170 | +        await TorProtocolService.newnym();
 |  
|  | 171 | +      } catch (e) {
 |  
|  | 172 | +        logger.error("Could not send the newnym command", e);
 |  
|  | 173 | +        // TODO: What UX to use here? See tor-browser#41708
 |  
|  | 174 | +      }
 |  
|  | 175 | +    }
 |  
|  | 176 | +  }
 |  
|  | 177 | +
 |  
|  | 178 | +  /**
 |  
|  | 179 | +   * Setup a filter that for every HTTPChannel, replaces the default SOCKS proxy
 |  
|  | 180 | +   * with one that authenticates to the SOCKS server (the tor client process)
 |  
|  | 181 | +   * with a username (the first party domain and userContextId) and a nonce
 |  
|  | 182 | +   * password.
 |  
|  | 183 | +   * Tor provides a separate circuit for each username+password combination.
 |  
|  | 184 | +   */
 |  
|  | 185 | +  #setupProxyFilter() {
 |  
|  | 186 | +    const filterFunction = (aChannel, aProxy) => {
 |  
|  | 187 | +      if (!this.#isolationEnabled) {
 |  
|  | 188 | +        return aProxy;
 |  
|  | 189 | +      }
 |  
|  | 190 | +      try {
 |  
|  | 191 | +        const channel = aChannel.QueryInterface(Ci.nsIChannel);
 |  
|  | 192 | +        let firstPartyDomain =
 |  
|  | 193 | +          channel.loadInfo.originAttributes.firstPartyDomain;
 |  
|  | 194 | +        const userContextId = channel.loadInfo.originAttributes.userContextId;
 |  
|  | 195 | +        if (firstPartyDomain === "") {
 |  
|  | 196 | +          firstPartyDomain = CATCHALL_DOMAIN;
 |  
|  | 197 | +          if (Date.now() - this.#catchallDirtySince > 1000 * 10 * 60) {
 |  
|  | 198 | +            logger.info(
 |  
|  | 199 | +              "tor catchall circuit has been dirty for over 10 minutes. Rotating."
 |  
|  | 200 | +            );
 |  
|  | 201 | +            this.#newCircuitForDomain(CATCHALL_DOMAIN);
 |  
|  | 202 | +            this.#catchallDirtySince = Date.now();
 |  
|  | 203 | +          }
 |  
|  | 204 | +        }
 |  
|  | 205 | +        const replacementProxy = this.#applySocksProxyCredentials(
 |  
|  | 206 | +          aProxy,
 |  
|  | 207 | +          firstPartyDomain,
 |  
|  | 208 | +          userContextId
 |  
|  | 209 | +        );
 |  
|  | 210 | +        logger.debug(
 |  
|  | 211 | +          `Requested ${channel.URI.spec} via ${replacementProxy.username}:${replacementProxy.password}`
 |  
|  | 212 | +        );
 |  
|  | 213 | +        return replacementProxy;
 |  
|  | 214 | +      } catch (e) {
 |  
|  | 215 | +        logger.error("Error while setting a new proxy", e);
 |  
|  | 216 | +        return null;
 |  
|  | 217 | +      }
 |  
|  | 218 | +    };
 |  
|  | 219 | +
 |  
|  | 220 | +    ProtocolProxyService.registerChannelFilter(
 |  
|  | 221 | +      {
 |  
|  | 222 | +        applyFilter(aChannel, aProxy, aCallback) {
 |  
|  | 223 | +          aCallback.onProxyFilterResult(filterFunction(aChannel, aProxy));
 |  
|  | 224 | +        },
 |  
|  | 225 | +      },
 |  
|  | 226 | +      0
 |  
|  | 227 | +    );
 |  
|  | 228 | +  }
 |  
|  | 229 | +
 |  
|  | 230 | +  /**
 |  
|  | 231 | +   * Takes a proxyInfo object (originalProxy) and returns a new proxyInfo
 |  
|  | 232 | +   * object with the same properties, except the username is set to the
 |  
|  | 233 | +   * the domain and userContextId, and the password is a nonce.
 |  
|  | 234 | +   */
 |  
|  | 235 | +  #applySocksProxyCredentials(originalProxy, domain, userContextId) {
 |  
|  | 236 | +    const proxy = originalProxy.QueryInterface(Ci.nsIProxyInfo);
 |  
|  | 237 | +    const { username, password } = this.getSocksProxyCredentials(
 |  
|  | 238 | +      domain,
 |  
|  | 239 | +      userContextId,
 |  
|  | 240 | +      true
 |  
|  | 241 | +    );
 |  
|  | 242 | +    return ProtocolProxyService.newProxyInfoWithAuth(
 |  
|  | 243 | +      "socks",
 |  
|  | 244 | +      proxy.host,
 |  
|  | 245 | +      proxy.port,
 |  
|  | 246 | +      username,
 |  
|  | 247 | +      password,
 |  
|  | 248 | +      "", // aProxyAuthorizationHeader
 |  
|  | 249 | +      "", // aConnectionIsolationKey
 |  
|  | 250 | +      proxy.flags,
 |  
|  | 251 | +      proxy.failoverTimeout,
 |  
|  | 252 | +      proxy.failoverProxy
 |  
|  | 253 | +    );
 |  
|  | 254 | +  }
 |  
|  | 255 | +
 |  
|  | 256 | +  /**
 |  
|  | 257 | +   * Combine the needed data into a username for the proxy.
 |  
|  | 258 | +   */
 |  
|  | 259 | +  #makeUsername(domain, userContextId) {
 |  
|  | 260 | +    if (!domain) {
 |  
|  | 261 | +      domain = CATCHALL_DOMAIN;
 |  
|  | 262 | +    }
 |  
|  | 263 | +    return `${domain}:${userContextId}`;
 |  
|  | 264 | +  }
 |  
|  | 265 | +
 |  
|  | 266 | +  /**
 |  
|  | 267 | +   * Generate a new 128 bit random tag.
 |  
|  | 268 | +   *
 |  
|  | 269 | +   * Strictly speaking both using a cryptographic entropy source and using 128
 |  
|  | 270 | +   * bits of entropy for the tag are likely overkill, as correct behavior only
 |  
|  | 271 | +   * depends on how unlikely it is for there to be a collision.
 |  
|  | 272 | +   */
 |  
|  | 273 | +  #nonce() {
 |  
|  | 274 | +    return Array.from(crypto.getRandomValues(new Uint8Array(16)), byte =>
 |  
|  | 275 | +      byte.toString(16).padStart(2, "0")
 |  
|  | 276 | +    ).join("");
 |  
|  | 277 | +  }
 |  
|  | 278 | +
 |  
|  | 279 | +  /**
 |  
|  | 280 | +   * Re-generate the nonce for a certain domain.
 |  
|  | 281 | +   */
 |  
|  | 282 | +  #newCircuitForDomain(domain) {
 |  
|  | 283 | +    if (!domain) {
 |  
|  | 284 | +      domain = CATCHALL_DOMAIN;
 |  
|  | 285 | +    }
 |  
|  | 286 | +    this.#noncesForDomains.set(domain, this.#nonce());
 |  
|  | 287 | +    logger.info(
 |  
|  | 288 | +      `New domain isolation for ${domain}: ${this.#noncesForDomains.get(
 |  
|  | 289 | +        domain
 |  
|  | 290 | +      )}`
 |  
|  | 291 | +    );
 |  
|  | 292 | +  }
 |  
|  | 293 | +
 |  
|  | 294 | +  /**
 |  
|  | 295 | +   * Re-generate the nonce for a userContextId.
 |  
|  | 296 | +   *
 |  
|  | 297 | +   * Currently, this function is not hooked to anything.
 |  
|  | 298 | +   */
 |  
|  | 299 | +  #newCircuitForUserContextId(userContextId) {
 |  
|  | 300 | +    this.#noncesForUserContextId.set(userContextId, this.#nonce());
 |  
|  | 301 | +    logger.info(
 |  
|  | 302 | +      `New container isolation for ${userContextId}: ${this.#noncesForUserContextId.get(
 |  
|  | 303 | +        userContextId
 |  
|  | 304 | +      )}`
 |  
|  | 305 | +    );
 |  
|  | 306 | +  }
 |  
|  | 307 | +}
 |  
|  | 308 | +
 |  
|  | 309 | +/**
 |  
|  | 310 | + * Get the first party domain for a certain browser.
 |  
|  | 311 | + *
 |  
|  | 312 | + * @param browser The browser to get the FP-domain for.
 |  
|  | 313 | + *
 |  
|  | 314 | + * Please notice that it should be gBrowser.selectedBrowser, because
 |  
|  | 315 | + * browser.documentURI is the actual shown page, and might be an error page.
 |  
|  | 316 | + * In this case, we rely on currentURI, which for gBrowser is an alias of
 |  
|  | 317 | + * gBrowser.selectedBrowser.currentURI.
 |  
|  | 318 | + * See browser/base/content/tabbrowser.js and tor-browser#31562.
 |  
|  | 319 | + */
 |  
|  | 320 | +function getDomainForBrowser(browser) {
 |  
|  | 321 | +  let fpd = browser.contentPrincipal.originAttributes.firstPartyDomain;
 |  
|  | 322 | +
 |  
|  | 323 | +  // Bug 31562: For neterror or certerror, get the original URL from
 |  
|  | 324 | +  // browser.currentURI and use it to calculate the firstPartyDomain.
 |  
|  | 325 | +  const knownErrors = [
 |  
|  | 326 | +    "about:neterror",
 |  
|  | 327 | +    "about:certerror",
 |  
|  | 328 | +    "about:httpsonlyerror",
 |  
|  | 329 | +  ];
 |  
|  | 330 | +  const { documentURI } = browser;
 |  
|  | 331 | +  if (
 |  
|  | 332 | +    documentURI &&
 |  
|  | 333 | +    documentURI.schemeIs("about") &&
 |  
|  | 334 | +    knownErrors.some(x => documentURI.spec.startsWith(x))
 |  
|  | 335 | +  ) {
 |  
|  | 336 | +    const knownSchemes = ["http", "https"];
 |  
|  | 337 | +    const currentURI = browser.currentURI;
 |  
|  | 338 | +    if (currentURI && knownSchemes.some(x => currentURI.schemeIs(x))) {
 |  
|  | 339 | +      try {
 |  
|  | 340 | +        fpd = Services.eTLD.getBaseDomainFromHost(currentURI.host);
 |  
|  | 341 | +      } catch (e) {
 |  
|  | 342 | +        if (
 |  
|  | 343 | +          e.result === Cr.NS_ERROR_HOST_IS_IP_ADDRESS ||
 |  
|  | 344 | +          e.result === Cr.NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS
 |  
|  | 345 | +        ) {
 |  
|  | 346 | +          fpd = currentURI.host;
 |  
|  | 347 | +        } else {
 |  
|  | 348 | +          logger.error(
 |  
|  | 349 | +            `Failed to get first party domain for host ${currentURI.host}`,
 |  
|  | 350 | +            e
 |  
|  | 351 | +          );
 |  
|  | 352 | +        }
 |  
|  | 353 | +      }
 |  
|  | 354 | +    }
 |  
|  | 355 | +  }
 |  
|  | 356 | +
 |  
|  | 357 | +  return fpd;
 |  
|  | 358 | +}
 |  
|  | 359 | +
 |  
|  | 360 | +const TorDomainIsolator = new TorDomainIsolatorImpl();
 |  
|  | 361 | +// Reduce global vars pollution
 |  
|  | 362 | +TorDomainIsolator.getDomainForBrowser = getDomainForBrowser; |  toolkit/components/tor-launcher/TorProtocolService.jsm
 
 
| ... | ... | @@ -4,6 +4,7 @@ |  
| 4 | 4 |  
 |  
| 5 | 5 |  var EXPORTED_SYMBOLS = ["TorProtocolService"];
 |  
| 6 | 6 |  
 |  
|  | 7 | +const { ConsoleAPI } = ChromeUtils.import("resource://gre/modules/Console.jsm");
 |  
| 7 | 8 |  const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
 |  
| 8 | 9 |  const { setTimeout } = ChromeUtils.import("resource://gre/modules/Timer.jsm");
 |  
| 9 | 10 |  ChromeUtils.defineModuleGetter(
 |  
| ... | ... | @@ -11,9 +12,6 @@ ChromeUtils.defineModuleGetter( |  
| 11 | 12 |    "FileUtils",
 |  
| 12 | 13 |    "resource://gre/modules/FileUtils.jsm"
 |  
| 13 | 14 |  );
 |  
| 14 |  | -const { XPCOMUtils } = ChromeUtils.import(
 |  
| 15 |  | -  "resource://gre/modules/XPCOMUtils.jsm"
 |  
| 16 |  | -);
 |  
| 17 | 15 |  
 |  
| 18 | 16 |  Cu.importGlobalProperties(["crypto"]);
 |  
| 19 | 17 |  
 |  
| ... | ... | @@ -45,18 +43,9 @@ const TorTopics = Object.freeze({ |  
| 45 | 43 |    ProcessRestarted: "TorProcessRestarted",
 |  
| 46 | 44 |  });
 |  
| 47 | 45 |  
 |  
| 48 |  | -// Logger adapted from CustomizableUI.jsm
 |  
| 49 |  | -XPCOMUtils.defineLazyGetter(this, "logger", () => {
 |  
| 50 |  | -  const { ConsoleAPI } = ChromeUtils.import(
 |  
| 51 |  | -    "resource://gre/modules/Console.jsm"
 |  
| 52 |  | -  );
 |  
| 53 |  | -  // TODO: Use a preference to set the log level.
 |  
| 54 |  | -  const consoleOptions = {
 |  
| 55 |  | -    // maxLogLevel: "warn",
 |  
| 56 |  | -    maxLogLevel: "all",
 |  
| 57 |  | -    prefix: "TorProtocolService",
 |  
| 58 |  | -  };
 |  
| 59 |  | -  return new ConsoleAPI(consoleOptions);
 |  
|  | 46 | +const logger = new ConsoleAPI({
 |  
|  | 47 | +  maxLogLevel: "warn",
 |  
|  | 48 | +  prefix: "TorProtocolService",
 |  
| 60 | 49 |  });
 |  
| 61 | 50 |  
 |  
| 62 | 51 |  // Manage the connection to tor's control port, to update its settings and query
 |  
| ... | ... | @@ -194,6 +183,10 @@ const TorProtocolService = { |  
| 194 | 183 |      TorMonitorService.retrieveBootstrapStatus();
 |  
| 195 | 184 |    },
 |  
| 196 | 185 |  
 |  
|  | 186 | +  async newnym() {
 |  
|  | 187 | +    return this.sendCommand("SIGNAL NEWNYM");
 |  
|  | 188 | +  },
 |  
|  | 189 | +
 |  
| 197 | 190 |    // TODO: transform the following 4 functions in getters. At the moment they
 |  
| 198 | 191 |    // are also used in torbutton.
 |  
| 199 | 192 |  
 |  toolkit/components/tor-launcher/TorStartupService.jsm
 
 
| ... | ... | @@ -33,6 +33,12 @@ ChromeUtils.defineModuleGetter( |  
| 33 | 33 |    "resource:///modules/TorSettings.jsm"
 |  
| 34 | 34 |  );
 |  
| 35 | 35 |  
 |  
|  | 36 | +ChromeUtils.defineModuleGetter(
 |  
|  | 37 | +  this,
 |  
|  | 38 | +  "TorDomainIsolator",
 |  
|  | 39 | +  "resource://gre/modules/TorDomainIsolator.jsm"
 |  
|  | 40 | +);
 |  
|  | 41 | +
 |  
| 36 | 42 |  /* Browser observer topis */
 |  
| 37 | 43 |  const BrowserTopics = Object.freeze({
 |  
| 38 | 44 |    ProfileAfterChange: "profile-after-change",
 |  
| ... | ... | @@ -67,12 +73,16 @@ class TorStartupService { |  
| 67 | 73 |      TorSettings.init();
 |  
| 68 | 74 |      TorConnect.init();
 |  
| 69 | 75 |  
 |  
|  | 76 | +    TorDomainIsolator.init();
 |  
|  | 77 | +
 |  
| 70 | 78 |      gInited = true;
 |  
| 71 | 79 |    }
 |  
| 72 | 80 |  
 |  
| 73 | 81 |    _uninit() {
 |  
| 74 | 82 |      Services.obs.removeObserver(this, BrowserTopics.QuitApplicationGranted);
 |  
| 75 | 83 |  
 |  
|  | 84 | +    TorDomainIsolator.uninit();
 |  
|  | 85 | +
 |  
| 76 | 86 |      // Close any helper connection first...
 |  
| 77 | 87 |      TorProtocolService.uninit();
 |  
| 78 | 88 |      // ... and only then closes the event monitor connection, which will cause
 |  toolkit/components/tor-launcher/moz.build
 
 
| 1 | 1 |  EXTRA_JS_MODULES += [
 |  
| 2 | 2 |      "TorBootstrapRequest.jsm",
 |  
|  | 3 | +    "TorDomainIsolator.jsm",
 |  
| 3 | 4 |      "TorLauncherUtil.jsm",
 |  
| 4 | 5 |      "TorMonitorService.jsm",
 |  
| 5 | 6 |      "TorParsers.jsm",
 |  toolkit/torbutton/chrome/content/torbutton.js
 
 
| 1 | 1 |  // window globals
 |  
| 2 | 2 |  var torbutton_init;
 |  
| 3 |  | -var torbutton_new_circuit;
 |  
| 4 | 3 |  
 |  
| 5 | 4 |  (() => {
 |  
| 6 | 5 |    // Bug 1506 P1-P5: This is the main Torbutton overlay file. Much needs to be
 |  
| ... | ... | @@ -16,9 +15,7 @@ var torbutton_new_circuit; |  
| 16 | 15 |  
 |  
| 17 | 16 |    let {
 |  
| 18 | 17 |      unescapeTorString,
 |  
| 19 |  | -    getDomainForBrowser,
 |  
| 20 | 18 |      torbutton_log,
 |  
| 21 |  | -    torbutton_get_property_string,
 |  
| 22 | 19 |    } = ChromeUtils.import("resource://torbutton/modules/utils.js");
 |  
| 23 | 20 |    let { configureControlPortModule, wait_for_controller } = ChromeUtils.import(
 |  
| 24 | 21 |      "resource://torbutton/modules/tor-control-port.js"
 |  
| ... | ... | @@ -46,32 +43,22 @@ var torbutton_new_circuit; |  
| 46 | 43 |    // in a component, not the XUL overlay.
 |  
| 47 | 44 |    var torbutton_unique_pref_observer = {
 |  
| 48 | 45 |      register() {
 |  
| 49 |  | -      this.forced_ua = false;
 |  
| 50 |  | -      m_tb_prefs.addObserver("extensions.torbutton", this);
 |  
| 51 |  | -      m_tb_prefs.addObserver("browser.privatebrowsing.autostart", this);
 |  
| 52 |  | -      m_tb_prefs.addObserver("_javascript_", this);
 |  
|  | 46 | +      Services.prefs.addObserver("browser.privatebrowsing.autostart", this);
 |  
| 53 | 47 |      },
 |  
| 54 | 48 |  
 |  
| 55 | 49 |      unregister() {
 |  
| 56 |  | -      m_tb_prefs.removeObserver("extensions.torbutton", this);
 |  
| 57 |  | -      m_tb_prefs.removeObserver("browser.privatebrowsing.autostart", this);
 |  
| 58 |  | -      m_tb_prefs.removeObserver("_javascript_", this);
 |  
|  | 50 | +      Services.prefs.removeObserver("browser.privatebrowsing.autostart", this);
 |  
| 59 | 51 |      },
 |  
| 60 | 52 |  
 |  
| 61 | 53 |      // topic:   what event occurred
 |  
| 62 | 54 |      // subject: what nsIPrefBranch we're observing
 |  
| 63 | 55 |      // data:    which pref has been changed (relative to subject)
 |  
| 64 | 56 |      observe(subject, topic, data) {
 |  
| 65 |  | -      if (topic !== "nsPref:changed") {
 |  
| 66 |  | -        return;
 |  
| 67 |  | -      }
 |  
| 68 |  | -      switch (data) {
 |  
| 69 |  | -        case "browser.privatebrowsing.autostart":
 |  
| 70 |  | -          torbutton_update_disk_prefs();
 |  
| 71 |  | -          break;
 |  
| 72 |  | -        case "extensions.torbutton.use_nontor_proxy":
 |  
| 73 |  | -          torbutton_use_nontor_proxy();
 |  
| 74 |  | -          break;
 |  
|  | 57 | +      if (
 |  
|  | 58 | +        topic === "nsPref:changed" &&
 |  
|  | 59 | +        data === "browser.privatebrowsing.autostart"
 |  
|  | 60 | +      ) {
 |  
|  | 61 | +        torbutton_update_disk_prefs();
 |  
| 75 | 62 |        }
 |  
| 76 | 63 |      },
 |  
| 77 | 64 |    };
 |  
| ... | ... | @@ -113,62 +100,6 @@ var torbutton_new_circuit; |  
| 113 | 100 |      },
 |  
| 114 | 101 |    };
 |  
| 115 | 102 |  
 |  
| 116 |  | -  var torbutton_new_identity_observers = {
 |  
| 117 |  | -    register() {
 |  
| 118 |  | -      Services.obs.addObserver(this, "new-identity-requested");
 |  
| 119 |  | -    },
 |  
| 120 |  | -
 |  
| 121 |  | -    observe(aSubject, aTopic, aData) {
 |  
| 122 |  | -      if (aTopic !== "new-identity-requested") {
 |  
| 123 |  | -        return;
 |  
| 124 |  | -      }
 |  
| 125 |  | -
 |  
| 126 |  | -      // Clear the domain isolation state.
 |  
| 127 |  | -      torbutton_log(3, "Clearing domain isolator");
 |  
| 128 |  | -      const domainIsolator = Cc["@torproject.org/domain-isolator;1"].getService(
 |  
| 129 |  | -        Ci.nsISupports
 |  
| 130 |  | -      ).wrappedJSObject;
 |  
| 131 |  | -      domainIsolator.clearIsolation();
 |  
| 132 |  | -
 |  
| 133 |  | -      torbutton_log(3, "New Identity: Sending NEWNYM");
 |  
| 134 |  | -      // We only support TBB for newnym.
 |  
| 135 |  | -      if (
 |  
| 136 |  | -        !m_tb_control_pass ||
 |  
| 137 |  | -        (!m_tb_control_ipc_file && !m_tb_control_port)
 |  
| 138 |  | -      ) {
 |  
| 139 |  | -        const warning = torbutton_get_property_string(
 |  
| 140 |  | -          "torbutton.popup.no_newnym"
 |  
| 141 |  | -        );
 |  
| 142 |  | -        torbutton_log(
 |  
| 143 |  | -          5,
 |  
| 144 |  | -          "Torbutton cannot safely newnym. It does not have access to the Tor Control Port."
 |  
| 145 |  | -        );
 |  
| 146 |  | -        window.alert(warning);
 |  
| 147 |  | -      } else {
 |  
| 148 |  | -        const warning = torbutton_get_property_string(
 |  
| 149 |  | -          "torbutton.popup.no_newnym"
 |  
| 150 |  | -        );
 |  
| 151 |  | -        torbutton_send_ctrl_cmd("SIGNAL NEWNYM")
 |  
| 152 |  | -          .then(res => {
 |  
| 153 |  | -            if (!res) {
 |  
| 154 |  | -              torbutton_log(
 |  
| 155 |  | -                5,
 |  
| 156 |  | -                "Torbutton was unable to request a new circuit from Tor"
 |  
| 157 |  | -              );
 |  
| 158 |  | -              window.alert(warning);
 |  
| 159 |  | -            }
 |  
| 160 |  | -          })
 |  
| 161 |  | -          .catch(e => {
 |  
| 162 |  | -            torbutton_log(
 |  
| 163 |  | -              5,
 |  
| 164 |  | -              "Torbutton was unable to request a new circuit from Tor " + e
 |  
| 165 |  | -            );
 |  
| 166 |  | -            window.alert(warning);
 |  
| 167 |  | -          });
 |  
| 168 |  | -      }
 |  
| 169 |  | -    },
 |  
| 170 |  | -  };
 |  
| 171 |  | -
 |  
| 172 | 103 |    // Bug 1506 P2-P4: This code sets some version variables that are irrelevant.
 |  
| 173 | 104 |    // It does read out some important environment variables, though. It is
 |  
| 174 | 105 |    // called once per browser window.. This might belong in a component.
 |  
| ... | ... | @@ -258,8 +189,6 @@ var torbutton_new_circuit; |  
| 258 | 189 |        true
 |  
| 259 | 190 |      );
 |  
| 260 | 191 |  
 |  
| 261 |  | -    torbutton_new_identity_observers.register();
 |  
| 262 |  | -
 |  
| 263 | 192 |      torbutton_log(3, "init completed");
 |  
| 264 | 193 |    };
 |  
| 265 | 194 |  
 |  
| ... | ... | @@ -374,36 +303,6 @@ var torbutton_new_circuit; |  
| 374 | 303 |      return response;
 |  
| 375 | 304 |    }
 |  
| 376 | 305 |  
 |  
| 377 |  | -  // Bug 1506 P4: Needed for New IP Address
 |  
| 378 |  | -  torbutton_new_circuit = function() {
 |  
| 379 |  | -    let firstPartyDomain = getDomainForBrowser(gBrowser.selectedBrowser);
 |  
| 380 |  | -
 |  
| 381 |  | -    let domainIsolator = Cc["@torproject.org/domain-isolator;1"].getService(
 |  
| 382 |  | -      Ci.nsISupports
 |  
| 383 |  | -    ).wrappedJSObject;
 |  
| 384 |  | -
 |  
| 385 |  | -    domainIsolator.newCircuitForDomain(firstPartyDomain);
 |  
| 386 |  | -
 |  
| 387 |  | -    gBrowser.reloadWithFlags(Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE);
 |  
| 388 |  | -  };
 |  
| 389 |  | -
 |  
| 390 |  | -  /* Called when we switch the use_nontor_proxy pref in either direction.
 |  
| 391 |  | -   *
 |  
| 392 |  | -   * Enables/disables domain isolation and then does new identity
 |  
| 393 |  | -   */
 |  
| 394 |  | -  function torbutton_use_nontor_proxy() {
 |  
| 395 |  | -    let domainIsolator = Cc["@torproject.org/domain-isolator;1"].getService(
 |  
| 396 |  | -      Ci.nsISupports
 |  
| 397 |  | -    ).wrappedJSObject;
 |  
| 398 |  | -
 |  
| 399 |  | -    if (m_tb_prefs.getBoolPref("extensions.torbutton.use_nontor_proxy")) {
 |  
| 400 |  | -      // Disable domain isolation
 |  
| 401 |  | -      domainIsolator.disableIsolation();
 |  
| 402 |  | -    } else {
 |  
| 403 |  | -      domainIsolator.enableIsolation();
 |  
| 404 |  | -    }
 |  
| 405 |  | -  }
 |  
| 406 |  | -
 |  
| 407 | 306 |    async function torbutton_do_tor_check() {
 |  
| 408 | 307 |      let checkSvc = Cc["@torproject.org/torbutton-torCheckService;1"].getService(
 |  
| 409 | 308 |        Ci.nsISupports
 |  toolkit/torbutton/components/domain-isolator.js
deleted
 
 
| 1 |  | -// # domain-isolator.js
 |  
| 2 |  | -// A component for TorBrowser that puts requests from different
 |  
| 3 |  | -// first party domains on separate tor circuits.
 |  
| 4 |  | -
 |  
| 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.
 |  
| 8 |  | -
 |  
| 9 |  | -// ### Abbreviations
 |  
| 10 |  | -
 |  
| 11 |  | -const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
 |  
| 12 |  | -const { XPCOMUtils } = ChromeUtils.import(
 |  
| 13 |  | -  "resource://gre/modules/XPCOMUtils.jsm"
 |  
| 14 |  | -);
 |  
| 15 |  | -
 |  
| 16 |  | -XPCOMUtils.defineLazyModuleGetters(this, {
 |  
| 17 |  | -  ComponentUtils: "resource://gre/modules/ComponentUtils.jsm",
 |  
| 18 |  | -});
 |  
| 19 |  | -
 |  
| 20 |  | -// Make the logger available.
 |  
| 21 |  | -let logger = Cc["@torproject.org/torbutton-logger;1"].getService(Ci.nsISupports)
 |  
| 22 |  | -  .wrappedJSObject;
 |  
| 23 |  | -
 |  
| 24 |  | -// Import crypto object (FF 37+).
 |  
| 25 |  | -Cu.importGlobalProperties(["crypto"]);
 |  
| 26 |  | -
 |  
| 27 |  | -// ## mozilla namespace.
 |  
| 28 |  | -// Useful functionality for interacting with Mozilla services.
 |  
| 29 |  | -let mozilla = {};
 |  
| 30 |  | -
 |  
| 31 |  | -// __mozilla.protocolProxyService__.
 |  
| 32 |  | -// Mozilla's protocol proxy service, useful for managing proxy connections made
 |  
| 33 |  | -// by the browser.
 |  
| 34 |  | -mozilla.protocolProxyService = Cc[
 |  
| 35 |  | -  "@mozilla.org/network/protocol-proxy-service;1"
 |  
| 36 |  | -].getService(Ci.nsIProtocolProxyService);
 |  
| 37 |  | -
 |  
| 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) {
 |  
| 45 |  | -  let proxyFilter = {
 |  
| 46 |  | -    applyFilter(aChannel, aProxy, aCallback) {
 |  
| 47 |  | -      aCallback.onProxyFilterResult(filterFunction(aChannel, aProxy));
 |  
| 48 |  | -    },
 |  
| 49 |  | -  };
 |  
| 50 |  | -  mozilla.protocolProxyService.registerChannelFilter(
 |  
| 51 |  | -    proxyFilter,
 |  
| 52 |  | -    positionIndex
 |  
| 53 |  | -  );
 |  
| 54 |  | -};
 |  
| 55 |  | -
 |  
| 56 |  | -// ## tor functionality.
 |  
| 57 |  | -let tor = {};
 |  
| 58 |  | -
 |  
| 59 |  | -// __tor.noncesForDomains__.
 |  
| 60 |  | -// A mutable map that records what nonce we are using for each domain.
 |  
| 61 |  | -tor.noncesForDomains = new Map();
 |  
| 62 |  | -
 |  
| 63 |  | -// __tor.noncesForUserContextId__.
 |  
| 64 |  | -// A mutable map that records what nonce we are using for each tab container.
 |  
| 65 |  | -tor.noncesForUserContextId = new Map();
 |  
| 66 |  | -
 |  
| 67 |  | -// __tor.isolationEabled__.
 |  
| 68 |  | -// A bool that controls if we use SOCKS auth for isolation or not.
 |  
| 69 |  | -tor.isolationEnabled = true;
 |  
| 70 |  | -
 |  
| 71 |  | -// __tor.unknownDirtySince__.
 |  
| 72 |  | -// Specifies when the current catch-all circuit was first used
 |  
| 73 |  | -tor.unknownDirtySince = Date.now();
 |  
| 74 |  | -
 |  
| 75 |  | -tor.passwordForDomainAndUserContextId = function(
 |  
| 76 |  | -  domain,
 |  
| 77 |  | -  userContextId,
 |  
| 78 |  | -  create
 |  
| 79 |  | -) {
 |  
| 80 |  | -  // Check if we already have a nonce. If not, possibly create one for this
 |  
| 81 |  | -  // domain and userContextId.
 |  
| 82 |  | -  if (!tor.noncesForDomains.has(domain)) {
 |  
| 83 |  | -    if (!create) {
 |  
| 84 |  | -      return null;
 |  
| 85 |  | -    }
 |  
| 86 |  | -    tor.noncesForDomains.set(domain, tor.nonce());
 |  
| 87 |  | -  }
 |  
| 88 |  | -  if (!tor.noncesForUserContextId.has(userContextId)) {
 |  
| 89 |  | -    if (!create) {
 |  
| 90 |  | -      return null;
 |  
| 91 |  | -    }
 |  
| 92 |  | -    tor.noncesForUserContextId.set(userContextId, tor.nonce());
 |  
| 93 |  | -  }
 |  
| 94 |  | -  return (
 |  
| 95 |  | -    tor.noncesForDomains.get(domain) +
 |  
| 96 |  | -    tor.noncesForUserContextId.get(userContextId)
 |  
| 97 |  | -  );
 |  
| 98 |  | -};
 |  
| 99 |  | -
 |  
| 100 |  | -tor.usernameForDomainAndUserContextId = function(domain, userContextId) {
 |  
| 101 |  | -  return `${domain}:${userContextId}`;
 |  
| 102 |  | -};
 |  
| 103 |  | -
 |  
| 104 |  | -// __tor.socksProxyCredentials(originalProxy, domain, userContextId)__.
 |  
| 105 |  | -// Takes a proxyInfo object (originalProxy) and returns a new proxyInfo
 |  
| 106 |  | -// object with the same properties, except the username is set to the
 |  
| 107 |  | -// the domain and userContextId, and the password is a nonce.
 |  
| 108 |  | -tor.socksProxyCredentials = function(originalProxy, domain, userContextId) {
 |  
| 109 |  | -  let proxy = originalProxy.QueryInterface(Ci.nsIProxyInfo);
 |  
| 110 |  | -  let proxyUsername = tor.usernameForDomainAndUserContextId(
 |  
| 111 |  | -    domain,
 |  
| 112 |  | -    userContextId
 |  
| 113 |  | -  );
 |  
| 114 |  | -  let proxyPassword = tor.passwordForDomainAndUserContextId(
 |  
| 115 |  | -    domain,
 |  
| 116 |  | -    userContextId,
 |  
| 117 |  | -    true
 |  
| 118 |  | -  );
 |  
| 119 |  | -  return mozilla.protocolProxyService.newProxyInfoWithAuth(
 |  
| 120 |  | -    "socks",
 |  
| 121 |  | -    proxy.host,
 |  
| 122 |  | -    proxy.port,
 |  
| 123 |  | -    proxyUsername,
 |  
| 124 |  | -    proxyPassword,
 |  
| 125 |  | -    "", // aProxyAuthorizationHeader
 |  
| 126 |  | -    "", // aConnectionIsolationKey
 |  
| 127 |  | -    proxy.flags,
 |  
| 128 |  | -    proxy.failoverTimeout,
 |  
| 129 |  | -    proxy.failoverProxy
 |  
| 130 |  | -  );
 |  
| 131 |  | -};
 |  
| 132 |  | -
 |  
| 133 |  | -tor.nonce = function() {
 |  
| 134 |  | -  // Generate a new 128 bit random tag.  Strictly speaking both using a
 |  
| 135 |  | -  // cryptographic entropy source and using 128 bits of entropy for the
 |  
| 136 |  | -  // tag are likely overkill, as correct behavior only depends on how
 |  
| 137 |  | -  // unlikely it is for there to be a collision.
 |  
| 138 |  | -  let tag = new Uint8Array(16);
 |  
| 139 |  | -  crypto.getRandomValues(tag);
 |  
| 140 |  | -
 |  
| 141 |  | -  // Convert the tag to a hex string.
 |  
| 142 |  | -  let tagStr = "";
 |  
| 143 |  | -  for (let i = 0; i < tag.length; i++) {
 |  
| 144 |  | -    tagStr += (tag[i] >>> 4).toString(16);
 |  
| 145 |  | -    tagStr += (tag[i] & 0x0f).toString(16);
 |  
| 146 |  | -  }
 |  
| 147 |  | -
 |  
| 148 |  | -  return tagStr;
 |  
| 149 |  | -};
 |  
| 150 |  | -
 |  
| 151 |  | -tor.newCircuitForDomain = function(domain) {
 |  
| 152 |  | -  // Re-generate the nonce for the domain.
 |  
| 153 |  | -  if (domain === "") {
 |  
| 154 |  | -    domain = "--unknown--";
 |  
| 155 |  | -  }
 |  
| 156 |  | -  tor.noncesForDomains.set(domain, tor.nonce());
 |  
| 157 |  | -  logger.eclog(
 |  
| 158 |  | -    3,
 |  
| 159 |  | -    `New domain isolation for ${domain}: ${tor.noncesForDomains.get(domain)}`
 |  
| 160 |  | -  );
 |  
| 161 |  | -};
 |  
| 162 |  | -
 |  
| 163 |  | -tor.newCircuitForUserContextId = function(userContextId) {
 |  
| 164 |  | -  // Re-generate the nonce for the context.
 |  
| 165 |  | -  tor.noncesForUserContextId.set(userContextId, tor.nonce());
 |  
| 166 |  | -  logger.eclog(
 |  
| 167 |  | -    3,
 |  
| 168 |  | -    `New container isolation for ${userContextId}: ${tor.noncesForUserContextId.get(
 |  
| 169 |  | -      userContextId
 |  
| 170 |  | -    )}`
 |  
| 171 |  | -  );
 |  
| 172 |  | -};
 |  
| 173 |  | -
 |  
| 174 |  | -// __tor.clearIsolation()_.
 |  
| 175 |  | -// Clear the isolation state cache, forcing new circuits to be used for all
 |  
| 176 |  | -// subsequent requests.
 |  
| 177 |  | -tor.clearIsolation = function() {
 |  
| 178 |  | -  // Per-domain and per contextId nonces are stored in maps, so simply clear them.
 |  
| 179 |  | -  tor.noncesForDomains.clear();
 |  
| 180 |  | -  tor.noncesForUserContextId.clear();
 |  
| 181 |  | -
 |  
| 182 |  | -  // Force a rotation on the next catch-all circuit use by setting the creation
 |  
| 183 |  | -  // time to the epoch.
 |  
| 184 |  | -  tor.unknownDirtySince = 0;
 |  
| 185 |  | -};
 |  
| 186 |  | -
 |  
| 187 |  | -// __tor.isolateCircuitsByDomain()__.
 |  
| 188 |  | -// For every HTTPChannel, replaces the default SOCKS proxy with one that authenticates
 |  
| 189 |  | -// to the SOCKS server (the tor client process) with a username (the first party domain
 |  
| 190 |  | -// and userContextId) and a nonce password. Tor provides a separate circuit for each
 |  
| 191 |  | -// username+password combination.
 |  
| 192 |  | -tor.isolateCircuitsByDomain = function() {
 |  
| 193 |  | -  mozilla.registerProxyChannelFilter(function(aChannel, aProxy) {
 |  
| 194 |  | -    if (!tor.isolationEnabled) {
 |  
| 195 |  | -      return aProxy;
 |  
| 196 |  | -    }
 |  
| 197 |  | -    try {
 |  
| 198 |  | -      let channel = aChannel.QueryInterface(Ci.nsIChannel),
 |  
| 199 |  | -        firstPartyDomain = channel.loadInfo.originAttributes.firstPartyDomain,
 |  
| 200 |  | -        userContextId = channel.loadInfo.originAttributes.userContextId;
 |  
| 201 |  | -      if (firstPartyDomain === "") {
 |  
| 202 |  | -        firstPartyDomain = "--unknown--";
 |  
| 203 |  | -        if (Date.now() - tor.unknownDirtySince > 1000 * 10 * 60) {
 |  
| 204 |  | -          logger.eclog(
 |  
| 205 |  | -            3,
 |  
| 206 |  | -            "tor catchall circuit has been dirty for over 10 minutes. Rotating."
 |  
| 207 |  | -          );
 |  
| 208 |  | -          tor.newCircuitForDomain("--unknown--");
 |  
| 209 |  | -          tor.unknownDirtySince = Date.now();
 |  
| 210 |  | -        }
 |  
| 211 |  | -      }
 |  
| 212 |  | -      let replacementProxy = tor.socksProxyCredentials(
 |  
| 213 |  | -        aProxy,
 |  
| 214 |  | -        firstPartyDomain,
 |  
| 215 |  | -        userContextId
 |  
| 216 |  | -      );
 |  
| 217 |  | -      logger.eclog(
 |  
| 218 |  | -        3,
 |  
| 219 |  | -        `tor SOCKS: ${channel.URI.spec} via
 |  
| 220 |  | -                       ${replacementProxy.username}:${replacementProxy.password}`
 |  
| 221 |  | -      );
 |  
| 222 |  | -      return replacementProxy;
 |  
| 223 |  | -    } catch (e) {
 |  
| 224 |  | -      logger.eclog(4, `tor domain isolator error: ${e.message}`);
 |  
| 225 |  | -      return null;
 |  
| 226 |  | -    }
 |  
| 227 |  | -  }, 0);
 |  
| 228 |  | -};
 |  
| 229 |  | -
 |  
| 230 |  | -// ## XPCOM component construction.
 |  
| 231 |  | -// Module specific constants
 |  
| 232 |  | -const kMODULE_NAME = "TorBrowser Domain Isolator";
 |  
| 233 |  | -const kMODULE_CONTRACTID = "@torproject.org/domain-isolator;1";
 |  
| 234 |  | -const kMODULE_CID = Components.ID("e33fd6d4-270f-475f-a96f-ff3140279f68");
 |  
| 235 |  | -
 |  
| 236 |  | -// DomainIsolator object.
 |  
| 237 |  | -function DomainIsolator() {
 |  
| 238 |  | -  this.wrappedJSObject = this;
 |  
| 239 |  | -}
 |  
| 240 |  | -
 |  
| 241 |  | -// Firefox component requirements
 |  
| 242 |  | -DomainIsolator.prototype = {
 |  
| 243 |  | -  QueryInterface: ChromeUtils.generateQI([Ci.nsIObserver]),
 |  
| 244 |  | -  classDescription: kMODULE_NAME,
 |  
| 245 |  | -  classID: kMODULE_CID,
 |  
| 246 |  | -  contractID: kMODULE_CONTRACTID,
 |  
| 247 |  | -  observe(subject, topic, data) {
 |  
| 248 |  | -    if (topic === "profile-after-change") {
 |  
| 249 |  | -      logger.eclog(3, "domain isolator: set up isolating circuits by domain");
 |  
| 250 |  | -
 |  
| 251 |  | -      if (Services.prefs.getBoolPref("extensions.torbutton.use_nontor_proxy")) {
 |  
| 252 |  | -        tor.isolationEnabled = false;
 |  
| 253 |  | -      }
 |  
| 254 |  | -      tor.isolateCircuitsByDomain();
 |  
| 255 |  | -    }
 |  
| 256 |  | -  },
 |  
| 257 |  | -
 |  
| 258 |  | -  newCircuitForDomain(domain) {
 |  
| 259 |  | -    tor.newCircuitForDomain(domain);
 |  
| 260 |  | -  },
 |  
| 261 |  | -
 |  
| 262 |  | -  /**
 |  
| 263 |  | -   * Return the stored SOCKS proxy username and password for the given domain
 |  
| 264 |  | -   * and user context ID.
 |  
| 265 |  | -   *
 |  
| 266 |  | -   * @param {string} firstPartyDomain - The domain to lookup credentials for.
 |  
| 267 |  | -   * @param {integer} userContextId - The ID for the user context.
 |  
| 268 |  | -   *
 |  
| 269 |  | -   * @return {{ username: string, password: string }?} - The SOCKS credentials,
 |  
| 270 |  | -   *   or null if none are found.
 |  
| 271 |  | -   */
 |  
| 272 |  | -  getSocksProxyCredentials(firstPartyDomain, userContextId) {
 |  
| 273 |  | -    if (firstPartyDomain == "") {
 |  
| 274 |  | -      firstPartyDomain = "--unknown--";
 |  
| 275 |  | -    }
 |  
| 276 |  | -    let proxyPassword = tor.passwordForDomainAndUserContextId(
 |  
| 277 |  | -      firstPartyDomain,
 |  
| 278 |  | -      userContextId,
 |  
| 279 |  | -      // Do not create a new entry if it does not exist.
 |  
| 280 |  | -      false
 |  
| 281 |  | -    );
 |  
| 282 |  | -    if (!proxyPassword) {
 |  
| 283 |  | -      return null;
 |  
| 284 |  | -    }
 |  
| 285 |  | -    return {
 |  
| 286 |  | -      username: tor.usernameForDomainAndUserContextId(
 |  
| 287 |  | -        firstPartyDomain,
 |  
| 288 |  | -        userContextId
 |  
| 289 |  | -      ),
 |  
| 290 |  | -      password: proxyPassword,
 |  
| 291 |  | -    };
 |  
| 292 |  | -  },
 |  
| 293 |  | -
 |  
| 294 |  | -  enableIsolation() {
 |  
| 295 |  | -    tor.isolationEnabled = true;
 |  
| 296 |  | -  },
 |  
| 297 |  | -
 |  
| 298 |  | -  disableIsolation() {
 |  
| 299 |  | -    tor.isolationEnabled = false;
 |  
| 300 |  | -  },
 |  
| 301 |  | -
 |  
| 302 |  | -  clearIsolation() {
 |  
| 303 |  | -    tor.clearIsolation();
 |  
| 304 |  | -  },
 |  
| 305 |  | -
 |  
| 306 |  | -  wrappedJSObject: null,
 |  
| 307 |  | -};
 |  
| 308 |  | -
 |  
| 309 |  | -// Assign factory to global object.
 |  
| 310 |  | -const NSGetFactory = XPCOMUtils.generateNSGetFactory
 |  
| 311 |  | -  ? XPCOMUtils.generateNSGetFactory([DomainIsolator])
 |  
| 312 |  | -  : ComponentUtils.generateNSGetFactory([DomainIsolator]); |  toolkit/torbutton/jar.mn
 
 
| ... | ... | @@ -43,9 +43,5 @@ torbutton.jar: |  
| 43 | 43 |  % component {f36d72c9-9718-4134-b550-e109638331d7} %components/torbutton-logger.js
 |  
| 44 | 44 |  % contract @torproject.org/torbutton-logger;1 {f36d72c9-9718-4134-b550-e109638331d7}
 |  
| 45 | 45 |  
 |  
| 46 |  | -% component {e33fd6d4-270f-475f-a96f-ff3140279f68} %components/domain-isolator.js
 |  
| 47 |  | -% contract @torproject.org/domain-isolator;1 {e33fd6d4-270f-475f-a96f-ff3140279f68}
 |  
| 48 |  | -
 |  
| 49 | 46 |  % category profile-after-change StartupObserver @torproject.org/startup-observer;1
 |  
| 50 |  | -% category profile-after-change DomainIsolator @torproject.org/domain-isolator;1
 |  
| 51 | 47 |  % category profile-after-change DragDropFilter @torproject.org/torbutton-dragDropFilter;1 |  toolkit/torbutton/modules/utils.js
 
 
| ... | ... | @@ -213,45 +213,6 @@ var unescapeTorString = function(str) { |  
| 213 | 213 |    return _torControl._strUnescape(str);
 |  
| 214 | 214 |  };
 |  
| 215 | 215 |  
 |  
| 216 |  | -var getFPDFromHost = hostname => {
 |  
| 217 |  | -  try {
 |  
| 218 |  | -    return Services.eTLD.getBaseDomainFromHost(hostname);
 |  
| 219 |  | -  } catch (e) {
 |  
| 220 |  | -    if (
 |  
| 221 |  | -      e.result == Cr.NS_ERROR_HOST_IS_IP_ADDRESS ||
 |  
| 222 |  | -      e.result == Cr.NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS
 |  
| 223 |  | -    ) {
 |  
| 224 |  | -      return hostname;
 |  
| 225 |  | -    }
 |  
| 226 |  | -  }
 |  
| 227 |  | -  return null;
 |  
| 228 |  | -};
 |  
| 229 |  | -
 |  
| 230 |  | -// Assuming this is called with gBrowser.selectedBrowser
 |  
| 231 |  | -var getDomainForBrowser = browser => {
 |  
| 232 |  | -  let fpd = browser.contentPrincipal.originAttributes.firstPartyDomain;
 |  
| 233 |  | -  // Bug 31562: For neterror or certerror, get the original URL from
 |  
| 234 |  | -  // browser.currentURI and use it to calculate the firstPartyDomain.
 |  
| 235 |  | -  let knownErrors = [
 |  
| 236 |  | -    "about:neterror",
 |  
| 237 |  | -    "about:certerror",
 |  
| 238 |  | -    "about:httpsonlyerror",
 |  
| 239 |  | -  ];
 |  
| 240 |  | -  let documentURI = browser.documentURI;
 |  
| 241 |  | -  if (
 |  
| 242 |  | -    documentURI &&
 |  
| 243 |  | -    documentURI.schemeIs("about") &&
 |  
| 244 |  | -    knownErrors.some(x => documentURI.spec.startsWith(x))
 |  
| 245 |  | -  ) {
 |  
| 246 |  | -    let knownSchemes = ["http", "https", "ftp"];
 |  
| 247 |  | -    let currentURI = browser.currentURI;
 |  
| 248 |  | -    if (currentURI && knownSchemes.some(x => currentURI.schemeIs(x))) {
 |  
| 249 |  | -      fpd = getFPDFromHost(currentURI.host) || fpd;
 |  
| 250 |  | -    }
 |  
| 251 |  | -  }
 |  
| 252 |  | -  return fpd;
 |  
| 253 |  | -};
 |  
| 254 |  | -
 |  
| 255 | 216 |  var m_tb_torlog = Cc["@torproject.org/torbutton-logger;1"].getService(
 |  
| 256 | 217 |    Ci.nsISupports
 |  
| 257 | 218 |  ).wrappedJSObject;
 |  
| ... | ... | @@ -310,7 +271,6 @@ let EXPORTED_SYMBOLS = [ |  
| 310 | 271 |    "bindPrefAndInit",
 |  
| 311 | 272 |    "getEnv",
 |  
| 312 | 273 |    "getLocale",
 |  
| 313 |  | -  "getDomainForBrowser",
 |  
| 314 | 274 |    "getPrefValue",
 |  
| 315 | 275 |    "observe",
 |  
| 316 | 276 |    "showDialog",
 |  
 |