tor-commits
Threads by month
- ----- 2025 -----
- July
- June
- May
- April
- March
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
August 2020
- 18 participants
- 1961 discussions

[tor-browser/tor-browser-78.1.0esr-10.0-1] Bug 27604: Fix addon issues when moving TB directory
by gk@torproject.org 07 Aug '20
by gk@torproject.org 07 Aug '20
07 Aug '20
commit b39391e65c10f7fd552a318d9f095cf68d9ae8ae
Author: Alex Catarineu <acat(a)torproject.org>
Date: Wed Oct 30 10:44:48 2019 +0100
Bug 27604: Fix addon issues when moving TB directory
---
toolkit/mozapps/extensions/internal/XPIProvider.jsm | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/toolkit/mozapps/extensions/internal/XPIProvider.jsm b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
index bf31932b59f1..5e467fb4f14c 100644
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -485,7 +485,7 @@ class XPIState {
// Builds prior to be 1512436 did not include the rootURI property.
// If we're updating from such a build, add that property now.
- if (!("rootURI" in this) && this.file) {
+ if (this.file) {
this.rootURI = getURIForResourceInFile(this.file, "").spec;
}
@@ -498,7 +498,10 @@ class XPIState {
saved.currentModifiedTime != this.lastModifiedTime
) {
this.lastModifiedTime = saved.currentModifiedTime;
- } else if (saved.currentModifiedTime === null) {
+ } else if (
+ saved.currentModifiedTime === null &&
+ (!this.file || !this.file.exists())
+ ) {
this.missing = true;
}
}
@@ -1439,6 +1442,7 @@ var XPIStates = {
if (oldState[loc.name]) {
loc.restore(oldState[loc.name]);
+ changed = changed || loc.path != oldState[loc.name].path;
}
changed = changed || loc.changed;
1
0

[tor-browser/tor-browser-78.1.0esr-10.0-1] Bug 31286: Implementation of bridge, proxy, and firewall settings in about:preferences#tor
by gk@torproject.org 07 Aug '20
by gk@torproject.org 07 Aug '20
07 Aug '20
commit 9e803c96c4c303a90113b027ae99492db1e18a07
Author: Richard Pospesel <richard(a)torproject.org>
Date: Mon Sep 16 15:25:39 2019 -0700
Bug 31286: Implementation of bridge, proxy, and firewall settings in about:preferences#tor
This patch adds a new about:preferences#tor page which allows modifying
bridge, proxy, and firewall settings from within Tor Browser. All of the
functionality present in tor-launcher's Network Configuration panel is
present:
- Setting built-in bridges
- Requesting bridges from BridgeDB via moat
- Using user-provided bridges
- Configuring SOCKS4, SOCKS5, and HTTP/HTTPS proxies
- Setting firewall ports
- Viewing and Copying Tor's logs
- The Networking Settings in General preferences has been removed
---
browser/components/moz.build | 1 +
browser/components/preferences/main.inc.xhtml | 55 --
browser/components/preferences/main.js | 14 -
browser/components/preferences/preferences.js | 9 +
browser/components/preferences/preferences.xhtml | 5 +
browser/components/preferences/privacy.js | 1 +
.../torpreferences/content/parseFunctions.jsm | 89 +++
.../torpreferences/content/requestBridgeDialog.jsm | 220 ++++++
.../content/requestBridgeDialog.xhtml | 35 +
.../torpreferences/content/torBridgeSettings.jsm | 325 ++++++++
.../torpreferences/content/torCategory.inc.xhtml | 9 +
.../torpreferences/content/torFirewallSettings.jsm | 72 ++
.../torpreferences/content/torLogDialog.jsm | 65 ++
.../torpreferences/content/torLogDialog.xhtml | 22 +
.../components/torpreferences/content/torPane.js | 857 +++++++++++++++++++++
.../torpreferences/content/torPane.xhtml | 123 +++
.../torpreferences/content/torPreferences.css | 77 ++
.../torpreferences/content/torPreferencesIcon.svg | 5 +
.../torpreferences/content/torProxySettings.jsm | 245 ++++++
browser/components/torpreferences/jar.mn | 14 +
browser/components/torpreferences/moz.build | 1 +
browser/modules/BridgeDB.jsm | 110 +++
browser/modules/TorProtocolService.jsm | 212 +++++
browser/modules/moz.build | 2 +
24 files changed, 2499 insertions(+), 69 deletions(-)
diff --git a/browser/components/moz.build b/browser/components/moz.build
index cb6eeb9164ef..09e209dc9c3b 100644
--- a/browser/components/moz.build
+++ b/browser/components/moz.build
@@ -58,6 +58,7 @@ DIRS += [
'syncedtabs',
'uitour',
'urlbar',
+ 'torpreferences',
'translation',
]
diff --git a/browser/components/preferences/main.inc.xhtml b/browser/components/preferences/main.inc.xhtml
index f3502e87af98..37ac50ee940b 100644
--- a/browser/components/preferences/main.inc.xhtml
+++ b/browser/components/preferences/main.inc.xhtml
@@ -676,59 +676,4 @@
<label id="cfrFeaturesLearnMore" class="learnMore" data-l10n-id="browsing-cfr-recommendations-learn-more" is="text-link"/>
</hbox>
</groupbox>
-
-<hbox id="networkProxyCategory"
- class="subcategory"
- hidden="true"
- data-category="paneGeneral">
- <html:h1 data-l10n-id="network-settings-title"/>
-</hbox>
-
-<!-- Network Settings-->
-<groupbox id="connectionGroup" data-category="paneGeneral" hidden="true">
- <label class="search-header" hidden="true"><html:h2 data-l10n-id="network-settings-title"/></label>
-
- <hbox align="center">
- <hbox align="center" flex="1">
- <description id="connectionSettingsDescription" control="connectionSettings"/>
- <spacer width="5"/>
- <label id="connectionSettingsLearnMore" class="learnMore" is="text-link"
- data-l10n-id="network-proxy-connection-learn-more">
- </label>
- <separator orient="vertical"/>
- </hbox>
-
- <!-- Please don't remove the wrapping hbox/vbox/box for these elements. It's used to properly compute the search tooltip position. -->
- <hbox>
- <button id="connectionSettings"
- is="highlightable-button"
- class="accessory-button"
- data-l10n-id="network-proxy-connection-settings"
- searchkeywords="doh trr"
- search-l10n-ids="
- connection-window.title,
- connection-proxy-option-no.label,
- connection-proxy-option-auto.label,
- connection-proxy-option-system.label,
- connection-proxy-option-manual.label,
- connection-proxy-http,
- connection-proxy-https,
- connection-proxy-ftp,
- connection-proxy-http-port,
- connection-proxy-socks,
- connection-proxy-socks4,
- connection-proxy-socks5,
- connection-proxy-noproxy,
- connection-proxy-noproxy-desc,
- connection-proxy-http-sharing.label,
- connection-proxy-autotype.label,
- connection-proxy-reload.label,
- connection-proxy-autologin.label,
- connection-proxy-socks-remote-dns.label,
- connection-dns-over-https.label,
- connection-dns-over-https-url-custom.label,
- " />
- </hbox>
- </hbox>
-</groupbox>
</html:template>
diff --git a/browser/components/preferences/main.js b/browser/components/preferences/main.js
index e2b0cf84f560..329cbeba163a 100644
--- a/browser/components/preferences/main.js
+++ b/browser/components/preferences/main.js
@@ -361,15 +361,6 @@ var gMainPane = {
});
this.updatePerformanceSettingsBox({ duringChangeEvent: false });
this.displayUseSystemLocale();
- let connectionSettingsLink = document.getElementById(
- "connectionSettingsLearnMore"
- );
- let connectionSettingsUrl =
- Services.urlFormatter.formatURLPref("app.support.baseURL") +
- "prefs-connection-settings";
- connectionSettingsLink.setAttribute("href", connectionSettingsUrl);
- this.updateProxySettingsUI();
- initializeProxyUI(gMainPane);
if (Services.prefs.getBoolPref("intl.multilingual.enabled")) {
gMainPane.initBrowserLocale();
@@ -503,11 +494,6 @@ var gMainPane = {
"change",
gMainPane.updateHardwareAcceleration.bind(gMainPane)
);
- setEventListener(
- "connectionSettings",
- "command",
- gMainPane.showConnections
- );
setEventListener(
"browserContainersCheckbox",
"command",
diff --git a/browser/components/preferences/preferences.js b/browser/components/preferences/preferences.js
index 27e9763a1f9e..089533f20ade 100644
--- a/browser/components/preferences/preferences.js
+++ b/browser/components/preferences/preferences.js
@@ -13,6 +13,7 @@
/* import-globals-from findInPage.js */
/* import-globals-from ../../base/content/utilityOverlay.js */
/* import-globals-from ../../../toolkit/content/preferencesBindings.js */
+/* import-globals-from ../torpreferences/content/torPane.js */
"use strict";
@@ -91,6 +92,14 @@ function init_all() {
document.getElementById("template-paneSync").remove();
}
register_module("paneSearchResults", gSearchResultsPane);
+ if (gTorPane.enabled) {
+ document.getElementById("category-tor").hidden = false;
+ register_module("paneTor", gTorPane);
+ } else {
+ // Remove the pane from the DOM so it doesn't get incorrectly included in search results.
+ document.getElementById("template-paneTor").remove();
+ }
+
gSearchResultsPane.init();
gMainPane.preInit();
diff --git a/browser/components/preferences/preferences.xhtml b/browser/components/preferences/preferences.xhtml
index e593f2dacd1e..5e341331da49 100644
--- a/browser/components/preferences/preferences.xhtml
+++ b/browser/components/preferences/preferences.xhtml
@@ -13,6 +13,7 @@
<?xml-stylesheet href="chrome://browser/skin/preferences/containers.css"?>
<?xml-stylesheet href="chrome://browser/skin/preferences/privacy.css"?>
<?xml-stylesheet href="chrome://browser/content/securitylevel/securityLevelPreferences.css"?>
+<?xml-stylesheet href="chrome://browser/content/torpreferences/torPreferences.css"?>
<!DOCTYPE html [
<!ENTITY % aboutTorDTD SYSTEM "chrome://torbutton/locale/aboutTor.dtd">
@@ -141,6 +142,9 @@
<image class="category-icon"/>
<label class="category-name" flex="1" data-l10n-id="pane-sync-title2"></label>
</richlistitem>
+
+#include ../torpreferences/content/torCategory.inc.xhtml
+
</richlistbox>
<spacer flex="1"/>
@@ -200,6 +204,7 @@
#include privacy.inc.xhtml
#include containers.inc.xhtml
#include sync.inc.xhtml
+#include ../torpreferences/content/torPane.xhtml
</vbox>
</vbox>
</vbox>
diff --git a/browser/components/preferences/privacy.js b/browser/components/preferences/privacy.js
index 81ded441b6e6..23fcffe7b5eb 100644
--- a/browser/components/preferences/privacy.js
+++ b/browser/components/preferences/privacy.js
@@ -77,6 +77,7 @@ XPCOMUtils.defineLazyGetter(this, "AlertsServiceDND", function() {
}
});
+// TODO: module import via ChromeUtils.defineModuleGetter
XPCOMUtils.defineLazyScriptGetter(
this,
["SecurityLevelPreferences"],
diff --git a/browser/components/torpreferences/content/parseFunctions.jsm b/browser/components/torpreferences/content/parseFunctions.jsm
new file mode 100644
index 000000000000..954759de63a5
--- /dev/null
+++ b/browser/components/torpreferences/content/parseFunctions.jsm
@@ -0,0 +1,89 @@
+"use strict";
+
+var EXPORTED_SYMBOLS = [
+ "parsePort",
+ "parseAddrPort",
+ "parseUsernamePassword",
+ "parseAddrPortList",
+ "parseBridgeStrings",
+ "parsePortList",
+];
+
+// expects a string representation of an integer from 1 to 65535
+let parsePort = function(aPort) {
+ // ensure port string is a valid positive integer
+ const validIntRegex = /^[0-9]+$/;
+ if (!validIntRegex.test(aPort)) {
+ throw new Error(`Invalid PORT string : '${aPort}'`);
+ }
+
+ // ensure port value is on valid range
+ let port = Number.parseInt(aPort);
+ if (port < 1 || port > 65535) {
+ throw new Error(
+ `Invalid PORT value, needs to be on range [1,65535] : '${port}'`
+ );
+ }
+
+ return port;
+};
+// expects a string in the format: "ADDRESS:PORT"
+let parseAddrPort = function(aAddrColonPort) {
+ let tokens = aAddrColonPort.split(":");
+ if (tokens.length != 2) {
+ throw new Error(`Invalid ADDRESS:PORT string : '${aAddrColonPort}'`);
+ }
+ let address = tokens[0];
+ let port = parsePort(tokens[1]);
+ return [address, port];
+};
+
+// expects a string in the format: "USERNAME:PASSWORD"
+// split on the first colon and any subsequent go into password
+let parseUsernamePassword = function(aUsernameColonPassword) {
+ let colonIndex = aUsernameColonPassword.indexOf(":");
+ if (colonIndex < 0) {
+ // we don't log the contents of the potentially password containing string
+ throw new Error("Invalid USERNAME:PASSWORD string");
+ }
+
+ let username = aUsernameColonPassword.substring(0, colonIndex);
+ let password = aUsernameColonPassword.substring(colonIndex + 1);
+
+ return [username, password];
+};
+
+// expects a string in the format: ADDRESS:PORT,ADDRESS:PORT,...
+// returns array of ports (as ints)
+let parseAddrPortList = function(aAddrPortList) {
+ let addrPorts = aAddrPortList.split(",");
+ // parse ADDRESS:PORT string and only keep the port (second element in returned array)
+ let retval = addrPorts.map(addrPort => parseAddrPort(addrPort)[1]);
+ return retval;
+};
+
+// 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
+let parseBridgeStrings = function(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
+ let splitStrings = aBridgeStrings.split("\n");
+ return splitStrings.map(val => val.trim().replace(/^bridge\s+/i, ""))
+ .filter(bridgeString => bridgeString != "");
+};
+
+// expecting a ',' delimited list of ints with possible white space between
+// returns an array of ints
+let parsePortList = function(aPortListString) {
+ let splitStrings = aPortListString.split(",");
+ return splitStrings.map(val => parsePort(val.trim()));
+};
diff --git a/browser/components/torpreferences/content/requestBridgeDialog.jsm b/browser/components/torpreferences/content/requestBridgeDialog.jsm
new file mode 100644
index 000000000000..027e6a152655
--- /dev/null
+++ b/browser/components/torpreferences/content/requestBridgeDialog.jsm
@@ -0,0 +1,220 @@
+"use strict";
+
+var EXPORTED_SYMBOLS = ["RequestBridgeDialog"];
+
+const { BridgeDB } = ChromeUtils.import("resource:///modules/BridgeDB.jsm");
+const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm");
+
+class RequestBridgeDialog {
+ constructor() {
+ this._dialog = null;
+ this._submitCommand = null;
+ this._submitButton = null;
+ this._dialogDescription = null;
+ this._captchaImage = null;
+ this._captchaEntryTextbox = null;
+ this._captchaRefreshCommand = null;
+ this._captchaRefreshButton = null;
+ this._incorrectCaptchaHbox = null;
+ this._incorrectCaptchaLabel = null;
+ this._bridges = [];
+ this._proxyURI = null;
+ }
+
+ static get selectors() {
+ return {
+ submitButton:
+ "accept" /* not really a selector but a key for dialog's getButton */,
+ dialogDescription: "description#torPreferences-requestBridge-description",
+ submitCommand: "command#torPreferences-requestBridge-submitCommand",
+ captchaImage: "image#torPreferences-requestBridge-captchaImage",
+ captchaEntryTextbox:
+ "input#torPreferences-requestBridge-captchaTextbox",
+ refreshCaptchaCommand:
+ "command#torPreferences-requestBridge-refreshCaptchaCommand",
+ refreshCaptchaButton:
+ "button#torPreferences-requestBridge-refreshCaptchaButton",
+ incorrectCaptchaHbox:
+ "hbox#torPreferences-requestBridge-incorrectCaptchaHbox",
+ incorrectCaptchaLabel:
+ "label#torPreferences-requestBridge-incorrectCaptchaError",
+ };
+ }
+
+ _populateXUL(dialog) {
+ const selectors = RequestBridgeDialog.selectors;
+
+ this._dialog = dialog;
+ this._dialog.setAttribute(
+ "title",
+ TorStrings.settings.requestBridgeDialogTitle
+ );
+ // user may have opened a Request Bridge dialog in another tab, so update the
+ // CAPTCHA image or close out the dialog if we have a bridge list
+ this._dialog.addEventListener("focusin", () => {
+ const uri = BridgeDB.currentCaptchaImage;
+ const bridges = BridgeDB.currentBridges;
+
+ // new captcha image
+ if (uri) {
+ this._setcaptchaImage(uri);
+ } else if (bridges) {
+ this._bridges = bridges;
+ this._submitButton.disabled = false;
+ this._dialog.acceptDialog();
+ }
+ });
+
+ this._submitCommand = this._dialog.querySelector(selectors.submitCommand);
+
+ this._submitButton = this._dialog.getButton(selectors.submitButton);
+ this._submitButton.setAttribute("label", TorStrings.settings.submitCaptcha);
+ this._submitButton.setAttribute("command", this._submitCommand.id);
+ this._submitButton.disabled = true;
+
+ this._dialogDescription = this._dialog.querySelector(
+ selectors.dialogDescription
+ );
+ this._dialogDescription.textContent =
+ TorStrings.settings.contactingBridgeDB;
+
+ this._captchaImage = this._dialog.querySelector(selectors.captchaImage);
+
+ // request captcha from bridge db
+ BridgeDB.requestNewCaptchaImage(this._proxyURI).then(uri => {
+ this._setcaptchaImage(uri);
+ });
+
+ this._captchaEntryTextbox = this._dialog.querySelector(
+ selectors.captchaEntryTextbox
+ );
+ this._captchaEntryTextbox.setAttribute(
+ "placeholder",
+ TorStrings.settings.captchaTextboxPlaceholder
+ );
+ this._captchaEntryTextbox.disabled = true;
+ this._captchaEntryTextbox.onkeypress = evt => {
+ const ENTER_KEY = 13;
+ if (evt.keyCode == ENTER_KEY) {
+ // logically same as pressing the 'submit' button of the parent dialog
+ this.onSubmitCaptcha();
+ return false;
+ }
+ return true;
+ };
+ // disable submit if entry textbox is empty
+ this._captchaEntryTextbox.oninput = () => {
+ this._submitButton.disabled = this._captchaEntryTextbox.value == "";
+ };
+
+ this._captchaRefreshCommand = this._dialog.querySelector(
+ selectors.refreshCaptchaCommand
+ );
+ this._captchaRefreshButton = this._dialog.querySelector(
+ selectors.refreshCaptchaButton
+ );
+ this._captchaRefreshButton.setAttribute(
+ "command",
+ this._captchaRefreshCommand.id
+ );
+ this._captchaRefreshButton.disabled = true;
+
+ this._incorrectCaptchaHbox = this._dialog.querySelector(
+ selectors.incorrectCaptchaHbox
+ );
+ this._incorrectCaptchaLabel = this._dialog.querySelector(
+ selectors.incorrectCaptchaLabel
+ );
+ this._incorrectCaptchaLabel.setAttribute(
+ "value",
+ TorStrings.settings.incorrectCaptcha
+ );
+
+ return true;
+ }
+
+ _setcaptchaImage(uri) {
+ if (uri != this._captchaImage.src) {
+ this._captchaImage.src = uri;
+ this._dialogDescription.textContent = TorStrings.settings.solveTheCaptcha;
+ this._setUIDisabled(false);
+ this._captchaEntryTextbox.focus();
+ this._captchaEntryTextbox.select();
+ }
+ }
+
+ _setUIDisabled(disabled) {
+ this._submitButton.disabled = this._captchaGuessIsEmpty() || disabled;
+ this._captchaEntryTextbox.disabled = disabled;
+ this._captchaRefreshButton.disabled = disabled;
+ }
+
+ _captchaGuessIsEmpty() {
+ return this._captchaEntryTextbox.value == "";
+ }
+
+ init(window, dialog) {
+ // defer to later until firefox has populated the dialog with all our elements
+ window.setTimeout(() => {
+ this._populateXUL(dialog);
+ }, 0);
+ }
+
+ close() {
+ BridgeDB.close();
+ }
+
+ /*
+ Event Handlers
+ */
+ onSubmitCaptcha() {
+ let captchaText = this._captchaEntryTextbox.value.trim();
+ // noop if the field is empty
+ if (captchaText == "") {
+ return;
+ }
+
+ // freeze ui while we make request
+ this._setUIDisabled(true);
+ this._incorrectCaptchaHbox.style.visibility = "hidden";
+
+ BridgeDB.submitCaptchaGuess(captchaText)
+ .then(aBridges => {
+ this._bridges = aBridges;
+
+ this._submitButton.disabled = false;
+ this._dialog.acceptDialog();
+ })
+ .catch(aError => {
+ this._bridges = [];
+ this._setUIDisabled(false);
+ this._incorrectCaptchaHbox.style.visibility = "visible";
+ });
+ }
+
+ onRefreshCaptcha() {
+ this._setUIDisabled(true);
+ this._captchaImage.src = "";
+ this._dialogDescription.textContent =
+ TorStrings.settings.contactingBridgeDB;
+ this._captchaEntryTextbox.value = "";
+ this._incorrectCaptchaHbox.style.visibility = "hidden";
+
+ BridgeDB.requestNewCaptchaImage(this._proxyURI).then(uri => {
+ this._setcaptchaImage(uri);
+ });
+ }
+
+ openDialog(gSubDialog, aProxyURI, aCloseCallback) {
+ this._proxyURI = aProxyURI;
+ gSubDialog.open(
+ "chrome://browser/content/torpreferences/requestBridgeDialog.xhtml",
+ "resizable=yes",
+ this,
+ () => {
+ this.close();
+ aCloseCallback(this._bridges);
+ }
+ );
+ }
+}
diff --git a/browser/components/torpreferences/content/requestBridgeDialog.xhtml b/browser/components/torpreferences/content/requestBridgeDialog.xhtml
new file mode 100644
index 000000000000..1081194caf50
--- /dev/null
+++ b/browser/components/torpreferences/content/requestBridgeDialog.xhtml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css"?>
+<?xml-stylesheet href="chrome://browser/content/torpreferences/torPreferences.css"?>
+
+<dialog id="torPreferences-requestBridge-dialog" type="child" class="prefwindow"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ title="Request Bridge"
+ buttons="cancel,accept"
+ role="dialog">
+ <command id="torPreferences-requestBridge-submitCommand" oncommand="requestBridgeDialog.onSubmitCaptcha();"/>
+ <!-- ok, so ​ is a zero-width space. We need to have *something* in the innerText so that XUL knows how tall the
+ description node is so that it can determine how large to make the dialog element's inner draw area. If we have
+ nothing in the innerText, then it collapse to 0 height, and the contents of the dialog ends up partially hidden >:( -->
+ <description id="torPreferences-requestBridge-description">​</description>
+ <!-- init to transparent 400x125 png -->
+ <image id="torPreferences-requestBridge-captchaImage" flex="1"/>
+ <hbox id="torPreferences-requestBridge-inputHbox">
+ <html:input id="torPreferences-requestBridge-captchaTextbox" type="text" style="-moz-box-flex: 1;"/>
+ <command id="torPreferences-requestBridge-refreshCaptchaCommand" oncommand="requestBridgeDialog.onRefreshCaptcha();"/>
+ <button id="torPreferences-requestBridge-refreshCaptchaButton" image="chrome://browser/skin/reload.svg"/>
+ </hbox>
+ <hbox id="torPreferences-requestBridge-incorrectCaptchaHbox" align="center">
+ <image id="torPreferences-requestBridge-errorIcon" />
+ <label id="torPreferences-requestBridge-incorrectCaptchaError" flex="1"/>
+ </hbox>
+ <script type="application/javascript"><![CDATA[
+ "use strict";
+
+ let requestBridgeDialog = window.arguments[0];
+ let dialog = document.getElementById("torPreferences-requestBridge-dialog");
+ requestBridgeDialog.init(window, dialog);
+ ]]></script>
+</dialog>
\ No newline at end of file
diff --git a/browser/components/torpreferences/content/torBridgeSettings.jsm b/browser/components/torpreferences/content/torBridgeSettings.jsm
new file mode 100644
index 000000000000..ceb61d3ec972
--- /dev/null
+++ b/browser/components/torpreferences/content/torBridgeSettings.jsm
@@ -0,0 +1,325 @@
+"use strict";
+
+var EXPORTED_SYMBOLS = [
+ "TorBridgeSource",
+ "TorBridgeSettings",
+ "makeTorBridgeSettingsNone",
+ "makeTorBridgeSettingsBuiltin",
+ "makeTorBridgeSettingsBridgeDB",
+ "makeTorBridgeSettingsUserProvided",
+];
+
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+const { TorProtocolService } = ChromeUtils.import(
+ "resource:///modules/TorProtocolService.jsm"
+);
+const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm");
+
+const TorBridgeSource = {
+ NONE: "NONE",
+ BUILTIN: "BUILTIN",
+ BRIDGEDB: "BRIDGEDB",
+ USERPROVIDED: "USERPROVIDED",
+};
+
+class TorBridgeSettings {
+ constructor() {
+ this._bridgeSource = TorBridgeSource.NONE;
+ this._selectedDefaultBridgeType = null;
+ this._bridgeStrings = [];
+ }
+
+ get selectedDefaultBridgeType() {
+ if (this._bridgeSource == TorBridgeSource.BUILTIN) {
+ return this._selectedDefaultBridgeType;
+ }
+ return undefined;
+ }
+
+ get bridgeSource() {
+ return this._bridgeSource;
+ }
+
+ // for display
+ get bridgeStrings() {
+ return this._bridgeStrings.join("\n");
+ }
+
+ // raw
+ get bridgeStringsArray() {
+ return this._bridgeStrings;
+ }
+
+ static get defaultBridgeTypes() {
+ if (TorBridgeSettings._defaultBridgeTypes) {
+ return TorBridgeSettings._defaultBridgeTypes;
+ }
+
+ let bridgeListBranch = Services.prefs.getBranch(
+ TorStrings.preferenceBranches.defaultBridge
+ );
+ let bridgePrefs = bridgeListBranch.getChildList("", {});
+
+ // an unordered set for shoving bridge types into
+ let 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
+ let recommendedBridgeType = Services.prefs.getCharPref(
+ TorStrings.preferenceKeys.recommendedBridgeType,
+ null
+ );
+
+ let retval = [];
+ if (recommendedBridgeType && bridgeTypes.has(recommendedBridgeType)) {
+ retval.push(recommendedBridgeType);
+ }
+
+ for (const bridgeType of bridgeTypes.values()) {
+ if (bridgeType != recommendedBridgeType) {
+ retval.push(bridgeType);
+ }
+ }
+
+ // cache off
+ TorBridgeSettings._defaultBridgeTypes = retval;
+ return retval;
+ }
+
+ _readDefaultBridges(aBridgeType) {
+ let bridgeBranch = Services.prefs.getBranch(
+ TorStrings.preferenceBranches.defaultBridge
+ );
+ let bridgeBranchPrefs = bridgeBranch.getChildList("", {});
+
+ let retval = [];
+
+ // regex matches against strings ending in ".N" where N is a positive integer
+ let 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) == aBridgeType.length &&
+ key.startsWith(aBridgeType)
+ ) {
+ let bridgeStr = bridgeBranch.getCharPref(key);
+ retval.push(bridgeStr);
+ }
+ }
+
+ // fisher-yates shuffle
+ // shuffle so that Tor Browser users don't all try the built-in bridges in the same order
+ for (let i = retval.length - 1; i > 0; --i) {
+ // number n such that 0.0 <= n < 1.0
+ const n = Math.random();
+ // integer j such that 0 <= j <= i
+ const j = Math.floor(n * (i + 1));
+
+ // swap values at indices i and j
+ const tmp = retval[i];
+ retval[i] = retval[j];
+ retval[j] = tmp;
+ }
+
+ return retval;
+ }
+
+ _readBridgeDBBridges() {
+ let bridgeBranch = Services.prefs.getBranch(
+ `${TorStrings.preferenceBranches.bridgeDBBridges}`
+ );
+ let bridgeBranchPrefs = bridgeBranch.getChildList("", {});
+ // the child prefs do not come in any particular order so sort the keys
+ // so the values can be compared to what we get out off torrc
+ bridgeBranchPrefs.sort();
+
+ // just assume all of the prefs under the parent point to valid bridge string
+ let retval = bridgeBranchPrefs.map(key =>
+ bridgeBranch.getCharPref(key).trim()
+ );
+
+ return retval;
+ }
+
+ _readTorrcBridges() {
+ let bridgeList = TorProtocolService.readStringArraySetting(
+ TorStrings.configKeys.bridgeList
+ );
+
+ let retval = [];
+ for (const line of bridgeList) {
+ let trimmedLine = line.trim();
+ if (trimmedLine) {
+ retval.push(trimmedLine);
+ }
+ }
+
+ return retval;
+ }
+
+ // analagous to initBridgeSettings()
+ readSettings() {
+ // restore to defaults
+ this._bridgeSource = TorBridgeSource.NONE;
+ this._selectedDefaultBridgeType = null;
+ this._bridgeStrings = [];
+
+ // So the way tor-launcher determines the origin of the configured bridges is a bit
+ // weird and depends on inferring our scenario based on some firefox prefs and the
+ // relationship between the saved list of bridges in about:config vs the list saved in torrc
+
+ // first off, if "extensions.torlauncher.default_bridge_type" is set to one of our
+ // builtin default types (obfs4, meek-azure, snowflake, etc) then we provide the
+ // bridges in "extensions.torlauncher.default_bridge.*" (filtered by our default_bridge_type)
+
+ // next, we compare the list of bridges saved in torrc to the bridges stored in the
+ // "extensions.torlauncher.bridgedb_bridge."" branch. If they match *exactly* then we assume
+ // the bridges were retrieved from BridgeDB and use those. If the torrc list is empty then we know
+ // we have no bridge settings
+
+ // finally, if none of the previous conditions are not met, it is assumed the bridges stored in
+ // torrc are user-provided
+
+ // what we should(?) do once we excise tor-launcher entirely is explicitly store an int/enum in
+ // about:config that tells us which scenario we are in so we don't have to guess
+
+ let defaultBridgeType = Services.prefs.getCharPref(
+ TorStrings.preferenceKeys.defaultBridgeType,
+ null
+ );
+
+ // check if source is BUILTIN
+ if (defaultBridgeType) {
+ this._bridgeStrings = this._readDefaultBridges(defaultBridgeType);
+ this._bridgeSource = TorBridgeSource.BUILTIN;
+ this._selectedDefaultBridgeType = defaultBridgeType;
+ return;
+ }
+
+ let torrcBridges = this._readTorrcBridges();
+
+ // no stored bridges means no bridge is in use
+ if (torrcBridges.length == 0) {
+ this._bridgeStrings = [];
+ this._bridgeSource = TorBridgeSource.NONE;
+ return;
+ }
+
+ let bridgedbBridges = this._readBridgeDBBridges();
+
+ // if these two lists are equal then we got our bridges from bridgedb
+ // ie: same element in identical order
+ let arraysEqual = (left, right) => {
+ if (left.length != right.length) {
+ return false;
+ }
+ const length = left.length;
+ for (let i = 0; i < length; ++i) {
+ if (left[i] != right[i]) {
+ return false;
+ }
+ }
+ return true;
+ };
+
+ // agreement between prefs and torrc means bridgedb bridges
+ if (arraysEqual(torrcBridges, bridgedbBridges)) {
+ this._bridgeStrings = torrcBridges;
+ this._bridgeSource = TorBridgeSource.BRIDGEDB;
+ return;
+ }
+
+ // otherwise they must be user provided
+ this._bridgeStrings = torrcBridges;
+ this._bridgeSource = TorBridgeSource.USERPROVIDED;
+ }
+
+ writeSettings() {
+ let settingsObject = new Map();
+
+ // init tor bridge settings to null
+ settingsObject.set(TorStrings.configKeys.useBridges, null);
+ settingsObject.set(TorStrings.configKeys.bridgeList, null);
+
+ // clear bridge related firefox prefs
+ Services.prefs.setCharPref(TorStrings.preferenceKeys.defaultBridgeType, "");
+ let bridgeBranch = Services.prefs.getBranch(
+ `${TorStrings.preferenceBranches.bridgeDBBridges}`
+ );
+ let bridgeBranchPrefs = bridgeBranch.getChildList("", {});
+ for (const pref of bridgeBranchPrefs) {
+ Services.prefs.clearUserPref(
+ `${TorStrings.preferenceBranches.bridgeDBBridges}${pref}`
+ );
+ }
+
+ switch (this._bridgeSource) {
+ case TorBridgeSource.BUILTIN:
+ // set builtin bridge type to use in prefs
+ Services.prefs.setCharPref(
+ TorStrings.preferenceKeys.defaultBridgeType,
+ this._selectedDefaultBridgeType
+ );
+ break;
+ case TorBridgeSource.BRIDGEDB:
+ // save bridges off to prefs
+ for (let i = 0; i < this.bridgeStringsArray.length; ++i) {
+ Services.prefs.setCharPref(
+ `${TorStrings.preferenceBranches.bridgeDBBridges}${i}`,
+ this.bridgeStringsArray[i]
+ );
+ }
+ break;
+ }
+
+ // write over our bridge list if bridges are enabled
+ if (this._bridgeSource != TorBridgeSource.NONE) {
+ settingsObject.set(TorStrings.configKeys.useBridges, true);
+ settingsObject.set(
+ TorStrings.configKeys.bridgeList,
+ this.bridgeStringsArray
+ );
+ }
+ TorProtocolService.writeSettings(settingsObject);
+ }
+}
+
+function makeTorBridgeSettingsNone() {
+ return new TorBridgeSettings();
+}
+
+function makeTorBridgeSettingsBuiltin(aBridgeType) {
+ let retval = new TorBridgeSettings();
+ retval._bridgeSource = TorBridgeSource.BUILTIN;
+ retval._selectedDefaultBridgeType = aBridgeType;
+ retval._bridgeStrings = retval._readDefaultBridges(aBridgeType);
+
+ return retval;
+}
+
+function makeTorBridgeSettingsBridgeDB(aBridges) {
+ let retval = new TorBridgeSettings();
+ retval._bridgeSource = TorBridgeSource.BRIDGEDB;
+ retval._selectedDefaultBridgeType = null;
+ retval._bridgeStrings = aBridges;
+
+ return retval;
+}
+
+function makeTorBridgeSettingsUserProvided(aBridges) {
+ let retval = new TorBridgeSettings();
+ retval._bridgeSource = TorBridgeSource.USERPROVIDED;
+ retval._selectedDefaultBridgeType = null;
+ retval._bridgeStrings = aBridges;
+
+ return retval;
+}
diff --git a/browser/components/torpreferences/content/torCategory.inc.xhtml b/browser/components/torpreferences/content/torCategory.inc.xhtml
new file mode 100644
index 000000000000..abe56200f571
--- /dev/null
+++ b/browser/components/torpreferences/content/torCategory.inc.xhtml
@@ -0,0 +1,9 @@
+<richlistitem id="category-tor"
+ class="category"
+ value="paneTor"
+ helpTopic="prefs-tor"
+ align="center"
+ hidden="true">
+ <image class="category-icon"/>
+ <label id="torPreferences-labelCategory" class="category-name" flex="1" value="Tor"/>
+</richlistitem>
diff --git a/browser/components/torpreferences/content/torFirewallSettings.jsm b/browser/components/torpreferences/content/torFirewallSettings.jsm
new file mode 100644
index 000000000000..e77f18ef2fae
--- /dev/null
+++ b/browser/components/torpreferences/content/torFirewallSettings.jsm
@@ -0,0 +1,72 @@
+"use strict";
+
+var EXPORTED_SYMBOLS = [
+ "TorFirewallSettings",
+ "makeTorFirewallSettingsNone",
+ "makeTorFirewallSettingsCustom",
+];
+
+const { TorProtocolService } = ChromeUtils.import(
+ "resource:///modules/TorProtocolService.jsm"
+);
+const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm");
+const { parseAddrPortList } = ChromeUtils.import(
+ "chrome://browser/content/torpreferences/parseFunctions.jsm"
+);
+
+class TorFirewallSettings {
+ constructor() {
+ this._allowedPorts = [];
+ }
+
+ get portsConfigurationString() {
+ let portStrings = this._allowedPorts.map(port => `*:${port}`);
+ return portStrings.join(",");
+ }
+
+ get commaSeparatedListString() {
+ return this._allowedPorts.join(",");
+ }
+
+ get hasPorts() {
+ return this._allowedPorts.length > 0;
+ }
+
+ readSettings() {
+ let addressPortList = TorProtocolService.readStringSetting(
+ TorStrings.configKeys.reachableAddresses
+ );
+
+ let allowedPorts = [];
+ if (addressPortList) {
+ allowedPorts = parseAddrPortList(addressPortList);
+ }
+ this._allowedPorts = allowedPorts;
+ }
+
+ writeSettings() {
+ let settingsObject = new Map();
+
+ // init to null so Tor daemon resets if no ports
+ settingsObject.set(TorStrings.configKeys.reachableAddresses, null);
+
+ if (this._allowedPorts.length > 0) {
+ settingsObject.set(
+ TorStrings.configKeys.reachableAddresses,
+ this.portsConfigurationString
+ );
+ }
+
+ TorProtocolService.writeSettings(settingsObject);
+ }
+}
+
+function makeTorFirewallSettingsNone() {
+ return new TorFirewallSettings();
+}
+
+function makeTorFirewallSettingsCustom(aPortsList) {
+ let retval = new TorFirewallSettings();
+ retval._allowedPorts = aPortsList;
+ return retval;
+}
diff --git a/browser/components/torpreferences/content/torLogDialog.jsm b/browser/components/torpreferences/content/torLogDialog.jsm
new file mode 100644
index 000000000000..70e49ac048f8
--- /dev/null
+++ b/browser/components/torpreferences/content/torLogDialog.jsm
@@ -0,0 +1,65 @@
+"use strict";
+
+var EXPORTED_SYMBOLS = ["TorLogDialog"];
+
+const { TorProtocolService } = ChromeUtils.import(
+ "resource:///modules/TorProtocolService.jsm"
+);
+const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm");
+
+class TorLogDialog {
+ constructor() {
+ this._dialog = null;
+ this._logTextarea = null;
+ this._copyLogButton = null;
+ }
+
+ static get selectors() {
+ return {
+ copyLogButton: "extra1",
+ logTextarea: "textarea#torPreferences-torDialog-textarea",
+ };
+ }
+
+ _populateXUL(aDialog) {
+ this._dialog = aDialog;
+ this._dialog.setAttribute("title", TorStrings.settings.torLogDialogTitle);
+
+ this._logTextarea = this._dialog.querySelector(
+ TorLogDialog.selectors.logTextarea
+ );
+
+ this._copyLogButton = this._dialog.getButton(
+ TorLogDialog.selectors.copyLogButton
+ );
+ this._copyLogButton.setAttribute("label", TorStrings.settings.copyLog);
+ this._copyLogButton.addEventListener("command", () => {
+ this.copyTorLog();
+ });
+
+ this._logTextarea.value = TorProtocolService.getLog();
+ }
+
+ init(window, aDialog) {
+ // defer to later until firefox has populated the dialog with all our elements
+ window.setTimeout(() => {
+ this._populateXUL(aDialog);
+ }, 0);
+ }
+
+ copyTorLog() {
+ // Copy tor log messages to the system clipboard.
+ let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(
+ Ci.nsIClipboardHelper
+ );
+ clipboard.copyString(this._logTextarea.value);
+ }
+
+ openDialog(gSubDialog) {
+ gSubDialog.open(
+ "chrome://browser/content/torpreferences/torLogDialog.xhtml",
+ "resizable=yes",
+ this
+ );
+ }
+}
diff --git a/browser/components/torpreferences/content/torLogDialog.xhtml b/browser/components/torpreferences/content/torLogDialog.xhtml
new file mode 100644
index 000000000000..ae0f4b294204
--- /dev/null
+++ b/browser/components/torpreferences/content/torLogDialog.xhtml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css"?>
+<?xml-stylesheet href="chrome://browser/content/torpreferences/torPreferences.css"?>
+
+<dialog id="torPreferences-torLog-dialog" type="child" class="prefwindow"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ buttons="accept,extra1"
+ role="dialog">
+ <html:textarea
+ id="torPreferences-torDialog-textarea"
+ multiline="true"
+ readonly="true"/>
+ <script type="application/javascript"><![CDATA[
+ "use strict";
+
+ let torLogDialog = window.arguments[0];
+ let dialog = document.getElementById("torPreferences-torLog-dialog");
+ torLogDialog.init(window, dialog);
+ ]]></script>
+</dialog>
\ No newline at end of file
diff --git a/browser/components/torpreferences/content/torPane.js b/browser/components/torpreferences/content/torPane.js
new file mode 100644
index 000000000000..49054b5dac6a
--- /dev/null
+++ b/browser/components/torpreferences/content/torPane.js
@@ -0,0 +1,857 @@
+"use strict";
+
+const { TorProtocolService } = ChromeUtils.import(
+ "resource:///modules/TorProtocolService.jsm"
+);
+
+const {
+ TorBridgeSource,
+ TorBridgeSettings,
+ makeTorBridgeSettingsNone,
+ makeTorBridgeSettingsBuiltin,
+ makeTorBridgeSettingsBridgeDB,
+ makeTorBridgeSettingsUserProvided,
+} = ChromeUtils.import(
+ "chrome://browser/content/torpreferences/torBridgeSettings.jsm"
+);
+
+const {
+ TorProxyType,
+ TorProxySettings,
+ makeTorProxySettingsNone,
+ makeTorProxySettingsSocks4,
+ makeTorProxySettingsSocks5,
+ makeTorProxySettingsHTTPS,
+} = ChromeUtils.import(
+ "chrome://browser/content/torpreferences/torProxySettings.jsm"
+);
+const {
+ TorFirewallSettings,
+ makeTorFirewallSettingsNone,
+ makeTorFirewallSettingsCustom,
+} = ChromeUtils.import(
+ "chrome://browser/content/torpreferences/torFirewallSettings.jsm"
+);
+
+const { TorLogDialog } = ChromeUtils.import(
+ "chrome://browser/content/torpreferences/torLogDialog.jsm"
+);
+
+const { RequestBridgeDialog } = ChromeUtils.import(
+ "chrome://browser/content/torpreferences/requestBridgeDialog.jsm"
+);
+
+ChromeUtils.defineModuleGetter(
+ this,
+ "TorStrings",
+ "resource:///modules/TorStrings.jsm"
+);
+
+const { parsePort, parseBridgeStrings, parsePortList } = ChromeUtils.import(
+ "chrome://browser/content/torpreferences/parseFunctions.jsm"
+);
+
+/*
+ Tor Pane
+
+ Code for populating the XUL in about:preferences#tor, handling input events, interfacing with tor-launcher
+*/
+const gTorPane = (function() {
+ /* CSS selectors for all of the Tor Network DOM elements we need to access */
+ const selectors = {
+ category: {
+ title: "label#torPreferences-labelCategory",
+ },
+ torPreferences: {
+ header: "h1#torPreferences-header",
+ description: "span#torPreferences-description",
+ learnMore: "label#torPreferences-learnMore",
+ },
+ bridges: {
+ header: "h2#torPreferences-bridges-header",
+ description: "span#torPreferences-bridges-description",
+ learnMore: "label#torPreferences-bridges-learnMore",
+ useBridgeCheckbox: "checkbox#torPreferences-bridges-toggle",
+ bridgeSelectionRadiogroup:
+ "radiogroup#torPreferences-bridges-bridgeSelection",
+ builtinBridgeOption: "radio#torPreferences-bridges-radioBuiltin",
+ builtinBridgeList: "menulist#torPreferences-bridges-builtinList",
+ requestBridgeOption: "radio#torPreferences-bridges-radioRequestBridge",
+ requestBridgeButton: "button#torPreferences-bridges-buttonRequestBridge",
+ requestBridgeTextarea:
+ "textarea#torPreferences-bridges-textareaRequestBridge",
+ provideBridgeOption: "radio#torPreferences-bridges-radioProvideBridge",
+ provideBridgeDescription:
+ "description#torPreferences-bridges-descriptionProvideBridge",
+ provideBridgeTextarea:
+ "textarea#torPreferences-bridges-textareaProvideBridge",
+ },
+ advanced: {
+ header: "h2#torPreferences-advanced-header",
+ description: "span#torPreferences-advanced-description",
+ learnMore: "label#torPreferences-advanced-learnMore",
+ useProxyCheckbox: "checkbox#torPreferences-advanced-toggleProxy",
+ proxyTypeLabel: "label#torPreferences-localProxy-type",
+ proxyTypeList: "menulist#torPreferences-localProxy-builtinList",
+ proxyAddressLabel: "label#torPreferences-localProxy-address",
+ proxyAddressTextbox: "input#torPreferences-localProxy-textboxAddress",
+ proxyPortLabel: "label#torPreferences-localProxy-port",
+ proxyPortTextbox: "input#torPreferences-localProxy-textboxPort",
+ proxyUsernameLabel: "label#torPreferences-localProxy-username",
+ proxyUsernameTextbox: "input#torPreferences-localProxy-textboxUsername",
+ proxyPasswordLabel: "label#torPreferences-localProxy-password",
+ proxyPasswordTextbox: "input#torPreferences-localProxy-textboxPassword",
+ useFirewallCheckbox: "checkbox#torPreferences-advanced-toggleFirewall",
+ firewallAllowedPortsLabel: "label#torPreferences-advanced-allowedPorts",
+ firewallAllowedPortsTextbox:
+ "input#torPreferences-advanced-textboxAllowedPorts",
+ torLogsLabel: "label#torPreferences-torLogs",
+ torLogsButton: "button#torPreferences-buttonTorLogs",
+ },
+ }; /* selectors */
+
+ let retval = {
+ // cached frequently accessed DOM elements
+ _useBridgeCheckbox: null,
+ _bridgeSelectionRadiogroup: null,
+ _builtinBridgeOption: null,
+ _builtinBridgeMenulist: null,
+ _requestBridgeOption: null,
+ _requestBridgeButton: null,
+ _requestBridgeTextarea: null,
+ _provideBridgeOption: null,
+ _provideBridgeTextarea: null,
+ _useProxyCheckbox: null,
+ _proxyTypeLabel: null,
+ _proxyTypeMenulist: null,
+ _proxyAddressLabel: null,
+ _proxyAddressTextbox: null,
+ _proxyPortLabel: null,
+ _proxyPortTextbox: null,
+ _proxyUsernameLabel: null,
+ _proxyUsernameTextbox: null,
+ _proxyPasswordLabel: null,
+ _proxyPasswordTextbox: null,
+ _useFirewallCheckbox: null,
+ _allowedPortsLabel: null,
+ _allowedPortsTextbox: null,
+
+ // tor network settings
+ _bridgeSettings: null,
+ _proxySettings: null,
+ _firewallSettings: null,
+
+ // disables the provided list of elements
+ _setElementsDisabled(elements, disabled) {
+ for (let currentElement of elements) {
+ currentElement.disabled = disabled;
+ }
+ },
+
+ // populate xul with strings and cache the relevant elements
+ _populateXUL() {
+ // saves tor settings to disk when navigate away from about:preferences
+ window.addEventListener("blur", val => {
+ TorProtocolService.flushSettings();
+ });
+
+ document
+ .querySelector(selectors.category.title)
+ .setAttribute("value", TorStrings.settings.categoryTitle);
+
+ let prefpane = document.getElementById("mainPrefPane");
+
+ // Heading
+ prefpane.querySelector(selectors.torPreferences.header).innerText =
+ TorStrings.settings.torPreferencesHeading;
+ prefpane.querySelector(selectors.torPreferences.description).textContent =
+ TorStrings.settings.torPreferencesDescription;
+ {
+ let learnMore = prefpane.querySelector(
+ selectors.torPreferences.learnMore
+ );
+ learnMore.setAttribute("value", TorStrings.settings.learnMore);
+ learnMore.setAttribute(
+ "href",
+ TorStrings.settings.learnMoreTorBrowserURL
+ );
+ }
+
+ // Bridge setup
+ prefpane.querySelector(selectors.bridges.header).innerText =
+ TorStrings.settings.bridgesHeading;
+ prefpane.querySelector(selectors.bridges.description).textContent =
+ TorStrings.settings.bridgesDescription;
+ {
+ let learnMore = prefpane.querySelector(selectors.bridges.learnMore);
+ learnMore.setAttribute("value", TorStrings.settings.learnMore);
+ learnMore.setAttribute("href", TorStrings.settings.learnMoreBridgesURL);
+ }
+
+ this._useBridgeCheckbox = prefpane.querySelector(
+ selectors.bridges.useBridgeCheckbox
+ );
+ this._useBridgeCheckbox.setAttribute(
+ "label",
+ TorStrings.settings.useBridge
+ );
+ this._useBridgeCheckbox.addEventListener("command", e => {
+ const checked = this._useBridgeCheckbox.checked;
+ gTorPane.onToggleBridge(checked).onUpdateBridgeSettings();
+ });
+ this._bridgeSelectionRadiogroup = prefpane.querySelector(
+ selectors.bridges.bridgeSelectionRadiogroup
+ );
+ this._bridgeSelectionRadiogroup.value = TorBridgeSource.BUILTIN;
+ this._bridgeSelectionRadiogroup.addEventListener("command", e => {
+ const value = this._bridgeSelectionRadiogroup.value;
+ gTorPane.onSelectBridgeOption(value).onUpdateBridgeSettings();
+ });
+
+ // Builtin bridges
+ this._builtinBridgeOption = prefpane.querySelector(
+ selectors.bridges.builtinBridgeOption
+ );
+ this._builtinBridgeOption.setAttribute(
+ "label",
+ TorStrings.settings.selectBridge
+ );
+ this._builtinBridgeOption.setAttribute("value", TorBridgeSource.BUILTIN);
+ this._builtinBridgeMenulist = prefpane.querySelector(
+ selectors.bridges.builtinBridgeList
+ );
+ this._builtinBridgeMenulist.addEventListener("command", e => {
+ gTorPane.onUpdateBridgeSettings();
+ });
+
+ // Request bridge
+ this._requestBridgeOption = prefpane.querySelector(
+ selectors.bridges.requestBridgeOption
+ );
+ this._requestBridgeOption.setAttribute(
+ "label",
+ TorStrings.settings.requestBridgeFromTorProject
+ );
+ this._requestBridgeOption.setAttribute("value", TorBridgeSource.BRIDGEDB);
+ this._requestBridgeButton = prefpane.querySelector(
+ selectors.bridges.requestBridgeButton
+ );
+ this._requestBridgeButton.setAttribute(
+ "label",
+ TorStrings.settings.requestNewBridge
+ );
+ this._requestBridgeButton.addEventListener("command", () =>
+ gTorPane.onRequestBridge()
+ );
+ this._requestBridgeTextarea = prefpane.querySelector(
+ selectors.bridges.requestBridgeTextarea
+ );
+
+ // Provide a bridge
+ this._provideBridgeOption = prefpane.querySelector(
+ selectors.bridges.provideBridgeOption
+ );
+ this._provideBridgeOption.setAttribute(
+ "label",
+ TorStrings.settings.provideBridge
+ );
+ this._provideBridgeOption.setAttribute(
+ "value",
+ TorBridgeSource.USERPROVIDED
+ );
+ prefpane.querySelector(
+ selectors.bridges.provideBridgeDescription
+ ).textContent = TorStrings.settings.provideBridgeDirections;
+ this._provideBridgeTextarea = prefpane.querySelector(
+ selectors.bridges.provideBridgeTextarea
+ );
+ this._provideBridgeTextarea.setAttribute(
+ "placeholder",
+ TorStrings.settings.provideBridgePlaceholder
+ );
+ this._provideBridgeTextarea.addEventListener("blur", () => {
+ gTorPane.onUpdateBridgeSettings();
+ });
+
+ // Advanced setup
+ prefpane.querySelector(selectors.advanced.header).innerText =
+ TorStrings.settings.advancedHeading;
+ prefpane.querySelector(selectors.advanced.description).textContent =
+ TorStrings.settings.advancedDescription;
+ {
+ let learnMore = prefpane.querySelector(selectors.advanced.learnMore);
+ learnMore.setAttribute("value", TorStrings.settings.learnMore);
+ learnMore.setAttribute(
+ "href",
+ TorStrings.settings.learnMoreNetworkSettingsURL
+ );
+ }
+
+ // Local Proxy
+ this._useProxyCheckbox = prefpane.querySelector(
+ selectors.advanced.useProxyCheckbox
+ );
+ this._useProxyCheckbox.setAttribute(
+ "label",
+ TorStrings.settings.useLocalProxy
+ );
+ this._useProxyCheckbox.addEventListener("command", e => {
+ const checked = this._useProxyCheckbox.checked;
+ gTorPane.onToggleProxy(checked).onUpdateProxySettings();
+ });
+ this._proxyTypeLabel = prefpane.querySelector(
+ selectors.advanced.proxyTypeLabel
+ );
+ this._proxyTypeLabel.setAttribute("value", TorStrings.settings.proxyType);
+
+ let mockProxies = [
+ {
+ value: TorProxyType.SOCKS4,
+ label: TorStrings.settings.proxyTypeSOCKS4,
+ },
+ {
+ value: TorProxyType.SOCKS5,
+ label: TorStrings.settings.proxyTypeSOCKS5,
+ },
+ { value: TorProxyType.HTTPS, label: TorStrings.settings.proxyTypeHTTP },
+ ];
+ this._proxyTypeMenulist = prefpane.querySelector(
+ selectors.advanced.proxyTypeList
+ );
+ this._proxyTypeMenulist.addEventListener("command", e => {
+ const value = this._proxyTypeMenulist.value;
+ gTorPane.onSelectProxyType(value).onUpdateProxySettings();
+ });
+ for (let currentProxy of mockProxies) {
+ let menuEntry = document.createXULElement("menuitem");
+ menuEntry.setAttribute("value", currentProxy.value);
+ menuEntry.setAttribute("label", currentProxy.label);
+ this._proxyTypeMenulist
+ .querySelector("menupopup")
+ .appendChild(menuEntry);
+ }
+
+ this._proxyAddressLabel = prefpane.querySelector(
+ selectors.advanced.proxyAddressLabel
+ );
+ this._proxyAddressLabel.setAttribute(
+ "value",
+ TorStrings.settings.proxyAddress
+ );
+ this._proxyAddressTextbox = prefpane.querySelector(
+ selectors.advanced.proxyAddressTextbox
+ );
+ this._proxyAddressTextbox.setAttribute(
+ "placeholder",
+ TorStrings.settings.proxyAddressPlaceholder
+ );
+ this._proxyAddressTextbox.addEventListener("blur", () => {
+ gTorPane.onUpdateProxySettings();
+ });
+ this._proxyPortLabel = prefpane.querySelector(
+ selectors.advanced.proxyPortLabel
+ );
+ this._proxyPortLabel.setAttribute("value", TorStrings.settings.proxyPort);
+ this._proxyPortTextbox = prefpane.querySelector(
+ selectors.advanced.proxyPortTextbox
+ );
+ this._proxyPortTextbox.addEventListener("blur", () => {
+ gTorPane.onUpdateProxySettings();
+ });
+ this._proxyUsernameLabel = prefpane.querySelector(
+ selectors.advanced.proxyUsernameLabel
+ );
+ this._proxyUsernameLabel.setAttribute(
+ "value",
+ TorStrings.settings.proxyUsername
+ );
+ this._proxyUsernameTextbox = prefpane.querySelector(
+ selectors.advanced.proxyUsernameTextbox
+ );
+ this._proxyUsernameTextbox.setAttribute(
+ "placeholder",
+ TorStrings.settings.proxyUsernamePasswordPlaceholder
+ );
+ this._proxyUsernameTextbox.addEventListener("blur", () => {
+ gTorPane.onUpdateProxySettings();
+ });
+ this._proxyPasswordLabel = prefpane.querySelector(
+ selectors.advanced.proxyPasswordLabel
+ );
+ this._proxyPasswordLabel.setAttribute(
+ "value",
+ TorStrings.settings.proxyPassword
+ );
+ this._proxyPasswordTextbox = prefpane.querySelector(
+ selectors.advanced.proxyPasswordTextbox
+ );
+ this._proxyPasswordTextbox.setAttribute(
+ "placeholder",
+ TorStrings.settings.proxyUsernamePasswordPlaceholder
+ );
+ this._proxyPasswordTextbox.addEventListener("blur", () => {
+ gTorPane.onUpdateProxySettings();
+ });
+
+ // Local firewall
+ this._useFirewallCheckbox = prefpane.querySelector(
+ selectors.advanced.useFirewallCheckbox
+ );
+ this._useFirewallCheckbox.setAttribute(
+ "label",
+ TorStrings.settings.useFirewall
+ );
+ this._useFirewallCheckbox.addEventListener("command", e => {
+ const checked = this._useFirewallCheckbox.checked;
+ gTorPane.onToggleFirewall(checked).onUpdateFirewallSettings();
+ });
+ this._allowedPortsLabel = prefpane.querySelector(
+ selectors.advanced.firewallAllowedPortsLabel
+ );
+ this._allowedPortsLabel.setAttribute(
+ "value",
+ TorStrings.settings.allowedPorts
+ );
+ this._allowedPortsTextbox = prefpane.querySelector(
+ selectors.advanced.firewallAllowedPortsTextbox
+ );
+ this._allowedPortsTextbox.setAttribute(
+ "placeholder",
+ TorStrings.settings.allowedPortsPlaceholder
+ );
+ this._allowedPortsTextbox.addEventListener("blur", () => {
+ gTorPane.onUpdateFirewallSettings();
+ });
+
+ // Tor logs
+ prefpane
+ .querySelector(selectors.advanced.torLogsLabel)
+ .setAttribute("value", TorStrings.settings.showTorDaemonLogs);
+ let torLogsButton = prefpane.querySelector(
+ selectors.advanced.torLogsButton
+ );
+ torLogsButton.setAttribute("label", TorStrings.settings.showLogs);
+ torLogsButton.addEventListener("command", () => {
+ gTorPane.onViewTorLogs();
+ });
+
+ // Disable all relevant elements by default
+ this._setElementsDisabled(
+ [
+ this._builtinBridgeOption,
+ this._builtinBridgeMenulist,
+ this._requestBridgeOption,
+ this._requestBridgeButton,
+ this._requestBridgeTextarea,
+ this._provideBridgeOption,
+ this._provideBridgeTextarea,
+ this._proxyTypeLabel,
+ this._proxyTypeMenulist,
+ this._proxyAddressLabel,
+ this._proxyAddressTextbox,
+ this._proxyPortLabel,
+ this._proxyPortTextbox,
+ this._proxyUsernameLabel,
+ this._proxyUsernameTextbox,
+ this._proxyPasswordLabel,
+ this._proxyPasswordTextbox,
+ this._allowedPortsLabel,
+ this._allowedPortsTextbox,
+ ],
+ true
+ );
+
+ // load bridge settings
+ let torBridgeSettings = new TorBridgeSettings();
+ torBridgeSettings.readSettings();
+
+ // populate the bridge list
+ for (let currentBridge of TorBridgeSettings.defaultBridgeTypes) {
+ let menuEntry = document.createXULElement("menuitem");
+ menuEntry.setAttribute("value", currentBridge);
+ menuEntry.setAttribute("label", currentBridge);
+ this._builtinBridgeMenulist
+ .querySelector("menupopup")
+ .appendChild(menuEntry);
+ }
+
+ this.onSelectBridgeOption(torBridgeSettings.bridgeSource);
+ this.onToggleBridge(
+ torBridgeSettings.bridgeSource != TorBridgeSource.NONE
+ );
+ switch (torBridgeSettings.bridgeSource) {
+ case TorBridgeSource.NONE:
+ break;
+ case TorBridgeSource.BUILTIN:
+ this._builtinBridgeMenulist.value =
+ torBridgeSettings.selectedDefaultBridgeType;
+ break;
+ case TorBridgeSource.BRIDGEDB:
+ this._requestBridgeTextarea.value = torBridgeSettings.bridgeStrings;
+ break;
+ case TorBridgeSource.USERPROVIDED:
+ this._provideBridgeTextarea.value = torBridgeSettings.bridgeStrings;
+ break;
+ }
+
+ this._bridgeSettings = torBridgeSettings;
+
+ // load proxy settings
+ let torProxySettings = new TorProxySettings();
+ torProxySettings.readSettings();
+
+ if (torProxySettings.type != TorProxyType.NONE) {
+ this.onToggleProxy(true);
+ this.onSelectProxyType(torProxySettings.type);
+ this._proxyAddressTextbox.value = torProxySettings.address;
+ this._proxyPortTextbox.value = torProxySettings.port;
+ this._proxyUsernameTextbox.value = torProxySettings.username;
+ this._proxyPasswordTextbox.value = torProxySettings.password;
+ }
+
+ this._proxySettings = torProxySettings;
+
+ // load firewall settings
+ let torFirewallSettings = new TorFirewallSettings();
+ torFirewallSettings.readSettings();
+
+ if (torFirewallSettings.hasPorts) {
+ this.onToggleFirewall(true);
+ this._allowedPortsTextbox.value =
+ torFirewallSettings.commaSeparatedListString;
+ }
+
+ this._firewallSettings = torFirewallSettings;
+ },
+
+ init() {
+ this._populateXUL();
+ },
+
+ // whether the page should be present in about:preferences
+ get enabled() {
+ return TorProtocolService.ownsTorDaemon;
+ },
+
+ //
+ // Callbacks
+ //
+
+ // callback when using bridges toggled
+ onToggleBridge(enabled) {
+ this._useBridgeCheckbox.checked = enabled;
+ let disabled = !enabled;
+
+ // first disable all the bridge related elements
+ this._setElementsDisabled(
+ [
+ this._builtinBridgeOption,
+ this._builtinBridgeMenulist,
+ this._requestBridgeOption,
+ this._requestBridgeButton,
+ this._requestBridgeTextarea,
+ this._provideBridgeOption,
+ this._provideBridgeTextarea,
+ ],
+ disabled
+ );
+
+ // and selectively re-enable based on the radiogroup's current value
+ if (enabled) {
+ this.onSelectBridgeOption(this._bridgeSelectionRadiogroup.value);
+ } else {
+ this.onSelectBridgeOption(TorBridgeSource.NONE);
+ }
+ return this;
+ },
+
+ // callback when a bridge option is selected
+ onSelectBridgeOption(source) {
+ // disable all of the bridge elements under radio buttons
+ this._setElementsDisabled(
+ [
+ this._builtinBridgeMenulist,
+ this._requestBridgeButton,
+ this._requestBridgeTextarea,
+ this._provideBridgeTextarea,
+ ],
+ true
+ );
+
+ if (source != TorBridgeSource.NONE) {
+ this._bridgeSelectionRadiogroup.value = source;
+ }
+
+ switch (source) {
+ case TorBridgeSource.BUILTIN: {
+ this._setElementsDisabled([this._builtinBridgeMenulist], false);
+ break;
+ }
+ case TorBridgeSource.BRIDGEDB: {
+ this._setElementsDisabled(
+ [this._requestBridgeButton, this._requestBridgeTextarea],
+ false
+ );
+ break;
+ }
+ case TorBridgeSource.USERPROVIDED: {
+ this._setElementsDisabled([this._provideBridgeTextarea], false);
+ break;
+ }
+ }
+ return this;
+ },
+
+ // called when the request bridge button is activated
+ onRequestBridge() {
+ let requestBridgeDialog = new RequestBridgeDialog();
+ requestBridgeDialog.openDialog(
+ gSubDialog,
+ this._proxySettings.proxyURI,
+ aBridges => {
+ if (aBridges.length > 0) {
+ let bridgeSettings = makeTorBridgeSettingsBridgeDB(aBridges);
+ bridgeSettings.writeSettings();
+ this._bridgeSettings = bridgeSettings;
+
+ this._requestBridgeTextarea.value = bridgeSettings.bridgeStrings;
+ }
+ }
+ );
+ return this;
+ },
+
+ // pushes bridge settings from UI to tor
+ onUpdateBridgeSettings() {
+ let bridgeSettings = null;
+
+ let source = this._useBridgeCheckbox.checked
+ ? this._bridgeSelectionRadiogroup.value
+ : TorBridgeSource.NONE;
+ switch (source) {
+ case TorBridgeSource.NONE: {
+ bridgeSettings = makeTorBridgeSettingsNone();
+ break;
+ }
+ case TorBridgeSource.BUILTIN: {
+ // if there is a built-in bridge already selected, use that
+ let bridgeType = this._builtinBridgeMenulist.value;
+ if (bridgeType) {
+ bridgeSettings = makeTorBridgeSettingsBuiltin(bridgeType);
+ } else {
+ bridgeSettings = makeTorBridgeSettingsNone();
+ }
+ break;
+ }
+ case TorBridgeSource.BRIDGEDB: {
+ // if there are bridgedb bridges saved in the text area, use them
+ let bridgeStrings = this._requestBridgeTextarea.value;
+ if (bridgeStrings) {
+ let bridgeStringList = parseBridgeStrings(bridgeStrings);
+ bridgeSettings = makeTorBridgeSettingsBridgeDB(bridgeStringList);
+ } else {
+ bridgeSettings = makeTorBridgeSettingsNone();
+ }
+ break;
+ }
+ case TorBridgeSource.USERPROVIDED: {
+ // if bridges already exist in the text area, use them
+ let bridgeStrings = this._provideBridgeTextarea.value;
+ if (bridgeStrings) {
+ let bridgeStringList = parseBridgeStrings(bridgeStrings);
+ bridgeSettings = makeTorBridgeSettingsUserProvided(
+ bridgeStringList
+ );
+ } else {
+ bridgeSettings = makeTorBridgeSettingsNone();
+ }
+ break;
+ }
+ }
+ bridgeSettings.writeSettings();
+ this._bridgeSettings = bridgeSettings;
+ return this;
+ },
+
+ // callback when proxy is toggled
+ onToggleProxy(enabled) {
+ this._useProxyCheckbox.checked = enabled;
+ let disabled = !enabled;
+
+ this._setElementsDisabled(
+ [
+ this._proxyTypeLabel,
+ this._proxyTypeMenulist,
+ this._proxyAddressLabel,
+ this._proxyAddressTextbox,
+ this._proxyPortLabel,
+ this._proxyPortTextbox,
+ this._proxyUsernameLabel,
+ this._proxyUsernameTextbox,
+ this._proxyPasswordLabel,
+ this._proxyPasswordTextbox,
+ ],
+ disabled
+ );
+ this.onSelectProxyType(this._proxyTypeMenulist.value);
+ return this;
+ },
+
+ // callback when proxy type is changed
+ onSelectProxyType(value) {
+ if (value == "") {
+ value = TorProxyType.NONE;
+ }
+ this._proxyTypeMenulist.value = value;
+ switch (value) {
+ case TorProxyType.NONE: {
+ this._setElementsDisabled(
+ [
+ this._proxyAddressLabel,
+ this._proxyAddressTextbox,
+ this._proxyPortLabel,
+ this._proxyPortTextbox,
+ this._proxyUsernameLabel,
+ this._proxyUsernameTextbox,
+ this._proxyPasswordLabel,
+ this._proxyPasswordTextbox,
+ ],
+ true
+ ); // DISABLE
+
+ this._proxyAddressTextbox.value = "";
+ this._proxyPortTextbox.value = "";
+ this._proxyUsernameTextbox.value = "";
+ this._proxyPasswordTextbox.value = "";
+ break;
+ }
+ case TorProxyType.SOCKS4: {
+ this._setElementsDisabled(
+ [
+ this._proxyAddressLabel,
+ this._proxyAddressTextbox,
+ this._proxyPortLabel,
+ this._proxyPortTextbox,
+ ],
+ false
+ ); // ENABLE
+ this._setElementsDisabled(
+ [
+ this._proxyUsernameLabel,
+ this._proxyUsernameTextbox,
+ this._proxyPasswordLabel,
+ this._proxyPasswordTextbox,
+ ],
+ true
+ ); // DISABLE
+
+ this._proxyUsernameTextbox.value = "";
+ this._proxyPasswordTextbox.value = "";
+ break;
+ }
+ case TorProxyType.SOCKS5:
+ case TorProxyType.HTTPS: {
+ this._setElementsDisabled(
+ [
+ this._proxyAddressLabel,
+ this._proxyAddressTextbox,
+ this._proxyPortLabel,
+ this._proxyPortTextbox,
+ this._proxyUsernameLabel,
+ this._proxyUsernameTextbox,
+ this._proxyPasswordLabel,
+ this._proxyPasswordTextbox,
+ ],
+ false
+ ); // ENABLE
+ break;
+ }
+ }
+ return this;
+ },
+
+ // pushes proxy settings from UI to tor
+ onUpdateProxySettings() {
+ const proxyType = this._useProxyCheckbox.checked
+ ? this._proxyTypeMenulist.value
+ : TorProxyType.NONE;
+ const addressString = this._proxyAddressTextbox.value;
+ const portString = this._proxyPortTextbox.value;
+ const usernameString = this._proxyUsernameTextbox.value;
+ const passwordString = this._proxyPasswordTextbox.value;
+
+ let proxySettings = null;
+
+ switch (proxyType) {
+ case TorProxyType.NONE:
+ proxySettings = makeTorProxySettingsNone();
+ break;
+ case TorProxyType.SOCKS4:
+ proxySettings = makeTorProxySettingsSocks4(
+ addressString,
+ parsePort(portString)
+ );
+ break;
+ case TorProxyType.SOCKS5:
+ proxySettings = makeTorProxySettingsSocks5(
+ addressString,
+ parsePort(portString),
+ usernameString,
+ passwordString
+ );
+ break;
+ case TorProxyType.HTTPS:
+ proxySettings = makeTorProxySettingsHTTPS(
+ addressString,
+ parsePort(portString),
+ usernameString,
+ passwordString
+ );
+ break;
+ }
+
+ proxySettings.writeSettings();
+ this._proxySettings = proxySettings;
+ return this;
+ },
+
+ // callback when firewall proxy is toggled
+ onToggleFirewall(enabled) {
+ this._useFirewallCheckbox.checked = enabled;
+ let disabled = !enabled;
+
+ this._setElementsDisabled(
+ [this._allowedPortsLabel, this._allowedPortsTextbox],
+ disabled
+ );
+
+ return this;
+ },
+
+ // pushes firewall settings from UI to tor
+ onUpdateFirewallSettings() {
+ let portListString = this._useFirewallCheckbox.checked
+ ? this._allowedPortsTextbox.value
+ : "";
+ let firewallSettings = null;
+
+ if (portListString) {
+ firewallSettings = makeTorFirewallSettingsCustom(
+ parsePortList(portListString)
+ );
+ } else {
+ firewallSettings = makeTorFirewallSettingsNone();
+ }
+
+ firewallSettings.writeSettings();
+ this._firewallSettings = firewallSettings;
+ return this;
+ },
+
+ onViewTorLogs() {
+ let torLogDialog = new TorLogDialog();
+ torLogDialog.openDialog(gSubDialog);
+ },
+ };
+ return retval;
+})(); /* gTorPane */
diff --git a/browser/components/torpreferences/content/torPane.xhtml b/browser/components/torpreferences/content/torPane.xhtml
new file mode 100644
index 000000000000..3c966b2b3726
--- /dev/null
+++ b/browser/components/torpreferences/content/torPane.xhtml
@@ -0,0 +1,123 @@
+<!-- Tor panel -->
+
+<script type="application/javascript"
+ src="chrome://browser/content/torpreferences/torPane.js"/>
+<html:template id="template-paneTor">
+<hbox id="torPreferencesCategory"
+ class="subcategory"
+ data-category="paneTor"
+ hidden="true">
+ <html:h1 id="torPreferences-header"/>
+</hbox>
+
+<groupbox data-category="paneTor"
+ hidden="true">
+ <description flex="1">
+ <html:span id="torPreferences-description" class="tail-with-learn-more"/>
+ <label id="torPreferences-learnMore" class="learnMore text-link" is="text-link"/>
+ </description>
+</groupbox>
+
+<!-- Bridges -->
+<groupbox id="torPreferences-bridges-group"
+ data-category="paneTor"
+ hidden="true">
+ <html:h2 id="torPreferences-bridges-header"/>
+ <description flex="1">
+ <html:span id="torPreferences-bridges-description" class="tail-with-learn-more"/>
+ <label id="torPreferences-bridges-learnMore" class="learnMore text-link" is="text-link"/>
+ </description>
+ <checkbox id="torPreferences-bridges-toggle"/>
+ <radiogroup id="torPreferences-bridges-bridgeSelection">
+ <hbox class="indent">
+ <radio id="torPreferences-bridges-radioBuiltin"/>
+ <spacer flex="1"/>
+ <menulist id="torPreferences-bridges-builtinList" class="torMarginFix">
+ <menupopup/>
+ </menulist>
+ </hbox>
+ <vbox class="indent">
+ <hbox>
+ <radio id="torPreferences-bridges-radioRequestBridge"/>
+ <space flex="1"/>
+ <button id="torPreferences-bridges-buttonRequestBridge" class="torMarginFix"/>
+ </hbox>
+ <html:textarea
+ id="torPreferences-bridges-textareaRequestBridge"
+ class="indent torMarginFix"
+ multiline="true"
+ rows="3"
+ readonly="true"/>
+ </vbox>
+ <hbox class="indent" flex="1">
+ <vbox flex="1">
+ <radio id="torPreferences-bridges-radioProvideBridge"/>
+ <description id="torPreferences-bridges-descriptionProvideBridge" class="indent"/>
+ <html:textarea
+ id="torPreferences-bridges-textareaProvideBridge"
+ class="indent torMarginFix"
+ multiline="true"
+ rows="3"/>
+ </vbox>
+ </hbox>
+ </radiogroup>
+</groupbox>
+
+<!-- Advanced -->
+<groupbox id="torPreferences-advanced-group"
+ data-category="paneTor"
+ hidden="true">
+ <html:h2 id="torPreferences-advanced-header"/>
+ <description flex="1">
+ <html:span id="torPreferences-advanced-description" class="tail-with-learn-more"/>
+ <label id="torPreferences-advanced-learnMore" class="learnMore text-link" is="text-link" style="display:none"/>
+ </description>
+ <box id="torPreferences-advanced-grid">
+ <!-- Local Proxy -->
+ <hbox class="torPreferences-advanced-checkbox-container">
+ <checkbox id="torPreferences-advanced-toggleProxy"/>
+ </hbox>
+ <hbox class="indent" align="center">
+ <label id="torPreferences-localProxy-type"/>
+ </hbox>
+ <hbox align="center">
+ <spacer flex="1"/>
+ <menulist id="torPreferences-localProxy-builtinList" class="torMarginFix">
+ <menupopup/>
+ </menulist>
+ </hbox>
+ <hbox class="indent" align="center">
+ <label id="torPreferences-localProxy-address"/>
+ </hbox>
+ <hbox align="center">
+ <html:input id="torPreferences-localProxy-textboxAddress" type="text" class="torMarginFix"/>
+ <label id="torPreferences-localProxy-port"/>
+ <!-- proxy-port-input class style pulled from preferences.css and used in the vanilla proxy setup menu -->
+ <html:input id="torPreferences-localProxy-textboxPort" class="proxy-port-input torMarginFix" hidespinbuttons="true" type="number" min="0" max="65535" maxlength="5"/>
+ </hbox>
+ <hbox class="indent" align="center">
+ <label id="torPreferences-localProxy-username"/>
+ </hbox>
+ <hbox align="center">
+ <html:input id="torPreferences-localProxy-textboxUsername" type="text" class="torMarginFix"/>
+ <label id="torPreferences-localProxy-password"/>
+ <html:input id="torPreferences-localProxy-textboxPassword" class="torMarginFix" type="password"/>
+ </hbox>
+ <!-- Firewall -->
+ <hbox class="torPreferences-advanced-checkbox-container">
+ <checkbox id="torPreferences-advanced-toggleFirewall"/>
+ </hbox>
+ <hbox class="indent" align="center">
+ <label id="torPreferences-advanced-allowedPorts"/>
+ </hbox>
+ <hbox align="center">
+ <html:input id="torPreferences-advanced-textboxAllowedPorts" type="text" class="torMarginFix" value="80,443"/>
+ </hbox>
+ </box>
+ <hbox id="torPreferences-torDaemon-hbox" align="center">
+ <label id="torPreferences-torLogs"/>
+ <spacer flex="1"/>
+ <button id="torPreferences-buttonTorLogs" class="torMarginFix"/>
+ </hbox>
+</groupbox>
+</html:template>
\ No newline at end of file
diff --git a/browser/components/torpreferences/content/torPreferences.css b/browser/components/torpreferences/content/torPreferences.css
new file mode 100644
index 000000000000..4dac2c457823
--- /dev/null
+++ b/browser/components/torpreferences/content/torPreferences.css
@@ -0,0 +1,77 @@
+#category-tor > .category-icon {
+ list-style-image: url("chrome://browser/content/torpreferences/torPreferencesIcon.svg");
+}
+
+#torPreferences-advanced-grid {
+ display: grid;
+ grid-template-columns: auto 1fr;
+}
+
+.torPreferences-advanced-checkbox-container {
+ grid-column: 1 / 3;
+}
+
+#torPreferences-localProxy-textboxAddress,
+#torPreferences-localProxy-textboxUsername,
+#torPreferences-localProxy-textboxPassword,
+#torPreferences-advanced-textboxAllowedPorts {
+ -moz-box-flex: 1;
+}
+
+hbox#torPreferences-torDaemon-hbox {
+ margin-top: 20px;
+}
+
+description#torPreferences-requestBridge-description {
+ /*margin-bottom: 1em;*/
+ min-height: 2em;
+}
+
+image#torPreferences-requestBridge-captchaImage {
+ margin: 1em;
+ min-height: 125px;
+}
+
+button#torPreferences-requestBridge-refreshCaptchaButton {
+ min-width: initial;
+}
+
+dialog#torPreferences-requestBridge-dialog > hbox {
+ margin-bottom: 1em;
+}
+
+/*
+ Various elements that really should be lining up don't because they have inconsistent margins
+*/
+.torMarginFix {
+ margin-left : 4px;
+ margin-right : 4px;
+}
+
+/*
+ This hbox is hidden by css here by default so that the
+ xul dialog allocates enough screen space for the error message
+ element, otherwise it gets cut off since dialog's overflow is hidden
+*/
+hbox#torPreferences-requestBridge-incorrectCaptchaHbox {
+ visibility: hidden;
+}
+
+image#torPreferences-requestBridge-errorIcon {
+ list-style-image: url("chrome://browser/skin/warning.svg");
+}
+
+groupbox#torPreferences-bridges-group textarea {
+ white-space: pre;
+ overflow: auto;
+}
+
+textarea#torPreferences-torDialog-textarea {
+ -moz-box-flex: 1;
+ font-family: monospace;
+ font-size: 0.8em;
+ white-space: pre;
+ overflow: auto;
+ /* 10 lines */
+ min-height: 20em;
+}
\ No newline at end of file
diff --git a/browser/components/torpreferences/content/torPreferencesIcon.svg b/browser/components/torpreferences/content/torPreferencesIcon.svg
new file mode 100644
index 000000000000..d7895f1107c5
--- /dev/null
+++ b/browser/components/torpreferences/content/torPreferencesIcon.svg
@@ -0,0 +1,5 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
+ <g fill="context-fill" fill-opacity="context-fill-opacity" fill-rule="nonzero">
+ <path d="M12.0246161,21.8174863 L12.0246161,20.3628098 C16.6324777,20.3495038 20.3634751,16.6108555 20.3634751,11.9996673 C20.3634751,7.38881189 16.6324777,3.65016355 12.0246161,3.63685757 L12.0246161,2.18218107 C17.4358264,2.1958197 21.8178189,6.58546322 21.8178189,11.9996673 C21.8178189,17.4142042 17.4358264,21.8041803 12.0246161,21.8174863 L12.0246161,21.8174863 Z M12.0246161,16.7259522 C14.623607,16.7123136 16.7272828,14.6023175 16.7272828,11.9996673 C16.7272828,9.39734991 14.623607,7.28735377 12.0246161,7.27371516 L12.0246161,5.81937131 C15.4272884,5.8326773 18.1819593,8.59400123 18.1819593,11.9996673 C18.1819593,15.4056661 15.4272884,18.1669901 12.0246161,18.1802961 L12.0246161,16.7259522 Z M12.0246161,9.45556355 C13.4187503,9.46886953 14.5454344,10.6022066 14.5454344,11.9996673 C14.5454344,13.3974608 13.4187503,14.5307978 12.0246161,14.5441038 L12.0246161,9.45556355 Z M0,11.9996673 C0,18.6273771 5.37229031,24 12,24 C18.6273771,24 24,18.6273771 24,11.9996673 C24,5.37229031
18.6273771,0 12,0 C5.37229031,0 0,5.37229031 0,11.9996673 Z"/>
+ </g>
+</svg>
\ No newline at end of file
diff --git a/browser/components/torpreferences/content/torProxySettings.jsm b/browser/components/torpreferences/content/torProxySettings.jsm
new file mode 100644
index 000000000000..98bb5e8d5cbf
--- /dev/null
+++ b/browser/components/torpreferences/content/torProxySettings.jsm
@@ -0,0 +1,245 @@
+"use strict";
+
+var EXPORTED_SYMBOLS = [
+ "TorProxyType",
+ "TorProxySettings",
+ "makeTorProxySettingsNone",
+ "makeTorProxySettingsSocks4",
+ "makeTorProxySettingsSocks5",
+ "makeTorProxySettingsHTTPS",
+];
+
+const { TorProtocolService } = ChromeUtils.import(
+ "resource:///modules/TorProtocolService.jsm"
+);
+const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm");
+const { parseAddrPort, parseUsernamePassword } = ChromeUtils.import(
+ "chrome://browser/content/torpreferences/parseFunctions.jsm"
+);
+
+const TorProxyType = {
+ NONE: "NONE",
+ SOCKS4: "SOCKS4",
+ SOCKS5: "SOCKS5",
+ HTTPS: "HTTPS",
+};
+
+class TorProxySettings {
+ constructor() {
+ this._proxyType = TorProxyType.NONE;
+ this._proxyAddress = undefined;
+ this._proxyPort = undefined;
+ this._proxyUsername = undefined;
+ this._proxyPassword = undefined;
+ }
+
+ get type() {
+ return this._proxyType;
+ }
+ get address() {
+ return this._proxyAddress;
+ }
+ get port() {
+ return this._proxyPort;
+ }
+ get username() {
+ return this._proxyUsername;
+ }
+ get password() {
+ return this._proxyPassword;
+ }
+ get proxyURI() {
+ switch (this._proxyType) {
+ case TorProxyType.SOCKS4:
+ return `socks4a://${this._proxyAddress}:${this._proxyPort}`;
+ case TorProxyType.SOCKS5:
+ if (this._proxyUsername) {
+ return `socks5://${this._proxyUsername}:${this._proxyPassword}@${
+ this._proxyAddress
+ }:${this._proxyPort}`;
+ }
+ return `socks5://${this._proxyAddress}:${this._proxyPort}`;
+ case TorProxyType.HTTPS:
+ if (this._proxyUsername) {
+ return `http://${this._proxyUsername}:${this._proxyPassword}@${
+ this._proxyAddress
+ }:${this._proxyPort}`;
+ }
+ return `http://${this._proxyAddress}:${this._proxyPort}`;
+ }
+ return undefined;
+ }
+
+ // attempts to read proxy settings from Tor daemon
+ readSettings() {
+ // SOCKS4
+ {
+ let addressPort = TorProtocolService.readStringSetting(
+ TorStrings.configKeys.socks4Proxy
+ );
+ if (addressPort) {
+ // address+port
+ let [proxyAddress, proxyPort] = parseAddrPort(addressPort);
+
+ this._proxyType = TorProxyType.SOCKS4;
+ this._proxyAddress = proxyAddress;
+ this._proxyPort = proxyPort;
+ this._proxyUsername = "";
+ this._proxyPassword = "";
+
+ return;
+ }
+ }
+
+ // SOCKS5
+ {
+ let addressPort = TorProtocolService.readStringSetting(
+ TorStrings.configKeys.socks5Proxy
+ );
+
+ if (addressPort) {
+ // address+port
+ let [proxyAddress, proxyPort] = parseAddrPort(addressPort);
+ // username
+ let proxyUsername = TorProtocolService.readStringSetting(
+ TorStrings.configKeys.socks5ProxyUsername
+ );
+ // password
+ let proxyPassword = TorProtocolService.readStringSetting(
+ TorStrings.configKeys.socks5ProxyPassword
+ );
+
+ this._proxyType = TorProxyType.SOCKS5;
+ this._proxyAddress = proxyAddress;
+ this._proxyPort = proxyPort;
+ this._proxyUsername = proxyUsername;
+ this._proxyPassword = proxyPassword;
+
+ return;
+ }
+ }
+
+ // HTTP
+ {
+ let addressPort = TorProtocolService.readStringSetting(
+ TorStrings.configKeys.httpsProxy
+ );
+
+ if (addressPort) {
+ // address+port
+ let [proxyAddress, proxyPort] = parseAddrPort(addressPort);
+
+ // username:password
+ let proxyAuthenticator = TorProtocolService.readStringSetting(
+ TorStrings.configKeys.httpsProxyAuthenticator
+ );
+
+ let [proxyUsername, proxyPassword] = ["", ""];
+ if (proxyAuthenticator) {
+ [proxyUsername, proxyPassword] = parseUsernamePassword(
+ proxyAuthenticator
+ );
+ }
+
+ this._proxyType = TorProxyType.HTTPS;
+ this._proxyAddress = proxyAddress;
+ this._proxyPort = proxyPort;
+ this._proxyUsername = proxyUsername;
+ this._proxyPassword = proxyPassword;
+ }
+ }
+ // no proxy settings
+ } /* TorProxySettings::ReadFromTor() */
+
+ // attempts to write proxy settings to Tor daemon
+ // throws on error
+ writeSettings() {
+ let settingsObject = new Map();
+
+ // init proxy related settings to null so Tor daemon resets them
+ settingsObject.set(TorStrings.configKeys.socks4Proxy, null);
+ settingsObject.set(TorStrings.configKeys.socks5Proxy, null);
+ settingsObject.set(TorStrings.configKeys.socks5ProxyUsername, null);
+ settingsObject.set(TorStrings.configKeys.socks5ProxyPassword, null);
+ settingsObject.set(TorStrings.configKeys.httpsProxy, null);
+ settingsObject.set(TorStrings.configKeys.httpsProxyAuthenticator, null);
+
+ switch (this._proxyType) {
+ case TorProxyType.SOCKS4:
+ settingsObject.set(
+ TorStrings.configKeys.socks4Proxy,
+ `${this._proxyAddress}:${this._proxyPort}`
+ );
+ break;
+ case TorProxyType.SOCKS5:
+ settingsObject.set(
+ TorStrings.configKeys.socks5Proxy,
+ `${this._proxyAddress}:${this._proxyPort}`
+ );
+ settingsObject.set(
+ TorStrings.configKeys.socks5ProxyUsername,
+ this._proxyUsername
+ );
+ settingsObject.set(
+ TorStrings.configKeys.socks5ProxyPassword,
+ this._proxyPassword
+ );
+ break;
+ case TorProxyType.HTTPS:
+ settingsObject.set(
+ TorStrings.configKeys.httpsProxy,
+ `${this._proxyAddress}:${this._proxyPort}`
+ );
+ settingsObject.set(
+ TorStrings.configKeys.httpsProxyAuthenticator,
+ `${this._proxyUsername}:${this._proxyPassword}`
+ );
+ break;
+ }
+
+ TorProtocolService.writeSettings(settingsObject);
+ } /* TorProxySettings::WriteToTor() */
+}
+
+// factory methods for our various supported proxies
+function makeTorProxySettingsNone() {
+ return new TorProxySettings();
+}
+
+function makeTorProxySettingsSocks4(aProxyAddress, aProxyPort) {
+ let retval = new TorProxySettings();
+ retval._proxyType = TorProxyType.SOCKS4;
+ retval._proxyAddress = aProxyAddress;
+ retval._proxyPort = aProxyPort;
+ return retval;
+}
+
+function makeTorProxySettingsSocks5(
+ aProxyAddress,
+ aProxyPort,
+ aProxyUsername,
+ aProxyPassword
+) {
+ let retval = new TorProxySettings();
+ retval._proxyType = TorProxyType.SOCKS5;
+ retval._proxyAddress = aProxyAddress;
+ retval._proxyPort = aProxyPort;
+ retval._proxyUsername = aProxyUsername;
+ retval._proxyPassword = aProxyPassword;
+ return retval;
+}
+
+function makeTorProxySettingsHTTPS(
+ aProxyAddress,
+ aProxyPort,
+ aProxyUsername,
+ aProxyPassword
+) {
+ let retval = new TorProxySettings();
+ retval._proxyType = TorProxyType.HTTPS;
+ retval._proxyAddress = aProxyAddress;
+ retval._proxyPort = aProxyPort;
+ retval._proxyUsername = aProxyUsername;
+ retval._proxyPassword = aProxyPassword;
+ return retval;
+}
diff --git a/browser/components/torpreferences/jar.mn b/browser/components/torpreferences/jar.mn
new file mode 100644
index 000000000000..857bc9ee3eac
--- /dev/null
+++ b/browser/components/torpreferences/jar.mn
@@ -0,0 +1,14 @@
+browser.jar:
+ content/browser/torpreferences/parseFunctions.jsm (content/parseFunctions.jsm)
+ content/browser/torpreferences/requestBridgeDialog.xhtml (content/requestBridgeDialog.xhtml)
+ content/browser/torpreferences/requestBridgeDialog.jsm (content/requestBridgeDialog.jsm)
+ content/browser/torpreferences/torBridgeSettings.jsm (content/torBridgeSettings.jsm)
+ content/browser/torpreferences/torCategory.inc.xhtml (content/torCategory.inc.xhtml)
+ content/browser/torpreferences/torFirewallSettings.jsm (content/torFirewallSettings.jsm)
+ content/browser/torpreferences/torLogDialog.jsm (content/torLogDialog.jsm)
+ content/browser/torpreferences/torLogDialog.xhtml (content/torLogDialog.xhtml)
+ content/browser/torpreferences/torPane.js (content/torPane.js)
+ content/browser/torpreferences/torPane.xhtml (content/torPane.xhtml)
+ content/browser/torpreferences/torPreferences.css (content/torPreferences.css)
+ content/browser/torpreferences/torPreferencesIcon.svg (content/torPreferencesIcon.svg)
+ content/browser/torpreferences/torProxySettings.jsm (content/torProxySettings.jsm)
diff --git a/browser/components/torpreferences/moz.build b/browser/components/torpreferences/moz.build
new file mode 100644
index 000000000000..7e103239c8d6
--- /dev/null
+++ b/browser/components/torpreferences/moz.build
@@ -0,0 +1 @@
+JAR_MANIFESTS += ['jar.mn']
diff --git a/browser/modules/BridgeDB.jsm b/browser/modules/BridgeDB.jsm
new file mode 100644
index 000000000000..2caa26b4e2e0
--- /dev/null
+++ b/browser/modules/BridgeDB.jsm
@@ -0,0 +1,110 @@
+"use strict";
+
+var EXPORTED_SYMBOLS = ["BridgeDB"];
+
+const { TorLauncherBridgeDB } = ChromeUtils.import(
+ "resource://torlauncher/modules/tl-bridgedb.jsm"
+);
+const { TorProtocolService } = ChromeUtils.import(
+ "resource:///modules/TorProtocolService.jsm"
+);
+const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm");
+
+var BridgeDB = {
+ _moatRequestor: null,
+ _currentCaptchaInfo: null,
+ _bridges: null,
+
+ get currentCaptchaImage() {
+ if (this._currentCaptchaInfo) {
+ return this._currentCaptchaInfo.captchaImage;
+ }
+ return null;
+ },
+
+ get currentBridges() {
+ return this._bridges;
+ },
+
+ submitCaptchaGuess(aCaptchaSolution) {
+ if (this._moatRequestor && this._currentCaptchaInfo) {
+ return this._moatRequestor
+ .finishFetch(
+ this._currentCaptchaInfo.transport,
+ this._currentCaptchaInfo.challenge,
+ aCaptchaSolution
+ )
+ .then(aBridgeInfo => {
+ this._moatRequestor.close();
+ this._moatRequestor = null;
+ this._currentCaptchaInfo = null;
+ this._bridges = aBridgeInfo.bridges;
+ // array of bridge strings
+ return this._bridges;
+ });
+ }
+
+ return new Promise((aResponse, aReject) => {
+ aReject(new Error("Invalid _moatRequestor or _currentCaptchaInfo"));
+ });
+ },
+
+ requestNewCaptchaImage(aProxyURI) {
+ // close and clear out existing state on captcha request
+ this.close();
+
+ let transportPlugins = TorProtocolService.readStringArraySetting(
+ TorStrings.configKeys.clientTransportPlugin
+ );
+
+ let meekClientPath;
+ let meekTransport; // We support both "meek" and "meek_lite".
+ let meekClientArgs;
+ // TODO: shouldn't this early out once meek settings are found?
+ for (const line of transportPlugins) {
+ // Parse each ClientTransportPlugin line and look for the meek or
+ // meek_lite transport. This code works a lot like the Tor daemon's
+ // parse_transport_line() function.
+ let tokens = line.split(" ");
+ if (tokens.length > 2 && tokens[1] == "exec") {
+ let transportArray = tokens[0].split(",").map(aStr => aStr.trim());
+ let transport = transportArray.find(
+ aTransport => aTransport === "meek"
+ );
+ if (!transport) {
+ transport = transportArray.find(
+ aTransport => aTransport === "meek_lite"
+ );
+ }
+ if (transport) {
+ meekTransport = transport;
+ meekClientPath = tokens[2];
+ meekClientArgs = tokens.slice(3);
+ }
+ }
+ }
+
+ this._moatRequestor = TorLauncherBridgeDB.createMoatRequestor();
+
+ return this._moatRequestor
+ .init(aProxyURI, meekTransport, meekClientPath, meekClientArgs)
+ .then(() => {
+ // TODO: get this from TorLauncherUtil
+ let bridgeType = "obfs4";
+ return this._moatRequestor.fetchBridges([bridgeType]);
+ })
+ .then(aCaptchaInfo => {
+ // cache off the current captcha info as the challenge is needed for response
+ this._currentCaptchaInfo = aCaptchaInfo;
+ return aCaptchaInfo.captchaImage;
+ });
+ },
+
+ close() {
+ if (this._moatRequestor) {
+ this._moatRequestor.close();
+ this._moatRequestor = null;
+ }
+ this._currentCaptchaInfo = null;
+ },
+};
diff --git a/browser/modules/TorProtocolService.jsm b/browser/modules/TorProtocolService.jsm
new file mode 100644
index 000000000000..b4e6ed9a3253
--- /dev/null
+++ b/browser/modules/TorProtocolService.jsm
@@ -0,0 +1,212 @@
+"use strict";
+
+var EXPORTED_SYMBOLS = ["TorProtocolService"];
+
+const { TorLauncherUtil } = ChromeUtils.import(
+ "resource://torlauncher/modules/tl-util.jsm"
+);
+
+var TorProtocolService = {
+ _tlps: Cc["@torproject.org/torlauncher-protocol-service;1"].getService(
+ Ci.nsISupports
+ ).wrappedJSObject,
+
+ // maintain a map of tor settings set by Tor Browser so that we don't
+ // repeatedly set the same key/values over and over
+ // this map contains string keys to primitive or array values
+ _settingsCache: new Map(),
+
+ _typeof(aValue) {
+ switch (typeof aValue) {
+ case "boolean":
+ return "boolean";
+ case "string":
+ return "string";
+ case "object":
+ if (aValue == null) {
+ return "null";
+ } else if (Array.isArray(aValue)) {
+ return "array";
+ }
+ return "object";
+ }
+ return "unknown";
+ },
+
+ _assertValidSettingKey(aSetting) {
+ // ensure the 'key' is a string
+ if (typeof aSetting != "string") {
+ throw new Error(
+ `Expected setting of type string but received ${typeof aSetting}`
+ );
+ }
+ },
+
+ _assertValidSetting(aSetting, aValue) {
+ this._assertValidSettingKey(aSetting);
+
+ const valueType = this._typeof(aValue);
+ switch (valueType) {
+ case "boolean":
+ case "string":
+ case "null":
+ return;
+ case "array":
+ for (const element of aValue) {
+ if (typeof element != "string") {
+ throw new Error(
+ `Setting '${aSetting}' array contains value of invalid type '${typeof element}'`
+ );
+ }
+ }
+ return;
+ default:
+ throw new Error(
+ `Invalid object type received for setting '${aSetting}'`
+ );
+ }
+ },
+
+ // takes a Map containing tor settings
+ // throws on error
+ writeSettings(aSettingsObj) {
+ // only write settings that have changed
+ let newSettings = new Map();
+ for (const [setting, value] of aSettingsObj) {
+ let saveSetting = false;
+
+ // make sure we have valid data here
+ this._assertValidSetting(setting, value);
+
+ if (!this._settingsCache.has(setting)) {
+ // no cached setting, so write
+ saveSetting = true;
+ } else {
+ const cachedValue = this._settingsCache.get(setting);
+ if (value != cachedValue) {
+ // compare arrays member-wise
+ if (Array.isArray(value) && Array.isArray(cachedValue)) {
+ if (value.length != cachedValue.length) {
+ saveSetting = true;
+ } else {
+ const arrayLength = value.length;
+ for (let i = 0; i < arrayLength; ++i) {
+ if (value[i] != cachedValue[i]) {
+ saveSetting = true;
+ break;
+ }
+ }
+ }
+ } else {
+ // some other different values
+ saveSetting = true;
+ }
+ }
+ }
+
+ if (saveSetting) {
+ newSettings.set(setting, value);
+ }
+ }
+
+ // only write if new setting to save
+ if (newSettings.size > 0) {
+ // convert settingsObject map to js object for torlauncher-protocol-service
+ let settingsObject = {};
+ for (const [setting, value] of newSettings) {
+ settingsObject[setting] = value;
+ }
+
+ let errorObject = {};
+ if (!this._tlps.TorSetConfWithReply(settingsObject, errorObject)) {
+ throw new Error(errorObject.details);
+ }
+
+ // save settings to cache after successfully writing to Tor
+ for (const [setting, value] of newSettings) {
+ this._settingsCache.set(setting, value);
+ }
+ }
+ },
+
+ _readSetting(aSetting) {
+ this._assertValidSettingKey(aSetting);
+ let reply = this._tlps.TorGetConf(aSetting);
+ if (this._tlps.TorCommandSucceeded(reply)) {
+ return reply.lineArray;
+ }
+ throw new Error(reply.lineArray.join("\n"));
+ },
+
+ _readBoolSetting(aSetting) {
+ let lineArray = this._readSetting(aSetting);
+ if (lineArray.length != 1) {
+ throw new Error(
+ `Expected an array with length 1 but received array of length ${
+ lineArray.length
+ }`
+ );
+ }
+
+ let retval = lineArray[0];
+ switch (retval) {
+ case "0":
+ return false;
+ case "1":
+ return true;
+ default:
+ throw new Error(`Expected boolean (1 or 0) but received '${retval}'`);
+ }
+ },
+
+ _readStringSetting(aSetting) {
+ let lineArray = this._readSetting(aSetting);
+ if (lineArray.length != 1) {
+ throw new Error(
+ `Expected an array with length 1 but received array of length ${
+ lineArray.length
+ }`
+ );
+ }
+ return lineArray[0];
+ },
+
+ _readStringArraySetting(aSetting) {
+ let lineArray = this._readSetting(aSetting);
+ return lineArray;
+ },
+
+ readBoolSetting(aSetting) {
+ let value = this._readBoolSetting(aSetting);
+ this._settingsCache.set(aSetting, value);
+ return value;
+ },
+
+ readStringSetting(aSetting) {
+ let value = this._readStringSetting(aSetting);
+ this._settingsCache.set(aSetting, value);
+ return value;
+ },
+
+ readStringArraySetting(aSetting) {
+ let value = this._readStringArraySetting(aSetting);
+ this._settingsCache.set(aSetting, value);
+ return value;
+ },
+
+ // writes current tor settings to disk
+ flushSettings() {
+ this._tlps.TorSendCommand("SAVECONF");
+ },
+
+ getLog() {
+ let countObj = { value: 0 };
+ let torLog = this._tlps.TorGetLog(countObj);
+ return torLog;
+ },
+
+ // true if we launched and control tor, false if using system tor
+ get ownsTorDaemon() {
+ return TorLauncherUtil.shouldStartAndOwnTor;
+ },
+};
diff --git a/browser/modules/moz.build b/browser/modules/moz.build
index 61fe5371e48f..5fb78d1c07a8 100644
--- a/browser/modules/moz.build
+++ b/browser/modules/moz.build
@@ -128,6 +128,7 @@ EXTRA_JS_MODULES += [
'AboutNewTab.jsm',
'AppUpdater.jsm',
'AsyncTabSwitcher.jsm',
+ 'BridgeDB.jsm',
'BrowserUsageTelemetry.jsm',
'BrowserWindowTracker.jsm',
'ContentCrashHandlers.jsm',
@@ -154,6 +155,7 @@ EXTRA_JS_MODULES += [
'TabUnloader.jsm',
'ThemeVariableMap.jsm',
'TopSiteAttribution.jsm',
+ 'TorProtocolService.jsm',
'TorStrings.jsm',
'TransientPrefs.jsm',
'webrtcUI.jsm',
1
0

[translation/tails-onioncircuits_release] https://gitweb.torproject.org/translation.git/commit/?h=tails-onioncircuits_release
by translation@torproject.org 07 Aug '20
by translation@torproject.org 07 Aug '20
07 Aug '20
commit ec4ab594ea3fba3ed4a9eb4a0cf9f61e1564f83e
Author: Translation commit bot <translation(a)torproject.org>
Date: Fri Aug 7 13:16:53 2020 +0000
https://gitweb.torproject.org/translation.git/commit/?h=tails-onioncircuits…
---
ca/onioncircuits.po | 2 +-
cs/onioncircuits.po | 2 +-
da/onioncircuits.po | 2 +-
de/onioncircuits.po | 2 +-
el/onioncircuits.po | 2 +-
es/onioncircuits.po | 2 +-
es_AR/onioncircuits.po | 2 +-
fr/onioncircuits.po | 2 +-
ga/onioncircuits.po | 2 +-
he/onioncircuits.po | 2 +-
hr/onioncircuits.po | 2 +-
hu/onioncircuits.po | 2 +-
it/onioncircuits.po | 2 +-
lt/onioncircuits.po | 2 +-
mk/onioncircuits.po | 2 +-
nl/onioncircuits.po | 2 +-
pt_BR/onioncircuits.po | 2 +-
pt_PT/onioncircuits.po | 2 +-
ro/onioncircuits.po | 2 +-
sv/onioncircuits.po | 2 +-
tr/onioncircuits.po | 2 +-
zh_CN/onioncircuits.po | 2 +-
22 files changed, 22 insertions(+), 22 deletions(-)
diff --git a/ca/onioncircuits.po b/ca/onioncircuits.po
index e350db7fc1..4fe7351253 100644
--- a/ca/onioncircuits.po
+++ b/ca/onioncircuits.po
@@ -9,7 +9,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Tor Project\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-08-03 13:00+0000\n"
+"POT-Creation-Date: 2020-08-07 08:17+0200\n"
"PO-Revision-Date: 2017-09-23 22:22+0000\n"
"Last-Translator: laia_\n"
"Language-Team: Catalan (http://www.transifex.com/otf/torproject/language/ca/)\n"
diff --git a/cs/onioncircuits.po b/cs/onioncircuits.po
index 78cb82dc28..27703b6ecb 100644
--- a/cs/onioncircuits.po
+++ b/cs/onioncircuits.po
@@ -12,7 +12,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Tor Project\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-08-03 13:00+0000\n"
+"POT-Creation-Date: 2020-08-07 08:17+0200\n"
"PO-Revision-Date: 2019-12-26 13:59+0000\n"
"Last-Translator: Michal Stanke <mstanke(a)mozilla.cz>\n"
"Language-Team: Czech (http://www.transifex.com/otf/torproject/language/cs/)\n"
diff --git a/da/onioncircuits.po b/da/onioncircuits.po
index 60fdcbe369..603df73a73 100644
--- a/da/onioncircuits.po
+++ b/da/onioncircuits.po
@@ -10,7 +10,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Tor Project\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-08-03 13:00+0000\n"
+"POT-Creation-Date: 2020-08-07 08:17+0200\n"
"PO-Revision-Date: 2020-04-29 14:35+0000\n"
"Last-Translator: scootergrisen\n"
"Language-Team: Danish (http://www.transifex.com/otf/torproject/language/da/)\n"
diff --git a/de/onioncircuits.po b/de/onioncircuits.po
index b64d0f6237..dd7672671d 100644
--- a/de/onioncircuits.po
+++ b/de/onioncircuits.po
@@ -9,7 +9,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Tor Project\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-08-03 13:00+0000\n"
+"POT-Creation-Date: 2020-08-07 08:17+0200\n"
"PO-Revision-Date: 2018-09-01 16:28+0000\n"
"Last-Translator: spriver\n"
"Language-Team: German (http://www.transifex.com/otf/torproject/language/de/)\n"
diff --git a/el/onioncircuits.po b/el/onioncircuits.po
index 2f2dbc250e..191f64514c 100644
--- a/el/onioncircuits.po
+++ b/el/onioncircuits.po
@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Tor Project\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-08-03 13:00+0000\n"
+"POT-Creation-Date: 2020-08-07 08:17+0200\n"
"PO-Revision-Date: 2018-09-26 15:04+0000\n"
"Last-Translator: A Papac <ap909219(a)protonmail.com>\n"
"Language-Team: Greek (http://www.transifex.com/otf/torproject/language/el/)\n"
diff --git a/es/onioncircuits.po b/es/onioncircuits.po
index 773370d9a9..66c7443f2d 100644
--- a/es/onioncircuits.po
+++ b/es/onioncircuits.po
@@ -9,7 +9,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Tor Project\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-08-03 13:00+0000\n"
+"POT-Creation-Date: 2020-08-07 08:17+0200\n"
"PO-Revision-Date: 2017-09-23 22:22+0000\n"
"Last-Translator: Emma Peel\n"
"Language-Team: Spanish (http://www.transifex.com/otf/torproject/language/es/)\n"
diff --git a/es_AR/onioncircuits.po b/es_AR/onioncircuits.po
index 386e3923cc..a5bedda283 100644
--- a/es_AR/onioncircuits.po
+++ b/es_AR/onioncircuits.po
@@ -9,7 +9,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Tor Project\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-08-03 13:00+0000\n"
+"POT-Creation-Date: 2020-08-07 08:17+0200\n"
"PO-Revision-Date: 2018-09-15 02:45+0000\n"
"Last-Translator: Zuhualime Akoochimoya\n"
"Language-Team: Spanish (Argentina) (http://www.transifex.com/otf/torproject/language/es_AR/)\n"
diff --git a/fr/onioncircuits.po b/fr/onioncircuits.po
index 121e48be61..2b99a25d7d 100644
--- a/fr/onioncircuits.po
+++ b/fr/onioncircuits.po
@@ -14,7 +14,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Tor Project\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-08-03 13:00+0000\n"
+"POT-Creation-Date: 2020-08-07 08:17+0200\n"
"PO-Revision-Date: 2020-04-03 17:24+0000\n"
"Last-Translator: AO <ao(a)localizationlab.org>\n"
"Language-Team: French (http://www.transifex.com/otf/torproject/language/fr/)\n"
diff --git a/ga/onioncircuits.po b/ga/onioncircuits.po
index 34afc60851..1ffbd509a9 100644
--- a/ga/onioncircuits.po
+++ b/ga/onioncircuits.po
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Tor Project\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-08-03 13:00+0000\n"
+"POT-Creation-Date: 2020-08-07 08:17+0200\n"
"PO-Revision-Date: 2017-09-29 04:32+0000\n"
"Last-Translator: Kevin Scannell <kscanne(a)gmail.com>\n"
"Language-Team: Irish (http://www.transifex.com/otf/torproject/language/ga/)\n"
diff --git a/he/onioncircuits.po b/he/onioncircuits.po
index 649159360c..51d5e9c133 100644
--- a/he/onioncircuits.po
+++ b/he/onioncircuits.po
@@ -9,7 +9,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Tor Project\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-08-03 13:00+0000\n"
+"POT-Creation-Date: 2020-08-07 08:17+0200\n"
"PO-Revision-Date: 2019-11-08 20:20+0000\n"
"Last-Translator: ION\n"
"Language-Team: Hebrew (http://www.transifex.com/otf/torproject/language/he/)\n"
diff --git a/hr/onioncircuits.po b/hr/onioncircuits.po
index 027d4a382a..2ed1753ac1 100644
--- a/hr/onioncircuits.po
+++ b/hr/onioncircuits.po
@@ -9,7 +9,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Tor Project\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-08-03 13:00+0000\n"
+"POT-Creation-Date: 2020-08-07 08:17+0200\n"
"PO-Revision-Date: 2020-04-10 13:11+0000\n"
"Last-Translator: milotype <mail(a)milotype.de>\n"
"Language-Team: Croatian (http://www.transifex.com/otf/torproject/language/hr/)\n"
diff --git a/hu/onioncircuits.po b/hu/onioncircuits.po
index 6c5e987118..708a99f76d 100644
--- a/hu/onioncircuits.po
+++ b/hu/onioncircuits.po
@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Tor Project\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-08-03 13:00+0000\n"
+"POT-Creation-Date: 2020-08-07 08:17+0200\n"
"PO-Revision-Date: 2018-09-12 21:04+0000\n"
"Last-Translator: vargaviktor <viktor.varga(a)gmail.com>\n"
"Language-Team: Hungarian (http://www.transifex.com/otf/torproject/language/hu/)\n"
diff --git a/it/onioncircuits.po b/it/onioncircuits.po
index bc8646c266..c3da653848 100644
--- a/it/onioncircuits.po
+++ b/it/onioncircuits.po
@@ -12,7 +12,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Tor Project\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-08-03 13:00+0000\n"
+"POT-Creation-Date: 2020-08-07 08:17+0200\n"
"PO-Revision-Date: 2019-08-21 17:31+0000\n"
"Last-Translator: Davide Sant <spuuu(a)outlook.it>\n"
"Language-Team: Italian (http://www.transifex.com/otf/torproject/language/it/)\n"
diff --git a/lt/onioncircuits.po b/lt/onioncircuits.po
index 8f647cb226..54d7853874 100644
--- a/lt/onioncircuits.po
+++ b/lt/onioncircuits.po
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Tor Project\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-08-03 13:00+0000\n"
+"POT-Creation-Date: 2020-08-07 08:17+0200\n"
"PO-Revision-Date: 2019-11-30 10:25+0000\n"
"Last-Translator: Moo\n"
"Language-Team: Lithuanian (http://www.transifex.com/otf/torproject/language/lt/)\n"
diff --git a/mk/onioncircuits.po b/mk/onioncircuits.po
index 2a774b089d..d3be84449d 100644
--- a/mk/onioncircuits.po
+++ b/mk/onioncircuits.po
@@ -10,7 +10,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Tor Project\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-08-03 13:00+0000\n"
+"POT-Creation-Date: 2020-08-07 08:17+0200\n"
"PO-Revision-Date: 2019-10-08 22:35+0000\n"
"Last-Translator: Zarko Gjurov <zarkogjurov(a)gmail.com>\n"
"Language-Team: Macedonian (http://www.transifex.com/otf/torproject/language/mk/)\n"
diff --git a/nl/onioncircuits.po b/nl/onioncircuits.po
index ac9ce7aa40..2faffc9ec2 100644
--- a/nl/onioncircuits.po
+++ b/nl/onioncircuits.po
@@ -13,7 +13,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Tor Project\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-08-03 13:00+0000\n"
+"POT-Creation-Date: 2020-08-07 08:17+0200\n"
"PO-Revision-Date: 2019-10-07 22:48+0000\n"
"Last-Translator: Nathan Follens\n"
"Language-Team: Dutch (http://www.transifex.com/otf/torproject/language/nl/)\n"
diff --git a/pt_BR/onioncircuits.po b/pt_BR/onioncircuits.po
index 9184c68d8b..d488cedfde 100644
--- a/pt_BR/onioncircuits.po
+++ b/pt_BR/onioncircuits.po
@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Tor Project\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-08-03 13:00+0000\n"
+"POT-Creation-Date: 2020-08-07 08:17+0200\n"
"PO-Revision-Date: 2020-07-31 16:42+0000\n"
"Last-Translator: yyyyyyyan <contact(a)yyyyyyyan.tech>\n"
"Language-Team: Portuguese (Brazil) (http://www.transifex.com/otf/torproject/language/pt_BR/)\n"
diff --git a/pt_PT/onioncircuits.po b/pt_PT/onioncircuits.po
index fd6f84e796..19ea5ddb11 100644
--- a/pt_PT/onioncircuits.po
+++ b/pt_PT/onioncircuits.po
@@ -10,7 +10,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Tor Project\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-08-03 13:00+0000\n"
+"POT-Creation-Date: 2020-08-07 08:17+0200\n"
"PO-Revision-Date: 2020-01-05 18:53+0000\n"
"Last-Translator: Manuela Silva <manuelarodsilva(a)gmail.com>\n"
"Language-Team: Portuguese (Portugal) (http://www.transifex.com/otf/torproject/language/pt_PT/)\n"
diff --git a/ro/onioncircuits.po b/ro/onioncircuits.po
index d155abbd01..b7b79f4476 100644
--- a/ro/onioncircuits.po
+++ b/ro/onioncircuits.po
@@ -10,7 +10,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Tor Project\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-08-03 13:00+0000\n"
+"POT-Creation-Date: 2020-08-07 08:17+0200\n"
"PO-Revision-Date: 2019-01-10 14:33+0000\n"
"Last-Translator: A C <ana(a)shiftout.net>\n"
"Language-Team: Romanian (http://www.transifex.com/otf/torproject/language/ro/)\n"
diff --git a/sv/onioncircuits.po b/sv/onioncircuits.po
index 5098f871a3..b5275f88b7 100644
--- a/sv/onioncircuits.po
+++ b/sv/onioncircuits.po
@@ -11,7 +11,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Tor Project\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-08-03 13:00+0000\n"
+"POT-Creation-Date: 2020-08-07 08:17+0200\n"
"PO-Revision-Date: 2019-11-09 18:22+0000\n"
"Last-Translator: Jonatan Nyberg <jonatan.nyberg.karl(a)gmail.com>\n"
"Language-Team: Swedish (http://www.transifex.com/otf/torproject/language/sv/)\n"
diff --git a/tr/onioncircuits.po b/tr/onioncircuits.po
index c26089abd2..186951f1e9 100644
--- a/tr/onioncircuits.po
+++ b/tr/onioncircuits.po
@@ -9,7 +9,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Tor Project\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-08-03 13:00+0000\n"
+"POT-Creation-Date: 2020-08-07 08:17+0200\n"
"PO-Revision-Date: 2019-01-15 12:27+0000\n"
"Last-Translator: falcontr06\n"
"Language-Team: Turkish (http://www.transifex.com/otf/torproject/language/tr/)\n"
diff --git a/zh_CN/onioncircuits.po b/zh_CN/onioncircuits.po
index ae74ac9924..988055a469 100644
--- a/zh_CN/onioncircuits.po
+++ b/zh_CN/onioncircuits.po
@@ -10,7 +10,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Tor Project\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-08-03 13:00+0000\n"
+"POT-Creation-Date: 2020-08-07 08:17+0200\n"
"PO-Revision-Date: 2019-05-30 00:48+0000\n"
"Last-Translator: ff98sha\n"
"Language-Team: Chinese (China) (http://www.transifex.com/otf/torproject/language/zh_CN/)\n"
1
0

[stem/master] get_listener() integ test broke for older tor versions
by atagar@torproject.org 07 Aug '20
by atagar@torproject.org 07 Aug '20
07 Aug '20
commit 0be767b7149bfbee579b00ad513217ec51b0e31c
Author: Damian Johnson <atagar(a)torproject.org>
Date: Thu Aug 6 17:24:47 2020 -0700
get_listener() integ test broke for older tor versions
Oops, adjusting this assertion in commit 435b980c broke our tests for prior tor
versions. Caught thanks to asn.
12:42 <+asn> AssertionError: Lists differ: [('0.0.0.0', 1113), ('::', 1113)]
!= [('0.0.0.0', 1113)]
...
20:06 <+atagar> asn, nickm: Thanks for pointing out the stem test assertion
error. I'd like to approach this via a conditional. What was the tor
version where the address behavior changed?
21:38 <+nickm> atagar: somewhere in 0.4.5.x
21:38 <+nickm> I believe that ">= 0.4.5.0" will do fine
21:42 <+nickm> (since 0.4.5.1 isn't out yet)
21:48 <+atagar> perfect, thanks
---
test/integ/control/controller.py | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/test/integ/control/controller.py b/test/integ/control/controller.py
index 25566229..59a7222c 100644
--- a/test/integ/control/controller.py
+++ b/test/integ/control/controller.py
@@ -980,7 +980,12 @@ class TestController(unittest.TestCase):
runner = test.runner.get_runner()
async with await runner.get_tor_controller() as controller:
- self.assertEqual([('0.0.0.0', test.runner.ORPORT), ('::', test.runner.ORPORT)], await controller.get_listeners(Listener.OR))
+ if test.tor_version() >= stem.version.Version('0.4.5.0'):
+ expected_orports = [('0.0.0.0', test.runner.ORPORT), ('::', test.runner.ORPORT)]
+ else:
+ expected_orports = [('0.0.0.0', test.runner.ORPORT)]
+
+ self.assertEqual(expected_orports, await controller.get_listeners(Listener.OR))
self.assertEqual([], await controller.get_listeners(Listener.DIR))
self.assertEqual([('127.0.0.1', test.runner.SOCKS_PORT)], await controller.get_listeners(Listener.SOCKS))
self.assertEqual([], await controller.get_listeners(Listener.TRANS))
1
0

[stem/master] Adds support for ONION_CLIENT_AUTH_ADD, ONION_CLIENT_AUTH_REMOVE and ONION_CLIENT_AUTH_VIEW
by atagar@torproject.org 07 Aug '20
by atagar@torproject.org 07 Aug '20
07 Aug '20
commit 0dba06fd54d6e1ce844e3a1518f15e9feb375db1
Author: Miguel Jacq <mig(a)mig5.net>
Date: Thu Jun 18 13:24:40 2020 +1000
Adds support for ONION_CLIENT_AUTH_ADD, ONION_CLIENT_AUTH_REMOVE and ONION_CLIENT_AUTH_VIEW
---
stem/control.py | 82 ++++++++++++++++++++++++++++++++++++++
stem/interpreter/settings.cfg | 12 ++++++
stem/response/__init__.py | 41 ++++++++++++++-----
stem/response/onion_client_auth.py | 58 +++++++++++++++++++++++++++
test/integ/control/controller.py | 48 ++++++++++++++++++++++
5 files changed, 230 insertions(+), 11 deletions(-)
diff --git a/stem/control.py b/stem/control.py
index 0b5721e8..5bdb4a5a 100644
--- a/stem/control.py
+++ b/stem/control.py
@@ -112,6 +112,10 @@ If you're fine with allowing your script to raise exceptions then this can be mo
|- create_ephemeral_hidden_service - create a new ephemeral hidden service
|- remove_ephemeral_hidden_service - removes an ephemeral hidden service
|
+ |- add_onion_client_auth - add Client Authentication for a v3 onion service
+ |- remove_onion_client_auth - remove Client Authentication for a v3 onion service
+ |- view_onion_client_auth - view Client Authentication for a v3 onion service
+ |
|- add_event_listener - attaches an event listener to be notified of tor events
|- remove_event_listener - removes a listener so it isn't notified of further events
|
@@ -257,6 +261,7 @@ import stem.exit_policy
import stem.response
import stem.response.add_onion
import stem.response.events
+import stem.response.onion_client_auth
import stem.response.protocolinfo
import stem.socket
import stem.util
@@ -2900,6 +2905,12 @@ class Controller(BaseController):
response. For instance, only bob can access using the given newly generated
credentials...
+ Note that **basic_auth** only works for legacy (v2) onion services.
+ There is not yet any Control Port support for adding Client Auth to the
+ server side of a v3 onion service.
+
+ To add Client Authentication on the client side of a v3 onion, you can use
+ :func`~stem.control.Controller.add_onion_client_auth`.
::
>>> response = controller.create_ephemeral_hidden_service(80, basic_auth = {'bob': None})
@@ -3074,6 +3085,77 @@ class Controller(BaseController):
else:
raise stem.ProtocolError('DEL_ONION returned unexpected response code: %s' % response.code)
+ async def add_onion_client_auth(self, service_id: str, private_key_blob: str, key_type: str = 'x25519', client_name: Optional[str] = None, permanent: Optional[bool] = False) -> stem.response.onion_client_auth.OnionClientAuthAddResponse:
+ """
+ Adds Client Authentication for a v3 onion service.
+
+ :param service_id: hidden service address without the '.onion' suffix
+ :param key_type: the type of private key in use. x25519 is the only one supported right now
+ :param private_key_blob: base64 encoding of x25519 private key
+ :param client_name: optional nickname for this client
+ :param permanent: optionally flag that this client's credentials should be stored in the filesystem.
+ If this is not set, the client's credentials are epheremal and stored in memory.
+
+ :returns: **True* if the client authentication was added or replaced, **False** if it
+ was rejected by the Tor controller
+
+ :raises: :class:`stem.ControllerError` if the call fails
+ """
+
+ request = 'ONION_CLIENT_AUTH_ADD %s %s:%s' % (service_id, key_type, private_key_blob)
+
+ if client_name:
+ request += ' ClientName=%s' % client_name
+
+ flags = []
+
+ if permanent:
+ flags.append('Permanent')
+
+ if flags:
+ request += ' Flags=%s' % ','.join(flags)
+
+ response = stem.response._convert_to_onion_client_auth_add(stem.response._convert_to_onion_client_auth_add(await self.msg(request)))
+
+ return response
+
+ async def remove_onion_client_auth(self, service_id: str) -> stem.response.onion_client_auth.OnionClientAuthRemoveResponse:
+ """
+ Removes Client Authentication for a v3 onion service.
+
+ :param service_id: hidden service address without the '.onion' suffix
+
+ :returns: **True* if the client authentication was removed, (or if no such
+ service ID existed), **False** if it was rejected by the Tor controller
+
+ :raises: :class:`stem.ControllerError` if the call fails
+ """
+
+ request = 'ONION_CLIENT_AUTH_REMOVE %s' % service_id
+
+ response = stem.response._convert_to_onion_client_auth_remove(stem.response._convert_to_onion_client_auth_remove(await self.msg(request)))
+
+ return response
+
+ async def view_onion_client_auth(self, service_id: str) -> stem.response.onion_client_auth.OnionClientAuthViewResponse:
+ """
+ View Client Authentication for a v3 onion service.
+
+ :param service_id: hidden service address without the '.onion' suffix
+
+ :returns: :class:`~stem.response.onion_client_auth.OnionClientAuthViewResponse` with the
+ client_auth_credential if there were credentials to view, **True** if the service ID
+ was valid but no credentials existed, **False** if the service ID was invalid
+
+ :raises: :class:`stem.ControllerError` if the call fails
+ """
+
+ request = 'ONION_CLIENT_AUTH_VIEW %s' % service_id
+
+ response = stem.response._convert_to_onion_client_auth_view(stem.response._convert_to_onion_client_auth_view(await self.msg(request)))
+
+ return response
+
async def add_event_listener(self, listener: Callable[[stem.response.events.Event], Union[None, Awaitable[None]]], *events: 'stem.control.EventType') -> None:
"""
Directs further tor controller events to a given function. The function is
diff --git a/stem/interpreter/settings.cfg b/stem/interpreter/settings.cfg
index af96d599..a373e617 100644
--- a/stem/interpreter/settings.cfg
+++ b/stem/interpreter/settings.cfg
@@ -87,6 +87,9 @@ help.general
| CLOSESTREAM - closes the given stream
| ADD_ONION - create a new hidden service
| DEL_ONION - delete a hidden service that was created with ADD_ONION
+| ONION_CLIENT_AUTH_ADD - add Client Authentication for a v3 onion service
+| ONION_CLIENT_AUTH_REMOVE - remove Client Authentication for a v3 onion service
+| ONION_CLIENT_AUTH_VIEW - view Client Authentication for a v3 onion service
| HSFETCH - retrieve a hidden service descriptor, providing it in a HS_DESC_CONTENT event
| HSPOST - uploads a hidden service descriptor
| RESOLVE - issues an asynchronous dns or rdns request over tor
@@ -122,6 +125,9 @@ help.usage REDIRECTSTREAM => REDIRECTSTREAM StreamID Address [Port]
help.usage CLOSESTREAM => CLOSESTREAM StreamID Reason [Flag]
help.usage ADD_ONION => KeyType:KeyBlob [Flags=Flag] (Port=Port [,Target])...
help.usage DEL_ONION => ServiceID
+help.usage ONION_CLIENT_AUTH_ADD => ServiceID KeyType PrivateKeyBlob [ClientName] [Permanent]
+help.usage ONION_CLIENT_AUTH_REMOVE => ServiceID
+help.usage ONION_CLIENT_AUTH_VIEW => ServiceID
help.usage HSFETCH => HSFETCH (HSAddress/v2-DescId) [SERVER=Server]...
help.usage HSPOST => [SERVER=Server] DESCRIPTOR
help.usage RESOLVE => RESOLVE [mode=reverse] address
@@ -264,6 +270,9 @@ help.description.add_onion
help.description.del_onion
|Delete a hidden service that was created with ADD_ONION.
+help.description.onion_client_auth
+|Add, remove or view Client Authentication for a v3 onion service.
+
help.description.hsfetch
|Retrieves the descriptor for a hidden service. This is an asynchronous
|request, with the descriptor provided by a HS_DESC_CONTENT event.
@@ -326,6 +335,9 @@ autocomplete ADD_ONION NEW:RSA1024
autocomplete ADD_ONION NEW:ED25519-V3
autocomplete ADD_ONION RSA1024:
autocomplete ADD_ONION ED25519-V3:
+autocomplete ONION_CLIENT_AUTH_ADD
+autocomplete ONION_CLIENT_AUTH_REMOVE
+autocomplete ONION_CLIENT_AUTH_VIEW
autocomplete DEL_ONION
autocomplete HSFETCH
autocomplete HSPOST
diff --git a/stem/response/__init__.py b/stem/response/__init__.py
index 2e251144..e77312c8 100644
--- a/stem/response/__init__.py
+++ b/stem/response/__init__.py
@@ -45,6 +45,7 @@ __all__ = [
'events',
'getinfo',
'getconf',
+ 'onion_client_auth',
'protocolinfo',
'authchallenge',
'convert',
@@ -66,14 +67,17 @@ def convert(response_type: str, message: 'stem.response.ControlMessage', **kwarg
=================== =====
response_type Class
=================== =====
- **ADD_ONION** :class:`stem.response.add_onion.AddOnionResponse`
- **AUTHCHALLENGE** :class:`stem.response.authchallenge.AuthChallengeResponse`
- **EVENT** :class:`stem.response.events.Event` subclass
- **GETCONF** :class:`stem.response.getconf.GetConfResponse`
- **GETINFO** :class:`stem.response.getinfo.GetInfoResponse`
- **MAPADDRESS** :class:`stem.response.mapaddress.MapAddressResponse`
- **PROTOCOLINFO** :class:`stem.response.protocolinfo.ProtocolInfoResponse`
- **SINGLELINE** :class:`stem.response.SingleLineResponse`
+ **ADD_ONION** :class:`stem.response.add_onion.AddOnionResponse`
+ **AUTHCHALLENGE** :class:`stem.response.authchallenge.AuthChallengeResponse`
+ **EVENT** :class:`stem.response.events.Event` subclass
+ **GETCONF** :class:`stem.response.getconf.GetConfResponse`
+ **GETINFO** :class:`stem.response.getinfo.GetInfoResponse`
+ **MAPADDRESS** :class:`stem.response.mapaddress.MapAddressResponse`
+ **ONION_CLIENT_AUTH_ADD** :class:`stem.response.onion_client_auth.OnionClientAuthAddResponse`
+ **ONION_CLIENT_AUTH_REMOVE** :class:`stem.response.onion_client_auth.OnionClientAuthRemoveResponse`
+ **ONION_CLIENT_AUTH_VIEW** :class:`stem.response.onion_client_auth.OnionClientAuthViewResponse`
+ **PROTOCOLINFO** :class:`stem.response.protocolinfo.ProtocolInfoResponse`
+ **SINGLELINE** :class:`stem.response.SingleLineResponse`
=================== =====
:param response_type: type of tor response to convert to
@@ -101,6 +105,7 @@ def convert(response_type: str, message: 'stem.response.ControlMessage', **kwarg
import stem.response.getinfo
import stem.response.getconf
import stem.response.mapaddress
+ import stem.response.onion_client_auth
import stem.response.protocolinfo
if not isinstance(message, ControlMessage):
@@ -113,6 +118,9 @@ def convert(response_type: str, message: 'stem.response.ControlMessage', **kwarg
'GETCONF': stem.response.getconf.GetConfResponse,
'GETINFO': stem.response.getinfo.GetInfoResponse,
'MAPADDRESS': stem.response.mapaddress.MapAddressResponse,
+ 'ONION_CLIENT_AUTH_ADD': stem.response.onion_client_auth.OnionClientAuthAddResponse,
+ 'ONION_CLIENT_AUTH_REMOVE': stem.response.onion_client_auth.OnionClientAuthRemoveResponse,
+ 'ONION_CLIENT_AUTH_VIEW': stem.response.onion_client_auth.OnionClientAuthViewResponse,
'PROTOCOLINFO': stem.response.protocolinfo.ProtocolInfoResponse,
'SINGLELINE': SingleLineResponse,
}
@@ -153,6 +161,17 @@ def _convert_to_add_onion(message: 'stem.response.ControlMessage', **kwargs: Any
stem.response.convert('ADD_ONION', message)
return message # type: ignore
+def _convert_to_onion_client_auth_add(message: 'stem.response.ControlMessage', **kwargs: Any) -> 'stem.response.onion_client_auth.OnionClientAuthAddResponse':
+ stem.response.convert('ONION_CLIENT_AUTH_ADD', message)
+ return message # type: ignore
+
+def _convert_to_onion_client_auth_remove(message: 'stem.response.ControlMessage', **kwargs: Any) -> 'stem.response.onion_client_auth.OnionClientAuthRemoveResponse':
+ stem.response.convert('ONION_CLIENT_AUTH_REMOVE', message)
+ return message # type: ignore
+
+def _convert_to_onion_client_auth_view(message: 'stem.response.ControlMessage', **kwargs: Any) -> 'stem.response.onion_client_auth.OnionClientAuthViewResponse':
+ stem.response.convert('ONION_CLIENT_AUTH_VIEW', message)
+ return message # type: ignore
def _convert_to_mapaddress(message: 'stem.response.ControlMessage', **kwargs: Any) -> 'stem.response.mapaddress.MapAddressResponse':
stem.response.convert('MAPADDRESS', message)
@@ -226,13 +245,13 @@ class ControlMessage(object):
def is_ok(self) -> bool:
"""
- Checks if any of our lines have a 250 response.
+ Checks if any of our lines have a 250, 251 or 252 response.
- :returns: **True** if any lines have a 250 response code, **False** otherwise
+ :returns: **True** if any lines have a 250, 251 or 252 response code, **False** otherwise
"""
for code, _, _ in self._parsed_content:
- if code == '250':
+ if code in ['250', '251', '252']:
return True
return False
diff --git a/stem/response/onion_client_auth.py b/stem/response/onion_client_auth.py
new file mode 100644
index 00000000..80800bf3
--- /dev/null
+++ b/stem/response/onion_client_auth.py
@@ -0,0 +1,58 @@
+# Copyright 2015-2020, Damian Johnson and The Tor Project
+# See LICENSE for licensing information
+
+import stem.response
+from stem.util import log
+
+class OnionClientAuthAddResponse(stem.response.ControlMessage):
+ """
+ ONION_CLIENT_AUTH_ADD response.
+ """
+
+ def _parse_message(self) -> None:
+ # ONION_CLIENT_AUTH_ADD responds with:
+ # '250 OK',
+ # '251 Client for onion existed and replaced',
+ # '252 Registered client and decrypted desc',
+ # '512 Invalid v3 address [service id]',
+ # '553 Unable to store creds for [service id]'
+
+ if not self.is_ok():
+ raise stem.ProtocolError("ONION_CLIENT_AUTH_ADD response didn't have an OK status: %s" % self)
+
+class OnionClientAuthRemoveResponse(stem.response.ControlMessage):
+ """
+ ONION_CLIENT_AUTH_REMOVE response.
+ """
+
+ def _parse_message(self) -> None:
+ # ONION_CLIENT_AUTH_REMOVE responds with:
+ # '250 OK',
+ # '251 No credentials for [service id]',
+ # '512 Invalid v3 address [service id]'
+
+ if not self.is_ok():
+ raise stem.ProtocolError("ONION_CLIENT_AUTH_REMOVE response didn't have an OK status: %s" % self)
+
+class OnionClientAuthViewResponse(stem.response.ControlMessage):
+ """
+ ONION_CLIENT_AUTH_VIEW response.
+ """
+
+ def _parse_message(self) -> None:
+ # ONION_CLIENT_AUTH_VIEW responds with:
+ # '250 OK' if there was Client Auth for this service or if the service is a valid address,
+ # ''512 Invalid v3 address [service id]'
+
+ self.client_auth_credential = None
+
+ if not self.is_ok():
+ raise stem.ProtocolError("ONION_CLIENT_AUTH_VIEW response didn't have an OK status: %s" % self)
+ else:
+ for line in list(self):
+ if line.startswith('CLIENT'):
+ key, value = line.split(' ', 1)
+ log.debug(key)
+ log.debug(value)
+
+ self.client_auth_credential = value
diff --git a/test/integ/control/controller.py b/test/integ/control/controller.py
index 19d4ba85..47c51caf 100644
--- a/test/integ/control/controller.py
+++ b/test/integ/control/controller.py
@@ -1602,6 +1602,54 @@ class TestController(unittest.TestCase):
finally:
await controller.set_conf('OrPort', str(test.runner.ORPORT))
+ @test.require.controller
+ @async_test
+ async def test_client_auth_for_v3_onion(self):
+ """
+ Exercises adding, viewing and removing Client Auth for a v3 ephemeral hidden service.
+ """
+
+ runner = test.runner.get_runner()
+
+ async with await runner.get_tor_controller() as controller:
+ service_id = 'yvhz3ofkv7gwf5hpzqvhonpr3gbax2cc7dee3xcnt7dmtlx2gu7vyvid'
+ # This is an invalid key, it should throw an error
+ private_key = 'XXXXXXXXXFCV0c0ELDKKDpSFgVIB8Yow8Evj5iD+GoiTtK878NkQ='
+ exc_msg = "ONION_CLIENT_AUTH_ADD response didn't have an OK status: Failed to decode x25519 private key"
+
+ with self.assertRaisesWith(stem.ProtocolError, exc_msg):
+ await controller.add_onion_client_auth(service_id, private_key)
+
+ # This is a valid key
+ private_key = 'FCV0c0ELDKKDpSFgVIB8Yow8Evj5iD+GoiTtK878NkQ='
+ response = await controller.add_onion_client_auth(service_id, private_key)
+
+ # View the credential
+ response = await controller.view_onion_client_auth(service_id)
+ self.assertEqual(response.client_auth_credential, '%s x25519:%s' % (service_id, private_key))
+
+ # Remove the credential
+ await controller.remove_onion_client_auth(service_id)
+ response = await controller.view_onion_client_auth(service_id)
+ self.assertTrue(response.client_auth_credential is None)
+
+ # Test that an invalid service ID throws the appropriate error for adding, removing or viewing client auth
+ service_id = 'xxxxxxxxyvhz3ofkv7gwf5hpzqvhonpr3gbax2cc7dee3xcnt7dmtlx2gu7vyvid'
+ exc_msg = "ONION_CLIENT_AUTH_ADD response didn't have an OK status: Invalid v3 address \"%s\"" % service_id
+
+ with self.assertRaisesWith(stem.ProtocolError, exc_msg):
+ await controller.add_onion_client_auth(service_id, private_key)
+
+ exc_msg = "ONION_CLIENT_AUTH_REMOVE response didn't have an OK status: Invalid v3 address \"%s\"" % service_id
+
+ with self.assertRaisesWith(stem.ProtocolError, exc_msg):
+ await controller.remove_onion_client_auth(service_id)
+
+ exc_msg = "ONION_CLIENT_AUTH_VIEW response didn't have an OK status: Invalid v3 address \"%s\"" % service_id
+
+ with self.assertRaisesWith(stem.ProtocolError, exc_msg):
+ await controller.view_onion_client_auth(service_id)
+
async def _get_router_status_entry(self, controller):
"""
Provides a router status entry for a relay with a nickname other than
1
0
commit 5a104e84dece6a1663d100dfac172cf27e0f95c5
Author: Damian Johnson <atagar(a)torproject.org>
Date: Wed Aug 5 16:49:22 2020 -0700
Rearrange hidden service auth test
No significant changes. These tests were great, just rearranging things a tad.
---
test/integ/control/controller.py | 48 ++++++++++++++++++----------------------
1 file changed, 22 insertions(+), 26 deletions(-)
diff --git a/test/integ/control/controller.py b/test/integ/control/controller.py
index 2f8f2da0..3840a1f6 100644
--- a/test/integ/control/controller.py
+++ b/test/integ/control/controller.py
@@ -1610,46 +1610,42 @@ class TestController(unittest.TestCase):
service.
"""
- runner = test.runner.get_runner()
-
- async with await runner.get_tor_controller() as controller:
+ async with await test.runner.get_runner().get_tor_controller() as controller:
service_id = 'yvhz3ofkv7gwf5hpzqvhonpr3gbax2cc7dee3xcnt7dmtlx2gu7vyvid'
- # This is an invalid key, it should throw an error
- private_key = 'XXXXXXXXXFCV0c0ELDKKDpSFgVIB8Yow8Evj5iD+GoiTtK878NkQ='
- exc_msg = "ONION_CLIENT_AUTH_ADD response didn't have an OK status: Failed to decode x25519 private key"
+ private_key = 'FCV0c0ELDKKDpSFgVIB8Yow8Evj5iD+GoiTtK878NkQ='
- with self.assertRaisesWith(stem.ProtocolError, exc_msg):
- await controller.add_hidden_service_auth(service_id, private_key)
+ # register authentication credentials
- # This is a valid key
- private_key = 'FCV0c0ELDKKDpSFgVIB8Yow8Evj5iD+GoiTtK878NkQ='
- response = await controller.add_hidden_service_auth(service_id, private_key)
+ await controller.add_hidden_service_auth(service_id, private_key)
- # View the credential
response = await controller.list_hidden_service_auth(service_id)
- self.assertEqual(response.client_auth_credential, '%s x25519:%s' % (service_id, private_key))
+ self.assertEqual('%s x25519:%s' % (service_id, private_key), response.client_auth_credential)
+
+ # deregister authentication credentials
- # Remove the credential
await controller.remove_hidden_service_auth(service_id)
+
response = await controller.list_hidden_service_auth(service_id)
- self.assertTrue(response.client_auth_credential is None)
+ self.assertEqual(None, response.client_auth_credential)
+
+ # exercise with an invalid address
- # Test that an invalid service ID throws the appropriate error for adding, removing or viewing client auth
- service_id = 'xxxxxxxxyvhz3ofkv7gwf5hpzqvhonpr3gbax2cc7dee3xcnt7dmtlx2gu7vyvid'
- exc_msg = "ONION_CLIENT_AUTH_ADD response didn't have an OK status: Invalid v3 address \"%s\"" % service_id
+ invalid_service_id = 'xxxxxxxxyvhz3ofkv7gwf5hpzqvhonpr3gbax2cc7dee3xcnt7dmtlx2gu7vyvid'
+ exc_msg = "%%s response didn't have an OK status: Invalid v3 address \"%s\"" % invalid_service_id
- with self.assertRaisesWith(stem.ProtocolError, exc_msg):
- await controller.add_hidden_service_auth(service_id, private_key)
+ with self.assertRaisesWith(stem.ProtocolError, exc_msg % 'ONION_CLIENT_AUTH_ADD'):
+ await controller.add_hidden_service_auth(invalid_service_id, private_key)
- exc_msg = "ONION_CLIENT_AUTH_REMOVE response didn't have an OK status: Invalid v3 address \"%s\"" % service_id
+ with self.assertRaisesWith(stem.ProtocolError, exc_msg % 'ONION_CLIENT_AUTH_REMOVE'):
+ await controller.remove_hidden_service_auth(invalid_service_id)
- with self.assertRaisesWith(stem.ProtocolError, exc_msg):
- await controller.remove_hidden_service_auth(service_id)
+ with self.assertRaisesWith(stem.ProtocolError, exc_msg % 'ONION_CLIENT_AUTH_VIEW'):
+ await controller.list_hidden_service_auth(invalid_service_id)
- exc_msg = "ONION_CLIENT_AUTH_VIEW response didn't have an OK status: Invalid v3 address \"%s\"" % service_id
+ # register with an invalid key
- with self.assertRaisesWith(stem.ProtocolError, exc_msg):
- await controller.list_hidden_service_auth(service_id)
+ with self.assertRaisesWith(stem.ProtocolError, "ONION_CLIENT_AUTH_ADD response didn't have an OK status: Failed to decode x25519 private key"):
+ await controller.add_hidden_service_auth(service_id, 'XXXXXXXXXFCV0c0ELDKKDpSFgVIB8Yow8Evj5iD+GoiTtK878NkQ=')
async def _get_router_status_entry(self, controller):
"""
1
0
commit e302e46be576afac28bb84f5c6865047cd7fdf68
Author: Damian Johnson <atagar(a)torproject.org>
Date: Tue Aug 4 19:00:37 2020 -0700
Rename new authentication methods
These method names were based on the controller commands which is fine, but we
have some conventions of our own. Renaming these methods for a couple
reasons...
* For consitency Stem still calls these 'hidden services', and will continue
to do so until...
https://trac.torproject.org/projects/tor/ticket/25918
* We prefix getter methods like this with 'list_'.
---
stem/control.py | 26 ++++++++++++--------------
test/integ/control/controller.py | 21 +++++++++++----------
2 files changed, 23 insertions(+), 24 deletions(-)
diff --git a/stem/control.py b/stem/control.py
index 5bdb4a5a..61c4a277 100644
--- a/stem/control.py
+++ b/stem/control.py
@@ -112,9 +112,9 @@ If you're fine with allowing your script to raise exceptions then this can be mo
|- create_ephemeral_hidden_service - create a new ephemeral hidden service
|- remove_ephemeral_hidden_service - removes an ephemeral hidden service
|
- |- add_onion_client_auth - add Client Authentication for a v3 onion service
- |- remove_onion_client_auth - remove Client Authentication for a v3 onion service
- |- view_onion_client_auth - view Client Authentication for a v3 onion service
+ |- add_hidden_service_auth - authenticate to a v3 hidden service
+ |- remove_hidden_service_auth - revoke authentication to a v3 hidden service
+ |- list_hidden_service_auth - list v3 hidden services we authenticate with
|
|- add_event_listener - attaches an event listener to be notified of tor events
|- remove_event_listener - removes a listener so it isn't notified of further events
@@ -2905,12 +2905,6 @@ class Controller(BaseController):
response. For instance, only bob can access using the given newly generated
credentials...
- Note that **basic_auth** only works for legacy (v2) onion services.
- There is not yet any Control Port support for adding Client Auth to the
- server side of a v3 onion service.
-
- To add Client Authentication on the client side of a v3 onion, you can use
- :func`~stem.control.Controller.add_onion_client_auth`.
::
>>> response = controller.create_ephemeral_hidden_service(80, basic_auth = {'bob': None})
@@ -2927,6 +2921,10 @@ class Controller(BaseController):
'bob': 'vGnNRpWYiMBFTWD2gbBlcA',
})
+ Please note that **basic_auth** only works for legacy (v2) hidden services.
+ Version 3 can't enable service authentication through the control protocol
+ (`ticket <https://gitlab.torproject.org/tpo/core/tor/-/issues/40084>`_).
+
To create a **version 3** service simply specify **ED25519-V3** as the
our key type, and to create a **version 2** service use **RSA1024**. The
default version of newly created hidden services is based on the
@@ -3085,9 +3083,9 @@ class Controller(BaseController):
else:
raise stem.ProtocolError('DEL_ONION returned unexpected response code: %s' % response.code)
- async def add_onion_client_auth(self, service_id: str, private_key_blob: str, key_type: str = 'x25519', client_name: Optional[str] = None, permanent: Optional[bool] = False) -> stem.response.onion_client_auth.OnionClientAuthAddResponse:
+ async def add_hidden_service_auth(self, service_id: str, private_key_blob: str, key_type: str = 'x25519', client_name: Optional[str] = None, permanent: Optional[bool] = False) -> stem.response.onion_client_auth.OnionClientAuthAddResponse:
"""
- Adds Client Authentication for a v3 onion service.
+ Authenticate with a v3 hidden service.
:param service_id: hidden service address without the '.onion' suffix
:param key_type: the type of private key in use. x25519 is the only one supported right now
@@ -3119,9 +3117,9 @@ class Controller(BaseController):
return response
- async def remove_onion_client_auth(self, service_id: str) -> stem.response.onion_client_auth.OnionClientAuthRemoveResponse:
+ async def remove_hidden_service_auth(self, service_id: str) -> stem.response.onion_client_auth.OnionClientAuthRemoveResponse:
"""
- Removes Client Authentication for a v3 onion service.
+ Revoke authentication with a v3 hidden service.
:param service_id: hidden service address without the '.onion' suffix
@@ -3137,7 +3135,7 @@ class Controller(BaseController):
return response
- async def view_onion_client_auth(self, service_id: str) -> stem.response.onion_client_auth.OnionClientAuthViewResponse:
+ async def list_hidden_service_auth(self, service_id: str) -> stem.response.onion_client_auth.OnionClientAuthViewResponse:
"""
View Client Authentication for a v3 onion service.
diff --git a/test/integ/control/controller.py b/test/integ/control/controller.py
index 47c51caf..2f8f2da0 100644
--- a/test/integ/control/controller.py
+++ b/test/integ/control/controller.py
@@ -1604,9 +1604,10 @@ class TestController(unittest.TestCase):
@test.require.controller
@async_test
- async def test_client_auth_for_v3_onion(self):
+ async def test_hidden_service_v3_authentication(self):
"""
- Exercises adding, viewing and removing Client Auth for a v3 ephemeral hidden service.
+ Exercises adding, viewing and removing authentication credentials for a v3
+ service.
"""
runner = test.runner.get_runner()
@@ -1618,19 +1619,19 @@ class TestController(unittest.TestCase):
exc_msg = "ONION_CLIENT_AUTH_ADD response didn't have an OK status: Failed to decode x25519 private key"
with self.assertRaisesWith(stem.ProtocolError, exc_msg):
- await controller.add_onion_client_auth(service_id, private_key)
+ await controller.add_hidden_service_auth(service_id, private_key)
# This is a valid key
private_key = 'FCV0c0ELDKKDpSFgVIB8Yow8Evj5iD+GoiTtK878NkQ='
- response = await controller.add_onion_client_auth(service_id, private_key)
+ response = await controller.add_hidden_service_auth(service_id, private_key)
# View the credential
- response = await controller.view_onion_client_auth(service_id)
+ response = await controller.list_hidden_service_auth(service_id)
self.assertEqual(response.client_auth_credential, '%s x25519:%s' % (service_id, private_key))
# Remove the credential
- await controller.remove_onion_client_auth(service_id)
- response = await controller.view_onion_client_auth(service_id)
+ await controller.remove_hidden_service_auth(service_id)
+ response = await controller.list_hidden_service_auth(service_id)
self.assertTrue(response.client_auth_credential is None)
# Test that an invalid service ID throws the appropriate error for adding, removing or viewing client auth
@@ -1638,17 +1639,17 @@ class TestController(unittest.TestCase):
exc_msg = "ONION_CLIENT_AUTH_ADD response didn't have an OK status: Invalid v3 address \"%s\"" % service_id
with self.assertRaisesWith(stem.ProtocolError, exc_msg):
- await controller.add_onion_client_auth(service_id, private_key)
+ await controller.add_hidden_service_auth(service_id, private_key)
exc_msg = "ONION_CLIENT_AUTH_REMOVE response didn't have an OK status: Invalid v3 address \"%s\"" % service_id
with self.assertRaisesWith(stem.ProtocolError, exc_msg):
- await controller.remove_onion_client_auth(service_id)
+ await controller.remove_hidden_service_auth(service_id)
exc_msg = "ONION_CLIENT_AUTH_VIEW response didn't have an OK status: Invalid v3 address \"%s\"" % service_id
with self.assertRaisesWith(stem.ProtocolError, exc_msg):
- await controller.view_onion_client_auth(service_id)
+ await controller.list_hidden_service_auth(service_id)
async def _get_router_status_entry(self, controller):
"""
1
0
commit 6af714a4bdae173f39ea4a9ba002dc3ddb60bef6
Author: Damian Johnson <atagar(a)torproject.org>
Date: Wed Aug 5 17:39:41 2020 -0700
Rewrite ONION_CLIENT_AUTH_VIEW parsing
Mig5's parser was a fine proof of concept but stem parses everything within the
spec. Our list_hidden_service_auth() method now returns either a credential or
dictionary of credentials based on if we're requesting a single service or
everything.
---
docs/change_log.rst | 1 +
stem/control.py | 87 +++++++++++++++++++++++---------------
stem/interpreter/settings.cfg | 2 +-
stem/response/__init__.py | 14 ------
stem/response/onion_client_auth.py | 86 +++++++++++++++++++------------------
test/integ/control/controller.py | 49 +++++++++++++++------
6 files changed, 136 insertions(+), 103 deletions(-)
diff --git a/docs/change_log.rst b/docs/change_log.rst
index d1d04c87..f76b933f 100644
--- a/docs/change_log.rst
+++ b/docs/change_log.rst
@@ -49,6 +49,7 @@ The following are only available within Stem's `git repository
* **Controller**
* Socket based control connections often raised BrokenPipeError when closed
+ * Added :func:`~stem.control.Controller.add_hidden_service_auth`, :func:`~stem.control.Controller.remove_hidden_service_auth`, and :func:`~stem.control.Controller.list_hidden_service_auth` to the :class:`~stem.control.Controller`
* **Descriptors**
diff --git a/stem/control.py b/stem/control.py
index 61c4a277..d77eb311 100644
--- a/stem/control.py
+++ b/stem/control.py
@@ -457,6 +457,18 @@ class CreateHiddenServiceOutput(collections.namedtuple('CreateHiddenServiceOutpu
"""
+class HiddenServiceCredential(collections.namedtuple('HiddenServiceCredential', ['service_id', 'private_key', 'key_type', 'client_name', 'flags'])):
+ """
+ Credentials for authenticating with a v3 hidden service.
+
+ :var str service_id: hidden service id without its '.onion' suffix
+ :var str private_key: base64 encoded credential
+ :var str key_type: encryption type being used
+ :var str client_name: optional nickname for our client
+ :var list flags: flags associated with this credential
+ """
+
+
def with_default(yields: bool = False) -> Callable:
"""
Provides a decorator to support having a default value. This should be
@@ -3083,76 +3095,83 @@ class Controller(BaseController):
else:
raise stem.ProtocolError('DEL_ONION returned unexpected response code: %s' % response.code)
- async def add_hidden_service_auth(self, service_id: str, private_key_blob: str, key_type: str = 'x25519', client_name: Optional[str] = None, permanent: Optional[bool] = False) -> stem.response.onion_client_auth.OnionClientAuthAddResponse:
+ async def add_hidden_service_auth(self, service_id: str, private_key: str, key_type: str = 'x25519', client_name: Optional[str] = None, write: bool = False) -> None:
"""
Authenticate with a v3 hidden service.
+ .. versionadded:: 2.0.0
+
:param service_id: hidden service address without the '.onion' suffix
- :param key_type: the type of private key in use. x25519 is the only one supported right now
- :param private_key_blob: base64 encoding of x25519 private key
+ :param private_key: base64 encoded private key to authenticate with
+ :param key_type: type of private key in use
:param client_name: optional nickname for this client
- :param permanent: optionally flag that this client's credentials should be stored in the filesystem.
- If this is not set, the client's credentials are epheremal and stored in memory.
-
- :returns: **True* if the client authentication was added or replaced, **False** if it
- was rejected by the Tor controller
+ :param write: persists these credentials to disk if **True**, only resides
+ in memory otherwise
:raises: :class:`stem.ControllerError` if the call fails
"""
- request = 'ONION_CLIENT_AUTH_ADD %s %s:%s' % (service_id, key_type, private_key_blob)
+ request = 'ONION_CLIENT_AUTH_ADD %s %s:%s' % (service_id, key_type, private_key)
if client_name:
request += ' ClientName=%s' % client_name
- flags = []
-
- if permanent:
- flags.append('Permanent')
-
- if flags:
- request += ' Flags=%s' % ','.join(flags)
+ if write:
+ request += ' Flags=Permanent'
- response = stem.response._convert_to_onion_client_auth_add(stem.response._convert_to_onion_client_auth_add(await self.msg(request)))
+ response = stem.response._convert_to_single_line(await self.msg(request))
- return response
+ if not response.is_ok():
+ raise stem.ProtocolError("ONION_CLIENT_AUTH_ADD response didn't have an OK status: %s" % response.message)
- async def remove_hidden_service_auth(self, service_id: str) -> stem.response.onion_client_auth.OnionClientAuthRemoveResponse:
+ async def remove_hidden_service_auth(self, service_id: str) -> None:
"""
Revoke authentication with a v3 hidden service.
- :param service_id: hidden service address without the '.onion' suffix
+ .. versionadded:: 2.0.0
- :returns: **True* if the client authentication was removed, (or if no such
- service ID existed), **False** if it was rejected by the Tor controller
+ :param service_id: hidden service address without the '.onion' suffix
:raises: :class:`stem.ControllerError` if the call fails
"""
- request = 'ONION_CLIENT_AUTH_REMOVE %s' % service_id
+ response = stem.response._convert_to_single_line(await self.msg('ONION_CLIENT_AUTH_REMOVE %s' % service_id))
- response = stem.response._convert_to_onion_client_auth_remove(stem.response._convert_to_onion_client_auth_remove(await self.msg(request)))
-
- return response
+ if not response.is_ok():
+ raise stem.ProtocolError("ONION_CLIENT_AUTH_REMOVE response didn't have an OK status: %s" % response.message)
- async def list_hidden_service_auth(self, service_id: str) -> stem.response.onion_client_auth.OnionClientAuthViewResponse:
+ async def list_hidden_service_auth(self, service_id: Optional[str] = None) -> Union['stem.control.HiddenServiceCredential', Dict[str, 'stem.control.HiddenServiceCredential']]:
"""
View Client Authentication for a v3 onion service.
- :param service_id: hidden service address without the '.onion' suffix
+ .. versionadded:: 2.0.0
+
+ :param service_id: restrict query to this service, otherwise provides all
+ credentials
- :returns: :class:`~stem.response.onion_client_auth.OnionClientAuthViewResponse` with the
- client_auth_credential if there were credentials to view, **True** if the service ID
- was valid but no credentials existed, **False** if the service ID was invalid
+ :returns: single :class:`~stem.control.HiddenServiceCredential` if called
+ with a **service_id**, otherwise this provides a **dict** mapping hidden
+ service ids with their credential
:raises: :class:`stem.ControllerError` if the call fails
"""
- request = 'ONION_CLIENT_AUTH_VIEW %s' % service_id
+ request = 'ONION_CLIENT_AUTH_VIEW'
- response = stem.response._convert_to_onion_client_auth_view(stem.response._convert_to_onion_client_auth_view(await self.msg(request)))
+ if service_id:
+ request += ' %s' % service_id
- return response
+ response = stem.response._convert_to_onion_client_auth_view(await self.msg(request))
+
+ if service_id:
+ if service_id not in response.credentials:
+ raise stem.ProtocolError('ONION_CLIENT_AUTH_VIEW failed to provide a credential for %s: %s' % (service_id, response))
+ elif len(response.credentials) != 1:
+ raise stem.ProtocolError('ONION_CLIENT_AUTH_VIEW should only contain credentials for %s but had %s' % (service_id, ', '.join(response.credentials.keys())))
+
+ return response.credentials[service_id]
+ else:
+ return response.credentials
async def add_event_listener(self, listener: Callable[[stem.response.events.Event], Union[None, Awaitable[None]]], *events: 'stem.control.EventType') -> None:
"""
diff --git a/stem/interpreter/settings.cfg b/stem/interpreter/settings.cfg
index a373e617..fe853616 100644
--- a/stem/interpreter/settings.cfg
+++ b/stem/interpreter/settings.cfg
@@ -271,7 +271,7 @@ help.description.del_onion
|Delete a hidden service that was created with ADD_ONION.
help.description.onion_client_auth
-|Add, remove or view Client Authentication for a v3 onion service.
+|Add, remove or list authentication credentials for a v3 hidden service.
help.description.hsfetch
|Retrieves the descriptor for a hidden service. This is an asynchronous
diff --git a/stem/response/__init__.py b/stem/response/__init__.py
index eb6231bd..5749fbcb 100644
--- a/stem/response/__init__.py
+++ b/stem/response/__init__.py
@@ -73,8 +73,6 @@ def convert(response_type: str, message: 'stem.response.ControlMessage', **kwarg
**GETCONF** :class:`stem.response.getconf.GetConfResponse`
**GETINFO** :class:`stem.response.getinfo.GetInfoResponse`
**MAPADDRESS** :class:`stem.response.mapaddress.MapAddressResponse`
- **ONION_CLIENT_AUTH_ADD** :class:`stem.response.onion_client_auth.OnionClientAuthAddResponse`
- **ONION_CLIENT_AUTH_REMOVE** :class:`stem.response.onion_client_auth.OnionClientAuthRemoveResponse`
**ONION_CLIENT_AUTH_VIEW** :class:`stem.response.onion_client_auth.OnionClientAuthViewResponse`
**PROTOCOLINFO** :class:`stem.response.protocolinfo.ProtocolInfoResponse`
**SINGLELINE** :class:`stem.response.SingleLineResponse`
@@ -118,8 +116,6 @@ def convert(response_type: str, message: 'stem.response.ControlMessage', **kwarg
'GETCONF': stem.response.getconf.GetConfResponse,
'GETINFO': stem.response.getinfo.GetInfoResponse,
'MAPADDRESS': stem.response.mapaddress.MapAddressResponse,
- 'ONION_CLIENT_AUTH_ADD': stem.response.onion_client_auth.OnionClientAuthAddResponse,
- 'ONION_CLIENT_AUTH_REMOVE': stem.response.onion_client_auth.OnionClientAuthRemoveResponse,
'ONION_CLIENT_AUTH_VIEW': stem.response.onion_client_auth.OnionClientAuthViewResponse,
'PROTOCOLINFO': stem.response.protocolinfo.ProtocolInfoResponse,
'SINGLELINE': SingleLineResponse,
@@ -162,16 +158,6 @@ def _convert_to_add_onion(message: 'stem.response.ControlMessage', **kwargs: Any
return message # type: ignore
-def _convert_to_onion_client_auth_add(message: 'stem.response.ControlMessage', **kwargs: Any) -> 'stem.response.onion_client_auth.OnionClientAuthAddResponse':
- stem.response.convert('ONION_CLIENT_AUTH_ADD', message)
- return message # type: ignore
-
-
-def _convert_to_onion_client_auth_remove(message: 'stem.response.ControlMessage', **kwargs: Any) -> 'stem.response.onion_client_auth.OnionClientAuthRemoveResponse':
- stem.response.convert('ONION_CLIENT_AUTH_REMOVE', message)
- return message # type: ignore
-
-
def _convert_to_onion_client_auth_view(message: 'stem.response.ControlMessage', **kwargs: Any) -> 'stem.response.onion_client_auth.OnionClientAuthViewResponse':
stem.response.convert('ONION_CLIENT_AUTH_VIEW', message)
return message # type: ignore
diff --git a/stem/response/onion_client_auth.py b/stem/response/onion_client_auth.py
index c21f9158..8735808c 100644
--- a/stem/response/onion_client_auth.py
+++ b/stem/response/onion_client_auth.py
@@ -1,61 +1,65 @@
-# Copyright 2015-2020, Damian Johnson and The Tor Project
+# Copyright 2020, Damian Johnson and The Tor Project
# See LICENSE for licensing information
+import stem.control
import stem.response
-from stem.util import log
-class OnionClientAuthAddResponse(stem.response.ControlMessage):
+class OnionClientAuthViewResponse(stem.response.ControlMessage):
"""
- ONION_CLIENT_AUTH_ADD response.
+ ONION_CLIENT_AUTH_VIEW response.
+
+ :var str requested: queried hidden service id, this is None if all
+ credentials were requested
+ :var dict credentials: mapping of hidden service ids to their
+ :class:`~stem.control.HiddenServiceCredential`
"""
def _parse_message(self) -> None:
- # ONION_CLIENT_AUTH_ADD responds with:
- # '250 OK',
- # '251 Client for onion existed and replaced',
- # '252 Registered client and decrypted desc',
- # '512 Invalid v3 address [service id]',
- # '553 Unable to store creds for [service id]'
+ # Example:
+ # 250-ONION_CLIENT_AUTH_VIEW yvhz3ofkv7gwf5hpzqvhonpr3gbax2cc7dee3xcnt7dmtlx2gu7vyvid
+ # 250-CLIENT yvhz3ofkv7gwf5hpzqvhonpr3gbax2cc7dee3xcnt7dmtlx2gu7vyvid x25519:FCV0c0ELDKKDpSFgVIB8Yow8Evj5iD+GoiTtK878NkQ=
+ # 250 OK
+
+ self.requested = None
+ self.credentials = {}
if not self.is_ok():
- raise stem.ProtocolError("ONION_CLIENT_AUTH_ADD response didn't have an OK status: %s" % self)
+ raise stem.ProtocolError("ONION_CLIENT_AUTH_VIEW response didn't have an OK status: %s" % self)
+ # first line optionally contains the service id this request was for
-class OnionClientAuthRemoveResponse(stem.response.ControlMessage):
- """
- ONION_CLIENT_AUTH_REMOVE response.
- """
+ first_line = list(self)[0]
- def _parse_message(self) -> None:
- # ONION_CLIENT_AUTH_REMOVE responds with:
- # '250 OK',
- # '251 No credentials for [service id]',
- # '512 Invalid v3 address [service id]'
+ if not first_line.startswith('ONION_CLIENT_AUTH_VIEW'):
+ raise stem.ProtocolError("Response should begin with 'ONION_CLIENT_AUTH_VIEW': %s" % self)
+ elif ' ' in first_line:
+ self.requested = first_line.split(' ')[1]
- if not self.is_ok():
- raise stem.ProtocolError("ONION_CLIENT_AUTH_REMOVE response didn't have an OK status: %s" % self)
+ for credential_line in list(self)[1:-1]:
+ attributes = credential_line.split(' ')
+ if len(attributes) < 3:
+ raise stem.ProtocolError('ONION_CLIENT_AUTH_VIEW lines must contain an address and credential: %s' % self)
+ elif attributes[0] != 'CLIENT':
+ raise stem.ProtocolError("ONION_CLIENT_AUTH_VIEW lines should begin with 'CLIENT': %s" % self)
+ elif ':' not in attributes[2]:
+ raise stem.ProtocolError("ONION_CLIENT_AUTH_VIEW credentials must be of the form 'encryption_type:key': %s" % self)
-class OnionClientAuthViewResponse(stem.response.ControlMessage):
- """
- ONION_CLIENT_AUTH_VIEW response.
- """
+ service_id = attributes[1]
+ key_type, private_key = attributes[2].split(':', 1)
+ client_name = None
+ flags = []
- def _parse_message(self) -> None:
- # ONION_CLIENT_AUTH_VIEW responds with:
- # '250 OK' if there was Client Auth for this service or if the service is a valid address,
- # ''512 Invalid v3 address [service id]'
+ for attr in attributes[2:]:
+ if '=' not in attr:
+ raise stem.ProtocolError("'%s' expected to be a 'key=value' mapping: %s" % (attr, self))
- self.client_auth_credential = None
+ key, value = attr.split('=', 1)
- if not self.is_ok():
- raise stem.ProtocolError("ONION_CLIENT_AUTH_VIEW response didn't have an OK status: %s" % self)
- else:
- for line in list(self):
- if line.startswith('CLIENT'):
- key, value = line.split(' ', 1)
- log.debug(key)
- log.debug(value)
-
- self.client_auth_credential = value
+ if key == 'ClientName':
+ client_name = value
+ elif key == 'Flags':
+ flags = value.split(',')
+
+ self.credentials[service_id] = stem.control.HiddenServiceCredential(service_id, private_key, key_type, client_name, flags)
diff --git a/test/integ/control/controller.py b/test/integ/control/controller.py
index 3840a1f6..25566229 100644
--- a/test/integ/control/controller.py
+++ b/test/integ/control/controller.py
@@ -29,6 +29,9 @@ from stem.control import EventType, Listener, State
from stem.exit_policy import ExitPolicy
from stem.util.test_tools import async_test
+SERVICE_ID = 'yvhz3ofkv7gwf5hpzqvhonpr3gbax2cc7dee3xcnt7dmtlx2gu7vyvid'
+PRIVATE_KEY = 'FCV0c0ELDKKDpSFgVIB8Yow8Evj5iD+GoiTtK878NkQ='
+
# Router status entry for a relay with a nickname other than 'Unnamed'. This is
# used for a few tests that need to look up a relay.
@@ -1604,37 +1607,55 @@ class TestController(unittest.TestCase):
@test.require.controller
@async_test
- async def test_hidden_service_v3_authentication(self):
+ async def test_hidden_service_auth(self):
"""
Exercises adding, viewing and removing authentication credentials for a v3
service.
"""
async with await test.runner.get_runner().get_tor_controller() as controller:
- service_id = 'yvhz3ofkv7gwf5hpzqvhonpr3gbax2cc7dee3xcnt7dmtlx2gu7vyvid'
- private_key = 'FCV0c0ELDKKDpSFgVIB8Yow8Evj5iD+GoiTtK878NkQ='
-
# register authentication credentials
- await controller.add_hidden_service_auth(service_id, private_key)
+ await controller.add_hidden_service_auth(SERVICE_ID, PRIVATE_KEY, client_name = 'StemInteg')
+
+ credential = await controller.list_hidden_service_auth(SERVICE_ID)
- response = await controller.list_hidden_service_auth(service_id)
- self.assertEqual('%s x25519:%s' % (service_id, private_key), response.client_auth_credential)
+ self.assertEqual(SERVICE_ID, credential.service_id)
+ self.assertEqual(PRIVATE_KEY, credential.private_key)
+ self.assertEqual('x25519', credential.key_type)
+ self.assertEqual([], credential.flags)
+
+ # TODO: We should assert our client_name's value...
+ #
+ # self.assertEqual('StemInteg', credential.client_name)
+ #
+ # ... but that's broken within tor...
+ #
+ # https://gitlab.torproject.org/tpo/core/tor/-/issues/40089
# deregister authentication credentials
- await controller.remove_hidden_service_auth(service_id)
+ await controller.remove_hidden_service_auth(SERVICE_ID)
+ self.assertEqual({}, await controller.list_hidden_service_auth())
- response = await controller.list_hidden_service_auth(service_id)
- self.assertEqual(None, response.client_auth_credential)
+ # TODO: We should add a persistance test (calling with 'write = True')
+ # but that doesn't look to be working...
+ #
+ # https://gitlab.torproject.org/tpo/core/tor/-/issues/40090
- # exercise with an invalid address
+ @test.require.controller
+ @async_test
+ async def test_hidden_service_auth_invalid(self):
+ """
+ Exercises hidden service authentication with invalid data.
+ """
+ async with await test.runner.get_runner().get_tor_controller() as controller:
invalid_service_id = 'xxxxxxxxyvhz3ofkv7gwf5hpzqvhonpr3gbax2cc7dee3xcnt7dmtlx2gu7vyvid'
exc_msg = "%%s response didn't have an OK status: Invalid v3 address \"%s\"" % invalid_service_id
with self.assertRaisesWith(stem.ProtocolError, exc_msg % 'ONION_CLIENT_AUTH_ADD'):
- await controller.add_hidden_service_auth(invalid_service_id, private_key)
+ await controller.add_hidden_service_auth(invalid_service_id, PRIVATE_KEY)
with self.assertRaisesWith(stem.ProtocolError, exc_msg % 'ONION_CLIENT_AUTH_REMOVE'):
await controller.remove_hidden_service_auth(invalid_service_id)
@@ -1642,10 +1663,12 @@ class TestController(unittest.TestCase):
with self.assertRaisesWith(stem.ProtocolError, exc_msg % 'ONION_CLIENT_AUTH_VIEW'):
await controller.list_hidden_service_auth(invalid_service_id)
+ invalid_key = 'XXXXXXXXXFCV0c0ELDKKDpSFgVIB8Yow8Evj5iD+GoiTtK878NkQ='
+
# register with an invalid key
with self.assertRaisesWith(stem.ProtocolError, "ONION_CLIENT_AUTH_ADD response didn't have an OK status: Failed to decode x25519 private key"):
- await controller.add_hidden_service_auth(service_id, 'XXXXXXXXXFCV0c0ELDKKDpSFgVIB8Yow8Evj5iD+GoiTtK878NkQ=')
+ await controller.add_hidden_service_auth(SERVICE_ID, invalid_key)
async def _get_router_status_entry(self, controller):
"""
1
0
commit 0d60896e0d41a20972463381a165795295de07f5
Author: Damian Johnson <atagar(a)torproject.org>
Date: Tue Aug 4 17:02:09 2020 -0700
Minor whitespace fixes
Address minor issues cited by pycodestyle...
STATIC CHECKS
* /home/atagar/Desktop/stem/stem/response/__init__.py
line 164 - E302 expected 2 blank lines, found 1 | def _convert_to_onion_client_auth_add(message: 'stem.response.ControlMessage', **kwargs: Any) -> 'stem.response.onion_client_auth.OnionClientAuthAddResponse':
line 168 - E302 expected 2 blank lines, found 1 | def _convert_to_onion_client_auth_remove(message: 'stem.response.ControlMessage', **kwargs: Any) -> 'stem.response.onion_client_auth.OnionClientAuthRemoveResponse':
line 172 - E302 expected 2 blank lines, found 1 | def _convert_to_onion_client_auth_view(message: 'stem.response.ControlMessage', **kwargs: Any) -> 'stem.response.onion_client_auth.OnionClientAuthViewResponse':
line 176 - E302 expected 2 blank lines, found 1 | def _convert_to_mapaddress(message: 'stem.response.ControlMessage', **kwargs: Any) -> 'stem.response.mapaddress.MapAddressResponse':
* /home/atagar/Desktop/stem/stem/response/onion_client_auth.py
line 7 - E302 expected 2 blank lines, found 1 | class OnionClientAuthAddResponse(stem.response.ControlMessage):
line 23 - E302 expected 2 blank lines, found 1 | class OnionClientAuthRemoveResponse(stem.response.ControlMessage):
line 30 - W291 trailing whitespace | # '250 OK',
line 37 - E302 expected 2 blank lines, found 1 | class OnionClientAuthViewResponse(stem.response.ControlMessage):
---
stem/response/__init__.py | 4 ++++
stem/response/onion_client_auth.py | 5 ++++-
2 files changed, 8 insertions(+), 1 deletion(-)
diff --git a/stem/response/__init__.py b/stem/response/__init__.py
index e77312c8..eb6231bd 100644
--- a/stem/response/__init__.py
+++ b/stem/response/__init__.py
@@ -161,18 +161,22 @@ def _convert_to_add_onion(message: 'stem.response.ControlMessage', **kwargs: Any
stem.response.convert('ADD_ONION', message)
return message # type: ignore
+
def _convert_to_onion_client_auth_add(message: 'stem.response.ControlMessage', **kwargs: Any) -> 'stem.response.onion_client_auth.OnionClientAuthAddResponse':
stem.response.convert('ONION_CLIENT_AUTH_ADD', message)
return message # type: ignore
+
def _convert_to_onion_client_auth_remove(message: 'stem.response.ControlMessage', **kwargs: Any) -> 'stem.response.onion_client_auth.OnionClientAuthRemoveResponse':
stem.response.convert('ONION_CLIENT_AUTH_REMOVE', message)
return message # type: ignore
+
def _convert_to_onion_client_auth_view(message: 'stem.response.ControlMessage', **kwargs: Any) -> 'stem.response.onion_client_auth.OnionClientAuthViewResponse':
stem.response.convert('ONION_CLIENT_AUTH_VIEW', message)
return message # type: ignore
+
def _convert_to_mapaddress(message: 'stem.response.ControlMessage', **kwargs: Any) -> 'stem.response.mapaddress.MapAddressResponse':
stem.response.convert('MAPADDRESS', message)
return message # type: ignore
diff --git a/stem/response/onion_client_auth.py b/stem/response/onion_client_auth.py
index 80800bf3..c21f9158 100644
--- a/stem/response/onion_client_auth.py
+++ b/stem/response/onion_client_auth.py
@@ -4,6 +4,7 @@
import stem.response
from stem.util import log
+
class OnionClientAuthAddResponse(stem.response.ControlMessage):
"""
ONION_CLIENT_AUTH_ADD response.
@@ -20,6 +21,7 @@ class OnionClientAuthAddResponse(stem.response.ControlMessage):
if not self.is_ok():
raise stem.ProtocolError("ONION_CLIENT_AUTH_ADD response didn't have an OK status: %s" % self)
+
class OnionClientAuthRemoveResponse(stem.response.ControlMessage):
"""
ONION_CLIENT_AUTH_REMOVE response.
@@ -27,13 +29,14 @@ class OnionClientAuthRemoveResponse(stem.response.ControlMessage):
def _parse_message(self) -> None:
# ONION_CLIENT_AUTH_REMOVE responds with:
- # '250 OK',
+ # '250 OK',
# '251 No credentials for [service id]',
# '512 Invalid v3 address [service id]'
if not self.is_ok():
raise stem.ProtocolError("ONION_CLIENT_AUTH_REMOVE response didn't have an OK status: %s" % self)
+
class OnionClientAuthViewResponse(stem.response.ControlMessage):
"""
ONION_CLIENT_AUTH_VIEW response.
1
0
commit 31d25caffccf3ad94d196243acd62b14d99dc82a
Merge: 2946016e 6af714a4
Author: Damian Johnson <atagar(a)torproject.org>
Date: Thu Aug 6 16:59:44 2020 -0700
Client side authentication for HSv3
Thanks to mig5 adding support for tor's new ONION_CLIENT_AUTH_ADD,
ONION_CLIENT_AUTH_REMOVE, and ONION_CLIENT_AUTH_VIEW features. These
add support for authenticating *to* a version 3 hidden service.
https://github.com/torproject/stem/issues/66
Implementation uncovered some rough edges within tor...
* Controllers cannot create authenticated services
https://gitlab.torproject.org/tpo/core/tor/-/issues/40084
* Client names apparently don't work
https://gitlab.torproject.org/tpo/core/tor/-/issues/40089
* Credential persistence either doesn't work or vague error response
https://gitlab.torproject.org/tpo/core/tor/-/issues/40090
docs/change_log.rst | 1 +
stem/control.py | 99 ++++++++++++++++++++++++++++++++++++++
stem/interpreter/settings.cfg | 12 +++++
stem/response/__init__.py | 31 +++++++-----
stem/response/onion_client_auth.py | 65 +++++++++++++++++++++++++
test/integ/control/controller.py | 68 ++++++++++++++++++++++++++
6 files changed, 265 insertions(+), 11 deletions(-)
1
0