|
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* vim: set ts=2 et sw=2 tw=80: */ |
|
3 /* Any copyright is dedicated to the Public Domain. |
|
4 * http://creativecommons.org/publicdomain/zero/1.0/ */ |
|
5 |
|
6 /** |
|
7 * This file tests signature extraction using Windows Authenticode APIs of |
|
8 * downloaded files. |
|
9 */ |
|
10 |
|
11 //////////////////////////////////////////////////////////////////////////////// |
|
12 //// Globals |
|
13 |
|
14 Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
|
15 |
|
16 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", |
|
17 "resource://gre/modules/FileUtils.jsm"); |
|
18 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", |
|
19 "resource://gre/modules/NetUtil.jsm"); |
|
20 XPCOMUtils.defineLazyModuleGetter(this, "Promise", |
|
21 "resource://gre/modules/Promise.jsm"); |
|
22 XPCOMUtils.defineLazyModuleGetter(this, "Task", |
|
23 "resource://gre/modules/Task.jsm"); |
|
24 |
|
25 const BackgroundFileSaverOutputStream = Components.Constructor( |
|
26 "@mozilla.org/network/background-file-saver;1?mode=outputstream", |
|
27 "nsIBackgroundFileSaver"); |
|
28 |
|
29 const StringInputStream = Components.Constructor( |
|
30 "@mozilla.org/io/string-input-stream;1", |
|
31 "nsIStringInputStream", |
|
32 "setData"); |
|
33 |
|
34 const TEST_FILE_NAME_1 = "test-backgroundfilesaver-1.txt"; |
|
35 |
|
36 /** |
|
37 * Returns a reference to a temporary file. If the file is then created, it |
|
38 * will be removed when tests in this file finish. |
|
39 */ |
|
40 function getTempFile(aLeafName) { |
|
41 let file = FileUtils.getFile("TmpD", [aLeafName]); |
|
42 do_register_cleanup(function GTF_cleanup() { |
|
43 if (file.exists()) { |
|
44 file.remove(false); |
|
45 } |
|
46 }); |
|
47 return file; |
|
48 } |
|
49 |
|
50 /** |
|
51 * Waits for the given saver object to complete. |
|
52 * |
|
53 * @param aSaver |
|
54 * The saver, with the output stream or a stream listener implementation. |
|
55 * @param aOnTargetChangeFn |
|
56 * Optional callback invoked with the target file name when it changes. |
|
57 * |
|
58 * @return {Promise} |
|
59 * @resolves When onSaveComplete is called with a success code. |
|
60 * @rejects With an exception, if onSaveComplete is called with a failure code. |
|
61 */ |
|
62 function promiseSaverComplete(aSaver, aOnTargetChangeFn) { |
|
63 let deferred = Promise.defer(); |
|
64 aSaver.observer = { |
|
65 onTargetChange: function BFSO_onSaveComplete(aSaver, aTarget) |
|
66 { |
|
67 if (aOnTargetChangeFn) { |
|
68 aOnTargetChangeFn(aTarget); |
|
69 } |
|
70 }, |
|
71 onSaveComplete: function BFSO_onSaveComplete(aSaver, aStatus) |
|
72 { |
|
73 if (Components.isSuccessCode(aStatus)) { |
|
74 deferred.resolve(); |
|
75 } else { |
|
76 deferred.reject(new Components.Exception("Saver failed.", aStatus)); |
|
77 } |
|
78 }, |
|
79 }; |
|
80 return deferred.promise; |
|
81 } |
|
82 |
|
83 /** |
|
84 * Feeds a string to a BackgroundFileSaverOutputStream. |
|
85 * |
|
86 * @param aSourceString |
|
87 * The source data to copy. |
|
88 * @param aSaverOutputStream |
|
89 * The BackgroundFileSaverOutputStream to feed. |
|
90 * @param aCloseWhenDone |
|
91 * If true, the output stream will be closed when the copy finishes. |
|
92 * |
|
93 * @return {Promise} |
|
94 * @resolves When the copy completes with a success code. |
|
95 * @rejects With an exception, if the copy fails. |
|
96 */ |
|
97 function promiseCopyToSaver(aSourceString, aSaverOutputStream, aCloseWhenDone) { |
|
98 let deferred = Promise.defer(); |
|
99 let inputStream = new StringInputStream(aSourceString, aSourceString.length); |
|
100 let copier = Cc["@mozilla.org/network/async-stream-copier;1"] |
|
101 .createInstance(Ci.nsIAsyncStreamCopier); |
|
102 copier.init(inputStream, aSaverOutputStream, null, false, true, 0x8000, true, |
|
103 aCloseWhenDone); |
|
104 copier.asyncCopy({ |
|
105 onStartRequest: function () { }, |
|
106 onStopRequest: function (aRequest, aContext, aStatusCode) |
|
107 { |
|
108 if (Components.isSuccessCode(aStatusCode)) { |
|
109 deferred.resolve(); |
|
110 } else { |
|
111 deferred.reject(new Components.Exception(aResult)); |
|
112 } |
|
113 }, |
|
114 }, null); |
|
115 return deferred.promise; |
|
116 } |
|
117 |
|
118 let gStillRunning = true; |
|
119 |
|
120 //////////////////////////////////////////////////////////////////////////////// |
|
121 //// Tests |
|
122 |
|
123 function run_test() |
|
124 { |
|
125 run_next_test(); |
|
126 } |
|
127 |
|
128 add_task(function test_setup() |
|
129 { |
|
130 // Wait 10 minutes, that is half of the external xpcshell timeout. |
|
131 do_timeout(10 * 60 * 1000, function() { |
|
132 if (gStillRunning) { |
|
133 do_throw("Test timed out."); |
|
134 } |
|
135 }) |
|
136 }); |
|
137 |
|
138 function readFileToString(aFilename) { |
|
139 let f = do_get_file(aFilename); |
|
140 let stream = Cc["@mozilla.org/network/file-input-stream;1"] |
|
141 .createInstance(Ci.nsIFileInputStream); |
|
142 stream.init(f, -1, 0, 0); |
|
143 let buf = NetUtil.readInputStreamToString(stream, stream.available()); |
|
144 return buf; |
|
145 } |
|
146 |
|
147 add_task(function test_signature() |
|
148 { |
|
149 // Check that we get a signature if the saver is finished on Windows. |
|
150 let destFile = getTempFile(TEST_FILE_NAME_1); |
|
151 |
|
152 let data = readFileToString("data/signed_win.exe"); |
|
153 let saver = new BackgroundFileSaverOutputStream(); |
|
154 let completionPromise = promiseSaverComplete(saver); |
|
155 |
|
156 try { |
|
157 let signatureInfo = saver.signatureInfo; |
|
158 do_throw("Can't get signature before saver is complete."); |
|
159 } catch (ex if ex.result == Cr.NS_ERROR_NOT_AVAILABLE) { } |
|
160 |
|
161 saver.enableSignatureInfo(); |
|
162 saver.setTarget(destFile, false); |
|
163 yield promiseCopyToSaver(data, saver, true); |
|
164 |
|
165 saver.finish(Cr.NS_OK); |
|
166 yield completionPromise; |
|
167 |
|
168 // There's only one nsIX509CertList in the signature array. |
|
169 do_check_eq(1, saver.signatureInfo.length); |
|
170 let certLists = saver.signatureInfo.enumerate(); |
|
171 do_check_true(certLists.hasMoreElements()); |
|
172 let certList = certLists.getNext().QueryInterface(Ci.nsIX509CertList); |
|
173 do_check_false(certLists.hasMoreElements()); |
|
174 |
|
175 // Check that it has 3 certs. |
|
176 let certs = certList.getEnumerator(); |
|
177 do_check_true(certs.hasMoreElements()); |
|
178 let signer = certs.getNext().QueryInterface(Ci.nsIX509Cert); |
|
179 do_check_true(certs.hasMoreElements()); |
|
180 let issuer = certs.getNext().QueryInterface(Ci.nsIX509Cert); |
|
181 do_check_true(certs.hasMoreElements()); |
|
182 let root = certs.getNext().QueryInterface(Ci.nsIX509Cert); |
|
183 do_check_false(certs.hasMoreElements()); |
|
184 |
|
185 // Check that the certs have expected strings attached. |
|
186 let organization = "Microsoft Corporation"; |
|
187 do_check_eq("Microsoft Corporation", signer.commonName); |
|
188 do_check_eq(organization, signer.organization); |
|
189 do_check_eq("Copyright (c) 2002 Microsoft Corp.", signer.organizationalUnit); |
|
190 |
|
191 do_check_eq("Microsoft Code Signing PCA", issuer.commonName); |
|
192 do_check_eq(organization, issuer.organization); |
|
193 do_check_eq("Copyright (c) 2000 Microsoft Corp.", issuer.organizationalUnit); |
|
194 |
|
195 do_check_eq("Microsoft Root Authority", root.commonName); |
|
196 do_check_false(root.organization); |
|
197 do_check_eq("Copyright (c) 1997 Microsoft Corp.", root.organizationalUnit); |
|
198 |
|
199 // Clean up. |
|
200 destFile.remove(false); |
|
201 }); |
|
202 |
|
203 add_task(function test_teardown() |
|
204 { |
|
205 gStillRunning = false; |
|
206 }); |