tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }