Pier Angelo Vendrame pushed to branch tor-browser-115.1.0esr-13.0-1 at The Tor Project / Applications / Tor Browser
Commits: c3d2496b by Pier Angelo Vendrame at 2023-07-27T21:07:52+02:00 fixup! Add TorStrings module for localization
Move the `getLocale` function here from TorButton util.js and stop importing it.
- - - - - 642df684 by Pier Angelo Vendrame at 2023-07-27T21:07:53+02:00 fixup! Bug 40933: Add tor-launcher functionality
Fix a couple of problems in TorLauncherUtil and TorParsers.
Also, moved the function to parse bridges in TorParsers.
- - - - - 9e825b61 by Pier Angelo Vendrame at 2023-07-27T21:07:53+02:00 fixup! Bug 31286: Implementation of bridge, proxy, and firewall settings in about:preferences#connection
Use the bridge line parser from TorParsers.
- - - - - 8061f810 by Pier Angelo Vendrame at 2023-07-27T21:07:53+02:00 fixup! Bug 40933: Add tor-launcher functionality
Use actual private members for TorProcess.
- - - - - f5a3f4af by Pier Angelo Vendrame at 2023-07-27T21:07:54+02:00 fixup! Bug 40933: Add tor-launcher functionality
Group arg pushes in one line (keys and values together).
- - - - - 71b08c4e by Pier Angelo Vendrame at 2023-07-27T21:07:54+02:00 fixup! Bug 40933: Add tor-launcher functionality
TorProcess: use real private properties instead of _, and removed the dependency on TorProtocolService (temporarily moved it to TorMonitorService, but eventually we should unify TorProtocolService and TorMonitorService, to then split them again in a smarter way).
- - - - - e1a69b4e by Pier Angelo Vendrame at 2023-07-27T21:07:55+02:00 fixup! Bug 10760: Integrate TorButton to TorBrowser core
Move the SOCKS preference updater to TorProtocolService. We will need to refactor all this kind of stuff, but at least let's get it in a single place.
Also, since this was the last bit of the startup service, remove the file, the component and what else was needed to add them.
- - - - - d457b6f8 by Pier Angelo Vendrame at 2023-07-31T20:42:54+02:00 fixup! Bug 40933: Add tor-launcher functionality
Hashing the control port password is needed only on the process, so move this function to TorProcess.
- - - - - c4e8cd0f by Pier Angelo Vendrame at 2023-07-31T20:42:57+02:00 fixup! Bug 10760: Integrate TorButton to TorBrowser core
The hashpassword parameter has been removed.
- - - - -
13 changed files:
- browser/components/torpreferences/content/connectionPane.js - browser/installer/package-manifest.in - browser/modules/TorStrings.jsm - toolkit/components/tor-launcher/TorLauncherUtil.sys.mjs - toolkit/components/tor-launcher/TorMonitorService.sys.mjs - toolkit/components/tor-launcher/TorParsers.sys.mjs - toolkit/components/tor-launcher/TorProcess.sys.mjs - toolkit/components/tor-launcher/TorProtocolService.sys.mjs - toolkit/torbutton/chrome/content/torbutton.js - toolkit/torbutton/components.conf - − toolkit/torbutton/modules/TorbuttonStartupObserver.jsm - toolkit/torbutton/moz.build - − toolkit/torbutton/torbutton.manifest
Changes:
===================================== browser/components/torpreferences/content/connectionPane.js ===================================== @@ -14,8 +14,11 @@ const { setTimeout, clearTimeout } = ChromeUtils.import( const { TorSettings, TorSettingsTopics, TorSettingsData, TorBridgeSource } = ChromeUtils.import("resource:///modules/TorSettings.jsm");
-const { TorProtocolService } = ChromeUtils.import( - "resource://gre/modules/TorProtocolService.jsm" +const { TorParsers } = ChromeUtils.importESModule( + "resource://gre/modules/TorParsers.sys.mjs" +); +const { TorProtocolService } = ChromeUtils.importESModule( + "resource://gre/modules/TorProtocolService.sys.mjs" ); const { TorMonitorService, TorMonitorTopics } = ChromeUtils.import( "resource://gre/modules/TorMonitorService.jsm" @@ -495,7 +498,7 @@ const gConnectionPane = (function () { }); const idString = TorStrings.settings.bridgeId; const id = card.querySelector(selectors.bridges.cardId); - const details = parseBridgeLine(bridgeString); + const details = TorParsers.parseBridgeLine(bridgeString); if (details && details.id !== undefined) { card.setAttribute("data-bridge-id", details.id); } @@ -1111,23 +1114,3 @@ function makeBridgeId(bridgeString) { hash & 0x000000ff, ]; } - -function parseBridgeLine(line) { - const re = - /^\s*(\S+\s+)?([0-9a-fA-F.[]:]+:\d{1,5})(\s+[0-9a-fA-F]{40})?(\s+.+)?/; - const matches = line.match(re); - if (!matches) { - return null; - } - let bridge = { addr: matches[2] }; - if (matches[1] !== undefined) { - bridge.transport = matches[1].trim(); - } - if (matches[3] !== undefined) { - bridge.id = matches[3].trim().toUpperCase(); - } - if (matches[4] !== undefined) { - bridge.args = matches[4].trim(); - } - return bridge; -}
===================================== browser/installer/package-manifest.in ===================================== @@ -228,7 +228,6 @@ @RESPATH@/components/tor-launcher.manifest @RESPATH@/chrome/torbutton.manifest @RESPATH@/chrome/torbutton/* -@RESPATH@/components/torbutton.manifest @RESPATH@/chrome/toolkit@JAREXT@ @RESPATH@/chrome/toolkit.manifest #ifdef MOZ_GTK
===================================== browser/modules/TorStrings.jsm ===================================== @@ -11,9 +11,11 @@ const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); const { AppConstants } = ChromeUtils.import( "resource://gre/modules/AppConstants.jsm" ); -const { getLocale } = ChromeUtils.import( - "resource://torbutton/modules/utils.js" -); + +function getLocale() { + const locale = Services.locale.appLocaleAsBCP47; + return locale === "ja-JP-macos" ? "ja" : locale; +}
/* Tor Property String Bundle
===================================== toolkit/components/tor-launcher/TorLauncherUtil.sys.mjs ===================================== @@ -5,6 +5,12 @@ * Tor Launcher Util JS Module *************************************************************************/
+const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + FileUtils: "resource://gre/modules/FileUtils.sys.jsm", +}); + const kPropBundleURI = "chrome://torbutton/locale/torlauncher.properties"; const kPropNamePrefix = "torlauncher."; const kIPCDirPrefName = "extensions.torlauncher.tmp_ipc_dir"; @@ -209,14 +215,15 @@ class TorFile { // and return a file object. The control and SOCKS IPC objects will be // created by tor. normalize() { - if (!this.file.exists() && !this.isIPC) { + if (this.file.exists()) { + try { + this.file.normalize(); + } catch (e) { + console.warn("Normalization of the path failed", e); + } + } else if (!this.isIPC) { throw new Error(`${this.fileType} file not found: ${this.file.path}`); } - try { - this.file.normalize(); - } catch (e) { - console.warn("Normalization of the path failed", e); - }
// Ensure that the IPC path length is short enough for use by the // operating system. If not, create and use a unique directory under @@ -452,6 +459,154 @@ export const TorLauncherUtil = Object.freeze({ return result ? result : ""; },
+ /** + * Determine what kind of SOCKS port has been requested for this session or + * the browser has been configured for. + * On Windows (where Unix domain sockets are not supported), TCP is always + * used. + * + * The following environment variables are supported and take precedence over + * preferences: + * TOR_TRANSPROXY (do not use a proxy) + * TOR_SOCKS_IPC_PATH (file system path; ignored on Windows) + * TOR_SOCKS_HOST + * TOR_SOCKS_PORT + * + * The following preferences are consulted: + * network.proxy.socks + * network.proxy.socks_port + * extensions.torlauncher.socks_port_use_ipc (Boolean) + * extensions.torlauncher.socks_ipc_path (file system path) + * If extensions.torlauncher.socks_ipc_path is empty, a default path is used. + * + * When using TCP, if a value is not defined via an env variable it is + * taken from the corresponding browser preference if possible. The + * exceptions are: + * If network.proxy.socks contains a file: URL, a default value of + * "127.0.0.1" is used instead. + * If the network.proxy.socks_port value is not valid (outside the + * (0; 65535] range), a default value of 9150 is used instead. + * + * The SOCKS configuration will not influence the launch of a tor daemon and + * the configuration of the control port in any way. + * When a SOCKS configuration is required without TOR_SKIP_LAUNCH, the browser + * will try to configure the tor instance to use the required configuration. + * This also applies to TOR_TRANSPROXY (at least for now): tor will be + * launched with its defaults. + * + * TODO: add a preference to ignore the current configuration, and let tor + * listen on any free port. Then, the browser will prompt the daemon the port + * to use through the control port (even though this is quite dangerous at the + * moment, because with network disabled tor will disable also the SOCKS + * listeners, so it means that we will have to check it every time we change + * the network status). + */ + getPreferredSocksConfiguration() { + if (Services.env.exists("TOR_TRANSPROXY")) { + Services.prefs.setBoolPref("network.proxy.socks_remote_dns", false); + Services.prefs.setIntPref("network.proxy.type", 0); + Services.prefs.setIntPref("network.proxy.socks_port", 0); + Services.prefs.setCharPref("network.proxy.socks", ""); + return { transproxy: true }; + } + + let useIPC; + const socksPortInfo = { + transproxy: false, + }; + + if (!this.isWindows && Services.env.exists("TOR_SOCKS_IPC_PATH")) { + useIPC = true; + const ipcPath = Services.env.get("TOR_SOCKS_IPC_PATH"); + if (ipcPath) { + socksPortInfo.ipcFile = new lazy.FileUtils.File(ipcPath); + } + } else { + // Check for TCP host and port environment variables. + if (Services.env.exists("TOR_SOCKS_HOST")) { + socksPortInfo.host = Services.env.get("TOR_SOCKS_HOST"); + useIPC = false; + } + if (Services.env.exists("TOR_SOCKS_PORT")) { + const port = parseInt(Services.env.get("TOR_SOCKS_PORT"), 10); + if (Number.isInteger(port) && port > 0 && port <= 65535) { + socksPortInfo.port = port; + useIPC = false; + } + } + } + + if (useIPC === undefined) { + socksPortInfo.useIPC = + !this.isWindows && + Services.prefs.getBoolPref( + "extensions.torlauncher.socks_port_use_ipc", + false + ); + } + + // Fill in missing SOCKS info from prefs. + if (socksPortInfo.useIPC) { + if (!socksPortInfo.ipcFile) { + socksPortInfo.ipcFile = TorLauncherUtil.getTorFile("socks_ipc", false); + } + } else { + if (!socksPortInfo.host) { + let socksAddr = Services.prefs.getCharPref( + "network.proxy.socks", + "127.0.0.1" + ); + let socksAddrHasHost = socksAddr && !socksAddr.startsWith("file:"); + socksPortInfo.host = socksAddrHasHost ? socksAddr : "127.0.0.1"; + } + + if (!socksPortInfo.port) { + let socksPort = Services.prefs.getIntPref( + "network.proxy.socks_port", + 0 + ); + // This pref is set as 0 by default in Firefox, use 9150 if we get 0. + socksPortInfo.port = + socksPort > 0 && socksPort <= 65535 ? socksPort : 9150; + } + } + + return socksPortInfo; + }, + + setProxyConfiguration(socksPortInfo) { + if (socksPortInfo.transproxy) { + return; + } + + if (socksPortInfo.useIPC) { + const fph = Services.io + .getProtocolHandler("file") + .QueryInterface(Ci.nsIFileProtocolHandler); + const fileURI = fph.newFileURI(socksPortInfo.ipcFile); + Services.prefs.setCharPref("network.proxy.socks", fileURI.spec); + Services.prefs.setIntPref("network.proxy.socks_port", 0); + } else { + if (socksPortInfo.host) { + Services.prefs.setCharPref("network.proxy.socks", socksPortInfo.host); + } + if (socksPortInfo.port) { + Services.prefs.setIntPref( + "network.proxy.socks_port", + socksPortInfo.port + ); + } + } + + if (socksPortInfo.ipcFile || socksPortInfo.host || socksPortInfo.port) { + Services.prefs.setBoolPref("network.proxy.socks_remote_dns", true); + Services.prefs.setIntPref("network.proxy.type", 1); + } + + // Force prefs to be synced to disk + Services.prefs.savePrefFile(null); + }, + get shouldStartAndOwnTor() { const kPrefStartTor = "extensions.torlauncher.start_tor"; try {
===================================== toolkit/components/tor-launcher/TorMonitorService.sys.mjs ===================================== @@ -13,6 +13,10 @@ import { TorLauncherUtil } from "resource://gre/modules/TorLauncherUtil.sys.mjs"
const lazy = {};
+ChromeUtils.defineESModuleGetters(lazy, { + TorProtocolService: "resource://gre/modules/TorProtocolService.sys.mjs", +}); + ChromeUtils.defineModuleGetter( lazy, "controller", @@ -233,7 +237,10 @@ export const TorMonitorService = { // TorProcess should be instanced once, then always reused and restarted // only through the prompt it exposes when the controlled process dies. if (!this._torProcess) { - this._torProcess = new TorProcess(); + this._torProcess = new TorProcess( + lazy.TorProtocolService.torControlPortInfo, + lazy.TorProtocolService.torSOCKSPortInfo + ); this._torProcess.onExit = () => { this._shutDownEventMonitor(); Services.obs.notifyObservers(null, TorTopics.ProcessExited); @@ -254,6 +261,7 @@ export const TorMonitorService = { await this._torProcess.start(); if (this._torProcess.isRunning) { logger.info("tor started"); + this._torProcessStartTime = Date.now(); } } catch (e) { // TorProcess already logs the error.
===================================== toolkit/components/tor-launcher/TorParsers.sys.mjs ===================================== @@ -267,4 +267,18 @@ export const TorParsers = Object.freeze({ rv += aStr.substring(lastAdded, aStr.length - 1); return rv; }, + + parseBridgeLine(line) { + const re = + /\s*(?:(?<transport>\S+)\s+)?(?<addr>[0-9a-fA-F.[]:]+:\d{1,5})(?:\s+(?<id>[0-9a-fA-F]{40}))?(?:\s+(?<args>.+))?/; + const match = re.exec(line); + if (!match) { + throw new Error("Invalid bridge line."); + } + const bridge = match.groups; + if (!bridge.transport) { + bridge.transport = "vanilla"; + } + return bridge; + }, });
===================================== toolkit/components/tor-launcher/TorProcess.sys.mjs ===================================== @@ -1,21 +1,17 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + import { setTimeout } from "resource://gre/modules/Timer.sys.mjs"; import { ConsoleAPI } from "resource://gre/modules/Console.sys.mjs"; import { Subprocess } from "resource://gre/modules/Subprocess.sys.mjs";
const lazy = {};
-ChromeUtils.defineModuleGetter( - lazy, - "TorProtocolService", - "resource://gre/modules/TorProtocolService.jsm" -); -const { TorLauncherUtil } = ChromeUtils.import( - "resource://gre/modules/TorLauncherUtil.jsm" -); - -const { TorParsers } = ChromeUtils.import( - "resource://gre/modules/TorParsers.jsm" -); +ChromeUtils.defineESModuleGetters(lazy, { + TorLauncherUtil: "resource://gre/modules/TorLauncherUtil.sys.mjs", + TorParsers: "resource://gre/modules/TorParsers.sys.mjs", +});
const TorProcessStatus = Object.freeze({ Unknown: 0, @@ -30,53 +26,86 @@ const logger = new ConsoleAPI({ });
export class TorProcess { - _exeFile = null; - _dataDir = null; - _args = []; - _subprocess = null; - _status = TorProcessStatus.Unknown; - _torProcessStartTime = null; // JS Date.now() - _didConnectToTorControlPort = false; // Have we ever made a connection? + #controlSettings; + #socksSettings; + #exeFile = null; + #dataDir = null; + #args = []; + #subprocess = null; + #status = TorProcessStatus.Unknown; + // Have we ever made a connection on the control port? + #didConnectToTorControlPort = false; + + onExit = exitCode => {}; + onRestart = () => {}; + + constructor(controlSettings, socksSettings) { + if ( + controlSettings && + !controlSettings.password && + !controlSettings.cookieFilePath + ) { + throw new Error("Unauthenticated control port is not supported"); + }
- onExit = null; - onRestart = null; + const checkPort = port => + port === undefined || + (Number.isInteger(controlSettings.port) && + controlSettings.port > 0 && + controlSettings.port < 65535); + if (!checkPort(controlSettings?.port)) { + throw new Error("Invalid control port"); + } + if (!checkPort(socksSettings.port)) { + throw new Error("Invalid port specified for the SOCKS port"); + } + + this.#controlSettings = { ...controlSettings }; + const ipcFileToString = file => + "unix:" + lazy.TorParsers.escapeString(file.path); + if (controlSettings.ipcFile) { + this.#controlSettings.ipcFile = ipcFileToString(controlSettings.ipcFile); + } + this.#socksSettings = { ...socksSettings }; + if (socksSettings.ipcFile) { + this.#socksSettings.ipcFile = ipcFileToString(socksSettings.ipcFile); + } + }
get status() { - return this._status; + return this.#status; }
get isRunning() { return ( - this._status === TorProcessStatus.Starting || - this._status === TorProcessStatus.Running + this.#status === TorProcessStatus.Starting || + this.#status === TorProcessStatus.Running ); }
async start() { - if (this._subprocess) { + if (this.#subprocess) { return; }
- this._status = TorProcessStatus.Unknown; + this.#status = TorProcessStatus.Unknown;
try { - this._makeArgs(); - this._addControlPortArg(); - this._addSocksPortArg(); + this.#makeArgs(); + this.#addControlPortArgs(); + this.#addSocksPortArg();
const pid = Services.appinfo.processID; if (pid !== 0) { - this._args.push("__OwningControllerProcess"); - this._args.push("" + pid); + this.#args.push("__OwningControllerProcess", pid.toString()); }
- if (TorLauncherUtil.shouldShowNetworkSettings) { - this._args.push("DisableNetwork"); - this._args.push("1"); + if (lazy.TorLauncherUtil.shouldShowNetworkSettings) { + this.#args.push("DisableNetwork", "1"); }
- this._status = TorProcessStatus.Starting; - this._didConnectToTorControlPort = false; + this.#status = TorProcessStatus.Starting; + this.#didConnectToTorControlPort = false;
// useful for simulating slow tor daemon launch const kPrefTorDaemonLaunchDelay = "extensions.torlauncher.launch_delay"; @@ -88,29 +117,31 @@ export class TorProcess { await new Promise(resolve => setTimeout(() => resolve(), launchDelay)); }
- logger.debug(`Starting ${this._exeFile.path}`, this._args); + logger.debug(`Starting ${this.#exeFile.path}`, this.#args); const options = { - command: this._exeFile.path, - arguments: this._args, + command: this.#exeFile.path, + arguments: this.#args, stderr: "stdout", - workdir: TorLauncherUtil.getTorFile("pt-startup-dir", false).path, + workdir: lazy.TorLauncherUtil.getTorFile("pt-startup-dir", false).path, }; - this._subprocess = await Subprocess.call(options); - this._dumpStdout(); - this._watchProcess(); - this._status = TorProcessStatus.Running; - this._torProcessStartTime = Date.now(); + this.#subprocess = await Subprocess.call(options); + this.#status = TorProcessStatus.Running; } catch (e) { - this._status = TorProcessStatus.Exited; - this._subprocess = null; + this.#status = TorProcessStatus.Exited; + this.#subprocess = null; logger.error("startTor error:", e); throw e; } + + // Do not await the following functions, as they will return only when the + // process exits. + this.#dumpStdout(); + this.#watchProcess(); }
// Forget about a process. // - // Instead of killing the tor process, we rely on the TAKEOWNERSHIP feature + // Instead of killing the tor process, we rely on the TAKEOWNERSHIP feature // to shut down tor when we close the control port connection. // // Previously, we sent a SIGNAL HALT command to the tor control port, @@ -123,36 +154,38 @@ export class TorProcess { // Still, before closing the owning connection, this class should forget about // the process, so that future notifications will be ignored. forget() { - this._subprocess = null; - this._status = TorProcessStatus.Exited; + this.#subprocess = null; + this.#status = TorProcessStatus.Exited; }
// The owner of the process can use this function to tell us that they // successfully connected to the control port. This information will be used // only to decide which text to show in the confirmation dialog if tor exits. connectionWorked() { - this._didConnectToTorControlPort = true; + this.#didConnectToTorControlPort = true; }
- async _dumpStdout() { + async #dumpStdout() { let string; while ( - this._subprocess && - (string = await this._subprocess.stdout.readString()) + this.#subprocess && + (string = await this.#subprocess.stdout.readString()) ) { dump(string); } }
- async _watchProcess() { - const watched = this._subprocess; + async #watchProcess() { + const watched = this.#subprocess; if (!watched) { return; } + let processExitCode; try { const { exitCode } = await watched.wait(); + processExitCode = exitCode;
- if (watched !== this._subprocess) { + if (watched !== this.#subprocess) { logger.debug(`A Tor process exited with code ${exitCode}.`); } else if (exitCode) { logger.warn(`The watched Tor process exited with code ${exitCode}.`); @@ -163,30 +196,31 @@ export class TorProcess { logger.error("Failed to watch the tor process", e); }
- if (watched === this._subprocess) { - this._processExitedUnexpectedly(); + if (watched === this.#subprocess) { + this.#processExitedUnexpectedly(processExitCode); } }
- _processExitedUnexpectedly() { - this._subprocess = null; - this._status = TorProcessStatus.Exited; + #processExitedUnexpectedly(exitCode) { + this.#subprocess = null; + this.#status = TorProcessStatus.Exited;
// TODO: Move this logic somewhere else? let s; - if (!this._didConnectToTorControlPort) { + if (!this.#didConnectToTorControlPort) { // tor might be misconfigured, becauser we could never connect to it const key = "tor_exited_during_startup"; - s = TorLauncherUtil.getLocalizedString(key); + s = lazy.TorLauncherUtil.getLocalizedString(key); } else { // tor exited suddenly, so configuration should be okay s = - TorLauncherUtil.getLocalizedString("tor_exited") + + lazy.TorLauncherUtil.getLocalizedString("tor_exited") + "\n\n" + - TorLauncherUtil.getLocalizedString("tor_exited2"); + lazy.TorLauncherUtil.getLocalizedString("tor_exited2"); } logger.info(s); - const defaultBtnLabel = TorLauncherUtil.getLocalizedString("restart_tor"); + const defaultBtnLabel = + lazy.TorLauncherUtil.getLocalizedString("restart_tor"); let cancelBtnLabel = "OK"; try { const kSysBundleURI = "chrome://global/locale/commonDialogs.properties"; @@ -196,51 +230,43 @@ export class TorProcess { logger.warn("Could not localize the cancel button", e); }
- const restart = TorLauncherUtil.showConfirm( + const restart = lazy.TorLauncherUtil.showConfirm( null, s, defaultBtnLabel, cancelBtnLabel ); if (restart) { - this.start().then(() => { - if (this.onRestart) { - this.onRestart(); - } - }); - } else if (this.onExit) { - this.onExit(); + this.start().then(this.onRestart); + } else { + this.onExit(exitCode); } }
- _makeArgs() { - // Ideally, we would cd to the Firefox application directory before - // starting tor (but we don't know how to do that). Instead, we - // rely on the TBB launcher to start Firefox from the right place. - + #makeArgs() { + this.#exeFile = lazy.TorLauncherUtil.getTorFile("tor", false); + const torrcFile = lazy.TorLauncherUtil.getTorFile("torrc", true); // Get the Tor data directory first so it is created before we try to // construct paths to files that will be inside it. - this._exeFile = TorLauncherUtil.getTorFile("tor", false); - const torrcFile = TorLauncherUtil.getTorFile("torrc", true); - this._dataDir = TorLauncherUtil.getTorFile("tordatadir", true); - const onionAuthDir = TorLauncherUtil.getTorFile("toronionauthdir", true); - const hashedPassword = lazy.TorProtocolService.torGetPassword(true); + this.#dataDir = lazy.TorLauncherUtil.getTorFile("tordatadir", true); + const onionAuthDir = lazy.TorLauncherUtil.getTorFile( + "toronionauthdir", + true + ); let detailsKey; - if (!this._exeFile) { + if (!this.#exeFile) { detailsKey = "tor_missing"; } else if (!torrcFile) { detailsKey = "torrc_missing"; - } else if (!this._dataDir) { + } else if (!this.#dataDir) { detailsKey = "datadir_missing"; } else if (!onionAuthDir) { detailsKey = "onionauthdir_missing"; - } else if (!hashedPassword) { - detailsKey = "password_hash_missing"; } if (detailsKey) { - const details = TorLauncherUtil.getLocalizedString(detailsKey); + const details = lazy.TorLauncherUtil.getLocalizedString(detailsKey); const key = "unable_to_start_tor"; - const err = TorLauncherUtil.getFormattedLocalizedString( + const err = lazy.TorLauncherUtil.getFormattedLocalizedString( key, [details], 1 @@ -248,7 +274,7 @@ export class TorProcess { throw new Error(err); }
- const torrcDefaultsFile = TorLauncherUtil.getTorFile( + const torrcDefaultsFile = lazy.TorLauncherUtil.getTorFile( "torrc-defaults", false ); @@ -258,77 +284,131 @@ export class TorProcess { const geoip6File = torrcDefaultsFile.clone(); geoip6File.leafName = "geoip6";
- this._args = []; + this.#args = []; if (torrcDefaultsFile) { - this._args.push("--defaults-torrc"); - this._args.push(torrcDefaultsFile.path); + this.#args.push("--defaults-torrc", torrcDefaultsFile.path); } - this._args.push("-f"); - this._args.push(torrcFile.path); - this._args.push("DataDirectory"); - this._args.push(this._dataDir.path); - this._args.push("ClientOnionAuthDir"); - this._args.push(onionAuthDir.path); - this._args.push("GeoIPFile"); - this._args.push(geoipFile.path); - this._args.push("GeoIPv6File"); - this._args.push(geoip6File.path); - this._args.push("HashedControlPassword"); - this._args.push(hashedPassword); + this.#args.push("-f", torrcFile.path); + this.#args.push("DataDirectory", this.#dataDir.path); + this.#args.push("ClientOnionAuthDir", onionAuthDir.path); + this.#args.push("GeoIPFile", geoipFile.path); + this.#args.push("GeoIPv6File", geoip6File.path); }
- _addControlPortArg() { - // Include a ControlPort argument to support switching between - // a TCP port and an IPC port (e.g., a Unix domain socket). We - // include a "+__" prefix so that (1) this control port is added - // to any control ports that the user has defined in their torrc - // file and (2) it is never written to torrc. + /** + * Add all the arguments related to the control port. + * We use the + prefix so that the the port is added to any other port already + * defined in the torrc, and the __ prefix so that it is never written to + * torrc. + */ + #addControlPortArgs() { + if (!this.#controlSettings) { + return; + } + let controlPortArg; - const controlIPCFile = lazy.TorProtocolService.torGetControlIPCFile(); - const controlPort = lazy.TorProtocolService.torGetControlPort(); - if (controlIPCFile) { - controlPortArg = this._ipcPortArg(controlIPCFile); - } else if (controlPort) { - controlPortArg = "" + controlPort; + if (this.#controlSettings.ipcFile) { + controlPortArg = this.#controlSettings.ipcFile; + } else if (this.#controlSettings.port) { + controlPortArg = this.#controlSettings.host + ? `${this.#controlSettings.host}:${this.#controlSettings.port}` + : this.#controlSettings.port.toString(); } if (controlPortArg) { - this._args.push("+__ControlPort"); - this._args.push(controlPortArg); + this.#args.push("+__ControlPort", controlPortArg); + } + + if (this.#controlSettings.password) { + this.#args.push( + "HashedControlPassword", + this.#hashPassword(this.#controlSettings.password) + ); + } + if (this.#controlSettings.cookieFilePath) { + this.#args.push("CookieAuthentication", "1"); + this.#args.push("CookieAuthFile", this.#controlSettings.cookieFilePath); } }
- _addSocksPortArg() { - // Include a SocksPort argument to support switching between - // a TCP port and an IPC port (e.g., a Unix domain socket). We - // include a "+__" prefix so that (1) this SOCKS port is added - // to any SOCKS ports that the user has defined in their torrc - // file and (2) it is never written to torrc. - const socksPortInfo = lazy.TorProtocolService.torGetSOCKSPortInfo(); - if (socksPortInfo) { - let socksPortArg; - if (socksPortInfo.ipcFile) { - socksPortArg = this._ipcPortArg(socksPortInfo.ipcFile); - } else if (socksPortInfo.host && socksPortInfo.port != 0) { - socksPortArg = socksPortInfo.host + ":" + socksPortInfo.port; - } - if (socksPortArg) { - let socksPortFlags = Services.prefs.getCharPref( - "extensions.torlauncher.socks_port_flags", - "IPv6Traffic PreferIPv6 KeepAliveIsolateSOCKSAuth" - ); - if (socksPortFlags) { - socksPortArg += " " + socksPortFlags; - } - this._args.push("+__SocksPort"); - this._args.push(socksPortArg); + /** + * Add the argument related to the control port. + * We use the + prefix so that the the port is added to any other port already + * defined in the torrc, and the __ prefix so that it is never written to + * torrc. + */ + #addSocksPortArg() { + let socksPortArg; + if (this.#socksSettings.ipcFile) { + socksPortArg = this.#socksSettings.ipcFile; + } else if (this.#socksSettings.port != 0) { + socksPortArg = this.#socksSettings.host + ? `${this.#socksSettings.host}:${this.#socksSettings.port}` + : this.#socksSettings.port.toString(); + } + if (socksPortArg) { + const socksPortFlags = Services.prefs.getCharPref( + "extensions.torlauncher.socks_port_flags", + "IPv6Traffic PreferIPv6 KeepAliveIsolateSOCKSAuth" + ); + if (socksPortFlags) { + socksPortArg += " " + socksPortFlags; } + this.#args.push("+__SocksPort", socksPortArg); + } + } + + // Based on Vidalia's TorSettings::hashPassword(). + #hashPassword(aHexPassword) { + if (!aHexPassword) { + return null; } + + // Generate a random, 8 byte salt value. + const salt = Array.from(crypto.getRandomValues(new Uint8Array(8))); + + // Convert hex-encoded password to an array of bytes. + const password = []; + for (let i = 0; i < aHexPassword.length; i += 2) { + password.push(parseInt(aHexPassword.substring(i, i + 2), 16)); + } + + // Run through the S2K algorithm and convert to a string. + const toHex = v => v.toString(16).padStart(2, "0"); + const arrayToHex = aArray => aArray.map(toHex).join(""); + const kCodedCount = 96; + const hashVal = this.#cryptoSecretToKey(password, salt, kCodedCount); + return "16:" + arrayToHex(salt) + toHex(kCodedCount) + arrayToHex(hashVal); }
- // Return a ControlPort or SocksPort argument for aIPCFile (an nsIFile). - // The result is unix:/path or unix:"/path with spaces" with appropriate - // C-style escaping within the path portion. - _ipcPortArg(aIPCFile) { - return "unix:" + TorParsers.escapeString(aIPCFile.path); + // #cryptoSecretToKey() is similar to Vidalia's crypto_secret_to_key(). + // It generates and returns a hash of aPassword by following the iterated + // and salted S2K algorithm (see RFC 2440 section 3.6.1.3). + // See also https://gitlab.torproject.org/tpo/core/torspec/-/blob/main/control-spec.txt#.... + // Returns an array of bytes. + #cryptoSecretToKey(aPassword, aSalt, aCodedCount) { + const inputArray = aSalt.concat(aPassword); + + // Subtle crypto only has the final digest, and does not allow incremental + // updates. + const hasher = Cc["@mozilla.org/security/hash;1"].createInstance( + Ci.nsICryptoHash + ); + hasher.init(hasher.SHA1); + const kEXPBIAS = 6; + let count = (16 + (aCodedCount & 15)) << ((aCodedCount >> 4) + kEXPBIAS); + while (count > 0) { + if (count > inputArray.length) { + hasher.update(inputArray, inputArray.length); + count -= inputArray.length; + } else { + const finalArray = inputArray.slice(0, count); + hasher.update(finalArray, finalArray.length); + count = 0; + } + } + return hasher + .finish(false) + .split("") + .map(b => b.charCodeAt(0)); } }
===================================== toolkit/components/tor-launcher/TorProtocolService.sys.mjs ===================================== @@ -289,9 +289,8 @@ export const TorProtocolService = { // are also used in torbutton.
// Returns Tor password string or null if an error occurs. - torGetPassword(aPleaseHash) { - const pw = this._controlPassword; - return aPleaseHash ? this._hashPassword(pw) : pw; + torGetPassword() { + return this._controlPassword; },
torGetControlIPCFile() { @@ -306,6 +305,24 @@ export const TorProtocolService = { return this._SOCKSPortInfo; },
+ get torControlPortInfo() { + const info = { + password: this._controlPassword, + }; + if (this._controlIPCFile) { + info.ipcFile = this._controlIPCFile?.clone(); + } + if (this._controlPort) { + info.host = this._controlHost; + info.port = this._controlPort; + } + return info; + }, + + get torSOCKSPortInfo() { + return this._SOCKSPortInfo; + }, + // Public, but called only internally
// Executes a command on the control port. @@ -469,115 +486,8 @@ export const TorProtocolService = { this._controlPassword = this._generateRandomPassword(); }
- // Determine what kind of SOCKS port Tor and the browser will use. - // On Windows (where Unix domain sockets are not supported), TCP is - // always used. - // - // The following environment variables are supported and take - // precedence over preferences: - // TOR_SOCKS_IPC_PATH (file system path; ignored on Windows) - // TOR_SOCKS_HOST - // TOR_SOCKS_PORT - // - // The following preferences are consulted: - // network.proxy.socks - // network.proxy.socks_port - // extensions.torlauncher.socks_port_use_ipc (Boolean) - // extensions.torlauncher.socks_ipc_path (file system path) - // If extensions.torlauncher.socks_ipc_path is empty, a default - // path is used (<tor-data-directory>/socks.socket). - // - // When using TCP, if a value is not defined via an env variable it is - // taken from the corresponding browser preference if possible. The - // exceptions are: - // If network.proxy.socks contains a file: URL, a default value of - // "127.0.0.1" is used instead. - // If the network.proxy.socks_port value is 0, a default value of - // 9150 is used instead. - // - // Supported scenarios: - // 1. By default, an IPC object at a default path is used. - // 2. If extensions.torlauncher.socks_port_use_ipc is set to false, - // a TCP socket at 127.0.0.1:9150 is used, unless different values - // are set in network.proxy.socks and network.proxy.socks_port. - // 3. If the TOR_SOCKS_IPC_PATH env var is set, an IPC object at that - // path is used (e.g., a Unix domain socket). - // 4. If the TOR_SOCKS_HOST and/or TOR_SOCKS_PORT env vars are set, TCP - // is used. Values not set via env vars will be taken from the - // network.proxy.socks and network.proxy.socks_port prefs as described - // above. - // 5. If extensions.torlauncher.socks_port_use_ipc is true and - // extensions.torlauncher.socks_ipc_path is set, an IPC object at - // the specified path is used. - // 6. Tor Launcher is disabled. Torbutton will respect the env vars if - // present; if not, the values in network.proxy.socks and - // network.proxy.socks_port are used without modification. - - let useIPC; - this._SOCKSPortInfo = { ipcFile: undefined, host: undefined, port: 0 }; - if (!isWindows && Services.env.exists("TOR_SOCKS_IPC_PATH")) { - let ipcPath = Services.env.get("TOR_SOCKS_IPC_PATH"); - this._SOCKSPortInfo.ipcFile = new lazy.FileUtils.File(ipcPath); - useIPC = true; - } else { - // Check for TCP host and port environment variables. - if (Services.env.exists("TOR_SOCKS_HOST")) { - this._SOCKSPortInfo.host = Services.env.get("TOR_SOCKS_HOST"); - useIPC = false; - } - if (Services.env.exists("TOR_SOCKS_PORT")) { - this._SOCKSPortInfo.port = parseInt( - Services.env.get("TOR_SOCKS_PORT"), - 10 - ); - useIPC = false; - } - } - - if (useIPC === undefined) { - useIPC = - !isWindows && - Services.prefs.getBoolPref( - "extensions.torlauncher.socks_port_use_ipc", - false - ); - } - - // Fill in missing SOCKS info from prefs. - if (useIPC) { - if (!this._SOCKSPortInfo.ipcFile) { - this._SOCKSPortInfo.ipcFile = TorLauncherUtil.getTorFile( - "socks_ipc", - false - ); - } - } else { - if (!this._SOCKSPortInfo.host) { - let socksAddr = Services.prefs.getCharPref( - "network.proxy.socks", - "127.0.0.1" - ); - let socksAddrHasHost = socksAddr && !socksAddr.startsWith("file:"); - this._SOCKSPortInfo.host = socksAddrHasHost ? socksAddr : "127.0.0.1"; - } - - if (!this._SOCKSPortInfo.port) { - let socksPort = Services.prefs.getIntPref( - "network.proxy.socks_port", - 0 - ); - // This pref is set as 0 by default in Firefox, use 9150 if we get 0. - this._SOCKSPortInfo.port = socksPort != 0 ? socksPort : 9150; - } - } - - logger.info("SOCKS port type: " + (useIPC ? "IPC" : "TCP")); - if (useIPC) { - logger.info(`ipcFile: ${this._SOCKSPortInfo.ipcFile.path}`); - } else { - logger.info(`SOCKS host: ${this._SOCKSPortInfo.host}`); - logger.info(`SOCKS port: ${this._SOCKSPortInfo.port}`); - } + this._SOCKSPortInfo = TorLauncherUtil.getPreferredSocksConfiguration(); + TorLauncherUtil.setProxyConfiguration(this._SOCKSPortInfo);
// Set the global control port info parameters. // These values may be overwritten by torbutton when it initializes, but @@ -781,38 +691,6 @@ export const TorProtocolService = { return pwd; },
- // Based on Vidalia's TorSettings::hashPassword(). - _hashPassword(aHexPassword) { - if (!aHexPassword) { - return null; - } - - // Generate a random, 8 byte salt value. - const salt = Array.from(crypto.getRandomValues(new Uint8Array(8))); - - // Convert hex-encoded password to an array of bytes. - const password = []; - for (let i = 0; i < aHexPassword.length; i += 2) { - password.push(parseInt(aHexPassword.substring(i, i + 2), 16)); - } - - // Run through the S2K algorithm and convert to a string. - const kCodedCount = 96; - const hashVal = this._cryptoSecretToKey(password, salt, kCodedCount); - if (!hashVal) { - logger.error("_cryptoSecretToKey() failed"); - return null; - } - - const arrayToHex = aArray => - aArray.map(item => this._toHex(item, 2)).join(""); - let rv = "16:"; - rv += arrayToHex(salt); - rv += this._toHex(kCodedCount, 2); - rv += arrayToHex(hashVal); - return rv; - }, - // Returns -1 upon failure. _cryptoRandInt(aMax) { // Based on tor's crypto_rand_int(). @@ -831,43 +709,6 @@ export const TorProtocolService = { return val % aMax; },
- // _cryptoSecretToKey() is similar to Vidalia's crypto_secret_to_key(). - // It generates and returns a hash of aPassword by following the iterated - // and salted S2K algorithm (see RFC 2440 section 3.6.1.3). - // Returns an array of bytes. - _cryptoSecretToKey(aPassword, aSalt, aCodedCount) { - if (!aPassword || !aSalt) { - return null; - } - - const inputArray = aSalt.concat(aPassword); - - // Subtle crypto only has the final digest, and does not allow incremental - // updates. Also, it is async, so we should hash and keep the hash in a - // variable if we wanted to switch to getters. - // So, keeping this implementation should be okay for now. - const hasher = Cc["@mozilla.org/security/hash;1"].createInstance( - Ci.nsICryptoHash - ); - hasher.init(hasher.SHA1); - const kEXPBIAS = 6; - let count = (16 + (aCodedCount & 15)) << ((aCodedCount >> 4) + kEXPBIAS); - while (count > 0) { - if (count > inputArray.length) { - hasher.update(inputArray, inputArray.length); - count -= inputArray.length; - } else { - const finalArray = inputArray.slice(0, count); - hasher.update(finalArray, finalArray.length); - count = 0; - } - } - return hasher - .finish(false) - .split("") - .map(b => b.charCodeAt(0)); - }, - _toHex(aValue, aMinLen) { return aValue.toString(16).padStart(aMinLen, "0"); },
===================================== toolkit/torbutton/chrome/content/torbutton.js ===================================== @@ -60,7 +60,7 @@ var torbutton_init; } else { try { // Try to get password from Tor Launcher. - m_tb_control_pass = TorProtocolService.torGetPassword(false); + m_tb_control_pass = TorProtocolService.torGetPassword(); } catch (e) {} }
===================================== toolkit/torbutton/components.conf ===================================== @@ -1,12 +1,4 @@ Classes = [ - { - "cid": "{06322def-6fde-4c06-aef6-47ae8e799629}", - "contract_ids": [ - "@torproject.org/startup-observer;1" - ], - "jsm": "resource://torbutton/modules/TorbuttonStartupObserver.jsm", - "constructor": "StartupObserver", - }, { "cid": "{f36d72c9-9718-4134-b550-e109638331d7}", "contract_ids": [
===================================== toolkit/torbutton/modules/TorbuttonStartupObserver.jsm deleted ===================================== @@ -1,138 +0,0 @@ -// Bug 1506 P1-3: This code is mostly hackish remnants of session store -// support. There are a couple of observer events that *might* be worth -// listening to. Search for 1506 in the code. - -/************************************************************************* - * Startup observer (JavaScript XPCOM component) - * - * Cases tested (each during Tor and Non-Tor, FF4 and FF3.6) - * 1. Crash - * 2. Upgrade - * 3. Fresh install - * - *************************************************************************/ - -var EXPORTED_SYMBOLS = ["StartupObserver"]; - -const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); -const { XPCOMUtils } = ChromeUtils.import( - "resource://gre/modules/XPCOMUtils.jsm" -); - -const { TorProtocolService } = ChromeUtils.import( - "resource://gre/modules/TorProtocolService.jsm" -); - -const lazy = {}; - -XPCOMUtils.defineLazyModuleGetters(lazy, { - FileUtils: "resource://gre/modules/FileUtils.jsm", -}); - -function StartupObserver() { - this.logger = Cc["@torproject.org/torbutton-logger;1"].getService( - Ci.nsISupports - ).wrappedJSObject; - this._prefs = Services.prefs; - this.logger.log(3, "Startup Observer created"); - - try { - // XXX: We're in a race with HTTPS-Everywhere to update our proxy settings - // before the initial SSL-Observatory test... If we lose the race, Firefox - // caches the old proxy settings for check.tp.o somehwere, and it never loads :( - this.setProxySettings(); - } catch (e) { - this.logger.log( - 4, - "Early proxy change failed. Will try again at profile load. Error: " + e - ); - } -} - -StartupObserver.prototype = { - // Bug 6803: We need to get the env vars early due to - // some weird proxy caching code that showed up in FF15. - // Otherwise, homepage domain loads fail forever. - setProxySettings() { - // Bug 1506: Still want to get these env vars - if (Services.env.exists("TOR_TRANSPROXY")) { - this.logger.log(3, "Resetting Tor settings to transproxy"); - this._prefs.setBoolPref("network.proxy.socks_remote_dns", false); - this._prefs.setIntPref("network.proxy.type", 0); - this._prefs.setIntPref("network.proxy.socks_port", 0); - this._prefs.setCharPref("network.proxy.socks", ""); - } else { - // Try to retrieve SOCKS proxy settings from Tor Launcher. - let socksPortInfo; - try { - socksPortInfo = TorProtocolService.torGetSOCKSPortInfo(); - } catch (e) { - this.logger.log(3, "tor launcher failed " + e); - } - - // If Tor Launcher is not available, check environment variables. - if (!socksPortInfo) { - socksPortInfo = { ipcFile: undefined, host: undefined, port: 0 }; - - let isWindows = Services.appinfo.OS === "WINNT"; - if (!isWindows && Services.env.exists("TOR_SOCKS_IPC_PATH")) { - socksPortInfo.ipcFile = new lazy.FileUtils.File( - Services.env.get("TOR_SOCKS_IPC_PATH") - ); - } else { - if (Services.env.exists("TOR_SOCKS_HOST")) { - socksPortInfo.host = Services.env.get("TOR_SOCKS_HOST"); - } - if (Services.env.exists("TOR_SOCKS_PORT")) { - socksPortInfo.port = parseInt(Services.env.get("TOR_SOCKS_PORT")); - } - } - } - - // Adjust network.proxy prefs. - if (socksPortInfo.ipcFile) { - let fph = Services.io - .getProtocolHandler("file") - .QueryInterface(Ci.nsIFileProtocolHandler); - let fileURI = fph.newFileURI(socksPortInfo.ipcFile); - this.logger.log(3, "Reset socks to " + fileURI.spec); - this._prefs.setCharPref("network.proxy.socks", fileURI.spec); - this._prefs.setIntPref("network.proxy.socks_port", 0); - } else { - if (socksPortInfo.host) { - this._prefs.setCharPref("network.proxy.socks", socksPortInfo.host); - this.logger.log(3, "Reset socks host to " + socksPortInfo.host); - } - if (socksPortInfo.port) { - this._prefs.setIntPref( - "network.proxy.socks_port", - socksPortInfo.port - ); - this.logger.log(3, "Reset socks port to " + socksPortInfo.port); - } - } - - if (socksPortInfo.ipcFile || socksPortInfo.host || socksPortInfo.port) { - this._prefs.setBoolPref("network.proxy.socks_remote_dns", true); - this._prefs.setIntPref("network.proxy.type", 1); - } - } - - // Force prefs to be synced to disk - Services.prefs.savePrefFile(null); - - this.logger.log(3, "Synced network settings to environment."); - }, - - observe(subject, topic, data) { - if (topic == "profile-after-change") { - this.setProxySettings(); - } - - // In all cases, force prefs to be synced to disk - Services.prefs.savePrefFile(null); - }, - - // Hack to get us registered early to observe recovery - _xpcom_categories: [{ category: "profile-after-change" }], -};
===================================== toolkit/torbutton/moz.build ===================================== @@ -8,7 +8,3 @@ JAR_MANIFESTS += ['jar.mn'] XPCOM_MANIFESTS += [ "components.conf", ] - -EXTRA_COMPONENTS += [ - "torbutton.manifest", -]
===================================== toolkit/torbutton/torbutton.manifest deleted ===================================== @@ -1 +0,0 @@ -category profile-after-change StartupObserver @torproject.org/startup-observer;1
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/f0493f9...
tbb-commits@lists.torproject.org