Bug 1235572 - Tests of enforcing SRI on remote about:newtab r?francois draft
authorJonathan Hao <jhao@mozilla.com>
Wed, 16 Mar 2016 11:19:20 +0800
changeset 340869 8774d06061b4
parent 340868 8d6a126616f5
child 516287 671d60943bc6
push id13084
push userjhao@mozilla.com
push dateWed, 16 Mar 2016 03:19:30 +0000
reviewersfrancois
bugs1235572
milestone48.0a1
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;
+}