|
1 "use strict"; |
|
2 /* To regenerate the certificates and apps for this test: |
|
3 |
|
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 |
|
8 |
|
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. |
|
12 |
|
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 */ |
|
17 |
|
18 // XXX from prio.h |
|
19 const PR_RDWR = 0x04; |
|
20 const PR_CREATE_FILE = 0x08; |
|
21 const PR_TRUNCATE = 0x20; |
|
22 |
|
23 do_get_profile(); // must be called before getting nsIX509CertDB |
|
24 const certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(Ci.nsIX509CertDB); |
|
25 |
|
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 } |
|
71 |
|
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 } |
|
79 |
|
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 } |
|
99 |
|
100 function removeEntry(entry, entryInput) { return [null, null]; } |
|
101 |
|
102 function truncateEntry(entry, entryInput) { |
|
103 if (entryInput.available() == 0) |
|
104 throw "Truncating already-zero length entry will result in identical entry."; |
|
105 |
|
106 var content = Cc["@mozilla.org/io/string-input-stream;1"] |
|
107 .createInstance(Ci.nsIStringInputStream); |
|
108 content.data = ""; |
|
109 |
|
110 return [entry, content] |
|
111 } |
|
112 |
|
113 function run_test() { |
|
114 run_next_test(); |
|
115 } |
|
116 |
|
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 } |
|
126 |
|
127 function original_app_path(test_name) { |
|
128 return do_get_file("test_signed_apps/" + test_name + ".zip", false); |
|
129 } |
|
130 |
|
131 function tampered_app_path(test_name) { |
|
132 return FileUtils.getFile("TmpD", ["test_signed_app-" + test_name + ".zip"]); |
|
133 } |
|
134 |
|
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 }); |
|
140 |
|
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 }); |
|
146 |
|
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 }); |
|
153 |
|
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 }); |
|
162 |
|
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 }); |
|
170 |
|
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 }); |
|
178 |
|
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 }); |
|
187 |
|
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 }); |
|
195 |
|
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 }); |
|
203 |
|
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 }); |
|
212 |
|
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 }); |
|
222 |
|
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 |