1/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ 2/* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5// sjs for remote about:newtab (bug 1226928) 6"use strict"; 7 8const {classes: Cc, interfaces: Ci, utils: Cu} = Components; 9Cu.import("resource://gre/modules/NetUtil.jsm"); 10Cu.import("resource://gre/modules/FileUtils.jsm"); 11Cu.importGlobalProperties(["URLSearchParams"]); 12 13const path = "browser/dom/security/test/contentverifier/"; 14 15const goodFileName = "file_about_newtab.html"; 16const goodFileBase = path + goodFileName; 17const goodFile = FileUtils.getDir("TmpD", [], true); 18goodFile.append(goodFileName); 19const goodSignature = path + "file_about_newtab_good_signature"; 20const goodX5UString = "\"https://example.com/browser/dom/security/test/contentverifier/file_contentserver.sjs?x5u=default\""; 21 22const scriptFileName = "script.js"; 23const cssFileName = "style.css"; 24const badFile = path + "file_about_newtab_bad.html"; 25const brokenSignature = path + "file_about_newtab_broken_signature"; 26const badSignature = path + "file_about_newtab_bad_signature"; 27const badX5UString = "\"https://example.com/browser/dom/security/test/contentverifier/file_contentserver.sjs?x5u=bad\""; 28const httpX5UString = "\"http://example.com/browser/dom/security/test/contentverifier/file_contentserver.sjs?x5u=default\""; 29 30const sriFile = path + "file_about_newtab_sri.html"; 31const sriSignature = path + "file_about_newtab_sri_signature"; 32 33const badCspFile = path + "file_about_newtab_bad_csp.html"; 34const badCspSignature = path + "file_about_newtab_bad_csp_signature"; 35 36// This cert chain is copied from 37// security/manager/ssl/tests/unit/test_content_signing/ 38// using the certificates 39// * content_signing_remote_newtab_ee.pem 40// * content_signing_int.pem 41// * content_signing_root.pem 42const goodCertChainPath = path + "goodChain.pem"; 43 44const tempFileNames = [goodFileName, scriptFileName, cssFileName]; 45 46// we copy the file to serve as newtab to a temp directory because 47// we modify it during tests. 48setupTestFiles(); 49 50function setupTestFiles() { 51 for (let fileName of tempFileNames) { 52 let tempFile = FileUtils.getDir("TmpD", [], true); 53 tempFile.append(fileName); 54 if (!tempFile.exists()) { 55 let fileIn = getFileName(path + fileName, "CurWorkD"); 56 fileIn.copyTo(FileUtils.getDir("TmpD", [], true), ""); 57 } 58 } 59} 60 61function getFileName(filePath, dir) { 62 // Since it's relative to the cwd of the test runner, we start there and 63 // append to get to the actual path of the file. 64 let testFile = 65 Cc["@mozilla.org/file/directory_service;1"]. 66 getService(Components.interfaces.nsIProperties). 67 get(dir, Components.interfaces.nsILocalFile); 68 let dirs = filePath.split("/"); 69 for (let i = 0; i < dirs.length; i++) { 70 testFile.append(dirs[i]); 71 } 72 return testFile; 73} 74 75function loadFile(file) { 76 // Load a file to return it. 77 let testFileStream = 78 Cc["@mozilla.org/network/file-input-stream;1"] 79 .createInstance(Components.interfaces.nsIFileInputStream); 80 testFileStream.init(file, -1, 0, 0); 81 return NetUtil.readInputStreamToString(testFileStream, 82 testFileStream.available()); 83} 84 85function appendToFile(aFile, content) { 86 try { 87 let file = FileUtils.openFileOutputStream(aFile, FileUtils.MODE_APPEND | 88 FileUtils.MODE_WRONLY); 89 file.write(content, content.length); 90 file.close(); 91 } catch (e) { 92 dump(">>> Error in appendToFile "+e); 93 return "Error"; 94 } 95 return "Done"; 96} 97 98function truncateFile(aFile, length) { 99 let fileIn = loadFile(aFile); 100 fileIn = fileIn.slice(0, -length); 101 102 try { 103 let file = FileUtils.openFileOutputStream(aFile, FileUtils.MODE_WRONLY | 104 FileUtils.MODE_TRUNCATE); 105 file.write(fileIn, fileIn.length); 106 file.close(); 107 } catch (e) { 108 dump(">>> Error in truncateFile "+e); 109 return "Error"; 110 } 111 return "Done"; 112} 113 114function cleanupTestFiles() { 115 for (let fileName of tempFileNames) { 116 let tempFile = FileUtils.getDir("TmpD", [], true); 117 tempFile.append(fileName); 118 tempFile.remove(true); 119 } 120} 121 122/* 123 * handle requests of the following form: 124 * sig=good&key=good&file=good&header=good&cached=no to serve pages with 125 * content signatures 126 * 127 * it further handles invalidateFile=yep and validateFile=yep to change the 128 * served file 129 */ 130function handleRequest(request, response) { 131 let params = new URLSearchParams(request.queryString); 132 let x5uType = params.get("x5u"); 133 let signatureType = params.get("sig"); 134 let fileType = params.get("file"); 135 let headerType = params.get("header"); 136 let cached = params.get("cached"); 137 let invalidateFile = params.get("invalidateFile"); 138 let validateFile = params.get("validateFile"); 139 let resource = params.get("resource"); 140 let x5uParam = params.get("x5u"); 141 142 if (params.get("cleanup")) { 143 cleanupTestFiles(); 144 response.setHeader("Content-Type", "text/html", false); 145 response.write("Done"); 146 return; 147 } 148 149 if (resource) { 150 if (resource == "script") { 151 response.setHeader("Content-Type", "application/javascript", false); 152 response.write(loadFile(getFileName(scriptFileName, "TmpD"))); 153 } else { // resource == "css1" || resource == "css2" 154 response.setHeader("Content-Type", "text/css", false); 155 response.write(loadFile(getFileName(cssFileName, "TmpD"))); 156 } 157 return; 158 } 159 160 // if invalidateFile is set, this doesn't actually return a newtab page 161 // but changes the served file to invalidate the signature 162 // NOTE: make sure to make the file valid again afterwards! 163 if (invalidateFile) { 164 let r = "Done"; 165 for (let fileName of tempFileNames) { 166 if (appendToFile(getFileName(fileName, "TmpD"), "!") != "Done") { 167 r = "Error"; 168 } 169 } 170 response.setHeader("Content-Type", "text/html", false); 171 response.write(r); 172 return; 173 } 174 175 // if validateFile is set, this doesn't actually return a newtab page 176 // but changes the served file to make the signature valid again 177 if (validateFile) { 178 let r = "Done"; 179 for (let fileName of tempFileNames) { 180 if (truncateFile(getFileName(fileName, "TmpD"), 1) != "Done") { 181 r = "Error"; 182 } 183 } 184 response.setHeader("Content-Type", "text/html", false); 185 response.write(r); 186 return; 187 } 188 189 // we have to return the certificate chain on request for the x5u parameter 190 if (x5uParam && x5uParam == "default") { 191 response.setHeader("Cache-Control", "max-age=216000", false); 192 response.setHeader("Content-Type", "text/plain", false); 193 response.write(loadFile(getFileName(goodCertChainPath, "CurWorkD"))); 194 return; 195 } 196 197 // avoid confusing cache behaviours 198 if (!cached) { 199 response.setHeader("Cache-Control", "no-cache", false); 200 } else { 201 response.setHeader("Cache-Control", "max-age=3600", false); 202 } 203 204 // send HTML to test allowed/blocked behaviours 205 response.setHeader("Content-Type", "text/html", false); 206 207 // set signature header and key for Content-Signature header 208 /* By default a good content-signature header is returned. Any broken return 209 * value has to be indicated in the url. 210 */ 211 let csHeader = ""; 212 let x5uString = goodX5UString; 213 let signature = goodSignature; 214 let file = goodFile; 215 if (x5uType == "bad") { 216 x5uString = badX5UString; 217 } else if (x5uType == "http") { 218 x5uString = httpX5UString; 219 } 220 if (signatureType == "bad") { 221 signature = badSignature; 222 } else if (signatureType == "broken") { 223 signature = brokenSignature; 224 } else if (signatureType == "sri") { 225 signature = sriSignature; 226 } else if (signatureType == "bad-csp") { 227 signature = badCspSignature; 228 } 229 if (fileType == "bad") { 230 file = getFileName(badFile, "CurWorkD"); 231 } else if (fileType == "sri") { 232 file = getFileName(sriFile, "CurWorkD"); 233 } else if (fileType == "bad-csp") { 234 file = getFileName(badCspFile, "CurWorkD"); 235 } 236 237 if (headerType == "good") { 238 // a valid content-signature header 239 csHeader = "x5u=" + x5uString + ";p384ecdsa=" + 240 loadFile(getFileName(signature, "CurWorkD")); 241 } else if (headerType == "error") { 242 // this content-signature header is missing ; before p384ecdsa 243 csHeader = "x5u=" + x5uString + "p384ecdsa=" + 244 loadFile(getFileName(signature, "CurWorkD")); 245 } else if (headerType == "errorInX5U") { 246 // this content-signature header is missing the keyid directive 247 csHeader = "x6u=" + x5uString + ";p384ecdsa=" + 248 loadFile(getFileName(signature, "CurWorkD")); 249 } else if (headerType == "errorInSignature") { 250 // this content-signature header is missing the p384ecdsa directive 251 csHeader = "x5u=" + x5uString + ";p385ecdsa=" + 252 loadFile(getFileName(signature, "CurWorkD")); 253 } 254 255 if (csHeader) { 256 response.setHeader("Content-Signature", csHeader, false); 257 } 258 let result = loadFile(file); 259 260 response.write(result); 261} 262