michael@0: "use strict"; michael@0: /* To regenerate the certificates and apps for this test: michael@0: michael@0: cd security/manager/ssl/tests/unit/test_signed_apps michael@0: PATH=$NSS/bin:$NSS/lib:$PATH ./generate.sh michael@0: cd ../../../../../.. michael@0: make -C $OBJDIR/security/manager/ssl/tests michael@0: michael@0: $NSS is the path to NSS binaries and libraries built for the host platform. michael@0: If you get error messages about "CertUtil" on Windows, then it means that michael@0: the Windows CertUtil.exe is ahead of the NSS certutil.exe in $PATH. michael@0: michael@0: Check in the generated files. These steps are not done as part of the build michael@0: because we do not want to add a build-time dependency on the OpenSSL or NSS michael@0: tools or libraries built for the host platform. michael@0: */ michael@0: michael@0: // XXX from prio.h michael@0: const PR_RDWR = 0x04; michael@0: const PR_CREATE_FILE = 0x08; michael@0: const PR_TRUNCATE = 0x20; michael@0: michael@0: do_get_profile(); // must be called before getting nsIX509CertDB michael@0: const certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(Ci.nsIX509CertDB); michael@0: michael@0: // Creates a new app package based in the inFilePath package, with a set of michael@0: // modifications (including possibly deletions) applied to the existing entries, michael@0: // and/or a set of new entries to be included. michael@0: function tamper(inFilePath, outFilePath, modifications, newEntries) { michael@0: var writer = Cc["@mozilla.org/zipwriter;1"].createInstance(Ci.nsIZipWriter); michael@0: writer.open(outFilePath, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE); michael@0: try { michael@0: var reader = Cc["@mozilla.org/libjar/zip-reader;1"].createInstance(Ci.nsIZipReader); michael@0: reader.open(inFilePath); michael@0: try { michael@0: var entries = reader.findEntries(""); michael@0: while (entries.hasMore()) { michael@0: var entryName = entries.getNext(); michael@0: var inEntry = reader.getEntry(entryName); michael@0: var entryInput = reader.getInputStream(entryName); michael@0: try { michael@0: var f = modifications[entryName]; michael@0: var outEntry, outEntryInput; michael@0: if (f) { michael@0: [outEntry, outEntryInput] = f(inEntry, entryInput); michael@0: delete modifications[entryName]; michael@0: } else { michael@0: [outEntry, outEntryInput] = [inEntry, entryInput]; michael@0: } michael@0: // if f does not want the input entry to be copied to the output entry michael@0: // at all (i.e. it wants it to be deleted), it will return null. michael@0: if (outEntryInput) { michael@0: try { michael@0: writer.addEntryStream(entryName, michael@0: outEntry.lastModifiedTime, michael@0: outEntry.compression, michael@0: outEntryInput, michael@0: false); michael@0: } finally { michael@0: if (entryInput != outEntryInput) michael@0: outEntryInput.close(); michael@0: } michael@0: } michael@0: } finally { michael@0: entryInput.close(); michael@0: } michael@0: } michael@0: } finally { michael@0: reader.close(); michael@0: } michael@0: michael@0: // Any leftover modification means that we were expecting to modify an entry michael@0: // in the input file that wasn't there. michael@0: for(var name in modifications) { michael@0: if (modifications.hasOwnProperty(name)) { michael@0: throw "input file was missing expected entries: " + name; michael@0: } michael@0: } michael@0: michael@0: // Now, append any new entries to the end michael@0: newEntries.forEach(function(newEntry) { michael@0: var sis = Cc["@mozilla.org/io/string-input-stream;1"] michael@0: .createInstance(Ci.nsIStringInputStream); michael@0: try { michael@0: sis.setData(newEntry.content, newEntry.content.length); michael@0: writer.addEntryStream(newEntry.name, michael@0: new Date(), michael@0: Ci.nsIZipWriter.COMPRESSION_BEST, michael@0: sis, michael@0: false); michael@0: } finally { michael@0: sis.close(); michael@0: } michael@0: }); michael@0: } finally { michael@0: writer.close(); michael@0: } michael@0: } michael@0: michael@0: function removeEntry(entry, entryInput) { return [null, null]; } michael@0: michael@0: function truncateEntry(entry, entryInput) { michael@0: if (entryInput.available() == 0) michael@0: throw "Truncating already-zero length entry will result in identical entry."; michael@0: michael@0: var content = Cc["@mozilla.org/io/string-input-stream;1"] michael@0: .createInstance(Ci.nsIStringInputStream); michael@0: content.data = ""; michael@0: michael@0: return [entry, content] michael@0: } michael@0: michael@0: function run_test() { michael@0: run_next_test(); michael@0: } michael@0: michael@0: function check_open_result(name, expectedRv) { michael@0: return function openSignedAppFileCallback(rv, aZipReader, aSignerCert) { michael@0: do_print("openSignedAppFileCallback called for " + name); michael@0: do_check_eq(rv, expectedRv); michael@0: do_check_eq(aZipReader != null, Components.isSuccessCode(expectedRv)); michael@0: do_check_eq(aSignerCert != null, Components.isSuccessCode(expectedRv)); michael@0: run_next_test(); michael@0: }; michael@0: } michael@0: michael@0: function original_app_path(test_name) { michael@0: return do_get_file("test_signed_apps/" + test_name + ".zip", false); michael@0: } michael@0: michael@0: function tampered_app_path(test_name) { michael@0: return FileUtils.getFile("TmpD", ["test_signed_app-" + test_name + ".zip"]); michael@0: } michael@0: michael@0: add_test(function () { michael@0: certdb.openSignedAppFileAsync( michael@0: Ci.nsIX509CertDB.AppXPCShellRoot, original_app_path("valid_app_1"), michael@0: check_open_result("valid", Cr.NS_OK)); michael@0: }); michael@0: michael@0: add_test(function () { michael@0: certdb.openSignedAppFileAsync( michael@0: Ci.nsIX509CertDB.AppXPCShellRoot, original_app_path("unsigned_app_1"), michael@0: check_open_result("unsigned", Cr.NS_ERROR_SIGNED_JAR_NOT_SIGNED)); michael@0: }); michael@0: michael@0: add_test(function () { michael@0: certdb.openSignedAppFileAsync( michael@0: Ci.nsIX509CertDB.AppXPCShellRoot, original_app_path("unknown_issuer_app_1"), michael@0: check_open_result("unknown_issuer", michael@0: getXPCOMStatusFromNSS(SEC_ERROR_UNKNOWN_ISSUER))); michael@0: }); michael@0: michael@0: // Sanity check to ensure a no-op tampering gives a valid result michael@0: add_test(function () { michael@0: var tampered = tampered_app_path("identity_tampering"); michael@0: tamper(original_app_path("valid_app_1"), tampered, { }, []); michael@0: certdb.openSignedAppFileAsync( michael@0: Ci.nsIX509CertDB.AppXPCShellRoot, original_app_path("valid_app_1"), michael@0: check_open_result("identity_tampering", Cr.NS_OK)); michael@0: }); michael@0: michael@0: add_test(function () { michael@0: var tampered = tampered_app_path("missing_rsa"); michael@0: tamper(original_app_path("valid_app_1"), tampered, { "META-INF/A.RSA" : removeEntry }, []); michael@0: certdb.openSignedAppFileAsync( michael@0: Ci.nsIX509CertDB.AppXPCShellRoot, tampered, michael@0: check_open_result("missing_rsa", Cr.NS_ERROR_SIGNED_JAR_NOT_SIGNED)); michael@0: }); michael@0: michael@0: add_test(function () { michael@0: var tampered = tampered_app_path("missing_sf"); michael@0: tamper(original_app_path("valid_app_1"), tampered, { "META-INF/A.SF" : removeEntry }, []); michael@0: certdb.openSignedAppFileAsync( michael@0: Ci.nsIX509CertDB.AppXPCShellRoot, tampered, michael@0: check_open_result("missing_sf", Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID)); michael@0: }); michael@0: michael@0: add_test(function () { michael@0: var tampered = tampered_app_path("missing_manifest_mf"); michael@0: tamper(original_app_path("valid_app_1"), tampered, { "META-INF/MANIFEST.MF" : removeEntry }, []); michael@0: certdb.openSignedAppFileAsync( michael@0: Ci.nsIX509CertDB.AppXPCShellRoot, tampered, michael@0: check_open_result("missing_manifest_mf", michael@0: Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID)); michael@0: }); michael@0: michael@0: add_test(function () { michael@0: var tampered = tampered_app_path("missing_entry"); michael@0: tamper(original_app_path("valid_app_1"), tampered, { "manifest.webapp" : removeEntry }, []); michael@0: certdb.openSignedAppFileAsync( michael@0: Ci.nsIX509CertDB.AppXPCShellRoot, tampered, michael@0: check_open_result("missing_entry", Cr.NS_ERROR_SIGNED_JAR_ENTRY_MISSING)); michael@0: }); michael@0: michael@0: add_test(function () { michael@0: var tampered = tampered_app_path("truncated_entry"); michael@0: tamper(original_app_path("valid_app_1"), tampered, { "manifest.webapp" : truncateEntry }, []); michael@0: certdb.openSignedAppFileAsync( michael@0: Ci.nsIX509CertDB.AppXPCShellRoot, tampered, michael@0: check_open_result("truncated_entry", Cr.NS_ERROR_SIGNED_JAR_MODIFIED_ENTRY)); michael@0: }); michael@0: michael@0: add_test(function () { michael@0: var tampered = tampered_app_path("unsigned_entry"); michael@0: tamper(original_app_path("valid_app_1"), tampered, {}, michael@0: [ { "name": "unsigned.txt", "content": "unsigned content!" } ]); michael@0: certdb.openSignedAppFileAsync( michael@0: Ci.nsIX509CertDB.AppXPCShellRoot, tampered, michael@0: check_open_result("unsigned_entry", Cr.NS_ERROR_SIGNED_JAR_UNSIGNED_ENTRY)); michael@0: }); michael@0: michael@0: add_test(function () { michael@0: var tampered = tampered_app_path("unsigned_metainf_entry"); michael@0: tamper(original_app_path("valid_app_1"), tampered, {}, michael@0: [ { name: "META-INF/unsigned.txt", content: "unsigned content!" } ]); michael@0: certdb.openSignedAppFileAsync( michael@0: Ci.nsIX509CertDB.AppXPCShellRoot, tampered, michael@0: check_open_result("unsigned_metainf_entry", michael@0: Cr.NS_ERROR_SIGNED_JAR_UNSIGNED_ENTRY)); michael@0: }); michael@0: michael@0: // TODO: tampered MF, tampered SF michael@0: // TODO: too-large MF, too-large RSA, too-large SF michael@0: // TODO: MF and SF that end immediately after the last main header michael@0: // (no CR nor LF) michael@0: // TODO: broken headers to exercise the parser