Pier Angelo Vendrame pushed to branch tor-browser-115.6.0esr-13.5-1 at The Tor Project / Applications / Tor Browser
Commits: be025b30 by Pier Angelo Vendrame at 2024-01-08T14:38:58+01:00 fixup! Bug 40597: Implement TorSettings module
Convert TorSettings to an ES class.
- - - - - 440a0aae by Pier Angelo Vendrame at 2024-01-08T14:39:01+01:00 fixup! Bug 40597: Implement TorSettings module
Replace _ with # for private stuff in TorSettings.
- - - - - 08bc6b26 by Pier Angelo Vendrame at 2024-01-08T14:39:01+01:00 fixup! Bug 40597: Implement TorSettings module
Bug 42343: Read built-in bridges from pt_config.json instead of preferences.
- - - - - 28351513 by Pier Angelo Vendrame at 2024-01-08T14:39:02+01:00 fixup! Bug 31286: Implementation of bridge, proxy, and firewall settings in about:preferences#connection
Bug 42343: Read built-in bridges from pt_config.json instead of preferences.
Update the way in which we query the PTs we have bundled bridge lines for.
- - - - - 6e7488b0 by Pier Angelo Vendrame at 2024-01-08T14:39:02+01:00 fixup! Bug 40562: Added Tor Browser preferences to 000-tor-browser.js
Bug 42343: Read built-in bridges from pt_config.json instead of preferences.
Remove some preferences we do not use anymore and their documentation.
- - - - - 8da34d3a by Pier Angelo Vendrame at 2024-01-08T14:39:02+01:00 fixup! Bug 41089: Add tor-browser build scripts + Makefile to tor-browser
Bug 42343: Read built-in bridges from pt_config.json instead of preferences.
Do not copy the bridges anymore when doing the deploy.
- - - - - 51e8c714 by Pier Angelo Vendrame at 2024-01-08T14:39:03+01:00 fixup! Bug 40597: Implement TorSettings module
Added checks on TorSettings.initialized, and some documentation improvements.
- - - - - 7272b554 by Pier Angelo Vendrame at 2024-01-08T14:39:03+01:00 fixup! Bug 40597: Implement TorSettings module
Batch of changes requested in the MR.
- - - - -
9 changed files:
- browser/app/profile/000-tor-browser.js - browser/components/torpreferences/content/builtinBridgeDialog.mjs - browser/components/torpreferences/content/connectionPane.js - toolkit/content/jar.mn - + toolkit/content/pt_config.json - toolkit/modules/TorConnect.sys.mjs - toolkit/modules/TorSettings.sys.mjs - − tools/torbrowser/bridges.js - tools/torbrowser/deploy.sh
Changes:
===================================== browser/app/profile/000-tor-browser.js ===================================== @@ -69,7 +69,6 @@ pref("extensions.torbutton.pref_fixup_version", 0);
pref("extensions.torlauncher.start_tor", true); pref("extensions.torlauncher.prompt_at_startup", true); -pref("extensions.torlauncher.quickstart", false);
pref("extensions.torlauncher.max_tor_log_entries", 1000);
@@ -113,11 +112,3 @@ pref("extensions.torlauncher.tordatadir_path", ""); pref("extensions.torlauncher.bridgedb_front", "foursquare.com"); pref("extensions.torlauncher.bridgedb_reflector", "https://moat.torproject.org.global.prod.fastly.net/"); pref("extensions.torlauncher.moat_service", "https://bridges.torproject.org/moat"); -pref("extensions.torlauncher.bridgedb_bridge_type", "obfs4"); - -// Recommended default bridge type. -// pref("extensions.torlauncher.default_bridge_recommended_type", "obfs3"); - -// Default bridges. -// pref("extensions.torlauncher.default_bridge.TYPE.1", "TYPE x.x.x.x:yy"); -// pref("extensions.torlauncher.default_bridge.TYPE.2", "TYPE x.x.x.x:yy");
===================================== browser/components/torpreferences/content/builtinBridgeDialog.mjs ===================================== @@ -3,7 +3,6 @@ import { TorStrings } from "resource://gre/modules/TorStrings.sys.mjs"; import { TorSettings, TorBridgeSource, - TorBuiltinBridgeTypes, } from "resource://gre/modules/TorSettings.sys.mjs";
import { @@ -62,7 +61,7 @@ export class BuiltinBridgeDialog { )) { const radio = optionEl.querySelector("radio"); const type = radio.value; - optionEl.hidden = !TorBuiltinBridgeTypes.includes(type); + optionEl.hidden = !TorSettings.builtinBridgeTypes.includes(type); radio.label = typeStrings[type].label; optionEl.querySelector( ".builtin-bridges-option-description"
===================================== browser/components/torpreferences/content/connectionPane.js ===================================== @@ -867,7 +867,7 @@ const gConnectionPane = (function () { },
init() { - this._populateXUL(); + TorSettings.initializedPromise.then(() => this._populateXUL());
const onUnload = () => { window.removeEventListener("unload", onUnload);
===================================== toolkit/content/jar.mn ===================================== @@ -137,3 +137,5 @@ toolkit.jar: # Third party files content/global/third_party/d3/d3.js (/third_party/js/d3/d3.js) content/global/third_party/cfworker/json-schema.js (/third_party/js/cfworker/json-schema.js) + + content/global/pt_config.json (pt_config.json)
===================================== toolkit/content/pt_config.json ===================================== @@ -0,0 +1,32 @@ +{ + "_comment": "Used for dev build, replaced for release builds in tor-browser-build. This file is copied from tor-browser-build cb513eec:tor-expert-bundle/pt_config.json", + "recommendedDefault" : "obfs4", + "pluggableTransports" : { + "lyrebird" : "ClientTransportPlugin meek_lite,obfs2,obfs3,obfs4,scramblesuit exec ${pt_path}lyrebird${pt_extension}", + "snowflake" : "ClientTransportPlugin snowflake exec ${pt_path}snowflake-client${pt_extension}", + "webtunnel" : "ClientTransportPlugin webtunnel exec ${pt_path}webtunnel-client${pt_extension}", + "conjure" : "ClientTransportPlugin conjure exec ${pt_path}conjure-client${pt_extension} -registerURL https://registration.refraction.network/api" + }, + "bridges" : { + "meek-azure" : [ + "meek_lite 192.0.2.18:80 BE776A53492E1E044A26F17306E1BC46A55A1625 url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com" + ], + "obfs4" : [ + "obfs4 192.95.36.142:443 CDF2E852BF539B82BD10E27E9115A31734E378C2 cert=qUVQ0srL1JI/vO6V6m/24anYXiJD3QP2HgzUKQtQ7GRqqUvs7P+tG43RtAqdhLOALP7DJQ iat-mode=1", + "obfs4 37.218.245.14:38224 D9A82D2F9C2F65A18407B1D2B764F130847F8B5D cert=bjRaMrr1BRiAW8IE9U5z27fQaYgOhX1UCmOpg2pFpoMvo6ZgQMzLsaTzzQNTlm7hNcb+Sg iat-mode=0", + "obfs4 85.31.186.98:443 011F2599C0E9B27EE74B353155E244813763C3E5 cert=ayq0XzCwhpdysn5o0EyDUbmSOx3X/oTEbzDMvczHOdBJKlvIdHHLJGkZARtT4dcBFArPPg iat-mode=0", + "obfs4 85.31.186.26:443 91A6354697E6B02A386312F68D82CF86824D3606 cert=PBwr+S8JTVZo6MPdHnkTwXJPILWADLqfMGoVvhZClMq/Urndyd42BwX9YFJHZnBB3H0XCw iat-mode=0", + "obfs4 193.11.166.194:27015 2D82C2E354D531A68469ADF7F878FA6060C6BACA cert=4TLQPJrTSaDffMK7Nbao6LC7G9OW/NHkUwIdjLSS3KYf0Nv4/nQiiI8dY2TcsQx01NniOg iat-mode=0", + "obfs4 193.11.166.194:27020 86AC7B8D430DAC4117E9F42C9EAED18133863AAF cert=0LDeJH4JzMDtkJJrFphJCiPqKx7loozKN7VNfuukMGfHO0Z8OGdzHVkhVAOfo1mUdv9cMg iat-mode=0", + "obfs4 193.11.166.194:27025 1AE2C08904527FEA90C4C4F8C1083EA59FBC6FAF cert=ItvYZzW5tn6v3G4UnQa6Qz04Npro6e81AP70YujmK/KXwDFPTs3aHXcHp4n8Vt6w/bv8cA iat-mode=0", + "obfs4 209.148.46.65:443 74FAD13168806246602538555B5521A0383A1875 cert=ssH+9rP8dG2NLDN2XuFw63hIO/9MNNinLmxQDpVa+7kTOa9/m+tGWT1SmSYpQ9uTBGa6Hw iat-mode=0", + "obfs4 146.57.248.225:22 10A6CD36A537FCE513A322361547444B393989F0 cert=K1gDtDAIcUfeLqbstggjIw2rtgIKqdIhUlHp82XRqNSq/mtAjp1BIC9vHKJ2FAEpGssTPw iat-mode=0", + "obfs4 45.145.95.6:27015 C5B7CD6946FF10C5B3E89691A7D3F2C122D2117C cert=TD7PbUO0/0k6xYHMPW3vJxICfkMZNdkRrb63Zhl5j9dW3iRGiCx0A7mPhe5T2EDzQ35+Zw iat-mode=0", + "obfs4 51.222.13.177:80 5EDAC3B810E12B01F6FD8050D2FD3E277B289A08 cert=2uplIpLQ0q9+0qMFrK5pkaYRDOe460LL9WHBvatgkuRr/SL31wBOEupaMMJ6koRE6Ld0ew iat-mode=0" + ], + "snowflake" : [ + "snowflake 192.0.2.3:80 2B280B23E1107BB62ABFC40DDCC8824814F80A72 fingerprint=2B280B23E1107BB62ABFC40DDCC8824814F80A72 url=https://snowflake-broker.torproject.net.global.prod.fastly.net/ front=foursquare.com ice=stun:stun.l.google.com:19302,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.com:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478 utls-imitate=hellorandomizedalpn", + "snowflake 192.0.2.4:80 8838024498816A039FCBBAB14E6F40A0843051FA fingerprint=8838024498816A039FCBBAB14E6F40A0843051FA url=https://snowflake-broker.torproject.net.global.prod.fastly.net/ front=foursquare.com ice=stun:stun.l.google.com:19302,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.net:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478 utls-imitate=hellorandomizedalpn" + ] + } +}
===================================== toolkit/modules/TorConnect.sys.mjs ===================================== @@ -23,7 +23,6 @@ import { TorLauncherUtil } from "resource://gre/modules/TorLauncherUtil.sys.mjs" import { TorSettings, TorSettingsTopics, - TorBuiltinBridgeTypes, } from "resource://gre/modules/TorSettings.sys.mjs";
import { TorStrings } from "resource://gre/modules/TorStrings.sys.mjs"; @@ -609,7 +608,7 @@ export const TorConnect = (() => { }
const settings = await this.mrpc.circumvention_settings( - [...TorBuiltinBridgeTypes, "vanilla"], + [...TorSettings.builtinBridgeTypes, "vanilla"], countryCode );
@@ -625,7 +624,7 @@ export const TorConnect = (() => { } else { try { this.settings = await this.mrpc.circumvention_defaults([ - ...TorBuiltinBridgeTypes, + ...TorSettings.builtinBridgeTypes, "vanilla", ]); } catch (err) {
===================================== toolkit/modules/TorSettings.sys.mjs ===================================== @@ -67,16 +67,6 @@ const TorSettingsPrefs = Object.freeze({ }, });
-/* Legacy tor-launcher prefs and pref branches*/ -const TorLauncherPrefs = Object.freeze({ - quickstart: "extensions.torlauncher.quickstart", - default_bridge_type: "extensions.torlauncher.default_bridge_type", - default_bridge: "extensions.torlauncher.default_bridge.", - default_bridge_recommended_type: - "extensions.torlauncher.default_bridge_recommended_type", - bridgedb_bridge: "extensions.torlauncher.bridgedb_bridge.", -}); - /* Config Keys used to configure tor daemon */ const TorConfigKeys = Object.freeze({ useBridges: "UseBridges", @@ -105,101 +95,41 @@ export const TorProxyType = Object.freeze({ HTTPS: 2, });
-export const TorBuiltinBridgeTypes = Object.freeze( - (() => { - const bridgeListBranch = Services.prefs.getBranch( - TorLauncherPrefs.default_bridge - ); - const bridgePrefs = bridgeListBranch.getChildList(""); - - // an unordered set for shoving bridge types into - const bridgeTypes = new Set(); - // look for keys ending in ".N" and treat string before that as the bridge type - const pattern = /.[0-9]+$/; - for (const key of bridgePrefs) { - const offset = key.search(pattern); - if (offset != -1) { - const bt = key.substring(0, offset); - bridgeTypes.add(bt); - } - } - - // recommended bridge type goes first in the list - const recommendedBridgeType = Services.prefs.getCharPref( - TorLauncherPrefs.default_bridge_recommended_type, - null - ); - - const retval = []; - if (recommendedBridgeType && bridgeTypes.has(recommendedBridgeType)) { - retval.push(recommendedBridgeType); - } - - for (const bridgeType of bridgeTypes.values()) { - if (bridgeType != recommendedBridgeType) { - retval.push(bridgeType); - } - } - return retval; - })() -); - -/* Parsing Methods */ - -// expects a '\n' or '\r\n' delimited bridge string, which we split and trim -// each bridge string can also optionally have 'bridge' at the beginning ie: -// bridge $(type) $(address):$(port) $(certificate) -// we strip out the 'bridge' prefix here -const parseBridgeStrings = function (aBridgeStrings) { +/** + * Split a blob of bridge lines into an array with single lines. + * Lines are delimited by \r\n or \n and each bridge string can also optionally + * have 'bridge' at the beginning. + * We split the text by \r\n, we trim the lines, remove the bridge prefix and + * filter out any remaiing empty item. + * + * @param {string} aBridgeStrings The text with the lines + * @returns {string[]} An array where each bridge line is an item + */ +function parseBridgeStrings(aBridgeStrings) { // replace carriage returns ('\r') with new lines ('\n') aBridgeStrings = aBridgeStrings.replace(/\r/g, "\n"); // then replace contiguous new lines ('\n') with a single one aBridgeStrings = aBridgeStrings.replace(/[\n]+/g, "\n");
- // split on the newline and for each bridge string: trim, remove starting 'bridge' string - // finally discard entries that are empty strings; empty strings could occur if we receive - // a new line containing only whitespace + // Split on the newline and for each bridge string: trim, remove starting + // 'bridge' string. + // Finally, discard entries that are empty strings; empty strings could occur + // if we receive a new line containing only whitespace. const splitStrings = aBridgeStrings.split("\n"); return splitStrings .map(val => val.trim().replace(/^bridge\s+/i, "")) - .filter(bridgeString => bridgeString != ""); -}; - -const getBuiltinBridgeStrings = function (builtinType) { - if (!builtinType) { - return []; - } - - const bridgeBranch = Services.prefs.getBranch( - TorLauncherPrefs.default_bridge - ); - const bridgeBranchPrefs = bridgeBranch.getChildList(""); - const retval = []; - - // regex matches against strings ending in ".N" where N is a positive integer - const pattern = /.[0-9]+$/; - for (const key of bridgeBranchPrefs) { - // verify the location of the match is the correct offset required for aBridgeType - // to fit, and that the string begins with aBridgeType - if ( - key.search(pattern) == builtinType.length && - key.startsWith(builtinType) - ) { - const bridgeStr = bridgeBranch.getCharPref(key); - retval.push(bridgeStr); - } - } - - // shuffle so that Tor Browser users don't all try the built-in bridges in the same order - arrayShuffle(retval); - - return retval; -}; - -/* Helper methods */ - -const arrayShuffle = function (array) { - // fisher-yates shuffle + .filter(bridgeString => bridgeString !== ""); +} + +/** + * Return a shuffled (Fisher-Yates) copy of an array. + * + * @template T + * @param {T[]} array + * @returns {T[]} + */ +function arrayShuffle(array) { + array = [...array]; for (let i = array.length - 1; i > 0; --i) { // number n such that 0.0 <= n < 1.0 const n = Math.random(); @@ -211,17 +141,18 @@ const arrayShuffle = function (array) { array[i] = array[j]; array[j] = tmp; } -}; + return array; +}
/* TorSettings module */
-export const TorSettings = { +class TorSettingsImpl { /** * The underlying settings values. * * @type {object} */ - _settings: { + #settings = { quickstart: { enabled: false, }, @@ -243,34 +174,207 @@ export const TorSettings = { enabled: false, allowed_ports: [], }, - }, + }; + + /** + * The recommended pluggable transport. + * + * @type {string} + */ + #recommendedPT = ""; + + /** + * The bridge lines for built-in bridges. + * Keys are pluggable transports, and values are arrays of bridge lines. + * + * @type {Object.<string, string[]>} + */ + #builtinBridges = {}; + + /** + * Resolve callback of the initializedPromise. + */ + #initComplete; + /** + * Reject callback of the initializedPromise. + */ + #initFailed; + /** + * Tell whether the initializedPromise has been resolved. + * We keep this additional member to avoid making everything async. + * + * @type {boolean} + */ + #initialized = false; + /** + * During some phases of the initialization, allow calling setters and + * getters without throwing errors. + * + * @type {boolean} + */ + #allowUninitialized = false; + + constructor() { + this.initializedPromise = new Promise((resolve, reject) => { + this.#initComplete = resolve; + this.#initFailed = reject; + }); + + this.#addProperties("quickstart", { + enabled: {}, + }); + this.#addProperties("bridges", { + enabled: {}, + source: { + transform: val => { + if (Object.values(TorBridgeSource).includes(val)) { + return val; + } + lazy.logger.error(`Not a valid bridge source: "${val}"`); + return TorBridgeSource.Invalid; + }, + }, + bridge_strings: { + transform: val => { + if (Array.isArray(val)) { + return [...val]; + } + return parseBridgeStrings(val); + }, + copy: val => [...val], + equal: (val1, val2) => this.#arrayEqual(val1, val2), + }, + builtin_type: { + callback: val => { + if (!val) { + // Make sure that the source is not BuiltIn + if (this.bridges.source === TorBridgeSource.BuiltIn) { + this.bridges.source = TorBridgeSource.Invalid; + } + return; + } + const bridgeStrings = this.#getBuiltinBridges(val); + if (bridgeStrings.length) { + this.bridges.bridge_strings = bridgeStrings; + return; + } + lazy.logger.error(`No built-in ${val} bridges found`); + // Change to be empty, this will trigger this callback again, + // but with val as "". + this.bridges.builtin_type == ""; + }, + }, + }); + this.#addProperties("proxy", { + enabled: { + callback: val => { + if (val) { + return; + } + // Reset proxy settings. + this.proxy.type = TorProxyType.Invalid; + this.proxy.address = ""; + this.proxy.port = 0; + this.proxy.username = ""; + this.proxy.password = ""; + }, + }, + type: { + transform: val => { + if (Object.values(TorProxyType).includes(val)) { + return val; + } + lazy.logger.error(`Not a valid proxy type: "${val}"`); + return TorProxyType.Invalid; + }, + }, + address: {}, + port: { + transform: val => { + if (val === 0) { + // This is a valid value that "unsets" the port. + // Keep this value without giving a warning. + // NOTE: In contrast, "0" is not valid. + return 0; + } + // Unset to 0 if invalid null is returned. + return this.#parsePort(val, false) ?? 0; + }, + }, + username: {}, + password: {}, + uri: { + getter: () => { + const { type, address, port, username, password } = this.proxy; + switch (type) { + case TorProxyType.Socks4: + return `socks4a://${address}:${port}`; + case TorProxyType.Socks5: + if (username) { + return `socks5://${username}:${password}@${address}:${port}`; + } + return `socks5://${address}:${port}`; + case TorProxyType.HTTPS: + if (username) { + return `http://$%7Busername%7D:$%7Bpassword%7D@$%7Baddress%7D:$%7Bport%7D%60; + } + return `http://$%7Baddress%7D:$%7Bport%7D%60; + } + return null; + }, + }, + }); + this.#addProperties("firewall", { + enabled: { + callback: val => { + if (!val) { + this.firewall.allowed_ports = ""; + } + }, + }, + allowed_ports: { + transform: val => { + if (!Array.isArray(val)) { + val = val === "" ? [] : val.split(","); + } + // parse and remove duplicates + const portSet = new Set(val.map(p => this.#parsePort(p, true))); + // parsePort returns null for failed parses, so remove it. + portSet.delete(null); + return [...portSet]; + }, + copy: val => [...val], + equal: (val1, val2) => this.#arrayEqual(val1, val2), + }, + }); + }
/** * The current number of freezes applied to the notifications. * * @type {integer} */ - _freezeNotificationsCount: 0, + #freezeNotificationsCount = 0; /** * The queue for settings that have changed. To be broadcast in the * notification when not frozen. * * @type {Set<string>} */ - _notificationQueue: new Set(), + #notificationQueue = new Set(); /** * Send a notification if we have any queued and we are not frozen. */ - _tryNotification() { - if (this._freezeNotificationsCount || !this._notificationQueue.size) { + #tryNotification() { + if (this.#freezeNotificationsCount || !this.#notificationQueue.size) { return; } Services.obs.notifyObservers( - { changes: [...this._notificationQueue] }, + { changes: [...this.#notificationQueue] }, TorSettingsTopics.SettingsChanged ); - this._notificationQueue.clear(); - }, + this.#notificationQueue.clear(); + } /** * Pause notifications for changes in setting values. This is useful if you * need to make batch changes to settings. @@ -281,8 +385,8 @@ export const TorSettings = { * `finally` block. */ freezeNotifications() { - this._freezeNotificationsCount++; - }, + this.#freezeNotificationsCount++; + } /** * Release the hold on notifications so they may be sent out. * @@ -290,9 +394,9 @@ export const TorSettings = { * only release them once it has also called this method. */ thawNotifications() { - this._freezeNotificationsCount--; - this._tryNotification(); - }, + this.#freezeNotificationsCount--; + this.#tryNotification(); + } /** * @typedef {object} TorSettingProperty * @@ -316,22 +420,32 @@ export const TorSettings = { * @param {string} groupname - The name of the setting group. The given * settings will be accessible from the TorSettings property of the same * name. - * @param {object<string, TorSettingProperty>} propParams - An object that + * @param {object.<string, TorSettingProperty>} propParams - An object that * defines the settings to add to this group. The object property names * will be mapped to properties of TorSettings under the given groupname * property. Details about the setting should be described in the * TorSettingProperty property value. */ - _addProperties(groupname, propParams) { + #addProperties(groupname, propParams) { // Create a new object to hold all these settings. const group = {}; for (const name in propParams) { const { getter, transform, callback, copy, equal } = propParams[name]; Object.defineProperty(group, name, { get: getter - ? getter + ? () => { + // Allow getting in loadFromPrefs before we are initialized. + if (!this.#allowUninitialized) { + this.#checkIfInitialized(); + } + return getter(); + } : () => { - let val = this._settings[groupname][name]; + // Allow getting in loadFromPrefs before we are initialized. + if (!this.#allowUninitialized) { + this.#checkIfInitialized(); + } + let val = this.#settings[groupname][name]; if (copy) { val = copy(val); } @@ -341,7 +455,11 @@ export const TorSettings = { set: getter ? undefined : val => { - const prevVal = this._settings[groupname][name]; + // Allow setting in loadFromPrefs before we are initialized. + if (!this.#allowUninitialized) { + this.#checkIfInitialized(); + } + const prevVal = this.#settings[groupname][name]; this.freezeNotifications(); try { if (transform) { @@ -352,8 +470,8 @@ export const TorSettings = { if (callback) { callback(val); } - this._settings[groupname][name] = val; - this._notificationQueue.add(`${groupname}.${name}`); + this.#settings[groupname][name] = val; + this.#notificationQueue.add(`${groupname}.${name}`); } } finally { this.thawNotifications(); @@ -367,14 +485,14 @@ export const TorSettings = { writable: false, value: group, }); - }, + }
/** * Regular expression for a decimal non-negative integer. * * @type {RegExp} */ - _portRegex: /^[0-9]+$/, + #portRegex = /^[0-9]+$/; /** * Parse a string as a port number. * @@ -385,13 +503,13 @@ export const TorSettings = { * @return {integer?} - The port number, or null if the given value was not * valid. */ - _parsePort(val, trim) { + #parsePort(val, trim) { if (typeof val === "string") { if (trim) { val = val.trim(); } // ensure port string is a valid positive integer - if (this._portRegex.test(val)) { + if (this.#portRegex.test(val)) { val = Number.parseInt(val, 10); } else { lazy.logger.error(`Invalid port string "${val}"`); @@ -403,7 +521,7 @@ export const TorSettings = { return null; } return val; - }, + } /** * Test whether two arrays have equal members and order. * @@ -412,142 +530,57 @@ export const TorSettings = { * * @return {boolean} - Whether the two arrays are equal. */ - _arrayEqual(val1, val2) { + #arrayEqual(val1, val2) { if (val1.length !== val2.length) { return false; } return val1.every((v, i) => v === val2[i]); - }, + } + + /** + * Return the bridge lines associated to a certain pluggable transport. + * + * @param {string} pt The pluggable transport to return the lines for + * @returns {string[]} The bridge lines in random order + */ + #getBuiltinBridges(pt) { + // Shuffle so that Tor Browser users do not all try the built-in bridges in + // the same order. + return arrayShuffle(this.#builtinBridges[pt] ?? []); + }
- /* load or init our settings, and register observers */ + /** + * Load or init our settings, and register observers. + */ async init() { - this._addProperties("quickstart", { - enabled: {}, - }); - this._addProperties("bridges", { - enabled: {}, - source: { - transform: val => { - if (Object.values(TorBridgeSource).includes(val)) { - return val; - } - lazy.logger.error(`Not a valid bridge source: "${val}"`); - return TorBridgeSource.Invalid; - }, - }, - bridge_strings: { - transform: val => { - if (Array.isArray(val)) { - return [...val]; - } - return parseBridgeStrings(val); - }, - copy: val => [...val], - equal: (val1, val2) => this._arrayEqual(val1, val2), - }, - builtin_type: { - callback: val => { - if (!val) { - // Make sure that the source is not BuiltIn - if (this.bridges.source === TorBridgeSource.BuiltIn) { - this.bridges.source = TorBridgeSource.Invalid; - } - return; - } - const bridgeStrings = getBuiltinBridgeStrings(val); - if (bridgeStrings.length) { - this.bridges.bridge_strings = bridgeStrings; - return; - } - lazy.logger.error(`No built-in ${val} bridges found`); - // Change to be empty, this will trigger this callback again, - // but with val as "". - this.bridges.builtin_type == ""; - }, - }, - }); - this._addProperties("proxy", { - enabled: { - callback: val => { - if (val) { - return; - } - // Reset proxy settings. - this.proxy.type = TorProxyType.Invalid; - this.proxy.address = ""; - this.proxy.port = 0; - this.proxy.username = ""; - this.proxy.password = ""; - }, - }, - type: { - transform: val => { - if (Object.values(TorProxyType).includes(val)) { - return val; - } - lazy.logger.error(`Not a valid proxy type: "${val}"`); - return TorProxyType.Invalid; - }, - }, - address: {}, - port: { - transform: val => { - if (val === 0) { - // This is a valid value that "unsets" the port. - // Keep this value without giving a warning. - // NOTE: In contrast, "0" is not valid. - return 0; - } - // Unset to 0 if invalid null is returned. - return this._parsePort(val, false) ?? 0; - }, - }, - username: {}, - password: {}, - uri: { - getter: () => { - const { type, address, port, username, password } = this.proxy; - switch (type) { - case TorProxyType.Socks4: - return `socks4a://${address}:${port}`; - case TorProxyType.Socks5: - if (username) { - return `socks5://${username}:${password}@${address}:${port}`; - } - return `socks5://${address}:${port}`; - case TorProxyType.HTTPS: - if (username) { - return `http://$%7Busername%7D:$%7Bpassword%7D@$%7Baddress%7D:$%7Bport%7D%60; - } - return `http://$%7Baddress%7D:$%7Bport%7D%60; - } - return null; - }, - }, - }); - this._addProperties("firewall", { - enabled: { - callback: val => { - if (!val) { - this.firewall.allowed_ports = ""; - } - }, - }, - allowed_ports: { - transform: val => { - if (!Array.isArray(val)) { - val = val === "" ? [] : val.split(","); - } - // parse and remove duplicates - const portSet = new Set(val.map(p => this._parsePort(p, true))); - // parsePort returns null for failed parses, so remove it. - portSet.delete(null); - return [...portSet]; - }, - copy: val => [...val], - equal: (val1, val2) => this._arrayEqual(val1, val2), - }, - }); + if (this.#initialized) { + lazy.logger.warn("Called init twice."); + return; + } + try { + await this.#initInternal(); + this.#initialized = true; + this.#initComplete(); + } catch (e) { + this.#initFailed(e); + throw e; + } + } + + /** + * The actual implementation of the initialization, which is wrapped to make + * it easier to update initializatedPromise. + */ + async #initInternal() { + try { + const req = await fetch("chrome://global/content/pt_config.json"); + const config = await req.json(); + lazy.logger.debug("Loaded pt_config.json", config); + this.#recommendedPT = config.recommendedDefault; + this.#builtinBridges = config.bridges; + } catch (e) { + lazy.logger.error("Could not load the built-in PT config.", e); + }
// TODO: We could use a shared promise, and wait for it to be fullfilled // instead of Service.obs. @@ -557,16 +590,18 @@ export const TorSettings = { // Do not want notifications for initially loaded prefs. this.freezeNotifications(); try { - this.loadFromPrefs(); + this.#allowUninitialized = true; + this.#loadFromPrefs(); } finally { - this._notificationQueue.clear(); + this.#allowUninitialized = false; + this.#notificationQueue.clear(); this.thawNotifications(); } } try { const provider = await lazy.TorProviderBuilder.build(); if (provider.isRunning) { - this.handleProcessReady(); + this.#handleProcessReady(); // No need to add an observer to call this again. return; } @@ -574,9 +609,33 @@ export const TorSettings = {
Services.obs.addObserver(this, lazy.TorProviderTopics.ProcessIsReady); } - }, + } + + /** + * Check whether the object has been successfully initialized, and throw if + * it has not. + */ + #checkIfInitialized() { + if (!this.#initialized) { + lazy.logger.trace("Not initialized code path."); + throw new Error( + "TorSettings has not been initialized yet, or its initialization failed" + ); + } + } + + /** + * Tell whether TorSettings has been successfully initialized. + * + * @returns {boolean} + */ + get initialized() { + return this.#initialized; + }
- /* wait for relevant life-cycle events to apply saved settings */ + /** + * Wait for relevant life-cycle events to apply saved settings. + */ async observe(subject, topic, data) { lazy.logger.debug(`Observed ${topic}`);
@@ -586,21 +645,26 @@ export const TorSettings = { this, lazy.TorProviderTopics.ProcessIsReady ); - await this.handleProcessReady(); + await this.#handleProcessReady(); break; } - }, + }
- // once the tor daemon is ready, we need to apply our settings - async handleProcessReady() { + /** + * Apply the settings once the tor provider is ready and notify any observer + * that the settings can be used. + */ + async #handleProcessReady() { // push down settings to tor - await this.applySettings(); + await this.#applySettings(true); lazy.logger.info("Ready"); Services.obs.notifyObservers(null, TorSettingsTopics.Ready); - }, + }
- // load our settings from prefs - loadFromPrefs() { + /** + * Load our settings from prefs. + */ + #loadFromPrefs() { lazy.logger.debug("loadFromPrefs()");
/* Quickstart */ @@ -671,12 +735,16 @@ export const TorSettings = { "" ); } - }, + }
- // save our settings to prefs + /** + * Save our settings to prefs. + */ saveToPrefs() { lazy.logger.debug("saveToPrefs()");
+ this.#checkIfInitialized(); + /* Quickstart */ Services.prefs.setBoolPref( TorSettingsPrefs.quickstart.enabled, @@ -758,77 +826,100 @@ export const TorSettings = { Services.prefs.setBoolPref(TorSettingsPrefs.enabled, true);
return this; - }, + }
- // push our settings down to the tor daemon + /** + * Push our settings down to the tor provider. + */ async applySettings() { - lazy.logger.debug("applySettings()"); + this.#checkIfInitialized(); + return this.#applySettings(false); + } + + /** + * Internal implementation of applySettings that does not check if we are + * initialized. + */ + async #applySettings(allowUninitialized) { + lazy.logger.debug("#applySettings()"); + const settingsMap = new Map();
- /* Bridges */ - const haveBridges = - this.bridges.enabled && !!this.bridges.bridge_strings.length; - settingsMap.set(TorConfigKeys.useBridges, haveBridges); - if (haveBridges) { - settingsMap.set(TorConfigKeys.bridgeList, this.bridges.bridge_strings); - } else { - settingsMap.set(TorConfigKeys.bridgeList, null); - } + // #applySettings can be called only when #allowUninitialized is false + this.#allowUninitialized = allowUninitialized;
- /* Proxy */ - settingsMap.set(TorConfigKeys.socks4Proxy, null); - settingsMap.set(TorConfigKeys.socks5Proxy, null); - settingsMap.set(TorConfigKeys.socks5ProxyUsername, null); - settingsMap.set(TorConfigKeys.socks5ProxyPassword, null); - settingsMap.set(TorConfigKeys.httpsProxy, null); - settingsMap.set(TorConfigKeys.httpsProxyAuthenticator, null); - if (this.proxy.enabled) { - const address = this.proxy.address; - const port = this.proxy.port; - const username = this.proxy.username; - const password = this.proxy.password; - - switch (this.proxy.type) { - case TorProxyType.Socks4: - settingsMap.set(TorConfigKeys.socks4Proxy, `${address}:${port}`); - break; - case TorProxyType.Socks5: - settingsMap.set(TorConfigKeys.socks5Proxy, `${address}:${port}`); - settingsMap.set(TorConfigKeys.socks5ProxyUsername, username); - settingsMap.set(TorConfigKeys.socks5ProxyPassword, password); - break; - case TorProxyType.HTTPS: - settingsMap.set(TorConfigKeys.httpsProxy, `${address}:${port}`); - settingsMap.set( - TorConfigKeys.httpsProxyAuthenticator, - `${username}:${password}` - ); - break; + try { + /* Bridges */ + const haveBridges = + this.bridges.enabled && !!this.bridges.bridge_strings.length; + settingsMap.set(TorConfigKeys.useBridges, haveBridges); + if (haveBridges) { + settingsMap.set(TorConfigKeys.bridgeList, this.bridges.bridge_strings); + } else { + settingsMap.set(TorConfigKeys.bridgeList, null); } - }
- /* Firewall */ - if (this.firewall.enabled) { - const reachableAddresses = this.firewall.allowed_ports - .map(port => `*:${port}`) - .join(","); - settingsMap.set(TorConfigKeys.reachableAddresses, reachableAddresses); - } else { - settingsMap.set(TorConfigKeys.reachableAddresses, null); + /* Proxy */ + settingsMap.set(TorConfigKeys.socks4Proxy, null); + settingsMap.set(TorConfigKeys.socks5Proxy, null); + settingsMap.set(TorConfigKeys.socks5ProxyUsername, null); + settingsMap.set(TorConfigKeys.socks5ProxyPassword, null); + settingsMap.set(TorConfigKeys.httpsProxy, null); + settingsMap.set(TorConfigKeys.httpsProxyAuthenticator, null); + if (this.proxy.enabled) { + const address = this.proxy.address; + const port = this.proxy.port; + const username = this.proxy.username; + const password = this.proxy.password; + + switch (this.proxy.type) { + case TorProxyType.Socks4: + settingsMap.set(TorConfigKeys.socks4Proxy, `${address}:${port}`); + break; + case TorProxyType.Socks5: + settingsMap.set(TorConfigKeys.socks5Proxy, `${address}:${port}`); + settingsMap.set(TorConfigKeys.socks5ProxyUsername, username); + settingsMap.set(TorConfigKeys.socks5ProxyPassword, password); + break; + case TorProxyType.HTTPS: + settingsMap.set(TorConfigKeys.httpsProxy, `${address}:${port}`); + settingsMap.set( + TorConfigKeys.httpsProxyAuthenticator, + `${username}:${password}` + ); + break; + } + } + + /* Firewall */ + if (this.firewall.enabled) { + const reachableAddresses = this.firewall.allowed_ports + .map(port => `*:${port}`) + .join(","); + settingsMap.set(TorConfigKeys.reachableAddresses, reachableAddresses); + } else { + settingsMap.set(TorConfigKeys.reachableAddresses, null); + } + } finally { + this.#allowUninitialized = false; }
/* Push to Tor */ const provider = await lazy.TorProviderBuilder.build(); await provider.writeSettings(settingsMap); + }
- return this; - }, - - // set all of our settings at once from a settings object + /** + * Set all of our settings at once from a settings object. + * + * @param {object} settings The settings object to set + */ setSettings(settings) { lazy.logger.debug("setSettings()"); + this.#checkIfInitialized(); + const backup = this.getSettings(); - const backup_notifications = [...this._notificationQueue]; + const backupNotifications = [...this.#notificationQueue];
// Hold off on lots of notifications until all settings are changed. this.freezeNotifications(); @@ -869,10 +960,10 @@ export const TorSettings = { // some other call to TorSettings to change anything whilst we are // in this context (other than lower down in this call stack), so it is // safe to discard all changes to settings and notifications. - this._settings = backup; - this._notificationQueue.clear(); - for (const notification of backup_notifications) { - this._notificationQueue.add(notification); + this.#settings = backup; + this.#notificationQueue.clear(); + for (const notification of backupNotifications) { + this.#notificationQueue.add(notification); }
lazy.logger.error("setSettings failed", ex); @@ -880,12 +971,36 @@ export const TorSettings = { this.thawNotifications(); }
- lazy.logger.debug("setSettings result", this._settings); - }, + lazy.logger.debug("setSettings result", this.#settings); + }
- // get a copy of all our settings + /** + * Get a copy of all our settings. + * + * @returns {object} A copy of the settings object + */ getSettings() { lazy.logger.debug("getSettings()"); - return structuredClone(this._settings); - }, -}; + this.#checkIfInitialized(); + return structuredClone(this.#settings); + } + + /** + * Return an array with the pluggable transports for which we have at least a + * built-in bridge line. + * + * @returns {string[]} An array with PT identifiers + */ + get builtinBridgeTypes() { + this.#checkIfInitialized(); + const types = Object.keys(this.#builtinBridges); + const recommendedIndex = types.indexOf(this.#recommendedPT); + if (recommendedIndex > 0) { + types.splice(recommendedIndex, 1); + types.unshift(this.#recommendedPT); + } + return types; + } +} + +export const TorSettings = new TorSettingsImpl();
===================================== tools/torbrowser/bridges.js deleted ===================================== @@ -1,62 +0,0 @@ -pref("extensions.torlauncher.default_bridge_recommended_type", "obfs4"); - -// Default bridges. -pref( - "extensions.torlauncher.default_bridge.obfs4.1", - "obfs4 192.95.36.142:443 CDF2E852BF539B82BD10E27E9115A31734E378C2 cert=qUVQ0srL1JI/vO6V6m/24anYXiJD3QP2HgzUKQtQ7GRqqUvs7P+tG43RtAqdhLOALP7DJQ iat-mode=1" -); -pref( - "extensions.torlauncher.default_bridge.obfs4.2", - "obfs4 37.218.245.14:38224 D9A82D2F9C2F65A18407B1D2B764F130847F8B5D cert=bjRaMrr1BRiAW8IE9U5z27fQaYgOhX1UCmOpg2pFpoMvo6ZgQMzLsaTzzQNTlm7hNcb+Sg iat-mode=0" -); -pref( - "extensions.torlauncher.default_bridge.obfs4.3", - "obfs4 85.31.186.98:443 011F2599C0E9B27EE74B353155E244813763C3E5 cert=ayq0XzCwhpdysn5o0EyDUbmSOx3X/oTEbzDMvczHOdBJKlvIdHHLJGkZARtT4dcBFArPPg iat-mode=0" -); -pref( - "extensions.torlauncher.default_bridge.obfs4.4", - "obfs4 85.31.186.26:443 91A6354697E6B02A386312F68D82CF86824D3606 cert=PBwr+S8JTVZo6MPdHnkTwXJPILWADLqfMGoVvhZClMq/Urndyd42BwX9YFJHZnBB3H0XCw iat-mode=0" -); -pref( - "extensions.torlauncher.default_bridge.obfs4.5", - "obfs4 193.11.166.194:27015 2D82C2E354D531A68469ADF7F878FA6060C6BACA cert=4TLQPJrTSaDffMK7Nbao6LC7G9OW/NHkUwIdjLSS3KYf0Nv4/nQiiI8dY2TcsQx01NniOg iat-mode=0" -); -pref( - "extensions.torlauncher.default_bridge.obfs4.6", - "obfs4 193.11.166.194:27020 86AC7B8D430DAC4117E9F42C9EAED18133863AAF cert=0LDeJH4JzMDtkJJrFphJCiPqKx7loozKN7VNfuukMGfHO0Z8OGdzHVkhVAOfo1mUdv9cMg iat-mode=0" -); -pref( - "extensions.torlauncher.default_bridge.obfs4.7", - "obfs4 193.11.166.194:27025 1AE2C08904527FEA90C4C4F8C1083EA59FBC6FAF cert=ItvYZzW5tn6v3G4UnQa6Qz04Npro6e81AP70YujmK/KXwDFPTs3aHXcHp4n8Vt6w/bv8cA iat-mode=0" -); -pref( - "extensions.torlauncher.default_bridge.obfs4.8", - "obfs4 209.148.46.65:443 74FAD13168806246602538555B5521A0383A1875 cert=ssH+9rP8dG2NLDN2XuFw63hIO/9MNNinLmxQDpVa+7kTOa9/m+tGWT1SmSYpQ9uTBGa6Hw iat-mode=0" -); -pref( - "extensions.torlauncher.default_bridge.obfs4.9", - "obfs4 146.57.248.225:22 10A6CD36A537FCE513A322361547444B393989F0 cert=K1gDtDAIcUfeLqbstggjIw2rtgIKqdIhUlHp82XRqNSq/mtAjp1BIC9vHKJ2FAEpGssTPw iat-mode=0" -); -pref( - "extensions.torlauncher.default_bridge.obfs4.10", - "obfs4 45.145.95.6:27015 C5B7CD6946FF10C5B3E89691A7D3F2C122D2117C cert=TD7PbUO0/0k6xYHMPW3vJxICfkMZNdkRrb63Zhl5j9dW3iRGiCx0A7mPhe5T2EDzQ35+Zw iat-mode=0" -); -pref( - "extensions.torlauncher.default_bridge.obfs4.11", - "obfs4 51.222.13.177:80 5EDAC3B810E12B01F6FD8050D2FD3E277B289A08 cert=2uplIpLQ0q9+0qMFrK5pkaYRDOe460LL9WHBvatgkuRr/SL31wBOEupaMMJ6koRE6Ld0ew iat-mode=0" -); - -pref( - "extensions.torlauncher.default_bridge.meek-azure.1", - "meek_lite 192.0.2.18:80 BE776A53492E1E044A26F17306E1BC46A55A1625 url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com" -); - -pref( - "extensions.torlauncher.default_bridge.snowflake.1", - "snowflake 192.0.2.3:80 2B280B23E1107BB62ABFC40DDCC8824814F80A72 fingerprint=2B280B23E1107BB62ABFC40DDCC8824814F80A72 url=https://snowflake-broker.torproject.net.global.prod.fastly.net/ front=cdn.sstatic.net ice=stun:stun.l.google.com:19302,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.com:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478 utls-imitate=hellorandomizedalpn" -); - -pref( - "extensions.torlauncher.default_bridge.snowflake.2", - "snowflake 192.0.2.4:80 8838024498816A039FCBBAB14E6F40A0843051FA fingerprint=8838024498816A039FCBBAB14E6F40A0843051FA url=https://snowflake-broker.torproject.net.global.prod.fastly.net/ front=cdn.sstatic.net ice=stun:stun.l.google.com:19302,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.net:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478 utls-imitate=hellorandomizedalpn" -);
===================================== tools/torbrowser/deploy.sh ===================================== @@ -6,17 +6,10 @@ BUILD_OUTPUT="$2" SCRIPT_DIR="$(realpath "$(dirname "$0")")"
RESDIR="$BUILD_OUTPUT/dist/firefox" -if [ "$(uname)" = "Darwin" ]; then +if [ "$(uname)" = "Darwin" ]; then RESDIR="$RESDIR/Tor Browser.app/Contents/Resources" fi
-# Add built-in bridges -mkdir -p "$BUILD_OUTPUT/_omni/defaults/preferences" -cat "$BUILD_OUTPUT/dist/bin/browser/defaults/preferences/000-tor-browser.js" "$SCRIPT_DIR/bridges.js" >> "$BUILD_OUTPUT/_omni/defaults/preferences/000-tor-browser.js" -cd "$BUILD_OUTPUT/_omni" -zip -Xmr "$RESDIR/browser/omni.ja" "defaults/preferences/000-tor-browser.js" -rm -rf "$BUILD_OUTPUT/_omni" - # Repackage the manual # rm -rf $BUILD_OUTPUT/_omni # mkdir $BUILD_OUTPUT/_omni @@ -34,12 +27,12 @@ if [ "$(uname)" = "Darwin" ]; then cd "$BINARIES/Tor Browser.app/Contents/MacOS" "$SCRIPT_DIR/browser-self-sign-macos.sh"
- else +else
# backup the startup script mv "$BINARIES/dev/Browser/firefox" "$BINARIES/dev/Browser/firefox.bak" - - # copy binaries + + # copy binaries cp -r "$RESDIR/"* "$BINARIES/dev/Browser" rm -rf "$BINARIES/dev/Browser/TorBrowser/Data/Browser/profile.default/startupCache"
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/0d07d3a...