[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

[tor-commits] [Git][tpo/applications/tor-browser][tor-browser-115.10.0esr-13.5-1] 8 commits: fixup! Bug 30237: Add v3 onion services client authentication prompt



Title: GitLab

richard pushed to branch tor-browser-115.10.0esr-13.5-1 at The Tor Project / Applications / Tor Browser

Commits:

  • 795b62a7
    by Henry Wilkes at 2024-04-25T10:32:13+01:00
    fixup! Bug 30237: Add v3 onion services client authentication prompt
    
    Bug 42542: Remove the OnionServicesAuthPrompt class.
    
    The class is merged into OnionAuthPrompt. This currently only works when
    only one tab triggers the prompt at a time.
    
    Not linted to improve commit readability.
    
  • 38d07333
    by Henry Wilkes at 2024-04-25T10:32:14+01:00
    fixup! Bug 30237: Add v3 onion services client authentication prompt
    
    Bug 42542: Lint after removing OnionServicesAuthPrompt class.
    
  • a35c7a68
    by Henry Wilkes at 2024-04-25T11:55:07+01:00
    fixup! Bug 30237: Add v3 onion services client authentication prompt
    
    Bug 42542: Re-handle showing errors.
    
    Call "_showWarning" rather than "show" when we have an error.
    
    Do not attempt to show the prompt if we cannot determine the onion
    service id.
    
    For the service id regex, use "^(.*\.)?" instead of "^(.*\.)*": since the
    ".*" is greedy, this can only ever match up to once.
    
  • df3374c4
    by Henry Wilkes at 2024-04-30T11:11:39+01:00
    fixup! Bug 30237: Add v3 onion services client authentication prompt
    
    Bug 42542: Reset the authentication prompt when switching between two
    authentication tabs.
    
    We keep a record of which tab details are being shown in the
    notification popup. We reset the prompt whenever we want to show
    different details.
    
    We also fetch elements and set event listeners (once) when we initialize
    OnionAuthPrompt. In particular, the listeners should only react to the
    shown details.
    
    We also assume that elements with an ID exist in the DOM.
    
  • 0cacad0c
    by Henry Wilkes at 2024-04-30T11:11:46+01:00
    fixup! Bug 30237: Add v3 onion services client authentication prompt
    
    Bug 42542: Follow recent coding conventions.
    
    Do not prefix variable names with "a" and use triple equality.
    
  • 82fa0a97
    by Henry Wilkes at 2024-04-30T11:11:46+01:00
    fixup! Bug 30237: Add v3 onion services client authentication prompt
    
    Bug 42542: Use "keydown" instead of deprecated "keypress".
    
    Also, stop handling "Escape" key since it is already handled by
    PopupNotification.
    
  • 78107767
    by Henry Wilkes at 2024-04-30T11:11:47+01:00
    fixup! Bug 30237: Add v3 onion services client authentication prompt
    
    Bug 42542: Add documentation to OnionAuthPrompt.
    
  • 170cb7ab
    by Henry Wilkes at 2024-04-30T11:11:47+01:00
    fixup! Bug 30237: Add v3 onion services client authentication prompt
    
    Bug 42542: Add a logger to OnionAuthPrompt.
    

2 changed files:

Changes:

  • browser/components/onionservices/content/authPopup.inc.xhtml
    ... ... @@ -5,7 +5,9 @@
    5 5
         <description id="tor-clientauth-notification-desc"/>
    
    6 6
         <label id="tor-clientauth-notification-learnmore"
    
    7 7
                class="text-link popup-notification-learnmore-link"
    
    8
    -           is="text-link"/>
    
    8
    +           is="text-link"
    
    9
    +           href="">"about:manual#onion-services_onion-service-authentication"
    
    10
    +           useoriginprincipal="true"/>
    
    9 11
         <html:div>
    
    10 12
           <html:input id="tor-clientauth-notification-key" type="password"/>
    
    11 13
           <html:div id="tor-clientauth-warning"/>
    

  • browser/components/onionservices/content/authPrompt.js
    ... ... @@ -2,350 +2,417 @@
    2 2
     
    
    3 3
     "use strict";
    
    4 4
     
    
    5
    -const OnionAuthPrompt = (function () {
    
    5
    +var OnionAuthPrompt = {
    
    6 6
       // Only import to our internal scope, rather than the global scope of
    
    7 7
       // browser.xhtml.
    
    8
    -  const lazy = {};
    
    9
    -  ChromeUtils.defineESModuleGetters(lazy, {
    
    10
    -    TorProviderBuilder: "resource://gre/modules/TorProviderBuilder.sys.mjs",
    
    11
    -    TorStrings: "resource://gre/modules/TorStrings.sys.mjs",
    
    12
    -    CommonUtils: "resource://services-common/utils.sys.mjs",
    
    13
    -  });
    
    14
    -
    
    15
    -  // OnionServicesAuthPrompt objects run within the main/chrome process.
    
    16
    -  // aReason is the topic passed within the observer notification that is
    
    17
    -  // causing this auth prompt to be displayed.
    
    18
    -  function OnionServicesAuthPrompt(aBrowser, aFailedURI, aReason, aOnionName) {
    
    19
    -    this._browser = aBrowser;
    
    20
    -    this._failedURI = aFailedURI;
    
    21
    -    this._reasonForPrompt = aReason;
    
    22
    -    this._onionHostname = aOnionName;
    
    23
    -  }
    
    24
    -
    
    25
    -  const topics = {
    
    8
    +  _lazy: {},
    
    9
    +
    
    10
    +  /**
    
    11
    +   * The topics to listen to.
    
    12
    +   *
    
    13
    +   * @type {Object<string, string>}
    
    14
    +   */
    
    15
    +  _topics: {
    
    26 16
         clientAuthMissing: "tor-onion-services-clientauth-missing",
    
    27 17
         clientAuthIncorrect: "tor-onion-services-clientauth-incorrect",
    
    28
    -  };
    
    29
    -
    
    30
    -  OnionServicesAuthPrompt.prototype = {
    
    31
    -    show(aWarningMessage) {
    
    32
    -      let mainAction = {
    
    33
    -        label: lazy.TorStrings.onionServices.authPrompt.done,
    
    34
    -        accessKey: lazy.TorStrings.onionServices.authPrompt.doneAccessKey,
    
    35
    -        leaveOpen: true, // Callback is responsible for closing the notification.
    
    36
    -        callback: this._onDone.bind(this),
    
    37
    -      };
    
    38
    -
    
    39
    -      let dialogBundle = Services.strings.createBundle(
    
    40
    -        "chrome://global/locale/dialog.properties"
    
    41
    -      );
    
    42
    -
    
    43
    -      let cancelAccessKey = dialogBundle.GetStringFromName("accesskey-cancel");
    
    44
    -      if (!cancelAccessKey) {
    
    45
    -        cancelAccessKey = "c";
    
    46
    -      } // required by PopupNotifications.show()
    
    47
    -
    
    48
    -      let cancelAction = {
    
    49
    -        label: dialogBundle.GetStringFromName("button-cancel"),
    
    50
    -        accessKey: cancelAccessKey,
    
    51
    -        callback: this._onCancel.bind(this),
    
    52
    -      };
    
    53
    -
    
    54
    -      let _this = this;
    
    55
    -      let options = {
    
    56
    -        autofocus: true,
    
    57
    -        hideClose: true,
    
    58
    -        persistent: true,
    
    59
    -        removeOnDismissal: false,
    
    60
    -        eventCallback(aTopic) {
    
    61
    -          if (aTopic === "showing") {
    
    62
    -            _this._onPromptShowing(aWarningMessage);
    
    63
    -          } else if (aTopic === "shown") {
    
    64
    -            _this._onPromptShown();
    
    65
    -          } else if (aTopic === "removed") {
    
    66
    -            _this._onPromptRemoved();
    
    67
    -          }
    
    68
    -        },
    
    69
    -      };
    
    70
    -
    
    71
    -      this._prompt = PopupNotifications.show(
    
    72
    -        this._browser,
    
    73
    -        "tor-clientauth",
    
    74
    -        "",
    
    75
    -        "tor-clientauth-notification-icon",
    
    76
    -        mainAction,
    
    77
    -        [cancelAction],
    
    78
    -        options
    
    79
    -      );
    
    80
    -    },
    
    81
    -
    
    82
    -    _onPromptShowing(aWarningMessage) {
    
    83
    -      let xulDoc = this._browser.ownerDocument;
    
    84
    -      let descElem = xulDoc.getElementById("tor-clientauth-notification-desc");
    
    85
    -      if (descElem) {
    
    86
    -        // Handle replacement of the onion name within the localized
    
    87
    -        // string ourselves so we can show the onion name as bold text.
    
    88
    -        // We do this by splitting the localized string and creating
    
    89
    -        // several HTML <span> elements.
    
    90
    -        const fmtString = lazy.TorStrings.onionServices.authPrompt.description;
    
    91
    -        const [prefix, suffix] = fmtString.split("%S");
    
    92
    -
    
    93
    -        const domainEl = xulDoc.createElement("span");
    
    94
    -        domainEl.id = "tor-clientauth-notification-onionname";
    
    95
    -        domainEl.textContent = TorUIUtils.shortenOnionAddress(
    
    96
    -          this._onionHostname
    
    97
    -        );
    
    98
    -
    
    99
    -        descElem.replaceChildren(prefix, domainEl, suffix);
    
    100
    -      }
    
    101
    -
    
    102
    -      // Set "Learn More" label and href.
    
    103
    -      let learnMoreElem = xulDoc.getElementById(
    
    104
    -        "tor-clientauth-notification-learnmore"
    
    105
    -      );
    
    106
    -      if (learnMoreElem) {
    
    107
    -        learnMoreElem.setAttribute(
    
    108
    -          "value",
    
    109
    -          lazy.TorStrings.onionServices.learnMore
    
    110
    -        );
    
    111
    -        learnMoreElem.setAttribute(
    
    112
    -          "href",
    
    113
    -          "about:manual#onion-services_onion-service-authentication"
    
    114
    -        );
    
    115
    -        learnMoreElem.setAttribute("useoriginprincipal", "true");
    
    116
    -      }
    
    117
    -
    
    118
    -      this._showWarning(aWarningMessage);
    
    119
    -      let checkboxElem = this._getCheckboxElement();
    
    120
    -      if (checkboxElem) {
    
    121
    -        checkboxElem.checked = false;
    
    122
    -      }
    
    123
    -    },
    
    124
    -
    
    125
    -    _onPromptShown() {
    
    126
    -      let keyElem = this._getKeyElement();
    
    127
    -      if (keyElem) {
    
    128
    -        keyElem.setAttribute(
    
    129
    -          "placeholder",
    
    130
    -          lazy.TorStrings.onionServices.authPrompt.keyPlaceholder
    
    131
    -        );
    
    132
    -        this._boundOnKeyFieldKeyPress = this._onKeyFieldKeyPress.bind(this);
    
    133
    -        this._boundOnKeyFieldInput = this._onKeyFieldInput.bind(this);
    
    134
    -        keyElem.addEventListener("keypress", this._boundOnKeyFieldKeyPress);
    
    135
    -        keyElem.addEventListener("input", this._boundOnKeyFieldInput);
    
    136
    -        keyElem.focus();
    
    137
    -      }
    
    138
    -    },
    
    139
    -
    
    140
    -    _onPromptRemoved() {
    
    141
    -      if (this._boundOnKeyFieldKeyPress) {
    
    142
    -        let keyElem = this._getKeyElement();
    
    143
    -        if (keyElem) {
    
    144
    -          keyElem.value = "";
    
    145
    -          keyElem.removeEventListener(
    
    146
    -            "keypress",
    
    147
    -            this._boundOnKeyFieldKeyPress
    
    148
    -          );
    
    149
    -          this._boundOnKeyFieldKeyPress = undefined;
    
    150
    -          keyElem.removeEventListener("input", this._boundOnKeyFieldInput);
    
    151
    -          this._boundOnKeyFieldInput = undefined;
    
    18
    +  },
    
    19
    +
    
    20
    +  /**
    
    21
    +   * @typedef {object} PromptDetails
    
    22
    +   *
    
    23
    +   * @property {Browser} browser - The browser this prompt is for.
    
    24
    +   * @property {string} cause - The notification that cause this prompt.
    
    25
    +   * @property {string} onionHost - The onion host name.
    
    26
    +   * @property {nsIURI} uri - The browser URI when the notification was
    
    27
    +   *   triggered.
    
    28
    +   * @property {string} onionServiceId - The onion service ID for this host.
    
    29
    +   * @property {Notification} [notification] - The notification instance for
    
    30
    +   *   this prompt.
    
    31
    +   */
    
    32
    +
    
    33
    +  /**
    
    34
    +   * The currently shown details in the prompt.
    
    35
    +   */
    
    36
    +  _shownDetails: null,
    
    37
    +
    
    38
    +  /**
    
    39
    +   * Used for logging to represent PromptDetails.
    
    40
    +   *
    
    41
    +   * @param {PromptDetails} details - The details to represent.
    
    42
    +   * @returns {string} - The representation of these details.
    
    43
    +   */
    
    44
    +  _detailsRepr(details) {
    
    45
    +    if (!details) {
    
    46
    +      return "none";
    
    47
    +    }
    
    48
    +    return `${details.browser.browserId}:${details.onionHost}`;
    
    49
    +  },
    
    50
    +
    
    51
    +  /**
    
    52
    +   * Show a new prompt, using the given details.
    
    53
    +   *
    
    54
    +   * @param {PromptDetails} details - The details to show.
    
    55
    +   */
    
    56
    +  show(details) {
    
    57
    +    this._logger.debug(`New Notification: ${this._detailsRepr(details)}`);
    
    58
    +
    
    59
    +    let mainAction = {
    
    60
    +      label: this.TorStrings.onionServices.authPrompt.done,
    
    61
    +      accessKey: this.TorStrings.onionServices.authPrompt.doneAccessKey,
    
    62
    +      leaveOpen: true, // Callback is responsible for closing the notification.
    
    63
    +      callback: this._onDone.bind(this),
    
    64
    +    };
    
    65
    +
    
    66
    +    let dialogBundle = Services.strings.createBundle(
    
    67
    +      "chrome://global/locale/dialog.properties"
    
    68
    +    );
    
    69
    +
    
    70
    +    let cancelAccessKey = dialogBundle.GetStringFromName("accesskey-cancel");
    
    71
    +    if (!cancelAccessKey) {
    
    72
    +      cancelAccessKey = "c";
    
    73
    +    } // required by PopupNotifications.show()
    
    74
    +
    
    75
    +    // The first secondarybuttoncommand (cancelAction) should be triggered when
    
    76
    +    // the user presses "Escape".
    
    77
    +    let cancelAction = {
    
    78
    +      label: dialogBundle.GetStringFromName("button-cancel"),
    
    79
    +      accessKey: cancelAccessKey,
    
    80
    +      callback: this._onCancel.bind(this),
    
    81
    +    };
    
    82
    +
    
    83
    +    let options = {
    
    84
    +      autofocus: true,
    
    85
    +      hideClose: true,
    
    86
    +      persistent: true,
    
    87
    +      removeOnDismissal: false,
    
    88
    +      eventCallback: topic => {
    
    89
    +        if (topic === "showing") {
    
    90
    +          this._onPromptShowing(details);
    
    91
    +        } else if (topic === "shown") {
    
    92
    +          this._onPromptShown();
    
    93
    +        } else if (topic === "removed") {
    
    94
    +          this._onPromptRemoved(details);
    
    152 95
             }
    
    96
    +      },
    
    97
    +    };
    
    98
    +
    
    99
    +    details.notification = PopupNotifications.show(
    
    100
    +      details.browser,
    
    101
    +      "tor-clientauth",
    
    102
    +      "",
    
    103
    +      "tor-clientauth-notification-icon",
    
    104
    +      mainAction,
    
    105
    +      [cancelAction],
    
    106
    +      options
    
    107
    +    );
    
    108
    +  },
    
    109
    +
    
    110
    +  /**
    
    111
    +   * Callback when the prompt is about to be shown.
    
    112
    +   *
    
    113
    +   * @param {PromptDetails?} details - The details to show, or null to shown
    
    114
    +   *   none.
    
    115
    +   */
    
    116
    +  _onPromptShowing(details) {
    
    117
    +    if (details === this._shownDetails) {
    
    118
    +      // The last shown details match this one exactly.
    
    119
    +      // This happens when we switch tabs to a page that has no prompt and then
    
    120
    +      // switch back.
    
    121
    +      // We don't want to reset the current state in this case.
    
    122
    +      // In particular, we keep the current _keyInput value and _persistCheckbox
    
    123
    +      // the same.
    
    124
    +      this._logger.debug(`Already showing: ${this._detailsRepr(details)}`);
    
    125
    +      return;
    
    126
    +    }
    
    127
    +
    
    128
    +    this._logger.debug(`Now showing: ${this._detailsRepr(details)}`);
    
    129
    +
    
    130
    +    this._shownDetails = details;
    
    131
    +
    
    132
    +    // Clear the key input.
    
    133
    +    // In particular, clear the input when switching tabs.
    
    134
    +    this._keyInput.value = "";
    
    135
    +    this._persistCheckbox.checked = false;
    
    136
    +
    
    137
    +    // Handle replacement of the onion name within the localized
    
    138
    +    // string ourselves so we can show the onion name as bold text.
    
    139
    +    // We do this by splitting the localized string and creating
    
    140
    +    // several HTML <span> elements.
    
    141
    +    const fmtString = this.TorStrings.onionServices.authPrompt.description;
    
    142
    +    const [prefix, suffix] = fmtString.split("%S");
    
    143
    +
    
    144
    +    const domainEl = document.createElement("span");
    
    145
    +    domainEl.id = "tor-clientauth-notification-onionname";
    
    146
    +    domainEl.textContent = TorUIUtils.shortenOnionAddress(
    
    147
    +      this._shownDetails?.onionHost ?? ""
    
    148
    +    );
    
    149
    +
    
    150
    +    this._descriptionEl.replaceChildren(prefix, domainEl, suffix);
    
    151
    +
    
    152
    +    this._showWarning(undefined);
    
    153
    +  },
    
    154
    +
    
    155
    +  /**
    
    156
    +   * Callback after the prompt is shown.
    
    157
    +   */
    
    158
    +  _onPromptShown() {
    
    159
    +    this._keyInput.focus();
    
    160
    +  },
    
    161
    +
    
    162
    +  /**
    
    163
    +   * Callback when a Notification is removed.
    
    164
    +   *
    
    165
    +   * @param {PromptDetails} details - The details for the removed notification.
    
    166
    +   */
    
    167
    +  _onPromptRemoved(details) {
    
    168
    +    if (details !== this._shownDetails) {
    
    169
    +      // Removing the notification for some other page.
    
    170
    +      // For example, closing another tab that also requires authentication.
    
    171
    +      this._logger.debug(`Removed not shown: ${this._detailsRepr(details)}`);
    
    172
    +      return;
    
    173
    +    }
    
    174
    +    this._logger.debug(`Removed shown: ${this._detailsRepr(details)}`);
    
    175
    +    // Reset the prompt as a precaution.
    
    176
    +    // In particular, we want to clear the input so that the entered key does
    
    177
    +    // not persist.
    
    178
    +    this._onPromptShowing(null);
    
    179
    +  },
    
    180
    +
    
    181
    +  /**
    
    182
    +   * Callback when the user submits the key.
    
    183
    +   */
    
    184
    +  async _onDone() {
    
    185
    +    this._logger.debug(
    
    186
    +      `Sumbitting key: ${this._detailsRepr(this._shownDetails)}`
    
    187
    +    );
    
    188
    +
    
    189
    +    // Grab the details before they might change as we await.
    
    190
    +    const { browser, onionServiceId, notification } = this._shownDetails;
    
    191
    +    const isPermanent = this._persistCheckbox.checked;
    
    192
    +
    
    193
    +    const base64key = this._keyToBase64(this._keyInput.value);
    
    194
    +    if (!base64key) {
    
    195
    +      this._showWarning(this.TorStrings.onionServices.authPrompt.invalidKey);
    
    196
    +      return;
    
    197
    +    }
    
    198
    +
    
    199
    +    try {
    
    200
    +      const provider = await this._lazy.TorProviderBuilder.build();
    
    201
    +      await provider.onionAuthAdd(onionServiceId, base64key, isPermanent);
    
    202
    +    } catch (e) {
    
    203
    +      if (e.torMessage) {
    
    204
    +        this._showWarning(e.torMessage);
    
    205
    +      } else {
    
    206
    +        this._logger.error(`Failed to set key for ${onionServiceId}`, e);
    
    207
    +        this._showWarning(
    
    208
    +          this.TorStrings.onionServices.authPrompt.failedToSetKey
    
    209
    +        );
    
    153 210
           }
    
    154
    -    },
    
    155
    -
    
    156
    -    _onKeyFieldKeyPress(aEvent) {
    
    157
    -      if (aEvent.keyCode == aEvent.DOM_VK_RETURN) {
    
    158
    -        this._onDone();
    
    159
    -      } else if (aEvent.keyCode == aEvent.DOM_VK_ESCAPE) {
    
    160
    -        this._prompt.remove();
    
    161
    -        this._onCancel();
    
    162
    -      }
    
    163
    -    },
    
    164
    -
    
    165
    -    _onKeyFieldInput(aEvent) {
    
    166
    -      this._showWarning(undefined); // Remove the warning.
    
    167
    -    },
    
    168
    -
    
    169
    -    async _onDone() {
    
    170
    -      const keyElem = this._getKeyElement();
    
    171
    -      if (!keyElem) {
    
    172
    -        return;
    
    173
    -      }
    
    174
    -
    
    175
    -      const base64key = this._keyToBase64(keyElem.value);
    
    176
    -      if (!base64key) {
    
    177
    -        this._showWarning(lazy.TorStrings.onionServices.authPrompt.invalidKey);
    
    178
    -        return;
    
    179
    -      }
    
    180
    -
    
    181
    -      this._prompt.remove();
    
    182
    -
    
    183
    -      const controllerFailureMsg =
    
    184
    -        lazy.TorStrings.onionServices.authPrompt.failedToSetKey;
    
    211
    +      return;
    
    212
    +    }
    
    213
    +
    
    214
    +    notification.remove();
    
    215
    +    // Success! Reload the page.
    
    216
    +    browser.sendMessageToActor("Browser:Reload", {}, "BrowserTab");
    
    217
    +  },
    
    218
    +
    
    219
    +  /**
    
    220
    +   * Callback when the user dismisses the prompt.
    
    221
    +   */
    
    222
    +  _onCancel() {
    
    223
    +    // Arrange for an error page to be displayed:
    
    224
    +    // we build a short script calling docShell.displayError()
    
    225
    +    // and we pass it as a data: URI to loadFrameScript(),
    
    226
    +    // which runs it in the content frame which triggered
    
    227
    +    // this authentication prompt.
    
    228
    +    this._logger.debug(`Cancelling: ${this._detailsRepr(this._shownDetails)}`);
    
    229
    +
    
    230
    +    const { browser, cause, uri } = this._shownDetails;
    
    231
    +    const errorCode =
    
    232
    +      cause === this._topics.clientAuthMissing
    
    233
    +        ? Cr.NS_ERROR_TOR_ONION_SVC_MISSING_CLIENT_AUTH
    
    234
    +        : Cr.NS_ERROR_TOR_ONION_SVC_BAD_CLIENT_AUTH;
    
    235
    +    const io =
    
    236
    +      'ChromeUtils.import("resource://gre/modules/Services.jsm").Services.io';
    
    237
    +
    
    238
    +    browser.messageManager.loadFrameScript(
    
    239
    +      `data:application/_javascript_,${encodeURIComponent(
    
    240
    +        `docShell.displayLoadError(${errorCode}, ${io}.newURI(${JSON.stringify(
    
    241
    +          uri.spec
    
    242
    +        )}), undefined, undefined);`
    
    243
    +      )}`,
    
    244
    +      false
    
    245
    +    );
    
    246
    +  },
    
    247
    +
    
    248
    +  /**
    
    249
    +   * Show a warning message to the user or clear the warning.
    
    250
    +   *
    
    251
    +   * @param {string?} warningMessage - The message to show, or undefined to
    
    252
    +   *   clear the current message.
    
    253
    +   */
    
    254
    +  _showWarning(warningMessage) {
    
    255
    +    this._logger.debug(`Showing warning: ${warningMessage}`);
    
    256
    +    if (warningMessage) {
    
    257
    +      this._warningEl.textContent = warningMessage;
    
    258
    +      this._warningEl.removeAttribute("hidden");
    
    259
    +      this._keyInput.classList.add("invalid");
    
    260
    +    } else {
    
    261
    +      this._warningEl.setAttribute("hidden", "true");
    
    262
    +      this._keyInput.classList.remove("invalid");
    
    263
    +    }
    
    264
    +  },
    
    265
    +
    
    266
    +  /**
    
    267
    +   * Convert the user-entered key into base64.
    
    268
    +   *
    
    269
    +   * @param {string} keyString - The key to convert.
    
    270
    +   * @returns {string?} - The base64 representation, or undefined if the given
    
    271
    +   *   key was not the correct format.
    
    272
    +   */
    
    273
    +  _keyToBase64(keyString) {
    
    274
    +    if (!keyString) {
    
    275
    +      return undefined;
    
    276
    +    }
    
    277
    +
    
    278
    +    let base64key;
    
    279
    +    if (keyString.length === 52) {
    
    280
    +      // The key is probably base32-encoded. Attempt to decode.
    
    281
    +      // Although base32 specifies uppercase letters, we accept lowercase
    
    282
    +      // as well because users may type in lowercase or copy a key out of
    
    283
    +      // a tor onion-auth file (which uses lowercase).
    
    284
    +      let rawKey;
    
    185 285
           try {
    
    186
    -        // ^(subdomain.)*onionserviceid.onion$ (case-insensitive)
    
    187
    -        const onionServiceIdRegExp =
    
    188
    -          /^(.*\.)*(?<onionServiceId>[a-z2-7]{56})\.onion$/i;
    
    189
    -        // match() will return null on bad match, causing throw
    
    190
    -        const onionServiceId = this._onionHostname
    
    191
    -          .match(onionServiceIdRegExp)
    
    192
    -          .groups.onionServiceId.toLowerCase();
    
    193
    -
    
    194
    -        const checkboxElem = this._getCheckboxElement();
    
    195
    -        const isPermanent = checkboxElem && checkboxElem.checked;
    
    196
    -        const provider = await lazy.TorProviderBuilder.build();
    
    197
    -        await provider.onionAuthAdd(onionServiceId, base64key, isPermanent);
    
    198
    -        // Success! Reload the page.
    
    199
    -        this._browser.sendMessageToActor("Browser:Reload", {}, "BrowserTab");
    
    200
    -      } catch (e) {
    
    201
    -        if (e.torMessage) {
    
    202
    -          this.show(e.torMessage);
    
    203
    -        } else {
    
    204
    -          console.error(controllerFailureMsg, e);
    
    205
    -          this.show(controllerFailureMsg);
    
    206
    -        }
    
    207
    -      }
    
    208
    -    },
    
    209
    -
    
    210
    -    _onCancel() {
    
    211
    -      // Arrange for an error page to be displayed:
    
    212
    -      // we build a short script calling docShell.displayError()
    
    213
    -      // and we pass it as a data: URI to loadFrameScript(),
    
    214
    -      // which runs it in the content frame which triggered
    
    215
    -      // this authentication prompt.
    
    216
    -      const failedURI = this._failedURI.spec;
    
    217
    -      const errorCode =
    
    218
    -        this._reasonForPrompt === topics.clientAuthMissing
    
    219
    -          ? Cr.NS_ERROR_TOR_ONION_SVC_MISSING_CLIENT_AUTH
    
    220
    -          : Cr.NS_ERROR_TOR_ONION_SVC_BAD_CLIENT_AUTH;
    
    221
    -      const io =
    
    222
    -        'ChromeUtils.import("resource://gre/modules/Services.jsm").Services.io';
    
    223
    -
    
    224
    -      this._browser.messageManager.loadFrameScript(
    
    225
    -        `data:application/_javascript_,${encodeURIComponent(
    
    226
    -          `docShell.displayLoadError(${errorCode}, ${io}.newURI(${JSON.stringify(
    
    227
    -            failedURI
    
    228
    -          )}), undefined, undefined);`
    
    229
    -        )}`,
    
    230
    -        false
    
    231
    -      );
    
    232
    -    },
    
    233
    -
    
    234
    -    _getKeyElement() {
    
    235
    -      let xulDoc = this._browser.ownerDocument;
    
    236
    -      return xulDoc.getElementById("tor-clientauth-notification-key");
    
    237
    -    },
    
    238
    -
    
    239
    -    _getCheckboxElement() {
    
    240
    -      let xulDoc = this._browser.ownerDocument;
    
    241
    -      return xulDoc.getElementById("tor-clientauth-persistkey-checkbox");
    
    242
    -    },
    
    243
    -
    
    244
    -    _showWarning(aWarningMessage) {
    
    245
    -      let xulDoc = this._browser.ownerDocument;
    
    246
    -      let warningElem = xulDoc.getElementById("tor-clientauth-warning");
    
    247
    -      let keyElem = this._getKeyElement();
    
    248
    -      if (warningElem) {
    
    249
    -        if (aWarningMessage) {
    
    250
    -          warningElem.textContent = aWarningMessage;
    
    251
    -          warningElem.removeAttribute("hidden");
    
    252
    -          if (keyElem) {
    
    253
    -            keyElem.className = "invalid";
    
    254
    -          }
    
    255
    -        } else {
    
    256
    -          warningElem.setAttribute("hidden", "true");
    
    257
    -          if (keyElem) {
    
    258
    -            keyElem.className = "";
    
    259
    -          }
    
    260
    -        }
    
    261
    -      }
    
    262
    -    },
    
    263
    -
    
    264
    -    // Returns undefined if the key is the wrong length or format.
    
    265
    -    _keyToBase64(aKeyString) {
    
    266
    -      if (!aKeyString) {
    
    267
    -        return undefined;
    
    268
    -      }
    
    286
    +        rawKey = this._lazy.CommonUtils.decodeBase32(keyString.toUpperCase());
    
    287
    +      } catch (e) {}
    
    269 288
     
    
    270
    -      let base64key;
    
    271
    -      if (aKeyString.length == 52) {
    
    272
    -        // The key is probably base32-encoded. Attempt to decode.
    
    273
    -        // Although base32 specifies uppercase letters, we accept lowercase
    
    274
    -        // as well because users may type in lowercase or copy a key out of
    
    275
    -        // a tor onion-auth file (which uses lowercase).
    
    276
    -        let rawKey;
    
    289
    +      if (rawKey) {
    
    277 290
             try {
    
    278
    -          rawKey = lazy.CommonUtils.decodeBase32(aKeyString.toUpperCase());
    
    291
    +          base64key = btoa(rawKey);
    
    279 292
             } catch (e) {}
    
    280
    -
    
    281
    -        if (rawKey) {
    
    282
    -          try {
    
    283
    -            base64key = btoa(rawKey);
    
    284
    -          } catch (e) {}
    
    285
    -        }
    
    286
    -      } else if (
    
    287
    -        aKeyString.length == 44 &&
    
    288
    -        /^[a-zA-Z0-9+/]*=*$/.test(aKeyString)
    
    289
    -      ) {
    
    290
    -        // The key appears to be a correctly formatted base64 value. If not,
    
    291
    -        // tor will return an error when we try to add the key via the
    
    292
    -        // control port.
    
    293
    -        base64key = aKeyString;
    
    294 293
           }
    
    295
    -
    
    296
    -      return base64key;
    
    297
    -    },
    
    298
    -  };
    
    299
    -
    
    300
    -  let retval = {
    
    301
    -    init() {
    
    302
    -      Services.obs.addObserver(this, topics.clientAuthMissing);
    
    303
    -      Services.obs.addObserver(this, topics.clientAuthIncorrect);
    
    304
    -    },
    
    305
    -
    
    306
    -    uninit() {
    
    307
    -      Services.obs.removeObserver(this, topics.clientAuthMissing);
    
    308
    -      Services.obs.removeObserver(this, topics.clientAuthIncorrect);
    
    309
    -    },
    
    310
    -
    
    311
    -    // aSubject is the DOM Window or browser where the prompt should be shown.
    
    312
    -    // aData contains the .onion name.
    
    313
    -    observe(aSubject, aTopic, aData) {
    
    314
    -      if (
    
    315
    -        aTopic != topics.clientAuthMissing &&
    
    316
    -        aTopic != topics.clientAuthIncorrect
    
    317
    -      ) {
    
    318
    -        return;
    
    319
    -      }
    
    320
    -
    
    321
    -      let browser;
    
    322
    -      if (aSubject instanceof Ci.nsIDOMWindow) {
    
    323
    -        let contentWindow = aSubject.QueryInterface(Ci.nsIDOMWindow);
    
    324
    -        browser = contentWindow.docShell.chromeEventHandler;
    
    325
    -      } else {
    
    326
    -        browser = aSubject.QueryInterface(Ci.nsIBrowser);
    
    327
    -      }
    
    328
    -
    
    329
    -      if (!gBrowser.browsers.some(aBrowser => aBrowser == browser)) {
    
    330
    -        return; // This window does not contain the subject browser; ignore.
    
    294
    +    } else if (
    
    295
    +      keyString.length === 44 &&
    
    296
    +      /^[a-zA-Z0-9+/]*=*$/.test(keyString)
    
    297
    +    ) {
    
    298
    +      // The key appears to be a correctly formatted base64 value. If not,
    
    299
    +      // tor will return an error when we try to add the key via the
    
    300
    +      // control port.
    
    301
    +      base64key = keyString;
    
    302
    +    }
    
    303
    +
    
    304
    +    return base64key;
    
    305
    +  },
    
    306
    +
    
    307
    +  /**
    
    308
    +   * Initialize the authentication prompt.
    
    309
    +   */
    
    310
    +  init() {
    
    311
    +    this._logger = console.createInstance({
    
    312
    +      prefix: "OnionAuthPrompt",
    
    313
    +      maxLogLevel: "Warn",
    
    314
    +      maxLogLevelPref: "browser.onionAuthPrompt.loglevel",
    
    315
    +    });
    
    316
    +
    
    317
    +    const { TorStrings } = ChromeUtils.importESModule(
    
    318
    +      "resource://gre/modules/TorStrings.sys.mjs"
    
    319
    +    );
    
    320
    +    this.TorStrings = TorStrings;
    
    321
    +    ChromeUtils.defineESModuleGetters(this._lazy, {
    
    322
    +      TorProviderBuilder: "resource://gre/modules/TorProviderBuilder.sys.mjs",
    
    323
    +      CommonUtils: "resource://services-common/utils.sys.mjs",
    
    324
    +    });
    
    325
    +
    
    326
    +    this._keyInput = document.getElementById("tor-clientauth-notification-key");
    
    327
    +    this._persistCheckbox = document.getElementById(
    
    328
    +      "tor-clientauth-persistkey-checkbox"
    
    329
    +    );
    
    330
    +    this._warningEl = document.getElementById("tor-clientauth-warning");
    
    331
    +    this._descriptionEl = document.getElementById(
    
    332
    +      "tor-clientauth-notification-desc"
    
    333
    +    );
    
    334
    +
    
    335
    +    // Set "Learn More" label and href.
    
    336
    +    const learnMoreElem = document.getElementById(
    
    337
    +      "tor-clientauth-notification-learnmore"
    
    338
    +    );
    
    339
    +    learnMoreElem.setAttribute(
    
    340
    +      "value",
    
    341
    +      this.TorStrings.onionServices.learnMore
    
    342
    +    );
    
    343
    +
    
    344
    +    this._keyInput.setAttribute(
    
    345
    +      "placeholder",
    
    346
    +      this.TorStrings.onionServices.authPrompt.keyPlaceholder
    
    347
    +    );
    
    348
    +    this._keyInput.addEventListener("keydown", event => {
    
    349
    +      if (event.key === "Enter") {
    
    350
    +        event.preventDefault();
    
    351
    +        this._onDone();
    
    331 352
           }
    
    332
    -
    
    333
    -      let failedURI = browser.currentURI;
    
    334
    -      let authPrompt = new OnionServicesAuthPrompt(
    
    335
    -        browser,
    
    336
    -        failedURI,
    
    337
    -        aTopic,
    
    338
    -        aData
    
    353
    +    });
    
    354
    +    this._keyInput.addEventListener("input", event => {
    
    355
    +      // Remove the warning.
    
    356
    +      this._showWarning(undefined);
    
    357
    +    });
    
    358
    +
    
    359
    +    Services.obs.addObserver(this, this._topics.clientAuthMissing);
    
    360
    +    Services.obs.addObserver(this, this._topics.clientAuthIncorrect);
    
    361
    +  },
    
    362
    +
    
    363
    +  /**
    
    364
    +   * Un-initialize the authentication prompt.
    
    365
    +   */
    
    366
    +  uninit() {
    
    367
    +    Services.obs.removeObserver(this, this._topics.clientAuthMissing);
    
    368
    +    Services.obs.removeObserver(this, this._topics.clientAuthIncorrect);
    
    369
    +  },
    
    370
    +
    
    371
    +  observe(subject, topic, data) {
    
    372
    +    if (
    
    373
    +      topic !== this._topics.clientAuthMissing &&
    
    374
    +      topic !== this._topics.clientAuthIncorrect
    
    375
    +    ) {
    
    376
    +      return;
    
    377
    +    }
    
    378
    +
    
    379
    +    // "subject" is the DOM window or browser where the prompt should be shown.
    
    380
    +    let browser;
    
    381
    +    if (subject instanceof Ci.nsIDOMWindow) {
    
    382
    +      let contentWindow = subject.QueryInterface(Ci.nsIDOMWindow);
    
    383
    +      browser = contentWindow.docShell.chromeEventHandler;
    
    384
    +    } else {
    
    385
    +      browser = subject.QueryInterface(Ci.nsIBrowser);
    
    386
    +    }
    
    387
    +
    
    388
    +    if (!gBrowser.browsers.includes(browser)) {
    
    389
    +      // This window does not contain the subject browser.
    
    390
    +      this._logger.debug(
    
    391
    +        `Window ${window.docShell.outerWindowID}: Ignoring ${topic}`
    
    339 392
           );
    
    340
    -      authPrompt.show(undefined);
    
    341
    -    },
    
    342
    -  };
    
    343
    -
    
    344
    -  return retval;
    
    345
    -})(); /* OnionAuthPrompt */
    
    346
    -
    
    347
    -Object.defineProperty(this, "OnionAuthPrompt", {
    
    348
    -  value: OnionAuthPrompt,
    
    349
    -  enumerable: true,
    
    350
    -  writable: false,
    
    351
    -});
    393
    +      return;
    
    394
    +    }
    
    395
    +    this._logger.debug(
    
    396
    +      `Window ${window.docShell.outerWindowID}: Handling ${topic}`
    
    397
    +    );
    
    398
    +
    
    399
    +    const onionHost = data;
    
    400
    +    // ^(subdomain.)*onionserviceid.onion$ (case-insensitive)
    
    401
    +    const onionServiceId = onionHost
    
    402
    +      .match(/^(.*\.)?(?<onionServiceId>[a-z2-7]{56})\.onion$/i)
    
    403
    +      ?.groups.onionServiceId.toLowerCase();
    
    404
    +    if (!onionServiceId) {
    
    405
    +      this._logger.error(`Malformed onion address: ${onionHost}`);
    
    406
    +      return;
    
    407
    +    }
    
    408
    +
    
    409
    +    const details = {
    
    410
    +      browser,
    
    411
    +      cause: topic,
    
    412
    +      onionHost,
    
    413
    +      uri: browser.currentURI,
    
    414
    +      onionServiceId,
    
    415
    +    };
    
    416
    +    this.show(details);
    
    417
    +  },
    
    418
    +};

  • _______________________________________________
    tor-commits mailing list
    tor-commits@xxxxxxxxxxxxxxxxxxxx
    https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits