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