tor-browser

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

test_altsvc.js (16552B)


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