1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/security/manager/ssl/tests/unit/test_signed_apps.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,227 @@ 1.4 +"use strict"; 1.5 +/* To regenerate the certificates and apps for this test: 1.6 + 1.7 + cd security/manager/ssl/tests/unit/test_signed_apps 1.8 + PATH=$NSS/bin:$NSS/lib:$PATH ./generate.sh 1.9 + cd ../../../../../.. 1.10 + make -C $OBJDIR/security/manager/ssl/tests 1.11 + 1.12 + $NSS is the path to NSS binaries and libraries built for the host platform. 1.13 + If you get error messages about "CertUtil" on Windows, then it means that 1.14 + the Windows CertUtil.exe is ahead of the NSS certutil.exe in $PATH. 1.15 + 1.16 + Check in the generated files. These steps are not done as part of the build 1.17 + because we do not want to add a build-time dependency on the OpenSSL or NSS 1.18 + tools or libraries built for the host platform. 1.19 +*/ 1.20 + 1.21 +// XXX from prio.h 1.22 +const PR_RDWR = 0x04; 1.23 +const PR_CREATE_FILE = 0x08; 1.24 +const PR_TRUNCATE = 0x20; 1.25 + 1.26 +do_get_profile(); // must be called before getting nsIX509CertDB 1.27 +const certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(Ci.nsIX509CertDB); 1.28 + 1.29 +// Creates a new app package based in the inFilePath package, with a set of 1.30 +// modifications (including possibly deletions) applied to the existing entries, 1.31 +// and/or a set of new entries to be included. 1.32 +function tamper(inFilePath, outFilePath, modifications, newEntries) { 1.33 + var writer = Cc["@mozilla.org/zipwriter;1"].createInstance(Ci.nsIZipWriter); 1.34 + writer.open(outFilePath, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE); 1.35 + try { 1.36 + var reader = Cc["@mozilla.org/libjar/zip-reader;1"].createInstance(Ci.nsIZipReader); 1.37 + reader.open(inFilePath); 1.38 + try { 1.39 + var entries = reader.findEntries(""); 1.40 + while (entries.hasMore()) { 1.41 + var entryName = entries.getNext(); 1.42 + var inEntry = reader.getEntry(entryName); 1.43 + var entryInput = reader.getInputStream(entryName); 1.44 + try { 1.45 + var f = modifications[entryName]; 1.46 + var outEntry, outEntryInput; 1.47 + if (f) { 1.48 + [outEntry, outEntryInput] = f(inEntry, entryInput); 1.49 + delete modifications[entryName]; 1.50 + } else { 1.51 + [outEntry, outEntryInput] = [inEntry, entryInput]; 1.52 + } 1.53 + // if f does not want the input entry to be copied to the output entry 1.54 + // at all (i.e. it wants it to be deleted), it will return null. 1.55 + if (outEntryInput) { 1.56 + try { 1.57 + writer.addEntryStream(entryName, 1.58 + outEntry.lastModifiedTime, 1.59 + outEntry.compression, 1.60 + outEntryInput, 1.61 + false); 1.62 + } finally { 1.63 + if (entryInput != outEntryInput) 1.64 + outEntryInput.close(); 1.65 + } 1.66 + } 1.67 + } finally { 1.68 + entryInput.close(); 1.69 + } 1.70 + } 1.71 + } finally { 1.72 + reader.close(); 1.73 + } 1.74 + 1.75 + // Any leftover modification means that we were expecting to modify an entry 1.76 + // in the input file that wasn't there. 1.77 + for(var name in modifications) { 1.78 + if (modifications.hasOwnProperty(name)) { 1.79 + throw "input file was missing expected entries: " + name; 1.80 + } 1.81 + } 1.82 + 1.83 + // Now, append any new entries to the end 1.84 + newEntries.forEach(function(newEntry) { 1.85 + var sis = Cc["@mozilla.org/io/string-input-stream;1"] 1.86 + .createInstance(Ci.nsIStringInputStream); 1.87 + try { 1.88 + sis.setData(newEntry.content, newEntry.content.length); 1.89 + writer.addEntryStream(newEntry.name, 1.90 + new Date(), 1.91 + Ci.nsIZipWriter.COMPRESSION_BEST, 1.92 + sis, 1.93 + false); 1.94 + } finally { 1.95 + sis.close(); 1.96 + } 1.97 + }); 1.98 + } finally { 1.99 + writer.close(); 1.100 + } 1.101 +} 1.102 + 1.103 +function removeEntry(entry, entryInput) { return [null, null]; } 1.104 + 1.105 +function truncateEntry(entry, entryInput) { 1.106 + if (entryInput.available() == 0) 1.107 + throw "Truncating already-zero length entry will result in identical entry."; 1.108 + 1.109 + var content = Cc["@mozilla.org/io/string-input-stream;1"] 1.110 + .createInstance(Ci.nsIStringInputStream); 1.111 + content.data = ""; 1.112 + 1.113 + return [entry, content] 1.114 +} 1.115 + 1.116 +function run_test() { 1.117 + run_next_test(); 1.118 +} 1.119 + 1.120 +function check_open_result(name, expectedRv) { 1.121 + return function openSignedAppFileCallback(rv, aZipReader, aSignerCert) { 1.122 + do_print("openSignedAppFileCallback called for " + name); 1.123 + do_check_eq(rv, expectedRv); 1.124 + do_check_eq(aZipReader != null, Components.isSuccessCode(expectedRv)); 1.125 + do_check_eq(aSignerCert != null, Components.isSuccessCode(expectedRv)); 1.126 + run_next_test(); 1.127 + }; 1.128 +} 1.129 + 1.130 +function original_app_path(test_name) { 1.131 + return do_get_file("test_signed_apps/" + test_name + ".zip", false); 1.132 +} 1.133 + 1.134 +function tampered_app_path(test_name) { 1.135 + return FileUtils.getFile("TmpD", ["test_signed_app-" + test_name + ".zip"]); 1.136 +} 1.137 + 1.138 +add_test(function () { 1.139 + certdb.openSignedAppFileAsync( 1.140 + Ci.nsIX509CertDB.AppXPCShellRoot, original_app_path("valid_app_1"), 1.141 + check_open_result("valid", Cr.NS_OK)); 1.142 +}); 1.143 + 1.144 +add_test(function () { 1.145 + certdb.openSignedAppFileAsync( 1.146 + Ci.nsIX509CertDB.AppXPCShellRoot, original_app_path("unsigned_app_1"), 1.147 + check_open_result("unsigned", Cr.NS_ERROR_SIGNED_JAR_NOT_SIGNED)); 1.148 +}); 1.149 + 1.150 +add_test(function () { 1.151 + certdb.openSignedAppFileAsync( 1.152 + Ci.nsIX509CertDB.AppXPCShellRoot, original_app_path("unknown_issuer_app_1"), 1.153 + check_open_result("unknown_issuer", 1.154 + getXPCOMStatusFromNSS(SEC_ERROR_UNKNOWN_ISSUER))); 1.155 +}); 1.156 + 1.157 +// Sanity check to ensure a no-op tampering gives a valid result 1.158 +add_test(function () { 1.159 + var tampered = tampered_app_path("identity_tampering"); 1.160 + tamper(original_app_path("valid_app_1"), tampered, { }, []); 1.161 + certdb.openSignedAppFileAsync( 1.162 + Ci.nsIX509CertDB.AppXPCShellRoot, original_app_path("valid_app_1"), 1.163 + check_open_result("identity_tampering", Cr.NS_OK)); 1.164 +}); 1.165 + 1.166 +add_test(function () { 1.167 + var tampered = tampered_app_path("missing_rsa"); 1.168 + tamper(original_app_path("valid_app_1"), tampered, { "META-INF/A.RSA" : removeEntry }, []); 1.169 + certdb.openSignedAppFileAsync( 1.170 + Ci.nsIX509CertDB.AppXPCShellRoot, tampered, 1.171 + check_open_result("missing_rsa", Cr.NS_ERROR_SIGNED_JAR_NOT_SIGNED)); 1.172 +}); 1.173 + 1.174 +add_test(function () { 1.175 + var tampered = tampered_app_path("missing_sf"); 1.176 + tamper(original_app_path("valid_app_1"), tampered, { "META-INF/A.SF" : removeEntry }, []); 1.177 + certdb.openSignedAppFileAsync( 1.178 + Ci.nsIX509CertDB.AppXPCShellRoot, tampered, 1.179 + check_open_result("missing_sf", Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID)); 1.180 +}); 1.181 + 1.182 +add_test(function () { 1.183 + var tampered = tampered_app_path("missing_manifest_mf"); 1.184 + tamper(original_app_path("valid_app_1"), tampered, { "META-INF/MANIFEST.MF" : removeEntry }, []); 1.185 + certdb.openSignedAppFileAsync( 1.186 + Ci.nsIX509CertDB.AppXPCShellRoot, tampered, 1.187 + check_open_result("missing_manifest_mf", 1.188 + Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID)); 1.189 +}); 1.190 + 1.191 +add_test(function () { 1.192 + var tampered = tampered_app_path("missing_entry"); 1.193 + tamper(original_app_path("valid_app_1"), tampered, { "manifest.webapp" : removeEntry }, []); 1.194 + certdb.openSignedAppFileAsync( 1.195 + Ci.nsIX509CertDB.AppXPCShellRoot, tampered, 1.196 + check_open_result("missing_entry", Cr.NS_ERROR_SIGNED_JAR_ENTRY_MISSING)); 1.197 +}); 1.198 + 1.199 +add_test(function () { 1.200 + var tampered = tampered_app_path("truncated_entry"); 1.201 + tamper(original_app_path("valid_app_1"), tampered, { "manifest.webapp" : truncateEntry }, []); 1.202 + certdb.openSignedAppFileAsync( 1.203 + Ci.nsIX509CertDB.AppXPCShellRoot, tampered, 1.204 + check_open_result("truncated_entry", Cr.NS_ERROR_SIGNED_JAR_MODIFIED_ENTRY)); 1.205 +}); 1.206 + 1.207 +add_test(function () { 1.208 + var tampered = tampered_app_path("unsigned_entry"); 1.209 + tamper(original_app_path("valid_app_1"), tampered, {}, 1.210 + [ { "name": "unsigned.txt", "content": "unsigned content!" } ]); 1.211 + certdb.openSignedAppFileAsync( 1.212 + Ci.nsIX509CertDB.AppXPCShellRoot, tampered, 1.213 + check_open_result("unsigned_entry", Cr.NS_ERROR_SIGNED_JAR_UNSIGNED_ENTRY)); 1.214 +}); 1.215 + 1.216 +add_test(function () { 1.217 + var tampered = tampered_app_path("unsigned_metainf_entry"); 1.218 + tamper(original_app_path("valid_app_1"), tampered, {}, 1.219 + [ { name: "META-INF/unsigned.txt", content: "unsigned content!" } ]); 1.220 + certdb.openSignedAppFileAsync( 1.221 + Ci.nsIX509CertDB.AppXPCShellRoot, tampered, 1.222 + check_open_result("unsigned_metainf_entry", 1.223 + Cr.NS_ERROR_SIGNED_JAR_UNSIGNED_ENTRY)); 1.224 +}); 1.225 + 1.226 +// TODO: tampered MF, tampered SF 1.227 +// TODO: too-large MF, too-large RSA, too-large SF 1.228 +// TODO: MF and SF that end immediately after the last main header 1.229 +// (no CR nor LF) 1.230 +// TODO: broken headers to exercise the parser