Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 */
5 "use strict";
7 const { 'classes': Cc, 'interfaces': Ci, 'utils': Cu, 'results': Cr } = Components;
9 let { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {});
10 let { FileUtils } = Cu.import("resource://gre/modules/FileUtils.jsm", {});
11 let { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
12 let { Promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
13 let { HttpServer } = Cu.import("resource://testing-common/httpd.js", {});
14 let { ctypes } = Cu.import("resource://gre/modules/ctypes.jsm");
16 let gIsWindows = ("@mozilla.org/windows-registry-key;1" in Cc);
18 const isDebugBuild = Cc["@mozilla.org/xpcom/debug;1"]
19 .getService(Ci.nsIDebug2).isDebugBuild;
21 const SEC_ERROR_BASE = Ci.nsINSSErrorsService.NSS_SEC_ERROR_BASE;
22 const SSL_ERROR_BASE = Ci.nsINSSErrorsService.NSS_SSL_ERROR_BASE;
24 // Sort in numerical order
25 const SEC_ERROR_INVALID_ARGS = SEC_ERROR_BASE + 5; // -8187
26 const SEC_ERROR_BAD_DER = SEC_ERROR_BASE + 9;
27 const SEC_ERROR_EXPIRED_CERTIFICATE = SEC_ERROR_BASE + 11;
28 const SEC_ERROR_REVOKED_CERTIFICATE = SEC_ERROR_BASE + 12; // -8180
29 const SEC_ERROR_UNKNOWN_ISSUER = SEC_ERROR_BASE + 13;
30 const SEC_ERROR_BAD_DATABASE = SEC_ERROR_BASE + 18;
31 const SEC_ERROR_UNTRUSTED_ISSUER = SEC_ERROR_BASE + 20; // -8172
32 const SEC_ERROR_UNTRUSTED_CERT = SEC_ERROR_BASE + 21; // -8171
33 const SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE = SEC_ERROR_BASE + 30; // -8162
34 const SEC_ERROR_EXTENSION_VALUE_INVALID = SEC_ERROR_BASE + 34; // -8158
35 const SEC_ERROR_EXTENSION_NOT_FOUND = SEC_ERROR_BASE + 35; // -8157
36 const SEC_ERROR_CA_CERT_INVALID = SEC_ERROR_BASE + 36;
37 const SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION = SEC_ERROR_BASE + 41;
38 const SEC_ERROR_INADEQUATE_KEY_USAGE = SEC_ERROR_BASE + 90; // -8102
39 const SEC_ERROR_INADEQUATE_CERT_TYPE = SEC_ERROR_BASE + 91; // -8101
40 const SEC_ERROR_CERT_NOT_IN_NAME_SPACE = SEC_ERROR_BASE + 112; // -8080
41 const SEC_ERROR_CERT_BAD_ACCESS_LOCATION = SEC_ERROR_BASE + 117; // -8075
42 const SEC_ERROR_OCSP_MALFORMED_REQUEST = SEC_ERROR_BASE + 120;
43 const SEC_ERROR_OCSP_SERVER_ERROR = SEC_ERROR_BASE + 121; // -8071
44 const SEC_ERROR_OCSP_TRY_SERVER_LATER = SEC_ERROR_BASE + 122;
45 const SEC_ERROR_OCSP_REQUEST_NEEDS_SIG = SEC_ERROR_BASE + 123;
46 const SEC_ERROR_OCSP_UNAUTHORIZED_REQUEST = SEC_ERROR_BASE + 124;
47 const SEC_ERROR_OCSP_UNKNOWN_CERT = SEC_ERROR_BASE + 126; // -8066
48 const SEC_ERROR_OCSP_MALFORMED_RESPONSE = SEC_ERROR_BASE + 129;
49 const SEC_ERROR_OCSP_UNAUTHORIZED_RESPONSE = SEC_ERROR_BASE + 130;
50 const SEC_ERROR_OCSP_OLD_RESPONSE = SEC_ERROR_BASE + 132;
51 const SEC_ERROR_OCSP_INVALID_SIGNING_CERT = SEC_ERROR_BASE + 144;
52 const SEC_ERROR_POLICY_VALIDATION_FAILED = SEC_ERROR_BASE + 160; // -8032
53 const SEC_ERROR_OCSP_BAD_SIGNATURE = SEC_ERROR_BASE + 157;
54 const SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED = SEC_ERROR_BASE + 176;
55 const SEC_ERROR_APPLICATION_CALLBACK_ERROR = SEC_ERROR_BASE + 178;
57 const SSL_ERROR_BAD_CERT_DOMAIN = SSL_ERROR_BASE + 12;
59 // Supported Certificate Usages
60 const certificateUsageSSLClient = 0x0001;
61 const certificateUsageSSLServer = 0x0002;
62 const certificateUsageSSLCA = 0x0008;
63 const certificateUsageEmailSigner = 0x0010;
64 const certificateUsageEmailRecipient = 0x0020;
65 const certificateUsageObjectSigner = 0x0040;
66 const certificateUsageVerifyCA = 0x0100;
67 const certificateUsageStatusResponder = 0x0400;
69 const NO_FLAGS = 0;
71 function readFile(file) {
72 let fstream = Cc["@mozilla.org/network/file-input-stream;1"]
73 .createInstance(Ci.nsIFileInputStream);
74 fstream.init(file, -1, 0, 0);
75 let data = NetUtil.readInputStreamToString(fstream, fstream.available());
76 fstream.close();
77 return data;
78 }
80 function addCertFromFile(certdb, filename, trustString) {
81 let certFile = do_get_file(filename, false);
82 let der = readFile(certFile);
83 certdb.addCert(der, trustString, null);
84 }
86 function constructCertFromFile(filename) {
87 let certFile = do_get_file(filename, false);
88 let certDER = readFile(certFile);
89 let certdb = Cc["@mozilla.org/security/x509certdb;1"]
90 .getService(Ci.nsIX509CertDB);
91 return certdb.constructX509(certDER, certDER.length);
92 }
94 function setCertTrust(cert, trustString) {
95 let certdb = Cc["@mozilla.org/security/x509certdb;1"]
96 .getService(Ci.nsIX509CertDB);
97 certdb.setCertTrustFromString(cert, trustString);
98 }
100 function getXPCOMStatusFromNSS(statusNSS) {
101 let nssErrorsService = Cc["@mozilla.org/nss_errors_service;1"]
102 .getService(Ci.nsINSSErrorsService);
103 return nssErrorsService.getXPCOMFromNSSError(statusNSS);
104 }
106 function checkCertErrorGeneric(certdb, cert, expectedError, usage) {
107 let hasEVPolicy = {};
108 let verifiedChain = {};
109 let error = certdb.verifyCertNow(cert, usage, NO_FLAGS, verifiedChain,
110 hasEVPolicy);
111 // expected error == -1 is a special marker for any error is OK
112 if (expectedError != -1 ) {
113 do_check_eq(error, expectedError);
114 } else {
115 do_check_neq (error, 0);
116 }
117 }
119 function _getLibraryFunctionWithNoArguments(functionName, libraryName) {
120 // Open the NSS library. copied from services/crypto/modules/WeaveCrypto.js
121 let path = ctypes.libraryName(libraryName);
123 // XXX really want to be able to pass specific dlopen flags here.
124 let nsslib;
125 try {
126 nsslib = ctypes.open(path);
127 } catch(e) {
128 // In case opening the library without a full path fails,
129 // try again with a full path.
130 let file = Services.dirsvc.get("GreD", Ci.nsILocalFile);
131 file.append(path);
132 nsslib = ctypes.open(file.path);
133 }
135 let SECStatus = ctypes.int;
136 let func = nsslib.declare(functionName, ctypes.default_abi, SECStatus);
137 return func;
138 }
140 function clearOCSPCache() {
141 let certdb = Cc["@mozilla.org/security/x509certdb;1"]
142 .getService(Ci.nsIX509CertDB);
143 certdb.clearOCSPCache();
144 }
146 function clearSessionCache() {
147 let SSL_ClearSessionCache = null;
148 try {
149 SSL_ClearSessionCache =
150 _getLibraryFunctionWithNoArguments("SSL_ClearSessionCache", "ssl3");
151 } catch (e) {
152 // On Windows, this is actually in the nss3 library.
153 SSL_ClearSessionCache =
154 _getLibraryFunctionWithNoArguments("SSL_ClearSessionCache", "nss3");
155 }
156 if (!SSL_ClearSessionCache || SSL_ClearSessionCache() != 0) {
157 throw "Failed to clear SSL session cache";
158 }
159 }
161 // Set up a TLS testing environment that has a TLS server running and
162 // ready to accept connections. This async function starts the server and
163 // waits for the server to indicate that it is ready.
164 //
165 // Each test should have its own subdomain of example.com, for example
166 // my-first-connection-test.example.com. The server can use the server
167 // name (passed through the SNI TLS extension) to determine what behavior
168 // the server side of the text should exhibit. See TLSServer.h for more
169 // information on how to write the server side of tests.
170 //
171 // Create a new source file for your new server executable in
172 // security/manager/ssl/tests/unit/tlsserver/cmd similar to the other ones in
173 // that directory, and add a reference to it to the sources variable in that
174 // directory's moz.build.
175 //
176 // Modify TEST_HARNESS_BINS in
177 // testing/mochitest/Makefile.in and NO_PKG_FILES in
178 // toolkit/mozapps/installer/packager.mk to make sure the new executable
179 // gets included in the packages used for shipping the tests to the test
180 // runners in our build/test farm. (Things will work fine locally without
181 // these changes but will break on TBPL.)
182 //
183 // Your test script should look something like this:
184 /*
186 // -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
187 // This Source Code Form is subject to the terms of the Mozilla Public
188 // License, v. 2.0. If a copy of the MPL was not distributed with this
189 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
190 "use strict";
192 // <documentation on your test>
194 function run_test() {
195 do_get_profile();
196 add_tls_server_setup("<test-server-name>");
198 add_connection_test("<test-name-1>.example.com",
199 getXPCOMStatusFromNSS(SEC_ERROR_xxx),
200 function() { ... },
201 function(aTransportSecurityInfo) { ... });
202 [...]
203 add_connection_test("<test-name-n>.example.com", Cr.NS_OK);
205 run_next_test();
206 }
208 */
209 function add_tls_server_setup(serverBinName) {
210 add_test(function() {
211 _setupTLSServerTest(serverBinName);
212 });
213 }
215 // Add a TLS connection test case. aHost is the hostname to pass in the SNI TLS
216 // extension; this should unambiguously identifiy which test is being run.
217 // aExpectedResult is the expected nsresult of the connection.
218 // aBeforeConnect is a callback function that takes no arguments that will be
219 // called before the connection is attempted.
220 // aWithSecurityInfo is a callback function that takes an
221 // nsITransportSecurityInfo, which is called after the TLS handshake succeeds.
222 function add_connection_test(aHost, aExpectedResult,
223 aBeforeConnect, aWithSecurityInfo) {
224 const REMOTE_PORT = 8443;
226 function Connection(aHost) {
227 this.host = aHost;
228 let threadManager = Cc["@mozilla.org/thread-manager;1"]
229 .getService(Ci.nsIThreadManager);
230 this.thread = threadManager.currentThread;
231 this.defer = Promise.defer();
232 let sts = Cc["@mozilla.org/network/socket-transport-service;1"]
233 .getService(Ci.nsISocketTransportService);
234 this.transport = sts.createTransport(["ssl"], 1, aHost, REMOTE_PORT, null);
235 this.transport.setEventSink(this, this.thread);
236 this.inputStream = null;
237 this.outputStream = null;
238 this.connected = false;
239 }
241 Connection.prototype = {
242 // nsITransportEventSink
243 onTransportStatus: function(aTransport, aStatus, aProgress, aProgressMax) {
244 if (!this.connected && aStatus == Ci.nsISocketTransport.STATUS_CONNECTED_TO) {
245 this.connected = true;
246 this.outputStream.asyncWait(this, 0, 0, this.thread);
247 }
248 },
250 // nsIInputStreamCallback
251 onInputStreamReady: function(aStream) {
252 try {
253 // this will throw if the stream has been closed by an error
254 let str = NetUtil.readInputStreamToString(aStream, aStream.available());
255 do_check_eq(str, "0");
256 this.inputStream.close();
257 this.outputStream.close();
258 this.result = Cr.NS_OK;
259 } catch (e) {
260 this.result = e.result;
261 }
262 this.defer.resolve(this);
263 },
265 // nsIOutputStreamCallback
266 onOutputStreamReady: function(aStream) {
267 let sslSocketControl = this.transport.securityInfo
268 .QueryInterface(Ci.nsISSLSocketControl);
269 sslSocketControl.proxyStartSSL();
270 this.outputStream.write("0", 1);
271 let inStream = this.transport.openInputStream(0, 0, 0)
272 .QueryInterface(Ci.nsIAsyncInputStream);
273 this.inputStream = inStream;
274 this.inputStream.asyncWait(this, 0, 0, this.thread);
275 },
277 go: function() {
278 this.outputStream = this.transport.openOutputStream(0, 0, 0)
279 .QueryInterface(Ci.nsIAsyncOutputStream);
280 return this.defer.promise;
281 }
282 };
284 /* Returns a promise to connect to aHost that resolves to the result of that
285 * connection */
286 function connectTo(aHost) {
287 Services.prefs.setCharPref("network.dns.localDomains", aHost);
288 let connection = new Connection(aHost);
289 return connection.go();
290 }
292 add_test(function() {
293 if (aBeforeConnect) {
294 aBeforeConnect();
295 }
296 connectTo(aHost).then(function(conn) {
297 do_check_eq(conn.result, aExpectedResult);
298 if (aWithSecurityInfo) {
299 aWithSecurityInfo(conn.transport.securityInfo
300 .QueryInterface(Ci.nsITransportSecurityInfo));
301 }
302 run_next_test();
303 });
304 });
305 }
307 function _getBinaryUtil(binaryUtilName) {
308 let directoryService = Cc["@mozilla.org/file/directory_service;1"]
309 .getService(Ci.nsIProperties);
311 let utilBin = directoryService.get("CurProcD", Ci.nsILocalFile);
312 utilBin.append(binaryUtilName + (gIsWindows ? ".exe" : ""));
313 // If we're testing locally, the above works. If not, the server executable
314 // is in another location.
315 if (!utilBin.exists()) {
316 utilBin = directoryService.get("CurWorkD", Ci.nsILocalFile);
317 while (utilBin.path.indexOf("xpcshell") != -1) {
318 utilBin = utilBin.parent;
319 }
320 utilBin.append("bin");
321 utilBin.append(binaryUtilName + (gIsWindows ? ".exe" : ""));
322 }
323 do_check_true(utilBin.exists());
324 return utilBin;
325 }
327 // Do not call this directly; use add_tls_server_setup
328 function _setupTLSServerTest(serverBinName)
329 {
330 let certdb = Cc["@mozilla.org/security/x509certdb;1"]
331 .getService(Ci.nsIX509CertDB);
332 // The trusted CA that is typically used for "good" certificates.
333 addCertFromFile(certdb, "tlsserver/test-ca.der", "CTu,u,u");
335 const CALLBACK_PORT = 8444;
337 let directoryService = Cc["@mozilla.org/file/directory_service;1"]
338 .getService(Ci.nsIProperties);
339 let envSvc = Cc["@mozilla.org/process/environment;1"]
340 .getService(Ci.nsIEnvironment);
341 let greDir = directoryService.get("GreD", Ci.nsIFile);
342 envSvc.set("DYLD_LIBRARY_PATH", greDir.path);
343 envSvc.set("LD_LIBRARY_PATH", greDir.path);
344 envSvc.set("MOZ_TLS_SERVER_DEBUG_LEVEL", "3");
345 envSvc.set("MOZ_TLS_SERVER_CALLBACK_PORT", CALLBACK_PORT);
347 let httpServer = new HttpServer();
348 httpServer.registerPathHandler("/",
349 function handleServerCallback(aRequest, aResponse) {
350 aResponse.setStatusLine(aRequest.httpVersion, 200, "OK");
351 aResponse.setHeader("Content-Type", "text/plain");
352 let responseBody = "OK!";
353 aResponse.bodyOutputStream.write(responseBody, responseBody.length);
354 do_execute_soon(function() {
355 httpServer.stop(run_next_test);
356 });
357 });
358 httpServer.start(CALLBACK_PORT);
360 let serverBin = _getBinaryUtil(serverBinName);
361 let process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
362 process.init(serverBin);
363 let certDir = directoryService.get("CurWorkD", Ci.nsILocalFile);
364 certDir.append("tlsserver");
365 do_check_true(certDir.exists());
366 process.run(false, [certDir.path], 1);
368 do_register_cleanup(function() {
369 process.kill();
370 });
371 }
373 // Returns an Array of OCSP responses for a given ocspRespArray and a location
374 // for a nssDB where the certs and public keys are prepopulated.
375 // ocspRespArray is an array of arrays like:
376 // [ [typeOfResponse, certnick, extracertnick]...]
377 function generateOCSPResponses(ocspRespArray, nssDBlocation)
378 {
379 let utilBinName = "GenerateOCSPResponse";
380 let ocspGenBin = _getBinaryUtil(utilBinName);
381 let retArray = new Array();
383 for (let i = 0; i < ocspRespArray.length; i++) {
384 let argArray = new Array();
385 let ocspFilepre = do_get_file(i.toString() + ".ocsp", true);
386 let filename = ocspFilepre.path;
387 argArray.push(nssDBlocation);
388 argArray.push(ocspRespArray[i][0]); // ocsRespType;
389 argArray.push(ocspRespArray[i][1]); // nick;
390 argArray.push(ocspRespArray[i][2]); // extranickname
391 argArray.push(filename);
392 do_print("arg_array ="+argArray);
394 let process = Cc["@mozilla.org/process/util;1"]
395 .createInstance(Ci.nsIProcess);
396 process.init(ocspGenBin);
397 process.run(true, argArray, 5);
398 do_check_eq(0, process.exitValue);
399 let ocspFile = do_get_file(i.toString() + ".ocsp", false);
400 retArray.push(readFile(ocspFile));
401 ocspFile.remove(false);
402 }
403 return retArray;
404 }
406 // Starts and returns an http responder that will cause a test failure if it is
407 // queried. The server identities are given by a non-empty array
408 // serverIdentities.
409 function getFailingHttpServer(serverPort, serverIdentities) {
410 let httpServer = new HttpServer();
411 httpServer.registerPrefixHandler("/", function(request, response) {
412 do_check_true(false);
413 });
414 httpServer.identity.setPrimary("http", serverIdentities.shift(), serverPort);
415 serverIdentities.forEach(function(identity) {
416 httpServer.identity.add("http", identity, serverPort);
417 });
418 httpServer.start(serverPort);
419 return httpServer;
420 }
422 // Starts an http OCSP responder that serves good OCSP responses and
423 // returns an object with a method stop that should be called to stop
424 // the http server.
425 // NB: Because generating OCSP responses inside the HTTP request
426 // handler can cause timeouts, the expected responses are pre-generated
427 // all at once before starting the server. This means that their producedAt
428 // times will all be the same. If a test depends on this not being the case,
429 // perhaps calling startOCSPResponder twice (at different times) will be
430 // necessary.
431 //
432 // serverPort is the port of the http OCSP responder
433 // identity is the http hostname that will answer the OCSP requests
434 // invalidIdentities is an array of identities that if used an
435 // will cause a test failure
436 // nssDBlocaion is the location of the NSS database from where the OCSP
437 // responses will be generated (assumes appropiate keys are present)
438 // expectedCertNames is an array of nicks of the certs to be responsed
439 // expectedBasePaths is an optional array that is used to indicate
440 // what is the expected base path of the OCSP request.
441 function startOCSPResponder(serverPort, identity, invalidIdentities,
442 nssDBLocation, expectedCertNames,
443 expectedBasePaths, expectedMethods,
444 expectedResponseTypes) {
445 let ocspResponseGenerationArgs = expectedCertNames.map(
446 function(expectedNick) {
447 let responseType = "good";
448 if (expectedResponseTypes && expectedResponseTypes.length >= 1) {
449 responseType = expectedResponseTypes.shift();
450 }
451 return [responseType, expectedNick, "unused"];
452 }
453 );
454 let ocspResponses = generateOCSPResponses(ocspResponseGenerationArgs,
455 nssDBLocation);
456 let httpServer = new HttpServer();
457 httpServer.registerPrefixHandler("/",
458 function handleServerCallback(aRequest, aResponse) {
459 invalidIdentities.forEach(function(identity) {
460 do_check_neq(aRequest.host, identity)
461 });
462 do_print("got request for: " + aRequest.path);
463 let basePath = aRequest.path.slice(1).split("/")[0];
464 if (expectedBasePaths.length >= 1) {
465 do_check_eq(basePath, expectedBasePaths.shift());
466 }
467 do_check_true(expectedCertNames.length >= 1);
468 if (expectedMethods && expectedMethods.length >= 1) {
469 do_check_eq(aRequest.method, expectedMethods.shift());
470 }
471 aResponse.setStatusLine(aRequest.httpVersion, 200, "OK");
472 aResponse.setHeader("Content-Type", "application/ocsp-response");
473 aResponse.write(ocspResponses.shift());
474 });
475 httpServer.identity.setPrimary("http", identity, serverPort);
476 invalidIdentities.forEach(function(identity) {
477 httpServer.identity.add("http", identity, serverPort);
478 });
479 httpServer.start(serverPort);
480 return {
481 stop: function(callback) {
482 // make sure we consumed each expected response
483 do_check_eq(ocspResponses.length, 0);
484 if (expectedBasePaths) {
485 do_check_eq(expectedBasePaths.length, 0);
486 }
487 if (expectedResponseTypes) {
488 do_check_eq(expectedResponseTypes.length, 0);
489 }
490 httpServer.stop(callback);
491 }
492 };
493 }