tor-browser

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

test_be_conservative_error_handling.js (6171B)


      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 // Tests that nsIHttpChannelInternal.beConservative correctly limits the use of
      8 // advanced TLS features that may cause compatibility issues. Does so by
      9 // starting a TLS server that requires the advanced features and then ensuring
     10 // that a client that is set to be conservative will fail when connecting.
     11 
     12 // Get a profile directory and ensure PSM initializes NSS.
     13 do_get_profile();
     14 Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports);
     15 
     16 class InputStreamCallback {
     17  constructor(output) {
     18    this.output = output;
     19    this.stopped = false;
     20  }
     21 
     22  onInputStreamReady(stream) {
     23    info("input stream ready");
     24    if (this.stopped) {
     25      info("input stream callback stopped - bailing");
     26      return;
     27    }
     28    let available = 0;
     29    try {
     30      available = stream.available();
     31    } catch (e) {
     32      // onInputStreamReady may fire when the stream has been closed.
     33      equal(
     34        e.result,
     35        Cr.NS_BASE_STREAM_CLOSED,
     36        "error should be NS_BASE_STREAM_CLOSED"
     37      );
     38    }
     39    if (available > 0) {
     40      let request = NetUtil.readInputStreamToString(stream, available, {
     41        charset: "utf8",
     42      });
     43      ok(
     44        request.startsWith("GET / HTTP/1.1\r\n"),
     45        "Should get a simple GET / HTTP/1.1 request"
     46      );
     47      let response =
     48        "HTTP/1.1 200 OK\r\n" +
     49        "Content-Length: 2\r\n" +
     50        "Content-Type: text/plain\r\n" +
     51        "\r\nOK";
     52      let written = this.output.write(response, response.length);
     53      equal(
     54        written,
     55        response.length,
     56        "should have been able to write entire response"
     57      );
     58    }
     59    this.output.close();
     60    info("done with input stream ready");
     61  }
     62 
     63  stop() {
     64    this.stopped = true;
     65    this.output.close();
     66  }
     67 }
     68 
     69 class TLSServerSecurityObserver {
     70  constructor(input, output) {
     71    this.input = input;
     72    this.output = output;
     73    this.callbacks = [];
     74    this.stopped = false;
     75  }
     76 
     77  onHandshakeDone(socket, status) {
     78    info("TLS handshake done");
     79    info(`TLS version used: ${status.tlsVersionUsed}`);
     80 
     81    if (this.stopped) {
     82      info("handshake done callback stopped - bailing");
     83      return;
     84    }
     85 
     86    let callback = new InputStreamCallback(this.output);
     87    this.callbacks.push(callback);
     88    this.input.asyncWait(callback, 0, 0, Services.tm.currentThread);
     89  }
     90 
     91  stop() {
     92    this.stopped = true;
     93    this.input.close();
     94    this.output.close();
     95    this.callbacks.forEach(callback => {
     96      callback.stop();
     97    });
     98  }
     99 }
    100 
    101 class ServerSocketListener {
    102  constructor() {
    103    this.securityObservers = [];
    104  }
    105 
    106  onSocketAccepted(socket, transport) {
    107    info("accepted TLS client connection");
    108    let connectionInfo = transport.securityCallbacks.getInterface(
    109      Ci.nsITLSServerConnectionInfo
    110    );
    111    let input = transport.openInputStream(0, 0, 0);
    112    let output = transport.openOutputStream(0, 0, 0);
    113    let securityObserver = new TLSServerSecurityObserver(input, output);
    114    this.securityObservers.push(securityObserver);
    115    connectionInfo.setSecurityObserver(securityObserver);
    116  }
    117 
    118  // For some reason we get input stream callback events after we've stopped
    119  // listening, so this ensures we just drop those events.
    120  onStopListening() {
    121    info("onStopListening");
    122    this.securityObservers.forEach(observer => {
    123      observer.stop();
    124    });
    125  }
    126 }
    127 
    128 function startServer(cert, minServerVersion, maxServerVersion) {
    129  let tlsServer = Cc["@mozilla.org/network/tls-server-socket;1"].createInstance(
    130    Ci.nsITLSServerSocket
    131  );
    132  tlsServer.init(-1, true, -1);
    133  tlsServer.serverCert = cert;
    134  tlsServer.setVersionRange(minServerVersion, maxServerVersion);
    135  tlsServer.setSessionTickets(false);
    136  tlsServer.asyncListen(new ServerSocketListener());
    137  return tlsServer;
    138 }
    139 
    140 const hostname = "example.com";
    141 
    142 function storeCertOverride(port, cert) {
    143  let certOverrideService = Cc[
    144    "@mozilla.org/security/certoverride;1"
    145  ].getService(Ci.nsICertOverrideService);
    146  certOverrideService.rememberValidityOverride(hostname, port, {}, cert, true);
    147 }
    148 
    149 function startClient(port, beConservative, expectSuccess) {
    150  let req = new XMLHttpRequest();
    151  req.open("GET", `https://${hostname}:${port}`);
    152  let internalChannel = req.channel.QueryInterface(Ci.nsIHttpChannelInternal);
    153  internalChannel.beConservative = beConservative;
    154  return new Promise(resolve => {
    155    req.onload = () => {
    156      ok(
    157        expectSuccess,
    158        `should ${expectSuccess ? "" : "not "}have gotten load event`
    159      );
    160      equal(req.responseText, "OK", "response text should be 'OK'");
    161      resolve();
    162    };
    163    req.onerror = () => {
    164      ok(
    165        !expectSuccess,
    166        `should ${!expectSuccess ? "" : "not "}have gotten an error`
    167      );
    168      resolve();
    169    };
    170 
    171    req.send();
    172  });
    173 }
    174 
    175 add_task(async function () {
    176  // Restrict to only TLS 1.3.
    177  Services.prefs.setIntPref("security.tls.version.min", 4);
    178  Services.prefs.setIntPref("security.tls.version.max", 4);
    179  Services.prefs.setCharPref("network.dns.localDomains", hostname);
    180  let cert = getTestServerCertificate();
    181 
    182  // Run a server that accepts TLS 1.2 and 1.3. The connection should succeed.
    183  let server = startServer(
    184    cert,
    185    Ci.nsITLSClientStatus.TLS_VERSION_1_2,
    186    Ci.nsITLSClientStatus.TLS_VERSION_1_3
    187  );
    188  storeCertOverride(server.port, cert);
    189  await startClient(
    190    server.port,
    191    true /*be conservative*/,
    192    true /*should succeed*/
    193  );
    194  server.close();
    195 
    196  // Now run a server that only accepts TLS 1.3. A conservative client will not
    197  // succeed in this case.
    198  server = startServer(
    199    cert,
    200    Ci.nsITLSClientStatus.TLS_VERSION_1_3,
    201    Ci.nsITLSClientStatus.TLS_VERSION_1_3
    202  );
    203  storeCertOverride(server.port, cert);
    204  await startClient(
    205    server.port,
    206    true /*be conservative*/,
    207    false /*should fail*/
    208  );
    209  server.close();
    210 });
    211 
    212 registerCleanupFunction(function () {
    213  Services.prefs.clearUserPref("security.tls.version.min");
    214  Services.prefs.clearUserPref("security.tls.version.max");
    215  Services.prefs.clearUserPref("network.dns.localDomains");
    216 });