tor-browser

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

test_httpssvc_https_upgrade.js (11890B)


      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 const certOverrideService = Cc[
      8  "@mozilla.org/security/certoverride;1"
      9 ].getService(Ci.nsICertOverrideService);
     10 const { HttpServer } = ChromeUtils.importESModule(
     11  "resource://testing-common/httpd.sys.mjs"
     12 );
     13 const { TestUtils } = ChromeUtils.importESModule(
     14  "resource://testing-common/TestUtils.sys.mjs"
     15 );
     16 
     17 const ReferrerInfo = Components.Constructor(
     18  "@mozilla.org/referrer-info;1",
     19  "nsIReferrerInfo",
     20  "init"
     21 );
     22 
     23 let h2Port;
     24 let trrServer;
     25 
     26 add_setup(async function setup() {
     27  trr_test_setup();
     28 
     29  Services.prefs.setBoolPref(
     30    "dom.security.https_first_for_custom_ports",
     31    false
     32  );
     33 
     34  trrServer = new TRRServer();
     35  await trrServer.start();
     36  h2Port = trrServer.port();
     37 
     38  Services.prefs.setCharPref(
     39    "network.trr.uri",
     40    "https://foo.example.com:" + h2Port + "/doh?httpssvc_as_altsvc=1"
     41  );
     42 
     43  Services.prefs.setBoolPref("network.dns.upgrade_with_https_rr", true);
     44  Services.prefs.setBoolPref("network.dns.use_https_rr_as_altsvc", true);
     45  Services.prefs.setBoolPref(
     46    "network.dns.https_rr.check_record_with_cname",
     47    false
     48  );
     49 
     50  Services.prefs.setBoolPref(
     51    "network.dns.use_https_rr_for_speculative_connection",
     52    true
     53  );
     54 
     55  registerCleanupFunction(async () => {
     56    trr_clear_prefs();
     57    Services.prefs.clearUserPref("network.dns.upgrade_with_https_rr");
     58    Services.prefs.clearUserPref("network.dns.use_https_rr_as_altsvc");
     59    Services.prefs.clearUserPref(
     60      "network.dns.use_https_rr_for_speculative_connection"
     61    );
     62    Services.prefs.clearUserPref("network.dns.notifyResolution");
     63    Services.prefs.clearUserPref("network.dns.disablePrefetch");
     64    Services.prefs.clearUserPref("dom.security.https_first_for_custom_ports");
     65    Services.prefs.clearUserPref(
     66      "network.dns.https_rr.check_record_with_cname"
     67    );
     68  });
     69 
     70  if (mozinfo.socketprocess_networking) {
     71    Services.dns; // Needed to trigger socket process.
     72    await TestUtils.waitForCondition(() => Services.io.socketProcessLaunched);
     73  }
     74 
     75  Services.prefs.setIntPref("network.trr.mode", Ci.nsIDNSService.MODE_TRRONLY);
     76 });
     77 
     78 function makeChan(url) {
     79  let chan = NetUtil.newChannel({
     80    uri: url,
     81    loadUsingSystemPrincipal: true,
     82    contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
     83  }).QueryInterface(Ci.nsIHttpChannel);
     84  return chan;
     85 }
     86 
     87 // When observer is specified, the channel will be suspended when receiving
     88 // "http-on-modify-request".
     89 function channelOpenPromise(chan, flags, observer) {
     90  return new Promise(resolve => {
     91    function finish(req, buffer) {
     92      certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
     93        false
     94      );
     95      resolve([req, buffer]);
     96    }
     97    certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
     98      true
     99    );
    100 
    101    if (observer) {
    102      let topic = "http-on-modify-request";
    103      Services.obs.addObserver(observer, topic);
    104    }
    105    chan.asyncOpen(new ChannelListener(finish, null, flags));
    106  });
    107 }
    108 
    109 class EventSinkListener {
    110  getInterface(iid) {
    111    if (iid.equals(Ci.nsIChannelEventSink)) {
    112      return this;
    113    }
    114    throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
    115  }
    116  asyncOnChannelRedirect(oldChan, newChan, flags, callback) {
    117    Assert.equal(oldChan.URI.hostPort, newChan.URI.hostPort);
    118    Assert.equal(oldChan.URI.scheme, "http");
    119    Assert.equal(newChan.URI.scheme, "https");
    120    callback.onRedirectVerifyCallback(Cr.NS_OK);
    121  }
    122 }
    123 
    124 EventSinkListener.prototype.QueryInterface = ChromeUtils.generateQI([
    125  "nsIInterfaceRequestor",
    126  "nsIChannelEventSink",
    127 ]);
    128 
    129 // Test if the request is upgraded to https with a HTTPSSVC record.
    130 add_task(async function testUseHTTPSSVCAsHSTS() {
    131  Services.dns.clearCache(true);
    132  // Do DNS resolution before creating the channel, so the HTTPSSVC record will
    133  // be resolved from the cache.
    134  await new TRRDNSListener("test.httpssvc.com", {
    135    type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
    136  });
    137 
    138  // Since the HTTPS RR should be served from cache, the DNS record is available
    139  // before nsHttpChannel::MaybeUseHTTPSRRForUpgrade() is called.
    140  let chan = makeChan(`http://test.httpssvc.com:80/server-timing`);
    141  let listener = new EventSinkListener();
    142  chan.notificationCallbacks = listener;
    143 
    144  let [req] = await channelOpenPromise(chan);
    145 
    146  req.QueryInterface(Ci.nsIHttpChannel);
    147  Assert.equal(req.getResponseHeader("x-connection-http2"), "yes");
    148 
    149  chan = makeChan(`http://test.httpssvc.com:80/server-timing`);
    150  listener = new EventSinkListener();
    151  chan.notificationCallbacks = listener;
    152 
    153  [req] = await channelOpenPromise(chan);
    154 
    155  req.QueryInterface(Ci.nsIHttpChannel);
    156  Assert.equal(req.getResponseHeader("x-connection-http2"), "yes");
    157 });
    158 
    159 // Test the case that we got an invalid DNS response. In this case,
    160 // nsHttpChannel::OnHTTPSRRAvailable is called after
    161 // nsHttpChannel::MaybeUseHTTPSRRForUpgrade.
    162 add_task(async function testInvalidDNSResult() {
    163  Services.dns.clearCache(true);
    164 
    165  let httpserv = new HttpServer();
    166  let content = "ok";
    167  httpserv.registerPathHandler("/", function handler(metadata, response) {
    168    response.setHeader("Content-Length", `${content.length}`);
    169    response.bodyOutputStream.write(content, content.length);
    170  });
    171  httpserv.start(-1);
    172  httpserv.identity.setPrimary(
    173    "http",
    174    "foo.notexisted.com",
    175    httpserv.identity.primaryPort
    176  );
    177 
    178  let chan = makeChan(
    179    `http://foo.notexisted.com:${httpserv.identity.primaryPort}/`
    180  );
    181  let [, response] = await channelOpenPromise(chan);
    182  Assert.equal(response, content);
    183  await new Promise(resolve => httpserv.stop(resolve));
    184 });
    185 
    186 // The same test as above, but nsHttpChannel::MaybeUseHTTPSRRForUpgrade is
    187 // called after nsHttpChannel::OnHTTPSRRAvailable.
    188 add_task(async function testInvalidDNSResult1() {
    189  Services.dns.clearCache(true);
    190 
    191  let httpserv = new HttpServer();
    192  let content = "ok";
    193  httpserv.registerPathHandler("/", function handler(metadata, response) {
    194    response.setHeader("Content-Length", `${content.length}`);
    195    response.bodyOutputStream.write(content, content.length);
    196  });
    197  httpserv.start(-1);
    198  httpserv.identity.setPrimary(
    199    "http",
    200    "foo.notexisted.com",
    201    httpserv.identity.primaryPort
    202  );
    203 
    204  let chan = makeChan(
    205    `http://foo.notexisted.com:${httpserv.identity.primaryPort}/`
    206  );
    207 
    208  let topic = "http-on-modify-request";
    209  let observer = {
    210    QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
    211    observe(aSubject, aTopic) {
    212      if (aTopic == topic) {
    213        Services.obs.removeObserver(observer, topic);
    214        let channel = aSubject.QueryInterface(Ci.nsIChannel);
    215        channel.suspend();
    216 
    217        new TRRDNSListener("foo.notexisted.com", {
    218          type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
    219          expectedSuccess: false,
    220        }).then(() => channel.resume());
    221      }
    222    },
    223  };
    224 
    225  let [, response] = await channelOpenPromise(chan, 0, observer);
    226  Assert.equal(response, content);
    227  await new Promise(resolve => httpserv.stop(resolve));
    228 });
    229 
    230 add_task(async function testLiteralIP() {
    231  let httpserv = new HttpServer();
    232  let content = "ok";
    233  httpserv.registerPathHandler("/", function handler(metadata, response) {
    234    response.setHeader("Content-Length", `${content.length}`);
    235    response.bodyOutputStream.write(content, content.length);
    236  });
    237  httpserv.start(-1);
    238 
    239  let chan = makeChan(`http://127.0.0.1:${httpserv.identity.primaryPort}/`);
    240  let [, response] = await channelOpenPromise(chan);
    241  Assert.equal(response, content);
    242  await new Promise(resolve => httpserv.stop(resolve));
    243 });
    244 
    245 // Test the case that an HTTPS RR is available and the server returns a 307
    246 // for redirecting back to http.
    247 add_task(async function testEndlessUpgradeDowngrade() {
    248  Services.dns.clearCache(true);
    249 
    250  let httpserv = new HttpServer();
    251  let content = "okok";
    252  httpserv.start(-1);
    253  let port = httpserv.identity.primaryPort;
    254  httpserv.registerPathHandler(
    255    `/redirect_to_http`,
    256    function handler(metadata, response) {
    257      response.setHeader("Content-Length", `${content.length}`);
    258      response.bodyOutputStream.write(content, content.length);
    259    }
    260  );
    261  httpserv.identity.setPrimary("http", "test.httpsrr.redirect.com", port);
    262 
    263  let chan = makeChan(
    264    `http://test.httpsrr.redirect.com:${port}/redirect_to_http?port=${port}`
    265  );
    266 
    267  let [, response] = await channelOpenPromise(chan);
    268  Assert.equal(response, content);
    269  await new Promise(resolve => httpserv.stop(resolve));
    270 });
    271 
    272 add_task(async function testHttpRequestBlocked() {
    273  Services.dns.clearCache(true);
    274 
    275  let dnsRequestObserver = {
    276    register() {
    277      this.obs = Services.obs;
    278      this.obs.addObserver(this, "dns-resolution-request");
    279    },
    280    unregister() {
    281      if (this.obs) {
    282        this.obs.removeObserver(this, "dns-resolution-request");
    283      }
    284    },
    285    observe(subject, topic) {
    286      if (topic == "dns-resolution-request") {
    287        Assert.ok(false, "unreachable");
    288      }
    289    },
    290  };
    291 
    292  dnsRequestObserver.register();
    293  Services.prefs.setBoolPref("network.dns.notifyResolution", true);
    294  Services.prefs.setBoolPref("network.dns.disablePrefetch", true);
    295 
    296  let httpserv = new HttpServer();
    297  httpserv.registerPathHandler("/", function handler() {
    298    Assert.ok(false, "unreachable");
    299  });
    300  httpserv.start(-1);
    301  httpserv.identity.setPrimary(
    302    "http",
    303    "foo.blocked.com",
    304    httpserv.identity.primaryPort
    305  );
    306 
    307  let chan = makeChan(
    308    `http://foo.blocked.com:${httpserv.identity.primaryPort}/`
    309  );
    310 
    311  let topic = "http-on-modify-request";
    312  let observer = {
    313    QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
    314    observe(aSubject, aTopic) {
    315      if (aTopic == topic) {
    316        Services.obs.removeObserver(observer, topic);
    317        let channel = aSubject.QueryInterface(Ci.nsIChannel);
    318        channel.cancel(Cr.NS_BINDING_ABORTED);
    319      }
    320    },
    321  };
    322 
    323  let [request] = await channelOpenPromise(chan, CL_EXPECT_FAILURE, observer);
    324  request.QueryInterface(Ci.nsIHttpChannel);
    325  Assert.equal(request.status, Cr.NS_BINDING_ABORTED);
    326  dnsRequestObserver.unregister();
    327  await new Promise(resolve => httpserv.stop(resolve));
    328 });
    329 
    330 function createPrincipal(url) {
    331  return Services.scriptSecurityManager.createContentPrincipal(
    332    Services.io.newURI(url),
    333    {}
    334  );
    335 }
    336 
    337 // Test if the Origin header stays the same after an internal HTTPS upgrade
    338 // caused by HTTPS RR.
    339 add_task(async function testHTTPSRRUpgradeWithOriginHeader() {
    340  Services.dns.clearCache(true);
    341 
    342  const url = "http://test.httpssvc.com:80/origin_header";
    343  const originURL = "http://example.com";
    344  let chan = Services.io
    345    .newChannelFromURIWithProxyFlags(
    346      Services.io.newURI(url),
    347      null,
    348      Ci.nsIProtocolProxyService.RESOLVE_ALWAYS_TUNNEL,
    349      null,
    350      createPrincipal(originURL),
    351      createPrincipal(url),
    352      Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
    353      Ci.nsIContentPolicy.TYPE_DOCUMENT
    354    )
    355    .QueryInterface(Ci.nsIHttpChannel);
    356  chan.referrerInfo = new ReferrerInfo(
    357    Ci.nsIReferrerInfo.EMPTY,
    358    true,
    359    NetUtil.newURI(url)
    360  );
    361  chan.setRequestHeader("Origin", originURL, false);
    362 
    363  let [req, buf] = await channelOpenPromise(chan);
    364 
    365  req.QueryInterface(Ci.nsIHttpChannel);
    366  Assert.equal(req.getResponseHeader("x-connection-http2"), "yes");
    367  Assert.equal(buf, originURL);
    368 });
    369 
    370 // See bug 1899841. Test the case when network.dns.use_https_rr_as_altsvc
    371 // is disabled.
    372 add_task(async function testPrefDisabled() {
    373  Services.prefs.setBoolPref("network.dns.use_https_rr_as_altsvc", false);
    374 
    375  let chan = makeChan(`https://test.httpssvc.com:${h2Port}/server-timing`);
    376  let [req] = await channelOpenPromise(chan);
    377 
    378  req.QueryInterface(Ci.nsIHttpChannel);
    379  Assert.equal(req.getResponseHeader("x-connection-http2"), "yes");
    380 });