Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
michael@0 | 1 | "use strict"; |
michael@0 | 2 | /* To regenerate the certificates and apps for this test: |
michael@0 | 3 | |
michael@0 | 4 | cd security/manager/ssl/tests/unit/test_signed_apps |
michael@0 | 5 | PATH=$NSS/bin:$NSS/lib:$PATH ./generate.sh |
michael@0 | 6 | cd ../../../../../.. |
michael@0 | 7 | make -C $OBJDIR/security/manager/ssl/tests |
michael@0 | 8 | |
michael@0 | 9 | $NSS is the path to NSS binaries and libraries built for the host platform. |
michael@0 | 10 | If you get error messages about "CertUtil" on Windows, then it means that |
michael@0 | 11 | the Windows CertUtil.exe is ahead of the NSS certutil.exe in $PATH. |
michael@0 | 12 | |
michael@0 | 13 | Check in the generated files. These steps are not done as part of the build |
michael@0 | 14 | because we do not want to add a build-time dependency on the OpenSSL or NSS |
michael@0 | 15 | tools or libraries built for the host platform. |
michael@0 | 16 | */ |
michael@0 | 17 | |
michael@0 | 18 | // XXX from prio.h |
michael@0 | 19 | const PR_RDWR = 0x04; |
michael@0 | 20 | const PR_CREATE_FILE = 0x08; |
michael@0 | 21 | const PR_TRUNCATE = 0x20; |
michael@0 | 22 | |
michael@0 | 23 | do_get_profile(); // must be called before getting nsIX509CertDB |
michael@0 | 24 | const certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(Ci.nsIX509CertDB); |
michael@0 | 25 | |
michael@0 | 26 | // Creates a new app package based in the inFilePath package, with a set of |
michael@0 | 27 | // modifications (including possibly deletions) applied to the existing entries, |
michael@0 | 28 | // and/or a set of new entries to be included. |
michael@0 | 29 | function tamper(inFilePath, outFilePath, modifications, newEntries) { |
michael@0 | 30 | var writer = Cc["@mozilla.org/zipwriter;1"].createInstance(Ci.nsIZipWriter); |
michael@0 | 31 | writer.open(outFilePath, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE); |
michael@0 | 32 | try { |
michael@0 | 33 | var reader = Cc["@mozilla.org/libjar/zip-reader;1"].createInstance(Ci.nsIZipReader); |
michael@0 | 34 | reader.open(inFilePath); |
michael@0 | 35 | try { |
michael@0 | 36 | var entries = reader.findEntries(""); |
michael@0 | 37 | while (entries.hasMore()) { |
michael@0 | 38 | var entryName = entries.getNext(); |
michael@0 | 39 | var inEntry = reader.getEntry(entryName); |
michael@0 | 40 | var entryInput = reader.getInputStream(entryName); |
michael@0 | 41 | try { |
michael@0 | 42 | var f = modifications[entryName]; |
michael@0 | 43 | var outEntry, outEntryInput; |
michael@0 | 44 | if (f) { |
michael@0 | 45 | [outEntry, outEntryInput] = f(inEntry, entryInput); |
michael@0 | 46 | delete modifications[entryName]; |
michael@0 | 47 | } else { |
michael@0 | 48 | [outEntry, outEntryInput] = [inEntry, entryInput]; |
michael@0 | 49 | } |
michael@0 | 50 | // if f does not want the input entry to be copied to the output entry |
michael@0 | 51 | // at all (i.e. it wants it to be deleted), it will return null. |
michael@0 | 52 | if (outEntryInput) { |
michael@0 | 53 | try { |
michael@0 | 54 | writer.addEntryStream(entryName, |
michael@0 | 55 | outEntry.lastModifiedTime, |
michael@0 | 56 | outEntry.compression, |
michael@0 | 57 | outEntryInput, |
michael@0 | 58 | false); |
michael@0 | 59 | } finally { |
michael@0 | 60 | if (entryInput != outEntryInput) |
michael@0 | 61 | outEntryInput.close(); |
michael@0 | 62 | } |
michael@0 | 63 | } |
michael@0 | 64 | } finally { |
michael@0 | 65 | entryInput.close(); |
michael@0 | 66 | } |
michael@0 | 67 | } |
michael@0 | 68 | } finally { |
michael@0 | 69 | reader.close(); |
michael@0 | 70 | } |
michael@0 | 71 | |
michael@0 | 72 | // Any leftover modification means that we were expecting to modify an entry |
michael@0 | 73 | // in the input file that wasn't there. |
michael@0 | 74 | for(var name in modifications) { |
michael@0 | 75 | if (modifications.hasOwnProperty(name)) { |
michael@0 | 76 | throw "input file was missing expected entries: " + name; |
michael@0 | 77 | } |
michael@0 | 78 | } |
michael@0 | 79 | |
michael@0 | 80 | // Now, append any new entries to the end |
michael@0 | 81 | newEntries.forEach(function(newEntry) { |
michael@0 | 82 | var sis = Cc["@mozilla.org/io/string-input-stream;1"] |
michael@0 | 83 | .createInstance(Ci.nsIStringInputStream); |
michael@0 | 84 | try { |
michael@0 | 85 | sis.setData(newEntry.content, newEntry.content.length); |
michael@0 | 86 | writer.addEntryStream(newEntry.name, |
michael@0 | 87 | new Date(), |
michael@0 | 88 | Ci.nsIZipWriter.COMPRESSION_BEST, |
michael@0 | 89 | sis, |
michael@0 | 90 | false); |
michael@0 | 91 | } finally { |
michael@0 | 92 | sis.close(); |
michael@0 | 93 | } |
michael@0 | 94 | }); |
michael@0 | 95 | } finally { |
michael@0 | 96 | writer.close(); |
michael@0 | 97 | } |
michael@0 | 98 | } |
michael@0 | 99 | |
michael@0 | 100 | function removeEntry(entry, entryInput) { return [null, null]; } |
michael@0 | 101 | |
michael@0 | 102 | function truncateEntry(entry, entryInput) { |
michael@0 | 103 | if (entryInput.available() == 0) |
michael@0 | 104 | throw "Truncating already-zero length entry will result in identical entry."; |
michael@0 | 105 | |
michael@0 | 106 | var content = Cc["@mozilla.org/io/string-input-stream;1"] |
michael@0 | 107 | .createInstance(Ci.nsIStringInputStream); |
michael@0 | 108 | content.data = ""; |
michael@0 | 109 | |
michael@0 | 110 | return [entry, content] |
michael@0 | 111 | } |
michael@0 | 112 | |
michael@0 | 113 | function run_test() { |
michael@0 | 114 | run_next_test(); |
michael@0 | 115 | } |
michael@0 | 116 | |
michael@0 | 117 | function check_open_result(name, expectedRv) { |
michael@0 | 118 | return function openSignedAppFileCallback(rv, aZipReader, aSignerCert) { |
michael@0 | 119 | do_print("openSignedAppFileCallback called for " + name); |
michael@0 | 120 | do_check_eq(rv, expectedRv); |
michael@0 | 121 | do_check_eq(aZipReader != null, Components.isSuccessCode(expectedRv)); |
michael@0 | 122 | do_check_eq(aSignerCert != null, Components.isSuccessCode(expectedRv)); |
michael@0 | 123 | run_next_test(); |
michael@0 | 124 | }; |
michael@0 | 125 | } |
michael@0 | 126 | |
michael@0 | 127 | function original_app_path(test_name) { |
michael@0 | 128 | return do_get_file("test_signed_apps/" + test_name + ".zip", false); |
michael@0 | 129 | } |
michael@0 | 130 | |
michael@0 | 131 | function tampered_app_path(test_name) { |
michael@0 | 132 | return FileUtils.getFile("TmpD", ["test_signed_app-" + test_name + ".zip"]); |
michael@0 | 133 | } |
michael@0 | 134 | |
michael@0 | 135 | add_test(function () { |
michael@0 | 136 | certdb.openSignedAppFileAsync( |
michael@0 | 137 | Ci.nsIX509CertDB.AppXPCShellRoot, original_app_path("valid_app_1"), |
michael@0 | 138 | check_open_result("valid", Cr.NS_OK)); |
michael@0 | 139 | }); |
michael@0 | 140 | |
michael@0 | 141 | add_test(function () { |
michael@0 | 142 | certdb.openSignedAppFileAsync( |
michael@0 | 143 | Ci.nsIX509CertDB.AppXPCShellRoot, original_app_path("unsigned_app_1"), |
michael@0 | 144 | check_open_result("unsigned", Cr.NS_ERROR_SIGNED_JAR_NOT_SIGNED)); |
michael@0 | 145 | }); |
michael@0 | 146 | |
michael@0 | 147 | add_test(function () { |
michael@0 | 148 | certdb.openSignedAppFileAsync( |
michael@0 | 149 | Ci.nsIX509CertDB.AppXPCShellRoot, original_app_path("unknown_issuer_app_1"), |
michael@0 | 150 | check_open_result("unknown_issuer", |
michael@0 | 151 | getXPCOMStatusFromNSS(SEC_ERROR_UNKNOWN_ISSUER))); |
michael@0 | 152 | }); |
michael@0 | 153 | |
michael@0 | 154 | // Sanity check to ensure a no-op tampering gives a valid result |
michael@0 | 155 | add_test(function () { |
michael@0 | 156 | var tampered = tampered_app_path("identity_tampering"); |
michael@0 | 157 | tamper(original_app_path("valid_app_1"), tampered, { }, []); |
michael@0 | 158 | certdb.openSignedAppFileAsync( |
michael@0 | 159 | Ci.nsIX509CertDB.AppXPCShellRoot, original_app_path("valid_app_1"), |
michael@0 | 160 | check_open_result("identity_tampering", Cr.NS_OK)); |
michael@0 | 161 | }); |
michael@0 | 162 | |
michael@0 | 163 | add_test(function () { |
michael@0 | 164 | var tampered = tampered_app_path("missing_rsa"); |
michael@0 | 165 | tamper(original_app_path("valid_app_1"), tampered, { "META-INF/A.RSA" : removeEntry }, []); |
michael@0 | 166 | certdb.openSignedAppFileAsync( |
michael@0 | 167 | Ci.nsIX509CertDB.AppXPCShellRoot, tampered, |
michael@0 | 168 | check_open_result("missing_rsa", Cr.NS_ERROR_SIGNED_JAR_NOT_SIGNED)); |
michael@0 | 169 | }); |
michael@0 | 170 | |
michael@0 | 171 | add_test(function () { |
michael@0 | 172 | var tampered = tampered_app_path("missing_sf"); |
michael@0 | 173 | tamper(original_app_path("valid_app_1"), tampered, { "META-INF/A.SF" : removeEntry }, []); |
michael@0 | 174 | certdb.openSignedAppFileAsync( |
michael@0 | 175 | Ci.nsIX509CertDB.AppXPCShellRoot, tampered, |
michael@0 | 176 | check_open_result("missing_sf", Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID)); |
michael@0 | 177 | }); |
michael@0 | 178 | |
michael@0 | 179 | add_test(function () { |
michael@0 | 180 | var tampered = tampered_app_path("missing_manifest_mf"); |
michael@0 | 181 | tamper(original_app_path("valid_app_1"), tampered, { "META-INF/MANIFEST.MF" : removeEntry }, []); |
michael@0 | 182 | certdb.openSignedAppFileAsync( |
michael@0 | 183 | Ci.nsIX509CertDB.AppXPCShellRoot, tampered, |
michael@0 | 184 | check_open_result("missing_manifest_mf", |
michael@0 | 185 | Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID)); |
michael@0 | 186 | }); |
michael@0 | 187 | |
michael@0 | 188 | add_test(function () { |
michael@0 | 189 | var tampered = tampered_app_path("missing_entry"); |
michael@0 | 190 | tamper(original_app_path("valid_app_1"), tampered, { "manifest.webapp" : removeEntry }, []); |
michael@0 | 191 | certdb.openSignedAppFileAsync( |
michael@0 | 192 | Ci.nsIX509CertDB.AppXPCShellRoot, tampered, |
michael@0 | 193 | check_open_result("missing_entry", Cr.NS_ERROR_SIGNED_JAR_ENTRY_MISSING)); |
michael@0 | 194 | }); |
michael@0 | 195 | |
michael@0 | 196 | add_test(function () { |
michael@0 | 197 | var tampered = tampered_app_path("truncated_entry"); |
michael@0 | 198 | tamper(original_app_path("valid_app_1"), tampered, { "manifest.webapp" : truncateEntry }, []); |
michael@0 | 199 | certdb.openSignedAppFileAsync( |
michael@0 | 200 | Ci.nsIX509CertDB.AppXPCShellRoot, tampered, |
michael@0 | 201 | check_open_result("truncated_entry", Cr.NS_ERROR_SIGNED_JAR_MODIFIED_ENTRY)); |
michael@0 | 202 | }); |
michael@0 | 203 | |
michael@0 | 204 | add_test(function () { |
michael@0 | 205 | var tampered = tampered_app_path("unsigned_entry"); |
michael@0 | 206 | tamper(original_app_path("valid_app_1"), tampered, {}, |
michael@0 | 207 | [ { "name": "unsigned.txt", "content": "unsigned content!" } ]); |
michael@0 | 208 | certdb.openSignedAppFileAsync( |
michael@0 | 209 | Ci.nsIX509CertDB.AppXPCShellRoot, tampered, |
michael@0 | 210 | check_open_result("unsigned_entry", Cr.NS_ERROR_SIGNED_JAR_UNSIGNED_ENTRY)); |
michael@0 | 211 | }); |
michael@0 | 212 | |
michael@0 | 213 | add_test(function () { |
michael@0 | 214 | var tampered = tampered_app_path("unsigned_metainf_entry"); |
michael@0 | 215 | tamper(original_app_path("valid_app_1"), tampered, {}, |
michael@0 | 216 | [ { name: "META-INF/unsigned.txt", content: "unsigned content!" } ]); |
michael@0 | 217 | certdb.openSignedAppFileAsync( |
michael@0 | 218 | Ci.nsIX509CertDB.AppXPCShellRoot, tampered, |
michael@0 | 219 | check_open_result("unsigned_metainf_entry", |
michael@0 | 220 | Cr.NS_ERROR_SIGNED_JAR_UNSIGNED_ENTRY)); |
michael@0 | 221 | }); |
michael@0 | 222 | |
michael@0 | 223 | // TODO: tampered MF, tampered SF |
michael@0 | 224 | // TODO: too-large MF, too-large RSA, too-large SF |
michael@0 | 225 | // TODO: MF and SF that end immediately after the last main header |
michael@0 | 226 | // (no CR nor LF) |
michael@0 | 227 | // TODO: broken headers to exercise the parser |