tor-browser

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

test_httpssvc_retry_with_ech.js (14381B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 "use strict";
      6 
      7 var { setTimeout } = ChromeUtils.importESModule(
      8  "resource://gre/modules/Timer.sys.mjs"
      9 );
     10 
     11 let trrServer;
     12 let h3Port;
     13 let h3EchConfig;
     14 
     15 const certOverrideService = Cc[
     16  "@mozilla.org/security/certoverride;1"
     17 ].getService(Ci.nsICertOverrideService);
     18 
     19 function checkSecurityInfo(chan, expectPrivateDNS, expectAcceptedECH) {
     20  let securityInfo = chan.securityInfo;
     21  Assert.equal(
     22    securityInfo.isAcceptedEch,
     23    expectAcceptedECH,
     24    "ECH Status == Expected Status"
     25  );
     26  Assert.equal(
     27    securityInfo.usedPrivateDNS,
     28    expectPrivateDNS,
     29    "Private DNS Status == Expected Status"
     30  );
     31 }
     32 
     33 add_setup(async function setup() {
     34  // Allow telemetry probes which may otherwise be disabled for some
     35  // applications (e.g. Thunderbird).
     36  Services.prefs.setBoolPref(
     37    "toolkit.telemetry.testing.overrideProductsCheck",
     38    true
     39  );
     40 
     41  trrServer = new TRRServer();
     42  await trrServer.start();
     43  trr_test_setup();
     44 
     45  Services.prefs.setBoolPref("network.dns.upgrade_with_https_rr", true);
     46  Services.prefs.setBoolPref("network.dns.use_https_rr_as_altsvc", true);
     47  Services.prefs.setBoolPref("network.dns.echconfig.enabled", true);
     48  Services.prefs.setBoolPref("network.dns.http3_echconfig.enabled", true);
     49  Services.prefs.setIntPref("network.http.speculative-parallel-limit", 0);
     50  Services.prefs.setIntPref("network.trr.mode", Ci.nsIDNSService.MODE_TRRONLY);
     51 
     52  await asyncStartTLSTestServer(
     53    "EncryptedClientHelloServer",
     54    "../../../security/manager/ssl/tests/unit/test_encrypted_client_hello"
     55  );
     56 
     57  h3Port = Services.env.get("MOZHTTP3_PORT_ECH");
     58  Assert.notEqual(h3Port, null);
     59  Assert.notEqual(h3Port, "");
     60 
     61  h3EchConfig = Services.env.get("MOZHTTP3_ECH");
     62  Assert.notEqual(h3EchConfig, null);
     63  Assert.notEqual(h3EchConfig, "");
     64 
     65  registerCleanupFunction(async () => {
     66    trr_clear_prefs();
     67    Services.prefs.clearUserPref("network.trr.mode");
     68    Services.prefs.clearUserPref("network.trr.uri");
     69    Services.prefs.clearUserPref("network.dns.upgrade_with_https_rr");
     70    Services.prefs.clearUserPref("network.dns.use_https_rr_as_altsvc");
     71    Services.prefs.clearUserPref("network.dns.echconfig.enabled");
     72    Services.prefs.clearUserPref("network.dns.http3_echconfig.enabled");
     73    Services.prefs.clearUserPref(
     74      "network.dns.echconfig.fallback_to_origin_when_all_failed"
     75    );
     76    Services.prefs.clearUserPref("network.http.speculative-parallel-limit");
     77    Services.prefs.clearUserPref("network.dns.port_prefixed_qname_https_rr");
     78    Services.prefs.clearUserPref("security.tls.ech.grease_http3");
     79    Services.prefs.clearUserPref("security.tls.ech.grease_probability");
     80    if (trrServer) {
     81      await trrServer.stop();
     82    }
     83  });
     84 });
     85 
     86 function makeChan(url) {
     87  let chan = NetUtil.newChannel({
     88    uri: url,
     89    loadUsingSystemPrincipal: true,
     90    contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
     91  }).QueryInterface(Ci.nsIHttpChannel);
     92  return chan;
     93 }
     94 
     95 function channelOpenPromise(chan, flags) {
     96  return new Promise(resolve => {
     97    function finish(req, buffer) {
     98      certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
     99        false
    100      );
    101      resolve([req, buffer]);
    102    }
    103    certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
    104      true
    105    );
    106    chan.asyncOpen(new ChannelListener(finish, null, flags));
    107  });
    108 }
    109 
    110 function ActivityObserver() {}
    111 
    112 ActivityObserver.prototype = {
    113  activites: [],
    114  observeConnectionActivity(
    115    aHost,
    116    aPort,
    117    aSSL,
    118    aHasECH,
    119    aIsHttp3,
    120    aActivityType,
    121    aActivitySubtype,
    122    aTimestamp,
    123    aExtraStringData
    124  ) {
    125    dump(
    126      "*** Connection Activity 0x" +
    127        aActivityType.toString(16) +
    128        " 0x" +
    129        aActivitySubtype.toString(16) +
    130        " " +
    131        aExtraStringData +
    132        "\n"
    133    );
    134    this.activites.push({ host: aHost, subType: aActivitySubtype });
    135  },
    136 };
    137 
    138 function checkHttpActivities(activites, expectECH) {
    139  let foundDNSAndSocket = false;
    140  let foundSettingECH = false;
    141  let foundConnectionCreated = false;
    142  for (let activity of activites) {
    143    switch (activity.subType) {
    144      case Ci.nsIHttpActivityObserver.ACTIVITY_SUBTYPE_DNSANDSOCKET_CREATED:
    145      case Ci.nsIHttpActivityObserver
    146        .ACTIVITY_SUBTYPE_SPECULATIVE_DNSANDSOCKET_CREATED:
    147        foundDNSAndSocket = true;
    148        break;
    149      case Ci.nsIHttpActivityDistributor.ACTIVITY_SUBTYPE_ECH_SET:
    150        foundSettingECH = true;
    151        break;
    152      case Ci.nsIHttpActivityDistributor.ACTIVITY_SUBTYPE_CONNECTION_CREATED:
    153        foundConnectionCreated = true;
    154        break;
    155      default:
    156        break;
    157    }
    158  }
    159 
    160  Assert.equal(foundDNSAndSocket, true, "Should have one DnsAndSock created");
    161  Assert.equal(foundSettingECH, expectECH, "Should have echConfig");
    162  Assert.equal(
    163    foundConnectionCreated,
    164    true,
    165    "Should have one connection created"
    166  );
    167 }
    168 
    169 add_task(async function testConnectWithECH() {
    170  const ECH_CONFIG_FIXED =
    171    "AEn+DQBFTQAgACCKB1Y5SfrGIyk27W82xPpzWTDs3q72c04xSurDWlb9CgAEAAEAA2QWZWNoLXB1YmxpYy5leGFtcGxlLmNvbQAA";
    172  trrServer = new TRRServer();
    173  await trrServer.start();
    174 
    175  let observerService = Cc[
    176    "@mozilla.org/network/http-activity-distributor;1"
    177  ].getService(Ci.nsIHttpActivityDistributor);
    178  let observer = new ActivityObserver();
    179  observerService.addObserver(observer);
    180  observerService.observeConnection = true;
    181 
    182  Services.prefs.setCharPref(
    183    "network.trr.uri",
    184    `https://foo.example.com:${trrServer.port()}/dns-query`
    185  );
    186 
    187  // Only the last record is valid to use.
    188  await trrServer.registerDoHAnswers("ech-private.example.com", "HTTPS", {
    189    answers: [
    190      {
    191        name: "ech-private.example.com",
    192        ttl: 55,
    193        type: "HTTPS",
    194        flush: false,
    195        data: {
    196          priority: 1,
    197          name: "ech-private.example.com",
    198          values: [
    199            { key: "alpn", value: "http/1.1" },
    200            { key: "port", value: 8443 },
    201            {
    202              key: "echconfig",
    203              value: ECH_CONFIG_FIXED,
    204              needBase64Decode: true,
    205            },
    206          ],
    207        },
    208      },
    209    ],
    210  });
    211 
    212  await trrServer.registerDoHAnswers("ech-private.example.com", "A", {
    213    answers: [
    214      {
    215        name: "ech-private.example.com",
    216        ttl: 55,
    217        type: "A",
    218        flush: false,
    219        data: "127.0.0.1",
    220      },
    221    ],
    222  });
    223 
    224  await new TRRDNSListener("ech-private.example.com", {
    225    type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
    226  });
    227 
    228  HandshakeTelemetryHelpers.resetHistograms();
    229  let chan = makeChan(`https://ech-private.example.com`);
    230  await channelOpenPromise(chan, CL_ALLOW_UNKNOWN_CL);
    231  checkSecurityInfo(chan, true, true);
    232  // Only check telemetry if network process is disabled.
    233  if (!mozinfo.socketprocess_networking) {
    234    HandshakeTelemetryHelpers.checkSuccess(["", "_ECH", "_FIRST_TRY"]);
    235    HandshakeTelemetryHelpers.checkEmpty(["_CONSERVATIVE", "_ECH_GREASE"]);
    236  }
    237 
    238  observerService.removeObserver(observer);
    239  observerService.observeConnection = false;
    240 
    241  let filtered = observer.activites.filter(
    242    activity => activity.host === "ech-private.example.com"
    243  );
    244  checkHttpActivities(filtered, true);
    245 });
    246 
    247 add_task(async function testEchRetry() {
    248  Services.obs.notifyObservers(null, "net:cancel-all-connections");
    249  // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
    250  await new Promise(resolve => setTimeout(resolve, 1000));
    251 
    252  Services.dns.clearCache(true);
    253 
    254  const ECH_CONFIG_TRUSTED_RETRY =
    255    "AEn+DQBFTQAgACCKB1Y5SfrGIyk27W82xPpzWTDs3q72c04xSurDWlb9CgAEAAMAA2QWZWNoLXB1YmxpYy5leGFtcGxlLmNvbQAA";
    256  Services.prefs.setIntPref("network.trr.mode", 3);
    257  Services.prefs.setCharPref(
    258    "network.trr.uri",
    259    `https://foo.example.com:${trrServer.port()}/dns-query`
    260  );
    261 
    262  // Only the last record is valid to use.
    263  await trrServer.registerDoHAnswers("ech-private.example.com", "HTTPS", {
    264    answers: [
    265      {
    266        name: "ech-private.example.com",
    267        ttl: 55,
    268        type: "HTTPS",
    269        flush: false,
    270        data: {
    271          priority: 1,
    272          name: "ech-private.example.com",
    273          values: [
    274            { key: "alpn", value: "http/1.1" },
    275            { key: "port", value: 8443 },
    276            {
    277              key: "echconfig",
    278              value: ECH_CONFIG_TRUSTED_RETRY,
    279              needBase64Decode: true,
    280            },
    281          ],
    282        },
    283      },
    284    ],
    285  });
    286 
    287  await trrServer.registerDoHAnswers("ech-private.example.com", "A", {
    288    answers: [
    289      {
    290        name: "ech-private.example.com",
    291        ttl: 55,
    292        type: "A",
    293        flush: false,
    294        data: "127.0.0.1",
    295      },
    296    ],
    297  });
    298 
    299  await new TRRDNSListener("ech-private.example.com", {
    300    type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
    301  });
    302 
    303  Services.prefs.setBoolPref("network.dns.echconfig.enabled", true);
    304 
    305  HandshakeTelemetryHelpers.resetHistograms();
    306  let chan = makeChan(`https://ech-private.example.com`);
    307  await channelOpenPromise(chan, CL_ALLOW_UNKNOWN_CL);
    308  checkSecurityInfo(chan, true, true);
    309  // Only check telemetry if network process is disabled.
    310  if (!mozinfo.socketprocess_networking) {
    311    for (let hName of ["SSL_HANDSHAKE_RESULT", "SSL_HANDSHAKE_RESULT_ECH"]) {
    312      let h = Services.telemetry.getHistogramById(hName);
    313      HandshakeTelemetryHelpers.assertHistogramMap(
    314        h.snapshot(),
    315        new Map([
    316          ["0", 1],
    317          ["188", 1],
    318        ])
    319      );
    320    }
    321    HandshakeTelemetryHelpers.checkEntry(["_FIRST_TRY"], 188, 1);
    322    HandshakeTelemetryHelpers.checkEmpty(["_CONSERVATIVE", "_ECH_GREASE"]);
    323  }
    324 });
    325 
    326 async function H3ECHTest(
    327  echConfig,
    328  expectedHistKey,
    329  expectedHistEntries,
    330  advertiseECH
    331 ) {
    332  Services.dns.clearCache(true);
    333  Services.obs.notifyObservers(null, "net:cancel-all-connections");
    334  /* eslint-disable mozilla/no-arbitrary-setTimeout */
    335  await new Promise(resolve => setTimeout(resolve, 1000));
    336  resetEchTelemetry();
    337 
    338  Services.prefs.setCharPref(
    339    "network.trr.uri",
    340    `https://foo.example.com:${trrServer.port()}/dns-query`
    341  );
    342  Services.prefs.setBoolPref("network.dns.port_prefixed_qname_https_rr", true);
    343 
    344  let observerService = Cc[
    345    "@mozilla.org/network/http-activity-distributor;1"
    346  ].getService(Ci.nsIHttpActivityDistributor);
    347  Services.obs.notifyObservers(null, "net:cancel-all-connections");
    348  let observer = new ActivityObserver();
    349  observerService.addObserver(observer);
    350  observerService.observeConnection = true;
    351  // Clear activities for past connections
    352  observer.activites = [];
    353 
    354  let portPrefixedName = `_${h3Port}._https.public.example.com`;
    355  let vals = [
    356    { key: "alpn", value: "h3" },
    357    { key: "port", value: h3Port },
    358  ];
    359  if (advertiseECH) {
    360    vals.push({
    361      key: "echconfig",
    362      value: echConfig,
    363      needBase64Decode: true,
    364    });
    365  }
    366  // Only the last record is valid to use.
    367 
    368  await trrServer.registerDoHAnswers(portPrefixedName, "HTTPS", {
    369    answers: [
    370      {
    371        name: portPrefixedName,
    372        ttl: 55,
    373        type: "HTTPS",
    374        flush: false,
    375        data: {
    376          priority: 1,
    377          name: ".",
    378          values: vals,
    379        },
    380      },
    381    ],
    382  });
    383 
    384  await trrServer.registerDoHAnswers("public.example.com", "A", {
    385    answers: [
    386      {
    387        name: "public.example.com",
    388        ttl: 55,
    389        type: "A",
    390        flush: false,
    391        data: "127.0.0.1",
    392      },
    393    ],
    394  });
    395 
    396  await new TRRDNSListener("public.example.com", {
    397    type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
    398    port: h3Port,
    399  });
    400 
    401  let chan = makeChan(`https://public.example.com:${h3Port}`);
    402  let [req] = await channelOpenPromise(chan, CL_ALLOW_UNKNOWN_CL);
    403  req.QueryInterface(Ci.nsIHttpChannel);
    404  Assert.equal(req.protocolVersion, "h3");
    405  checkSecurityInfo(chan, true, advertiseECH);
    406 
    407  observerService.removeObserver(observer);
    408  observerService.observeConnection = false;
    409 
    410  let filtered = observer.activites.filter(
    411    activity => activity.host === "public.example.com"
    412  );
    413  checkHttpActivities(filtered, advertiseECH);
    414  await checkEchTelemetry(expectedHistKey, expectedHistEntries);
    415 }
    416 
    417 function resetEchTelemetry() {
    418  Services.telemetry.getKeyedHistogramById("HTTP3_ECH_OUTCOME").clear();
    419 }
    420 
    421 async function checkEchTelemetry(histKey, histEntries) {
    422  Services.obs.notifyObservers(null, "net:cancel-all-connections");
    423  /* eslint-disable mozilla/no-arbitrary-setTimeout */
    424  await new Promise(resolve => setTimeout(resolve, 1000));
    425  let values = Services.telemetry
    426    .getKeyedHistogramById("HTTP3_ECH_OUTCOME")
    427    .snapshot()[histKey];
    428  if (!mozinfo.socketprocess_networking) {
    429    HandshakeTelemetryHelpers.assertHistogramMap(values, histEntries);
    430  }
    431 }
    432 
    433 add_task(async function testH3WithNoEch() {
    434  Services.prefs.setBoolPref("security.tls.ech.grease_http3", false);
    435  Services.prefs.setIntPref("security.tls.ech.grease_probability", 0);
    436  await H3ECHTest(
    437    h3EchConfig,
    438    "NONE",
    439    new Map([
    440      ["0", 1],
    441      ["1", 0],
    442    ]),
    443    false
    444  );
    445 });
    446 
    447 add_task(async function testH3WithECH() {
    448  await H3ECHTest(
    449    h3EchConfig,
    450    "REAL",
    451    new Map([
    452      ["0", 1],
    453      ["1", 0],
    454    ]),
    455    true
    456  );
    457 });
    458 
    459 add_task(async function testH3WithGreaseEch() {
    460  Services.prefs.setBoolPref("security.tls.ech.grease_http3", true);
    461  Services.prefs.setIntPref("security.tls.ech.grease_probability", 100);
    462  await H3ECHTest(
    463    h3EchConfig,
    464    "GREASE",
    465    new Map([
    466      ["0", 1],
    467      ["1", 0],
    468    ]),
    469    false
    470  );
    471 });
    472 
    473 add_task(async function testH3WithECHRetry() {
    474  Services.dns.clearCache(true);
    475  Services.obs.notifyObservers(null, "net:cancel-all-connections");
    476  // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
    477  await new Promise(resolve => setTimeout(resolve, 1000));
    478 
    479  function base64ToArray(base64) {
    480    var binary_string = atob(base64);
    481    var len = binary_string.length;
    482    var bytes = new Uint8Array(len);
    483    for (var i = 0; i < len; i++) {
    484      bytes[i] = binary_string.charCodeAt(i);
    485    }
    486    return bytes;
    487  }
    488 
    489  let decodedConfig = base64ToArray(h3EchConfig);
    490  decodedConfig[6] ^= 0x94;
    491  let encoded = btoa(String.fromCharCode.apply(null, decodedConfig));
    492  await H3ECHTest(
    493    encoded,
    494    "REAL",
    495    new Map([
    496      ["0", 1],
    497      ["1", 1],
    498    ]),
    499    true
    500  );
    501 });