tor-browser

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

test_logoutAndTeardown.js (5332B)


      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 
      5 "use strict";
      6 
      7 // This test ensures that in-progress https connections are cancelled when the
      8 // user logs out of a PKCS#11 token.
      9 
     10 // Get a profile directory and ensure PSM initializes NSS.
     11 do_get_profile();
     12 Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports);
     13 
     14 function getTestServerCertificate() {
     15  const certDB = Cc["@mozilla.org/security/x509certdb;1"].getService(
     16    Ci.nsIX509CertDB
     17  );
     18  const certFile = do_get_file("test_certDB_import/encrypted_with_aes.p12");
     19  certDB.importPKCS12File(certFile, "password");
     20  for (const cert of certDB.getCerts()) {
     21    if (cert.commonName == "John Doe") {
     22      return cert;
     23    }
     24  }
     25  return null;
     26 }
     27 
     28 class InputStreamCallback {
     29  constructor(output) {
     30    this.output = output;
     31    this.stopped = false;
     32  }
     33 
     34  onInputStreamReady(stream) {
     35    info("input stream ready");
     36    if (this.stopped) {
     37      info("input stream callback stopped - bailing");
     38      return;
     39    }
     40    let available = 0;
     41    try {
     42      available = stream.available();
     43    } catch (e) {
     44      // onInputStreamReady may fire when the stream has been closed.
     45      equal(
     46        e.result,
     47        Cr.NS_BASE_STREAM_CLOSED,
     48        "error should be NS_BASE_STREAM_CLOSED"
     49      );
     50    }
     51    if (available > 0) {
     52      let request = NetUtil.readInputStreamToString(stream, available, {
     53        charset: "utf8",
     54      });
     55      ok(
     56        request.startsWith("GET / HTTP/1.1\r\n"),
     57        "Should get a simple GET / HTTP/1.1 request"
     58      );
     59      let response = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\n";
     60      this.output.write(response, response.length);
     61      // Keep writing a response until the client disconnects due to the
     62      // logoutAndTeardown. If the client never disconnects, the test will time
     63      // out, indicating a bug.
     64      while (true) {
     65        this.output.write("a", 1);
     66      }
     67    }
     68    this.output.close();
     69    info("done with input stream ready");
     70  }
     71 
     72  stop() {
     73    this.stopped = true;
     74    this.output.close();
     75  }
     76 }
     77 
     78 class TLSServerSecurityObserver {
     79  constructor(input, output) {
     80    this.input = input;
     81    this.output = output;
     82    this.callbacks = [];
     83    this.stopped = false;
     84  }
     85 
     86  onHandshakeDone(socket, status) {
     87    info("TLS handshake done");
     88    info(`TLS version used: ${status.tlsVersionUsed}`);
     89 
     90    if (this.stopped) {
     91      info("handshake done callback stopped - bailing");
     92      return;
     93    }
     94 
     95    let callback = new InputStreamCallback(this.output);
     96    this.callbacks.push(callback);
     97    this.input.asyncWait(callback, 0, 0, Services.tm.currentThread);
     98 
     99    // We've set up everything needed for a successful request/response,
    100    // but calling logoutAndTeardown should cause the request to be cancelled.
    101    Cc["@mozilla.org/security/sdr;1"]
    102      .getService(Ci.nsISecretDecoderRing)
    103      .logoutAndTeardown();
    104  }
    105 
    106  stop() {
    107    this.stopped = true;
    108    this.input.close();
    109    this.output.close();
    110    this.callbacks.forEach(callback => {
    111      callback.stop();
    112    });
    113  }
    114 }
    115 
    116 class ServerSocketListener {
    117  constructor() {
    118    this.securityObservers = [];
    119  }
    120 
    121  onSocketAccepted(socket, transport) {
    122    info("accepted TLS client connection");
    123    let connectionInfo = transport.securityCallbacks.getInterface(
    124      Ci.nsITLSServerConnectionInfo
    125    );
    126    let input = transport.openInputStream(0, 0, 0);
    127    let output = transport.openOutputStream(0, 0, 0);
    128    let securityObserver = new TLSServerSecurityObserver(input, output);
    129    this.securityObservers.push(securityObserver);
    130    connectionInfo.setSecurityObserver(securityObserver);
    131  }
    132 
    133  // For some reason we get input stream callback events after we've stopped
    134  // listening, so this ensures we just drop those events.
    135  onStopListening() {
    136    info("onStopListening");
    137    this.securityObservers.forEach(observer => {
    138      observer.stop();
    139    });
    140  }
    141 }
    142 
    143 function getStartedServer(cert) {
    144  let tlsServer = Cc["@mozilla.org/network/tls-server-socket;1"].createInstance(
    145    Ci.nsITLSServerSocket
    146  );
    147  tlsServer.init(-1, true, -1);
    148  tlsServer.serverCert = cert;
    149  tlsServer.setSessionTickets(false);
    150  tlsServer.asyncListen(new ServerSocketListener());
    151  return tlsServer;
    152 }
    153 
    154 const hostname = "example.com";
    155 
    156 function storeCertOverride(port, cert) {
    157  let certOverrideService = Cc[
    158    "@mozilla.org/security/certoverride;1"
    159  ].getService(Ci.nsICertOverrideService);
    160  certOverrideService.rememberValidityOverride(hostname, port, {}, cert, true);
    161 }
    162 
    163 function startClient(port) {
    164  let req = new XMLHttpRequest();
    165  req.open("GET", `https://${hostname}:${port}`);
    166  return new Promise(resolve => {
    167    req.onload = () => {
    168      ok(false, "should not have gotten load event");
    169      resolve();
    170    };
    171    req.onerror = () => {
    172      ok(true, "should have gotten an error");
    173      resolve();
    174    };
    175 
    176    req.send();
    177  });
    178 }
    179 
    180 add_task(async function () {
    181  Services.prefs.setCharPref("network.dns.localDomains", hostname);
    182  let cert = getTestServerCertificate();
    183 
    184  let server = getStartedServer(cert);
    185  storeCertOverride(server.port, cert);
    186  await startClient(server.port);
    187  server.close();
    188 });
    189 
    190 registerCleanupFunction(function () {
    191  Services.prefs.clearUserPref("network.dns.localDomains");
    192 });