diff options
Diffstat (limited to 'netwerk/test/unit/test_httpssvc_https_upgrade.js')
-rw-r--r-- | netwerk/test/unit/test_httpssvc_https_upgrade.js | 350 |
1 files changed, 350 insertions, 0 deletions
diff --git a/netwerk/test/unit/test_httpssvc_https_upgrade.js b/netwerk/test/unit/test_httpssvc_https_upgrade.js new file mode 100644 index 0000000000..51b711d983 --- /dev/null +++ b/netwerk/test/unit/test_httpssvc_https_upgrade.js @@ -0,0 +1,350 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const certOverrideService = Cc[ + "@mozilla.org/security/certoverride;1" +].getService(Ci.nsICertOverrideService); +const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js"); +const { TestUtils } = ChromeUtils.importESModule( + "resource://testing-common/TestUtils.sys.mjs" +); + +const ReferrerInfo = Components.Constructor( + "@mozilla.org/referrer-info;1", + "nsIReferrerInfo", + "init" +); + +add_setup(async function setup() { + trr_test_setup(); + + let h2Port = Services.env.get("MOZHTTP2_PORT"); + Assert.notEqual(h2Port, null); + Assert.notEqual(h2Port, ""); + + Services.prefs.setCharPref( + "network.trr.uri", + "https://foo.example.com:" + h2Port + "/httpssvc_as_altsvc" + ); + + Services.prefs.setBoolPref("network.dns.upgrade_with_https_rr", true); + Services.prefs.setBoolPref("network.dns.use_https_rr_as_altsvc", true); + + Services.prefs.setBoolPref( + "network.dns.use_https_rr_for_speculative_connection", + true + ); + + registerCleanupFunction(async () => { + trr_clear_prefs(); + Services.prefs.clearUserPref("network.dns.upgrade_with_https_rr"); + Services.prefs.clearUserPref("network.dns.use_https_rr_as_altsvc"); + Services.prefs.clearUserPref( + "network.dns.use_https_rr_for_speculative_connection" + ); + Services.prefs.clearUserPref("network.dns.notifyResolution"); + Services.prefs.clearUserPref("network.dns.disablePrefetch"); + }); + + if (mozinfo.socketprocess_networking) { + Services.dns; // Needed to trigger socket process. + await TestUtils.waitForCondition(() => Services.io.socketProcessLaunched); + } + + Services.prefs.setIntPref("network.trr.mode", Ci.nsIDNSService.MODE_TRRFIRST); +}); + +function makeChan(url) { + let chan = NetUtil.newChannel({ + uri: url, + loadUsingSystemPrincipal: true, + contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT, + }).QueryInterface(Ci.nsIHttpChannel); + return chan; +} + +// When observer is specified, the channel will be suspended when receiving +// "http-on-modify-request". +function channelOpenPromise(chan, flags, observer) { + return new Promise(resolve => { + function finish(req, buffer) { + certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData( + false + ); + resolve([req, buffer]); + } + certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData( + true + ); + + if (observer) { + let topic = "http-on-modify-request"; + Services.obs.addObserver(observer, topic); + } + chan.asyncOpen(new ChannelListener(finish, null, flags)); + }); +} + +class EventSinkListener { + getInterface(iid) { + if (iid.equals(Ci.nsIChannelEventSink)) { + return this; + } + throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE); + } + asyncOnChannelRedirect(oldChan, newChan, flags, callback) { + Assert.equal(oldChan.URI.hostPort, newChan.URI.hostPort); + Assert.equal(oldChan.URI.scheme, "http"); + Assert.equal(newChan.URI.scheme, "https"); + callback.onRedirectVerifyCallback(Cr.NS_OK); + } +} + +EventSinkListener.prototype.QueryInterface = ChromeUtils.generateQI([ + "nsIInterfaceRequestor", + "nsIChannelEventSink", +]); + +// Test if the request is upgraded to https with a HTTPSSVC record. +add_task(async function testUseHTTPSSVCAsHSTS() { + Services.dns.clearCache(true); + // Do DNS resolution before creating the channel, so the HTTPSSVC record will + // be resolved from the cache. + await new TRRDNSListener("test.httpssvc.com", { + type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC, + }); + + // Since the HTTPS RR should be served from cache, the DNS record is available + // before nsHttpChannel::MaybeUseHTTPSRRForUpgrade() is called. + let chan = makeChan(`http://test.httpssvc.com:80/server-timing`); + let listener = new EventSinkListener(); + chan.notificationCallbacks = listener; + + let [req] = await channelOpenPromise(chan); + + req.QueryInterface(Ci.nsIHttpChannel); + Assert.equal(req.getResponseHeader("x-connection-http2"), "yes"); + + chan = makeChan(`http://test.httpssvc.com:80/server-timing`); + listener = new EventSinkListener(); + chan.notificationCallbacks = listener; + + [req] = await channelOpenPromise(chan); + + req.QueryInterface(Ci.nsIHttpChannel); + Assert.equal(req.getResponseHeader("x-connection-http2"), "yes"); +}); + +// Test the case that we got an invalid DNS response. In this case, +// nsHttpChannel::OnHTTPSRRAvailable is called after +// nsHttpChannel::MaybeUseHTTPSRRForUpgrade. +add_task(async function testInvalidDNSResult() { + Services.dns.clearCache(true); + + let httpserv = new HttpServer(); + let content = "ok"; + httpserv.registerPathHandler("/", function handler(metadata, response) { + response.setHeader("Content-Length", `${content.length}`); + response.bodyOutputStream.write(content, content.length); + }); + httpserv.start(-1); + httpserv.identity.setPrimary( + "http", + "foo.notexisted.com", + httpserv.identity.primaryPort + ); + + let chan = makeChan( + `http://foo.notexisted.com:${httpserv.identity.primaryPort}/` + ); + let [, response] = await channelOpenPromise(chan); + Assert.equal(response, content); + await new Promise(resolve => httpserv.stop(resolve)); +}); + +// The same test as above, but nsHttpChannel::MaybeUseHTTPSRRForUpgrade is +// called after nsHttpChannel::OnHTTPSRRAvailable. +add_task(async function testInvalidDNSResult1() { + Services.dns.clearCache(true); + + let httpserv = new HttpServer(); + let content = "ok"; + httpserv.registerPathHandler("/", function handler(metadata, response) { + response.setHeader("Content-Length", `${content.length}`); + response.bodyOutputStream.write(content, content.length); + }); + httpserv.start(-1); + httpserv.identity.setPrimary( + "http", + "foo.notexisted.com", + httpserv.identity.primaryPort + ); + + let chan = makeChan( + `http://foo.notexisted.com:${httpserv.identity.primaryPort}/` + ); + + let topic = "http-on-modify-request"; + let observer = { + QueryInterface: ChromeUtils.generateQI(["nsIObserver"]), + observe(aSubject, aTopic, aData) { + if (aTopic == topic) { + Services.obs.removeObserver(observer, topic); + let channel = aSubject.QueryInterface(Ci.nsIChannel); + channel.suspend(); + + new TRRDNSListener("foo.notexisted.com", { + type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC, + expectedSuccess: false, + }).then(() => channel.resume()); + } + }, + }; + + let [, response] = await channelOpenPromise(chan, 0, observer); + Assert.equal(response, content); + await new Promise(resolve => httpserv.stop(resolve)); +}); + +add_task(async function testLiteralIP() { + let httpserv = new HttpServer(); + let content = "ok"; + httpserv.registerPathHandler("/", function handler(metadata, response) { + response.setHeader("Content-Length", `${content.length}`); + response.bodyOutputStream.write(content, content.length); + }); + httpserv.start(-1); + + let chan = makeChan(`http://127.0.0.1:${httpserv.identity.primaryPort}/`); + let [, response] = await channelOpenPromise(chan); + Assert.equal(response, content); + await new Promise(resolve => httpserv.stop(resolve)); +}); + +// Test the case that an HTTPS RR is available and the server returns a 307 +// for redirecting back to http. +add_task(async function testEndlessUpgradeDowngrade() { + Services.dns.clearCache(true); + + let httpserv = new HttpServer(); + let content = "okok"; + httpserv.start(-1); + let port = httpserv.identity.primaryPort; + httpserv.registerPathHandler( + `/redirect_to_http`, + function handler(metadata, response) { + response.setHeader("Content-Length", `${content.length}`); + response.bodyOutputStream.write(content, content.length); + } + ); + httpserv.identity.setPrimary("http", "test.httpsrr.redirect.com", port); + + let chan = makeChan( + `http://test.httpsrr.redirect.com:${port}/redirect_to_http?port=${port}` + ); + + let [, response] = await channelOpenPromise(chan); + Assert.equal(response, content); + await new Promise(resolve => httpserv.stop(resolve)); +}); + +add_task(async function testHttpRequestBlocked() { + Services.dns.clearCache(true); + + let dnsRequestObserver = { + register() { + this.obs = Services.obs; + this.obs.addObserver(this, "dns-resolution-request"); + }, + unregister() { + if (this.obs) { + this.obs.removeObserver(this, "dns-resolution-request"); + } + }, + observe(subject, topic, data) { + if (topic == "dns-resolution-request") { + Assert.ok(false, "unreachable"); + } + }, + }; + + dnsRequestObserver.register(); + Services.prefs.setBoolPref("network.dns.notifyResolution", true); + Services.prefs.setBoolPref("network.dns.disablePrefetch", true); + + let httpserv = new HttpServer(); + httpserv.registerPathHandler("/", function handler(metadata, response) { + Assert.ok(false, "unreachable"); + }); + httpserv.start(-1); + httpserv.identity.setPrimary( + "http", + "foo.blocked.com", + httpserv.identity.primaryPort + ); + + let chan = makeChan( + `http://foo.blocked.com:${httpserv.identity.primaryPort}/` + ); + + let topic = "http-on-modify-request"; + let observer = { + QueryInterface: ChromeUtils.generateQI(["nsIObserver"]), + observe(aSubject, aTopic, aData) { + if (aTopic == topic) { + Services.obs.removeObserver(observer, topic); + let channel = aSubject.QueryInterface(Ci.nsIChannel); + channel.cancel(Cr.NS_BINDING_ABORTED); + } + }, + }; + + let [request] = await channelOpenPromise(chan, CL_EXPECT_FAILURE, observer); + request.QueryInterface(Ci.nsIHttpChannel); + Assert.equal(request.status, Cr.NS_BINDING_ABORTED); + dnsRequestObserver.unregister(); + await new Promise(resolve => httpserv.stop(resolve)); +}); + +function createPrincipal(url) { + return Services.scriptSecurityManager.createContentPrincipal( + Services.io.newURI(url), + {} + ); +} + +// Test if the Origin header stays the same after an internal HTTPS upgrade +// caused by HTTPS RR. +add_task(async function testHTTPSRRUpgradeWithOriginHeader() { + Services.dns.clearCache(true); + + const url = "http://test.httpssvc.com:80/origin_header"; + const originURL = "http://example.com"; + let chan = Services.io + .newChannelFromURIWithProxyFlags( + Services.io.newURI(url), + null, + Ci.nsIProtocolProxyService.RESOLVE_ALWAYS_TUNNEL, + null, + createPrincipal(originURL), + createPrincipal(url), + Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, + Ci.nsIContentPolicy.TYPE_DOCUMENT + ) + .QueryInterface(Ci.nsIHttpChannel); + chan.referrerInfo = new ReferrerInfo( + Ci.nsIReferrerInfo.EMPTY, + true, + NetUtil.newURI(url) + ); + chan.setRequestHeader("Origin", originURL, false); + + let [req, buf] = await channelOpenPromise(chan); + + req.QueryInterface(Ci.nsIHttpChannel); + Assert.equal(req.getResponseHeader("x-connection-http2"), "yes"); + Assert.equal(buf, originURL); +}); |