lists.torproject.org
Sign In Sign Up
Manage this list Sign In Sign Up

Keyboard Shortcuts

Thread View

  • j: Next unread message
  • k: Previous unread message
  • j a: Jump to all threads
  • j l: Jump to MailingList overview

tbb-commits

Thread Start a new thread
Download
Threads by month
  • ----- 2025 -----
  • 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
tbb-commits@lists.torproject.org

November 2020

  • 3 participants
  • 399 discussions
[tor-browser/tor-browser-78.5.0esr-10.5-1] Bug 30237: Add v3 onion services client authentication prompt
by sysrqb@torproject.org 14 Nov '20

14 Nov '20
commit 5375075bf2afbb5cc691bd5a814af3333b27628f Author: Kathy Brade <brade(a)pearlcrescent.com> Date: Tue Nov 12 16:11:05 2019 -0500 Bug 30237: Add v3 onion services client authentication prompt When Tor informs the browser that client authentication is needed, temporarily load about:blank instead of about:neterror and prompt for the user's key. If a correctly formatted key is entered, use Tor's ONION_CLIENT_AUTH_ADD control port command to add the key (via Torbutton's control port module) and reload the page. If the user cancels the prompt, display the standard about:neterror "Unable to connect" page. This requires a small change to browser/actors/NetErrorChild.jsm to account for the fact that the docShell no longer has the failedChannel information. The failedChannel is used to extract TLS-related error info, which is not applicable in the case of a canceled .onion authentication prompt. Add a leaveOpen option to PopupNotifications.show so we can display error messages within the popup notification doorhanger without closing the prompt. Add support for onion services strings to the TorStrings module. Add support for Tor extended SOCKS errors (Tor proposal 304) to the socket transport and SOCKS layers. Improved display of all of these errors will be implemented as part of bug 30025. Also fixes bug 19757: Add a "Remember this key" checkbox to the client auth prompt. Add an "Onion Services Authentication" section within the about:preferences "Privacy & Security section" to allow viewing and removal of v3 onion client auth keys that have been stored on disk. Also fixes bug 19251: use enhanced error pages for onion service errors. --- browser/actors/NetErrorChild.jsm | 7 + browser/base/content/aboutNetError.js | 10 +- browser/base/content/aboutNetError.xhtml | 1 + browser/base/content/browser.js | 10 + browser/base/content/browser.xhtml | 3 + browser/base/content/tab-content.js | 5 + browser/components/moz.build | 1 + .../content/authNotificationIcon.inc.xhtml | 6 + .../onionservices/content/authPopup.inc.xhtml | 16 ++ .../onionservices/content/authPreferences.css | 20 ++ .../content/authPreferences.inc.xhtml | 19 ++ .../onionservices/content/authPreferences.js | 66 +++++ .../components/onionservices/content/authPrompt.js | 316 +++++++++++++++++++++ .../components/onionservices/content/authUtil.jsm | 47 +++ .../onionservices/content/netError/browser.svg | 3 + .../onionservices/content/netError/network.svg | 3 + .../content/netError/onionNetError.css | 65 +++++ .../content/netError/onionNetError.js | 244 ++++++++++++++++ .../onionservices/content/netError/onionsite.svg | 7 + .../onionservices/content/onionservices.css | 69 +++++ .../onionservices/content/savedKeysDialog.js | 259 +++++++++++++++++ .../onionservices/content/savedKeysDialog.xhtml | 42 +++ browser/components/onionservices/jar.mn | 9 + browser/components/onionservices/moz.build | 1 + browser/components/preferences/preferences.xhtml | 1 + browser/components/preferences/privacy.inc.xhtml | 2 + browser/components/preferences/privacy.js | 7 + browser/themes/shared/notification-icons.inc.css | 3 + docshell/base/nsDocShell.cpp | 81 +++++- dom/ipc/BrowserParent.cpp | 21 ++ dom/ipc/BrowserParent.h | 3 + dom/ipc/PBrowser.ipdl | 9 + js/xpconnect/src/xpc.msg | 10 + netwerk/base/nsSocketTransport2.cpp | 6 + netwerk/socket/nsSOCKSIOLayer.cpp | 49 ++++ toolkit/modules/PopupNotifications.jsm | 6 + toolkit/modules/RemotePageAccessManager.jsm | 1 + .../lib/environments/frame-script.js | 1 + xpcom/base/ErrorList.py | 22 ++ 39 files changed, 1449 insertions(+), 2 deletions(-) diff --git a/browser/actors/NetErrorChild.jsm b/browser/actors/NetErrorChild.jsm index af9d6bd46128..de66e9eeda18 100644 --- a/browser/actors/NetErrorChild.jsm +++ b/browser/actors/NetErrorChild.jsm @@ -13,6 +13,8 @@ const { RemotePageChild } = ChromeUtils.import( "resource://gre/actors/RemotePageChild.jsm" ); +const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm"); + XPCOMUtils.defineLazyServiceGetter( this, "gSerializationHelper", @@ -29,6 +31,7 @@ class NetErrorChild extends RemotePageChild { "RPMPrefIsLocked", "RPMAddToHistogram", "RPMRecordTelemetryEvent", + "RPMGetTorStrings", ]; this.exportFunctions(exportableFunctions); } @@ -82,4 +85,8 @@ class NetErrorChild extends RemotePageChild { RPMRecordTelemetryEvent(category, event, object, value, extra) { Services.telemetry.recordEvent(category, event, object, value, extra); } + + RPMGetTorStrings() { + return Cu.cloneInto(TorStrings.onionServices, this.contentWindow); + } } diff --git a/browser/base/content/aboutNetError.js b/browser/base/content/aboutNetError.js index 053d26ade512..60db17f46eb9 100644 --- a/browser/base/content/aboutNetError.js +++ b/browser/base/content/aboutNetError.js @@ -3,6 +3,7 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */ /* eslint-env mozilla/frame-script */ +/* import-globals-from ../../components/onionservices/content/netError/onionNetError.js */ const formatter = new Intl.DateTimeFormat("default"); @@ -241,7 +242,10 @@ function initPage() { errDesc = document.getElementById("ed_generic"); } - setErrorPageStrings(err); + const isOnionError = err.startsWith("onionServices."); + if (!isOnionError) { + setErrorPageStrings(err); + } var sd = document.getElementById("errorShortDescText"); if (sd) { @@ -387,6 +391,10 @@ function initPage() { span.textContent = document.location.hostname; } } + + if (isOnionError) { + OnionServicesAboutNetError.initPage(document); + } } function setupErrorUI() { diff --git a/browser/base/content/aboutNetError.xhtml b/browser/base/content/aboutNetError.xhtml index 3e9677e3c0ff..957b6f15a0be 100644 --- a/browser/base/content/aboutNetError.xhtml +++ b/browser/base/content/aboutNetError.xhtml @@ -208,5 +208,6 @@ </div> </div> </body> + <script src="chrome://browser/content/onionservices/netError/onionNetError.js"/> <script src="chrome://browser/content/aboutNetError.js"/> </html> diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index d192bc433fe6..6246068e88df 100644 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -220,6 +220,11 @@ XPCOMUtils.defineLazyScriptGetter( ["SecurityLevelButton"], "chrome://browser/content/securitylevel/securityLevel.js" ); +XPCOMUtils.defineLazyScriptGetter( + this, + ["OnionAuthPrompt"], + "chrome://browser/content/onionservices/authPrompt.js" +); XPCOMUtils.defineLazyScriptGetter( this, "gEditItemOverlay", @@ -1883,6 +1888,9 @@ var gBrowserInit = { // Init the SecuritySettingsButton SecurityLevelButton.init(); + // Init the OnionAuthPrompt + OnionAuthPrompt.init(); + // Certain kinds of automigration rely on this notification to complete // their tasks BEFORE the browser window is shown. SessionStore uses it to // restore tabs into windows AFTER important parts like gMultiProcessBrowser @@ -2567,6 +2575,8 @@ var gBrowserInit = { SecurityLevelButton.uninit(); + OnionAuthPrompt.uninit(); + gAccessibilityServiceIndicator.uninit(); AccessibilityRefreshBlocker.uninit(); diff --git a/browser/base/content/browser.xhtml b/browser/base/content/browser.xhtml index ddf5d766126b..4cab5fad6475 100644 --- a/browser/base/content/browser.xhtml +++ b/browser/base/content/browser.xhtml @@ -33,6 +33,7 @@ <?xml-stylesheet href="chrome://browser/skin/places/editBookmark.css" type="text/css"?> <?xml-stylesheet href="chrome://torbutton/skin/tor-circuit-display.css" type="text/css"?> <?xml-stylesheet href="chrome://torbutton/skin/torbutton.css" type="text/css"?> +<?xml-stylesheet href="chrome://browser/content/onionservices/onionservices.css" type="text/css"?> # All DTD information is stored in a separate file so that it can be shared by # hiddenWindowMac.xhtml. @@ -626,6 +627,7 @@ #include ../../components/downloads/content/downloadsPanel.inc.xhtml #include ../../../devtools/startup/enableDevToolsPopup.inc.xhtml #include ../../components/securitylevel/content/securityLevelPanel.inc.xhtml +#include ../../components/onionservices/content/authPopup.inc.xhtml #include browser-allTabsMenu.inc.xhtml <hbox id="downloads-animation-container"> @@ -994,6 +996,7 @@ data-l10n-id="urlbar-indexed-db-notification-anchor"/> <image id="password-notification-icon" class="notification-anchor-icon login-icon" role="button" data-l10n-id="urlbar-password-notification-anchor"/> +#include ../../components/onionservices/content/authNotificationIcon.inc.xhtml <stack id="plugins-notification-icon" class="notification-anchor-icon" role="button" align="center" data-l10n-id="urlbar-plugins-notification-anchor"> <image class="plugin-icon" /> <image id="plugin-icon-badge" /> diff --git a/browser/base/content/tab-content.js b/browser/base/content/tab-content.js index 30cfa891c1fb..c57244a962ee 100644 --- a/browser/base/content/tab-content.js +++ b/browser/base/content/tab-content.js @@ -19,6 +19,9 @@ ChromeUtils.defineModuleGetter( "BrowserUtils", "resource://gre/modules/BrowserUtils.jsm" ); +var { OnionAuthUtil } = ChromeUtils.import( + "chrome://browser/content/onionservices/authUtil.jsm" +); var { ActorManagerChild } = ChromeUtils.import( "resource://gre/modules/ActorManagerChild.jsm" @@ -101,5 +104,7 @@ if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) { Services.obs.notifyObservers(this, "tab-content-frameloader-created"); +OnionAuthUtil.addCancelMessageListener(this, docShell); + // This is a temporary hack to prevent regressions (bug 1471327). void content; diff --git a/browser/components/moz.build b/browser/components/moz.build index 09e209dc9c3b..b660be047b14 100644 --- a/browser/components/moz.build +++ b/browser/components/moz.build @@ -41,6 +41,7 @@ DIRS += [ 'fxmonitor', 'migration', 'newtab', + 'onionservices', 'originattributes', 'pioneer', 'places', diff --git a/browser/components/onionservices/content/authNotificationIcon.inc.xhtml b/browser/components/onionservices/content/authNotificationIcon.inc.xhtml new file mode 100644 index 000000000000..91274d612739 --- /dev/null +++ b/browser/components/onionservices/content/authNotificationIcon.inc.xhtml @@ -0,0 +1,6 @@ +# Copyright (c) 2020, The Tor Project, Inc. + +<image id="tor-clientauth-notification-icon" + class="notification-anchor-icon tor-clientauth-icon" + role="button" + tooltiptext="&torbutton.onionServices.authPrompt.tooltip;"/> diff --git a/browser/components/onionservices/content/authPopup.inc.xhtml b/browser/components/onionservices/content/authPopup.inc.xhtml new file mode 100644 index 000000000000..bd0ec3aa0b00 --- /dev/null +++ b/browser/components/onionservices/content/authPopup.inc.xhtml @@ -0,0 +1,16 @@ +# Copyright (c) 2020, The Tor Project, Inc. + +<popupnotification id="tor-clientauth-notification" hidden="true"> + <popupnotificationcontent orient="vertical"> + <description id="tor-clientauth-notification-desc"/> + <label id="tor-clientauth-notification-learnmore" + class="text-link popup-notification-learnmore-link" + is="text-link"/> + <html:div> + <html:input id="tor-clientauth-notification-key" type="password"/> + <html:div id="tor-clientauth-warning"/> + <checkbox id="tor-clientauth-persistkey-checkbox" + label="&torbutton.onionServices.authPrompt.persistCheckboxLabel;"/> + </html:div> + </popupnotificationcontent> +</popupnotification> diff --git a/browser/components/onionservices/content/authPreferences.css b/browser/components/onionservices/content/authPreferences.css new file mode 100644 index 000000000000..b3fb79b26ddc --- /dev/null +++ b/browser/components/onionservices/content/authPreferences.css @@ -0,0 +1,20 @@ +/* Copyright (c) 2020, The Tor Project, Inc. */ + +#torOnionServiceKeys-overview-container { + margin-right: 30px; +} + +#onionservices-savedkeys-tree treechildren::-moz-tree-cell-text { + font-size: 80%; +} + +#onionservices-savedkeys-errorContainer { + margin-top: 4px; + min-height: 3em; +} + +#onionservices-savedkeys-errorIcon { + margin-right: 4px; + list-style-image: url("chrome://browser/skin/warning.svg"); + visibility: hidden; +} diff --git a/browser/components/onionservices/content/authPreferences.inc.xhtml b/browser/components/onionservices/content/authPreferences.inc.xhtml new file mode 100644 index 000000000000..f69c9dde66a2 --- /dev/null +++ b/browser/components/onionservices/content/authPreferences.inc.xhtml @@ -0,0 +1,19 @@ +# Copyright (c) 2020, The Tor Project, Inc. + +<groupbox id="torOnionServiceKeys" orient="vertical" + data-category="panePrivacy" hidden="true"> + <label><html:h2 id="torOnionServiceKeys-header"/></label> + <hbox> + <description id="torOnionServiceKeys-overview-container" flex="1"> + <html:span id="torOnionServiceKeys-overview" + class="tail-with-learn-more"/> + <label id="torOnionServiceKeys-learnMore" class="learnMore text-link" + is="text-link"/> + </description> + <vbox align="end"> + <button id="torOnionServiceKeys-savedKeys" + is="highlightable-button" + class="accessory-button"/> + </vbox> + </hbox> +</groupbox> diff --git a/browser/components/onionservices/content/authPreferences.js b/browser/components/onionservices/content/authPreferences.js new file mode 100644 index 000000000000..52f8272020cc --- /dev/null +++ b/browser/components/onionservices/content/authPreferences.js @@ -0,0 +1,66 @@ +// Copyright (c) 2020, The Tor Project, Inc. + +"use strict"; + +ChromeUtils.defineModuleGetter( + this, + "TorStrings", + "resource:///modules/TorStrings.jsm" +); + +/* + Onion Services Client Authentication Preferences Code + + Code to handle init and update of onion services authentication section + in about:preferences#privacy +*/ + +const OnionServicesAuthPreferences = { + selector: { + groupBox: "#torOnionServiceKeys", + header: "#torOnionServiceKeys-header", + overview: "#torOnionServiceKeys-overview", + learnMore: "#torOnionServiceKeys-learnMore", + savedKeysButton: "#torOnionServiceKeys-savedKeys", + }, + + init() { + // populate XUL with localized strings + this._populateXUL(); + }, + + _populateXUL() { + const groupbox = document.querySelector(this.selector.groupBox); + + let elem = groupbox.querySelector(this.selector.header); + elem.textContent = TorStrings.onionServices.authPreferences.header; + + elem = groupbox.querySelector(this.selector.overview); + elem.textContent = TorStrings.onionServices.authPreferences.overview; + + elem = groupbox.querySelector(this.selector.learnMore); + elem.setAttribute("value", TorStrings.onionServices.learnMore); + elem.setAttribute("href", TorStrings.onionServices.learnMoreURL); + + elem = groupbox.querySelector(this.selector.savedKeysButton); + elem.setAttribute( + "label", + TorStrings.onionServices.authPreferences.savedKeys + ); + elem.addEventListener("command", () => + OnionServicesAuthPreferences.onViewSavedKeys() + ); + }, + + onViewSavedKeys() { + gSubDialog.open( + "chrome://browser/content/onionservices/savedKeysDialog.xhtml" + ); + }, +}; // OnionServicesAuthPreferences + +Object.defineProperty(this, "OnionServicesAuthPreferences", { + value: OnionServicesAuthPreferences, + enumerable: true, + writable: false, +}); diff --git a/browser/components/onionservices/content/authPrompt.js b/browser/components/onionservices/content/authPrompt.js new file mode 100644 index 000000000000..d4a59ac46487 --- /dev/null +++ b/browser/components/onionservices/content/authPrompt.js @@ -0,0 +1,316 @@ +// Copyright (c) 2020, The Tor Project, Inc. + +"use strict"; + +XPCOMUtils.defineLazyModuleGetters(this, { + OnionAuthUtil: "chrome://browser/content/onionservices/authUtil.jsm", + CommonUtils: "resource://services-common/utils.js", + TorStrings: "resource:///modules/TorStrings.jsm", +}); + +const OnionAuthPrompt = (function() { + // OnionServicesAuthPrompt objects run within the main/chrome process. + // aReason is the topic passed within the observer notification that is + // causing this auth prompt to be displayed. + function OnionServicesAuthPrompt(aBrowser, aFailedURI, aReason, aOnionName) { + this._browser = aBrowser; + this._failedURI = aFailedURI; + this._reasonForPrompt = aReason; + this._onionName = aOnionName; + } + + OnionServicesAuthPrompt.prototype = { + show(aWarningMessage) { + let mainAction = { + label: TorStrings.onionServices.authPrompt.done, + accessKey: TorStrings.onionServices.authPrompt.doneAccessKey, + leaveOpen: true, // Callback is responsible for closing the notification. + callback: this._onDone.bind(this), + }; + + let dialogBundle = Services.strings.createBundle( + "chrome://global/locale/dialog.properties"); + + let cancelAccessKey = dialogBundle.GetStringFromName("accesskey-cancel"); + if (!cancelAccessKey) + cancelAccessKey = "c"; // required by PopupNotifications.show() + + let cancelAction = { + label: dialogBundle.GetStringFromName("button-cancel"), + accessKey: cancelAccessKey, + callback: this._onCancel.bind(this), + }; + + let _this = this; + let options = { + autofocus: true, + hideClose: true, + persistent: true, + removeOnDismissal: false, + eventCallback(aTopic) { + if (aTopic === "showing") { + _this._onPromptShowing(aWarningMessage); + } else if (aTopic === "shown") { + _this._onPromptShown(); + } else if (aTopic === "removed") { + _this._onPromptRemoved(); + } + } + }; + + this._prompt = PopupNotifications.show(this._browser, + OnionAuthUtil.domid.notification, "", + OnionAuthUtil.domid.anchor, + mainAction, [cancelAction], options); + }, + + _onPromptShowing(aWarningMessage) { + let xulDoc = this._browser.ownerDocument; + let descElem = xulDoc.getElementById(OnionAuthUtil.domid.description); + if (descElem) { + // Handle replacement of the onion name within the localized + // string ourselves so we can show the onion name as bold text. + // We do this by splitting the localized string and creating + // several HTML <span> elements. + while (descElem.firstChild) + descElem.removeChild(descElem.firstChild); + + let fmtString = TorStrings.onionServices.authPrompt.description; + let prefix = ""; + let suffix = ""; + const kToReplace = "%S"; + let idx = fmtString.indexOf(kToReplace); + if (idx < 0) { + prefix = fmtString; + } else { + prefix = fmtString.substring(0, idx); + suffix = fmtString.substring(idx + kToReplace.length); + } + + const kHTMLNS = "http://www.w3.org/1999/xhtml"; + let span = xulDoc.createElementNS(kHTMLNS, "span"); + span.textContent = prefix; + descElem.appendChild(span); + span = xulDoc.createElementNS(kHTMLNS, "span"); + span.id = OnionAuthUtil.domid.onionNameSpan; + span.textContent = this._onionName; + descElem.appendChild(span); + span = xulDoc.createElementNS(kHTMLNS, "span"); + span.textContent = suffix; + descElem.appendChild(span); + } + + // Set "Learn More" label and href. + let learnMoreElem = xulDoc.getElementById(OnionAuthUtil.domid.learnMore); + if (learnMoreElem) { + learnMoreElem.setAttribute("value", TorStrings.onionServices.learnMore); + learnMoreElem.setAttribute("href", TorStrings.onionServices.learnMoreURL); + } + + this._showWarning(aWarningMessage); + let checkboxElem = this._getCheckboxElement(); + if (checkboxElem) { + checkboxElem.checked = false; + } + }, + + _onPromptShown() { + let keyElem = this._getKeyElement(); + if (keyElem) { + keyElem.setAttribute("placeholder", + TorStrings.onionServices.authPrompt.keyPlaceholder); + this._boundOnKeyFieldKeyPress = this._onKeyFieldKeyPress.bind(this); + this._boundOnKeyFieldInput = this._onKeyFieldInput.bind(this); + keyElem.addEventListener("keypress", this._boundOnKeyFieldKeyPress); + keyElem.addEventListener("input", this._boundOnKeyFieldInput); + keyElem.focus(); + } + }, + + _onPromptRemoved() { + if (this._boundOnKeyFieldKeyPress) { + let keyElem = this._getKeyElement(); + if (keyElem) { + keyElem.value = ""; + keyElem.removeEventListener("keypress", + this._boundOnKeyFieldKeyPress); + this._boundOnKeyFieldKeyPress = undefined; + keyElem.removeEventListener("input", this._boundOnKeyFieldInput); + this._boundOnKeyFieldInput = undefined; + } + } + }, + + _onKeyFieldKeyPress(aEvent) { + if (aEvent.keyCode == aEvent.DOM_VK_RETURN) { + this._onDone(); + } else if (aEvent.keyCode == aEvent.DOM_VK_ESCAPE) { + this._prompt.remove(); + this._onCancel(); + } + }, + + _onKeyFieldInput(aEvent) { + this._showWarning(undefined); // Remove the warning. + }, + + _onDone() { + let keyElem = this._getKeyElement(); + if (!keyElem) + return; + + let base64key = this._keyToBase64(keyElem.value); + if (!base64key) { + this._showWarning(TorStrings.onionServices.authPrompt.invalidKey); + return; + } + + this._prompt.remove(); + + // Use Torbutton's controller module to add the private key to Tor. + let controllerFailureMsg = + TorStrings.onionServices.authPrompt.failedToSetKey; + try { + let { controller } = + Cu.import("resource://torbutton/modules/tor-control-port.js", {}); + let torController = controller(aError => { + this.show(controllerFailureMsg); + }); + let onionAddr = this._onionName.toLowerCase().replace(/\.onion$/, ""); + let checkboxElem = this._getCheckboxElement(); + let isPermanent = (checkboxElem && checkboxElem.checked); + torController.onionAuthAdd(onionAddr, base64key, isPermanent) + .then(aResponse => { + // Success! Reload the page. + this._browser.sendMessageToActor( + "Browser:Reload", + {}, + "BrowserTab" + ); + }) + .catch(aError => { + if (aError.torMessage) + this.show(aError.torMessage); + else + this.show(controllerFailureMsg); + }); + } catch (e) { + this.show(controllerFailureMsg); + } + }, + + _onCancel() { + // Arrange for an error page to be displayed. + this._browser.messageManager.sendAsyncMessage( + OnionAuthUtil.message.authPromptCanceled, + {failedURI: this._failedURI.spec, + reasonForPrompt: this._reasonForPrompt}); + }, + + _getKeyElement() { + let xulDoc = this._browser.ownerDocument; + return xulDoc.getElementById(OnionAuthUtil.domid.keyElement); + }, + + _getCheckboxElement() { + let xulDoc = this._browser.ownerDocument; + return xulDoc.getElementById(OnionAuthUtil.domid.checkboxElement); + }, + + _showWarning(aWarningMessage) { + let xulDoc = this._browser.ownerDocument; + let warningElem = + xulDoc.getElementById(OnionAuthUtil.domid.warningElement); + let keyElem = this._getKeyElement(); + if (warningElem) { + if (aWarningMessage) { + warningElem.textContent = aWarningMessage; + warningElem.removeAttribute("hidden"); + if (keyElem) + keyElem.className = "invalid"; + } else { + warningElem.setAttribute("hidden", "true"); + if (keyElem) + keyElem.className = ""; + } + } + }, + + // Returns undefined if the key is the wrong length or format. + _keyToBase64(aKeyString) { + if (!aKeyString) + return undefined; + + let base64key; + if (aKeyString.length == 52) { + // The key is probably base32-encoded. Attempt to decode. + // Although base32 specifies uppercase letters, we accept lowercase + // as well because users may type in lowercase or copy a key out of + // a tor onion-auth file (which uses lowercase). + let rawKey; + try { + rawKey = CommonUtils.decodeBase32(aKeyString.toUpperCase()); + } catch (e) {} + + if (rawKey) try { + base64key = btoa(rawKey); + } catch (e) {} + } else if ((aKeyString.length == 44) && + /^[a-zA-Z0-9+/]*=*$/.test(aKeyString)) { + // The key appears to be a correctly formatted base64 value. If not, + // tor will return an error when we try to add the key via the + // control port. + base64key = aKeyString; + } + + return base64key; + }, + }; + + let retval = { + init() { + Services.obs.addObserver(this, OnionAuthUtil.topic.clientAuthMissing); + Services.obs.addObserver(this, OnionAuthUtil.topic.clientAuthIncorrect); + }, + + uninit() { + Services.obs.removeObserver(this, OnionAuthUtil.topic.clientAuthMissing); + Services.obs.removeObserver(this, OnionAuthUtil.topic.clientAuthIncorrect); + }, + + // aSubject is the DOM Window or browser where the prompt should be shown. + // aData contains the .onion name. + observe(aSubject, aTopic, aData) { + if ((aTopic != OnionAuthUtil.topic.clientAuthMissing) && + (aTopic != OnionAuthUtil.topic.clientAuthIncorrect)) { + return; + } + + let browser; + if (aSubject instanceof Ci.nsIDOMWindow) { + let contentWindow = aSubject.QueryInterface(Ci.nsIDOMWindow); + browser = contentWindow.docShell.chromeEventHandler; + } else { + browser = aSubject.QueryInterface(Ci.nsIBrowser); + } + + if (!gBrowser.browsers.some(aBrowser => aBrowser == browser)) { + return; // This window does not contain the subject browser; ignore. + } + + let failedURI = browser.currentURI; + let authPrompt = new OnionServicesAuthPrompt(browser, failedURI, + aTopic, aData); + authPrompt.show(undefined); + } + }; + + return retval; +})(); /* OnionAuthPrompt */ + + +Object.defineProperty(this, "OnionAuthPrompt", { + value: OnionAuthPrompt, + enumerable: true, + writable: false +}); diff --git a/browser/components/onionservices/content/authUtil.jsm b/browser/components/onionservices/content/authUtil.jsm new file mode 100644 index 000000000000..c9d83774da1f --- /dev/null +++ b/browser/components/onionservices/content/authUtil.jsm @@ -0,0 +1,47 @@ +// Copyright (c) 2020, The Tor Project, Inc. + +"use strict"; + +var EXPORTED_SYMBOLS = [ + "OnionAuthUtil", +]; + +var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); + +const OnionAuthUtil = { + topic: { + clientAuthMissing: "tor-onion-services-clientauth-missing", + clientAuthIncorrect: "tor-onion-services-clientauth-incorrect", + }, + message: { + authPromptCanceled: "Tor:OnionServicesAuthPromptCanceled", + }, + domid: { + anchor: "tor-clientauth-notification-icon", + notification: "tor-clientauth", + description: "tor-clientauth-notification-desc", + learnMore: "tor-clientauth-notification-learnmore", + onionNameSpan: "tor-clientauth-notification-onionname", + keyElement: "tor-clientauth-notification-key", + warningElement: "tor-clientauth-warning", + checkboxElement: "tor-clientauth-persistkey-checkbox", + }, + + addCancelMessageListener(aTabContent, aDocShell) { + aTabContent.addMessageListener(this.message.authPromptCanceled, + (aMessage) => { + // Upon cancellation of the client authentication prompt, display + // the appropriate error page. When calling the docShell + // displayLoadError() function, we pass undefined for the failed + // channel so that displayLoadError() can determine that it should + // not display the client authentication prompt a second time. + let failedURI = Services.io.newURI(aMessage.data.failedURI); + let reasonForPrompt = aMessage.data.reasonForPrompt; + let errorCode = + (reasonForPrompt === this.topic.clientAuthMissing) ? + Cr.NS_ERROR_TOR_ONION_SVC_MISSING_CLIENT_AUTH : + Cr.NS_ERROR_TOR_ONION_SVC_BAD_CLIENT_AUTH; + aDocShell.displayLoadError(errorCode, failedURI, undefined, undefined); + }); + }, +}; diff --git a/browser/components/onionservices/content/netError/browser.svg b/browser/components/onionservices/content/netError/browser.svg new file mode 100644 index 000000000000..b4c433b37bbb --- /dev/null +++ b/browser/components/onionservices/content/netError/browser.svg @@ -0,0 +1,3 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="72" height="65" viewBox="0 0 72 65"> + <path fill="context-fill" fill-opacity="context-fill-opacity" d="M0.0 0.0C0.0 0.0 0.0 65.0 0.0 65.0C0.0 65.0 72.0 65.0 72.0 65.0C72.0 65.0 72.0 0.0 72.0 0.0C72.0 0.0 52.9019692 0.0 52.9019692 0.0C52.9019692 0.0 0.0 0.0 0.0 0.0C0.0 0.0 0.0 0.0 0.0 0.0M65.0 58.0C65.0 58.0 6.0 58.0 6.0 58.0C6.0 58.0 6.0 25.0 6.0 25.0C6.0 25.0 65.0 25.0 65.0 25.0C65.0 25.0 65.0 58.0 65.0 58.0C65.0 58.0 65.0 58.0 65.0 58.0M6.0 10.0C6.0 10.0 10.0 10.0 10.0 10.0C10.0 10.0 10.0 14.0 10.0 14.0C10.0 14.0 6.0 14.0 6.0 14.0C6.0 14.0 6.0 10.0 6.0 10.0C6.0 10.0 6.0 10.0 6.0 10.0M14.0 10.0C14.0 10.0 18.0 10.0 18.0 10.0C18.0 10.0 18.0 14.0 18.0 14.0C18.0 14.0 14.0 14.0 14.0 14.0C14.0 14.0 14.0 10.0 14.0 10.0C14.0 10.0 14.0 10.0 14.0 10.0M22.0 10.0C22.0 10.0 26.0 10.0 26.0 10.0C26.0 10.0 26.0 14.0 26.0 14.0C26.0 14.0 22.0 14.0 22.0 14.0C22.0 14.0 22.0 10.0 22.0 10.0C22.0 10.0 22.0 10.0 22.0 10.0" /> +</svg> diff --git a/browser/components/onionservices/content/netError/network.svg b/browser/components/onionservices/content/netError/network.svg new file mode 100644 index 000000000000..808c53dedd09 --- /dev/null +++ b/browser/components/onionservices/content/netError/network.svg @@ -0,0 +1,3 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="72" height="54" viewBox="0 0 72 54"> + <path fill="context-fill" fill-opacity="context-fill-opacity" d="M14.0487805 54.0C6.28990244 54.0 0.0 47.3306322 0.0 39.1034585C0.0 32.0105634 4.68716488 26.0867675 10.9481707 24.585103C10.6902 23.574652 10.5365854 22.5107596 10.5365854 21.4138156C10.5365854 14.7292347 15.6471278 9.3103384 21.9512195 9.3103384C24.8076351 9.3103384 27.4126741 10.4393194 29.4146341 12.2780088C32.1344254 5.0777841 38.77452 0.0 46.5365854 0.0C56.7201249 0.0 64.9756098 8.7536733 64.9756098 19.5517479C64.9756098 20.7691677 64.8471688 21.9453428 64.6463415 23.1013144C69.0576849 26.0679606 72.0 31.2693674 72.0 37.2413909C72.0 46.5256603 64.9510244 54.0 56.195122 54.0C56.195122 54.0 14.0487805 54.0 14.0487805 54.0C14.0487805 54.0 14.0487805 54.0 14.0487805 54.0" /> +</svg> diff --git a/browser/components/onionservices/content/netError/onionNetError.css b/browser/components/onionservices/content/netError/onionNetError.css new file mode 100644 index 000000000000..58117ab93223 --- /dev/null +++ b/browser/components/onionservices/content/netError/onionNetError.css @@ -0,0 +1,65 @@ +/* Copyright (c) 2020, The Tor Project, Inc. */ + +:root { + --grey-70: #38383d; +} + +#onionErrorDiagramContainer { + margin: 60px auto; + width: 460px; /* 3 columns @ 140px plus 2 column gaps @ 20px */ + display: grid; + grid-row-gap: 15px; + grid-column-gap: 20px; + grid-template-columns: 1fr 1fr 1fr; +} + +#onionErrorDiagramContainer > div { + margin: auto; + position: relative; /* needed to allow overlay of the ok or error icon */ +} + +.onionErrorImage { + width: 72px; + height: 72px; + background-position: center; + background-repeat: no-repeat; + -moz-context-properties: fill; + fill: var(--grey-70); +} + +#onionErrorBrowserImage { + background-image: url("browser.svg"); +} + +#onionErrorNetworkImage { + background-image: url("network.svg"); +} + +#onionErrorOnionSiteImage { + background-image: url("onionsite.svg"); +} + +/* rules to support overlay of the ok or error icon */ +.onionErrorImage[status]::after { + content: " "; + position: absolute; + left: -18px; + top: 18px; + width: 36px; + height: 36px; + -moz-context-properties: fill; + fill: var(--in-content-page-background); + background-color: var(--grey-70); + background-repeat: no-repeat; + background-position: center; + border: 3px solid var(--in-content-page-background); + border-radius: 50%; +} + +.onionErrorImage[status="ok"]::after { + background-image: url("chrome://global/skin/icons/check.svg"); +} + +.onionErrorImage[status="error"]::after { + background-image: url("chrome://browser/skin/stop.svg"); +} diff --git a/browser/components/onionservices/content/netError/onionNetError.js b/browser/components/onionservices/content/netError/onionNetError.js new file mode 100644 index 000000000000..8fabb3f38eb7 --- /dev/null +++ b/browser/components/onionservices/content/netError/onionNetError.js @@ -0,0 +1,244 @@ +// Copyright (c) 2020, The Tor Project, Inc. + +"use strict"; + +/* eslint-env mozilla/frame-script */ + +var OnionServicesAboutNetError = { + _selector: { + header: ".title-text", + longDesc: "#errorLongDesc", + learnMoreContainer: "#learnMoreContainer", + learnMoreLink: "#learnMoreLink", + contentContainer: "#errorLongContent", + tryAgainButtonContainer: "#netErrorButtonContainer", + }, + _status: { + ok: "ok", + error: "error", + }, + + _diagramInfoMap: undefined, + + // Public functions (called from outside this file). + // + // This initPage() function may need to be updated if the structure of + // browser/base/content/aboutNetError.xhtml changes. Specifically, it + // references the following elements: + // query string parameter e + // class title-text + // id errorLongDesc + // id learnMoreContainer + // id learnMoreLink + // id errorLongContent + initPage(aDoc) { + const searchParams = new URLSearchParams(aDoc.documentURI.split("?")[1]); + const err = searchParams.get("e"); + + const errPrefix = "onionServices."; + const errName = err.substring(errPrefix.length); + + this._strings = RPMGetTorStrings(); + + const stringsObj = this._strings[errName]; + if (!stringsObj) { + return; + } + + this._insertStylesheet(aDoc); + + const pageTitle = stringsObj.pageTitle; + const header = stringsObj.header; + const longDescription = stringsObj.longDescription; // optional + const learnMoreURL = stringsObj.learnMoreURL; + + if (pageTitle) { + aDoc.title = pageTitle; + } + + if (header) { + const headerElem = aDoc.querySelector(this._selector.header); + if (headerElem) { + headerElem.textContent = header; + } + } + + const ld = aDoc.querySelector(this._selector.longDesc); + if (ld) { + if (longDescription) { + const hexErr = this._hexErrorFromName(errName); + ld.textContent = longDescription.replace("%S", hexErr); + } else { + // This onion service error does not have a long description. Since + // it is set to a generic error string by the code in + // browser/base/content/aboutNetError.js, hide it here. + ld.style.display = "none"; + } + } + + if (learnMoreURL) { + const lmContainer = aDoc.querySelector(this._selector.learnMoreContainer); + if (lmContainer) { + lmContainer.style.display = "block"; + } + const lmLink = lmContainer.querySelector(this._selector.learnMoreLink); + if (lmLink) { + lmLink.setAttribute("href", learnMoreURL); + } + } + + // Remove the "Try Again" button if the user made a typo in the .onion + // address since it is not useful in that case. + if (errName === "badAddress") { + const tryAgainButton = aDoc.querySelector( + this._selector.tryAgainButtonContainer + ); + if (tryAgainButton) { + tryAgainButton.style.display = "none"; + } + } + + this._insertDiagram(aDoc, errName); + }, // initPage() + + _insertStylesheet(aDoc) { + const url = + "chrome://browser/content/onionservices/netError/onionNetError.css"; + let linkElem = aDoc.createElement("link"); + linkElem.rel = "stylesheet"; + linkElem.href = url; + linkElem.type = "text/css"; + aDoc.head.appendChild(linkElem); + }, + + _insertDiagram(aDoc, aErrorName) { + // The onion error diagram consists of a grid of div elements. + // The first row contains three images (Browser, Network, Onionsite) and + // the second row contains labels for the images that are in the first row. + // The _diagramInfoMap describes for each type of onion service error + // whether a small ok or error status icon is overlaid on top of the main + // Browser/Network/Onionsite images. + if (!this._diagramInfoMap) { + this._diagramInfoMap = new Map(); + this._diagramInfoMap.set("descNotFound", { + browser: this._status.ok, + network: this._status.ok, + onionSite: this._status.error, + }); + this._diagramInfoMap.set("descInvalid", { + browser: this._status.ok, + network: this._status.error, + }); + this._diagramInfoMap.set("introFailed", { + browser: this._status.ok, + network: this._status.error, + }); + this._diagramInfoMap.set("rendezvousFailed", { + browser: this._status.ok, + network: this._status.error, + }); + this._diagramInfoMap.set("clientAuthMissing", { + browser: this._status.error, + }); + this._diagramInfoMap.set("clientAuthIncorrect", { + browser: this._status.error, + }); + this._diagramInfoMap.set("badAddress", { + browser: this._status.error, + }); + this._diagramInfoMap.set("introTimedOut", { + browser: this._status.ok, + network: this._status.error, + }); + } + + const diagramInfo = this._diagramInfoMap.get(aErrorName); + + const container = this._createDiv(aDoc, "onionErrorDiagramContainer"); + const imageClass = "onionErrorImage"; + + const browserImage = this._createDiv( + aDoc, + "onionErrorBrowserImage", + imageClass, + container + ); + if (diagramInfo && diagramInfo.browser) { + browserImage.setAttribute("status", diagramInfo.browser); + } + + const networkImage = this._createDiv( + aDoc, + "onionErrorNetworkImage", + imageClass, + container + ); + if (diagramInfo && diagramInfo.network) { + networkImage.setAttribute("status", diagramInfo.network); + } + + const onionSiteImage = this._createDiv( + aDoc, + "onionErrorOnionSiteImage", + imageClass, + container + ); + if (diagramInfo && diagramInfo.onionSite) { + onionSiteImage.setAttribute("status", diagramInfo.onionSite); + } + + let labelDiv = this._createDiv(aDoc, undefined, undefined, container); + labelDiv.textContent = this._strings.errorPage.browser; + labelDiv = this._createDiv(aDoc, undefined, undefined, container); + labelDiv.textContent = this._strings.errorPage.network; + labelDiv = this._createDiv(aDoc, undefined, undefined, container); + labelDiv.textContent = this._strings.errorPage.onionSite; + + const contentContainer = aDoc.querySelector( + this._selector.contentContainer + ); + if (contentContainer) { + contentContainer.insertBefore(container, contentContainer.firstChild); + } + }, // _insertDiagram() + + _createDiv(aDoc, aID, aClass, aParentElem) { + const div = aDoc.createElement("div"); + if (aID) { + div.id = aID; + } + if (aClass) { + div.setAttribute("class", aClass); + } + if (aParentElem) { + aParentElem.appendChild(div); + } + + return div; + }, + + _hexErrorFromName(aErrorName) { + // We do not have access to the original Tor SOCKS error code here, so + // perform a reverse mapping from the error name. + switch (aErrorName) { + case "descNotFound": + return "0xF0"; + case "descInvalid": + return "0xF1"; + case "introFailed": + return "0xF2"; + case "rendezvousFailed": + return "0xF3"; + case "clientAuthMissing": + return "0xF4"; + case "clientAuthIncorrect": + return "0xF5"; + case "badAddress": + return "0xF6"; + case "introTimedOut": + return "0xF7"; + } + + return ""; + }, +}; diff --git a/browser/components/onionservices/content/netError/onionsite.svg b/browser/components/onionservices/content/netError/onionsite.svg new file mode 100644 index 000000000000..1f2777e6acc7 --- /dev/null +++ b/browser/components/onionservices/content/netError/onionsite.svg @@ -0,0 +1,7 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="70" height="63" viewBox="0 0 70 63"> + <g fill="context-fill" fill-opacity="context-fill-opacity"> + <path d="M64.0 2.0C64.0 2.0 4.0 2.0 4.0 2.0C2.8954305 2.0 2.0 2.81148389 2.0 3.8125C2.0 3.8125 2.0 58.1875 2.0 58.1875C2.0 59.1885161 2.8954305 60.0 4.0 60.0C4.0 60.0 36.0 60.0 36.0 60.0C36.0 60.0 36.0 56.375 36.0 56.375C36.0 56.375 6.0 56.375 6.0 56.375C6.0 56.375 6.0 41.875 6.0 41.875C6.0 41.875 38.0 41.875 38.0 41.875C38.0 41.875 38.0 38.25 38.0 38.25C38.0 38.25 6.0 38.25 6.0 38.25C6.0 38.25 6.0 23.75 6.0 23.75C6.0 23.75 62.0 23.75 62.0 23.75C62.0 23.75 62.0 36.4375 62.0 36.4375C62.0 36.4375 66.0 36.4375 66.0 36.4375C66.0 36.4375 66.0 3.8125 66.0 3.8125C66.0 2.81148389 65.1045695 2.0 64.0 2.0C64.0 2.0 64.0 2.0 64.0 2.0M62.0 20.125C62.0 20.125 6.0 20.125 6.0 20.125C6.0 20.125 6.0 5.625 6.0 5.625C6.0 5.625 62.0 5.625 62.0 5.625C62.0 5.625 62.0 20.125 62.0 20.125C62.0 20.125 62.0 20.125 62.0 20.125" /> + <path d="M24.0 47.0C24.0 47.0 24.0 51.0 24.0 51.0C24.0 51.0 20.0 51.0 20.0 51.0C20.0 51.0 20.0 47.0 20.0 47.0C20.0 47.0 24.0 47.0 24.0 47.0C24.0 47.0 24.0 47.0 24.0 47.0M16.0 47.0C16.0 47.0 16.0 51.0 16.0 51.0C16.0 51.0 12.0 51.0 12.0 51.0C12.0 51.0 12.0 47.0 12.0 47.0C12.0 47.0 16.0 47.0 16.0 47.0C16.0 47.0 16.0 47.0 16.0 47.0M56.0 29.0C56.0 29.0 56.0 33.0 56.0 33.0C56.0 33.0 52.0 33.0 52.0 33.0C52.0 33.0 52.0 29.0 52.0 29.0C52.0 29.0 56.0 29.0 56.0 29.0C56.0 29.0 56.0 29.0 56.0 29.0M48.0 29.0C48.0 29.0 48.0 33.0 48.0 33.0C48.0 33.0 12.0 33.0 12.0 33.0C12.0 33.0 12.0 29.0 12.0 29.0C12.0 29.0 48.0 29.0 48.0 29.0C48.0 29.0 48.0 29.0 48.0 29.0M22.0 11.0C22.0 11.0 22.0 15.0 22.0 15.0C22.0 15.0 10.0 15.0 10.0 15.0C10.0 15.0 10.0 11.0 10.0 11.0C10.0 11.0 22.0 11.0 22.0 11.0C22.0 11.0 22.0 11.0 22.0 11.0M70.0 0.0C70.0 0.0 70.0 36.5 70.0 36.5C70.0 36.5 65.0 36.5 65.0 36.5C65.0 36.5 65.0 4.5 65.0 4.5C65.0 4.5 5.0 4.5 5.0 4.5C5.0 4.5 5.0 58.5 5.0 58.5C5.0 58.5 36.0 58.5 36.0 58.5C36.0 58 .5 36.0 63.0 36.0 63.0C36.0 63.0 0.0 63.0 0.0 63.0C0.0 63.0 0.0 0.0 0.0 0.0C0.0 0.0 70.0 0.0 70.0 0.0C70.0 0.0 70.0 0.0 70.0 0.0M32.0 47.0C32.0 47.0 32.0 51.0 32.0 51.0C32.0 51.0 28.0 51.0 28.0 51.0C28.0 51.0 28.0 47.0 28.0 47.0C28.0 47.0 32.0 47.0 32.0 47.0C32.0 47.0 32.0 47.0 32.0 47.0M54.0 11.0C54.0 11.0 54.0 15.0 54.0 15.0C54.0 15.0 50.0 15.0 50.0 15.0C50.0 15.0 50.0 11.0 50.0 11.0C50.0 11.0 54.0 11.0 54.0 11.0C54.0 11.0 54.0 11.0 54.0 11.0M46.0 11.0C46.0 11.0 46.0 15.0 46.0 15.0C46.0 15.0 42.0 15.0 42.0 15.0C42.0 15.0 42.0 11.0 42.0 11.0C42.0 11.0 46.0 11.0 46.0 11.0C46.0 11.0 46.0 11.0 46.0 11.0M38.0 11.0C38.0 11.0 38.0 15.0 38.0 15.0C38.0 15.0 34.0 15.0 34.0 15.0C34.0 15.0 34.0 11.0 34.0 11.0C34.0 11.0 38.0 11.0 38.0 11.0C38.0 11.0 38.0 11.0 38.0 11.0M30.0 11.0C30.0 11.0 30.0 15.0 30.0 15.0C30.0 15.0 26.0 15.0 26.0 15.0C26.0 15.0 26.0 11.0 26.0 11.0C26.0 11.0 30.0 11.0 30.0 11.0C30.0 11.0 30.0 11.0 30.0 11.0" /> + <path d="M61.0 46.0C61.0 46.0 59.0 46.0 59.0 46.0C59.0 46.0 59.0 40.0 59.0 40.0C59.0 38.8954305 58.1045695 38.0 57.0 38.0C57.0 38.0 49.0 38.0 49.0 38.0C47.8954305 38.0 47.0 38.8954305 47.0 40.0C47.0 40.0 47.0 46.0 47.0 46.0C47.0 46.0 45.0 46.0 45.0 46.0C43.8954305 46.0 43.0 46.8954305 43.0 48.0C43.0 48.0 43.0 60.0 43.0 60.0C43.0 61.1045695 43.8954305 62.0 45.0 62.0C45.0 62.0 61.0 62.0 61.0 62.0C62.1045695 62.0 63.0 61.1045695 63.0 60.0C63.0 60.0 63.0 48.0 63.0 48.0C63.0 46.8954305 62.1045695 46.0 61.0 46.0C61.0 46.0 61.0 46.0 61.0 46.0M51.0 42.0C51.0 42.0 55.0 42.0 55.0 42.0C55.0 42.0 55.0 46.0 55.0 46.0C55.0 46.0 51.0 46.0 51.0 46.0C51.0 46.0 51.0 42.0 51.0 42.0C51.0 42.0 51.0 42.0 51.0 42.0M59.0 58.0C59.0 58.0 47.0 58.0 47.0 58.0C47.0 58.0 47.0 50.0 47.0 50.0C47.0 50.0 59.0 50.0 59.0 50.0C59.0 50.0 59.0 58.0 59.0 58.0C59.0 58.0 59.0 58.0 59.0 58.0" /> + </g> +</svg> diff --git a/browser/components/onionservices/content/onionservices.css b/browser/components/onionservices/content/onionservices.css new file mode 100644 index 000000000000..e2621ec8266d --- /dev/null +++ b/browser/components/onionservices/content/onionservices.css @@ -0,0 +1,69 @@ +/* Copyright (c) 2020, The Tor Project, Inc. */ + +@namespace html url("http://www.w3.org/1999/xhtml"); + +html|*#tor-clientauth-notification-onionname { + font-weight: bold; +} + +html|*#tor-clientauth-notification-key { + box-sizing: border-box; + width: 100%; + margin-top: 15px; + padding: 6px; +} + +/* Start of rules adapted from + * browser/components/newtab/css/activity-stream-mac.css (linux and windows + * use the same rules). + */ +html|*#tor-clientauth-notification-key.invalid { + border: 1px solid #D70022; + box-shadow: 0 0 0 1px #D70022, 0 0 0 4px rgba(215, 0, 34, 0.3); +} + +html|*#tor-clientauth-warning { + display: inline-block; + animation: fade-up-tt 450ms; + background: #D70022; + border-radius: 2px; + color: #FFF; + inset-inline-start: 3px; + padding: 5px 12px; + position: relative; + top: 6px; + z-index: 1; +} + +html|*#tor-clientauth-warning[hidden] { + display: none; +} + +html|*#tor-clientauth-warning::before { + background: #D70022; + bottom: -8px; + content: '.'; + height: 16px; + inset-inline-start: 12px; + position: absolute; + text-indent: -999px; + top: -7px; + transform: rotate(45deg); + white-space: nowrap; + width: 16px; + z-index: -1; +} + +@keyframes fade-up-tt { + 0% { + opacity: 0; + transform: translateY(15px); + } + 100% { + opacity: 1; + transform: translateY(0); + } +} +/* End of rules adapted from + * browser/components/newtab/css/activity-stream-mac.css + */ diff --git a/browser/components/onionservices/content/savedKeysDialog.js b/browser/components/onionservices/content/savedKeysDialog.js new file mode 100644 index 000000000000..b1376bbabe85 --- /dev/null +++ b/browser/components/onionservices/content/savedKeysDialog.js @@ -0,0 +1,259 @@ +// Copyright (c) 2020, The Tor Project, Inc. + +"use strict"; + +ChromeUtils.defineModuleGetter( + this, + "TorStrings", + "resource:///modules/TorStrings.jsm" +); + +ChromeUtils.defineModuleGetter( + this, + "controller", + "resource://torbutton/modules/tor-control-port.js" +); + +var gOnionServicesSavedKeysDialog = { + selector: { + dialog: "#onionservices-savedkeys-dialog", + intro: "#onionservices-savedkeys-intro", + tree: "#onionservices-savedkeys-tree", + onionSiteCol: "#onionservices-savedkeys-siteCol", + onionKeyCol: "#onionservices-savedkeys-keyCol", + errorIcon: "#onionservices-savedkeys-errorIcon", + errorMessage: "#onionservices-savedkeys-errorMessage", + removeButton: "#onionservices-savedkeys-remove", + removeAllButton: "#onionservices-savedkeys-removeall", + }, + + _tree: undefined, + _isBusy: false, // true when loading data, deleting a key, etc. + + // Public functions (called from outside this file). + async deleteSelectedKeys() { + this._setBusyState(true); + + const indexesToDelete = []; + const count = this._tree.view.selection.getRangeCount(); + for (let i = 0; i < count; ++i) { + const minObj = {}; + const maxObj = {}; + this._tree.view.selection.getRangeAt(i, minObj, maxObj); + for (let idx = minObj.value; idx <= maxObj.value; ++idx) { + indexesToDelete.push(idx); + } + } + + if (indexesToDelete.length > 0) { + const controllerFailureMsg = + TorStrings.onionServices.authPreferences.failedToRemoveKey; + try { + const torController = controller(aError => { + this._showError(controllerFailureMsg); + }); + + // Remove in reverse index order to avoid issues caused by index changes. + for (let i = indexesToDelete.length - 1; i >= 0; --i) { + await this._deleteOneKey(torController, indexesToDelete[i]); + } + } catch (e) { + if (e.torMessage) { + this._showError(e.torMessage); + } else { + this._showError(controllerFailureMsg); + } + } + } + + this._setBusyState(false); + }, + + async deleteAllKeys() { + this._tree.view.selection.selectAll(); + await this.deleteSelectedKeys(); + }, + + updateButtonsState() { + const haveSelection = this._tree.view.selection.getRangeCount() > 0; + const dialog = document.querySelector(this.selector.dialog); + const removeSelectedBtn = dialog.querySelector(this.selector.removeButton); + removeSelectedBtn.disabled = this._isBusy || !haveSelection; + const removeAllBtn = dialog.querySelector(this.selector.removeAllButton); + removeAllBtn.disabled = this._isBusy || this.rowCount === 0; + }, + + // Private functions. + _onLoad() { + document.mozSubdialogReady = this._init(); + }, + + async _init() { + await this._populateXUL(); + + window.addEventListener("keypress", this._onWindowKeyPress.bind(this)); + + // We don't use await here because we want _loadSavedKeys() to run + // in the background and not block loading of this dialog. + this._loadSavedKeys(); + }, + + async _populateXUL() { + const dialog = document.querySelector(this.selector.dialog); + const authPrefStrings = TorStrings.onionServices.authPreferences; + dialog.setAttribute("title", authPrefStrings.dialogTitle); + + let elem = dialog.querySelector(this.selector.intro); + elem.textContent = authPrefStrings.dialogIntro; + + elem = dialog.querySelector(this.selector.onionSiteCol); + elem.setAttribute("label", authPrefStrings.onionSite); + + elem = dialog.querySelector(this.selector.onionKeyCol); + elem.setAttribute("label", authPrefStrings.onionKey); + + elem = dialog.querySelector(this.selector.removeButton); + elem.setAttribute("label", authPrefStrings.remove); + + elem = dialog.querySelector(this.selector.removeAllButton); + elem.setAttribute("label", authPrefStrings.removeAll); + + this._tree = dialog.querySelector(this.selector.tree); + }, + + async _loadSavedKeys() { + const controllerFailureMsg = + TorStrings.onionServices.authPreferences.failedToGetKeys; + this._setBusyState(true); + + try { + this._tree.view = this; + + const torController = controller(aError => { + this._showError(controllerFailureMsg); + }); + + const keyInfoList = await torController.onionAuthViewKeys(); + if (keyInfoList) { + // Filter out temporary keys. + this._keyInfoList = keyInfoList.filter(aKeyInfo => { + if (!aKeyInfo.Flags) { + return false; + } + + const flags = aKeyInfo.Flags.split(","); + return flags.includes("Permanent"); + }); + + // Sort by the .onion address. + this._keyInfoList.sort((aObj1, aObj2) => { + const hsAddr1 = aObj1.hsAddress.toLowerCase(); + const hsAddr2 = aObj2.hsAddress.toLowerCase(); + if (hsAddr1 < hsAddr2) { + return -1; + } + return hsAddr1 > hsAddr2 ? 1 : 0; + }); + } + + // Render the tree content. + this._tree.rowCountChanged(0, this.rowCount); + } catch (e) { + if (e.torMessage) { + this._showError(e.torMessage); + } else { + this._showError(controllerFailureMsg); + } + } + + this._setBusyState(false); + }, + + // This method may throw; callers should catch errors. + async _deleteOneKey(aTorController, aIndex) { + const keyInfoObj = this._keyInfoList[aIndex]; + await aTorController.onionAuthRemove(keyInfoObj.hsAddress); + this._tree.view.selection.clearRange(aIndex, aIndex); + this._keyInfoList.splice(aIndex, 1); + this._tree.rowCountChanged(aIndex + 1, -1); + }, + + _setBusyState(aIsBusy) { + this._isBusy = aIsBusy; + this.updateButtonsState(); + }, + + _onWindowKeyPress(event) { + if (event.keyCode === KeyEvent.DOM_VK_ESCAPE) { + window.close(); + } else if (event.keyCode === KeyEvent.DOM_VK_DELETE) { + this.deleteSelectedKeys(); + } + }, + + _showError(aMessage) { + const dialog = document.querySelector(this.selector.dialog); + const errorIcon = dialog.querySelector(this.selector.errorIcon); + errorIcon.style.visibility = aMessage ? "visible" : "hidden"; + const errorDesc = dialog.querySelector(this.selector.errorMessage); + errorDesc.textContent = aMessage ? aMessage : ""; + }, + + // XUL tree widget view implementation. + get rowCount() { + return this._keyInfoList ? this._keyInfoList.length : 0; + }, + + getCellText(aRow, aCol) { + let val = ""; + if (this._keyInfoList && aRow < this._keyInfoList.length) { + const keyInfo = this._keyInfoList[aRow]; + if (aCol.id.endsWith("-siteCol")) { + val = keyInfo.hsAddress; + } else if (aCol.id.endsWith("-keyCol")) { + val = keyInfo.typeAndKey; + // Omit keyType because it is always "x25519". + const idx = val.indexOf(":"); + if (idx > 0) { + val = val.substring(idx + 1); + } + } + } + + return val; + }, + + isSeparator(index) { + return false; + }, + + isSorted() { + return false; + }, + + isContainer(index) { + return false; + }, + + setTree(tree) {}, + + getImageSrc(row, column) {}, + + getCellValue(row, column) {}, + + cycleHeader(column) {}, + + getRowProperties(row) { + return ""; + }, + + getColumnProperties(column) { + return ""; + }, + + getCellProperties(row, column) { + return ""; + }, +}; + +window.addEventListener("load", () => gOnionServicesSavedKeysDialog._onLoad()); diff --git a/browser/components/onionservices/content/savedKeysDialog.xhtml b/browser/components/onionservices/content/savedKeysDialog.xhtml new file mode 100644 index 000000000000..3db9bb05ea82 --- /dev/null +++ b/browser/components/onionservices/content/savedKeysDialog.xhtml @@ -0,0 +1,42 @@ +<?xml version="1.0"?> +<!-- Copyright (c) 2020, The Tor Project, Inc. --> + +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> +<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css" type="text/css"?> +<?xml-stylesheet href="chrome://browser/content/onionservices/authPreferences.css" type="text/css"?> + +<window id="onionservices-savedkeys-dialog" + windowtype="OnionServices:SavedKeys" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + style="width: 45em;"> + + <script src="chrome://browser/content/onionservices/savedKeysDialog.js"/> + + <vbox id="onionservices-savedkeys" class="contentPane" flex="1"> + <label id="onionservices-savedkeys-intro" + control="onionservices-savedkeys-tree"/> + <separator class="thin"/> + <tree id="onionservices-savedkeys-tree" flex="1" hidecolumnpicker="true" + width="750" + style="height: 20em;" + onselect="gOnionServicesSavedKeysDialog.updateButtonsState();"> + <treecols> + <treecol id="onionservices-savedkeys-siteCol" flex="1" persist="width"/> + <splitter class="tree-splitter"/> + <treecol id="onionservices-savedkeys-keyCol" flex="1" persist="width"/> + </treecols> + <treechildren/> + </tree> + <hbox id="onionservices-savedkeys-errorContainer" align="baseline" flex="1"> + <image id="onionservices-savedkeys-errorIcon"/> + <description id="onionservices-savedkeys-errorMessage" flex="1"/> + </hbox> + <separator class="thin"/> + <hbox id="onionservices-savedkeys-buttons"> + <button id="onionservices-savedkeys-remove" disabled="true" + oncommand="gOnionServicesSavedKeysDialog.deleteSelectedKeys();"/> + <button id="onionservices-savedkeys-removeall" + oncommand="gOnionServicesSavedKeysDialog.deleteAllKeys();"/> + </hbox> + </vbox> +</window> diff --git a/browser/components/onionservices/jar.mn b/browser/components/onionservices/jar.mn new file mode 100644 index 000000000000..9d6ce88d1841 --- /dev/null +++ b/browser/components/onionservices/jar.mn @@ -0,0 +1,9 @@ +browser.jar: + content/browser/onionservices/authPreferences.css (content/authPreferences.css) + content/browser/onionservices/authPreferences.js (content/authPreferences.js) + content/browser/onionservices/authPrompt.js (content/authPrompt.js) + content/browser/onionservices/authUtil.jsm (content/authUtil.jsm) + content/browser/onionservices/netError/ (content/netError/*) + content/browser/onionservices/onionservices.css (content/onionservices.css) + content/browser/onionservices/savedKeysDialog.js (content/savedKeysDialog.js) + content/browser/onionservices/savedKeysDialog.xhtml (content/savedKeysDialog.xhtml) diff --git a/browser/components/onionservices/moz.build b/browser/components/onionservices/moz.build new file mode 100644 index 000000000000..7e103239c8d6 --- /dev/null +++ b/browser/components/onionservices/moz.build @@ -0,0 +1 @@ +JAR_MANIFESTS += ['jar.mn'] diff --git a/browser/components/preferences/preferences.xhtml b/browser/components/preferences/preferences.xhtml index 2a99400bfb70..b09808e83c45 100644 --- a/browser/components/preferences/preferences.xhtml +++ b/browser/components/preferences/preferences.xhtml @@ -12,6 +12,7 @@ <?xml-stylesheet href="chrome://browser/skin/preferences/search.css"?> <?xml-stylesheet href="chrome://browser/skin/preferences/containers.css"?> <?xml-stylesheet href="chrome://browser/skin/preferences/privacy.css"?> +<?xml-stylesheet href="chrome://browser/content/onionservices/authPreferences.css"?> <?xml-stylesheet href="chrome://browser/content/securitylevel/securityLevelPreferences.css"?> <?xml-stylesheet href="chrome://browser/content/torpreferences/torPreferences.css"?> diff --git a/browser/components/preferences/privacy.inc.xhtml b/browser/components/preferences/privacy.inc.xhtml index f36145ea80d4..eb7587afa0e1 100644 --- a/browser/components/preferences/privacy.inc.xhtml +++ b/browser/components/preferences/privacy.inc.xhtml @@ -477,6 +477,8 @@ <label id="fips-desc" hidden="true" data-l10n-id="forms-master-pw-fips-desc"></label> </groupbox> +#include ../onionservices/content/authPreferences.inc.xhtml + <!-- The form autofill section is inserted in to this box after the form autofill extension has initialized. --> <groupbox id="formAutofillGroupBox" diff --git a/browser/components/preferences/privacy.js b/browser/components/preferences/privacy.js index 35e1fda9f96b..41dda96a14de 100644 --- a/browser/components/preferences/privacy.js +++ b/browser/components/preferences/privacy.js @@ -77,6 +77,12 @@ XPCOMUtils.defineLazyGetter(this, "AlertsServiceDND", function() { } }); +XPCOMUtils.defineLazyScriptGetter( + this, + ["OnionServicesAuthPreferences"], + "chrome://browser/content/onionservices/authPreferences.js" +); + // TODO: module import via ChromeUtils.defineModuleGetter XPCOMUtils.defineLazyScriptGetter( this, @@ -434,6 +440,7 @@ var gPrivacyPane = { this.trackingProtectionReadPrefs(); this.networkCookieBehaviorReadPrefs(); this._initTrackingProtectionExtensionControl(); + OnionServicesAuthPreferences.init(); this._initSecurityLevel(); Services.telemetry.setEventRecordingEnabled("pwmgr", true); diff --git a/browser/themes/shared/notification-icons.inc.css b/browser/themes/shared/notification-icons.inc.css index f17ddae9dc79..979ae9482244 100644 --- a/browser/themes/shared/notification-icons.inc.css +++ b/browser/themes/shared/notification-icons.inc.css @@ -117,6 +117,9 @@ list-style-image: url(chrome://browser/skin/notification-icons/indexedDB.svg); } +/* Reuse Firefox's login (key) icon for the Tor onion services auth. prompt */ +.popup-notification-icon[popupid="tor-clientauth"], +.tor-clientauth-icon, .popup-notification-icon[popupid="password"], .login-icon { list-style-image: url(chrome://browser/skin/login.svg); diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp index ee4af5e161f7..2bb1b7253c32 100644 --- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -3538,6 +3538,7 @@ nsDocShell::DisplayLoadError(nsresult aError, nsIURI* aURI, } } else { // Errors requiring simple formatting + bool isOnionAuthError = false; switch (aError) { case NS_ERROR_MALFORMED_URI: // URI is malformed @@ -3620,10 +3621,44 @@ nsDocShell::DisplayLoadError(nsresult aError, nsIURI* aURI, // HTTP/2 or HTTP/3 stack detected a protocol error error = "networkProtocolError"; break; - + case NS_ERROR_TOR_ONION_SVC_NOT_FOUND: + error = "onionServices.descNotFound"; + break; + case NS_ERROR_TOR_ONION_SVC_IS_INVALID: + error = "onionServices.descInvalid"; + break; + case NS_ERROR_TOR_ONION_SVC_INTRO_FAILED: + error = "onionServices.introFailed"; + break; + case NS_ERROR_TOR_ONION_SVC_REND_FAILED: + error = "onionServices.rendezvousFailed"; + break; + case NS_ERROR_TOR_ONION_SVC_MISSING_CLIENT_AUTH: + error = "onionServices.clientAuthMissing"; + isOnionAuthError = true; + break; + case NS_ERROR_TOR_ONION_SVC_BAD_CLIENT_AUTH: + error = "onionServices.clientAuthIncorrect"; + isOnionAuthError = true; + break; + case NS_ERROR_TOR_ONION_SVC_BAD_ADDRESS: + error = "onionServices.badAddress"; + break; + case NS_ERROR_TOR_ONION_SVC_INTRO_TIMEDOUT: + error = "onionServices.introTimedOut"; + break; default: break; } + + // The presence of aFailedChannel indicates that we arrived here due to a + // failed connection attempt. Note that we will arrive here a second time + // if the user cancels the Tor client auth prompt, but in that case we + // will not have a failed channel and therefore we will not prompt again. + if (isOnionAuthError && aFailedChannel) { + // Display about:blank while the Tor client auth prompt is open. + errorPage.AssignLiteral("blank"); + } } // If the HTTPS-Only Mode upgraded this request and the upgrade might have @@ -3712,6 +3747,20 @@ nsDocShell::DisplayLoadError(nsresult aError, nsIURI* aURI, nsAutoString str; rv = stringBundle->FormatStringFromName(errorDescriptionID, formatStrs, str); + if (NS_FAILED(rv)) { + // As a fallback, check torbutton.properties for the error string. + const char bundleURL[] = "chrome://torbutton/locale/torbutton.properties"; + nsCOMPtr<nsIStringBundleService> stringBundleService = + mozilla::services::GetStringBundleService(); + if (stringBundleService) { + nsCOMPtr<nsIStringBundle> tbStringBundle; + if (NS_SUCCEEDED(stringBundleService->CreateBundle( + bundleURL, getter_AddRefs(tbStringBundle)))) { + rv = tbStringBundle->FormatStringFromName(errorDescriptionID, + formatStrs, str); + } + } + } NS_ENSURE_SUCCESS(rv, rv); messageStr.Assign(str); } @@ -6191,6 +6240,7 @@ nsresult nsDocShell::EndPageLoad(nsIWebProgress* aProgress, aStatus == NS_ERROR_NET_INADEQUATE_SECURITY || aStatus == NS_ERROR_NET_HTTP2_SENT_GOAWAY || aStatus == NS_ERROR_NET_HTTP3_PROTOCOL_ERROR || + NS_ERROR_GET_MODULE(aStatus) == NS_ERROR_MODULE_TOR || NS_ERROR_GET_MODULE(aStatus) == NS_ERROR_MODULE_SECURITY) { // Errors to be shown for any frame DisplayLoadError(aStatus, url, nullptr, aChannel); @@ -7748,6 +7798,35 @@ nsresult nsDocShell::CreateContentViewer(const nsACString& aContentType, FireOnLocationChange(this, aRequest, mCurrentURI, locationFlags); } + // Arrange to show a Tor onion service client authentication prompt if + // appropriate. + if ((mLoadType == LOAD_ERROR_PAGE) && failedChannel) { + nsresult status = NS_OK; + if (NS_SUCCEEDED(failedChannel->GetStatus(&status)) && + ((status == NS_ERROR_TOR_ONION_SVC_MISSING_CLIENT_AUTH) || + (status == NS_ERROR_TOR_ONION_SVC_BAD_CLIENT_AUTH))) { + nsAutoCString onionHost; + failedURI->GetHost(onionHost); + const char* topic = (status == NS_ERROR_TOR_ONION_SVC_MISSING_CLIENT_AUTH) + ? "tor-onion-services-clientauth-missing" + : "tor-onion-services-clientauth-incorrect"; + if (XRE_IsContentProcess()) { + nsCOMPtr<nsIBrowserChild> browserChild = GetBrowserChild(); + if (browserChild) { + static_cast<BrowserChild*>(browserChild.get()) + ->SendShowOnionServicesAuthPrompt(onionHost, nsCString(topic)); + } + } else { + nsCOMPtr<nsPIDOMWindowOuter> browserWin = GetWindow(); + nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService(); + if (browserWin && obsSvc) { + obsSvc->NotifyObservers(browserWin, topic, + NS_ConvertUTF8toUTF16(onionHost).get()); + } + } + } + } + return NS_OK; } diff --git a/dom/ipc/BrowserParent.cpp b/dom/ipc/BrowserParent.cpp index dfd15c1fd17b..7749792cafb4 100644 --- a/dom/ipc/BrowserParent.cpp +++ b/dom/ipc/BrowserParent.cpp @@ -3941,6 +3941,27 @@ mozilla::ipc::IPCResult BrowserParent::RecvShowCanvasPermissionPrompt( return IPC_OK(); } +mozilla::ipc::IPCResult BrowserParent::RecvShowOnionServicesAuthPrompt( + const nsCString& aOnionName, const nsCString& aTopic) { + nsCOMPtr<nsIBrowser> browser = + mFrameElement ? mFrameElement->AsBrowser() : nullptr; + if (!browser) { + // If the tab is being closed, the browser may not be available. + // In this case we can ignore the request. + return IPC_OK(); + } + nsCOMPtr<nsIObserverService> os = services::GetObserverService(); + if (!os) { + return IPC_FAIL_NO_REASON(this); + } + nsresult rv = os->NotifyObservers(browser, aTopic.get(), + NS_ConvertUTF8toUTF16(aOnionName).get()); + if (NS_FAILED(rv)) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); +} + mozilla::ipc::IPCResult BrowserParent::RecvVisitURI(nsIURI* aURI, nsIURI* aLastVisitedURI, const uint32_t& aFlags) { diff --git a/dom/ipc/BrowserParent.h b/dom/ipc/BrowserParent.h index 66509194edba..816945504b6c 100644 --- a/dom/ipc/BrowserParent.h +++ b/dom/ipc/BrowserParent.h @@ -763,6 +763,9 @@ class BrowserParent final : public PBrowserParent, mozilla::ipc::IPCResult RecvShowCanvasPermissionPrompt( const nsCString& aOrigin, const bool& aHideDoorHanger); + mozilla::ipc::IPCResult RecvShowOnionServicesAuthPrompt( + const nsCString& aOnionName, const nsCString& aTopic); + mozilla::ipc::IPCResult RecvSetSystemFont(const nsCString& aFontName); mozilla::ipc::IPCResult RecvGetSystemFont(nsCString* aFontName); diff --git a/dom/ipc/PBrowser.ipdl b/dom/ipc/PBrowser.ipdl index ed31b32a4eb2..f36e2c6db353 100644 --- a/dom/ipc/PBrowser.ipdl +++ b/dom/ipc/PBrowser.ipdl @@ -592,6 +592,15 @@ parent: bool aNeedCollectSHistory, uint32_t aFlushId, bool aIsFinal, uint32_t aEpoch); + /** + * This function is used to notify the parent that it should display a + * onion services client authentication prompt. + * + * @param aOnionHost The hostname of the .onion that needs authentication. + * @param aTopic The reason for the prompt. + */ + async ShowOnionServicesAuthPrompt(nsCString aOnionHost, nsCString aTopic); + child: async NativeSynthesisResponse(uint64_t aObserverId, nsCString aResponse); async FlushTabState(uint32_t aFlushId, bool aIsFinal); diff --git a/js/xpconnect/src/xpc.msg b/js/xpconnect/src/xpc.msg index d884c6a85999..31e5e75ba35c 100644 --- a/js/xpconnect/src/xpc.msg +++ b/js/xpconnect/src/xpc.msg @@ -253,5 +253,15 @@ XPC_MSG_DEF(NS_ERROR_FINGERPRINTING_URI , "The URI is fingerprinti XPC_MSG_DEF(NS_ERROR_CRYPTOMINING_URI , "The URI is cryptomining") XPC_MSG_DEF(NS_ERROR_SOCIALTRACKING_URI , "The URI is social tracking") +/* Codes related to Tor */ +XPC_MSG_DEF(NS_ERROR_TOR_ONION_SVC_NOT_FOUND , "Tor onion service descriptor cannot be found") +XPC_MSG_DEF(NS_ERROR_TOR_ONION_SVC_IS_INVALID , "Tor onion service descriptor is invalid") +XPC_MSG_DEF(NS_ERROR_TOR_ONION_SVC_INTRO_FAILED , "Tor onion service introduction failed") +XPC_MSG_DEF(NS_ERROR_TOR_ONION_SVC_REND_FAILED , "Tor onion service rendezvous failed") +XPC_MSG_DEF(NS_ERROR_TOR_ONION_SVC_MISSING_CLIENT_AUTH, "Tor onion service missing client authorization") +XPC_MSG_DEF(NS_ERROR_TOR_ONION_SVC_BAD_CLIENT_AUTH , "Tor onion service wrong client authorization") +XPC_MSG_DEF(NS_ERROR_TOR_ONION_SVC_BAD_ADDRESS , "Tor onion service bad address") +XPC_MSG_DEF(NS_ERROR_TOR_ONION_SVC_INTRO_TIMEDOUT , "Tor onion service introduction timed out") + /* Profile manager error codes */ XPC_MSG_DEF(NS_ERROR_DATABASE_CHANGED , "Flushing the profiles to disk would have overwritten changes made elsewhere.") diff --git a/netwerk/base/nsSocketTransport2.cpp b/netwerk/base/nsSocketTransport2.cpp index 7256280697c8..ce739e1f0de8 100644 --- a/netwerk/base/nsSocketTransport2.cpp +++ b/netwerk/base/nsSocketTransport2.cpp @@ -215,6 +215,12 @@ nsresult ErrorAccordingToNSPR(PRErrorCode errorCode) { default: if (psm::IsNSSErrorCode(errorCode)) { rv = psm::GetXPCOMFromNSSError(errorCode); + } else { + // If we received a Tor extended error code via SOCKS, pass it through. + nsresult res = nsresult(errorCode); + if (NS_ERROR_GET_MODULE(res) == NS_ERROR_MODULE_TOR) { + rv = res; + } } break; diff --git a/netwerk/socket/nsSOCKSIOLayer.cpp b/netwerk/socket/nsSOCKSIOLayer.cpp index 6f2743ed5c71..57af13a7c026 100644 --- a/netwerk/socket/nsSOCKSIOLayer.cpp +++ b/netwerk/socket/nsSOCKSIOLayer.cpp @@ -1004,6 +1004,55 @@ PRStatus nsSOCKSSocketInfo::ReadV5ConnectResponseTop() { "08, Address type not supported.")); c = PR_BAD_ADDRESS_ERROR; break; + case 0xF0: // Tor SOCKS5_HS_NOT_FOUND + LOGERROR( + ("socks5: connect failed: F0," + " Tor onion service descriptor can not be found.")); + c = static_cast<uint32_t>(NS_ERROR_TOR_ONION_SVC_NOT_FOUND); + break; + case 0xF1: // Tor SOCKS5_HS_IS_INVALID + LOGERROR( + ("socks5: connect failed: F1," + " Tor onion service descriptor is invalid.")); + c = static_cast<uint32_t>(NS_ERROR_TOR_ONION_SVC_IS_INVALID); + break; + case 0xF2: // Tor SOCKS5_HS_INTRO_FAILED + LOGERROR( + ("socks5: connect failed: F2," + " Tor onion service introduction failed.")); + c = static_cast<uint32_t>(NS_ERROR_TOR_ONION_SVC_INTRO_FAILED); + break; + case 0xF3: // Tor SOCKS5_HS_REND_FAILED + LOGERROR( + ("socks5: connect failed: F3," + " Tor onion service rendezvous failed.")); + c = static_cast<uint32_t>(NS_ERROR_TOR_ONION_SVC_REND_FAILED); + break; + case 0xF4: // Tor SOCKS5_HS_MISSING_CLIENT_AUTH + LOGERROR( + ("socks5: connect failed: F4," + " Tor onion service missing client authorization.")); + c = static_cast<uint32_t>(NS_ERROR_TOR_ONION_SVC_MISSING_CLIENT_AUTH); + break; + case 0xF5: // Tor SOCKS5_HS_BAD_CLIENT_AUTH + LOGERROR( + ("socks5: connect failed: F5," + " Tor onion service wrong client authorization.")); + c = static_cast<uint32_t>(NS_ERROR_TOR_ONION_SVC_BAD_CLIENT_AUTH); + break; + case 0xF6: // Tor SOCKS5_HS_BAD_ADDRESS + LOGERROR( + ("socks5: connect failed: F6," + " Tor onion service bad address.")); + c = static_cast<uint32_t>(NS_ERROR_TOR_ONION_SVC_BAD_ADDRESS); + break; + case 0xF7: // Tor SOCKS5_HS_INTRO_TIMEDOUT + LOGERROR( + ("socks5: connect failed: F7," + " Tor onion service introduction timed out.")); + c = static_cast<uint32_t>(NS_ERROR_TOR_ONION_SVC_INTRO_TIMEDOUT); + break; + default: LOGERROR(("socks5: connect failed.")); break; diff --git a/toolkit/modules/PopupNotifications.jsm b/toolkit/modules/PopupNotifications.jsm index d31f91ab00a5..6886c0b13c5d 100644 --- a/toolkit/modules/PopupNotifications.jsm +++ b/toolkit/modules/PopupNotifications.jsm @@ -406,6 +406,8 @@ PopupNotifications.prototype = { * will be dismissed instead of removed after running the callback. * - [optional] disabled (boolean): If this is true, the button * will be disabled. + * - [optional] leaveOpen (boolean): If this is true, the notification + * will not be removed after running the callback. * - [optional] disableHighlight (boolean): If this is true, the button * will not apply the default highlight style. * If null, the notification will have a default "OK" action button @@ -1884,6 +1886,10 @@ PopupNotifications.prototype = { this._dismiss(); return; } + + if (action.leaveOpen) { + return; + } } this._remove(notification); diff --git a/toolkit/modules/RemotePageAccessManager.jsm b/toolkit/modules/RemotePageAccessManager.jsm index 9734324cbd5f..eceaa7c857de 100644 --- a/toolkit/modules/RemotePageAccessManager.jsm +++ b/toolkit/modules/RemotePageAccessManager.jsm @@ -95,6 +95,7 @@ let RemotePageAccessManager = { ], RPMPrefIsLocked: ["security.tls.version.min"], RPMAddToHistogram: ["*"], + RPMGetTorStrings: ["*"], }, "about:newinstall": { RPMGetUpdateChannel: ["*"], diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/frame-script.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/frame-script.js index 28f5d864b0bd..cd0d6d4d3656 100644 --- a/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/frame-script.js +++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/frame-script.js @@ -37,5 +37,6 @@ module.exports = { RPMRecordTelemetryEvent: false, RPMAddToHistogram: false, RPMRemoveMessageListener: false, + RPMGetTorStrings: false, }, }; diff --git a/xpcom/base/ErrorList.py b/xpcom/base/ErrorList.py index 6b1a05a91b31..5f35cf7771f9 100755 --- a/xpcom/base/ErrorList.py +++ b/xpcom/base/ErrorList.py @@ -85,6 +85,7 @@ modules["URL_CLASSIFIER"] = Mod(42) # ErrorResult gets its own module to reduce the chance of someone accidentally # defining an error code matching one of the ErrorResult ones. modules["ERRORRESULT"] = Mod(43) +modules["TOR"] = Mod(44) # NS_ERROR_MODULE_GENERAL should be used by modules that do not # care if return code values overlap. Callers of methods that @@ -1179,6 +1180,27 @@ with modules["ERRORRESULT"]: errors["NS_ERROR_INTERNAL_ERRORRESULT_RANGEERROR"] = FAILURE(5) +# ======================================================================= +# 44: Tor-specific error codes. +# ======================================================================= +with modules["TOR"]: + # Tor onion service descriptor can not be found. + errors["NS_ERROR_TOR_ONION_SVC_NOT_FOUND"] = FAILURE(1) + # Tor onion service descriptor is invalid. + errors["NS_ERROR_TOR_ONION_SVC_IS_INVALID"] = FAILURE(2) + # Tor onion service introduction failed. + errors["NS_ERROR_TOR_ONION_SVC_INTRO_FAILED"] = FAILURE(3) + # Tor onion service rendezvous failed. + errors["NS_ERROR_TOR_ONION_SVC_REND_FAILED"] = FAILURE(4) + # Tor onion service missing client authorization. + errors["NS_ERROR_TOR_ONION_SVC_MISSING_CLIENT_AUTH"] = FAILURE(5) + # Tor onion service wrong client authorization. + errors["NS_ERROR_TOR_ONION_SVC_BAD_CLIENT_AUTH"] = FAILURE(6) + # Tor onion service bad address. + errors["NS_ERROR_TOR_ONION_SVC_BAD_ADDRESS"] = FAILURE(7) + # Tor onion service introduction timed out. + errors["NS_ERROR_TOR_ONION_SVC_INTRO_TIMEDOUT"] = FAILURE(8) + # ======================================================================= # 51: NS_ERROR_MODULE_GENERAL # =======================================================================
1 0
0 0
[tor-browser/tor-browser-78.5.0esr-10.5-1] Orfox: Centralized proxy applied to AbstractCommunicator and BaseResources.
by sysrqb@torproject.org 14 Nov '20

14 Nov '20
commit 33c05e9198a0201fbc05058479fbf636e28135f9 Author: Amogh Pradeep <amoghbl1(a)gmail.com> Date: Fri Jun 12 02:07:45 2015 -0400 Orfox: Centralized proxy applied to AbstractCommunicator and BaseResources. See Bug 1357997 for partial uplift. Also: Bug 28051 - Use our Orbot for proxying our connections Bug 31144 - ESR68 Network Code Review --- .../main/java/org/mozilla/gecko/GeckoAppShell.java | 68 +++++++++++----------- .../java/org/mozilla/gecko/util/BitmapUtils.java | 7 --- .../java/org/mozilla/gecko/util/ProxySelector.java | 25 +++++++- 3 files changed, 59 insertions(+), 41 deletions(-) diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java index 995b23316c32..b9ca73bee2eb 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java @@ -1764,39 +1764,41 @@ public class GeckoAppShell { @WrapForJNI private static URLConnection getConnection(final String url) { - try { - String spec; - if (url.startsWith("android://")) { - spec = url.substring(10); - } else { - spec = url.substring(8); - } - - // Check if we are loading a package icon. - try { - if (spec.startsWith("icon/")) { - String[] splits = spec.split("/"); - if (splits.length != 2) { - return null; - } - final String pkg = splits[1]; - final PackageManager pm = getApplicationContext().getPackageManager(); - final Drawable d = pm.getApplicationIcon(pkg); - final Bitmap bitmap = BitmapUtils.getBitmapFromDrawable(d); - return new BitmapConnection(bitmap); - } - } catch (Exception ex) { - Log.e(LOGTAG, "error", ex); - } - - // if the colon got stripped, put it back - int colon = spec.indexOf(':'); - if (colon == -1 || colon > spec.indexOf('/')) { - spec = spec.replaceFirst("/", ":/"); - } - } catch (Exception ex) { - return null; - } + // Bug 31144 - Prevent potential proxy-bypass + + //try { + // String spec; + // if (url.startsWith("android://")) { + // spec = url.substring(10); + // } else { + // spec = url.substring(8); + // } + + // // Check if we are loading a package icon. + // try { + // if (spec.startsWith("icon/")) { + // String[] splits = spec.split("/"); + // if (splits.length != 2) { + // return null; + // } + // final String pkg = splits[1]; + // final PackageManager pm = getApplicationContext().getPackageManager(); + // final Drawable d = pm.getApplicationIcon(pkg); + // final Bitmap bitmap = BitmapUtils.getBitmapFromDrawable(d); + // return new BitmapConnection(bitmap); + // } + // } catch (Exception ex) { + // Log.e(LOGTAG, "error", ex); + // } + + // // if the colon got stripped, put it back + // int colon = spec.indexOf(':'); + // if (colon == -1 || colon > spec.indexOf('/')) { + // spec = spec.replaceFirst("/", ":/"); + // } + //} catch (Exception ex) { + // return null; + //} return null; } diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/BitmapUtils.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/BitmapUtils.java index 73a69a3abd66..f795dacffb47 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/BitmapUtils.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/BitmapUtils.java @@ -101,13 +101,6 @@ public final class BitmapUtils { public static Bitmap decodeUrl(final URL url) { InputStream stream = null; - try { - stream = url.openStream(); - } catch (IOException e) { - Log.w(LOGTAG, "decodeUrl: IOException downloading " + url); - return null; - } - if (stream == null) { Log.w(LOGTAG, "decodeUrl: stream not found downloading " + url); return null; diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/ProxySelector.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/ProxySelector.java index 3940d3c84249..9515975f680a 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/ProxySelector.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/ProxySelector.java @@ -29,6 +29,10 @@ import java.net.URLConnection; import java.util.List; public class ProxySelector { + private static final String TOR_PROXY_ADDRESS = "127.0.0.1"; + private static final int TOR_SOCKS_PROXY_PORT = 9150; + private static final int TOR_HTTP_PROXY_PORT = 8218; + public static URLConnection openConnectionWithProxy(final URI uri) throws IOException { java.net.ProxySelector ps = java.net.ProxySelector.getDefault(); Proxy proxy = Proxy.NO_PROXY; @@ -39,7 +43,26 @@ public class ProxySelector { } } - return uri.toURL().openConnection(proxy); + /* Ignore the proxy we found from the VM, only use Tor. We can probably + * safely use the logic in this class in the future. */ + return uri.toURL().openConnection(getProxy()); + } + + public static Proxy getProxy() { + // TODO make configurable + return new Proxy(Proxy.Type.SOCKS, new InetSocketAddress(TOR_PROXY_ADDRESS, TOR_SOCKS_PROXY_PORT)); + } + + public static String getProxyHostAddress() { + return TOR_PROXY_ADDRESS; + } + + public static int getSocksProxyPort() { + return TOR_SOCKS_PROXY_PORT; + } + + public static int getHttpProxyPort() { + return TOR_HTTP_PROXY_PORT; } public ProxySelector() {
1 0
0 0
[tor-browser/tor-browser-78.5.0esr-10.5-1] Bug 24796 - Comment out excess permissions from GeckoView
by sysrqb@torproject.org 14 Nov '20

14 Nov '20
commit ea20dbf54abd44c26f08907343340e1fb7efb9ee Author: Matthew Finkel <Matthew.Finkel(a)gmail.com> Date: Wed Apr 11 17:52:59 2018 +0000 Bug 24796 - Comment out excess permissions from GeckoView The GeckoView AndroidManifest.xml is not preprocessed unlike Fennec's manifest, so we can't use the ifdef preprocessor guards around the permissions we do not want. Commenting the permissions is the next-best-thing. --- .../android/geckoview/src/main/AndroidManifest.xml | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/mobile/android/geckoview/src/main/AndroidManifest.xml b/mobile/android/geckoview/src/main/AndroidManifest.xml index 87ad6dc28047..4c8ab2a9d996 100644 --- a/mobile/android/geckoview/src/main/AndroidManifest.xml +++ b/mobile/android/geckoview/src/main/AndroidManifest.xml @@ -6,20 +6,32 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.mozilla.geckoview"> +<!--#ifdef MOZ_ANDROID_NETWORK_STATE--> + <!-- <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> + --> +<!--#endif--> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.WAKE_LOCK"/> <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" /> +<!--#ifdef MOZ_ANDROID_LOCATION--> + <!-- <uses-feature android:name="android.hardware.location" android:required="false"/> <uses-feature android:name="android.hardware.location.gps" android:required="false"/> + --> +<!--#endif--> <uses-feature android:name="android.hardware.touchscreen" android:required="false"/> +<!--#ifdef MOZ_WEBRTC--> + <!-- TODO preprocess AndroidManifest.xml so that we can + conditionally include WebRTC permissions based on MOZ_WEBRTC. --> + <!-- <uses-feature android:name="android.hardware.camera" android:required="false"/> @@ -28,14 +40,16 @@ android:required="false"/> <uses-feature - android:name="android.hardware.audio.low_latency" + android:name="android.hardware.camera.any" android:required="false"/> <uses-feature - android:name="android.hardware.microphone" + android:name="android.hardware.audio.low_latency" android:required="false"/> <uses-feature - android:name="android.hardware.camera.any" + android:name="android.hardware.microphone" android:required="false"/> + --> +<!--#endif--> <!-- GeckoView requires OpenGL ES 2.0 --> <uses-feature
1 0
0 0
[tor-browser/tor-browser-78.5.0esr-10.5-1] Bug 28005: Implement .onion alias urlbar rewrites
by sysrqb@torproject.org 14 Nov '20

14 Nov '20
commit 9edd249882e418d36417df4aad7fe13507abe7b5 Author: Alex Catarineu <acat(a)torproject.org> Date: Thu Feb 13 13:24:33 2020 +0100 Bug 28005: Implement .onion alias urlbar rewrites A custom HTTPS Everywhere update channel is installed, which provides rules for locally redirecting some memorable .tor.onion URLs to non-memorable .onion URLs. When these redirects occur, we also rewrite the URL in the urlbar to display the human-memorable hostname instead of the actual .onion. Bug 34196: Update site info URL with the onion name --- browser/actors/ClickHandlerChild.jsm | 20 ++ browser/actors/ClickHandlerParent.jsm | 1 + browser/actors/ContextMenuChild.jsm | 4 + browser/base/content/browser-places.js | 12 +- browser/base/content/browser-siteIdentity.js | 12 +- browser/base/content/browser.js | 43 ++++- browser/base/content/nsContextMenu.js | 18 ++ browser/base/content/pageinfo/pageInfo.js | 2 +- browser/base/content/pageinfo/pageInfo.xhtml | 10 + browser/base/content/pageinfo/security.js | 17 +- browser/base/content/tabbrowser.js | 7 + browser/base/content/utilityOverlay.js | 12 ++ browser/components/BrowserGlue.jsm | 8 + .../onionservices/ExtensionMessaging.jsm | 86 +++++++++ .../onionservices/HttpsEverywhereControl.jsm | 119 ++++++++++++ .../components/onionservices/OnionAliasStore.jsm | 201 +++++++++++++++++++++ browser/components/onionservices/moz.build | 6 + browser/components/urlbar/UrlbarInput.jsm | 13 +- docshell/base/nsDocShell.cpp | 52 ++++++ docshell/base/nsDocShell.h | 6 + docshell/base/nsDocShellLoadState.cpp | 4 + docshell/base/nsIDocShell.idl | 5 + docshell/base/nsIWebNavigation.idl | 5 + docshell/shistory/SessionHistoryEntry.cpp | 14 ++ docshell/shistory/SessionHistoryEntry.h | 1 + docshell/shistory/nsISHEntry.idl | 5 + docshell/shistory/nsSHEntry.cpp | 22 ++- docshell/shistory/nsSHEntry.h | 1 + dom/interfaces/base/nsIBrowser.idl | 3 +- dom/ipc/BrowserChild.cpp | 2 + dom/ipc/BrowserParent.cpp | 3 +- dom/ipc/PBrowser.ipdl | 1 + modules/libpref/init/StaticPrefList.yaml | 6 + netwerk/dns/effective_tld_names.dat | 2 + netwerk/ipc/DocumentLoadListener.cpp | 10 + toolkit/content/widgets/browser-custom-element.js | 13 +- toolkit/modules/sessionstore/SessionHistory.jsm | 5 + 37 files changed, 729 insertions(+), 22 deletions(-) diff --git a/browser/actors/ClickHandlerChild.jsm b/browser/actors/ClickHandlerChild.jsm index 7dd060e83061..206a8fc97a4d 100644 --- a/browser/actors/ClickHandlerChild.jsm +++ b/browser/actors/ClickHandlerChild.jsm @@ -138,6 +138,26 @@ class ClickHandlerChild extends JSWindowActorChild { json.originStoragePrincipal = ownerDoc.effectiveStoragePrincipal; json.triggeringPrincipal = ownerDoc.nodePrincipal; + // Check if the link needs to be opened with .tor.onion urlbar rewrites + // allowed. Only when the owner doc has onionUrlbarRewritesAllowed = true + // and the same origin we should allow this. + json.onionUrlbarRewritesAllowed = false; + if (this.docShell.onionUrlbarRewritesAllowed) { + const sm = Services.scriptSecurityManager; + try { + let targetURI = Services.io.newURI(href); + let isPrivateWin = + ownerDoc.nodePrincipal.originAttributes.privateBrowsingId > 0; + sm.checkSameOriginURI( + docshell.currentDocumentChannel.URI, + targetURI, + false, + isPrivateWin + ); + json.onionUrlbarRewritesAllowed = true; + } catch (e) {} + } + // If a link element is clicked with middle button, user wants to open // the link somewhere rather than pasting clipboard content. Therefore, // when it's clicked with middle button, we should prevent multiple diff --git a/browser/actors/ClickHandlerParent.jsm b/browser/actors/ClickHandlerParent.jsm index 454c0fe69b27..42ab7a0f6e2a 100644 --- a/browser/actors/ClickHandlerParent.jsm +++ b/browser/actors/ClickHandlerParent.jsm @@ -102,6 +102,7 @@ class ClickHandlerParent extends JSWindowActorParent { charset: browser.characterSet, referrerInfo: E10SUtils.deserializeReferrerInfo(data.referrerInfo), allowMixedContent: data.allowMixedContent, + onionUrlbarRewritesAllowed: data.onionUrlbarRewritesAllowed, isContentWindowPrivate: data.isContentWindowPrivate, originPrincipal: data.originPrincipal, originStoragePrincipal: data.originStoragePrincipal, diff --git a/browser/actors/ContextMenuChild.jsm b/browser/actors/ContextMenuChild.jsm index 16a3f8ecbc81..1a1b38aa6375 100644 --- a/browser/actors/ContextMenuChild.jsm +++ b/browser/actors/ContextMenuChild.jsm @@ -575,6 +575,9 @@ class ContextMenuChild extends JSWindowActorChild { // The same-origin check will be done in nsContextMenu.openLinkInTab. let parentAllowsMixedContent = !!this.docShell.mixedContentChannel; + let parentAllowsOnionUrlbarRewrites = this.docShell + .onionUrlbarRewritesAllowed; + let disableSetDesktopBackground = null; // Media related cache info parent needs for saving @@ -687,6 +690,7 @@ class ContextMenuChild extends JSWindowActorChild { frameBrowsingContextID, disableSetDesktopBackground, parentAllowsMixedContent, + parentAllowsOnionUrlbarRewrites, }; if (context.inFrame && !context.inSrcdocFrame) { diff --git a/browser/base/content/browser-places.js b/browser/base/content/browser-places.js index d4b71f87da04..9a9bb3e0dad4 100644 --- a/browser/base/content/browser-places.js +++ b/browser/base/content/browser-places.js @@ -452,7 +452,8 @@ var PlacesCommandHook = { */ async bookmarkPage() { let browser = gBrowser.selectedBrowser; - let url = new URL(browser.currentURI.spec); + const uri = browser.currentOnionAliasURI || browser.currentURI; + let url = new URL(uri.spec); let info = await PlacesUtils.bookmarks.fetch({ url }); let isNewBookmark = !info; let showEditUI = !isNewBookmark || StarUI.showForNewBookmarks; @@ -556,7 +557,7 @@ var PlacesCommandHook = { tabs.forEach(tab => { let browser = tab.linkedBrowser; - let uri = browser.currentURI; + let uri = browser.currentOnionAliasURI || browser.currentURI; let title = browser.contentTitle || tab.label; let spec = uri.spec; if (!(spec in uniquePages)) { @@ -1655,14 +1656,17 @@ var BookmarkingUI = { }, onLocationChange: function BUI_onLocationChange() { - if (this._uri && gBrowser.currentURI.equals(this._uri)) { + const uri = + gBrowser.selectedBrowser.currentOnionAliasURI || gBrowser.currentURI; + if (this._uri && uri.equals(this._uri)) { return; } this.updateStarState(); }, updateStarState: function BUI_updateStarState() { - this._uri = gBrowser.currentURI; + this._uri = + gBrowser.selectedBrowser.currentOnionAliasURI || gBrowser.currentURI; this._itemGuids.clear(); let guids = new Set(); diff --git a/browser/base/content/browser-siteIdentity.js b/browser/base/content/browser-siteIdentity.js index b8172d6a85f6..b7df1ebaa187 100644 --- a/browser/base/content/browser-siteIdentity.js +++ b/browser/base/content/browser-siteIdentity.js @@ -474,13 +474,13 @@ var gIdentityHandler = { * nsIURI for which the identity UI should be displayed, already * processed by createExposableURI. */ - updateIdentity(state, uri) { + updateIdentity(state, uri, onionAliasURI) { let shouldHidePopup = this._uri && this._uri.spec != uri.spec; this._state = state; // Firstly, populate the state properties required to display the UI. See // the documentation of the individual properties for details. - this.setURI(uri); + this.setURI(uri, onionAliasURI); this._secInfo = gBrowser.securityUI.secInfo; this._isSecureContext = gBrowser.securityUI.isSecureContext; @@ -566,17 +566,18 @@ var gIdentityHandler = { * Attempt to provide proper IDN treatment for host names */ getEffectiveHost() { + let uri = this._onionAliasURI || this._uri; if (!this._IDNService) { this._IDNService = Cc["@mozilla.org/network/idn-service;1"].getService( Ci.nsIIDNService ); } try { - return this._IDNService.convertToDisplayIDN(this._uri.host, {}); + return this._IDNService.convertToDisplayIDN(uri.host, {}); } catch (e) { // If something goes wrong (e.g. host is an IP address) just fail back // to the full domain. - return this._uri.host; + return uri.host; } }, @@ -1010,8 +1011,9 @@ var gIdentityHandler = { this.updateSitePermissions(); }, - setURI(uri) { + setURI(uri, onionAliasURI) { this._uri = uri; + this._onionAliasURI = onionAliasURI; try { // Account for file: urls and catch when "" is the value diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index 6246068e88df..e1681a2759db 100644 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -77,6 +77,7 @@ XPCOMUtils.defineLazyModuleGetters(this, { TabCrashHandler: "resource:///modules/ContentCrashHandlers.jsm", TelemetryEnvironment: "resource://gre/modules/TelemetryEnvironment.jsm", Translation: "resource:///modules/translation/TranslationParent.jsm", + OnionAliasStore: "resource:///modules/OnionAliasStore.jsm", UITour: "resource:///modules/UITour.jsm", UpdateUtils: "resource://gre/modules/UpdateUtils.jsm", UrlbarInput: "resource:///modules/UrlbarInput.jsm", @@ -2324,6 +2325,7 @@ var gBrowserInit = { // [9]: allowInheritPrincipal (bool) // [10]: csp (nsIContentSecurityPolicy) // [11]: nsOpenWindowInfo + // [12]: onionUrlbarRewritesAllowed (bool) let userContextId = window.arguments[5] != undefined ? window.arguments[5] @@ -2343,7 +2345,8 @@ var gBrowserInit = { // TODO fix allowInheritPrincipal to default to false. // Default to true unless explicitly set to false because of bug 1475201. window.arguments[9] !== false, - window.arguments[10] + window.arguments[10], + window.arguments[12] ); window.focus(); } else { @@ -3229,7 +3232,8 @@ function loadURI( forceAboutBlankViewerInCurrent, triggeringPrincipal, allowInheritPrincipal = false, - csp = null + csp = null, + onionUrlbarRewritesAllowed = false ) { if (!triggeringPrincipal) { throw new Error("Must load with a triggering Principal"); @@ -3247,6 +3251,7 @@ function loadURI( csp, forceAboutBlankViewerInCurrent, allowInheritPrincipal, + onionUrlbarRewritesAllowed, }); } catch (e) { Cu.reportError(e); @@ -5358,11 +5363,24 @@ var XULBrowserWindow = { this.reloadCommand.removeAttribute("disabled"); } + // The onion memorable alias needs to be used in gURLBar.setURI, but also in + // other parts of the code (like the bookmarks UI), so we save it. + if (gBrowser.selectedBrowser.onionUrlbarRewritesAllowed) { + gBrowser.selectedBrowser.currentOnionAliasURI = OnionAliasStore.getShortURI( + aLocationURI + ); + } else { + gBrowser.selectedBrowser.currentOnionAliasURI = null; + } + // We want to update the popup visibility if we received this notification // via simulated locationchange events such as switching between tabs, however // if this is a document navigation then PopupNotifications will be updated // via TabsProgressListener.onLocationChange and we do not want it called twice - gURLBar.setURI(aLocationURI, aIsSimulated); + gURLBar.setURI( + gBrowser.selectedBrowser.currentOnionAliasURI || aLocationURI, + aIsSimulated + ); BookmarkingUI.onLocationChange(); @@ -5533,6 +5551,7 @@ var XULBrowserWindow = { // Don't need to do anything if the data we use to update the UI hasn't // changed let uri = gBrowser.currentURI; + let onionAliasURI = gBrowser.selectedBrowser.currentOnionAliasURI; let spec = uri.spec; if (this._state == aState && this._lastLocation == spec) { // Switching to a tab of the same URL doesn't change most security @@ -5550,7 +5569,7 @@ var XULBrowserWindow = { try { uri = Services.io.createExposableURI(uri); } catch (e) {} - gIdentityHandler.updateIdentity(this._state, uri); + gIdentityHandler.updateIdentity(this._state, uri, onionAliasURI); }, // simulate all change notifications after switching tabs @@ -7010,6 +7029,21 @@ function handleLinkClick(event, href, linkNode) { } catch (e) {} } + // Check if the link needs to be opened with .tor.onion urlbar rewrites + // allowed. Only when the owner doc has onionUrlbarRewritesAllowed = true + // and the same origin we should allow this. + let persistOnionUrlbarRewritesAllowedInChildTab = false; + if (where == "tab" && gBrowser.docShell.onionUrlbarRewritesAllowed) { + const sm = Services.scriptSecurityManager; + try { + let tURI = makeURI(href); + let isPrivateWin = + doc.nodePrincipal.originAttributes.privateBrowsingId > 0; + sm.checkSameOriginURI(doc.documentURIObject, tURI, false, isPrivateWin); + persistOnionUrlbarRewritesAllowedInChildTab = true; + } catch (e) {} + } + let frameOuterWindowID = WebNavigationFrames.getFrameId(doc.defaultView); urlSecurityCheck(href, doc.nodePrincipal); @@ -7022,6 +7056,7 @@ function handleLinkClick(event, href, linkNode) { triggeringPrincipal: doc.nodePrincipal, csp: doc.csp, frameOuterWindowID, + onionUrlbarRewritesAllowed: persistOnionUrlbarRewritesAllowedInChildTab, }; // The new tab/window must use the same userContextId diff --git a/browser/base/content/nsContextMenu.js b/browser/base/content/nsContextMenu.js index 314f7a1364da..80a8379ab35b 100644 --- a/browser/base/content/nsContextMenu.js +++ b/browser/base/content/nsContextMenu.js @@ -57,6 +57,7 @@ function openContextMenu(aMessage, aBrowser, aActor) { disableSetDesktopBackground: data.disableSetDesktopBackground, loginFillInfo: data.loginFillInfo, parentAllowsMixedContent: data.parentAllowsMixedContent, + parentAllowsOnionUrlbarRewrites: data.parentAllowsOnionUrlbarRewrites, userContextId: data.userContextId, webExtContextData: data.webExtContextData, }; @@ -1049,6 +1050,7 @@ class nsContextMenu { triggeringPrincipal: this.principal, csp: this.csp, frameOuterWindowID: this.contentData.frameOuterWindowID, + onionUrlbarRewritesAllowed: false, }; for (let p in extra) { params[p] = extra[p]; @@ -1072,6 +1074,22 @@ class nsContextMenu { } params.referrerInfo = referrerInfo; + + // Check if the link needs to be opened with .tor.onion urlbar rewrites + // allowed. Only when parent has onionUrlbarRewritesAllowed = true + // and the same origin we should allow this. + if (this.contentData.parentAllowsOnionUrlbarRewrites) { + let referrerURI = this.contentData.documentURIObject; + const sm = Services.scriptSecurityManager; + try { + let targetURI = this.linkURI; + let isPrivateWin = + this.browser.contentPrincipal.originAttributes.privateBrowsingId > 0; + sm.checkSameOriginURI(referrerURI, targetURI, false, isPrivateWin); + params.onionUrlbarRewritesAllowed = true; + } catch (e) {} + } + return params; } diff --git a/browser/base/content/pageinfo/pageInfo.js b/browser/base/content/pageinfo/pageInfo.js index 664dace84d9f..cd03cea3b6d9 100644 --- a/browser/base/content/pageinfo/pageInfo.js +++ b/browser/base/content/pageinfo/pageInfo.js @@ -390,7 +390,7 @@ async function onNonMediaPageInfoLoad(browser, pageInfoData, imageInfo) { ); } onLoadPermission(uri, principal); - securityOnLoad(uri, windowInfo); + securityOnLoad(uri, windowInfo, browser.currentOnionAliasURI); } function resetPageInfo(args) { diff --git a/browser/base/content/pageinfo/pageInfo.xhtml b/browser/base/content/pageinfo/pageInfo.xhtml index f40ffd3778d8..a23f2bb5748c 100644 --- a/browser/base/content/pageinfo/pageInfo.xhtml +++ b/browser/base/content/pageinfo/pageInfo.xhtml @@ -312,6 +312,16 @@ <input id="security-identity-domain-value" readonly="readonly"/> </td> </tr> + <!-- Onion Alias --> + <tr id="security-view-identity-onionalias-row"> + <th> + <xul:label id="security-view-identity-onionalias" + control="security-view-identity-onionalias-value"/> + </th> + <td> + <input id="security-view-identity-onionalias-value" readonly="true"/> + </td> + </tr> <!-- Owner --> <tr> <th> diff --git a/browser/base/content/pageinfo/security.js b/browser/base/content/pageinfo/security.js index 4331ebc4b219..29395e96ce57 100644 --- a/browser/base/content/pageinfo/security.js +++ b/browser/base/content/pageinfo/security.js @@ -250,7 +250,7 @@ var security = { }, }; -async function securityOnLoad(uri, windowInfo) { +async function securityOnLoad(uri, windowInfo, onionAliasURI) { await security.init(uri, windowInfo); let info = security.securityInfo; @@ -263,6 +263,21 @@ async function securityOnLoad(uri, windowInfo) { } document.getElementById("securityTab").hidden = false; + if (onionAliasURI) { + setText( + "security-view-identity-onionalias", + gTorButtonBundle.GetStringFromName("pageInfo_OnionName") + ); + setText("security-view-identity-onionalias-value", onionAliasURI.host); + document.getElementById( + "security-view-identity-onionalias-row" + ).hidden = false; + } else { + document.getElementById( + "security-view-identity-onionalias-row" + ).hidden = true; + } + /* Set Identity section text */ setText("security-identity-domain-value", windowInfo.hostName); diff --git a/browser/base/content/tabbrowser.js b/browser/base/content/tabbrowser.js index 22c7fafef72b..573b114d7813 100644 --- a/browser/base/content/tabbrowser.js +++ b/browser/base/content/tabbrowser.js @@ -1547,6 +1547,7 @@ var aRelatedToCurrent; var aAllowInheritPrincipal; var aAllowMixedContent; + var aOnionUrlbarRewritesAllowed; var aSkipAnimation; var aForceNotRemote; var aPreferredRemoteType; @@ -1577,6 +1578,7 @@ aRelatedToCurrent = params.relatedToCurrent; aAllowInheritPrincipal = !!params.allowInheritPrincipal; aAllowMixedContent = params.allowMixedContent; + aOnionUrlbarRewritesAllowed = params.onionUrlbarRewritesAllowed; aSkipAnimation = params.skipAnimation; aForceNotRemote = params.forceNotRemote; aPreferredRemoteType = params.preferredRemoteType; @@ -1618,6 +1620,7 @@ relatedToCurrent: aRelatedToCurrent, skipAnimation: aSkipAnimation, allowMixedContent: aAllowMixedContent, + onionUrlbarRewritesAllowed: aOnionUrlbarRewritesAllowed, forceNotRemote: aForceNotRemote, createLazyBrowser: aCreateLazyBrowser, preferredRemoteType: aPreferredRemoteType, @@ -2504,6 +2507,7 @@ { allowInheritPrincipal, allowMixedContent, + onionUrlbarRewritesAllowed, allowThirdPartyFixup, bulkOrderedOpen, charset, @@ -2833,6 +2837,9 @@ if (allowMixedContent) { flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_MIXED_CONTENT; } + if (onionUrlbarRewritesAllowed) { + flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_ONION_URLBAR_REWRITES; + } if (!allowInheritPrincipal) { flags |= Ci.nsIWebNavigation.LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL; } diff --git a/browser/base/content/utilityOverlay.js b/browser/base/content/utilityOverlay.js index a23d6f05e6a7..eb13d5a3435c 100644 --- a/browser/base/content/utilityOverlay.js +++ b/browser/base/content/utilityOverlay.js @@ -367,6 +367,7 @@ function openLinkIn(url, where, params) { var aRelatedToCurrent = params.relatedToCurrent; var aAllowInheritPrincipal = !!params.allowInheritPrincipal; var aAllowMixedContent = params.allowMixedContent; + var aOnionUrlbarRewritesAllowed = params.onionUrlbarRewritesAllowed; var aForceAllowDataURI = params.forceAllowDataURI; var aInBackground = params.inBackground; var aInitiatingDoc = params.initiatingDoc; @@ -482,6 +483,11 @@ function openLinkIn(url, where, params) { ].createInstance(Ci.nsISupportsPRBool); allowThirdPartyFixupSupports.data = aAllowThirdPartyFixup; + var onionUrlbarRewritesAllowed = Cc[ + "@mozilla.org/supports-PRBool;1" + ].createInstance(Ci.nsISupportsPRBool); + onionUrlbarRewritesAllowed.data = aOnionUrlbarRewritesAllowed; + var userContextIdSupports = Cc[ "@mozilla.org/supports-PRUint32;1" ].createInstance(Ci.nsISupportsPRUint32); @@ -498,6 +504,8 @@ function openLinkIn(url, where, params) { sa.appendElement(aTriggeringPrincipal); sa.appendElement(null); // allowInheritPrincipal sa.appendElement(aCsp); + sa.appendElement(null); // nsOpenWindowInfo + sa.appendElement(onionUrlbarRewritesAllowed); const sourceWindow = w || window; let win; @@ -614,6 +622,9 @@ function openLinkIn(url, where, params) { if (aForceAllowDataURI) { flags |= Ci.nsIWebNavigation.LOAD_FLAGS_FORCE_ALLOW_DATA_URI; } + if (aOnionUrlbarRewritesAllowed) { + flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_ONION_URLBAR_REWRITES; + } let { URI_INHERITS_SECURITY_CONTEXT } = Ci.nsIProtocolHandler; if ( @@ -661,6 +672,7 @@ function openLinkIn(url, where, params) { relatedToCurrent: aRelatedToCurrent, skipAnimation: aSkipTabAnimation, allowMixedContent: aAllowMixedContent, + onionUrlbarRewritesAllowed: aOnionUrlbarRewritesAllowed, userContextId: aUserContextId, originPrincipal: aPrincipal, originStoragePrincipal: aStoragePrincipal, diff --git a/browser/components/BrowserGlue.jsm b/browser/components/BrowserGlue.jsm index 7d147d01d561..d30abff54562 100644 --- a/browser/components/BrowserGlue.jsm +++ b/browser/components/BrowserGlue.jsm @@ -703,6 +703,7 @@ XPCOMUtils.defineLazyModuleGetters(this, { TabCrashHandler: "resource:///modules/ContentCrashHandlers.jsm", TabUnloader: "resource:///modules/TabUnloader.jsm", TRRRacer: "resource:///modules/TRRPerformance.jsm", + OnionAliasStore: "resource:///modules/OnionAliasStore.jsm", UIState: "resource://services-sync/UIState.jsm", WebChannel: "resource://gre/modules/WebChannel.jsm", WindowsRegistry: "resource://gre/modules/WindowsRegistry.jsm", @@ -2046,6 +2047,7 @@ BrowserGlue.prototype = { Normandy.uninit(); RFPHelper.uninit(); + OnionAliasStore.uninit(); }, // Set up a listener to enable/disable the screenshots extension @@ -2412,6 +2414,12 @@ BrowserGlue.prototype = { }, }, + { + task: () => { + OnionAliasStore.init(); + }, + }, + { task: () => { Blocklist.loadBlocklistAsync(); diff --git a/browser/components/onionservices/ExtensionMessaging.jsm b/browser/components/onionservices/ExtensionMessaging.jsm new file mode 100644 index 000000000000..b5d69df93807 --- /dev/null +++ b/browser/components/onionservices/ExtensionMessaging.jsm @@ -0,0 +1,86 @@ +// Copyright (c) 2020, The Tor Project, Inc. + +"use strict"; + +const EXPORTED_SYMBOLS = ["ExtensionMessaging"]; + +const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); +const { ExtensionUtils } = ChromeUtils.import( + "resource://gre/modules/ExtensionUtils.jsm" +); +const { MessageChannel } = ChromeUtils.import( + "resource://gre/modules/MessageChannel.jsm" +); +const { AddonManager } = ChromeUtils.import( + "resource://gre/modules/AddonManager.jsm" +); + +class ExtensionMessaging { + constructor() { + this._callback = null; + this._handlers = new Map(); + this._messageManager = Services.cpmm; + } + + async sendMessage(msg, extensionId) { + this._init(); + + const addon = await AddonManager.getAddonByID(extensionId); + if (!addon) { + throw new Error(`extension '${extensionId} does not exist`); + } + await addon.startupPromise; + + const channelId = ExtensionUtils.getUniqueId(); + return new Promise((resolve, reject) => { + this._handlers.set(channelId, { resolve, reject }); + this._messageManager.sendAsyncMessage("MessageChannel:Messages", [ + { + messageName: "Extension:Message", + sender: { + id: extensionId, + extensionId, + }, + recipient: { extensionId }, + data: new StructuredCloneHolder(msg), + channelId, + responseType: MessageChannel.RESPONSE_FIRST, + }, + ]); + }); + } + + unload() { + if (this._callback) { + this._handlers.clear(); + this._messageManager.removeMessageListener( + "MessageChannel:Response", + this._callback + ); + this._callback = null; + } + } + + _onMessage({ data }) { + const channelId = data.messageName; + if (this._handlers.has(channelId)) { + const { resolve, reject } = this._handlers.get(channelId); + this._handlers.delete(channelId); + if (data.error) { + reject(new Error(data.error.message)); + } else { + resolve(data.value); + } + } + } + + _init() { + if (this._callback === null) { + this._callback = this._onMessage.bind(this); + this._messageManager.addMessageListener( + "MessageChannel:Response", + this._callback + ); + } + } +} diff --git a/browser/components/onionservices/HttpsEverywhereControl.jsm b/browser/components/onionservices/HttpsEverywhereControl.jsm new file mode 100644 index 000000000000..60c3b5fca282 --- /dev/null +++ b/browser/components/onionservices/HttpsEverywhereControl.jsm @@ -0,0 +1,119 @@ +// Copyright (c) 2020, The Tor Project, Inc. + +"use strict"; + +const EXPORTED_SYMBOLS = ["HttpsEverywhereControl"]; + +const { ExtensionMessaging } = ChromeUtils.import( + "resource:///modules/ExtensionMessaging.jsm" +); +const { setTimeout } = ChromeUtils.import("resource://gre/modules/Timer.jsm"); + +const EXTENSION_ID = "https-everywhere-eff(a)eff.org"; +const SECUREDROP_TOR_ONION_CHANNEL = { + name: "SecureDropTorOnion", + jwk: { + kty: "RSA", + e: "AQAB", + n: + "p10BbUVc5Xj2S_-MH3bACNBaISo_r9e3PVPyTTjsGsdg2qSXvqUO42fBtpFAy0zUzIGS83v4JjiRdvKJaZTIvbC8AcpymzdsTqujMm8RPTSy3hO_8mXzGa4DEsIB1uNLnUWRBKXvSGCmT9kFyxhTpkYqokNBzafVihTU34tN2Md1xFHnmZGqfYtPtbJLWAa5Z1M11EyR4lIyUxIiPTV9t1XstDbWr3iS83REJrGEFmjG1-BAgx8_lDUTa41799N2yYEhgZud7bL0M3ei8s5OERjiion5uANkUV3-s2QqUZjiVA-XR_HizXjciaUWNd683KqekpNOZ_0STh_UGwpcwU-KwG07QyiCrLrRpz8S_vH8CqGrrcWY3GSzYe9dp34jJdO65oA-G8tK6fMXtvTCFDZI6oNNaXJH71F5J0YbqO2ZqwKYc2WSi0gKVl2wd9roOVjaBmkJqvocntYuNM7t38fDEWHn5KUkmrTbiG68Cy56tDUfpKl3D9Uj4LaMvxJ1tKGvzQ4k_60odT7gIxu6DqYjXUHZpwPsSGBq3njaD7boe4CUXF2K7ViOc87BsKxRNCzDD8OklRjjXzOTOBH3PqFJ93CJ-4ECE5t9STU20aZ8E-2zKB8vjKyCySE4-kcIvBBsnkwVaJTPy9Ft1qYybo-soXEWVEZATANNWklBt8k", + }, + update_path_prefix: "https://securedrop.org/https-everywhere/", + scope: + "^https?:\\/\\/[a-z0-9-]+(?:\\.[a-z0-9-]+)*\\.securedrop\\.tor\\.onion\\/", + replaces_default_rulesets: false, +}; + +class HttpsEverywhereControl { + constructor() { + this._extensionMessaging = null; + } + + async _sendMessage(type, object) { + return this._extensionMessaging.sendMessage( + { + type, + object, + }, + EXTENSION_ID + ); + } + + static async wait(seconds = 1) { + return new Promise(resolve => setTimeout(resolve, seconds * 1000)); + } + + /** + * Installs the .tor.onion update channel in https-everywhere + */ + async installTorOnionUpdateChannel(retries = 5) { + this._init(); + + // TODO: https-everywhere store is initialized asynchronously, so sending a message + // immediately results in a `store.get is undefined` error. + // For now, let's wait a bit and retry a few times if there is an error, but perhaps + // we could suggest https-everywhere to send a message when that happens and listen + // for that here. + await HttpsEverywhereControl.wait(); + + try { + // TODO: we may want a way to "lock" this update channel, so that it cannot be modified + // by the user via UI, but I think this is not possible at the time of writing via + // the existing messages in https-everywhere. + await this._sendMessage( + "create_update_channel", + SECUREDROP_TOR_ONION_CHANNEL.name + ); + } catch (e) { + if (retries <= 0) { + throw new Error("Could not install SecureDropTorOnion update channel"); + } + await this.installTorOnionUpdateChannel(retries - 1); + return; + } + + await this._sendMessage( + "update_update_channel", + SECUREDROP_TOR_ONION_CHANNEL + ); + } + + /** + * Returns the .tor.onion rulesets available in https-everywhere + */ + async getTorOnionRules() { + return this._sendMessage("get_simple_rules_ending_with", ".tor.onion"); + } + + /** + * Returns the timestamp of the last .tor.onion update channel update. + */ + async getRulesetTimestamp() { + const rulesets = await this._sendMessage("get_ruleset_timestamps"); + const securedrop = + rulesets && + rulesets.find(([{ name }]) => name === SECUREDROP_TOR_ONION_CHANNEL.name); + if (securedrop) { + const [ + updateChannel, // This has the same structure as SECUREDROP_TOR_ONION_CHANNEL + lastUpdatedTimestamp, // An integer, 0 if the update channel was never updated + ] = securedrop; + void updateChannel; // Ignore eslint unused warning for ruleset + return lastUpdatedTimestamp; + } + return null; + } + + unload() { + if (this._extensionMessaging) { + this._extensionMessaging.unload(); + this._extensionMessaging = null; + } + } + + _init() { + if (!this._extensionMessaging) { + this._extensionMessaging = new ExtensionMessaging(); + } + } +} diff --git a/browser/components/onionservices/OnionAliasStore.jsm b/browser/components/onionservices/OnionAliasStore.jsm new file mode 100644 index 000000000000..66cf569227bf --- /dev/null +++ b/browser/components/onionservices/OnionAliasStore.jsm @@ -0,0 +1,201 @@ +// Copyright (c) 2020, The Tor Project, Inc. + +"use strict"; + +const EXPORTED_SYMBOLS = ["OnionAliasStore"]; + +const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); +const { XPCOMUtils } = ChromeUtils.import( + "resource://gre/modules/XPCOMUtils.jsm" +); +const { setTimeout, clearTimeout } = ChromeUtils.import( + "resource://gre/modules/Timer.jsm" +); +const { HttpsEverywhereControl } = ChromeUtils.import( + "resource:///modules/HttpsEverywhereControl.jsm" +); + +// Logger adapted from CustomizableUI.jsm +const kPrefOnionAliasDebug = "browser.onionalias.debug"; +XPCOMUtils.defineLazyPreferenceGetter( + this, + "gDebuggingEnabled", + kPrefOnionAliasDebug, + false, + (pref, oldVal, newVal) => { + if (typeof log != "undefined") { + log.maxLogLevel = newVal ? "all" : "log"; + } + } +); +XPCOMUtils.defineLazyGetter(this, "log", () => { + let scope = {}; + ChromeUtils.import("resource://gre/modules/Console.jsm", scope); + let consoleOptions = { + maxLogLevel: gDebuggingEnabled ? "all" : "log", + prefix: "OnionAlias", + }; + return new scope.ConsoleAPI(consoleOptions); +}); + +function observe(topic, callback) { + let observer = { + observe(aSubject, aTopic, aData) { + if (topic === aTopic) { + callback(aSubject, aData); + } + }, + }; + Services.obs.addObserver(observer, topic); + return () => Services.obs.removeObserver(observer, topic); +} + +class _OnionAliasStore { + static get RULESET_CHECK_INTERVAL() { + return 1000 * 60; // 1 minute + } + + static get RULESET_CHECK_INTERVAL_FAST() { + return 1000 * 5; // 5 seconds + } + + constructor() { + this._onionMap = new Map(); + this._rulesetTimeout = null; + this._removeObserver = () => {}; + this._canLoadRules = false; + this._rulesetTimestamp = null; + this._updateChannelInstalled = false; + } + + async _periodicRulesetCheck() { + // TODO: it would probably be preferable to listen to some message broadcasted by + // the https-everywhere extension when some update channel is updated, instead of + // polling every N seconds. + log.debug("Checking for new rules"); + const ts = await this.httpsEverywhereControl.getRulesetTimestamp(); + log.debug( + `Found ruleset timestamp ${ts}, current is ${this._rulesetTimestamp}` + ); + if (ts !== this._rulesetTimestamp) { + this._rulesetTimestamp = ts; + log.debug("New rules found, updating"); + // We clear the mappings even if we cannot load the rules from https-everywhere, + // since we cannot be sure if the stored mappings are correct anymore. + this._clear(); + if (this._canLoadRules) { + await this._loadRules(); + } + } + // If the timestamp is 0, that means the update channel was not yet updated, so + // we schedule a check soon. + this._rulesetTimeout = setTimeout( + () => this._periodicRulesetCheck(), + ts === 0 + ? _OnionAliasStore.RULESET_CHECK_INTERVAL_FAST + : _OnionAliasStore.RULESET_CHECK_INTERVAL + ); + } + + async init() { + this.httpsEverywhereControl = new HttpsEverywhereControl(); + + // Setup .tor.onion rule loading. + // The http observer is a fallback, and is removed in _loadRules() as soon as we are able + // to load some rules from HTTPS Everywhere. + this._loadHttpObserver(); + try { + await this.httpsEverywhereControl.installTorOnionUpdateChannel(); + this._updateChannelInstalled = true; + await this.httpsEverywhereControl.getTorOnionRules(); + this._canLoadRules = true; + } catch (e) { + // Loading rules did not work, probably because "get_simple_rules_ending_with" is not yet + // working in https-everywhere. Use an http observer as a fallback for learning the rules. + log.debug(`Could not load rules: ${e.message}`); + } + + // Setup checker for https-everywhere ruleset updates + if (this._updateChannelInstalled) { + this._periodicRulesetCheck(); + } + } + + /** + * Loads the .tor.onion mappings from https-everywhere. + */ + async _loadRules() { + const rules = await this.httpsEverywhereControl.getTorOnionRules(); + // Remove http observer if we are able to load some rules directly. + if (rules.length) { + this._removeObserver(); + this._removeObserver = () => {}; + } + this._clear(); + log.debug(`Loading ${rules.length} rules`, rules); + for (const rule of rules) { + // Here we are trusting that the securedrop ruleset follows some conventions so that we can + // assume there is a host mapping from `rule.host` to the hostname of the URL in `rule.to`. + try { + const url = new URL(rule.to); + const shortHost = rule.host; + const longHost = url.hostname; + this._addMapping(shortHost, longHost); + } catch (e) { + log.error("Could not process rule:", rule); + } + } + } + + /** + * Loads a http observer to listen for local redirects for populating + * the .tor.onion -> .onion mappings. Should only be used if we cannot ask https-everywhere + * directly for the mappings. + */ + _loadHttpObserver() { + this._removeObserver = observe("http-on-before-connect", channel => { + if ( + channel.isMainDocumentChannel && + channel.originalURI.host.endsWith(".tor.onion") + ) { + this._addMapping(channel.originalURI.host, channel.URI.host); + } + }); + } + + uninit() { + this._clear(); + this._removeObserver(); + this._removeObserver = () => {}; + if (this.httpsEverywhereControl) { + this.httpsEverywhereControl.unload(); + delete this.httpsEverywhereControl; + } + clearTimeout(this._rulesetTimeout); + this._rulesetTimeout = null; + this._rulesetTimestamp = null; + } + + _clear() { + this._onionMap.clear(); + } + + _addMapping(shortOnionHost, longOnionHost) { + this._onionMap.set(longOnionHost, shortOnionHost); + } + + getShortURI(onionURI) { + if ( + (onionURI.schemeIs("http") || onionURI.schemeIs("https")) && + this._onionMap.has(onionURI.host) + ) { + return onionURI + .mutate() + .setHost(this._onionMap.get(onionURI.host)) + .finalize(); + } + return null; + } +} + +let OnionAliasStore = new _OnionAliasStore(); diff --git a/browser/components/onionservices/moz.build b/browser/components/onionservices/moz.build index 7e103239c8d6..e4b6d73f8f40 100644 --- a/browser/components/onionservices/moz.build +++ b/browser/components/onionservices/moz.build @@ -1 +1,7 @@ JAR_MANIFESTS += ['jar.mn'] + +EXTRA_JS_MODULES += [ + 'ExtensionMessaging.jsm', + 'HttpsEverywhereControl.jsm', + 'OnionAliasStore.jsm', +] diff --git a/browser/components/urlbar/UrlbarInput.jsm b/browser/components/urlbar/UrlbarInput.jsm index db1c497bcace..13b1279105f2 100644 --- a/browser/components/urlbar/UrlbarInput.jsm +++ b/browser/components/urlbar/UrlbarInput.jsm @@ -277,7 +277,10 @@ class UrlbarInput { // bar if the user has deleted the URL and we'd just put the same URL // back. See bug 304198. if (value === null) { - uri = uri || this.window.gBrowser.currentURI; + uri = + uri || + this.window.gBrowser.selectedBrowser.currentOnionAliasURI || + this.window.gBrowser.currentURI; // Strip off usernames and passwords for the location bar try { uri = Services.io.createExposableURI(uri); @@ -1541,7 +1544,13 @@ class UrlbarInput { } let uri; - if (this.getAttribute("pageproxystate") == "valid") { + // When we rewrite .onion to an alias, gBrowser.currentURI will be different than + // the URI displayed in the urlbar. We need to use the urlbar value to copy the + // alias instead of the actual .onion URI that is loaded. + if ( + this.getAttribute("pageproxystate") == "valid" && + !this.window.gBrowser.selectedBrowser.currentOnionAliasURI + ) { uri = this.window.gBrowser.currentURI; } else { // The value could be: diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp index 2bb1b7253c32..ee7a54e26de5 100644 --- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -5651,6 +5651,10 @@ void nsDocShell::OnRedirectStateChange(nsIChannel* aOldChannel, return; } + if (!mOnionUrlbarRewritesAllowed && IsTorOnionRedirect(oldURI, newURI)) { + mOnionUrlbarRewritesAllowed = true; + } + // DocumentChannel adds redirect chain to global history in the parent // process. The redirect chain can't be queried from the content process, so // there's no need to update global history here. @@ -8680,6 +8684,20 @@ nsresult nsDocShell::HandleSameDocumentNavigation( return NS_OK; } +/* static */ +bool nsDocShell::IsTorOnionRedirect(nsIURI* aOldURI, nsIURI* aNewURI) { + nsAutoCString oldHost; + nsAutoCString newHost; + if (aOldURI && aNewURI && NS_SUCCEEDED(aOldURI->GetHost(oldHost)) && + StringEndsWith(oldHost, NS_LITERAL_CSTRING(".tor.onion")) && + NS_SUCCEEDED(aNewURI->GetHost(newHost)) && + StringEndsWith(newHost, NS_LITERAL_CSTRING(".onion")) && + !StringEndsWith(newHost, NS_LITERAL_CSTRING(".tor.onion"))) { + return true; + } + return false; + } + nsresult nsDocShell::InternalLoad(nsDocShellLoadState* aLoadState, nsIDocShell** aDocShell, nsIRequest** aRequest) { @@ -8831,6 +8849,30 @@ nsresult nsDocShell::InternalLoad(nsDocShellLoadState* aLoadState, mAllowKeywordFixup = aLoadState->HasLoadFlags(INTERNAL_LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP); + + if (mOnionUrlbarRewritesAllowed) { + mOnionUrlbarRewritesAllowed = false; + nsCOMPtr<nsIURI> referrer; + nsIReferrerInfo* referrerInfo = aLoadState->GetReferrerInfo(); + if (referrerInfo) { + referrerInfo->GetOriginalReferrer(getter_AddRefs(referrer)); + bool isPrivateWin = false; + Document* doc = GetDocument(); + if (doc) { + isPrivateWin = + doc->NodePrincipal()->OriginAttributesRef().mPrivateBrowsingId > 0; + nsCOMPtr<nsIScriptSecurityManager> secMan = + do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID); + mOnionUrlbarRewritesAllowed = + secMan && NS_SUCCEEDED(secMan->CheckSameOriginURI( + aLoadState->URI(), referrer, false, isPrivateWin)); + } + } + } + mOnionUrlbarRewritesAllowed = + mOnionUrlbarRewritesAllowed || + aLoadState->HasLoadFlags(INTERNAL_LOAD_FLAGS_ALLOW_ONION_URLBAR_REWRITES); + mURIResultedInDocument = false; // reset the clock... // See if this is actually a load between two history entries for the same @@ -10976,6 +11018,7 @@ nsresult nsDocShell::AddToSessionHistory( mDynamicallyCreated, originalURI, resultPrincipalURI, loadReplace, referrerInfo, srcdoc, srcdocEntry, baseURI, saveLayoutState, expired); + entry->SetOnionUrlbarRewritesAllowed(mOnionUrlbarRewritesAllowed); if (root == static_cast<nsIDocShellTreeItem*>(this) && GetSessionHistory()) { bool shouldPersist = ShouldAddToSessionHistory(aURI, aChannel); @@ -12773,3 +12816,12 @@ bool nsDocShell::GetIsAttemptingToNavigate() { return false; } + +NS_IMETHODIMP +nsDocShell::GetOnionUrlbarRewritesAllowed(bool* aOnionUrlbarRewritesAllowed) { + NS_ENSURE_ARG(aOnionUrlbarRewritesAllowed); + *aOnionUrlbarRewritesAllowed = + StaticPrefs::browser_urlbar_onionRewrites_enabled() && + mOnionUrlbarRewritesAllowed; + return NS_OK; +} diff --git a/docshell/base/nsDocShell.h b/docshell/base/nsDocShell.h index dcffed8e5537..d403a06f4c9f 100644 --- a/docshell/base/nsDocShell.h +++ b/docshell/base/nsDocShell.h @@ -152,6 +152,9 @@ class nsDocShell final : public nsDocLoader, // Whether the load should go through LoadURIDelegate. INTERNAL_LOAD_FLAGS_BYPASS_LOAD_URI_DELEGATE = 0x2000, + + // Whether rewriting the urlbar to a short .onion alias is allowed. + INTERNAL_LOAD_FLAGS_ALLOW_ONION_URLBAR_REWRITES = 0x4000, }; // Event type dispatched by RestorePresentation @@ -582,6 +585,8 @@ class nsDocShell final : public nsDocLoader, virtual void DestroyChildren() override; + static bool IsTorOnionRedirect(nsIURI* aOldURI, nsIURI* aNewURI); + // Overridden from nsDocLoader, this provides more information than the // normal OnStateChange with flags STATE_REDIRECTING virtual void OnRedirectStateChange(nsIChannel* aOldChannel, @@ -1264,6 +1269,7 @@ class nsDocShell final : public nsDocLoader, bool mCSSErrorReportingEnabled : 1; bool mAllowAuth : 1; bool mAllowKeywordFixup : 1; + bool mOnionUrlbarRewritesAllowed : 1; bool mIsOffScreenBrowser : 1; bool mDisableMetaRefreshWhenInactive : 1; bool mIsAppTab : 1; diff --git a/docshell/base/nsDocShellLoadState.cpp b/docshell/base/nsDocShellLoadState.cpp index d91bb6034f9d..fe1216bd2509 100644 --- a/docshell/base/nsDocShellLoadState.cpp +++ b/docshell/base/nsDocShellLoadState.cpp @@ -730,6 +730,10 @@ void nsDocShellLoadState::CalculateLoadURIFlags() { mLoadFlags |= nsDocShell::INTERNAL_LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP; } + if (oldLoadFlags & nsIWebNavigation::LOAD_FLAGS_ALLOW_ONION_URLBAR_REWRITES) { + mLoadFlags |= nsDocShell::INTERNAL_LOAD_FLAGS_ALLOW_ONION_URLBAR_REWRITES; + } + if (oldLoadFlags & nsIWebNavigation::LOAD_FLAGS_FIRST_LOAD) { mLoadFlags |= nsDocShell::INTERNAL_LOAD_FLAGS_FIRST_LOAD; } diff --git a/docshell/base/nsIDocShell.idl b/docshell/base/nsIDocShell.idl index 01dc4734eb19..305acfd3a98f 100644 --- a/docshell/base/nsIDocShell.idl +++ b/docshell/base/nsIDocShell.idl @@ -1019,4 +1019,9 @@ interface nsIDocShell : nsIDocShellTreeItem * until session history state is moved into the parent process. */ void persistLayoutHistoryState(); + + /** + * Whether rewriting the urlbar to a short .onion alias is allowed. + */ + [infallible] readonly attribute boolean onionUrlbarRewritesAllowed; }; diff --git a/docshell/base/nsIWebNavigation.idl b/docshell/base/nsIWebNavigation.idl index bbc3eb7583a2..a4cceb52b2a0 100644 --- a/docshell/base/nsIWebNavigation.idl +++ b/docshell/base/nsIWebNavigation.idl @@ -237,6 +237,11 @@ interface nsIWebNavigation : nsISupports */ const unsigned long LOAD_FLAGS_BYPASS_LOAD_URI_DELEGATE = 0x4000000; + /** + * Allow rewriting the urlbar to a short .onion alias. + */ + const unsigned long LOAD_FLAGS_ALLOW_ONION_URLBAR_REWRITES = 0x8000000; + /** * Loads a given URI. This will give priority to loading the requested URI * in the object implementing this interface. If it can't be loaded here diff --git a/docshell/shistory/SessionHistoryEntry.cpp b/docshell/shistory/SessionHistoryEntry.cpp index 07a28d1291c9..566b7d68fae5 100644 --- a/docshell/shistory/SessionHistoryEntry.cpp +++ b/docshell/shistory/SessionHistoryEntry.cpp @@ -490,6 +490,20 @@ SessionHistoryEntry::SetPersist(bool aPersist) { return NS_OK; } +NS_IMETHODIMP +SessionHistoryEntry::GetOnionUrlbarRewritesAllowed( + bool* aOnionUrlbarRewritesAllowed) { + *aOnionUrlbarRewritesAllowed = mInfo->mOnionUrlbarRewritesAllowed; + return NS_OK; +} + +NS_IMETHODIMP +SessionHistoryEntry::SetOnionUrlbarRewritesAllowed( + bool aOnionUrlbarRewritesAllowed) { + mInfo->mOnionUrlbarRewritesAllowed = aOnionUrlbarRewritesAllowed; + return NS_OK; +} + NS_IMETHODIMP SessionHistoryEntry::GetScrollPosition(int32_t* aX, int32_t* aY) { *aX = mInfo->mScrollPositionX; diff --git a/docshell/shistory/SessionHistoryEntry.h b/docshell/shistory/SessionHistoryEntry.h index fa6108952688..c51682a65b0a 100644 --- a/docshell/shistory/SessionHistoryEntry.h +++ b/docshell/shistory/SessionHistoryEntry.h @@ -58,6 +58,7 @@ class SessionHistoryInfo { bool mIsSrcdocEntry = false; bool mScrollRestorationIsManual = false; bool mPersist = false; + bool mOnionUrlbarRewritesAllowed = false; }; // XXX Not sure that the id shouldn't just live in SessionHistoryInfo. diff --git a/docshell/shistory/nsISHEntry.idl b/docshell/shistory/nsISHEntry.idl index 8bbf2a68687c..bf410bfcc05a 100644 --- a/docshell/shistory/nsISHEntry.idl +++ b/docshell/shistory/nsISHEntry.idl @@ -242,6 +242,11 @@ interface nsISHEntry : nsISupports */ [infallible] attribute boolean persist; + /** + * Whether rewriting the urlbar to a short .onion alias is allowed. + */ + [infallible] attribute boolean onionUrlbarRewritesAllowed; + /** * Set/Get the visual viewport scroll position if session history is * changed through anchor navigation or pushState. diff --git a/docshell/shistory/nsSHEntry.cpp b/docshell/shistory/nsSHEntry.cpp index 229b15eff25d..12d38f4c55fe 100644 --- a/docshell/shistory/nsSHEntry.cpp +++ b/docshell/shistory/nsSHEntry.cpp @@ -44,7 +44,8 @@ nsSHEntry::nsSHEntry(nsISHistory* aSHistory) mIsSrcdocEntry(false), mScrollRestorationIsManual(false), mLoadedInThisProcess(false), - mPersist(true) {} + mPersist(true), + mOnionUrlbarRewritesAllowed(false) {} nsSHEntry::nsSHEntry(const nsSHEntry& aOther) : mShared(aOther.mShared), @@ -70,7 +71,8 @@ nsSHEntry::nsSHEntry(const nsSHEntry& aOther) mIsSrcdocEntry(aOther.mIsSrcdocEntry), mScrollRestorationIsManual(false), mLoadedInThisProcess(aOther.mLoadedInThisProcess), - mPersist(aOther.mPersist) {} + mPersist(aOther.mPersist), + mOnionUrlbarRewritesAllowed(aOther.mOnionUrlbarRewritesAllowed) {} nsSHEntry::~nsSHEntry() { // Null out the mParent pointers on all our kids. @@ -824,6 +826,18 @@ nsSHEntry::SetPersist(bool aPersist) { return NS_OK; } +NS_IMETHODIMP +nsSHEntry::GetOnionUrlbarRewritesAllowed(bool* aOnionUrlbarRewritesAllowed) { + *aOnionUrlbarRewritesAllowed = mOnionUrlbarRewritesAllowed; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::SetOnionUrlbarRewritesAllowed(bool aOnionUrlbarRewritesAllowed) { + mOnionUrlbarRewritesAllowed = aOnionUrlbarRewritesAllowed; + return NS_OK; +} + NS_IMETHODIMP nsSHEntry::CreateLoadInfo(nsDocShellLoadState** aLoadState) { nsCOMPtr<nsIURI> uri = GetURI(); @@ -873,6 +887,10 @@ nsSHEntry::CreateLoadInfo(nsDocShellLoadState** aLoadState) { } else { srcdoc = VoidString(); } + if (GetOnionUrlbarRewritesAllowed()) { + flags |= nsDocShell::InternalLoad:: + INTERNAL_LOAD_FLAGS_ALLOW_ONION_URLBAR_REWRITES; + } loadState->SetSrcdocData(srcdoc); loadState->SetBaseURI(baseURI); loadState->SetLoadFlags(flags); diff --git a/docshell/shistory/nsSHEntry.h b/docshell/shistory/nsSHEntry.h index 351f6f200a33..6bc9de521437 100644 --- a/docshell/shistory/nsSHEntry.h +++ b/docshell/shistory/nsSHEntry.h @@ -63,6 +63,7 @@ class nsSHEntry : public nsISHEntry { bool mScrollRestorationIsManual; bool mLoadedInThisProcess; bool mPersist; + bool mOnionUrlbarRewritesAllowed; }; #endif /* nsSHEntry_h */ diff --git a/dom/interfaces/base/nsIBrowser.idl b/dom/interfaces/base/nsIBrowser.idl index 300b09e13824..a9ff00e964b7 100644 --- a/dom/interfaces/base/nsIBrowser.idl +++ b/dom/interfaces/base/nsIBrowser.idl @@ -158,7 +158,8 @@ interface nsIBrowser : nsISupports in uint64_t aInnerWindowID, in boolean aHasRequestContextID, in uint64_t aRequestContextID, - in AString aContentType); + in AString aContentType, + in boolean aOnionUrlbarRewritesAllowed); /** * Called by Gecko when it wants to change the process which is currently diff --git a/dom/ipc/BrowserChild.cpp b/dom/ipc/BrowserChild.cpp index 2b17323d8a93..f72aa8faa11d 100644 --- a/dom/ipc/BrowserChild.cpp +++ b/dom/ipc/BrowserChild.cpp @@ -3645,6 +3645,8 @@ NS_IMETHODIMP BrowserChild::OnLocationChange(nsIWebProgress* aWebProgress, docShell->GetMayEnableCharacterEncodingMenu(); locationChangeData->charsetAutodetected() = docShell->GetCharsetAutodetected(); + locationChangeData->onionUrlbarRewritesAllowed() = + docShell->GetOnionUrlbarRewritesAllowed(); locationChangeData->contentPrincipal() = document->NodePrincipal(); locationChangeData->contentStoragePrincipal() = diff --git a/dom/ipc/BrowserParent.cpp b/dom/ipc/BrowserParent.cpp index 7749792cafb4..086ecd95e9e7 100644 --- a/dom/ipc/BrowserParent.cpp +++ b/dom/ipc/BrowserParent.cpp @@ -2698,7 +2698,8 @@ mozilla::ipc::IPCResult BrowserParent::RecvOnLocationChange( aWebProgressData->innerDOMWindowID(), aLocationChangeData->requestContextID().isSome(), aLocationChangeData->requestContextID().valueOr(0), - aLocationChangeData->contentType()); + aLocationChangeData->contentType(), + aLocationChangeData->onionUrlbarRewritesAllowed()); } Unused << managerAsListener->OnLocationChange(webProgress, request, aLocation, diff --git a/dom/ipc/PBrowser.ipdl b/dom/ipc/PBrowser.ipdl index f36e2c6db353..15756e7fef4f 100644 --- a/dom/ipc/PBrowser.ipdl +++ b/dom/ipc/PBrowser.ipdl @@ -142,6 +142,7 @@ struct WebProgressLocationChangeData bool isSyntheticDocument; bool mayEnableCharacterEncodingMenu; bool charsetAutodetected; + bool onionUrlbarRewritesAllowed; nsString contentType; nsString title; nsString charset; diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml index 63d53b3748d1..88d6532f15eb 100644 --- a/modules/libpref/init/StaticPrefList.yaml +++ b/modules/libpref/init/StaticPrefList.yaml @@ -1077,6 +1077,12 @@ value: true mirror: always + # Whether rewriting the urlbar to a short .onion alias is allowed. +- name: browser.urlbar.onionRewrites.enabled + type: RelaxedAtomicBool + value: true + mirror: always + - name: browser.viewport.desktopWidth type: RelaxedAtomicInt32 value: 980 diff --git a/netwerk/dns/effective_tld_names.dat b/netwerk/dns/effective_tld_names.dat index 12b1f6e5d0e3..d8b242398a36 100644 --- a/netwerk/dns/effective_tld_names.dat +++ b/netwerk/dns/effective_tld_names.dat @@ -5517,6 +5517,8 @@ pro.om // onion : https://tools.ietf.org/html/rfc7686 onion +tor.onion +securedrop.tor.onion // org : https://en.wikipedia.org/wiki/.org org diff --git a/netwerk/ipc/DocumentLoadListener.cpp b/netwerk/ipc/DocumentLoadListener.cpp index 901ce11b57ca..988ec94a82d4 100644 --- a/netwerk/ipc/DocumentLoadListener.cpp +++ b/netwerk/ipc/DocumentLoadListener.cpp @@ -1856,6 +1856,16 @@ DocumentLoadListener::AsyncOnChannelRedirect( mLoadStateLoadType, nsIWebNavigation::LOAD_FLAGS_ALLOW_MIXED_CONTENT)); } + // Like the code above for allowing mixed content, we need to check this here + // in case the redirect is not handled in the docshell. + nsCOMPtr<nsIURI> oldURI, newURI; + aOldChannel->GetURI(getter_AddRefs(oldURI)); + aNewChannel->GetURI(getter_AddRefs(newURI)); + if (nsDocShell::IsTorOnionRedirect(oldURI, newURI)) { + mLoadStateLoadFlags |= + nsDocShell::INTERNAL_LOAD_FLAGS_ALLOW_ONION_URLBAR_REWRITES; + } + // We need the original URI of the current channel to use to open the real // channel in the content process. Unfortunately we overwrite the original // uri of the new channel with the original pre-redirect URI, so grab diff --git a/toolkit/content/widgets/browser-custom-element.js b/toolkit/content/widgets/browser-custom-element.js index 62a1ab1d6796..67fa1e5645c0 100644 --- a/toolkit/content/widgets/browser-custom-element.js +++ b/toolkit/content/widgets/browser-custom-element.js @@ -261,6 +261,8 @@ this._mayEnableCharacterEncodingMenu = null; + this._onionUrlbarRewritesAllowed = false; + this._charsetAutodetected = false; this._contentPrincipal = null; @@ -686,6 +688,12 @@ } } + get onionUrlbarRewritesAllowed() { + return this.isRemoteBrowser + ? this._onionUrlbarRewritesAllowed + : this.docShell.onionUrlbarRewritesAllowed; + } + get charsetAutodetected() { return this.isRemoteBrowser ? this._charsetAutodetected @@ -1292,7 +1300,8 @@ aInnerWindowID, aHaveRequestContextID, aRequestContextID, - aContentType + aContentType, + aOnionUrlbarRewritesAllowed ) { if (this.isRemoteBrowser && this.messageManager) { if (aCharset != null) { @@ -1316,6 +1325,7 @@ this._contentRequestContextID = aHaveRequestContextID ? aRequestContextID : null; + this._onionUrlbarRewritesAllowed = aOnionUrlbarRewritesAllowed; } } @@ -1708,6 +1718,7 @@ "_contentStoragePrincipal", "_isSyntheticDocument", "_innerWindowID", + "_onionUrlbarRewritesAllowed", ] ); } diff --git a/toolkit/modules/sessionstore/SessionHistory.jsm b/toolkit/modules/sessionstore/SessionHistory.jsm index 6c16ac331659..cc47f437824f 100644 --- a/toolkit/modules/sessionstore/SessionHistory.jsm +++ b/toolkit/modules/sessionstore/SessionHistory.jsm @@ -318,6 +318,7 @@ var SessionHistoryInternal = { } entry.persist = shEntry.persist; + entry.onionUrlbarRewritesAllowed = shEntry.onionUrlbarRewritesAllowed; return entry; }, @@ -601,6 +602,10 @@ var SessionHistoryInternal = { } } + if (entry.onionUrlbarRewritesAllowed) { + shEntry.onionUrlbarRewritesAllowed = entry.onionUrlbarRewritesAllowed; + } + return shEntry; },
1 0
0 0
[tor-browser/tor-browser-78.5.0esr-10.5-1] Bug 33342: Avoid disconnect search addon error after removal.
by sysrqb@torproject.org 14 Nov '20

14 Nov '20
commit 73e62345acba7805a58aa1e51d847bc1b7bf790f Author: Alex Catarineu <acat(a)torproject.org> Date: Fri Mar 13 18:19:30 2020 +0100 Bug 33342: Avoid disconnect search addon error after removal. We removed the addon in #32767, but it was still being loaded from addonStartup.json.lz4 and throwing an error on startup because its resource: location is not available anymore. --- toolkit/mozapps/extensions/internal/XPIProvider.jsm | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/toolkit/mozapps/extensions/internal/XPIProvider.jsm b/toolkit/mozapps/extensions/internal/XPIProvider.jsm index 5e467fb4f14c..794c206fb453 100644 --- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm @@ -970,6 +970,12 @@ var BuiltInLocation = new (class _BuiltInLocation extends XPIStateLocation { get enumerable() { return false; } + + restore(saved) { + super.restore(saved); + // Bug 33342: avoid restoring disconnect addon from addonStartup.json.lz4. + this.removeAddon("disconnect(a)search.mozilla.org"); + } })(); /**
1 0
0 0
[tor-browser/tor-browser-78.5.0esr-10.5-1] Bug 25741 - TBA: Disable GeckoNetworkManager
by sysrqb@torproject.org 14 Nov '20

14 Nov '20
commit 78fbe14925e2050e021f02ae8d0dc270c575162c Author: Matthew Finkel <Matthew.Finkel(a)gmail.com> Date: Thu Apr 26 22:22:51 2018 +0000 Bug 25741 - TBA: Disable GeckoNetworkManager The browser should not need information related to the network interface or network state, tor should take care of that. --- .../src/main/java/org/mozilla/geckoview/GeckoRuntime.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntime.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntime.java index ed86dcc5c299..f5587dd7e59c 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntime.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntime.java @@ -121,7 +121,9 @@ public final class GeckoRuntime implements Parcelable { mPaused = false; // Monitor network status and send change notifications to Gecko // while active. - GeckoNetworkManager.getInstance().start(GeckoAppShell.getApplicationContext()); + if (BuildConfig.TOR_BROWSER_VERSION == "") { + GeckoNetworkManager.getInstance().start(GeckoAppShell.getApplicationContext()); + } } @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) @@ -129,7 +131,9 @@ public final class GeckoRuntime implements Parcelable { Log.d(LOGTAG, "Lifecycle: onPause"); mPaused = true; // Stop monitoring network status while inactive. - GeckoNetworkManager.getInstance().stop(); + if (BuildConfig.TOR_BROWSER_VERSION == "") { + GeckoNetworkManager.getInstance().stop(); + } GeckoThread.onPause(); } }
1 0
0 0
[tor-browser/tor-browser-78.5.0esr-10.5-1] Bug 28125 - Prevent non-Necko network connections
by sysrqb@torproject.org 14 Nov '20

14 Nov '20
commit def3ae403805e832a31dd5bc7a6b77aaa8eae8f4 Author: Matthew Finkel <Matthew.Finkel(a)gmail.com> Date: Thu Oct 25 19:17:09 2018 +0000 Bug 28125 - Prevent non-Necko network connections --- .../gecko/media/GeckoMediaDrmBridgeV21.java | 49 +--------------------- .../exoplayer2/upstream/DefaultHttpDataSource.java | 47 +-------------------- 2 files changed, 3 insertions(+), 93 deletions(-) diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/GeckoMediaDrmBridgeV21.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/GeckoMediaDrmBridgeV21.java index 3ba59bfd6776..eb57b1013642 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/GeckoMediaDrmBridgeV21.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/GeckoMediaDrmBridgeV21.java @@ -488,54 +488,7 @@ public class GeckoMediaDrmBridgeV21 implements GeckoMediaDrm { @Override protected Void doInBackground(final Void... params) { - HttpURLConnection urlConnection = null; - BufferedReader in = null; - try { - URI finalURI = new URI(mURL + "&signedRequest=" + URLEncoder.encode(new String(mDrmRequest), "UTF-8")); - urlConnection = (HttpURLConnection) ProxySelector.openConnectionWithProxy(finalURI); - urlConnection.setRequestMethod("POST"); - if (DEBUG) Log.d(LOGTAG, "Provisioning, posting url =" + finalURI.toString()); - - // Add data - urlConnection.setRequestProperty("Accept", "*/*"); - urlConnection.setRequestProperty("User-Agent", getCDMUserAgent()); - urlConnection.setRequestProperty("Content-Type", "application/json"); - - // Execute HTTP Post Request - urlConnection.connect(); - - int responseCode = urlConnection.getResponseCode(); - if (responseCode == HttpURLConnection.HTTP_OK) { - in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream(), StringUtils.UTF_8)); - String inputLine; - StringBuffer response = new StringBuffer(); - - while ((inputLine = in.readLine()) != null) { - response.append(inputLine); - } - in.close(); - mResponseBody = String.valueOf(response).getBytes(StringUtils.UTF_8); - if (DEBUG) Log.d(LOGTAG, "Provisioning, response received."); - if (mResponseBody != null) Log.d(LOGTAG, "response length=" + mResponseBody.length); - } else { - Log.d(LOGTAG, "Provisioning, server returned HTTP error code :" + responseCode); - } - } catch (IOException e) { - Log.e(LOGTAG, "Got exception during posting provisioning request ...", e); - } catch (URISyntaxException e) { - Log.e(LOGTAG, "Got exception during creating uri ...", e); - } finally { - if (urlConnection != null) { - urlConnection.disconnect(); - } - try { - if (in != null) { - in.close(); - } - } catch (IOException e) { - Log.e(LOGTAG, "Exception during closing in ...", e); - } - } + Log.i(LOGTAG, "This is Tor Browser. Skipping."); return null; } diff --git a/mobile/android/geckoview/src/thirdparty/java/org/mozilla/thirdparty/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java b/mobile/android/geckoview/src/thirdparty/java/org/mozilla/thirdparty/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java index fdb44cc2ea1f..7a2044721d2d 100644 --- a/mobile/android/geckoview/src/thirdparty/java/org/mozilla/thirdparty/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java +++ b/mobile/android/geckoview/src/thirdparty/java/org/mozilla/thirdparty/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java @@ -395,51 +395,8 @@ public class DefaultHttpDataSource implements HttpDataSource { */ private HttpURLConnection makeConnection(URL url, byte[] postBody, long position, long length, boolean allowGzip, boolean followRedirects) throws IOException, URISyntaxException { - /** - * Tor Project modified the way the connection object was created. For the sake of - * simplicity, instead of duplicating the whole file we changed the connection object - * to use the ProxySelector. - */ - HttpURLConnection connection = (HttpURLConnection) ProxySelector.openConnectionWithProxy(url.toURI()); - - connection.setConnectTimeout(connectTimeoutMillis); - connection.setReadTimeout(readTimeoutMillis); - if (defaultRequestProperties != null) { - for (Map.Entry<String, String> property : defaultRequestProperties.getSnapshot().entrySet()) { - connection.setRequestProperty(property.getKey(), property.getValue()); - } - } - for (Map.Entry<String, String> property : requestProperties.getSnapshot().entrySet()) { - connection.setRequestProperty(property.getKey(), property.getValue()); - } - if (!(position == 0 && length == C.LENGTH_UNSET)) { - String rangeRequest = "bytes=" + position + "-"; - if (length != C.LENGTH_UNSET) { - rangeRequest += (position + length - 1); - } - connection.setRequestProperty("Range", rangeRequest); - } - connection.setRequestProperty("User-Agent", userAgent); - if (!allowGzip) { - connection.setRequestProperty("Accept-Encoding", "identity"); - } - connection.setInstanceFollowRedirects(followRedirects); - connection.setDoOutput(postBody != null); - if (postBody != null) { - connection.setRequestMethod("POST"); - if (postBody.length == 0) { - connection.connect(); - } else { - connection.setFixedLengthStreamingMode(postBody.length); - connection.connect(); - OutputStream os = connection.getOutputStream(); - os.write(postBody); - os.close(); - } - } else { - connection.connect(); - } - return connection; + Log.i(TAG, "This is Tor Browser. Skipping."); + throw new IOException(); } /**
1 0
0 0
[tor-browser/tor-browser-78.5.0esr-10.5-1] Bug 40073: Disable remote Public Suffix List fetching
by sysrqb@torproject.org 14 Nov '20

14 Nov '20
commit dd6be9dfa7d7d77a64e200d05b6132fe1d61f05b Author: Alex Catarineu <acat(a)torproject.org> Date: Thu Aug 13 11:05:03 2020 +0200 Bug 40073: Disable remote Public Suffix List fetching In https://bugzilla.mozilla.org/show_bug.cgi?id=1563246 Firefox implemented fetching the Public Suffix List via RemoteSettings and replacing the default one at runtime, which we do not want. --- browser/components/BrowserGlue.jsm | 5 ----- 1 file changed, 5 deletions(-) diff --git a/browser/components/BrowserGlue.jsm b/browser/components/BrowserGlue.jsm index e08e461a27ff..ec38d0ca8b33 100644 --- a/browser/components/BrowserGlue.jsm +++ b/browser/components/BrowserGlue.jsm @@ -695,7 +695,6 @@ XPCOMUtils.defineLazyModuleGetters(this, { PluralForm: "resource://gre/modules/PluralForm.jsm", PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm", ProcessHangMonitor: "resource:///modules/ProcessHangMonitor.jsm", - PublicSuffixList: "resource://gre/modules/netwerk-dns/PublicSuffixList.jsm", RemoteSettings: "resource://services-settings/remote-settings.js", RemoteSecuritySettings: "resource://gre/modules/psm/RemoteSecuritySettings.jsm", @@ -2575,10 +2574,6 @@ BrowserGlue.prototype = { this._addBreachesSyncHandler(); }, - () => { - PublicSuffixList.init(); - }, - () => { RemoteSecuritySettings.init(); },
1 0
0 0
[tor-browser/tor-browser-78.5.0esr-10.5-1] Bug 1650281 - P2: Make sure `gCombinedSizes` won't be underflowed r=gerald
by sysrqb@torproject.org 14 Nov '20

14 Nov '20
commit 29264dc53cb59a7cc7e1a8991ef937533bd50866 Author: Chun-Min Chang <chun.m.chang(a)gmail.com> Date: Tue Jul 21 23:39:14 2020 +0000 Bug 1650281 - P2: Make sure `gCombinedSizes` won't be underflowed r=gerald In any case, `gCombinedSizes` should be larger than or equal to the buffer within `MemoryClockCache`. Differential Revision: https://phabricator.services.mozilla.com/D84274 --- dom/media/MemoryBlockCache.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/dom/media/MemoryBlockCache.cpp b/dom/media/MemoryBlockCache.cpp index bf073e6769d0..2848a3f3812c 100644 --- a/dom/media/MemoryBlockCache.cpp +++ b/dom/media/MemoryBlockCache.cpp @@ -52,6 +52,7 @@ MemoryBlockCache::MemoryBlockCache(int64_t aContentLength) } MemoryBlockCache::~MemoryBlockCache() { + MOZ_ASSERT(gCombinedSizes >= mBuffer.Length()); size_t sizes = static_cast<size_t>(gCombinedSizes -= mBuffer.Length()); LOG("~MemoryBlockCache() - destroying buffer of size %zu; combined sizes now " "%zu",
1 0
0 0
[tor-browser/tor-browser-78.5.0esr-10.5-1] Bug 40091: Load HTTPS Everywhere as a builtin addon in desktop
by sysrqb@torproject.org 14 Nov '20

14 Nov '20
commit d7bc5271f7d1dd6bdce85436c8d4164ba4fbcc14 Author: Alex Catarineu <acat(a)torproject.org> Date: Fri Sep 4 12:34:35 2020 +0200 Bug 40091: Load HTTPS Everywhere as a builtin addon in desktop This loads HTTPS Everywhere as a builtin addon from a hardcoded resource:// URI in desktop. It also ensures that the non-builtin HTTPS Everywhere addon is always uninstalled on browser startup. The reason of making this desktop-only is that there are some issues when installing a builtin extension from geckoview side, making the extension not available on first startup. So, at least for now we handle the Fenix case separately. See #40118 for a followup for investigating these. --- browser/components/BrowserGlue.jsm | 37 ++++++++++++++++++++++ toolkit/components/extensions/Extension.jsm | 14 ++++++-- .../mozapps/extensions/internal/XPIProvider.jsm | 13 ++++++++ 3 files changed, 61 insertions(+), 3 deletions(-) diff --git a/browser/components/BrowserGlue.jsm b/browser/components/BrowserGlue.jsm index ec38d0ca8b33..057a2121533c 100644 --- a/browser/components/BrowserGlue.jsm +++ b/browser/components/BrowserGlue.jsm @@ -56,6 +56,13 @@ XPCOMUtils.defineLazyServiceGetter( "nsIPushService" ); +XPCOMUtils.defineLazyServiceGetters(this, { + resProto: [ + "@mozilla.org/network/protocol;1?name=resource", + "nsISubstitutingProtocolHandler", + ], +}); + const PREF_PDFJS_ISDEFAULT_CACHE_STATE = "pdfjs.enabledCache.state"; /** @@ -675,6 +682,7 @@ XPCOMUtils.defineLazyModuleGetters(this, { "resource://gre/modules/ContextualIdentityService.jsm", Corroborate: "resource://gre/modules/Corroborate.jsm", Discovery: "resource:///modules/Discovery.jsm", + ExtensionData: "resource://gre/modules/Extension.jsm", ExtensionsUI: "resource:///modules/ExtensionsUI.jsm", FirefoxMonitor: "resource:///modules/FirefoxMonitor.jsm", FxAccounts: "resource://gre/modules/FxAccounts.jsm", @@ -1330,6 +1338,35 @@ BrowserGlue.prototype = { "resource:///modules/themes/dark/" ); + // Install https-everywhere builtin addon if needed. + (async () => { + const HTTPS_EVERYWHERE_ID = "https-everywhere-eff(a)eff.org"; + const HTTPS_EVERYWHERE_BUILTIN_URL = + "resource://torbutton/content/extensions/https-everywhere/"; + // This does something similar as GeckoViewWebExtension.jsm: it tries + // to load the manifest to retrieve the version of the builtin and + // compares it to the currently installed one to see whether we need + // to install or not. Here we delegate that to + // AddonManager.maybeInstallBuiltinAddon. + try { + const resolvedURI = Services.io.newURI( + resProto.resolveURI(Services.io.newURI(HTTPS_EVERYWHERE_BUILTIN_URL)) + ); + const extensionData = new ExtensionData(resolvedURI); + const manifest = await extensionData.loadManifest(); + + await AddonManager.maybeInstallBuiltinAddon( + HTTPS_EVERYWHERE_ID, + manifest.version, + HTTPS_EVERYWHERE_BUILTIN_URL + ); + } catch (e) { + const log = Log.repository.getLogger("HttpsEverywhereBuiltinLoader"); + log.addAppender(new Log.ConsoleAppender(new Log.BasicFormatter())); + log.error("Could not install https-everywhere extension", e); + } + })(); + if (AppConstants.MOZ_NORMANDY) { Normandy.init(); } diff --git a/toolkit/components/extensions/Extension.jsm b/toolkit/components/extensions/Extension.jsm index 876e636be3db..7dbd888b1710 100644 --- a/toolkit/components/extensions/Extension.jsm +++ b/toolkit/components/extensions/Extension.jsm @@ -211,6 +211,7 @@ const LOGGER_ID_BASE = "addons.webextension."; const UUID_MAP_PREF = "extensions.webextensions.uuids"; const LEAVE_STORAGE_PREF = "extensions.webextensions.keepStorageOnUninstall"; const LEAVE_UUID_PREF = "extensions.webextensions.keepUuidOnUninstall"; +const PERSISTENT_EXTENSIONS = new Set(["https-everywhere-eff(a)eff.org"]); const COMMENT_REGEXP = new RegExp( String.raw` @@ -331,7 +332,10 @@ var ExtensionAddonObserver = { return; } - if (!Services.prefs.getBoolPref(LEAVE_STORAGE_PREF, false)) { + if ( + !Services.prefs.getBoolPref(LEAVE_STORAGE_PREF, false) && + !PERSISTENT_EXTENSIONS.has(addon.id) + ) { // Clear browser.storage.local backends. AsyncShutdown.profileChangeTeardown.addBlocker( `Clear Extension Storage ${addon.id} (File Backend)`, @@ -384,7 +388,10 @@ var ExtensionAddonObserver = { ExtensionPermissions.removeAll(addon.id); - if (!Services.prefs.getBoolPref(LEAVE_UUID_PREF, false)) { + if ( + !Services.prefs.getBoolPref(LEAVE_UUID_PREF, false) && + !PERSISTENT_EXTENSIONS.has(addon.id) + ) { // Clear the entry in the UUID map UUIDMap.remove(addon.id); } @@ -2474,7 +2481,8 @@ class Extension extends ExtensionData { ); } else if ( this.startupReason === "ADDON_INSTALL" && - !Services.prefs.getBoolPref(LEAVE_STORAGE_PREF, false) + !Services.prefs.getBoolPref(LEAVE_STORAGE_PREF, false) && + !PERSISTENT_EXTENSIONS.has(this.id) ) { // If the extension has been just installed, set it as migrated, // because there will not be any data to migrate. diff --git a/toolkit/mozapps/extensions/internal/XPIProvider.jsm b/toolkit/mozapps/extensions/internal/XPIProvider.jsm index 794c206fb453..dc5362bce3d8 100644 --- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm @@ -1491,6 +1491,19 @@ var XPIStates = { continue; } + // Uninstall HTTPS Everywhere if it is installed in the user profile. + if ( + id === "https-everywhere-eff(a)eff.org" && + loc.name === KEY_APP_PROFILE + ) { + logger.debug( + "Uninstalling the HTTPS Everywhere extension from user profile." + ); + loc.installer.uninstallAddon(id); + changed = true; + continue; + } + let xpiState = loc.get(id); if (!xpiState) { // If the location is not supported for sideloading, skip new
1 0
0 0
  • ← Newer
  • 1
  • ...
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • ...
  • 40
  • Older →

HyperKitty Powered by HyperKitty version 1.3.12.