/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ // sjs for remote about:newtab (bug 1226928) "use strict"; 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 goodX5UString = "\"https://example.com/browser/dom/security/test/contentverifier/file_contentserver.sjs?x5u=default\""; 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 badX5UString = "\"https://example.com/browser/dom/security/test/contentverifier/file_contentserver.sjs?x5u=bad\""; const httpX5UString = "\"http://example.com/browser/dom/security/test/contentverifier/file_contentserver.sjs?x5u=default\""; const sriFile = path + "file_about_newtab_sri.html"; const sriSignature = path + "file_about_newtab_sri_signature"; const badCspFile = path + "file_about_newtab_bad_csp.html"; const badCspSignature = path + "file_about_newtab_bad_csp_signature"; // This cert chain is copied from // security/manager/ssl/tests/unit/test_content_signing/ // using the certificates // * content_signing_remote_newtab_ee.pem // * content_signing_int.pem // * content_signing_root.pem const goodCertChainPath = path + "goodChain.pem"; const tempFileNames = [goodFileName, scriptFileName, cssFileName]; // we copy the file to serve as newtab to a temp directory because // we modify it during tests. setupTestFiles(); 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"]. 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"; } 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 */ function handleRequest(request, response) { let params = new URLSearchParams(request.queryString); let x5uType = params.get("x5u"); 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"); let x5uParam = params.get("x5u"); 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); 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); response.write(r); return; } // we have to return the certificate chain on request for the x5u parameter if (x5uParam && x5uParam == "default") { response.setHeader("Cache-Control", "max-age=216000", false); response.setHeader("Content-Type", "text/plain", false); response.write(loadFile(getFileName(goodCertChainPath, "CurWorkD"))); 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 x5uString = goodX5UString; let signature = goodSignature; let file = goodFile; if (x5uType == "bad") { x5uString = badX5UString; } else if (x5uType == "http") { x5uString = httpX5UString; } if (signatureType == "bad") { signature = badSignature; } else if (signatureType == "broken") { signature = brokenSignature; } else if (signatureType == "sri") { signature = sriSignature; } else if (signatureType == "bad-csp") { signature = badCspSignature; } if (fileType == "bad") { file = getFileName(badFile, "CurWorkD"); } else if (fileType == "sri") { file = getFileName(sriFile, "CurWorkD"); } else if (fileType == "bad-csp") { file = getFileName(badCspFile, "CurWorkD"); } if (headerType == "good") { // a valid content-signature header csHeader = "x5u=" + x5uString + ";p384ecdsa=" + loadFile(getFileName(signature, "CurWorkD")); } else if (headerType == "error") { // this content-signature header is missing ; before p384ecdsa csHeader = "x5u=" + x5uString + "p384ecdsa=" + loadFile(getFileName(signature, "CurWorkD")); } else if (headerType == "errorInX5U") { // this content-signature header is missing the keyid directive csHeader = "x6u=" + x5uString + ";p384ecdsa=" + loadFile(getFileName(signature, "CurWorkD")); } else if (headerType == "errorInSignature") { // this content-signature header is missing the p384ecdsa directive csHeader = "x5u=" + x5uString + ";p385ecdsa=" + loadFile(getFileName(signature, "CurWorkD")); } if (csHeader) { response.setHeader("Content-Signature", csHeader, false); } let result = loadFile(file); response.write(result); }