tor-browser

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

test_be_conservative.js (7384B)


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