tor-browser

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

test_ech_grease.js (7475B)


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