tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

ClientAuthDialogService.sys.mjs (4859B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 // ClientAuthDialogService implements nsIClientAuthDialogService, and aims to
      6 // open a dialog asking the user to select a client authentication certificate.
      7 // Ideally the dialog will be tab-modal to the tab corresponding to the load
      8 // that resulted in the request for the client authentication certificate.
      9 export function ClientAuthDialogService() {}
     10 
     11 const lazy = {};
     12 
     13 ChromeUtils.defineESModuleGetters(lazy, {
     14  PromptUtils: "resource://gre/modules/PromptUtils.sys.mjs",
     15 });
     16 
     17 import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
     18 
     19 if (AppConstants.platform == "android") {
     20  ChromeUtils.defineESModuleGetters(lazy, {
     21    GeckoViewPrompter: "resource://gre/modules/GeckoViewPrompter.sys.mjs",
     22  });
     23 }
     24 
     25 // Given a loadContext (CanonicalBrowsingContext), attempts to return a
     26 // TabDialogBox for the browser corresponding to loadContext.
     27 function getTabDialogBoxForLoadContext(loadContext) {
     28  let tabBrowser = loadContext?.topFrameElement?.getTabBrowser();
     29  if (!tabBrowser) {
     30    return null;
     31  }
     32  for (let browser of tabBrowser.browsers) {
     33    if (browser.browserId == loadContext.top?.browserId) {
     34      return tabBrowser.getTabDialogBox(browser);
     35    }
     36  }
     37  return null;
     38 }
     39 
     40 ClientAuthDialogService.prototype = {
     41  classID: Components.ID("{d7d2490d-2640-411b-9f09-a538803c11ee}"),
     42  QueryInterface: ChromeUtils.generateQI(["nsIClientAuthDialogService"]),
     43 
     44  chooseCertificate: function ClientAuthDialogService_chooseCertificate(
     45    hostname,
     46    certArray,
     47    loadContext,
     48    caNames,
     49    callback
     50  ) {
     51    // On Android, the OS implements the prompt. However, we have to plumb the
     52    // relevant information through to the frontend, which will return the
     53    // alias of the certificate, or null if none was selected.
     54    if (AppConstants.platform == "android") {
     55      const prompt = new lazy.GeckoViewPrompter(
     56        loadContext.topFrameElement.ownerGlobal
     57      );
     58      let issuers = null;
     59      if (caNames.length) {
     60        issuers = [];
     61        for (let caName of caNames) {
     62          issuers.push(btoa(caName.map(b => String.fromCharCode(b)).join("")));
     63        }
     64      }
     65      prompt.asyncShowPrompt(
     66        { type: "certificate", host: hostname, issuers },
     67        result => {
     68          let certDB = Cc["@mozilla.org/security/x509certdb;1"].getService(
     69            Ci.nsIX509CertDB
     70          );
     71          let certificate = null;
     72          if (result.alias) {
     73            try {
     74              certificate = certDB.getAndroidCertificateFromAlias(result.alias);
     75            } catch (e) {
     76              console.error("couldn't get certificate from alias", e);
     77            }
     78          }
     79          // The UI provided by the OS has no option to choose how long to
     80          // remember the decision. The most broadly useful default is to
     81          // remember the decision for the session.
     82          callback.certificateChosen(
     83            certificate,
     84            Ci.nsIClientAuthRememberService.Session
     85          );
     86        }
     87      );
     88 
     89      return;
     90    }
     91 
     92    const clientAuthAskURI = "chrome://pippki/content/clientauthask.xhtml";
     93    let retVals = {
     94      cert: null,
     95      rememberDuration: Ci.nsIClientAuthRememberService.Session,
     96    };
     97    let args = lazy.PromptUtils.objectToPropBag({
     98      hostname,
     99      certArray,
    100      retVals,
    101    });
    102 
    103    // First attempt to find a TabDialogBox for the loadContext. This allows
    104    // for a tab-modal dialog specific to the tab causing the load, which is a
    105    // better user experience.
    106    let tabDialogBox = getTabDialogBoxForLoadContext(loadContext);
    107    if (tabDialogBox) {
    108      tabDialogBox.open(clientAuthAskURI, {}, args).closedPromise.then(() => {
    109        callback.certificateChosen(retVals.cert, retVals.rememberDuration);
    110      });
    111      return;
    112    }
    113    // Otherwise, attempt to open a window-modal dialog on the window that at
    114    // least has the tab the load is occurring in.
    115    let browserWindow = loadContext?.topFrameElement?.ownerGlobal;
    116    // Failing that, open a window-modal dialog on the most recent window.
    117    if (!browserWindow?.gDialogBox) {
    118      browserWindow = Services.wm.getMostRecentBrowserWindow();
    119    }
    120 
    121    if (browserWindow?.gDialogBox) {
    122      browserWindow.gDialogBox.open(clientAuthAskURI, args).then(() => {
    123        callback.certificateChosen(retVals.cert, retVals.rememberDuration);
    124      });
    125      return;
    126    }
    127 
    128    let mostRecentWindow = Services.wm.getMostRecentWindow("");
    129    Services.ww.openWindow(
    130      mostRecentWindow,
    131      clientAuthAskURI,
    132      "_blank",
    133      "centerscreen,chrome,modal,titlebar",
    134      args
    135    );
    136    callback.certificateChosen(retVals.cert, retVals.rememberDuration);
    137  },
    138 };