security/manager/ssl/tests/unit/test_signed_apps.js

changeset 0
6474c204b198
     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

mercurial