tor-browser

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

browser_clientAuthRememberService.js (8271B)


      1 // -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
      2 // Any copyright is dedicated to the Public Domain.
      3 // http://creativecommons.org/publicdomain/zero/1.0/
      4 "use strict";
      5 
      6 /**
      7 * Test certificate (i.e. build/pgo/certs/mochitest.client).
      8 *
      9 * @type {nsIX509Cert}
     10 */
     11 var cert;
     12 var cert2;
     13 var cert3;
     14 
     15 var sdr = Cc["@mozilla.org/security/sdr;1"].getService(Ci.nsISecretDecoderRing);
     16 var certDB = Cc["@mozilla.org/security/x509certdb;1"].getService(
     17  Ci.nsIX509CertDB
     18 );
     19 
     20 var deleted = false;
     21 
     22 const { MockRegistrar } = ChromeUtils.importESModule(
     23  "resource://testing-common/MockRegistrar.sys.mjs"
     24 );
     25 
     26 function findCertByCommonName(commonName) {
     27  for (let cert of certDB.getCerts()) {
     28    if (cert.commonName == commonName) {
     29      return cert;
     30    }
     31  }
     32  return null;
     33 }
     34 
     35 async function testHelper(connectURL, expectedURL) {
     36  let win = await BrowserTestUtils.openNewBrowserWindow();
     37 
     38  await SpecialPowers.pushPrefEnv({
     39    set: [["security.default_personal_cert", "Ask Every Time"]],
     40  });
     41 
     42  BrowserTestUtils.startLoadingURIString(
     43    win.gBrowser.selectedBrowser,
     44    connectURL
     45  );
     46 
     47  await BrowserTestUtils.browserLoaded(
     48    win.gBrowser.selectedBrowser,
     49    false,
     50    expectedURL,
     51    true
     52  );
     53  let loadedURL = win.gBrowser.selectedBrowser.documentURI.spec;
     54  Assert.ok(
     55    loadedURL.startsWith(expectedURL),
     56    `Expected and actual URLs should match (got '${loadedURL}', expected '${expectedURL}')`
     57  );
     58 
     59  await win.close();
     60 
     61  // This clears the TLS session cache so we don't use a previously-established
     62  // ticket to connect and bypass selecting a client auth certificate in
     63  // subsequent tests.
     64  sdr.logout();
     65 }
     66 
     67 async function openRequireClientCert() {
     68  gClientAuthDialogService.chooseCertificateCalled = false;
     69  await testHelper(
     70    "https://requireclientcert.example.com:443",
     71    "https://requireclientcert.example.com/"
     72  );
     73 }
     74 
     75 async function openRequireClientCert2() {
     76  gClientAuthDialogService.chooseCertificateCalled = false;
     77  await testHelper(
     78    "https://requireclientcert-2.example.com:443",
     79    "https://requireclientcert-2.example.com/"
     80  );
     81 }
     82 
     83 // Mock implementation of nsIClientAuthRememberService
     84 const gClientAuthRememberService = {
     85  forgetRememberedDecision(key) {
     86    deleted = true;
     87    Assert.equal(
     88      key,
     89      "exampleKey2",
     90      "Expected to get the same key that was passed in getDecisions()"
     91    );
     92  },
     93 
     94  getDecisions() {
     95    return [
     96      {
     97        asciiHost: "example.com",
     98        dbKey: cert.dbKey,
     99        entryKey: "exampleKey1",
    100      },
    101      {
    102        asciiHost: "example.org",
    103        dbKey: cert2.dbKey,
    104        entryKey: "exampleKey2",
    105      },
    106      {
    107        asciiHost: "example.test",
    108        dbKey: cert3.dbKey,
    109        entryKey: "exampleKey3",
    110      },
    111      {
    112        asciiHost: "unavailable.example.com",
    113        // This dbKey should not correspond to any real certificate. The first
    114        // 8 bytes have to be 0, followed by the lengths of the serial number
    115        // and issuer distinguished name, respectively, and then followed by
    116        // the bytes of the serial number and finally the encoded issuer
    117        // distinguished name. In this case, the serial number is a single 0
    118        // byte and the issuer distinguished name is a DER SEQUENCE of length 0
    119        // (the bytes 0x30 and 0).
    120        // See also the documentation in nsNSSCertificateDB::FindCertByDBKey.
    121        dbKey: "AAAAAAAAAAAAAAABAAAAAgAeAA==",
    122        entryKey: "exampleKey4",
    123      },
    124    ];
    125  },
    126 
    127  QueryInterface: ChromeUtils.generateQI(["nsIClientAuthRememberService"]),
    128 };
    129 
    130 const gClientAuthDialogService = {
    131  _chooseCertificateCalled: false,
    132 
    133  get chooseCertificateCalled() {
    134    return this._chooseCertificateCalled;
    135  },
    136 
    137  set chooseCertificateCalled(value) {
    138    this._chooseCertificateCalled = value;
    139  },
    140 
    141  chooseCertificate(hostname, certArray, loadContext, caNames, callback) {
    142    this.chooseCertificateCalled = true;
    143    callback.certificateChosen(certArray[0], true);
    144  },
    145 
    146  QueryInterface: ChromeUtils.generateQI([Ci.nsIClientAuthDialogService]),
    147 };
    148 
    149 add_task(async function testRememberedDecisionsUI() {
    150  cert = findCertByCommonName("Mochitest client");
    151  cert2 = await readCertificate("pgo-ca-all-usages.pem", ",,");
    152  cert3 = await readCertificate("client-cert-via-intermediate.pem", ",,");
    153  isnot(cert, null, "Should be able to find the test client cert");
    154  isnot(cert2, null, "Should be able to find pgo-ca-all-usages.pem");
    155  isnot(cert3, null, "Should be able to find client-cert-via-intermediate.pem");
    156 
    157  let clientAuthRememberServiceCID = MockRegistrar.register(
    158    "@mozilla.org/security/clientAuthRememberService;1",
    159    gClientAuthRememberService
    160  );
    161 
    162  let win = await openCertManager();
    163 
    164  let listItems = win.document
    165    .getElementById("rememberedList")
    166    .querySelectorAll("richlistitem");
    167 
    168  Assert.equal(
    169    listItems.length,
    170    4,
    171    "rememberedList has expected number of items"
    172  );
    173 
    174  let labels = win.document
    175    .getElementById("rememberedList")
    176    .querySelectorAll("label");
    177 
    178  Assert.equal(
    179    labels.length,
    180    12,
    181    "rememberedList has expected number of labels"
    182  );
    183 
    184  await BrowserTestUtils.waitForCondition(
    185    () => !!labels[10].textContent.length,
    186    "Localized label is populated"
    187  );
    188 
    189  let expectedHosts = [
    190    "example.com",
    191    "example.org",
    192    "example.test",
    193    "unavailable.example.com",
    194  ];
    195  let hosts = [
    196    labels[0].value,
    197    labels[3].value,
    198    labels[6].value,
    199    labels[9].value,
    200  ];
    201  let expectedNames = [
    202    cert.commonName,
    203    cert2.commonName,
    204    cert3.commonName,
    205    "(Unavailable)",
    206  ];
    207  let names = [
    208    labels[1].value,
    209    labels[4].value,
    210    labels[7].value,
    211    labels[10].textContent,
    212  ];
    213  let expectedSerialNumbers = [
    214    cert.serialNumber,
    215    cert2.serialNumber,
    216    cert3.serialNumber,
    217    "(Unavailable)",
    218  ];
    219  let serialNumbers = [
    220    labels[2].value,
    221    labels[5].value,
    222    labels[8].value,
    223    labels[11].textContent,
    224  ];
    225 
    226  for (let i = 0; i < listItems.length; i++) {
    227    Assert.equal(hosts[i], expectedHosts[i], "got expected asciiHost");
    228    Assert.equal(names[i], expectedNames[i], "got expected commonName");
    229    Assert.equal(
    230      serialNumbers[i],
    231      expectedSerialNumbers[i],
    232      "got expected serialNumber"
    233    );
    234  }
    235 
    236  win.document.getElementById("rememberedList").selectedIndex = 1;
    237  win.document.getElementById("remembered_deleteButton").click();
    238 
    239  Assert.ok(deleted, "Expected forgetRememberedDecision() to get called");
    240 
    241  win.document.getElementById("certmanager").acceptDialog();
    242  await BrowserTestUtils.windowClosed(win);
    243 
    244  MockRegistrar.unregister(clientAuthRememberServiceCID);
    245 });
    246 
    247 add_task(async function testDeletingRememberedDecisions() {
    248  let clientAuthDialogServiceCID = MockRegistrar.register(
    249    "@mozilla.org/security/ClientAuthDialogService;1",
    250    gClientAuthDialogService
    251  );
    252  let cars = Cc["@mozilla.org/security/clientAuthRememberService;1"].getService(
    253    Ci.nsIClientAuthRememberService
    254  );
    255 
    256  await openRequireClientCert();
    257  Assert.ok(
    258    gClientAuthDialogService.chooseCertificateCalled,
    259    "chooseCertificate should have been called if visiting 'requireclientcert.example.com' for the first time"
    260  );
    261 
    262  await openRequireClientCert();
    263  Assert.ok(
    264    !gClientAuthDialogService.chooseCertificateCalled,
    265    "chooseCertificate should not have been called if visiting 'requireclientcert.example.com' for the second time"
    266  );
    267 
    268  await openRequireClientCert2();
    269  Assert.ok(
    270    gClientAuthDialogService.chooseCertificateCalled,
    271    "chooseCertificate should have been called if visiting 'requireclientcert-2.example.com' for the first time"
    272  );
    273 
    274  let originAttributes = { privateBrowsingId: 0 };
    275  cars.deleteDecisionsByHost("requireclientcert.example.com", originAttributes);
    276 
    277  await openRequireClientCert();
    278  Assert.ok(
    279    gClientAuthDialogService.chooseCertificateCalled,
    280    "chooseCertificate should have been called after removing all remembered decisions for 'requireclientcert.example.com'"
    281  );
    282 
    283  await openRequireClientCert2();
    284  Assert.ok(
    285    !gClientAuthDialogService.chooseCertificateCalled,
    286    "chooseCertificate should not have been called if visiting 'requireclientcert-2.example.com' for the second time"
    287  );
    288 
    289  MockRegistrar.unregister(clientAuthDialogServiceCID);
    290 });