Bug 1226928 - content-signature verification tests for about:newtab, r=mconley
authorFranziskus Kiefer <franziskuskiefer@gmail.com>
Mon, 14 Mar 2016 11:57:16 +0100
changeset 288535 4c29953376d9d549820a49ef68c02571f56d1dee
parent 288534 7829b99f0d59d6c5fce347e3a43336f9a4a94710
child 288536 e1f5afcd210c857cb96fd7eeb578be3655737854
push id73442
push userttaubert@mozilla.com
push dateMon, 14 Mar 2016 11:00:12 +0000
treeherdermozilla-inbound@4c29953376d9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmconley
bugs1226928
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 1226928 - content-signature verification tests for about:newtab, r=mconley
dom/security/test/contentverifier/browser.ini
dom/security/test/contentverifier/browser_verify_content_about_newtab.js
dom/security/test/contentverifier/file_about_newtab.html
dom/security/test/contentverifier/file_about_newtab_bad.html
dom/security/test/contentverifier/file_about_newtab_bad_signature
dom/security/test/contentverifier/file_about_newtab_broken_signature
dom/security/test/contentverifier/file_about_newtab_good_signature
dom/security/test/contentverifier/file_contentserver.sjs
dom/security/test/contentverifier/signature.der
dom/security/test/contentverifier/sk.pem
dom/security/test/moz.build
new file mode 100644
--- /dev/null
+++ b/dom/security/test/contentverifier/browser.ini
@@ -0,0 +1,10 @@
+[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
+
+[browser_verify_content_about_newtab.js]
new file mode 100644
--- /dev/null
+++ b/dom/security/test/contentverifier/browser_verify_content_about_newtab.js
@@ -0,0 +1,188 @@
+/*
+ * Test Content-Signature for remote about:newtab
+ *  - Bug 1226928 - allow about:newtab to load remote content
+ *
+ * This tests content-signature verification on remote about:newtab in the
+ * following cases (see TESTS, all failed loads display about:blank fallback):
+ * - good case (signature should verify and correct page is displayed)
+ * - reload of newtab when the siganture was invalidated after the last correct
+ *   load
+ * - malformed content-signature header
+ * - malformed keyid directive
+ * - malformed p384ecdsa directive
+ * - wrong signature (this is not a siganture for the delivered document)
+ * - invalid signature (this is not even a signature)
+ * - loading a file that doesn't fit the key or signature
+ * - cache poisoning (load a malicious remote page not in newtab, subsequent
+ *   newtab load has to load the fallback)
+ */
+
+const ABOUT_NEWTAB_URI = "about:newtab";
+
+const BASE = "https://example.com/browser/dom/security/test/contentverifier/file_contentserver.sjs?";
+const URI_GOOD = BASE + "sig=good&key=good&file=good&header=good";
+
+const INVALIDATE_FILE = BASE + "invalidateFile=yep";
+const VALIDATE_FILE = BASE + "validateFile=yep";
+
+const URI_HEADER_BASE = BASE + "sig=good&key=good&file=good&header=";
+const URI_ERROR_HEADER = URI_HEADER_BASE + "error";
+const URI_KEYERROR_HEADER = URI_HEADER_BASE + "errorInKeyid";
+const URI_SIGERROR_HEADER = URI_HEADER_BASE + "errorInSignature";
+const URI_NO_HEADER = URI_HEADER_BASE + "noHeader";
+
+const URI_BAD_SIG = BASE + "sig=bad&key=good&file=good&header=good";
+const URI_BROKEN_SIG = BASE + "sig=broken&key=good&file=good&header=good";
+const URI_BAD_KEY = BASE + "sig=good&key=bad&file=good&header=good";
+const URI_BAD_FILE = BASE + "sig=good&key=good&file=bad&header=good";
+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 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 }
+];
+
+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) {
+  // 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],
+      ["browser.newtabpage.remote", true], [
+        "browser.newtabpage.remote.keys",
+        "RemoteNewTabNightlyv0=BO9QHuP6E2eLKybql8iuD4o4Np9YFDfW3D+k" +
+        "a70EcXXTqZcikc7Am1CwyP1xBDTpEoe6gb9SWzJmaDW3dNh1av2u90VkUM" +
+        "B7aHIrImjTjLNg/1oC8GRcTKM4+WzbKF00iA==;OtherKey=eKQJ2fNSId" +
+        "CFzL6N326EzZ/5LCeFU5eyq3enwZ5MLmvOw+3gycr4ZVRc36/EiSPsQYHE" +
+        "3JvJs1EKs0QCaguHFOZsHwqXMPicwp/gLdeYbuOmN2s1SEf/cxw8GtcxSA" +
+        "kG;RemoteNewTab=MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE4k3FmG7dFo" +
+        "Ot3Tuzl76abTRtK8sb/r/ibCSeVKa96RbrOX2ciscz/TT8wfqBYS/8cN4z" +
+        "Me1+f7wRmkNrCUojZR1ZKmYM2BeiUOMlMoqk2O7+uwsn1DwNQSYP58TkvZt6"
+      ]);
+
+  // start the test
+  yield BrowserTestUtils.withNewTab({
+      gBrowser,
+      url: aUrl,
+    },
+    function * (browser) {
+      // check if everything's set correct for testing
+      ok(Services.prefs.getBoolPref(
+          "browser.newtabpage.remote.content-signing-test"),
+          "sanity check: remote newtab signing test should be used");
+      ok(Services.prefs.getBoolPref("browser.newtabpage.remote"),
+          "sanity check: remote newtab should be used");
+      // 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);
+          });
+
+      // 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,
+          },
+          function * (browser2) {
+            yield ContentTask.spawn(browser2, null, function * () {
+              ok(content.document.documentElement.innerHTML.includes("Done"),
+                 "Expect the following value in the result\n" + "Done" +
+                 "\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);
+          }
+        );
+
+        yield BrowserTestUtils.withNewTab({
+            gBrowser,
+            url: VALIDATE_FILE,
+          },
+          function * (browser2) {
+            yield ContentTask.spawn(browser2, null, function * () {
+              ok(content.document.documentElement.innerHTML.includes("Done"),
+                 "Expect the following value in the result\n" + "Done" +
+                 "\nand got " + content.document.documentElement.innerHTML);
+              });
+          }
+        );
+      }
+    }
+  );
+}
+
+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;
+    if (testCase.aboutURI) {
+      url = ABOUT_NEWTAB_URI;
+      aNewTabPref = testCase.aboutURI;
+      if (aExpectedString == GOOD_ABOUT_STRING) {
+        reload = true;
+      }
+    } else {
+      url = testCase.url;
+    }
+
+    yield doTest(aExpectedString, reload, url, aNewTabPref);
+  }
+});
new file mode 100644
--- /dev/null
+++ b/dom/security/test/contentverifier/file_about_newtab.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+<!-- https://bugzilla.mozilla.org/show_bug.cgi?id=1226928 -->
+<head>
+  <meta charset="utf-8">
+  <title>Testpage for bug 1226928</title>
+</head>
+<body>
+  Just a fully good testpage for Bug 1226928<br/>
+</body>
+</html>
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/security/test/contentverifier/file_about_newtab_bad.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+<!-- https://bugzilla.mozilla.org/show_bug.cgi?id=1226928 -->
+<head>
+  <meta charset="utf-8">
+  <title>Testpage for bug 1226928</title>
+</head>
+<body>
+  Just a bad testpage for Bug 1226928<br/>
+</body>
+</html>
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/security/test/contentverifier/file_about_newtab_bad_signature
@@ -0,0 +1,1 @@
+KirX94omQL7lKfWGhc777t8U29enDg0O0UcJLH3PRXcvWGO8KA6mmLS3yNCFnGiTjP3vNnVtm-sUkXr4ix8WTkKABkU4fEAi77sNOkLCKw40M9sDJOesmYInS_J2AuXX
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/security/test/contentverifier/file_about_newtab_broken_signature
@@ -0,0 +1,1 @@
+MGUCMFwSs3o95ukwBWXN1WbLgnpJ_uHWFiQROPm9zjrSqzlfiSMyLwJwIZzldWo_pBJtOwIxAJIfhXIiMVfl5NkFEJUUMxzu6FuxOJl5DCpG2wHLy9AhayLUzm4X4SpwZ6QBPapdTg
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/security/test/contentverifier/file_about_newtab_good_signature
@@ -0,0 +1,1 @@
+XBKzej3i6TAFZc3VZsuCekn-4dYWJBE4-b3OOtKrOV-JIzIvAnAhnOV1aj-kEm07kh-FciIxV-Xk2QUQlRQzHO7oW7E4mXkMKkbbAcvL0CFrItTObhfhKnBnpAE9ql1O
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/security/test/contentverifier/file_contentserver.sjs
@@ -0,0 +1,179 @@
+// sjs for remote about:newtab (bug 1226928)
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://gre/modules/FileUtils.jsm");
+Cu.importGlobalProperties(["URLSearchParams"]);
+
+const path = "browser/dom/security/test/contentverifier/";
+
+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 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";
+
+// we copy the file to serve as newtab to a temp directory because
+// we modify it during tests.
+setupTestFile();
+
+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 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"].
+      getService(Components.interfaces.nsIProperties).
+      get(dir, Components.interfaces.nsILocalFile);
+  let dirs = filePath.split("/");
+  for (let i = 0; i < dirs.length; i++) {
+    testFile.append(dirs[i]);
+  }
+  return testFile;
+}
+
+function loadFile(file) {
+  // Load a file to return it.
+  let testFileStream =
+    Cc["@mozilla.org/network/file-input-stream;1"]
+      .createInstance(Components.interfaces.nsIFileInputStream);
+  testFileStream.init(file, -1, 0, 0);
+  return NetUtil.readInputStreamToString(testFileStream,
+                                         testFileStream.available());
+}
+
+function appendToFile(aFile, content) {
+  try {
+    let file = FileUtils.openFileOutputStream(aFile, FileUtils.MODE_APPEND |
+                                                     FileUtils.MODE_WRONLY);
+    file.write(content, content.length);
+    file.close();
+  } catch (e) {
+    dump(">>> Error in appendToFile "+e);
+    return "Error";
+  }
+  return "Done";
+}
+
+function truncateFile(aFile, length) {
+  let fileIn = loadFile(aFile);
+  fileIn = fileIn.slice(0, -length);
+
+  try {
+    let file = FileUtils.openFileOutputStream(aFile, FileUtils.MODE_WRONLY |
+                                                     FileUtils.MODE_TRUNCATE);
+    file.write(fileIn, fileIn.length);
+    file.close();
+  } catch (e) {
+    dump(">>> Error in truncateFile "+e);
+    return "Error";
+  }
+  return "Done";
+}
+
+/*
+ * 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
+ */
+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");
+
+  // 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) {
+    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) {
+    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 {
+    response.setHeader("Cache-Control", "max-age=3600", false);
+  }
+
+  // send HTML to test allowed/blocked behaviours
+  response.setHeader("Content-Type", "text/html", false);
+
+  // set signature header and key for Content-Signature header
+  /* By default a good content-signature header is returned. Any broken return
+   * value has to be indicated in the url.
+   */
+  let csHeader = "";
+  let keyId = goodKeyId;
+  let signature = goodSignature;
+  let file = goodFile;
+  if (keyType == "bad") {
+    keyId = badKeyId;
+  }
+  if (signatureType == "bad") {
+    signature = badSignature;
+  } else if (signatureType == "broken") {
+    signature = brokenSignature;
+  }
+  if (fileType == "bad") {
+    file = getFileName(badFile, "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
+    csHeader = "keyid=" + keyId + "p384ecdsa=" +
+               loadFile(getFileName(signature, "CurWorkD"));
+  } else if (headerType == "errorInKeyid") {
+    // this content-signature header is missing the keyid directive
+    csHeader = "keid=" + keyId + ";p384ecdsa=" +
+               loadFile(getFileName(signature, "CurWorkD"));
+  } else if (headerType == "errorInSignature") {
+    // this content-signature header is missing the p384ecdsa directive
+    csHeader = "keyid=" + keyId + ";p385ecdsa=" +
+               loadFile(getFileName(signature, "CurWorkD"));
+  }
+
+  if (csHeader) {
+    response.setHeader("Content-Signature", csHeader, false);
+  }
+  let result = loadFile(file);
+
+  response.write(result);
+}
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..011b94142c3d79406bbd812a154ade1531a5a002
GIT binary patch
literal 103
zc$@)e0GR(UWdbl<60>?e;^{C2WzE%Q%Yu4I{^8aZBoR3Iz0Nw)t2tkZBQh@na3P%K
zb!tDP5^XyIF#wVug>oV>SLNi{1rU`KGaT;dTd_Eqc?>E>+X2hV&>?Fg)Xr`f;VN)v
JqyasuT~785E71S|
new file mode 100644
--- /dev/null
+++ b/dom/security/test/contentverifier/sk.pem
@@ -0,0 +1,9 @@
+-----BEGIN EC PARAMETERS-----
+BgUrgQQAIg==
+-----END EC PARAMETERS-----
+-----BEGIN EC PRIVATE KEY-----
+MIGkAgEBBDAzX2TrGOr0WE92AbAl+nqnpqh25pKCLYNMTV2hJHztrkVPWOp8w0mh
+scIodK8RMpagBwYFK4EEACKhZANiAATiTcWYbt0Wg63dO7OXvpptNG0ryxv+v+Js
+JJ5Upr3pFus5fZyKxzP9NPzB+oFhL/xw3jMx7X5/vBGaQ2sJSiNlHVkqZgzYF6JQ
+4yUyiqTY7v67CyfUPA1BJg/nxOS9m3o=
+-----END EC PRIVATE KEY-----
--- a/dom/security/test/moz.build
+++ b/dom/security/test/moz.build
@@ -19,10 +19,11 @@ MOCHITEST_MANIFESTS += [
     'sri/mochitest.ini',
 ]
 
 MOCHITEST_CHROME_MANIFESTS += [
     'csp/chrome.ini',
 ]
 
 BROWSER_CHROME_MANIFESTS += [
+    'contentverifier/browser.ini',
     'csp/browser.ini',
 ]