Bug 1235572 - Tests of enforcing SRI on remote about:newtab. r=francois
authorJonathan Hao <jhao@mozilla.com>
Wed, 16 Mar 2016 11:19:20 +0800
changeset 289195 a6e3806b9aff
parent 289194 5c69aaedbde9
child 289196 a0a16898e8db
push id30099
push usercbook@mozilla.com
push dateFri, 18 Mar 2016 14:52:23 +0000
treeherdermozilla-central@9c5d494d0548 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfrancois
bugs1235572
milestone48.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1235572 - Tests of enforcing SRI on remote about:newtab. r=francois MozReview-Commit-ID: 6epw8D4M0FX
dom/security/test/contentverifier/browser.ini
dom/security/test/contentverifier/browser_verify_content_about_newtab.js
dom/security/test/contentverifier/file_about_newtab_sri.html
dom/security/test/contentverifier/file_about_newtab_sri_signature
dom/security/test/contentverifier/file_contentserver.sjs
dom/security/test/contentverifier/script.js
dom/security/test/contentverifier/style.css
--- a/dom/security/test/contentverifier/browser.ini
+++ b/dom/security/test/contentverifier/browser.ini
@@ -1,10 +1,14 @@
 [DEFAULT]
 support-files =
   file_contentserver.sjs
   file_about_newtab.html
   file_about_newtab_bad.html
   file_about_newtab_good_signature
   file_about_newtab_bad_signature
   file_about_newtab_broken_signature
+  file_about_newtab_sri.html
+  file_about_newtab_sri_signature
+  script.js
+  style.css
 
 [browser_verify_content_about_newtab.js]
--- a/dom/security/test/contentverifier/browser_verify_content_about_newtab.js
+++ b/dom/security/test/contentverifier/browser_verify_content_about_newtab.js
@@ -38,48 +38,66 @@ const URI_BAD_FILE = BASE + "sig=good&ke
 const URI_BAD_ALL = BASE + "sig=bad&key=bad&file=bad&header=bad";
 
 const URI_BAD_FILE_CACHED = BASE + "sig=good&key=good&file=bad&header=good&cached=true";
 
 const GOOD_ABOUT_STRING = "Just a fully good testpage for Bug 1226928";
 const BAD_ABOUT_STRING = "Just a bad testpage for Bug 1226928";
 const ABOUT_BLANK = "<head></head><body></body>";
 
+const URI_CLEANUP = BASE + "cleanup=true";
+const CLEANUP_DONE = "Done";
+
+const URI_SRI = BASE + "sig=sri&key=good&file=sri&header=good";
+const STYLESHEET_WITHOUT_SRI_BLOCKED = "Stylesheet without SRI blocked";
+const STYLESHEET_WITH_SRI_BLOCKED = "Stylesheet with SRI blocked";
+const STYLESHEET_WITH_SRI_LOADED = "Stylesheet with SRI loaded";
+const SCRIPT_WITHOUT_SRI_BLOCKED = "Script without SRI blocked";
+const SCRIPT_WITH_SRI_BLOCKED = "Script with SRI blocked";
+const SCRIPT_WITH_SRI_LOADED = "Script with SRI loaded";
+
 const TESTS = [
   // { newtab (aboutURI) or regular load (url) : url,
-  //   testString : expected string in the loaded page }
-  { "aboutURI" : URI_GOOD, "testString" : GOOD_ABOUT_STRING },
-  { "aboutURI" : URI_ERROR_HEADER, "testString" : ABOUT_BLANK },
-  { "aboutURI" : URI_KEYERROR_HEADER, "testString" : ABOUT_BLANK },
-  { "aboutURI" : URI_SIGERROR_HEADER, "testString" : ABOUT_BLANK },
-  { "aboutURI" : URI_NO_HEADER, "testString" : ABOUT_BLANK },
-  { "aboutURI" : URI_BAD_SIG, "testString" : ABOUT_BLANK },
-  { "aboutURI" : URI_BROKEN_SIG, "testString" : ABOUT_BLANK },
-  { "aboutURI" : URI_BAD_KEY, "testString" : ABOUT_BLANK },
-  { "aboutURI" : URI_BAD_FILE, "testString" : ABOUT_BLANK },
-  { "aboutURI" : URI_BAD_ALL, "testString" : ABOUT_BLANK },
-  { "url" : URI_BAD_FILE_CACHED, "testString" : BAD_ABOUT_STRING },
-  { "aboutURI" : URI_BAD_FILE_CACHED, "testString" : ABOUT_BLANK },
-  { "aboutURI" : URI_GOOD, "testString" : GOOD_ABOUT_STRING }
+  //   testStrings : expected strings in the loaded page }
+  { "aboutURI" : URI_GOOD, "testStrings" : [GOOD_ABOUT_STRING] },
+  { "aboutURI" : URI_ERROR_HEADER, "testStrings" : [ABOUT_BLANK] },
+  { "aboutURI" : URI_KEYERROR_HEADER, "testStrings" : [ABOUT_BLANK] },
+  { "aboutURI" : URI_SIGERROR_HEADER, "testStrings" : [ABOUT_BLANK] },
+  { "aboutURI" : URI_NO_HEADER, "testStrings" : [ABOUT_BLANK] },
+  { "aboutURI" : URI_BAD_SIG, "testStrings" : [ABOUT_BLANK] },
+  { "aboutURI" : URI_BROKEN_SIG, "testStrings" : [ABOUT_BLANK] },
+  { "aboutURI" : URI_BAD_KEY, "testStrings" : [ABOUT_BLANK] },
+  { "aboutURI" : URI_BAD_FILE, "testStrings" : [ABOUT_BLANK] },
+  { "aboutURI" : URI_BAD_ALL, "testStrings" : [ABOUT_BLANK] },
+  { "url" : URI_BAD_FILE_CACHED, "testStrings" : [BAD_ABOUT_STRING] },
+  { "aboutURI" : URI_BAD_FILE_CACHED, "testStrings" : [ABOUT_BLANK] },
+  { "aboutURI" : URI_GOOD, "testStrings" : [GOOD_ABOUT_STRING] },
+  { "aboutURI" : URI_SRI, "testStrings" : [
+    STYLESHEET_WITHOUT_SRI_BLOCKED,
+    STYLESHEET_WITH_SRI_LOADED,
+    SCRIPT_WITHOUT_SRI_BLOCKED,
+    SCRIPT_WITH_SRI_LOADED,
+    ]},
+  { "url" : URI_CLEANUP, "testStrings" : [CLEANUP_DONE] },
 ];
 
 var browser = null;
 var aboutNewTabService = Cc["@mozilla.org/browser/aboutnewtab-service;1"]
                            .getService(Ci.nsIAboutNewTabService);
 
 function pushPrefs(...aPrefs) {
   return new Promise((resolve) => {
     SpecialPowers.pushPrefEnv({"set": aPrefs}, resolve);
   });
 }
 
 /*
  * run tests with input from TESTS
  */
-function doTest(aExpectedString, reload, aUrl, aNewTabPref) {
+function doTest(aExpectedStrings, reload, aUrl, aNewTabPref) {
   // set about:newtab location for this test if it's a newtab test
   if (aNewTabPref) {
     aboutNewTabService.newTabURL = aNewTabPref;
   }
 
   // set prefs
   yield pushPrefs(
       ["browser.newtabpage.remote.content-signing-test", true],
@@ -110,20 +128,22 @@ function doTest(aExpectedString, reload,
       // we only check this if we really do a newtab test
       if (aNewTabPref) {
         ok(aboutNewTabService.overridden,
             "sanity check: default URL for about:newtab should be overriden");
         is(aboutNewTabService.newTabURL, aNewTabPref,
             "sanity check: default URL for about:newtab should return the new URL");
       }
       yield ContentTask.spawn(
-          browser, aExpectedString, function * (aExpectedString) {
-            ok(content.document.documentElement.innerHTML.includes(aExpectedString),
-               "Expect the following value in the result\n" + aExpectedString +
-               "\nand got " + content.document.documentElement.innerHTML);
+          browser, aExpectedStrings, function * (aExpectedStrings) {
+            for (let expectedString of aExpectedStrings) {
+              ok(content.document.documentElement.innerHTML.includes(expectedString),
+                 "Expect the following value in the result\n" + expectedString +
+                 "\nand got " + content.document.documentElement.innerHTML);
+            }
           });
 
       // for good test cases we check if a reload fails if the remote page
       // changed from valid to invalid in the meantime
       if (reload) {
         yield BrowserTestUtils.withNewTab({
             gBrowser,
             url: INVALIDATE_FILE,
@@ -135,22 +155,32 @@ function doTest(aExpectedString, reload,
                  "\nand got " + content.document.documentElement.innerHTML);
             });
           }
         );
 
         browser.reload();
         yield BrowserTestUtils.browserLoaded(browser);
 
-        aExpectedString = ABOUT_BLANK;
-        yield ContentTask.spawn(browser, aExpectedString,
-          function * (aExpectedString) {
-            ok(content.document.documentElement.innerHTML.includes(aExpectedString),
-               "Expect the following value in the result\n" + aExpectedString +
-               "\nand got " + content.document.documentElement.innerHTML);
+        let expectedStrings = [ABOUT_BLANK];
+        if (aNewTabPref == URI_SRI) {
+          expectedStrings = [
+            STYLESHEET_WITHOUT_SRI_BLOCKED,
+            STYLESHEET_WITH_SRI_BLOCKED,
+            SCRIPT_WITHOUT_SRI_BLOCKED,
+            SCRIPT_WITH_SRI_BLOCKED
+          ];
+        }
+        yield ContentTask.spawn(browser, expectedStrings,
+          function * (expectedStrings) {
+            for (let expectedString of expectedStrings) {
+              ok(content.document.documentElement.innerHTML.includes(expectedString),
+                 "Expect the following value in the result\n" + expectedString +
+                 "\nand got " + content.document.documentElement.innerHTML);
+            }
           }
         );
 
         yield BrowserTestUtils.withNewTab({
             gBrowser,
             url: VALIDATE_FILE,
           },
           function * (browser2) {
@@ -167,22 +197,22 @@ function doTest(aExpectedString, reload,
 }
 
 add_task(function * test() {
   // run tests from TESTS
   for (let i = 0; i < TESTS.length; i++) {
     let testCase = TESTS[i];
     let url = "", aNewTabPref = "";
     let reload = false;
-    let aExpectedString = testCase.testString;
+    var aExpectedStrings = testCase.testStrings;
     if (testCase.aboutURI) {
       url = ABOUT_NEWTAB_URI;
       aNewTabPref = testCase.aboutURI;
-      if (aExpectedString == GOOD_ABOUT_STRING) {
+      if (aNewTabPref == URI_GOOD || aNewTabPref == URI_SRI) {
         reload = true;
       }
     } else {
       url = testCase.url;
     }
 
-    yield doTest(aExpectedString, reload, url, aNewTabPref);
+    yield doTest(aExpectedStrings, reload, url, aNewTabPref);
   }
 });
new file mode 100644
--- /dev/null
+++ b/dom/security/test/contentverifier/file_about_newtab_sri.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html>
+<!-- https://bugzilla.mozilla.org/show_bug.cgi?id=1235572 -->
+<head>
+  <meta charset="utf-8">
+  <title>Testpage for bug 1235572</title>
+  <script>
+    function loaded(resource) {
+      document.getElementById("result").innerHTML += resource + " loaded\n";
+    }
+    function blocked(resource) {
+      document.getElementById("result").innerHTML += resource + " blocked\n";
+    }
+  </script>
+</head>
+<body>
+  Testing script loading without SRI for Bug 1235572<br/>
+  <div id="result"></div>
+
+  <!-- use css1 and css2 to make urls different to avoid the resource being cached-->
+  <link rel="stylesheet" href="https://example.com/browser/dom/security/test/contentverifier/file_contentserver.sjs?resource=css1"
+        onload="loaded('Stylesheet without SRI')"
+        onerror="blocked('Stylesheet without SRI')">
+  <link rel="stylesheet" href="https://example.com/browser/dom/security/test/contentverifier/file_contentserver.sjs?resource=css2"
+        integrity="sha384-/6Tvxh7SX39y62qePcvYoi5Vrf0lK8Ix3wJFLCYKI5KNJ5wIlCR8UsFC1OXwmwgd"
+        onload="loaded('Stylesheet with SRI')"
+        onerror="blocked('Stylesheet with SRI')">
+  <script src="https://example.com/browser/dom/security/test/contentverifier/file_contentserver.sjs?resource=script"
+          onload="loaded('Script without SRI')"
+          onerror="blocked('Script without SRI')"></script>
+  <script src="https://example.com/browser/dom/security/test/contentverifier/file_contentserver.sjs?resource=script"
+          integrity="sha384-zDCkvKOHXk8mM6Nk07oOGXGME17PA4+ydFw+hq0r9kgF6ZDYFWK3fLGPEy7FoOAo"
+          onload="loaded('Script with SRI')"
+          onerror="blocked('Script with SRI')"></script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/security/test/contentverifier/file_about_newtab_sri_signature
@@ -0,0 +1,1 @@
+i5jOnrZWwyNwrTcIjfJ6fUR-8MhhvhtMvQbdrUD7j8aHTybNolv25v9NwJAT6rVU6kgkxmD_st9Kla086CQmzYQdLhKfzgLbTDXz0-1j23fQnyjsP1_4MNIu2xTea11p
\ No newline at end of file
--- a/dom/security/test/contentverifier/file_contentserver.sjs
+++ b/dom/security/test/contentverifier/file_contentserver.sjs
@@ -9,31 +9,40 @@ const path = "browser/dom/security/test/
 
 const goodFileName = "file_about_newtab.html";
 const goodFileBase = path + goodFileName;
 const goodFile = FileUtils.getDir("TmpD", [], true);
 goodFile.append(goodFileName);
 const goodSignature = path + "file_about_newtab_good_signature";
 const goodKeyId = "RemoteNewTab";
 
+const scriptFileName = "script.js";
+const cssFileName = "style.css";
 const badFile = path + "file_about_newtab_bad.html";
 const brokenSignature = path + "file_about_newtab_broken_signature";
 const badSignature = path + "file_about_newtab_bad_signature";
 const badKeyId = "OldRemoteNewTabKey";
 
+const sriFile = path + "file_about_newtab_sri.html";
+const sriSignature = path + "file_about_newtab_sri_signature";
+
+const tempFileNames = [goodFileName, scriptFileName, cssFileName];
+
 // we copy the file to serve as newtab to a temp directory because
 // we modify it during tests.
-setupTestFile();
+setupTestFiles();
 
-function setupTestFile() {
-  let tempFile = FileUtils.getDir("TmpD", [], true);
-  tempFile.append(goodFileName);
-  if (!tempFile.exists()) {
-    let fileIn = getFileName(goodFileBase, "CurWorkD");
-    fileIn.copyTo(FileUtils.getDir("TmpD", [], true), "");
+function setupTestFiles() {
+  for (let fileName of tempFileNames) {
+    let tempFile = FileUtils.getDir("TmpD", [], true);
+    tempFile.append(fileName);
+    if (!tempFile.exists()) {
+      let fileIn = getFileName(path + fileName, "CurWorkD");
+      fileIn.copyTo(FileUtils.getDir("TmpD", [], true), "");
+    }
   }
 }
 
 function getFileName(filePath, dir) {
   // 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.
   let testFile =
     Cc["@mozilla.org/file/directory_service;1"].
@@ -80,16 +89,24 @@ function truncateFile(aFile, length) {
     file.close();
   } catch (e) {
     dump(">>> Error in truncateFile "+e);
     return "Error";
   }
   return "Done";
 }
 
+function cleanupTestFiles() {
+  for (let fileName of tempFileNames) {
+    let tempFile = FileUtils.getDir("TmpD", [], true);
+    tempFile.append(fileName);
+    tempFile.remove(true);
+  }
+}
+
 /*
  * handle requests of the following form:
  * sig=good&key=good&file=good&header=good&cached=no to serve pages with
  * content signatures
  *
  * it further handles invalidateFile=yep and validateFile=yep to change the
  * served file
  */
@@ -97,32 +114,61 @@ function handleRequest(request, response
   let params = new URLSearchParams(request.queryString);
   let keyType = params.get("key");
   let signatureType = params.get("sig");
   let fileType = params.get("file");
   let headerType = params.get("header");
   let cached = params.get("cached");
   let invalidateFile = params.get("invalidateFile");
   let validateFile = params.get("validateFile");
+  let resource = params.get("resource");
+
+  if (params.get("cleanup")) {
+    cleanupTestFiles();
+    response.setHeader("Content-Type", "text/html", false);
+    response.write("Done");
+    return;
+  }
+
+  if (resource) {
+    if (resource == "script") {
+      response.setHeader("Content-Type", "application/javascript", false);
+      response.write(loadFile(getFileName(scriptFileName, "TmpD")));
+    } else { // resource == "css1" || resource == "css2"
+      response.setHeader("Content-Type", "text/css", false);
+      response.write(loadFile(getFileName(cssFileName, "TmpD")));
+    }
+    return;
+  }
 
   // if invalidateFile is set, this doesn't actually return a newtab page
   // but changes the served file to invalidate the signature
   // NOTE: make sure to make the file valid again afterwards!
   if (invalidateFile) {
+    let r = "Done";
+    for (let fileName of tempFileNames) {
+      if (appendToFile(getFileName(fileName, "TmpD"), "!") != "Done") {
+        r = "Error";
+      }
+    }
     response.setHeader("Content-Type", "text/html", false);
-    let r = appendToFile(goodFile, "!");
     response.write(r);
     return;
   }
 
   // if validateFile is set, this doesn't actually return a newtab page
   // but changes the served file to make the signature valid again
   if (validateFile) {
+    let r = "Done";
+    for (let fileName of tempFileNames) {
+      if (truncateFile(getFileName(fileName, "TmpD"), 1) != "Done") {
+        r = "Error";
+      }
+    }
     response.setHeader("Content-Type", "text/html", false);
-    let r = truncateFile(goodFile, 1);
     response.write(r);
     return;
   }
 
   // avoid confusing cache behaviours
   if (!cached) {
     response.setHeader("Cache-Control", "no-cache", false);
   } else {
@@ -142,19 +188,23 @@ function handleRequest(request, response
   let file = goodFile;
   if (keyType == "bad") {
     keyId = badKeyId;
   }
   if (signatureType == "bad") {
     signature = badSignature;
   } else if (signatureType == "broken") {
     signature = brokenSignature;
+  } else if (signatureType == "sri") {
+    signature = sriSignature;
   }
   if (fileType == "bad") {
     file = getFileName(badFile, "CurWorkD");
+  } else if (fileType == "sri") {
+    file = getFileName(sriFile, "CurWorkD");
   }
 
   if (headerType == "good") {
     // a valid content-signature header
     csHeader = "keyid=" + keyId + ";p384ecdsa=" +
                loadFile(getFileName(signature, "CurWorkD"));
   } else if (headerType == "error") {
     // this content-signature header is missing ; before p384ecdsa
new file mode 100644
--- /dev/null
+++ b/dom/security/test/contentverifier/script.js
@@ -0,0 +1,1 @@
+var load=true;
new file mode 100644
--- /dev/null
+++ b/dom/security/test/contentverifier/style.css
@@ -0,0 +1,3 @@
+#red-text {
+  color: red;
+}