[tor-commits] [tor-browser/tor-browser-68.6.0esr-9.5-1] squash! Bug 30237: Add v3 onion services client authentication prompt

sysrqb at torproject.org sysrqb at torproject.org
Fri Apr 3 02:22:07 UTC 2020


commit e3b4a8bf4a19cee4838a5216a3ef5af58bea6e62
Author: Kathy Brade <brade at pearlcrescent.com>
Date:   Wed Feb 19 11:20:25 2020 -0500

    squash! Bug 30237: Add v3 onion services client authentication prompt
    
    Also fixes bug 19251: use enhanced error pages for onion service errors.
---
 browser/actors/NetErrorChild.jsm                   |  10 +
 .../components/onionservices/content/authPrompt.js |  20 +-
 .../components/onionservices/content/authUtil.jsm  |  16 +-
 .../onionservices/content/netError/browser.svg     |   3 +
 .../onionservices/content/netError/network.svg     |   3 +
 .../content/netError/onionNetError.css             |  65 ++++++
 .../content/netError/onionNetError.jsm             | 253 +++++++++++++++++++++
 .../onionservices/content/netError/onionsite.svg   |   7 +
 browser/components/onionservices/jar.mn            |   1 +
 browser/modules/TorStrings.jsm                     |  48 ++++
 docshell/base/nsDocShell.cpp                       |  47 ++--
 dom/ipc/BrowserParent.cpp                          |   4 +-
 dom/ipc/BrowserParent.h                            |   2 +-
 dom/ipc/PBrowser.ipdl                              |   3 +-
 js/xpconnect/src/xpc.msg                           |   2 +
 netwerk/socket/nsSOCKSIOLayer.cpp                  |  11 +
 xpcom/base/ErrorList.py                            |   4 +
 17 files changed, 472 insertions(+), 27 deletions(-)

diff --git a/browser/actors/NetErrorChild.jsm b/browser/actors/NetErrorChild.jsm
index 7834f7168a42..972ca7de7a01 100644
--- a/browser/actors/NetErrorChild.jsm
+++ b/browser/actors/NetErrorChild.jsm
@@ -23,6 +23,11 @@ ChromeUtils.defineModuleGetter(
   "WebNavigationFrames",
   "resource://gre/modules/WebNavigationFrames.jsm"
 );
+ChromeUtils.defineModuleGetter(
+  this,
+  "OnionServicesAboutNetError",
+  "chrome://browser/content/onionservices/netError/onionNetError.jsm"
+);
 
 XPCOMUtils.defineLazyGlobalGetters(this, ["URL"]);
 
@@ -854,6 +859,11 @@ class NetErrorChild extends ActorChild {
       let learnMoreLink = win.document.getElementById("learnMoreLink");
       let baseURL = Services.urlFormatter.formatURLPref("app.support.baseURL");
       learnMoreLink.setAttribute("href", baseURL + "connection-not-secure");
+
+      // Initialize the onion services error module, which customizes the
+      // content on the error page when an onion service error is being
+      // displayed.
+      OnionServicesAboutNetError.initPage(win.document);
     }
 
     let automatic = Services.prefs.getBoolPref(
diff --git a/browser/components/onionservices/content/authPrompt.js b/browser/components/onionservices/content/authPrompt.js
index f7a10e75158a..24bf7ae28ea2 100644
--- a/browser/components/onionservices/content/authPrompt.js
+++ b/browser/components/onionservices/content/authPrompt.js
@@ -10,9 +10,12 @@ XPCOMUtils.defineLazyModuleGetters(this, {
 
 const OnionAuthPrompt = (function() {
   // OnionServicesAuthPrompt objects run within the main/chrome process.
-  function OnionServicesAuthPrompt(aBrowser, aFailedURI, aOnionName) {
+  // 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;
   }
 
@@ -196,7 +199,8 @@ const OnionAuthPrompt = (function() {
       // Arrange for an error page to be displayed.
       this._browser.messageManager.sendAsyncMessage(
                                OnionAuthUtil.message.authPromptCanceled,
-                               {failedURI: this._failedURI.spec});
+                               {failedURI: this._failedURI.spec,
+                                reasonForPrompt: this._reasonForPrompt});
     },
 
     _getKeyElement() {
@@ -261,17 +265,20 @@ const OnionAuthPrompt = (function() {
 
   let retval = {
     init() {
-      Services.obs.addObserver(this, OnionAuthUtil.topic.authPrompt);
+      Services.obs.addObserver(this, OnionAuthUtil.topic.clientAuthMissing);
+      Services.obs.addObserver(this, OnionAuthUtil.topic.clientAuthIncorrect);
     },
 
     uninit() {
-      Services.obs.removeObserver(this, OnionAuthUtil.topic.authPrompt);
+      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.authPrompt) {
+      if ((aTopic != OnionAuthUtil.topic.clientAuthMissing) &&
+          (aTopic != OnionAuthUtil.topic.clientAuthIncorrect)) {
         return;
       }
 
@@ -288,7 +295,8 @@ const OnionAuthPrompt = (function() {
       }
 
       let failedURI = browser.currentURI;
-      let authPrompt = new OnionServicesAuthPrompt(browser, failedURI, aData);
+      let authPrompt = new OnionServicesAuthPrompt(browser, failedURI,
+                                                   aTopic, aData);
       authPrompt.show(undefined);
     }
   };
diff --git a/browser/components/onionservices/content/authUtil.jsm b/browser/components/onionservices/content/authUtil.jsm
index e9446f51cfcb..c9d83774da1f 100644
--- a/browser/components/onionservices/content/authUtil.jsm
+++ b/browser/components/onionservices/content/authUtil.jsm
@@ -10,7 +10,8 @@ var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
 
 const OnionAuthUtil = {
   topic: {
-    authPrompt: "tor-onion-services-auth-prompt",
+    clientAuthMissing: "tor-onion-services-clientauth-missing",
+    clientAuthIncorrect: "tor-onion-services-clientauth-incorrect",
   },
   message: {
     authPromptCanceled: "Tor:OnionServicesAuthPromptCanceled",
@@ -29,9 +30,18 @@ const OnionAuthUtil = {
   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);
-      aDocShell.displayLoadError(Cr.NS_ERROR_CONNECTION_REFUSED, failedURI,
-                                 undefined, undefined);
+      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.jsm b/browser/components/onionservices/content/netError/onionNetError.jsm
new file mode 100644
index 000000000000..34b4806c63da
--- /dev/null
+++ b/browser/components/onionservices/content/netError/onionNetError.jsm
@@ -0,0 +1,253 @@
+// Copyright (c) 2020, The Tor Project, Inc.
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["OnionServicesAboutNetError"];
+
+ChromeUtils.defineModuleGetter(
+  this,
+  "TorStrings",
+  "resource:///modules/TorStrings.jsm"
+);
+
+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.";
+    if (!err.startsWith(errPrefix)) {
+      return; // This is not an onion services error; nothing for us to do.
+    }
+
+    const errName = err.substring(errPrefix.length);
+
+    const stringsObj = TorStrings.onionServices[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);
+        // eslint-disable-next-line no-unsanitized/property
+        ld.innerHTML = 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 = TorStrings.onionServices.errorPage.browser;
+    labelDiv = this._createDiv(aDoc, undefined, undefined, container);
+    labelDiv.textContent = TorStrings.onionServices.errorPage.network;
+    labelDiv = this._createDiv(aDoc, undefined, undefined, container);
+    labelDiv.textContent = TorStrings.onionServices.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/jar.mn b/browser/components/onionservices/jar.mn
index 583ab77bc6d8..1272d5dc9b6a 100644
--- a/browser/components/onionservices/jar.mn
+++ b/browser/components/onionservices/jar.mn
@@ -3,6 +3,7 @@ browser.jar:
     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.xul              (content/savedKeysDialog.xul)
diff --git a/browser/modules/TorStrings.jsm b/browser/modules/TorStrings.jsm
index e9a8b3969297..a57d17f8361c 100644
--- a/browser/modules/TorStrings.jsm
+++ b/browser/modules/TorStrings.jsm
@@ -328,9 +328,57 @@ var TorStrings = {
       return tsb.getString(key, fallback);
     };
 
+    const kProblemLoadingSiteFallback = "Problem Loading Onionsite";
+    const kLongDescFallback = "Details: %S";
+
     let retval = {
       learnMore: getString("learnMore", "Learn more"),
       learnMoreURL: `https://2019.www.torproject.org/docs/tor-manual-dev.html.${getLocale()}#_client_authorization`,
+      errorPage: {
+        browser: getString("errorPage.browser", "Browser"),
+        network: getString("errorPage.network", "Network"),
+        onionSite: getString("errorPage.onionSite", "Onionsite"),
+      },
+      descNotFound: { // Tor SOCKS error 0xF0
+        pageTitle: getString("descNotFound.pageTitle", kProblemLoadingSiteFallback),
+        header: getString("descNotFound.header", "Onionsite Not Found"),
+        longDescription: getString("descNotFound.longDescription", kLongDescFallback),
+      },
+      descInvalid: { // Tor SOCKS error 0xF1
+        pageTitle: getString("descInvalid.pageTitle", kProblemLoadingSiteFallback),
+        header: getString("descInvalid.header", "Onionsite Cannot Be Reached"),
+        longDescription: getString("descInvalid.longDescription", kLongDescFallback),
+      },
+      introFailed: { // Tor SOCKS error 0xF2
+        pageTitle: getString("introFailed.pageTitle", kProblemLoadingSiteFallback),
+        header: getString("introFailed.header", "Onionsite Has Disconnected"),
+        longDescription: getString("introFailed.longDescription", kLongDescFallback),
+      },
+      rendezvousFailed: { // Tor SOCKS error 0xF3
+        pageTitle: getString("rendezvousFailed.pageTitle", kProblemLoadingSiteFallback),
+        header: getString("rendezvousFailed.header", "Unable to Connect to Onionsite"),
+        longDescription: getString("rendezvousFailed.longDescription", kLongDescFallback),
+      },
+      clientAuthMissing: { // Tor SOCKS error 0xF4
+        pageTitle: getString("clientAuthMissing.pageTitle", "Authorization Required"),
+        header: getString("clientAuthMissing.header", "Onionsite Requires Authentication"),
+        longDescription: getString("clientAuthMissing.longDescription", kLongDescFallback),
+      },
+      clientAuthIncorrect: { // Tor SOCKS error 0xF5
+        pageTitle: getString("clientAuthIncorrect.pageTitle", "Authorization Failed"),
+        header: getString("clientAuthIncorrect.header", "Onionsite Authentication Failed"),
+        longDescription: getString("clientAuthIncorrect.longDescription", kLongDescFallback),
+      },
+      badAddress: { // Tor SOCKS error 0xF6
+        pageTitle: getString("badAddress.pageTitle", kProblemLoadingSiteFallback),
+        header: getString("badAddress.header", "Invalid Onionsite Address"),
+        longDescription: getString("badAddress.longDescription", kLongDescFallback),
+      },
+      introTimedOut: { // Tor SOCKS error 0xF7
+        pageTitle: getString("introTimedOut.pageTitle", kProblemLoadingSiteFallback),
+        header: getString("introTimedOut.header", "Onionsite Circuit Creation Timed Out"),
+        longDescription: getString("introTimedOut.longDescription", kLongDescFallback),
+      },
       authPrompt: {
         description:
           getString("authPrompt.description", "%S is requesting your private key."),
diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp
index cc329a50c109..4c5352d875dc 100644
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -4284,6 +4284,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
@@ -4364,28 +4365,43 @@ nsDocShell::DisplayLoadError(nsresult aError, nsIURI* aURI,
         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:
-        // For now, handle these Tor onion service errors the same as
-        // NS_ERROR_CONNECTION_REFUSED.
-        NS_ENSURE_ARG_POINTER(aURI);
-        addHostPort = true;
-        error = "connectionFailure";
+        error = "onionServices.rendezvousFailed";
         break;
-      case NS_ERROR_TOR_ONION_SVC_BAD_CLIENT_AUTH:
-        // For now, we let this fall through but it should be handled in
-        // a special way, e.g., tell the user in our auth prompt that the
-        // key they provided was bad. This will be done as part of #30025.
-        MOZ_FALLTHROUGH;
       case NS_ERROR_TOR_ONION_SVC_MISSING_CLIENT_AUTH:
         error = "onionServices.clientAuthMissing";
-        // Display about:blank while the Tor client auth prompt is open.
-        errorPage.AssignLiteral("blank");
+        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");
+    }
   }
 
   // Test if the error should be displayed
@@ -8415,17 +8431,20 @@ nsresult nsDocShell::CreateContentViewer(const nsACString& aContentType,
          (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);
+              ->SendShowOnionServicesAuthPrompt(onionHost, nsCString(topic));
         }
       } else {
         nsCOMPtr<nsPIDOMWindowOuter> browserWin = GetWindow();
         nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService();
         if (browserWin && obsSvc) {
-          const char* topic = "tor-onion-services-auth-prompt";
           obsSvc->NotifyObservers(browserWin, topic,
                                   NS_ConvertUTF8toUTF16(onionHost).get());
         }
diff --git a/dom/ipc/BrowserParent.cpp b/dom/ipc/BrowserParent.cpp
index 88605c949502..ea2bbdc4821f 100644
--- a/dom/ipc/BrowserParent.cpp
+++ b/dom/ipc/BrowserParent.cpp
@@ -3777,7 +3777,7 @@ mozilla::ipc::IPCResult BrowserParent::RecvShowCanvasPermissionPrompt(
 }
 
 mozilla::ipc::IPCResult BrowserParent::RecvShowOnionServicesAuthPrompt(
-    const nsCString& aOnionName) {
+    const nsCString& aOnionName, const nsCString& aTopic) {
   nsCOMPtr<nsIBrowser> browser =
       mFrameElement ? mFrameElement->AsBrowser() : nullptr;
   if (!browser) {
@@ -3791,7 +3791,7 @@ mozilla::ipc::IPCResult BrowserParent::RecvShowOnionServicesAuthPrompt(
   }
   nsresult rv = os->NotifyObservers(
       browser,
-      "tor-onion-services-auth-prompt",
+      aTopic.get(),
       NS_ConvertUTF8toUTF16(aOnionName).get());
   if (NS_FAILED(rv)) {
     return IPC_FAIL_NO_REASON(this);
diff --git a/dom/ipc/BrowserParent.h b/dom/ipc/BrowserParent.h
index 23c189cf6243..5a841a09a48c 100644
--- a/dom/ipc/BrowserParent.h
+++ b/dom/ipc/BrowserParent.h
@@ -719,7 +719,7 @@ class BrowserParent final : public PBrowserParent,
       const nsCString& aOrigin, const bool& aHideDoorHanger);
 
   mozilla::ipc::IPCResult RecvShowOnionServicesAuthPrompt(
-      const nsCString& aOnionName);
+      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 b99d7f04fe39..9029bf0abcbe 100644
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -567,8 +567,9 @@ parent:
      * 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);
+    async ShowOnionServicesAuthPrompt(nsCString aOnionHost, nsCString aTopic);
 
 child:
     async NativeSynthesisResponse(uint64_t aObserverId, nsCString aResponse);
diff --git a/js/xpconnect/src/xpc.msg b/js/xpconnect/src/xpc.msg
index d9b64fe87fce..b0469d325669 100644
--- a/js/xpconnect/src/xpc.msg
+++ b/js/xpconnect/src/xpc.msg
@@ -253,6 +253,8 @@ XPC_MSG_DEF(NS_ERROR_TOR_ONION_SVC_INTRO_FAILED       , "Tor onion service intro
 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/socket/nsSOCKSIOLayer.cpp b/netwerk/socket/nsSOCKSIOLayer.cpp
index 6649056da1e7..56ff4a696845 100644
--- a/netwerk/socket/nsSOCKSIOLayer.cpp
+++ b/netwerk/socket/nsSOCKSIOLayer.cpp
@@ -1042,6 +1042,17 @@ PRStatus nsSOCKSSocketInfo::ReadV5ConnectResponseTop() {
                   " 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/xpcom/base/ErrorList.py b/xpcom/base/ErrorList.py
index 24cabd355b0d..9fe223d3bcca 100755
--- a/xpcom/base/ErrorList.py
+++ b/xpcom/base/ErrorList.py
@@ -1136,6 +1136,10 @@ with modules["TOR"]:
     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





More information about the tor-commits mailing list