tor-browser

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

test_altsvc_http3.js (15373B)


      1 "use strict";
      2 
      3 const { HttpServer } = ChromeUtils.importESModule(
      4  "resource://testing-common/httpd.sys.mjs"
      5 );
      6 
      7 var h2Port;
      8 var h3Port;
      9 
     10 // https://foo.example.com:(h3Port)
     11 // https://bar.example.com:(h3Port) <- invalid for bar, but ok for foo
     12 var h1Foo; // server http://foo.example.com:(h1Foo.identity.primaryPort)
     13 var h1Bar; // server http://bar.example.com:(h1bar.identity.primaryPort)
     14 
     15 var otherServer; // server socket listening for other connection.
     16 
     17 var h2FooRoute; // foo.example.com:H2PORT
     18 
     19 var h3FooRoute; // foo.example.com:H3PORT
     20 var h3BarRoute; // bar.example.com:H3PORT
     21 var h3Route; // :H3PORT
     22 var httpFooOrigin; // http://foo.exmaple.com:PORT/
     23 var httpsFooOrigin; // https://foo.exmaple.com:PORT/
     24 var httpBarOrigin; // http://bar.example.com:PORT/
     25 var httpsBarOrigin; // https://bar.example.com:PORT/
     26 
     27 function run_test() {
     28  h2Port = Services.env.get("MOZHTTP2_PORT");
     29  Assert.notEqual(h2Port, null);
     30  Assert.notEqual(h2Port, "");
     31 
     32  h3Port = Services.env.get("MOZHTTP3_PORT");
     33  Assert.notEqual(h3Port, null);
     34  Assert.notEqual(h3Port, "");
     35 
     36  // Set to allow the cert presented by our H3 server
     37  do_get_profile();
     38 
     39  Services.prefs.setBoolPref("network.http.http2.enabled", true);
     40  Services.prefs.setBoolPref("network.http.http3.enable", true);
     41  Services.prefs.setBoolPref("network.http.altsvc.enabled", true);
     42  Services.prefs.setBoolPref("network.http.altsvc.oe", true);
     43  Services.prefs.setCharPref(
     44    "network.dns.localDomains",
     45    "foo.example.com, bar.example.com"
     46  );
     47 
     48  // The moz-http2 cert is for foo.example.com and is signed by http2-ca.pem
     49  // so add that cert to the trust list as a signing cert. The same cert is used
     50  // for both h3FooRoute and h3BarRoute though it is only valid for
     51  // the foo.example.com domain name.
     52  let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
     53    Ci.nsIX509CertDB
     54  );
     55  addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
     56 
     57  h1Foo = new HttpServer();
     58  h1Foo.registerPathHandler("/altsvc-test", h1Server);
     59  h1Foo.registerPathHandler("/.well-known/http-opportunistic", h1ServerWK);
     60  h1Foo.start(-1);
     61  h1Foo.identity.setPrimary(
     62    "http",
     63    "foo.example.com",
     64    h1Foo.identity.primaryPort
     65  );
     66 
     67  h1Bar = new HttpServer();
     68  h1Bar.registerPathHandler("/altsvc-test", h1Server);
     69  h1Bar.start(-1);
     70  h1Bar.identity.setPrimary(
     71    "http",
     72    "bar.example.com",
     73    h1Bar.identity.primaryPort
     74  );
     75 
     76  h2FooRoute = "foo.example.com:" + h2Port;
     77 
     78  h3FooRoute = "foo.example.com:" + h3Port;
     79  h3BarRoute = "bar.example.com:" + h3Port;
     80  h3Route = ":" + h3Port;
     81 
     82  httpFooOrigin = "http://foo.example.com:" + h1Foo.identity.primaryPort + "/";
     83  httpsFooOrigin = "https://" + h3FooRoute + "/";
     84  httpBarOrigin = "http://bar.example.com:" + h1Bar.identity.primaryPort + "/";
     85  httpsBarOrigin = "https://" + h3BarRoute + "/";
     86  dump(
     87    "http foo - " +
     88      httpFooOrigin +
     89      "\n" +
     90      "https foo - " +
     91      httpsFooOrigin +
     92      "\n" +
     93      "http bar - " +
     94      httpBarOrigin +
     95      "\n" +
     96      "https bar - " +
     97      httpsBarOrigin +
     98      "\n"
     99  );
    100 
    101  doTest1();
    102 }
    103 
    104 function h1Server(metadata, response) {
    105  response.setStatusLine(metadata.httpVersion, 200, "OK");
    106  response.setHeader("Content-Type", "text/plain", false);
    107  response.setHeader("Connection", "close", false);
    108  response.setHeader("Cache-Control", "no-cache", false);
    109  response.setHeader("Access-Control-Allow-Origin", "*", false);
    110  response.setHeader("Access-Control-Allow-Method", "GET", false);
    111  response.setHeader("Access-Control-Allow-Headers", "x-altsvc", false);
    112 
    113  try {
    114    // If needed, prefix Alt-Svc with "h3=".
    115    if (metadata.getHeader("x-altsvc").includes("=")) {
    116      response.setHeader("Alt-Svc", metadata.getHeader("x-altsvc"), false);
    117    } else {
    118      response.setHeader(
    119        "Alt-Svc",
    120        "h3=" + metadata.getHeader("x-altsvc"),
    121        false
    122      );
    123    }
    124  } catch (e) {}
    125 
    126  var body = "Q: What did 0 say to 8? A: Nice Belt!\n";
    127  response.bodyOutputStream.write(body, body.length);
    128 }
    129 
    130 function h1ServerWK(metadata, response) {
    131  response.setStatusLine(metadata.httpVersion, 200, "OK");
    132  response.setHeader("Content-Type", "application/json", false);
    133  response.setHeader("Connection", "close", false);
    134  response.setHeader("Cache-Control", "no-cache", false);
    135  response.setHeader("Access-Control-Allow-Origin", "*", false);
    136  response.setHeader("Access-Control-Allow-Method", "GET", false);
    137  response.setHeader("Access-Control-Allow-Headers", "x-altsvc", false);
    138 
    139  var body = '["http://foo.example.com:' + h1Foo.identity.primaryPort + '"]';
    140  response.bodyOutputStream.write(body, body.length);
    141 }
    142 
    143 function resetPrefs() {
    144  Services.prefs.clearUserPref("network.http.http2.enabled");
    145  Services.prefs.clearUserPref("network.http.http3.enable");
    146  Services.prefs.clearUserPref("network.dns.localDomains");
    147  Services.prefs.clearUserPref("network.http.altsvc.enabled");
    148  Services.prefs.clearUserPref("network.http.altsvc.oe");
    149  Services.prefs.clearUserPref("network.dns.localDomains");
    150  Services.prefs.clearUserPref("network.security.ports.banned");
    151 }
    152 
    153 function makeChan(origin) {
    154  return NetUtil.newChannel({
    155    uri: origin + "altsvc-test",
    156    loadUsingSystemPrincipal: true,
    157  }).QueryInterface(Ci.nsIHttpChannel);
    158 }
    159 
    160 var origin;
    161 var xaltsvc;
    162 var loadWithoutClearingMappings = false;
    163 var disallowH3 = false;
    164 var disallowH2 = false;
    165 var testKeepAliveNotSet = false;
    166 var nextTest;
    167 var expectPass = true;
    168 var waitFor = 0;
    169 var originAttributes = {};
    170 
    171 var Listener = function (expectedHttpVersion, expectedRoute) {
    172  this._expectedRoute = expectedRoute;
    173  this._expectedHttpVersion = expectedHttpVersion;
    174 };
    175 
    176 Listener.prototype = {
    177  onStartRequest: function testOnStartRequest(request) {
    178    Assert.ok(request instanceof Ci.nsIHttpChannel);
    179 
    180    if (expectPass) {
    181      if (!Components.isSuccessCode(request.status)) {
    182        do_throw(
    183          "Channel should have a success code! (" + request.status + ")"
    184        );
    185      }
    186      Assert.equal(request.responseStatus, 200);
    187    } else {
    188      Assert.equal(Components.isSuccessCode(request.status), false);
    189    }
    190  },
    191 
    192  onDataAvailable: function testOnDataAvailable(request, stream, off, cnt) {
    193    read_stream(stream, cnt);
    194  },
    195 
    196  onStopRequest: function testOnStopRequest(request, status) {
    197    var routed = "";
    198    try {
    199      routed = request.getRequestHeader("Alt-Used");
    200    } catch (e) {}
    201    dump("routed is " + routed + "\n");
    202    Assert.equal(Components.isSuccessCode(status), expectPass);
    203 
    204    function assertHttpVersion(request, expectedHttpVersion) {
    205      if (expectedHttpVersion) {
    206        const httpVersion = request?.protocolVersion || "";
    207        Assert.equal(httpVersion, httpVersion);
    208      }
    209    }
    210 
    211    if (waitFor != 0) {
    212      Assert.equal(routed, "");
    213      do_test_pending();
    214      loadWithoutClearingMappings = true;
    215      do_timeout(waitFor, () => {
    216        doTest(this._expectedHttpVersion, this._expectedRoute);
    217      });
    218      waitFor = 0;
    219      xaltsvc = "NA";
    220    } else if (xaltsvc == "NA") {
    221      Assert.equal(routed, "");
    222      nextTest();
    223    } else if (this._expectedRoute && this._expectedRoute == routed) {
    224      assertHttpVersion(request, this._expectedHttpVersion);
    225      nextTest();
    226    } else if (routed == xaltsvc) {
    227      Assert.equal(routed, xaltsvc); // always true, but a useful log
    228      assertHttpVersion(request, this._expectedHttpVersion);
    229      nextTest();
    230    } else {
    231      dump("poll later for alt svc mapping\n");
    232      do_test_pending();
    233      loadWithoutClearingMappings = true;
    234      do_timeout(500, () => {
    235        doTest(this._expectedHttpVersion, this._expectedRoute);
    236      });
    237    }
    238 
    239    do_test_finished();
    240  },
    241 };
    242 
    243 function testsDone() {
    244  dump("testDone\n");
    245  resetPrefs();
    246  do_test_pending();
    247  otherServer.close();
    248  do_test_pending();
    249  h1Foo.stop(do_test_finished);
    250  do_test_pending();
    251  h1Bar.stop(do_test_finished);
    252 }
    253 
    254 function doTest(expectedHttpVersion, expectedRoute) {
    255  dump("execute doTest " + origin + "\n");
    256  var chan = makeChan(origin);
    257  var listener = new Listener(expectedHttpVersion, expectedRoute);
    258  if (xaltsvc != "NA") {
    259    chan.setRequestHeader("x-altsvc", xaltsvc, false);
    260  }
    261  if (testKeepAliveNotSet) {
    262    chan.setRequestHeader("Connection", "close", false);
    263    testKeepAliveNotSet = false;
    264  }
    265  if (loadWithoutClearingMappings) {
    266    chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
    267  } else {
    268    chan.loadFlags =
    269      Ci.nsIRequest.LOAD_FRESH_CONNECTION |
    270      Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
    271  }
    272  if (disallowH3) {
    273    let internalChannel = chan.QueryInterface(Ci.nsIHttpChannelInternal);
    274    internalChannel.allowHttp3 = false;
    275    disallowH3 = false;
    276  }
    277  if (disallowH2) {
    278    let internalChannel = chan.QueryInterface(Ci.nsIHttpChannelInternal);
    279    internalChannel.allowSpdy = false;
    280    disallowH2 = false;
    281  }
    282  loadWithoutClearingMappings = false;
    283  chan.loadInfo.originAttributes = originAttributes;
    284  chan.asyncOpen(listener);
    285 }
    286 
    287 // xaltsvc is overloaded to do two things..
    288 // 1] it is sent in the x-altsvc request header, and the response uses the value in the Alt-Svc response header
    289 // 2] the test polls until necko sets Alt-Used to that value (i.e. it uses that route)
    290 //
    291 // When xaltsvc is set to h3Route (i.e. :port with the implied hostname) it doesn't match the alt-used,
    292 // which is always explicit, so it needs to be changed after the channel is created but before the
    293 // listener is invoked
    294 
    295 // http://foo served from h3=:port
    296 function doTest1() {
    297  dump("doTest1()\n");
    298  origin = httpFooOrigin;
    299  xaltsvc = h3Route;
    300  nextTest = doTest2;
    301  do_test_pending();
    302  doTest("h3");
    303  xaltsvc = h3FooRoute;
    304 }
    305 
    306 // http://foo served from h3=foo:port
    307 function doTest2() {
    308  dump("doTest2()\n");
    309  origin = httpFooOrigin;
    310  xaltsvc = h3FooRoute;
    311  nextTest = doTest3;
    312  do_test_pending();
    313  doTest("h3");
    314 }
    315 
    316 // http://foo served from h3=bar:port
    317 // requires cert for foo
    318 function doTest3() {
    319  dump("doTest3()\n");
    320  origin = httpFooOrigin;
    321  xaltsvc = h3BarRoute;
    322  nextTest = doTest4;
    323  do_test_pending();
    324  doTest("h3");
    325 }
    326 
    327 // https://bar should fail because host bar has cert for foo
    328 function doTest4() {
    329  dump("doTest4()\n");
    330  origin = httpsBarOrigin;
    331  xaltsvc = "";
    332  expectPass = false;
    333  nextTest = doTest5;
    334  do_test_pending();
    335  doTest();
    336 }
    337 
    338 // http://bar via h3 on bar
    339 // should not use TLS/h3 because h3BarRoute is not auth'd for bar
    340 // however the test ought to PASS (i.e. get a 200) because fallback
    341 // to plaintext happens.. thus the timeout
    342 function doTest5() {
    343  dump("doTest5()\n");
    344  origin = httpBarOrigin;
    345  xaltsvc = h3BarRoute;
    346  expectPass = true;
    347  waitFor = 500;
    348  nextTest = doTest6;
    349  do_test_pending();
    350  doTest("h3");
    351 }
    352 
    353 // http://bar served from h3=:port, which is like the bar route in 8
    354 function doTest6() {
    355  dump("doTest6()\n");
    356  origin = httpBarOrigin;
    357  xaltsvc = h3Route;
    358  expectPass = true;
    359  waitFor = 500;
    360  nextTest = doTest7;
    361  do_test_pending();
    362  doTest();
    363  xaltsvc = h3BarRoute;
    364 }
    365 
    366 // check again https://bar should fail because host bar has cert for foo
    367 function doTest7() {
    368  dump("doTest7()\n");
    369  origin = httpsBarOrigin;
    370  xaltsvc = "";
    371  expectPass = false;
    372  nextTest = doTest8;
    373  do_test_pending();
    374  doTest();
    375 }
    376 
    377 // http://bar served from h3=foo, should fail because host foo only has
    378 // cert for foo. Fail in this case means alt-svc is not used, but content
    379 // is served
    380 function doTest8() {
    381  dump("doTest8()\n");
    382  origin = httpBarOrigin;
    383  xaltsvc = h3FooRoute;
    384  expectPass = true;
    385  waitFor = 500;
    386  nextTest = doTest9;
    387  do_test_pending();
    388  doTest("h3");
    389 }
    390 
    391 // Test 9-12:
    392 // Insert a cache of http://foo served from h3=:port with origin attributes.
    393 function doTest9() {
    394  dump("doTest9()\n");
    395  origin = httpFooOrigin;
    396  xaltsvc = h3Route;
    397  originAttributes = {
    398    userContextId: 1,
    399    firstPartyDomain: "a.com",
    400  };
    401  nextTest = doTest10;
    402  do_test_pending();
    403  doTest("h3");
    404  xaltsvc = h3FooRoute;
    405 }
    406 
    407 // Make sure we get a cache miss with a different userContextId.
    408 function doTest10() {
    409  dump("doTest10()\n");
    410  origin = httpFooOrigin;
    411  xaltsvc = "NA";
    412  originAttributes = {
    413    userContextId: 2,
    414    firstPartyDomain: "a.com",
    415  };
    416  loadWithoutClearingMappings = true;
    417  nextTest = doTest11;
    418  do_test_pending();
    419  doTest("h3");
    420 }
    421 
    422 // Make sure we get a cache miss with a different firstPartyDomain.
    423 function doTest11() {
    424  dump("doTest11()\n");
    425  origin = httpFooOrigin;
    426  xaltsvc = "NA";
    427  originAttributes = {
    428    userContextId: 1,
    429    firstPartyDomain: "b.com",
    430  };
    431  loadWithoutClearingMappings = true;
    432  nextTest = doTest12;
    433  do_test_pending();
    434  doTest("h3");
    435 }
    436 //
    437 // Make sure we get a cache hit with the same origin attributes.
    438 function doTest12() {
    439  dump("doTest12()\n");
    440  origin = httpFooOrigin;
    441  xaltsvc = "NA";
    442  originAttributes = {
    443    userContextId: 1,
    444    firstPartyDomain: "a.com",
    445  };
    446  loadWithoutClearingMappings = true;
    447  nextTest = doTest13;
    448  do_test_pending();
    449  doTest("h3");
    450  // This ensures a cache hit.
    451  xaltsvc = h3FooRoute;
    452 }
    453 
    454 // Make sure we do not use H3 if it is disabled on a channel.
    455 function doTest13() {
    456  dump("doTest13()\n");
    457  origin = httpFooOrigin;
    458  xaltsvc = "NA";
    459  disallowH3 = true;
    460  originAttributes = {
    461    userContextId: 1,
    462    firstPartyDomain: "a.com",
    463  };
    464  loadWithoutClearingMappings = true;
    465  nextTest = doTest14;
    466  do_test_pending();
    467  doTest("h3");
    468 }
    469 
    470 // Make sure we use H3 if only Http2 is disabled on a channel.
    471 function doTest14() {
    472  dump("doTest14()\n");
    473  origin = httpFooOrigin;
    474  xaltsvc = "NA";
    475  disallowH2 = true;
    476  originAttributes = {
    477    userContextId: 1,
    478    firstPartyDomain: "a.com",
    479  };
    480  loadWithoutClearingMappings = true;
    481  nextTest = doTest15;
    482  do_test_pending();
    483  doTest("h3");
    484  // This should ensures a cache hit.
    485  xaltsvc = h3FooRoute;
    486 }
    487 
    488 // Make sure we do not use H3 if NS_HTTP_ALLOW_KEEPALIVE is not set.
    489 function doTest15() {
    490  dump("doTest15()\n");
    491  origin = httpFooOrigin;
    492  xaltsvc = "NA";
    493  testKeepAliveNotSet = true;
    494  originAttributes = {
    495    userContextId: 1,
    496    firstPartyDomain: "a.com",
    497  };
    498  loadWithoutClearingMappings = true;
    499  nextTest = doTest16;
    500  do_test_pending();
    501  doTest("h3");
    502 }
    503 
    504 // Check we don't connect to blocked ports
    505 function doTest16() {
    506  dump("doTest16()\n");
    507  origin = httpFooOrigin;
    508  otherServer = Cc["@mozilla.org/network/server-socket;1"].createInstance(
    509    Ci.nsIServerSocket
    510  );
    511  otherServer.init(-1, true, -1);
    512  xaltsvc = "localhost:" + otherServer.port;
    513  Services.prefs.setCharPref(
    514    "network.security.ports.banned",
    515    "" + otherServer.port
    516  );
    517  dump("Blocked port: " + otherServer.port);
    518  waitFor = 500;
    519  otherServer.asyncListen({
    520    onSocketAccepted() {
    521      Assert.ok(false, "Got connection to socket when we didn't expect it!");
    522    },
    523    onStopListening() {
    524      // We get closed when the entire file is done, which guarantees we get the socket accept
    525      // if we do connect to the alt-svc header
    526      do_test_finished();
    527    },
    528  });
    529  nextTest = doTest17;
    530  do_test_pending();
    531  doTest("h3");
    532 }
    533 
    534 // Make sure we do not use a draft QUIC version.
    535 function doTest17() {
    536  dump("doTest17()\n");
    537  origin = httpFooOrigin;
    538  nextTest = testsDone;
    539  xaltsvc = "h3-29=" + h3FooRoute + ", h2=" + h2FooRoute;
    540  disallowH2 = false;
    541  originAttributes = {
    542    userContextId: 1,
    543    firstPartyDomain: "a.com",
    544  };
    545  loadWithoutClearingMappings = true;
    546  do_test_pending();
    547  doTest("h2", h2FooRoute);
    548 }