diff options
Diffstat (limited to 'dom/security/test/hsts')
16 files changed, 715 insertions, 0 deletions
diff --git a/dom/security/test/hsts/browser.ini b/dom/security/test/hsts/browser.ini new file mode 100644 index 000000000..ae75031df --- /dev/null +++ b/dom/security/test/hsts/browser.ini @@ -0,0 +1,19 @@ +[DEFAULT] +skip-if = debug # bug 1311599, bug 1311239, etc +support-files = + head.js + file_priming-top.html + file_testserver.sjs + file_1x1.png + file_priming.js + file_stylesheet.css + +[browser_hsts-priming_allow_active.js] +[browser_hsts-priming_block_active.js] +[browser_hsts-priming_hsts_after_mixed.js] +[browser_hsts-priming_allow_display.js] +[browser_hsts-priming_block_display.js] +[browser_hsts-priming_block_active_css.js] +[browser_hsts-priming_block_active_with_redir_same.js] +[browser_hsts-priming_no-duplicates.js] +[browser_hsts-priming_cache-timeout.js] diff --git a/dom/security/test/hsts/browser_hsts-priming_allow_active.js b/dom/security/test/hsts/browser_hsts-priming_allow_active.js new file mode 100644 index 000000000..a932b31b3 --- /dev/null +++ b/dom/security/test/hsts/browser_hsts-priming_allow_active.js @@ -0,0 +1,24 @@ +/* + * Description of the test: + * Check that HSTS priming occurs correctly with mixed content when active + * content is allowed. + */ +'use strict'; + +//jscs:disable +add_task(function*() { + //jscs:enable + Services.obs.addObserver(Observer, "console-api-log-event", false); + Services.obs.addObserver(Observer, "http-on-examine-response", false); + registerCleanupFunction(do_cleanup); + + let which = "allow_active"; + + SetupPrefTestEnvironment(which); + + for (let server of Object.keys(test_servers)) { + yield execute_test(server, test_settings[which].mimetype); + } + + SpecialPowers.popPrefEnv(); +}); diff --git a/dom/security/test/hsts/browser_hsts-priming_allow_display.js b/dom/security/test/hsts/browser_hsts-priming_allow_display.js new file mode 100644 index 000000000..06546ca65 --- /dev/null +++ b/dom/security/test/hsts/browser_hsts-priming_allow_display.js @@ -0,0 +1,24 @@ +/* + * Description of the test: + * Check that HSTS priming occurs correctly with mixed content when display + * content is allowed. + */ +'use strict'; + +//jscs:disable +add_task(function*() { + //jscs:enable + Services.obs.addObserver(Observer, "console-api-log-event", false); + Services.obs.addObserver(Observer, "http-on-examine-response", false); + registerCleanupFunction(do_cleanup); + + let which = "allow_display"; + + SetupPrefTestEnvironment(which); + + for (let server of Object.keys(test_servers)) { + yield execute_test(server, test_settings[which].mimetype); + } + + SpecialPowers.popPrefEnv(); +}); diff --git a/dom/security/test/hsts/browser_hsts-priming_block_active.js b/dom/security/test/hsts/browser_hsts-priming_block_active.js new file mode 100644 index 000000000..a5478b185 --- /dev/null +++ b/dom/security/test/hsts/browser_hsts-priming_block_active.js @@ -0,0 +1,24 @@ +/* + * Description of the test: + * Check that HSTS priming occurs correctly with mixed content when active + * content is blocked. + */ +'use strict'; + +//jscs:disable +add_task(function*() { + //jscs:enable + Services.obs.addObserver(Observer, "console-api-log-event", false); + Services.obs.addObserver(Observer, "http-on-examine-response", false); + registerCleanupFunction(do_cleanup); + + let which = "block_active"; + + SetupPrefTestEnvironment(which); + + for (let server of Object.keys(test_servers)) { + yield execute_test(server, test_settings[which].mimetype); + } + + SpecialPowers.popPrefEnv(); +}); diff --git a/dom/security/test/hsts/browser_hsts-priming_block_active_css.js b/dom/security/test/hsts/browser_hsts-priming_block_active_css.js new file mode 100644 index 000000000..340d11483 --- /dev/null +++ b/dom/security/test/hsts/browser_hsts-priming_block_active_css.js @@ -0,0 +1,24 @@ +/* + * Description of the test: + * Check that HSTS priming occurs correctly with mixed content when active + * content is blocked for css. + */ +'use strict'; + +//jscs:disable +add_task(function*() { + //jscs:enable + Services.obs.addObserver(Observer, "console-api-log-event", false); + Services.obs.addObserver(Observer, "http-on-examine-response", false); + registerCleanupFunction(do_cleanup); + + let which = "block_active_css"; + + SetupPrefTestEnvironment(which); + + for (let server of Object.keys(test_servers)) { + yield execute_test(server, test_settings[which].mimetype); + } + + SpecialPowers.popPrefEnv(); +}); diff --git a/dom/security/test/hsts/browser_hsts-priming_block_active_with_redir_same.js b/dom/security/test/hsts/browser_hsts-priming_block_active_with_redir_same.js new file mode 100644 index 000000000..130a3d5ec --- /dev/null +++ b/dom/security/test/hsts/browser_hsts-priming_block_active_with_redir_same.js @@ -0,0 +1,24 @@ +/* + * Description of the test: + * Check that HSTS priming occurs correctly with mixed content when active + * content is blocked and redirect to the same host should still upgrade. + */ +'use strict'; + +//jscs:disable +add_task(function*() { + //jscs:enable + Services.obs.addObserver(Observer, "console-api-log-event", false); + Services.obs.addObserver(Observer, "http-on-examine-response", false); + registerCleanupFunction(do_cleanup); + + let which = "block_active_with_redir_same"; + + SetupPrefTestEnvironment(which); + + for (let server of Object.keys(test_servers)) { + yield execute_test(server, test_settings[which].mimetype); + } + + SpecialPowers.popPrefEnv(); +}); diff --git a/dom/security/test/hsts/browser_hsts-priming_block_display.js b/dom/security/test/hsts/browser_hsts-priming_block_display.js new file mode 100644 index 000000000..4eca62718 --- /dev/null +++ b/dom/security/test/hsts/browser_hsts-priming_block_display.js @@ -0,0 +1,24 @@ +/* + * Description of the test: + * Check that HSTS priming occurs correctly with mixed content when display + * content is blocked. + */ +'use strict'; + +//jscs:disable +add_task(function*() { + //jscs:enable + Services.obs.addObserver(Observer, "console-api-log-event", false); + Services.obs.addObserver(Observer, "http-on-examine-response", false); + registerCleanupFunction(do_cleanup); + + let which = "block_display"; + + SetupPrefTestEnvironment(which); + + for (let server of Object.keys(test_servers)) { + yield execute_test(server, test_settings[which].mimetype); + } + + SpecialPowers.popPrefEnv(); +}); diff --git a/dom/security/test/hsts/browser_hsts-priming_cache-timeout.js b/dom/security/test/hsts/browser_hsts-priming_cache-timeout.js new file mode 100644 index 000000000..5416a71d2 --- /dev/null +++ b/dom/security/test/hsts/browser_hsts-priming_cache-timeout.js @@ -0,0 +1,36 @@ +/* + * Description of the test: + * Test that the network.hsts_priming.cache_timeout preferene causes the cache + * to timeout + */ +'use strict'; + +//jscs:disable +add_task(function*() { + //jscs:enable + Observer.add_observers(Services); + registerCleanupFunction(do_cleanup); + + let which = "block_display"; + + SetupPrefTestEnvironment(which, [["security.mixed_content.hsts_priming_cache_timeout", 1]]); + + yield execute_test("no-ssl", test_settings[which].mimetype); + + let pre_promise = performance.now(); + + while ((performance.now() - pre_promise) < 2000) { + yield new Promise(function (resolve) { + setTimeout(resolve, 2000); + }); + } + + // clear the fact that we saw a priming request + test_settings[which].priming = {}; + + yield execute_test("no-ssl", test_settings[which].mimetype); + is(test_settings[which].priming["no-ssl"], true, + "Correctly send a priming request after expiration."); + + SpecialPowers.popPrefEnv(); +}); diff --git a/dom/security/test/hsts/browser_hsts-priming_hsts_after_mixed.js b/dom/security/test/hsts/browser_hsts-priming_hsts_after_mixed.js new file mode 100644 index 000000000..89ea6fbeb --- /dev/null +++ b/dom/security/test/hsts/browser_hsts-priming_hsts_after_mixed.js @@ -0,0 +1,24 @@ +/* + * Description of the test: + * Check that HSTS priming occurs correctly with mixed content when the + * mixed-content blocks before HSTS. + */ +'use strict'; + +//jscs:disable +add_task(function*() { + //jscs:enable + Services.obs.addObserver(Observer, "console-api-log-event", false); + Services.obs.addObserver(Observer, "http-on-examine-response", false); + registerCleanupFunction(do_cleanup); + + let which = "hsts_after_mixed"; + + SetupPrefTestEnvironment(which); + + for (let server of Object.keys(test_servers)) { + yield execute_test(server, test_settings[which].mimetype); + } + + SpecialPowers.popPrefEnv(); +}); diff --git a/dom/security/test/hsts/browser_hsts-priming_no-duplicates.js b/dom/security/test/hsts/browser_hsts-priming_no-duplicates.js new file mode 100644 index 000000000..3846fe4f0 --- /dev/null +++ b/dom/security/test/hsts/browser_hsts-priming_no-duplicates.js @@ -0,0 +1,30 @@ +/* + * Description of the test: + * Only one request should be sent per host, even if we run the test more + * than once. + */ +'use strict'; + +//jscs:disable +add_task(function*() { + //jscs:enable + Observer.add_observers(Services); + registerCleanupFunction(do_cleanup); + + let which = "block_display"; + + SetupPrefTestEnvironment(which); + + for (let server of Object.keys(test_servers)) { + yield execute_test(server, test_settings[which].mimetype); + } + + test_settings[which].priming = {}; + + // run the tests twice to validate the cache is being used + for (let server of Object.keys(test_servers)) { + yield execute_test(server, test_settings[which].mimetype); + } + + SpecialPowers.popPrefEnv(); +}); diff --git a/dom/security/test/hsts/file_1x1.png b/dom/security/test/hsts/file_1x1.png Binary files differnew file mode 100644 index 000000000..1ba31ba1a --- /dev/null +++ b/dom/security/test/hsts/file_1x1.png diff --git a/dom/security/test/hsts/file_priming-top.html b/dom/security/test/hsts/file_priming-top.html new file mode 100644 index 000000000..b1d1bfa40 --- /dev/null +++ b/dom/security/test/hsts/file_priming-top.html @@ -0,0 +1,84 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Bug 1246540</title> + <meta http-equiv='content-type' content="text/html;charset=utf-8" /> +</head> +<body> + <p id="display"></p> + <div id="content" style="visibility: hidden"> + </div> + +<script type="text/javascript"> +/* + * Description of the test: + * Attempt to load an insecure resource. If the resource responds to HSTS + * priming with an STS header, the load should continue securely. + * If it does not, the load should continue be blocked or continue insecurely. + */ + +function parse_query_string() { + var q = {}; + document.location.search.substr(1). + split('&').forEach(function (item, idx, ar) { + let [k, v] = item.split('='); + q[k] = unescape(v); + }); + return q; +} + +var args = parse_query_string(); + +var subresources = { + css: { mimetype: 'text/css', file: 'file_stylesheet.css' }, + img: { mimetype: 'image/png', file: 'file_1x1.png' }, + script: { mimetype: 'text/javascript', file: 'file_priming.js' }, +}; + +function handler(ev) { + console.log("HSTS_PRIMING: Blocked "+args.id); +} + +function loadCss(src) { + let head = document.getElementsByTagName("head")[0]; + let link = document.createElement("link"); + link.setAttribute("rel", "stylesheet"); + link.setAttribute("type", subresources[args.type].mimetype); + link.setAttribute("href", src); + head.appendChild(link); +} + +function loadResource(src) { + let content = document.getElementById("content"); + let testElem = document.createElement(args.type); + testElem.setAttribute("id", args.id); + testElem.setAttribute("charset", "UTF-8"); + testElem.onerror = handler; + content.appendChild(testElem); + testElem.src = src; +} + +function loadTest() { + let subresource = subresources[args.type]; + + let src = "http://" + + args.host + + "/browser/dom/security/test/hsts/file_testserver.sjs" + + "?file=" +escape("browser/dom/security/test/hsts/" + subresource.file) + + "&primer=" + escape(args.id) + + "&mimetype=" + escape(subresource.mimetype) + ; + if (args.type == 'css') { + loadCss(src); + return; + } + + loadResource(src); +} + +// start running the tests +loadTest(); + +</script> +</body> +</html> diff --git a/dom/security/test/hsts/file_priming.js b/dom/security/test/hsts/file_priming.js new file mode 100644 index 000000000..023022da6 --- /dev/null +++ b/dom/security/test/hsts/file_priming.js @@ -0,0 +1,4 @@ +function completed() { + return; +} +completed(); diff --git a/dom/security/test/hsts/file_stylesheet.css b/dom/security/test/hsts/file_stylesheet.css new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/dom/security/test/hsts/file_stylesheet.css diff --git a/dom/security/test/hsts/file_testserver.sjs b/dom/security/test/hsts/file_testserver.sjs new file mode 100644 index 000000000..d5cd6b17a --- /dev/null +++ b/dom/security/test/hsts/file_testserver.sjs @@ -0,0 +1,66 @@ +// SJS file for HSTS mochitests + +Components.utils.import("resource://gre/modules/NetUtil.jsm"); +Components.utils.importGlobalProperties(["URLSearchParams"]); + +function loadFromFile(path) { + // Load the HTML to return in the response from file. + // Since it's relative to the cwd of the test runner, we start there and + // append to get to the actual path of the file. + var testFile = + Components.classes["@mozilla.org/file/directory_service;1"]. + getService(Components.interfaces.nsIProperties). + get("CurWorkD", Components.interfaces.nsILocalFile); + var dirs = path.split("/"); + for (var i = 0; i < dirs.length; i++) { + testFile.append(dirs[i]); + } + var testFileStream = + Components.classes["@mozilla.org/network/file-input-stream;1"]. + createInstance(Components.interfaces.nsIFileInputStream); + testFileStream.init(testFile, -1, 0, 0); + var test = NetUtil.readInputStreamToString(testFileStream, testFileStream.available()); + return test; +} + +function handleRequest(request, response) +{ + const query = new URLSearchParams(request.queryString); + + redir = query.get('redir'); + if (redir == 'same') { + query.delete("redir"); + response.setStatus(302); + let newURI = request.uri; + newURI.queryString = query.serialize(); + response.setHeader("Location", newURI.spec) + } + + // avoid confusing cache behaviors + response.setHeader("Cache-Control", "no-cache", false); + + // if we have a priming header, check for required behavior + // and set header appropriately + if (request.hasHeader('Upgrade-Insecure-Requests')) { + var expected = query.get('primer'); + if (expected == 'prime-hsts') { + // set it for 5 minutes + response.setHeader("Strict-Transport-Security", "max-age="+(60*5), false); + } else if (expected == 'reject-upgrade') { + response.setHeader("Strict-Transport-Security", "max-age=0", false); + } + response.write(''); + return; + } + + var file = query.get('file'); + if (file) { + var mimetype = unescape(query.get('mimetype')); + response.setHeader("Content-Type", mimetype, false); + response.write(loadFromFile(unescape(file))); + return; + } + + response.setHeader("Content-Type", "application/json", false); + response.write('{}'); +} diff --git a/dom/security/test/hsts/head.js b/dom/security/test/hsts/head.js new file mode 100644 index 000000000..362b36444 --- /dev/null +++ b/dom/security/test/hsts/head.js @@ -0,0 +1,308 @@ +/* + * Description of the tests: + * Check that HSTS priming occurs correctly with mixed content + * + * This test uses three hostnames, each of which treats an HSTS priming + * request differently. + * * no-ssl never returns an ssl response + * * reject-upgrade returns an ssl response, but with no STS header + * * prime-hsts returns an ssl response with the appropriate STS header + * + * For each server, test that it response appropriately when the we allow + * or block active or display content, as well as when we send an hsts priming + * request, but do not change the order of mixed-content and HSTS. + * + * Test use http-on-examine-response, so must be run in browser context. + */ +'use strict'; + +var TOP_URI = "https://example.com/browser/dom/security/test/hsts/file_priming-top.html"; + +var test_servers = { + // a test server that does not support TLS + 'no-ssl': { + host: 'example.co.jp', + response: false, + id: 'no-ssl', + }, + // a test server which does not support STS upgrade + 'reject-upgrade': { + host: 'example.org', + response: true, + id: 'reject-upgrade', + }, + // a test server when sends an STS header when priming + 'prime-hsts': { + host: 'test1.example.com', + response: true, + id: 'prime-hsts' + }, +}; + +var test_settings = { + // mixed active content is allowed, priming will upgrade + allow_active: { + block_active: false, + block_display: false, + use_hsts: true, + send_hsts_priming: true, + type: 'script', + result: { + 'no-ssl': 'insecure', + 'reject-upgrade': 'insecure', + 'prime-hsts': 'secure', + }, + }, + // mixed active content is blocked, priming will upgrade + block_active: { + block_active: true, + block_display: false, + use_hsts: true, + send_hsts_priming: true, + type: 'script', + result: { + 'no-ssl': 'blocked', + 'reject-upgrade': 'blocked', + 'prime-hsts': 'secure', + }, + }, + // keep the original order of mixed-content and HSTS, but send + // priming requests + hsts_after_mixed: { + block_active: true, + block_display: false, + use_hsts: false, + send_hsts_priming: true, + type: 'script', + result: { + 'no-ssl': 'blocked', + 'reject-upgrade': 'blocked', + 'prime-hsts': 'blocked', + }, + }, + // mixed display content is allowed, priming will upgrade + allow_display: { + block_active: true, + block_display: false, + use_hsts: true, + send_hsts_priming: true, + type: 'img', + result: { + 'no-ssl': 'insecure', + 'reject-upgrade': 'insecure', + 'prime-hsts': 'secure', + }, + }, + // mixed display content is blocked, priming will upgrade + block_display: { + block_active: true, + block_display: true, + use_hsts: true, + send_hsts_priming: true, + type: 'img', + result: { + 'no-ssl': 'blocked', + 'reject-upgrade': 'blocked', + 'prime-hsts': 'secure', + }, + }, + // mixed active content is blocked, priming will upgrade (css) + block_active_css: { + block_active: true, + block_display: false, + use_hsts: true, + send_hsts_priming: true, + type: 'css', + result: { + 'no-ssl': 'blocked', + 'reject-upgrade': 'blocked', + 'prime-hsts': 'secure', + }, + }, + // mixed active content is blocked, priming will upgrade + // redirect to the same host + block_active_with_redir_same: { + block_active: true, + block_display: false, + use_hsts: true, + send_hsts_priming: true, + type: 'script', + redir: 'same', + result: { + 'no-ssl': 'blocked', + 'reject-upgrade': 'blocked', + 'prime-hsts': 'secure', + }, + }, +} +// track which test we are on +var which_test = ""; + +const Observer = { + observe: function (subject, topic, data) { + switch (topic) { + case 'console-api-log-event': + return Observer.console_api_log_event(subject, topic, data); + case 'http-on-examine-response': + return Observer.http_on_examine_response(subject, topic, data); + case 'http-on-modify-request': + return Observer.http_on_modify_request(subject, topic, data); + } + throw "Can't handle topic "+topic; + }, + add_observers: function (services) { + services.obs.addObserver(Observer, "console-api-log-event", false); + services.obs.addObserver(Observer, "http-on-examine-response", false); + services.obs.addObserver(Observer, "http-on-modify-request", false); + }, + // When a load is blocked which results in an error event within a page, the + // test logs to the console. + console_api_log_event: function (subject, topic, data) { + var message = subject.wrappedJSObject.arguments[0]; + // when we are blocked, this will match the message we sent to the console, + // ignore everything else. + var re = RegExp(/^HSTS_PRIMING: Blocked ([-\w]+).*$/); + if (!re.test(message)) { + return; + } + + let id = message.replace(re, '$1'); + let curTest =test_servers[id]; + + if (!curTest) { + ok(false, "HSTS priming got a console message blocked, "+ + "but doesn't match expectations "+id+" (msg="+message); + return; + } + + is("blocked", test_settings[which_test].result[curTest.id], "HSTS priming "+ + which_test+":"+curTest.id+" expected "+ + test_settings[which_test].result[curTest.id]+", got blocked"); + test_settings[which_test].finished[curTest.id] = "blocked"; + }, + get_current_test: function(uri) { + for (let item in test_servers) { + let re = RegExp('https?://'+test_servers[item].host); + if (re.test(uri)) { + return test_servers[item]; + } + } + return null; + }, + http_on_modify_request: function (subject, topic, data) { + let channel = subject.QueryInterface(Ci.nsIHttpChannel); + if (channel.requestMethod != 'HEAD') { + return; + } + + let curTest = this.get_current_test(channel.URI.asciiSpec); + + if (!curTest) { + return; + } + + ok(!(curTest.id in test_settings[which_test].priming), "Already saw a priming request for " + curTest.id); + test_settings[which_test].priming[curTest.id] = true; + }, + // When we see a response come back, peek at the response and test it is secure + // or insecure as needed. Addtionally, watch the response for priming requests. + http_on_examine_response: function (subject, topic, data) { + let channel = subject.QueryInterface(Ci.nsIHttpChannel); + let curTest = this.get_current_test(channel.URI.asciiSpec); + + if (!curTest) { + return; + } + + let result = (channel.URI.asciiSpec.startsWith('https:')) ? "secure" : "insecure"; + + // This is a priming request, go ahead and validate we were supposed to see + // a response from the server + if (channel.requestMethod == 'HEAD') { + is(true, curTest.response, "HSTS priming response found " + curTest.id); + return; + } + + // This is the response to our query, make sure it matches + is(result, test_settings[which_test].result[curTest.id], + "HSTS priming result " + which_test + ":" + curTest.id); + test_settings[which_test].finished[curTest.id] = result; + }, +}; + +// opens `uri' in a new tab and focuses it. +// returns the newly opened tab +function openTab(uri) { + let tab = gBrowser.addTab(uri); + + // select tab and make sure its browser is focused + gBrowser.selectedTab = tab; + tab.ownerDocument.defaultView.focus(); + + return tab; +} + +function clear_sts_data() { + for (let test in test_servers) { + SpecialPowers.cleanUpSTSData('http://'+test_servers[test].host); + } +} + +function do_cleanup() { + clear_sts_data(); + + Services.obs.removeObserver(Observer, "console-api-log-event"); + Services.obs.removeObserver(Observer, "http-on-examine-response"); +} + +function SetupPrefTestEnvironment(which, additional_prefs) { + which_test = which; + clear_sts_data(); + + var settings = test_settings[which]; + // priming counts how many priming requests we saw + settings.priming = {}; + // priming counts how many tests were finished + settings.finished= {}; + + var prefs = [["security.mixed_content.block_active_content", + settings.block_active], + ["security.mixed_content.block_display_content", + settings.block_display], + ["security.mixed_content.use_hsts", + settings.use_hsts], + ["security.mixed_content.send_hsts_priming", + settings.send_hsts_priming]]; + + if (additional_prefs) { + for (let idx in additional_prefs) { + prefs.push(additional_prefs[idx]); + } + } + + console.log("prefs=%s", prefs); + + SpecialPowers.pushPrefEnv({'set': prefs}); +} + +// make the top-level test uri +function build_test_uri(base_uri, host, test_id, type) { + return base_uri + + "?host=" + escape(host) + + "&id=" + escape(test_id) + + "&type=" + escape(type); +} + +// open a new tab, load the test, and wait for it to finish +function execute_test(test, mimetype) { + var src = build_test_uri(TOP_URI, test_servers[test].host, + test, test_settings[which_test].type); + + let tab = openTab(src); + test_servers[test]['tab'] = tab; + + let browser = gBrowser.getBrowserForTab(tab); + yield BrowserTestUtils.browserLoaded(browser); + + yield BrowserTestUtils.removeTab(tab); +} |