head_psm.js (45546B)
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"; 6 7 const { AppConstants } = ChromeUtils.importESModule( 8 "resource://gre/modules/AppConstants.sys.mjs" 9 ); 10 const { ctypes } = ChromeUtils.importESModule( 11 "resource://gre/modules/ctypes.sys.mjs" 12 ); 13 const { FileUtils } = ChromeUtils.importESModule( 14 "resource://gre/modules/FileUtils.sys.mjs" 15 ); 16 const { HttpServer } = ChromeUtils.importESModule( 17 "resource://testing-common/httpd.sys.mjs" 18 ); 19 const { MockRegistrar } = ChromeUtils.importESModule( 20 "resource://testing-common/MockRegistrar.sys.mjs" 21 ); 22 const { NetUtil } = ChromeUtils.importESModule( 23 "resource://gre/modules/NetUtil.sys.mjs" 24 ); 25 const { XPCOMUtils } = ChromeUtils.importESModule( 26 "resource://gre/modules/XPCOMUtils.sys.mjs" 27 ); 28 29 const { X509 } = ChromeUtils.importESModule( 30 "resource://gre/modules/psm/X509.sys.mjs" 31 ); 32 33 const gIsDebugBuild = Cc["@mozilla.org/xpcom/debug;1"].getService( 34 Ci.nsIDebug2 35 ).isDebugBuild; 36 37 // The test EV roots are only enabled in debug builds as a security measure. 38 const gEVExpected = gIsDebugBuild; 39 40 const CLIENT_AUTH_FILE_NAME = "ClientAuthRememberList.bin"; 41 const SSS_STATE_FILE_NAME = "SiteSecurityServiceState.bin"; 42 const SSS_STATE_OLD_FILE_NAME = "SiteSecurityServiceState.txt"; 43 const CERT_OVERRIDE_FILE_NAME = "cert_override.txt"; 44 45 const SEC_ERROR_BASE = Ci.nsINSSErrorsService.NSS_SEC_ERROR_BASE; 46 const SSL_ERROR_BASE = Ci.nsINSSErrorsService.NSS_SSL_ERROR_BASE; 47 const MOZILLA_PKIX_ERROR_BASE = Ci.nsINSSErrorsService.MOZILLA_PKIX_ERROR_BASE; 48 49 // This isn't really a valid PRErrorCode, but is useful for signalling that 50 // a test is expected to succeed. 51 const PRErrorCodeSuccess = 0; 52 53 // Sort in numerical order 54 const SEC_ERROR_INVALID_TIME = SEC_ERROR_BASE + 8; 55 const SEC_ERROR_BAD_DER = SEC_ERROR_BASE + 9; 56 const SEC_ERROR_BAD_SIGNATURE = SEC_ERROR_BASE + 10; 57 const SEC_ERROR_EXPIRED_CERTIFICATE = SEC_ERROR_BASE + 11; 58 const SEC_ERROR_REVOKED_CERTIFICATE = SEC_ERROR_BASE + 12; 59 const SEC_ERROR_UNKNOWN_ISSUER = SEC_ERROR_BASE + 13; 60 const SEC_ERROR_UNTRUSTED_ISSUER = SEC_ERROR_BASE + 20; 61 const SEC_ERROR_UNTRUSTED_CERT = SEC_ERROR_BASE + 21; 62 const SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE = SEC_ERROR_BASE + 30; 63 const SEC_ERROR_CA_CERT_INVALID = SEC_ERROR_BASE + 36; 64 const SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION = SEC_ERROR_BASE + 41; 65 const SEC_ERROR_PKCS7_BAD_SIGNATURE = SEC_ERROR_BASE + 47; 66 const SEC_ERROR_INADEQUATE_KEY_USAGE = SEC_ERROR_BASE + 90; 67 const SEC_ERROR_INADEQUATE_CERT_TYPE = SEC_ERROR_BASE + 91; 68 const SEC_ERROR_CERT_NOT_IN_NAME_SPACE = SEC_ERROR_BASE + 112; 69 const SEC_ERROR_CERT_BAD_ACCESS_LOCATION = SEC_ERROR_BASE + 117; 70 const SEC_ERROR_OCSP_MALFORMED_REQUEST = SEC_ERROR_BASE + 120; 71 const SEC_ERROR_OCSP_SERVER_ERROR = SEC_ERROR_BASE + 121; 72 const SEC_ERROR_OCSP_TRY_SERVER_LATER = SEC_ERROR_BASE + 122; 73 const SEC_ERROR_OCSP_REQUEST_NEEDS_SIG = SEC_ERROR_BASE + 123; 74 const SEC_ERROR_OCSP_UNAUTHORIZED_REQUEST = SEC_ERROR_BASE + 124; 75 const SEC_ERROR_OCSP_UNKNOWN_CERT = SEC_ERROR_BASE + 126; 76 const SEC_ERROR_OCSP_MALFORMED_RESPONSE = SEC_ERROR_BASE + 129; 77 const SEC_ERROR_OCSP_UNAUTHORIZED_RESPONSE = SEC_ERROR_BASE + 130; 78 const SEC_ERROR_OCSP_OLD_RESPONSE = SEC_ERROR_BASE + 132; 79 const SEC_ERROR_UNSUPPORTED_ELLIPTIC_CURVE = SEC_ERROR_BASE + 141; 80 const SEC_ERROR_OCSP_INVALID_SIGNING_CERT = SEC_ERROR_BASE + 144; 81 const SEC_ERROR_POLICY_VALIDATION_FAILED = SEC_ERROR_BASE + 160; 82 const SEC_ERROR_OCSP_BAD_SIGNATURE = SEC_ERROR_BASE + 157; 83 const SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED = SEC_ERROR_BASE + 176; 84 85 const SSL_ERROR_NO_CYPHER_OVERLAP = SSL_ERROR_BASE + 2; 86 const SSL_ERROR_BAD_CERT_DOMAIN = SSL_ERROR_BASE + 12; 87 const SSL_ERROR_BAD_CERT_ALERT = SSL_ERROR_BASE + 17; 88 const SSL_ERROR_WEAK_SERVER_CERT_KEY = SSL_ERROR_BASE + 132; 89 const SSL_ERROR_DC_INVALID_KEY_USAGE = SSL_ERROR_BASE + 184; 90 91 const SSL_ERROR_ECH_RETRY_WITH_ECH = SSL_ERROR_BASE + 188; 92 const SSL_ERROR_ECH_RETRY_WITHOUT_ECH = SSL_ERROR_BASE + 189; 93 const SSL_ERROR_ECH_FAILED = SSL_ERROR_BASE + 190; 94 const SSL_ERROR_ECH_REQUIRED_ALERT = SSL_ERROR_BASE + 191; 95 96 const MOZILLA_PKIX_ERROR_KEY_PINNING_FAILURE = MOZILLA_PKIX_ERROR_BASE + 0; 97 const MOZILLA_PKIX_ERROR_CA_CERT_USED_AS_END_ENTITY = 98 MOZILLA_PKIX_ERROR_BASE + 1; 99 const MOZILLA_PKIX_ERROR_INADEQUATE_KEY_SIZE = MOZILLA_PKIX_ERROR_BASE + 2; 100 const MOZILLA_PKIX_ERROR_V1_CERT_USED_AS_CA = MOZILLA_PKIX_ERROR_BASE + 3; 101 const MOZILLA_PKIX_ERROR_NOT_YET_VALID_CERTIFICATE = 102 MOZILLA_PKIX_ERROR_BASE + 5; 103 const MOZILLA_PKIX_ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE = 104 MOZILLA_PKIX_ERROR_BASE + 6; 105 const MOZILLA_PKIX_ERROR_OCSP_RESPONSE_FOR_CERT_MISSING = 106 MOZILLA_PKIX_ERROR_BASE + 8; 107 const MOZILLA_PKIX_ERROR_REQUIRED_TLS_FEATURE_MISSING = 108 MOZILLA_PKIX_ERROR_BASE + 10; 109 const MOZILLA_PKIX_ERROR_EMPTY_ISSUER_NAME = MOZILLA_PKIX_ERROR_BASE + 12; 110 const MOZILLA_PKIX_ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED = 111 MOZILLA_PKIX_ERROR_BASE + 13; 112 const MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT = MOZILLA_PKIX_ERROR_BASE + 14; 113 const MOZILLA_PKIX_ERROR_MITM_DETECTED = MOZILLA_PKIX_ERROR_BASE + 15; 114 const MOZILLA_PKIX_ERROR_INSUFFICIENT_CERTIFICATE_TRANSPARENCY = 115 MOZILLA_PKIX_ERROR_BASE + 16; 116 const MOZILLA_PKIX_ERROR_ISSUER_NO_LONGER_TRUSTED = 117 MOZILLA_PKIX_ERROR_BASE + 17; 118 119 // A map from the name of a certificate usage to the value of the usage. 120 // Useful for printing debugging information and for enumerating all supported 121 // usages. 122 const verifyUsages = new Map([ 123 ["verifyUsageTLSClient", Ci.nsIX509CertDB.verifyUsageTLSClient], 124 ["verifyUsageTLSServer", Ci.nsIX509CertDB.verifyUsageTLSServer], 125 ["verifyUsageTLSServerCA", Ci.nsIX509CertDB.verifyUsageTLSServerCA], 126 ["verifyUsageEmailSigner", Ci.nsIX509CertDB.verifyUsageEmailSigner], 127 ["verifyUsageEmailRecipient", Ci.nsIX509CertDB.verifyUsageEmailRecipient], 128 ]); 129 130 const NO_FLAGS = 0; 131 132 const CRLiteModeDisabledPrefValue = 0; 133 const CRLiteModeTelemetryOnlyPrefValue = 1; 134 const CRLiteModeEnforcePrefValue = 2; 135 136 // Convert a string to an array of bytes consisting of the char code at each 137 // index. 138 function stringToArray(s) { 139 let a = []; 140 for (let i = 0; i < s.length; i++) { 141 a.push(s.charCodeAt(i)); 142 } 143 return a; 144 } 145 146 // Converts an array of bytes to a JS string using fromCharCode on each byte. 147 function arrayToString(a) { 148 let s = ""; 149 for (let b of a) { 150 s += String.fromCharCode(b); 151 } 152 return s; 153 } 154 155 // Commonly certificates are represented as PEM. The format is roughly as 156 // follows: 157 // 158 // -----BEGIN CERTIFICATE----- 159 // [some lines of base64, each typically 64 characters long] 160 // -----END CERTIFICATE----- 161 // 162 // However, nsIX509CertDB.constructX509FromBase64 and related functions do not 163 // handle input of this form. Instead, they require a single string of base64 164 // with no newlines or BEGIN/END headers. This is a helper function to convert 165 // PEM to the format that nsIX509CertDB requires. 166 function pemToBase64(pem) { 167 return pem 168 .replace(/-----BEGIN (CERTIFICATE|(EC )?PRIVATE KEY)-----/, "") 169 .replace(/-----END (CERTIFICATE|(EC )?PRIVATE KEY)-----/, "") 170 .replace(/[\r\n]/g, ""); 171 } 172 173 function build_cert_chain(certNames, testDirectory = "bad_certs") { 174 let certList = []; 175 certNames.forEach(function (certName) { 176 let cert = constructCertFromFile(`${testDirectory}/${certName}.pem`); 177 certList.push(cert); 178 }); 179 return certList; 180 } 181 182 function areCertsEqual(certA, certB) { 183 let derA = certA.getRawDER(); 184 let derB = certB.getRawDER(); 185 if (derA.length != derB.length) { 186 return false; 187 } 188 for (let i = 0; i < derA.length; i++) { 189 if (derA[i] != derB[i]) { 190 return false; 191 } 192 } 193 return true; 194 } 195 196 function areCertArraysEqual(certArrayA, certArrayB) { 197 if (certArrayA.length != certArrayB.length) { 198 return false; 199 } 200 201 for (let i = 0; i < certArrayA.length; i++) { 202 const certA = certArrayA[i]; 203 const certB = certArrayB[i]; 204 if (!areCertsEqual(certA, certB)) { 205 return false; 206 } 207 } 208 return true; 209 } 210 211 function readFile(file) { 212 let fstream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance( 213 Ci.nsIFileInputStream 214 ); 215 fstream.init(file, -1, 0, 0); 216 let available = fstream.available(); 217 let data = 218 available > 0 ? NetUtil.readInputStreamToString(fstream, available) : ""; 219 fstream.close(); 220 return data; 221 } 222 223 function readBinaryFile(file) { 224 let fstream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance( 225 Ci.nsIFileInputStream 226 ); 227 fstream.init(file, -1, 0, 0); 228 let available = fstream.available(); 229 let bytes = NetUtil.readInputStream(fstream, available); 230 fstream.close(); 231 return new Uint8Array(bytes); 232 } 233 234 function addCertFromFile(certdb, filename, trustString) { 235 let certFile = do_get_file(filename, false); 236 let certBytes = readFile(certFile); 237 try { 238 return certdb.addCert(certBytes, trustString); 239 } catch (e) {} 240 // It might be PEM instead of DER. 241 return certdb.addCertFromBase64(pemToBase64(certBytes), trustString); 242 } 243 244 function constructCertFromFile(filename) { 245 let certFile = do_get_file(filename, false); 246 let certBytes = readFile(certFile); 247 let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService( 248 Ci.nsIX509CertDB 249 ); 250 try { 251 return certdb.constructX509(stringToArray(certBytes)); 252 } catch (e) {} 253 // It might be PEM instead of DER. 254 return certdb.constructX509FromBase64(pemToBase64(certBytes)); 255 } 256 257 function setCertTrust(cert, trustString) { 258 let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService( 259 Ci.nsIX509CertDB 260 ); 261 certdb.setCertTrustFromString(cert, trustString); 262 } 263 264 function getXPCOMStatusFromNSS(statusNSS) { 265 let nssErrorsService = Cc["@mozilla.org/nss_errors_service;1"].getService( 266 Ci.nsINSSErrorsService 267 ); 268 return nssErrorsService.getXPCOMFromNSSError(statusNSS); 269 } 270 271 // Helper for checkCertErrorGenericAtTime 272 class CertVerificationExpectedErrorResult { 273 constructor(certName, expectedError, expectedEVStatus, resolve) { 274 this.certName = certName; 275 this.expectedError = expectedError; 276 this.expectedEVStatus = expectedEVStatus; 277 this.resolve = resolve; 278 } 279 280 verifyCertFinished(aPRErrorCode, aVerifiedChain, aHasEVPolicy) { 281 equal( 282 aPRErrorCode, 283 this.expectedError, 284 `verifying ${this.certName}: should get error ${this.expectedError}` 285 ); 286 if (this.expectedEVStatus != undefined) { 287 equal( 288 aHasEVPolicy, 289 this.expectedEVStatus, 290 `verifying ${this.certName}: ` + 291 `should ${this.expectedEVStatus ? "be" : "not be"} EV` 292 ); 293 } 294 this.resolve(); 295 } 296 } 297 298 // certdb implements nsIX509CertDB. See nsIX509CertDB.idl for documentation. 299 // In particular, hostname is optional. 300 function checkCertErrorGenericAtTime( 301 certdb, 302 cert, 303 expectedError, 304 usage, 305 time, 306 /* optional */ isEVExpected, 307 /* optional */ hostname, 308 /* optional */ flags = NO_FLAGS, 309 /* optional */ sctsFromTls = [] 310 ) { 311 return new Promise(resolve => { 312 let result = new CertVerificationExpectedErrorResult( 313 cert.commonName, 314 expectedError, 315 isEVExpected, 316 resolve 317 ); 318 certdb.asyncVerifyCertAtTime( 319 cert, 320 usage, 321 flags, 322 hostname, 323 time, 324 sctsFromTls, 325 result 326 ); 327 }); 328 } 329 330 // certdb implements nsIX509CertDB. See nsIX509CertDB.idl for documentation. 331 // In particular, hostname is optional. 332 function checkCertErrorGeneric( 333 certdb, 334 cert, 335 expectedError, 336 usage, 337 /* optional */ isEVExpected, 338 /* optional */ hostname 339 ) { 340 let now = new Date().getTime() / 1000; 341 return checkCertErrorGenericAtTime( 342 certdb, 343 cert, 344 expectedError, 345 usage, 346 now, 347 isEVExpected, 348 hostname 349 ); 350 } 351 352 // Helper for checkRootOfBuiltChain 353 class CertVerificationExpectedRootResult { 354 constructor(certName, rootSha256SpkiDigest, resolve) { 355 this.certName = certName; 356 this.rootSha256SpkiDigest = rootSha256SpkiDigest; 357 this.resolve = resolve; 358 } 359 360 verifyCertFinished(aPRErrorCode, aVerifiedChain, _aHasEVPolicy) { 361 equal( 362 aPRErrorCode, 363 PRErrorCodeSuccess, 364 `verifying ${this.certName}: should succeed` 365 ); 366 equal( 367 aVerifiedChain[aVerifiedChain.length - 1] 368 .sha256SubjectPublicKeyInfoDigest, 369 this.rootSha256SpkiDigest, 370 `verifying ${this.certName}: should build chain to ${this.rootSha256SpkiDigest}` 371 ); 372 this.resolve(); 373 } 374 } 375 376 function checkRootOfBuiltChain( 377 certdb, 378 cert, 379 rootSha256SpkiDigest, 380 time, 381 /* optional */ hostname, 382 /* optional */ flags = NO_FLAGS 383 ) { 384 return new Promise(resolve => { 385 let result = new CertVerificationExpectedRootResult( 386 cert.commonName, 387 rootSha256SpkiDigest, 388 resolve 389 ); 390 certdb.asyncVerifyCertAtTime( 391 cert, 392 Ci.nsIX509CertDB.verifyUsageTLSServer, 393 flags, 394 hostname, 395 time, 396 [], 397 result 398 ); 399 }); 400 } 401 402 function checkEVStatus(certDB, cert, usage, isEVExpected) { 403 return checkCertErrorGeneric( 404 certDB, 405 cert, 406 PRErrorCodeSuccess, 407 usage, 408 isEVExpected 409 ); 410 } 411 412 function _getLibraryFunctionWithNoArguments( 413 functionName, 414 libraryName, 415 returnType 416 ) { 417 // Open the NSS library. copied from services/crypto/modules/WeaveCrypto.js 418 let path = ctypes.libraryName(libraryName); 419 420 // XXX really want to be able to pass specific dlopen flags here. 421 let nsslib; 422 try { 423 nsslib = ctypes.open(path); 424 } catch (e) { 425 // In case opening the library without a full path fails, 426 // try again with a full path. 427 let file = Services.dirsvc.get("GreBinD", Ci.nsIFile); 428 file.append(path); 429 nsslib = ctypes.open(file.path); 430 } 431 432 let SECStatus = ctypes.int; 433 let func = nsslib.declare( 434 functionName, 435 ctypes.default_abi, 436 returnType || SECStatus 437 ); 438 return func; 439 } 440 441 function clearOCSPCache() { 442 let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService( 443 Ci.nsIX509CertDB 444 ); 445 certdb.clearOCSPCache(); 446 } 447 448 function clearSessionCache() { 449 let nssComponent = Cc["@mozilla.org/psm;1"].getService(Ci.nsINSSComponent); 450 nssComponent.clearSSLExternalAndInternalSessionCache(); 451 } 452 453 function getSSLStatistics() { 454 let SSL3Statistics = new ctypes.StructType("SSL3Statistics", [ 455 { sch_sid_cache_hits: ctypes.long }, 456 { sch_sid_cache_misses: ctypes.long }, 457 { sch_sid_cache_not_ok: ctypes.long }, 458 { hsh_sid_cache_hits: ctypes.long }, 459 { hsh_sid_cache_misses: ctypes.long }, 460 { hsh_sid_cache_not_ok: ctypes.long }, 461 { hch_sid_cache_hits: ctypes.long }, 462 { hch_sid_cache_misses: ctypes.long }, 463 { hch_sid_cache_not_ok: ctypes.long }, 464 { sch_sid_stateless_resumes: ctypes.long }, 465 { hsh_sid_stateless_resumes: ctypes.long }, 466 { hch_sid_stateless_resumes: ctypes.long }, 467 { hch_sid_ticket_parse_failures: ctypes.long }, 468 ]); 469 let SSL3StatisticsPtr = new ctypes.PointerType(SSL3Statistics); 470 let SSL_GetStatistics = null; 471 try { 472 SSL_GetStatistics = _getLibraryFunctionWithNoArguments( 473 "SSL_GetStatistics", 474 "ssl3", 475 SSL3StatisticsPtr 476 ); 477 } catch (e) { 478 // On Windows, this is actually in the nss3 library. 479 SSL_GetStatistics = _getLibraryFunctionWithNoArguments( 480 "SSL_GetStatistics", 481 "nss3", 482 SSL3StatisticsPtr 483 ); 484 } 485 if (!SSL_GetStatistics) { 486 throw new Error("Failed to get SSL statistics"); 487 } 488 return SSL_GetStatistics(); 489 } 490 491 // Set up a TLS testing environment that has a TLS server running and 492 // ready to accept connections. This async function starts the server and 493 // waits for the server to indicate that it is ready. 494 // 495 // Each test should have its own subdomain of example.com, for example 496 // my-first-connection-test.example.com. The server can use the server 497 // name (passed through the SNI TLS extension) to determine what behavior 498 // the server side of the text should exhibit. See TLSServer.h for more 499 // information on how to write the server side of tests. 500 // 501 // Create a new source file for your new server executable in 502 // security/manager/ssl/tests/unit/tlsserver/cmd similar to the other ones in 503 // that directory, and add a reference to it to the sources variable in that 504 // directory's moz.build. 505 // 506 // Modify TEST_HARNESS_BINS in 507 // testing/mochitest/Makefile.in and NO_PKG_FILES in 508 // toolkit/mozapps/installer/packager.mk to make sure the new executable 509 // gets included in the packages used for shipping the tests to the test 510 // runners in our build/test farm. (Things will work fine locally without 511 // these changes but will break on TBPL.) 512 // 513 // Your test script should look something like this: 514 /* 515 516 // -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- 517 // This Source Code Form is subject to the terms of the Mozilla Public 518 // License, v. 2.0. If a copy of the MPL was not distributed with this 519 // file, You can obtain one at http://mozilla.org/MPL/2.0/. 520 "use strict"; 521 522 // <documentation on your test> 523 524 function run_test() { 525 do_get_profile(); 526 add_tls_server_setup("<test-server-name>", "<path-to-certificate-directory>"); 527 528 add_connection_test("<test-name-1>.example.com", 529 SEC_ERROR_xxx, 530 function() { ... }, 531 function(aTransportSecurityInfo) { ... }, 532 function(aTransport) { ... }); 533 [...] 534 add_connection_test("<test-name-n>.example.com", PRErrorCodeSuccess); 535 536 run_next_test(); 537 } 538 */ 539 540 function add_tls_server_setup(serverBinName, certsPath, addDefaultRoot = true) { 541 add_test(function () { 542 _setupTLSServerTest(serverBinName, certsPath, addDefaultRoot); 543 }); 544 } 545 546 /** 547 * Add a TLS connection test case. 548 * 549 * @param {string} aHost 550 * The hostname to pass in the SNI TLS extension; this should unambiguously 551 * identify which test is being run. 552 * @param {PRErrorCode} aExpectedResult 553 * The expected result of the connection. If an error is not expected, pass 554 * in PRErrorCodeSuccess. 555 * @param {Function} aBeforeConnect 556 * A callback function that takes no arguments that will be called before the 557 * connection is attempted. 558 * @param {Function} aWithSecurityInfo 559 * A callback function that takes an nsITransportSecurityInfo, which is called 560 * after the TLS handshake succeeds. 561 * @param {Function} aAfterStreamOpen 562 * A callback function that is called with the nsISocketTransport once the 563 * output stream is ready. 564 * @param {OriginAttributes} aOriginAttributes (optional) 565 * The origin attributes that the socket transport will have. This parameter 566 * affects OCSP because OCSP cache is double-keyed by origin attributes' first 567 * party domain. 568 * 569 * @param {OriginAttributes} aEchConfig (optional) 570 * A Base64-encoded ECHConfig. If non-empty, it will be configured to the client 571 * socket resulting in an Encrypted Client Hello extension being sent. The client 572 * keypair is ephermeral and generated within NSS. 573 */ 574 function add_connection_test( 575 aHost, 576 aExpectedResult, 577 aBeforeConnect, 578 aWithSecurityInfo, 579 aAfterStreamOpen, 580 /* optional */ aOriginAttributes, 581 /* optional */ aEchConfig 582 ) { 583 add_test(function () { 584 if (aBeforeConnect) { 585 aBeforeConnect(); 586 } 587 asyncConnectTo( 588 aHost, 589 aExpectedResult, 590 aWithSecurityInfo, 591 aAfterStreamOpen, 592 aOriginAttributes, 593 aEchConfig 594 ).then(run_next_test); 595 }); 596 } 597 598 async function asyncConnectTo( 599 aHost, 600 aExpectedResult, 601 /* optional */ aWithSecurityInfo = undefined, 602 /* optional */ aAfterStreamOpen = undefined, 603 /* optional */ aOriginAttributes = undefined, 604 /* optional */ aEchConfig = undefined 605 ) { 606 const REMOTE_PORT = 8443; 607 608 function Connection(host) { 609 this.host = host; 610 this.thread = Services.tm.currentThread; 611 this.defer = Promise.withResolvers(); 612 let sts = Cc["@mozilla.org/network/socket-transport-service;1"].getService( 613 Ci.nsISocketTransportService 614 ); 615 this.transport = sts.createTransport( 616 ["ssl"], 617 host, 618 REMOTE_PORT, 619 null, 620 null 621 ); 622 if (aEchConfig) { 623 this.transport.setEchConfig(atob(aEchConfig)); 624 } 625 // See bug 1129771 - attempting to connect to [::1] when the server is 626 // listening on 127.0.0.1 causes frequent failures on OS X 10.10. 627 this.transport.connectionFlags |= Ci.nsISocketTransport.DISABLE_IPV6; 628 this.transport.setEventSink(this, this.thread); 629 if (aOriginAttributes) { 630 this.transport.originAttributes = aOriginAttributes; 631 } 632 this.inputStream = null; 633 this.outputStream = null; 634 this.connected = false; 635 } 636 637 Connection.prototype = { 638 // nsITransportEventSink 639 onTransportStatus(aTransport, aStatus) { 640 if ( 641 !this.connected && 642 aStatus == Ci.nsISocketTransport.STATUS_CONNECTED_TO 643 ) { 644 this.connected = true; 645 this.outputStream.asyncWait(this, 0, 0, this.thread); 646 } 647 }, 648 649 // nsIInputStreamCallback 650 onInputStreamReady(aStream) { 651 try { 652 // this will throw if the stream has been closed by an error 653 let str = NetUtil.readInputStreamToString(aStream, aStream.available()); 654 Assert.equal(str, "0", "Should have received ASCII '0' from server"); 655 this.inputStream.close(); 656 this.outputStream.close(); 657 this.result = Cr.NS_OK; 658 } catch (e) { 659 this.result = e.result; 660 } 661 this.defer.resolve(this); 662 }, 663 664 // nsIOutputStreamCallback 665 onOutputStreamReady() { 666 if (aAfterStreamOpen) { 667 aAfterStreamOpen(this.transport); 668 } 669 this.outputStream.write("0", 1); 670 let inStream = this.transport 671 .openInputStream(0, 0, 0) 672 .QueryInterface(Ci.nsIAsyncInputStream); 673 this.inputStream = inStream; 674 this.inputStream.asyncWait(this, 0, 0, this.thread); 675 }, 676 677 go() { 678 this.outputStream = this.transport 679 .openOutputStream(0, 0, 0) 680 .QueryInterface(Ci.nsIAsyncOutputStream); 681 return this.defer.promise; 682 }, 683 }; 684 685 /* Returns a promise to connect to host that resolves to the result of that 686 * connection */ 687 function connectTo(host) { 688 Services.prefs.setCharPref("network.dns.localDomains", host); 689 let connection = new Connection(host); 690 return connection.go(); 691 } 692 693 return connectTo(aHost).then(async function (conn) { 694 info("handling " + aHost); 695 let expectedNSResult = 696 aExpectedResult == PRErrorCodeSuccess 697 ? Cr.NS_OK 698 : getXPCOMStatusFromNSS(aExpectedResult); 699 Assert.equal( 700 conn.result, 701 expectedNSResult, 702 "Actual and expected connection result should match" 703 ); 704 if (aWithSecurityInfo) { 705 aWithSecurityInfo( 706 await conn.transport.tlsSocketControl.asyncGetSecurityInfo() 707 ); 708 } 709 }); 710 } 711 712 function _getBinaryUtil(binaryUtilName) { 713 let utilBin = Services.dirsvc.get("GreD", Ci.nsIFile); 714 // On macOS, GreD is .../Contents/Resources, and most binary utilities 715 // are located there, but certutil is in GreBinD (or .../Contents/MacOS), 716 // so we have to change the path accordingly. 717 if (binaryUtilName === "certutil") { 718 utilBin = Services.dirsvc.get("GreBinD", Ci.nsIFile); 719 } 720 utilBin.append(binaryUtilName + mozinfo.bin_suffix); 721 // If we're testing locally, the above works. If not, the server executable 722 // is in another location. 723 if (!utilBin.exists()) { 724 utilBin = Services.dirsvc.get("CurWorkD", Ci.nsIFile); 725 while (utilBin.path.includes("xpcshell")) { 726 utilBin = utilBin.parent; 727 } 728 utilBin.append("bin"); 729 utilBin.append(binaryUtilName + mozinfo.bin_suffix); 730 } 731 // But maybe we're on Android, where binaries are in /data/local/xpcb. 732 if (!utilBin.exists()) { 733 utilBin.initWithPath("/data/local/xpcb/"); 734 utilBin.append(binaryUtilName); 735 } 736 Assert.ok(utilBin.exists(), `Binary util ${binaryUtilName} should exist`); 737 return utilBin; 738 } 739 740 // Do not call this directly; use add_tls_server_setup 741 function _setupTLSServerTest(serverBinName, certsPath, addDefaultRoot) { 742 asyncStartTLSTestServer(serverBinName, certsPath, addDefaultRoot).then( 743 run_next_test 744 ); 745 } 746 747 async function asyncStartTLSTestServer( 748 serverBinName, 749 certsPath, 750 addDefaultRoot 751 ) { 752 let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService( 753 Ci.nsIX509CertDB 754 ); 755 // The trusted CA that is typically used for "good" certificates. 756 if (addDefaultRoot) { 757 addCertFromFile(certdb, `${certsPath}/test-ca.pem`, "CTu,u,u"); 758 } 759 760 const CALLBACK_PORT = 8444; 761 762 let greBinDir = Services.dirsvc.get("GreBinD", Ci.nsIFile); 763 Services.env.set("DYLD_LIBRARY_PATH", greBinDir.path); 764 // TODO(bug 1107794): Android libraries are in /data/local/xpcb, but "GreBinD" 765 // does not return this path on Android, so hard code it here. 766 Services.env.set("LD_LIBRARY_PATH", greBinDir.path + ":/data/local/xpcb"); 767 Services.env.set("MOZ_TLS_SERVER_DEBUG_LEVEL", "3"); 768 Services.env.set("MOZ_TLS_SERVER_CALLBACK_PORT", CALLBACK_PORT); 769 770 let httpServer = new HttpServer(); 771 let serverReady = new Promise(resolve => { 772 httpServer.registerPathHandler( 773 "/", 774 function handleServerCallback(aRequest, aResponse) { 775 aResponse.setStatusLine(aRequest.httpVersion, 200, "OK"); 776 aResponse.setHeader("Content-Type", "text/plain"); 777 let responseBody = "OK!"; 778 aResponse.bodyOutputStream.write(responseBody, responseBody.length); 779 executeSoon(function () { 780 httpServer.stop(resolve); 781 }); 782 } 783 ); 784 httpServer.start(CALLBACK_PORT); 785 }); 786 787 let serverBin = _getBinaryUtil(serverBinName); 788 let process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess); 789 process.init(serverBin); 790 let certDir = Services.dirsvc.get("CurWorkD", Ci.nsIFile); 791 certDir.append(`${certsPath}`); 792 Assert.ok(certDir.exists(), `certificate folder (${certsPath}) should exist`); 793 // Using "sql:" causes the SQL DB to be used so we can run tests on Android. 794 process.run(false, ["sql:" + certDir.path, Services.appinfo.processID], 2); 795 796 registerCleanupFunction(function () { 797 process.kill(); 798 }); 799 800 await serverReady; 801 } 802 803 // Returns an Array of OCSP responses for a given ocspRespArray and a location 804 // for a nssDB where the certs and public keys are prepopulated. 805 // ocspRespArray is an array of arrays like: 806 // [ [typeOfResponse, certnick, extracertnick, thisUpdateSkew]...] 807 function generateOCSPResponses(ocspRespArray, nssDBlocation) { 808 let utilBinName = "GenerateOCSPResponse"; 809 let ocspGenBin = _getBinaryUtil(utilBinName); 810 let retArray = []; 811 812 for (let i = 0; i < ocspRespArray.length; i++) { 813 let argArray = []; 814 let ocspFilepre = do_get_file(i.toString() + ".ocsp", true); 815 let filename = ocspFilepre.path; 816 // Using "sql:" causes the SQL DB to be used so we can run tests on Android. 817 argArray.push("sql:" + nssDBlocation); 818 argArray.push(ocspRespArray[i][0]); // ocsRespType; 819 argArray.push(ocspRespArray[i][1]); // nick; 820 argArray.push(ocspRespArray[i][2]); // extranickname 821 argArray.push(ocspRespArray[i][3]); // thisUpdate skew 822 argArray.push(filename); 823 info("argArray = " + argArray); 824 825 let process = Cc["@mozilla.org/process/util;1"].createInstance( 826 Ci.nsIProcess 827 ); 828 process.init(ocspGenBin); 829 process.run(true, argArray, argArray.length); 830 Assert.equal(0, process.exitValue, "Process exit value should be 0"); 831 let ocspFile = do_get_file(i.toString() + ".ocsp", false); 832 retArray.push(readFile(ocspFile)); 833 ocspFile.remove(false); 834 } 835 return retArray; 836 } 837 838 // Starts and returns an http responder that will cause a test failure if it is 839 // queried. The server identities are given by a non-empty array 840 // serverIdentities. 841 function getFailingHttpServer(serverPort, serverIdentities) { 842 let httpServer = new HttpServer(); 843 httpServer.registerPrefixHandler("/", function () { 844 Assert.ok(false, "HTTP responder should not have been queried"); 845 }); 846 httpServer.identity.setPrimary("http", serverIdentities.shift(), serverPort); 847 serverIdentities.forEach(function (identity) { 848 httpServer.identity.add("http", identity, serverPort); 849 }); 850 httpServer.start(serverPort); 851 return httpServer; 852 } 853 854 // Starts an http OCSP responder that serves good OCSP responses and 855 // returns an object with a method stop that should be called to stop 856 // the http server. 857 // NB: Because generating OCSP responses inside the HTTP request 858 // handler can cause timeouts, the expected responses are pre-generated 859 // all at once before starting the server. This means that their producedAt 860 // times will all be the same. If a test depends on this not being the case, 861 // perhaps calling startOCSPResponder twice (at different times) will be 862 // necessary. 863 // 864 // serverPort is the port of the http OCSP responder 865 // identity is the http hostname that will answer the OCSP requests 866 // nssDBLocation is the location of the NSS database from where the OCSP 867 // responses will be generated (assumes appropiate keys are present) 868 // expectedCertNames is an array of nicks of the certs to be responsed 869 // expectedBasePaths is an optional array that is used to indicate 870 // what is the expected base path of the OCSP request. 871 // expectedMethods is an optional array of methods ("GET" or "POST") indicating 872 // by which HTTP method the server is expected to be queried. 873 // expectedResponseTypes is an optional array of OCSP response types to use (see 874 // GenerateOCSPResponse.cpp). 875 // responseHeaderPairs is an optional array of HTTP header (name, value) pairs 876 // to set in each response. 877 function startOCSPResponder( 878 serverPort, 879 identity, 880 nssDBLocation, 881 expectedCertNames, 882 expectedBasePaths, 883 expectedMethods, 884 expectedResponseTypes, 885 responseHeaderPairs = [] 886 ) { 887 let ocspResponseGenerationArgs = expectedCertNames.map( 888 function (expectedNick) { 889 let responseType = "good"; 890 if (expectedResponseTypes && expectedResponseTypes.length >= 1) { 891 responseType = expectedResponseTypes.shift(); 892 } 893 return [responseType, expectedNick, "unused", 0]; 894 } 895 ); 896 let ocspResponses = generateOCSPResponses( 897 ocspResponseGenerationArgs, 898 nssDBLocation 899 ); 900 let httpServer = new HttpServer(); 901 httpServer.registerPrefixHandler( 902 "/", 903 function handleServerCallback(aRequest, aResponse) { 904 info("got request for: " + aRequest.path); 905 let basePath = aRequest.path.slice(1).split("/")[0]; 906 if (expectedBasePaths.length >= 1) { 907 Assert.equal( 908 basePath, 909 expectedBasePaths.shift(), 910 "Actual and expected base path should match" 911 ); 912 } 913 Assert.greaterOrEqual( 914 expectedCertNames.length, 915 1, 916 "expectedCertNames should contain >= 1 entries" 917 ); 918 if (expectedMethods && expectedMethods.length >= 1) { 919 Assert.equal( 920 aRequest.method, 921 expectedMethods.shift(), 922 "Actual and expected fetch method should match" 923 ); 924 } 925 aResponse.setStatusLine(aRequest.httpVersion, 200, "OK"); 926 aResponse.setHeader("Content-Type", "application/ocsp-response"); 927 for (let headerPair of responseHeaderPairs) { 928 aResponse.setHeader(headerPair[0], headerPair[1]); 929 } 930 aResponse.write(ocspResponses.shift()); 931 } 932 ); 933 httpServer.identity.setPrimary("http", identity, serverPort); 934 httpServer.start(serverPort); 935 return { 936 stop(callback) { 937 // make sure we consumed each expected response 938 Assert.equal( 939 ocspResponses.length, 940 0, 941 "Should have 0 remaining expected OCSP responses" 942 ); 943 if (expectedMethods) { 944 Assert.equal( 945 expectedMethods.length, 946 0, 947 "Should have 0 remaining expected fetch methods" 948 ); 949 } 950 if (expectedBasePaths) { 951 Assert.equal( 952 expectedBasePaths.length, 953 0, 954 "Should have 0 remaining expected base paths" 955 ); 956 } 957 if (expectedResponseTypes) { 958 Assert.equal( 959 expectedResponseTypes.length, 960 0, 961 "Should have 0 remaining expected response types" 962 ); 963 } 964 httpServer.stop(callback); 965 }, 966 }; 967 } 968 969 // Given an OCSP responder (see startOCSPResponder), returns a promise that 970 // resolves when the responder has successfully stopped. 971 function stopOCSPResponder(responder) { 972 return new Promise(resolve => { 973 responder.stop(resolve); 974 }); 975 } 976 977 // Utility functions for adding tests relating to certificate error overrides 978 979 // Helper function for add_cert_override_test. Probably doesn't need to be 980 // called directly. 981 function add_cert_override(aHost, aSecurityInfo) { 982 let cert = aSecurityInfo.serverCert; 983 let certOverrideService = Cc[ 984 "@mozilla.org/security/certoverride;1" 985 ].getService(Ci.nsICertOverrideService); 986 certOverrideService.rememberValidityOverride(aHost, 8443, {}, cert, true); 987 } 988 989 // Given a host and an expected error code, tests that an initial connection to 990 // the host fails with the expected error and that adding an override results 991 // in a subsequent connection succeeding. 992 function add_cert_override_test(aHost, aExpectedError) { 993 add_connection_test( 994 aHost, 995 aExpectedError, 996 null, 997 add_cert_override.bind(this, aHost) 998 ); 999 add_connection_test(aHost, PRErrorCodeSuccess, null, aSecurityInfo => { 1000 Assert.ok( 1001 aSecurityInfo.securityState & 1002 Ci.nsIWebProgressListener.STATE_CERT_USER_OVERRIDDEN, 1003 "Cert override flag should be set on the security state" 1004 ); 1005 }); 1006 } 1007 1008 // Helper function for add_prevented_cert_override_test. This is much like 1009 // add_cert_override except it may not be the case that the connection has an 1010 // SecInfo set on it. In this case, the error was not overridable anyway, so 1011 // we consider it a success. 1012 function attempt_adding_cert_override(aHost, aSecurityInfo) { 1013 if (aSecurityInfo.serverCert) { 1014 let cert = aSecurityInfo.serverCert; 1015 let certOverrideService = Cc[ 1016 "@mozilla.org/security/certoverride;1" 1017 ].getService(Ci.nsICertOverrideService); 1018 certOverrideService.rememberValidityOverride(aHost, 8443, {}, cert, true); 1019 } 1020 } 1021 1022 // Given a host and an expected error code, tests that an initial connection to 1023 // the host fails with the expected error and that adding an override does not 1024 // result in a subsequent connection succeeding (i.e. the same error code is 1025 // encountered). 1026 // The idea here is that for HSTS hosts or hosts with key pins, no error is 1027 // overridable, even if an entry is added to the override service. 1028 function add_prevented_cert_override_test(aHost, aExpectedError) { 1029 add_connection_test( 1030 aHost, 1031 aExpectedError, 1032 null, 1033 attempt_adding_cert_override.bind(this, aHost) 1034 ); 1035 add_connection_test(aHost, aExpectedError); 1036 } 1037 1038 // Helper for asyncTestCertificateUsages. 1039 class CertVerificationResult { 1040 constructor(certName, usageString, successExpected, resolve) { 1041 this.certName = certName; 1042 this.usageString = usageString; 1043 this.successExpected = successExpected; 1044 this.resolve = resolve; 1045 } 1046 1047 verifyCertFinished(aPRErrorCode) { 1048 if (this.successExpected) { 1049 equal( 1050 aPRErrorCode, 1051 PRErrorCodeSuccess, 1052 `verifying ${this.certName} for ${this.usageString} should succeed` 1053 ); 1054 } else { 1055 notEqual( 1056 aPRErrorCode, 1057 PRErrorCodeSuccess, 1058 `verifying ${this.certName} for ${this.usageString} should fail` 1059 ); 1060 } 1061 this.resolve(); 1062 } 1063 } 1064 1065 /** 1066 * Asynchronously attempts to verify the given certificate for all supported 1067 * usages (see allCertificateUsages). Verifies that the results match the 1068 * expected successful usages. Returns a promise that will resolve when all 1069 * verifications have been performed. 1070 * Verification happens "now" with no specified flags or hostname. 1071 * 1072 * @param {nsIX509CertDB} certdb 1073 * The certificate database to use to verify the certificate. 1074 * @param {nsIX509Cert} cert 1075 * The certificate to be verified. 1076 * @param {number[]} expectedUsages 1077 * A list of usages (as their integer values) that are expected to verify 1078 * successfully. 1079 * @returns {Promise} 1080 * A promise that will resolve with no value when all asynchronous operations 1081 * have completed. 1082 */ 1083 function asyncTestCertificateUsages(certdb, cert, expectedUsages) { 1084 let now = new Date().getTime() / 1000; 1085 let promises = []; 1086 verifyUsages.keys().forEach(usageString => { 1087 let promise = new Promise(resolve => { 1088 let usage = verifyUsages.get(usageString); 1089 let successExpected = expectedUsages.includes(usage); 1090 let result = new CertVerificationResult( 1091 cert.commonName, 1092 usageString, 1093 successExpected, 1094 resolve 1095 ); 1096 let flags = Ci.nsIX509CertDB.FLAG_LOCAL_ONLY; 1097 certdb.asyncVerifyCertAtTime(cert, usage, flags, null, now, [], result); 1098 }); 1099 promises.push(promise); 1100 }); 1101 return Promise.all(promises); 1102 } 1103 1104 /** 1105 * Loads the pkcs11testmodule.cpp test PKCS #11 module, and registers a cleanup 1106 * function that unloads it once the calling test completes. 1107 * 1108 * @param {nsIFile} libraryFile 1109 * The dynamic library file that implements the module to 1110 * load. 1111 * @param {string} moduleName 1112 * What to call the module. 1113 * @param {boolean} expectModuleUnloadToFail 1114 * Should be set to true for tests that manually unload the 1115 * test module, so the attempt to auto unload the test module 1116 * doesn't cause a test failure. Should be set to false 1117 * otherwise, so failure to automatically unload the test 1118 * module gets reported. 1119 */ 1120 function loadPKCS11Module(libraryFile, moduleName, expectModuleUnloadToFail) { 1121 ok(libraryFile.exists(), "The PKCS11 module file should exist"); 1122 1123 let pkcs11ModuleDB = Cc["@mozilla.org/security/pkcs11moduledb;1"].getService( 1124 Ci.nsIPKCS11ModuleDB 1125 ); 1126 registerCleanupFunction(() => { 1127 try { 1128 pkcs11ModuleDB.deleteModule(moduleName); 1129 } catch (e) { 1130 Assert.ok( 1131 expectModuleUnloadToFail, 1132 `Module unload should suceed only when expected: ${e}` 1133 ); 1134 } 1135 }); 1136 pkcs11ModuleDB.addModule(moduleName, libraryFile.path, 0, 0); 1137 } 1138 1139 /** 1140 * @param {string} data 1141 * @returns {string} 1142 */ 1143 function hexify(data) { 1144 // |slice(-2)| chomps off the last two characters of a string. 1145 // Therefore, if the Unicode value is < 0x10, we have a single-character hex 1146 // string when we want one that's two characters, and unconditionally 1147 // prepending a "0" solves the problem. 1148 return Array.from(data, (c, i) => 1149 ("0" + data.charCodeAt(i).toString(16)).slice(-2) 1150 ).join(""); 1151 } 1152 1153 /** 1154 * @param {string[]} lines 1155 * Lines to write. Each line automatically has "\n" appended to it when 1156 * being written. 1157 * @param {nsIFileOutputStream} outputStream 1158 */ 1159 function writeLinesAndClose(lines, outputStream) { 1160 for (let line of lines) { 1161 line += "\n"; 1162 outputStream.write(line, line.length); 1163 } 1164 outputStream.close(); 1165 } 1166 1167 /** 1168 * @param {string} moduleName 1169 * The name of the module that should not be loaded. 1170 * @param {string} libraryName 1171 * A unique substring of name of the dynamic library file of the module 1172 * that should not be loaded. 1173 */ 1174 function checkPKCS11ModuleNotPresent(moduleName, libraryName = "undefined") { 1175 let moduleDB = Cc["@mozilla.org/security/pkcs11moduledb;1"].getService( 1176 Ci.nsIPKCS11ModuleDB 1177 ); 1178 let modules = moduleDB.listModules(); 1179 ok( 1180 modules.hasMoreElements(), 1181 "One or more modules should be present with test module not present" 1182 ); 1183 for (let module of modules) { 1184 notEqual( 1185 module.name, 1186 moduleName, 1187 `Non-test module name shouldn't equal '${moduleName}'` 1188 ); 1189 if (libraryName != "undefined") { 1190 ok( 1191 !(module.libName && module.libName.includes(libraryName)), 1192 `Non-test module lib name should not include '${libraryName}'` 1193 ); 1194 } 1195 } 1196 } 1197 1198 /** 1199 * Checks that the test module exists in the module list. 1200 * Also checks various attributes of the test module for correctness. 1201 * 1202 * @param {string} moduleName 1203 * The name of the module that should be present. 1204 * @param {string} libraryName 1205 * A unique substring of the name of the dynamic library file 1206 * of the module that should be loaded. 1207 * @returns {nsIPKCS11Module} 1208 * The test module. 1209 */ 1210 function checkPKCS11ModuleExists(moduleName, libraryName = "undefined") { 1211 let moduleDB = Cc["@mozilla.org/security/pkcs11moduledb;1"].getService( 1212 Ci.nsIPKCS11ModuleDB 1213 ); 1214 let modules = moduleDB.listModules(); 1215 ok( 1216 modules.hasMoreElements(), 1217 "One or more modules should be present with test module present" 1218 ); 1219 let testModule = null; 1220 for (let module of modules) { 1221 if (module.name == moduleName) { 1222 testModule = module; 1223 break; 1224 } 1225 } 1226 notEqual(testModule, null, "Test module should have been found"); 1227 if (libraryName != "undefined") { 1228 notEqual( 1229 testModule.libName, 1230 null, 1231 "Test module lib name should not be null" 1232 ); 1233 ok( 1234 testModule.libName.includes(ctypes.libraryName(libraryName)), 1235 `Test module lib name should include lib name of '${libraryName}'` 1236 ); 1237 } 1238 1239 return testModule; 1240 } 1241 1242 // Given an nsIX509Cert, return the bytes of its subject DN (as a JS string) and 1243 // the sha-256 hash of its subject public key info, base64-encoded. 1244 function getSubjectAndSPKIHash(nsCert) { 1245 let certBytes = nsCert.getRawDER(); 1246 let cert = new X509.Certificate(); 1247 cert.parse(certBytes); 1248 let subject = cert.tbsCertificate.subject._der._bytes; 1249 let subjectString = arrayToString(subject); 1250 let spkiHashString = nsCert.sha256SubjectPublicKeyInfoDigest; 1251 return { subjectString, spkiHashString }; 1252 } 1253 1254 function run_certutil_on_directory(directory, args, expectSuccess = true) { 1255 let greBinDir = Services.dirsvc.get("GreBinD", Ci.nsIFile); 1256 Services.env.set("DYLD_LIBRARY_PATH", greBinDir.path); 1257 // TODO(bug 1107794): Android libraries are in /data/local/xpcb, but "GreBinD" 1258 // does not return this path on Android, so hard code it here. 1259 Services.env.set("LD_LIBRARY_PATH", greBinDir.path + ":/data/local/xpcb"); 1260 let certutilBin = _getBinaryUtil("certutil"); 1261 let process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess); 1262 process.init(certutilBin); 1263 args.push("-d"); 1264 args.push(`sql:${directory}`); 1265 process.run(true, args, args.length); 1266 if (expectSuccess) { 1267 Assert.equal(process.exitValue, 0, "certutil should succeed"); 1268 } 1269 } 1270 1271 function get_data_storage_contents(dataStorageFileName) { 1272 let stateFile = do_get_profile(); 1273 stateFile.append(dataStorageFileName); 1274 if (!stateFile.exists()) { 1275 return undefined; 1276 } 1277 return readFile(stateFile); 1278 } 1279 1280 function u16_to_big_endian_bytes(u16) { 1281 Assert.less(u16, 65536); 1282 return [u16 / 256, u16 % 256]; 1283 } 1284 1285 // Appends a line to the given data storage file (as an nsIOutputStream). 1286 // score is an integer representing the number of unique days the item has been accessed. 1287 // lastAccessed is the day since the epoch the item was last accessed. 1288 // key and value are strings representing the key and value of the item. 1289 function append_line_to_data_storage_file( 1290 outputStream, 1291 score, 1292 lastAccessed, 1293 key, 1294 value, 1295 valueLength = 24, 1296 useBadChecksum = false 1297 ) { 1298 let line = arrayToString(u16_to_big_endian_bytes(score)); 1299 line = line + arrayToString(u16_to_big_endian_bytes(lastAccessed)); 1300 line = line + key; 1301 let keyPadding = []; 1302 for (let i = 0; i < 256 - key.length; i++) { 1303 keyPadding.push(0); 1304 } 1305 line = line + arrayToString(keyPadding); 1306 line = line + value; 1307 let valuePadding = []; 1308 for (let i = 0; i < valueLength - value.length; i++) { 1309 valuePadding.push(0); 1310 } 1311 line = line + arrayToString(valuePadding); 1312 let checksum = 0; 1313 Assert.equal(line.length % 2, 0); 1314 for (let i = 0; i < line.length; i += 2) { 1315 checksum ^= (line.charCodeAt(i) << 8) + line.charCodeAt(i + 1); 1316 } 1317 line = 1318 arrayToString( 1319 u16_to_big_endian_bytes(useBadChecksum ? ~checksum & 0xffff : checksum) 1320 ) + line; 1321 outputStream.write(line, line.length); 1322 } 1323 1324 // Helper constants for setting security.pki.certificate_transparency.mode. 1325 const CT_MODE_DISABLE = 0; 1326 const CT_MODE_COLLECT_TELEMETRY = 1; 1327 const CT_MODE_ENFORCE = 2; 1328 1329 // Helper function for add_ct_test. Returns a function that checks that the 1330 // nsITransportSecurityInfo of the connection has the expected CT and resumed 1331 // statuses. 1332 function expectCT(expectedCTValue, expectedResumed) { 1333 return securityInfo => { 1334 Assert.equal( 1335 securityInfo.certificateTransparencyStatus, 1336 expectedCTValue, 1337 "actual and expected CT status should match" 1338 ); 1339 Assert.equal( 1340 securityInfo.resumed, 1341 expectedResumed, 1342 "connection should be resumed (or not) as expected" 1343 ); 1344 }; 1345 } 1346 1347 // Helper function to add a certificate transparency test. The connection is 1348 // expected to succeed with the given CT status (see nsITransportSecurityInfo). 1349 // Additionally, if an additional connection is made, it is expected that TLS 1350 // resumption is used and that the CT status is the same with the resumed 1351 // connection. 1352 function add_ct_test(host, expectedCTValue, expectConnectionSuccess) { 1353 add_connection_test( 1354 host, 1355 expectConnectionSuccess 1356 ? PRErrorCodeSuccess 1357 : MOZILLA_PKIX_ERROR_INSUFFICIENT_CERTIFICATE_TRANSPARENCY, 1358 null, 1359 expectCT(expectedCTValue, false) 1360 ); 1361 // Test that session resumption results in the same expected CT status for 1362 // successful connections. 1363 if (expectConnectionSuccess) { 1364 add_connection_test( 1365 host, 1366 PRErrorCodeSuccess, 1367 null, 1368 expectCT(expectedCTValue, true) 1369 ); 1370 } 1371 }