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