| <!DOCTYPE html> | |
| <meta charset=utf-8> | |
| <title>Subresource Integrity</title> | |
| <script src="../../../resources/testharness.js"></script> | |
| <script src="../../../resources/testharnessreport.js"></script> | |
| <div id="log"></div> | |
| <div id="container"></div> | |
| <script> | |
| // This horrible hack is needed for the 'use-credentials' tests because, on | |
| // response, if port 80 or 443 is the current port, it will not appear to | |
| // the browser as part of the origin string. Since the origin *string* is | |
| // used for CORS access control, instead of the origin itself, if there | |
| // isn't an exact string match, the check will fail. For example, | |
| // "http://example.com" would not match "http://example.com:80", because | |
| // they are not exact string matches, even though the origins are the same. | |
| // | |
| // Thus, we only want the Access-Control-Allow-Origin header to have | |
| // the port if it's not port 80 or 443, since the user agent will elide the | |
| // ports in those cases. | |
| var main_domain = "{{domains[]}}"; | |
| var www_domain = "{{domains[www]}}"; | |
| var default_port = "{{ports[http][0]}}"; | |
| if (location.protocol === "https:") { | |
| default_port = "{{ports[https][0]}}"; | |
| } | |
| var port_string = ""; | |
| if (default_port !== "80" && default_port !== "443") | |
| port_string = ":" + default_port; | |
| www_host_and_port = www_domain + port_string; | |
| // <script> tests | |
| var xorigin_anon_script = location.protocol | |
| + '//' + www_host_and_port | |
| + '/subresource-integrity/crossorigin-anon-script.js'; | |
| var xorigin_creds_script = location.protocol | |
| + '//' + www_host_and_port | |
| + '/subresource-integrity/crossorigin-creds-script.js?acao_port=' | |
| + port_string; | |
| var xorigin_ineligible_script = location.protocol | |
| + '//' + www_host_and_port | |
| + '/subresource-integrity/crossorigin-ineligible-script.js'; | |
| var SRIScriptTest = function(pass, name, src, integrityValue, crossoriginValue) { | |
| this.pass = pass; | |
| this.name = "Script: " + name; | |
| this.src = src; | |
| this.integrityValue = integrityValue; | |
| this.crossoriginValue = crossoriginValue; | |
| } | |
| SRIScriptTest.prototype.execute = function() { | |
| var test = async_test(this.name); | |
| var e = document.createElement("script"); | |
| e.src = this.src; | |
| e.setAttribute("integrity", this.integrityValue); | |
| if(this.crossoriginValue) { | |
| e.setAttribute("crossorigin", this.crossoriginValue); | |
| } | |
| if(this.pass) { | |
| e.addEventListener("load", function() {test.done()}); | |
| e.addEventListener("error", function() { | |
| test.step(function(){ assert_unreached("Good load fired error handler.") }) | |
| }); | |
| } else { | |
| e.addEventListener("load", function() { | |
| test.step(function() { assert_unreached("Bad load succeeded.") }) | |
| }); | |
| e.addEventListener("error", function() {test.done()}); | |
| } | |
| document.body.appendChild(e); | |
| }; | |
| // Note that all of these style URLs have query parameters started, so any | |
| // additional parameters should be appended starting with '&'. | |
| var xorigin_anon_style = location.protocol | |
| + '//' + www_host_and_port | |
| + '/subresource-integrity/crossorigin-anon-style.css?'; | |
| var xorigin_creds_style = location.protocol | |
| + '//' + www_host_and_port | |
| + '/subresource-integrity/crossorigin-creds-style.css?acao_port=' | |
| + port_string; | |
| var xorigin_ineligible_style = location.protocol | |
| + '//' + www_host_and_port | |
| + '/subresource-integrity/crossorigin-ineligible-style.css?'; | |
| // <link> tests | |
| // Style tests must be done synchronously because they rely on the presence | |
| // and absence of global style, which can affect later tests. Thus, instead | |
| // of executing them one at a time, the style tests are implemented as a | |
| // queue that builds up a list of tests, and then executes them one at a | |
| // time. | |
| var SRIStyleTest = function(queue, pass, name, attrs, customCallback, altPassValue) { | |
| this.pass = pass; | |
| this.name = "Style: " + name; | |
| this.customCallback = customCallback || function () {}; | |
| this.attrs = attrs || {}; | |
| this.passValue = altPassValue || "rgb(255, 255, 0)"; | |
| this.test = async_test(this.name); | |
| this.queue = queue; | |
| this.queue.push(this); | |
| } | |
| SRIStyleTest.prototype.execute = function() { | |
| var that = this; | |
| var container = document.getElementById("container"); | |
| while (container.hasChildNodes()) { | |
| container.removeChild(container.firstChild); | |
| } | |
| var test = this.test; | |
| var div = document.createElement("div"); | |
| div.className = "testdiv"; | |
| var e = document.createElement("link"); | |
| this.attrs.rel = this.attrs.rel || "stylesheet"; | |
| for (var key in this.attrs) { | |
| if (this.attrs.hasOwnProperty(key)) { | |
| e.setAttribute(key, this.attrs[key]); | |
| } | |
| } | |
| if(this.pass) { | |
| e.addEventListener("load", function() { | |
| test.step(function() { | |
| var background = window.getComputedStyle(div, null).getPropertyValue("background-color"); | |
| assert_equals(background, that.passValue); | |
| test.done(); | |
| }); | |
| }); | |
| e.addEventListener("error", function() { | |
| test.step(function(){ assert_unreached("Good load fired error handler.") }) | |
| }); | |
| } else { | |
| e.addEventListener("load", function() { | |
| test.step(function() { assert_unreached("Bad load succeeded.") }) | |
| }); | |
| e.addEventListener("error", function() { | |
| test.step(function() { | |
| var background = window.getComputedStyle(div, null).getPropertyValue("background-color"); | |
| assert_not_equals(background, that.passValue); | |
| test.done(); | |
| }); | |
| }); | |
| } | |
| container.appendChild(div); | |
| container.appendChild(e); | |
| this.customCallback(e, container); | |
| }; | |
| var style_tests = []; | |
| style_tests.execute = function() { | |
| if (this.length > 0) { | |
| this.shift().execute(); | |
| } | |
| } | |
| add_result_callback(function(res) { | |
| if (res.name.startsWith("Style: ")) { | |
| style_tests.execute(); | |
| } | |
| }); | |
| // Script tests | |
| new SRIScriptTest( | |
| true, | |
| "Same-origin with correct sha256 hash.", | |
| "matching-digest.js", | |
| "sha256-U9WYDtBWkcHx13+9UKk/3Q5eoqDc4YGxYb07EPWzb9E=" | |
| ).execute(); | |
| new SRIScriptTest( | |
| true, | |
| "Same-origin with correct sha384 hash.", | |
| "matching-digest.js", | |
| "sha384-BDRTPSywZFyxfLEAzaLcL4FfERBgJgXfEkuT0r04LG93Yqn1PWNYPZMomaqEfE3H" | |
| ).execute(); | |
| new SRIScriptTest( | |
| true, | |
| "Same-origin with correct sha512 hash.", | |
| "matching-digest.js", | |
| "sha512-geByvIIRspbnUnwooKGNNCb39nvg+EW0O9hDScTXeo/9pVZztLSUYU3LNV6H0lZapo8bCJUpyPPLAzE9fDzpxg==" | |
| ).execute(); | |
| new SRIScriptTest( | |
| true, | |
| "Same-origin with empty integrity.", | |
| "matching-digest.js", | |
| "" | |
| ).execute(); | |
| new SRIScriptTest( | |
| false, | |
| "Same-origin with incorrect hash.", | |
| "non-matching-digest.js", | |
| "sha256-deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdead" | |
| ).execute(); | |
| new SRIScriptTest( | |
| true, | |
| "Same-origin with multiple sha256 hashes, including correct.", | |
| "matching-digest.js", | |
| "sha256-U9WYDtBWkcHx13+9UKk/3Q5eoqDc4YGxYb07EPWzb9E= sha256-deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdead" | |
| ).execute(); | |
| new SRIScriptTest( | |
| true, | |
| "Same-origin with multiple sha256 hashes, including unknown algorithm.", | |
| "matching-digest.js", | |
| "sha256-U9WYDtBWkcHx13+9UKk/3Q5eoqDc4YGxYb07EPWzb9E= foo666-deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdead" | |
| ).execute(); | |
| new SRIScriptTest( | |
| true, | |
| "Same-origin with sha256 mismatch, sha512 match", | |
| "matching-digest.js", | |
| "sha512-geByvIIRspbnUnwooKGNNCb39nvg+EW0O9hDScTXeo/9pVZztLSUYU3LNV6H0lZapo8bCJUpyPPLAzE9fDzpxg== sha256-deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdead" | |
| ).execute(); | |
| new SRIScriptTest( | |
| false, | |
| "Same-origin with sha256 match, sha512 mismatch", | |
| "matching-digest.js", | |
| "sha512-deadbeefspbnUnwooKGNNCb39nvg+EW0O9hDScTXeo/9pVZztLSUYU3LNV6H0lZapo8bCJUpyPPLAzE9fDzpxg== sha256-U9WYDtBWkcHx13+9UKk/3Q5eoqDc4YGxYb07EPWzb9E=" | |
| ).execute(); | |
| new SRIScriptTest( | |
| true, | |
| "<crossorigin='anonymous'> with correct hash, ACAO: *", | |
| xorigin_anon_script, | |
| "sha256-51AjITq701Y0yKSx3/UoIKtIY2UQ9+H8WGyyMuOWOC0=", | |
| "anonymous" | |
| ).execute(); | |
| new SRIScriptTest( | |
| false, | |
| "<crossorigin='anonymous'> with incorrect hash, ACAO: *", | |
| xorigin_anon_script, | |
| "sha256-deadbeefcSLlbFZCj1OACLxTxVck2TOrBTEdUbwz1yU=", | |
| "anonymous" | |
| ).execute(); | |
| new SRIScriptTest( | |
| true, | |
| "<crossorigin='use-credentials'> with correct hash, CORS-eligible", | |
| xorigin_creds_script, | |
| "sha256-IaGApVboXPQxVSm2wVFmhMq1Yu37gWklajgMdxKLIvc=", | |
| "use-credentials" | |
| ).execute(); | |
| new SRIScriptTest( | |
| false, | |
| "<crossorigin='use-credentials'> with incorrect hash CORS-eligible", | |
| xorigin_creds_script, | |
| "sha256-deadbeef2S+pTRZgiw3DWrhC6JLDlt2zRyGpwH7unU8=", | |
| "use-credentials" | |
| ).execute(); | |
| new SRIScriptTest( | |
| false, | |
| "<crossorigin='anonymous'> with CORS-ineligible resource", | |
| xorigin_ineligible_script, | |
| "sha256-F5fXKTX7SiWjtgybxiBZIo2qhh2WiQnNx372E60XrOo=", | |
| "anonymous" | |
| ).execute(); | |
| new SRIScriptTest( | |
| false, | |
| "Cross-origin, not CORS request, with correct hash", | |
| xorigin_anon_script, | |
| "sha256-51AjITq701Y0yKSx3/UoIKtIY2UQ9+H8WGyyMuOWOC0=" | |
| ).execute(); | |
| new SRIScriptTest( | |
| false, | |
| "Cross-origin, not CORS request, with hash mismatch", | |
| xorigin_anon_script, | |
| "sha256-deadbeef01Y0yKSx3/UoIKtIY2UQ9+H8WGyyMuOWOC0=" | |
| ).execute(); | |
| new SRIScriptTest( | |
| true, | |
| "Cross-origin, empty integrity", | |
| xorigin_anon_script, | |
| "" | |
| ).execute(); | |
| new SRIScriptTest( | |
| true, | |
| "Same-origin with correct hash, options.", | |
| "matching-digest.js", | |
| "sha256-U9WYDtBWkcHx13+9UKk/3Q5eoqDc4YGxYb07EPWzb9E=?foo=bar?spam=eggs" | |
| ).execute(); | |
| new SRIScriptTest( | |
| true, | |
| "Same-origin with unknown algorithm only.", | |
| "matching-digest.js", | |
| "foo666-U9WYDtBWkcHx13+9UKk/3Q5eoqDc4YGxYb07EPWzb9E=" | |
| ).execute(); | |
| // Style tests | |
| new SRIStyleTest( | |
| style_tests, | |
| true, | |
| "Same-origin with correct sha256 hash", | |
| { | |
| href: "style.css?1", | |
| integrity: "sha256-CzHgdJ7wOccM8L89n4bhcJMz3F+SPLT7YZk7gyCWUV4=" | |
| } | |
| ); | |
| new SRIStyleTest( | |
| style_tests, | |
| true, | |
| "Same-origin with correct sha384 hash", | |
| { | |
| href: "style.css?2", | |
| integrity: "sha384-wDAWxH4tOWBwAwHfBn9B7XuNmFxHTMeigAMwn0iVQ0zq3FtmYMLxihcGnU64CwcX" | |
| } | |
| ); | |
| new SRIStyleTest( | |
| style_tests, | |
| true, | |
| "Same-origin with correct sha512 hash", | |
| { | |
| href: "style.css?3", | |
| integrity: "sha512-9wXDjd6Wq3H6nPAhI9zOvG7mJkUr03MTxaO+8ztTKnfJif42laL93Be/IF6YYZHHF4esitVYxiwpY2HSZX4l6w==" | |
| } | |
| ); | |
| new SRIStyleTest( | |
| style_tests, | |
| true, | |
| "Same-origin with empty integrity", | |
| { | |
| href: "style.css?4", | |
| integrity: "" | |
| } | |
| ); | |
| new SRIStyleTest( | |
| style_tests, | |
| false, | |
| "Same-origin with incorrect hash.", | |
| { | |
| href: "style.css?5", | |
| integrity: "sha256-deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdead" | |
| } | |
| ); | |
| new SRIStyleTest( | |
| style_tests, | |
| true, | |
| "Same-origin with multiple sha256 hashes, including correct.", | |
| { | |
| href: "style.css?6", | |
| integrity: "sha256-CzHgdJ7wOccM8L89n4bhcJMz3F+SPLT7YZk7gyCWUV4= sha256-deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdead" | |
| } | |
| ); | |
| new SRIStyleTest( | |
| style_tests, | |
| true, | |
| "Same-origin with multiple sha256 hashes, including unknown algorithm.", | |
| { | |
| href: "style.css?7", | |
| integrity: "sha256-CzHgdJ7wOccM8L89n4bhcJMz3F+SPLT7YZk7gyCWUV4= foo666-deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdead" | |
| } | |
| ); | |
| new SRIStyleTest( | |
| style_tests, | |
| true, | |
| "Same-origin with sha256 mismatch, sha512 match", | |
| { | |
| href: "style.css?8", | |
| integrity: "sha512-9wXDjd6Wq3H6nPAhI9zOvG7mJkUr03MTxaO+8ztTKnfJif42laL93Be/IF6YYZHHF4esitVYxiwpY2HSZX4l6w== sha256-deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdead" | |
| } | |
| ); | |
| new SRIStyleTest( | |
| style_tests, | |
| false, | |
| "Same-origin with sha256 match, sha512 mismatch", | |
| { | |
| href: "style.css?9", | |
| integrity: "sha512-deadbeef9wXDjd6Wq3H6nPAhI9zOvG7mJkUr03MTxaO+8ztTKnfJif42laL93Be/IF6YYZHHF4esitVYxiwpY2== sha256-CzHgdJ7wOccM8L89n4bhcJMz3F+SPLT7YZk7gyCWUV4=" | |
| } | |
| ); | |
| new SRIStyleTest( | |
| style_tests, | |
| true, | |
| "<crossorigin='anonymous'> with correct hash, ACAO: *", | |
| { | |
| href: xorigin_anon_style + '&1', | |
| integrity: "sha256-CzHgdJ7wOccM8L89n4bhcJMz3F+SPLT7YZk7gyCWUV4=", | |
| crossorigin: "anonymous" | |
| } | |
| ); | |
| new SRIStyleTest( | |
| style_tests, | |
| false, | |
| "<crossorigin='anonymous'> with incorrect hash, ACAO: *", | |
| { | |
| href: xorigin_anon_style + '&2', | |
| integrity: "sha256-deadbeefCzHgdJ7wOccM8L89n4bhcJMz3F+SPLT7YZk=", | |
| crossorigin: "anonymous" | |
| } | |
| ); | |
| new SRIStyleTest( | |
| style_tests, | |
| true, | |
| "<crossorigin='use-credentials'> with correct hash, CORS-eligible", | |
| { | |
| href: xorigin_creds_style + '&1', | |
| integrity: "sha256-CzHgdJ7wOccM8L89n4bhcJMz3F+SPLT7YZk7gyCWUV4=", | |
| crossorigin: "use-credentials" | |
| } | |
| ); | |
| new SRIStyleTest( | |
| style_tests, | |
| false, | |
| "<crossorigin='use-credentials'> with incorrect hash CORS-eligible", | |
| { | |
| href: xorigin_creds_style + '&2', | |
| integrity: "sha256-deadbeefCzHgdJ7wOccM8L89n4bhcJMz3F+SPLT7YZk=", | |
| crossorigin: "use-credentials" | |
| } | |
| ); | |
| new SRIStyleTest( | |
| style_tests, | |
| false, | |
| "<crossorigin='anonymous'> with CORS-ineligible resource", | |
| { | |
| href: xorigin_ineligible_style + '&1', | |
| integrity: "sha256-CzHgdJ7wOccM8L89n4bhcJMz3F+SPLT7YZk7gyCWUV4=", | |
| crossorigin: "anonymous" | |
| } | |
| ); | |
| new SRIStyleTest( | |
| style_tests, | |
| false, | |
| "Cross-origin, not CORS request, with correct hash", | |
| { | |
| href: xorigin_anon_style + '&3', | |
| integrity: "sha256-CzHgdJ7wOccM8L89n4bhcJMz3F+SPLT7YZk7gyCWUV4=" | |
| } | |
| ); | |
| new SRIStyleTest( | |
| style_tests, | |
| false, | |
| "Cross-origin, not CORS request, with hash mismatch", | |
| { | |
| href: xorigin_anon_style + '&4', | |
| integrity: "sha256-deadbeefCzHgdJ7wOccM8L89n4bhcJMz3F+SPLT7YZk=" | |
| } | |
| ); | |
| new SRIStyleTest( | |
| style_tests, | |
| true, | |
| "Cross-origin, empty integrity", | |
| { | |
| href: xorigin_anon_style + '&5', | |
| integrity: "" | |
| } | |
| ); | |
| new SRIStyleTest( | |
| style_tests, | |
| true, | |
| "Same-origin with correct hash, options.", | |
| { | |
| href: "style.css?10", | |
| integrity: "sha256-CzHgdJ7wOccM8L89n4bhcJMz3F+SPLT7YZk7gyCWUV4=?foo=bar?spam=eggs" | |
| } | |
| ); | |
| new SRIStyleTest( | |
| style_tests, | |
| true, | |
| "Same-origin with unknown algorithm only.", | |
| { | |
| href: "style.css?11", | |
| integrity: "foo666-CzHgdJ7wOccM8L89n4bhcJMz3F+SPLT7YZk7gyCWUV4=?foo=bar?spam=eggs" | |
| } | |
| ); | |
| new SRIStyleTest( | |
| style_tests, | |
| true, | |
| "Same-origin with correct sha256 hash, rel='stylesheet license'", | |
| { | |
| href: "style.css?12", | |
| integrity: "sha256-CzHgdJ7wOccM8L89n4bhcJMz3F+SPLT7YZk7gyCWUV4=", | |
| rel: "stylesheet license" | |
| } | |
| ); | |
| new SRIStyleTest( | |
| style_tests, | |
| true, | |
| "Same-origin with correct sha256 hash, rel='license stylesheet'", | |
| { | |
| href: "style.css?13", | |
| integrity: "sha256-CzHgdJ7wOccM8L89n4bhcJMz3F+SPLT7YZk7gyCWUV4=", | |
| rel: "license stylesheet" | |
| } | |
| ); | |
| new SRIStyleTest( | |
| style_tests, | |
| true, | |
| "Same-origin with correct sha256 and sha512 hash, rel='alternate stylesheet' enabled", | |
| { | |
| href: "alternate.css?1", | |
| title: "alt", | |
| type: "text/css", | |
| class: "alternate", | |
| disabled: "disabled", | |
| rel: "alternate stylesheet", | |
| integrity: "sha256-phbz83bWhnLig+d2VPKrRrTRyhqoDRo1ruGqZLZ0= sha512-8OYEB7ktnzcb6h+kB9CUIuc8qvKIyLpygRJdQSEEycRy74dUsB+Yu9rSjpOPjRUblle8WWX9Gn7v39LK2Oceig==", | |
| }, | |
| function (link, container) { | |
| var alternate = document.querySelector('link.alternate'); | |
| alternate.disabled = false; | |
| }, | |
| "rgb(255, 0, 0)" | |
| ); | |
| new SRIStyleTest( | |
| style_tests, | |
| false, | |
| "Same-origin with incorrect sha256 and sha512 hash, rel='alternate stylesheet' enabled", | |
| { | |
| href: "alternate.css?2", | |
| title: "alt", | |
| type: "text/css", | |
| class: "alternate", | |
| disabled: "disabled", | |
| rel: "alternate stylesheet", | |
| integrity: "sha256-fail83bWhnLig+d2VPKrRrTRyhqoDRo1ruGqZLZ0= sha512-failB7ktnzcb6h+kB9CUIuc8qvKIyLpygRJdQSEEycRy74dUsB+Yu9rSjpOPjRUblle8WWX9Gn7v39LK2Oceig==", | |
| }, | |
| function (link, container) { | |
| var alternate = document.querySelector('link.alternate'); | |
| alternate.disabled = false; | |
| } | |
| ); | |
| style_tests.execute(); | |
| </script> | |
| <!-- TODO check cache-poisoned resources, transfer-encoding, 3xx redirect | |
| to resource with matching hash, and cross-origin leakage test as in sec5.3. | |
| --> |