tor-browser

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

genHPKPStaticPins.js (20991B)


      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 // How to run this file:
      6 // 1. [obtain firefox source code]
      7 // 2. [build/obtain firefox binaries]
      8 // 3. run `[path to]/firefox -xpcshell [path to]/genHPKPStaticpins.js \
      9 //                                     [absolute path to]/PreloadedHPKPins.json \
     10 //                                     [absolute path to]/StaticHPKPins.h
     11 "use strict";
     12 
     13 if (arguments.length != 2) {
     14  throw new Error(
     15    "Usage: genHPKPStaticPins.js " +
     16      "<absolute path to PreloadedHPKPins.json> " +
     17      "<absolute path to StaticHPKPins.h>"
     18  );
     19 }
     20 
     21 Services.prefs.setBoolPref("security.osclientcerts.autoload", false);
     22 Services.prefs.setBoolPref("network.xhr.block_sync_system_requests", false);
     23 
     24 var { NetUtil } = ChromeUtils.importESModule(
     25  "resource://gre/modules/NetUtil.sys.mjs"
     26 );
     27 var { FileUtils } = ChromeUtils.importESModule(
     28  "resource://gre/modules/FileUtils.sys.mjs"
     29 );
     30 
     31 var gCertDB = Cc["@mozilla.org/security/x509certdb;1"].getService(
     32  Ci.nsIX509CertDB
     33 );
     34 
     35 const SHA256_PREFIX = "sha256/";
     36 const GOOGLE_PIN_PREFIX = "GOOGLE_PIN_";
     37 
     38 // Pins expire in 14 weeks (6 weeks on Beta + 8 weeks on stable)
     39 const PINNING_MINIMUM_REQUIRED_MAX_AGE = 60 * 60 * 24 * 7 * 14;
     40 
     41 const FILE_HEADER =
     42  "/* This Source Code Form is subject to the terms of the Mozilla Public\n" +
     43  " * License, v. 2.0. If a copy of the MPL was not distributed with this\n" +
     44  " * file, You can obtain one at http://mozilla.org/MPL/2.0/. */\n" +
     45  "\n" +
     46  "/*****************************************************************************/\n" +
     47  "/* This is an automatically generated file. If you're not                    */\n" +
     48  "/* PublicKeyPinningService.cpp, you shouldn't be #including it.              */\n" +
     49  "/*****************************************************************************/\n" +
     50  "#include <stdint.h>" +
     51  "\n";
     52 
     53 const DOMAINHEADER =
     54  "/* Domainlist */\n" +
     55  "struct TransportSecurityPreload {\n" +
     56  "  // See bug 1338873 about making these fields const.\n" +
     57  "  const char* mHost;\n" +
     58  "  bool mIncludeSubdomains;\n" +
     59  "  bool mTestMode;\n" +
     60  "  bool mIsMoz;\n" +
     61  "  int32_t mId;\n" +
     62  "  const StaticFingerprints* pinset;\n" +
     63  "};\n\n";
     64 
     65 const PINSETDEF =
     66  "/* Pinsets are each an ordered list by the actual value of the fingerprint */\n" +
     67  "struct StaticFingerprints {\n" +
     68  "  // See bug 1338873 about making these fields const.\n" +
     69  "  size_t size;\n" +
     70  "  const char* const* data;\n" +
     71  "};\n\n";
     72 
     73 // Command-line arguments
     74 var gStaticPins = parseJson(arguments[0]);
     75 
     76 // Open the output file.
     77 var file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
     78 file.initWithPath(arguments[1]);
     79 var gFileOutputStream = FileUtils.openSafeFileOutputStream(file);
     80 
     81 function writeString(string) {
     82  gFileOutputStream.write(string, string.length);
     83 }
     84 
     85 function readFileToString(filename) {
     86  let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
     87  file.initWithPath(filename);
     88  let stream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(
     89    Ci.nsIFileInputStream
     90  );
     91  stream.init(file, -1, 0, 0);
     92  let buf = NetUtil.readInputStreamToString(stream, stream.available());
     93  return buf;
     94 }
     95 
     96 function stripComments(buf) {
     97  let lines = buf.split("\n");
     98  let entryRegex = /^\s*\/\//;
     99  let data = "";
    100  for (let i = 0; i < lines.length; ++i) {
    101    let match = entryRegex.exec(lines[i]);
    102    if (!match) {
    103      data = data + lines[i];
    104    }
    105  }
    106  return data;
    107 }
    108 
    109 function download(filename) {
    110  let req = new XMLHttpRequest();
    111  req.open("GET", filename, false); // doing the request synchronously
    112  try {
    113    req.send();
    114  } catch (e) {
    115    throw new Error(`ERROR: problem downloading '${filename}': ${e}`);
    116  }
    117 
    118  if (req.status != 200) {
    119    throw new Error(
    120      "ERROR: problem downloading '" + filename + "': status " + req.status
    121    );
    122  }
    123 
    124  let resultDecoded;
    125  try {
    126    resultDecoded = atob(req.responseText);
    127  } catch (e) {
    128    throw new Error(
    129      "ERROR: could not decode data as base64 from '" + filename + "': " + e
    130    );
    131  }
    132  return resultDecoded;
    133 }
    134 
    135 function downloadAsJson(filename) {
    136  // we have to filter out '//' comments, while not mangling the json
    137  let result = download(filename).replace(/^(\s*)?\/\/[^\n]*\n/gm, "");
    138  let data = null;
    139  try {
    140    data = JSON.parse(result);
    141  } catch (e) {
    142    throw new Error(
    143      "ERROR: could not parse data from '" + filename + "': " + e
    144    );
    145  }
    146  return data;
    147 }
    148 
    149 // Returns a Subject Public Key Digest from the given pem, if it exists.
    150 function getSKDFromPem(pem) {
    151  let cert = gCertDB.constructX509FromBase64(pem, pem.length);
    152  return cert.sha256SubjectPublicKeyInfoDigest;
    153 }
    154 
    155 /**
    156 * Hashes |input| using the SHA-256 algorithm in the following manner:
    157 *   btoa(sha256(atob(input)))
    158 *
    159 * @param {string} input Base64 string to decode and return the hash of.
    160 * @returns {string} Base64 encoded SHA-256 hash.
    161 */
    162 function sha256Base64(input) {
    163  let decodedValue;
    164  try {
    165    decodedValue = atob(input);
    166  } catch (e) {
    167    throw new Error(`ERROR: could not decode as base64: '${input}': ${e}`);
    168  }
    169 
    170  // Convert |decodedValue| to an array so that it can be hashed by the
    171  // nsICryptoHash instance below.
    172  // In most cases across the code base, convertToByteArray() of
    173  // nsIScriptableUnicodeConverter is used to do this, but the method doesn't
    174  // seem to work here.
    175  let data = [];
    176  for (let i = 0; i < decodedValue.length; i++) {
    177    data[i] = decodedValue.charCodeAt(i);
    178  }
    179 
    180  let hasher = Cc["@mozilla.org/security/hash;1"].createInstance(
    181    Ci.nsICryptoHash
    182  );
    183  hasher.init(hasher.SHA256);
    184  hasher.update(data, data.length);
    185 
    186  // true is passed so that the hasher returns a Base64 encoded string.
    187  return hasher.finish(true);
    188 }
    189 
    190 // Downloads the static certs file and tries to map Google Chrome nicknames
    191 // to Mozilla nicknames, as well as storing any hashes for pins for which we
    192 // don't have root PEMs. Each entry consists of a line containing the name of
    193 // the pin followed either by a hash in the format "sha256/" + base64(hash),
    194 // a PEM encoded public key, or a PEM encoded certificate.
    195 // For certificates that we have in our database,
    196 // return a map of Google's nickname to ours. For ones that aren't return a
    197 // map of Google's nickname to SHA-256 values. This code is modeled after agl's
    198 // https://github.com/agl/transport-security-state-generate, which doesn't
    199 // live in the Chromium repo because go is not an official language in
    200 // Chromium.
    201 // For all of the entries in this file:
    202 // - If the entry has a hash format, find the Mozilla pin name (cert nickname)
    203 // and stick the hash into certSKDToName
    204 // - If the entry has a PEM format, parse the PEM, find the Mozilla pin name
    205 // and stick the hash in certSKDToName
    206 // We MUST be able to find a corresponding cert nickname for the Chrome names,
    207 // otherwise we skip all pinsets referring to that Chrome name.
    208 function downloadAndParseChromeCerts(filename, certNameToSKD, certSKDToName) {
    209  // Prefixes that we care about.
    210  const BEGIN_CERT = "-----BEGIN CERTIFICATE-----";
    211  const END_CERT = "-----END CERTIFICATE-----";
    212  const BEGIN_PUB_KEY = "-----BEGIN PUBLIC KEY-----";
    213  const END_PUB_KEY = "-----END PUBLIC KEY-----";
    214 
    215  // Parsing states.
    216  const PRE_NAME = 0;
    217  const POST_NAME = 1;
    218  const IN_CERT = 2;
    219  const IN_PUB_KEY = 3;
    220  let state = PRE_NAME;
    221 
    222  let lines = download(filename).split("\n");
    223  let pemCert = "";
    224  let pemPubKey = "";
    225  let hash = "";
    226  let chromeNameToHash = {};
    227  let chromeNameToMozName = {};
    228  let chromeName;
    229  for (let line of lines) {
    230    // Skip comments and newlines.
    231    if (!line.length || line[0] == "#") {
    232      continue;
    233    }
    234    switch (state) {
    235      case PRE_NAME:
    236        chromeName = line;
    237        state = POST_NAME;
    238        break;
    239      case POST_NAME:
    240        if (line.startsWith(SHA256_PREFIX)) {
    241          hash = line.substring(SHA256_PREFIX.length);
    242          chromeNameToHash[chromeName] = hash;
    243          certNameToSKD[chromeName] = hash;
    244          certSKDToName[hash] = chromeName;
    245          state = PRE_NAME;
    246        } else if (line.startsWith(BEGIN_CERT)) {
    247          state = IN_CERT;
    248        } else if (line.startsWith(BEGIN_PUB_KEY)) {
    249          state = IN_PUB_KEY;
    250        } else if (
    251          chromeName == "PinsListTimestamp" &&
    252          line.match(/^[0-9]+$/)
    253        ) {
    254          // If the name of this entry is "PinsListTimestamp", this line should
    255          // be the pins list timestamp. It should consist solely of digits.
    256          // Ignore it and expect other entries to come.
    257          state = PRE_NAME;
    258        } else {
    259          throw new Error(
    260            "ERROR: couldn't parse Chrome certificate file line: " + line
    261          );
    262        }
    263        break;
    264      case IN_CERT:
    265        if (line.startsWith(END_CERT)) {
    266          state = PRE_NAME;
    267          hash = getSKDFromPem(pemCert);
    268          pemCert = "";
    269          let mozName;
    270          if (hash in certSKDToName) {
    271            mozName = certSKDToName[hash];
    272          } else {
    273            // Not one of our built-in certs. Prefix the name with
    274            // GOOGLE_PIN_.
    275            mozName = GOOGLE_PIN_PREFIX + chromeName;
    276            dump(
    277              "Can't find hash in builtin certs for Chrome nickname " +
    278                chromeName +
    279                ", inserting " +
    280                mozName +
    281                "\n"
    282            );
    283            certSKDToName[hash] = mozName;
    284            certNameToSKD[mozName] = hash;
    285          }
    286          chromeNameToMozName[chromeName] = mozName;
    287        } else {
    288          pemCert += line;
    289        }
    290        break;
    291      case IN_PUB_KEY:
    292        if (line.startsWith(END_PUB_KEY)) {
    293          state = PRE_NAME;
    294          hash = sha256Base64(pemPubKey);
    295          pemPubKey = "";
    296          chromeNameToHash[chromeName] = hash;
    297          certNameToSKD[chromeName] = hash;
    298          certSKDToName[hash] = chromeName;
    299        } else {
    300          pemPubKey += line;
    301        }
    302        break;
    303      default:
    304        throw new Error(
    305          "ERROR: couldn't parse Chrome certificate file " + line
    306        );
    307    }
    308  }
    309  return [chromeNameToHash, chromeNameToMozName];
    310 }
    311 
    312 // We can only import pinsets from chrome if for every name in the pinset:
    313 // - We have a hash from Chrome's static certificate file
    314 // - We have a builtin cert
    315 // If the pinset meets these requirements, we store a map array of pinset
    316 // objects:
    317 // {
    318 //   pinset_name : {
    319 //     // Array of names with entries in certNameToSKD
    320 //     sha256_hashes: []
    321 //   }
    322 // }
    323 // and an array of imported pinset entries:
    324 // { name: string, include_subdomains: boolean, test_mode: boolean,
    325 //   pins: pinset_name }
    326 function downloadAndParseChromePins(
    327  filename,
    328  chromeNameToHash,
    329  chromeNameToMozName,
    330  certNameToSKD,
    331  certSKDToName
    332 ) {
    333  let chromePreloads = downloadAsJson(filename);
    334  let chromePins = chromePreloads.pinsets;
    335  let chromeImportedPinsets = {};
    336  let chromeImportedEntries = [];
    337 
    338  chromePins.forEach(function (pin) {
    339    let valid = true;
    340    let pinset = { name: pin.name, sha256_hashes: [] };
    341    // Translate the Chrome pinset format to ours
    342    pin.static_spki_hashes.forEach(function (name) {
    343      if (name in chromeNameToHash) {
    344        let hash = chromeNameToHash[name];
    345        pinset.sha256_hashes.push(certSKDToName[hash]);
    346 
    347        // We should have already added hashes for all of these when we
    348        // imported the certificate file.
    349        if (!certNameToSKD[name]) {
    350          throw new Error("ERROR: No hash for name: " + name);
    351        }
    352      } else if (name in chromeNameToMozName) {
    353        pinset.sha256_hashes.push(chromeNameToMozName[name]);
    354      } else {
    355        dump(
    356          "Skipping Chrome pinset " +
    357            pinset.name +
    358            ", couldn't find " +
    359            "builtin " +
    360            name +
    361            " from cert file\n"
    362        );
    363        valid = false;
    364      }
    365    });
    366    if (valid) {
    367      chromeImportedPinsets[pinset.name] = pinset;
    368    }
    369  });
    370 
    371  // Grab the domain entry lists. Chrome's entry format is similar to
    372  // ours, except theirs includes a HSTS mode.
    373  const cData = gStaticPins.chromium_data;
    374  let entries = chromePreloads.entries;
    375  entries.forEach(function (entry) {
    376    // HSTS entry only
    377    if (!entry.pins) {
    378      return;
    379    }
    380    let pinsetName = cData.substitute_pinsets[entry.pins];
    381    if (!pinsetName) {
    382      pinsetName = entry.pins;
    383    }
    384 
    385    // We trim the entry name here to avoid breaking hostname comparisons in the
    386    // HPKP implementation.
    387    entry.name = entry.name.trim();
    388 
    389    let isProductionDomain = cData.production_domains.includes(entry.name);
    390    let isProductionPinset = cData.production_pinsets.includes(pinsetName);
    391    let excludeDomain = cData.exclude_domains.includes(entry.name);
    392    let isTestMode = !isProductionPinset && !isProductionDomain;
    393    if (entry.pins && !excludeDomain && chromeImportedPinsets[entry.pins]) {
    394      chromeImportedEntries.push({
    395        name: entry.name,
    396        include_subdomains: entry.include_subdomains,
    397        test_mode: isTestMode,
    398        is_moz: false,
    399        pins: pinsetName,
    400      });
    401    }
    402  });
    403  return [chromeImportedPinsets, chromeImportedEntries];
    404 }
    405 
    406 // Returns a pair of maps [certNameToSKD, certSKDToName] between cert
    407 // nicknames and digests of the SPKInfo for the mozilla trust store
    408 function loadNSSCertinfo(extraCertificates) {
    409  let allCerts = gCertDB.getCerts();
    410  let certNameToSKD = {};
    411  let certSKDToName = {};
    412  for (let cert of allCerts) {
    413    let name = cert.displayName;
    414    let SKD = cert.sha256SubjectPublicKeyInfoDigest;
    415    certNameToSKD[name] = SKD;
    416    certSKDToName[SKD] = name;
    417  }
    418 
    419  for (let cert of extraCertificates) {
    420    let name = cert.commonName;
    421    let SKD = cert.sha256SubjectPublicKeyInfoDigest;
    422    certNameToSKD[name] = SKD;
    423    certSKDToName[SKD] = name;
    424  }
    425 
    426  {
    427    // This is the pinning test certificate. The key hash identifies the
    428    // default RSA key from pykey.
    429    let name = "End Entity Test Cert";
    430    let SKD = "VCIlmPM9NkgFQtrs4Oa5TeFcDu6MWRTKSNdePEhOgD8=";
    431    certNameToSKD[name] = SKD;
    432    certSKDToName[SKD] = name;
    433  }
    434  return [certNameToSKD, certSKDToName];
    435 }
    436 
    437 function parseJson(filename) {
    438  let json = stripComments(readFileToString(filename));
    439  return JSON.parse(json);
    440 }
    441 
    442 function nameToAlias(certName) {
    443  // change the name to a string valid as a c identifier
    444  // remove  non-ascii characters
    445  certName = certName.replace(/[^[:ascii:]]/g, "_");
    446  // replace non word characters
    447  certName = certName.replace(/[^A-Za-z0-9]/g, "_");
    448 
    449  return "k" + certName + "Fingerprint";
    450 }
    451 
    452 function compareByName(a, b) {
    453  return a.name.localeCompare(b.name);
    454 }
    455 
    456 function genExpirationTime() {
    457  let now = new Date();
    458  let nowMillis = now.getTime();
    459  let expirationMillis = nowMillis + PINNING_MINIMUM_REQUIRED_MAX_AGE * 1000;
    460  let expirationMicros = expirationMillis * 1000;
    461  return (
    462    "static const PRTime kPreloadPKPinsExpirationTime = INT64_C(" +
    463    expirationMicros +
    464    ");\n"
    465  );
    466 }
    467 
    468 function writeFullPinset(certNameToSKD, certSKDToName, pinset) {
    469  if (!pinset.sha256_hashes || !pinset.sha256_hashes.length) {
    470    throw new Error(`ERROR: Pinset ${pinset.name} does not contain any hashes`);
    471  }
    472  writeFingerprints(
    473    certNameToSKD,
    474    certSKDToName,
    475    pinset.name,
    476    pinset.sha256_hashes
    477  );
    478 }
    479 
    480 function writeFingerprints(certNameToSKD, certSKDToName, name, hashes) {
    481  let varPrefix = "kPinset_" + name;
    482  writeString("static const char* const " + varPrefix + "_Data[] = {\n");
    483  let SKDList = [];
    484  for (let certName of hashes) {
    485    if (!(certName in certNameToSKD)) {
    486      throw new Error(`ERROR: Can't find '${certName}' in certNameToSKD`);
    487    }
    488    SKDList.push(certNameToSKD[certName]);
    489  }
    490  for (let skd of SKDList.sort()) {
    491    writeString("  " + nameToAlias(certSKDToName[skd]) + ",\n");
    492  }
    493  if (!hashes.length) {
    494    // ANSI C requires that an initialiser list be non-empty.
    495    writeString("  0\n");
    496  }
    497  writeString("};\n");
    498  writeString(
    499    "static const StaticFingerprints " +
    500      varPrefix +
    501      " = {\n  " +
    502      "sizeof(" +
    503      varPrefix +
    504      "_Data) / sizeof(const char*),\n  " +
    505      varPrefix +
    506      "_Data\n};\n\n"
    507  );
    508 }
    509 
    510 function writeEntry(entry) {
    511  let printVal = `  { "${entry.name}", `;
    512  if (entry.include_subdomains) {
    513    printVal += "true, ";
    514  } else {
    515    printVal += "false, ";
    516  }
    517  // Default to test mode if not specified.
    518  let testMode = true;
    519  if (entry.hasOwnProperty("test_mode")) {
    520    testMode = entry.test_mode;
    521  }
    522  if (testMode) {
    523    printVal += "true, ";
    524  } else {
    525    printVal += "false, ";
    526  }
    527  if (
    528    entry.is_moz ||
    529    (entry.pins.includes("mozilla") && entry.pins != "mozilla_test")
    530  ) {
    531    printVal += "true, ";
    532  } else {
    533    printVal += "false, ";
    534  }
    535  if ("id" in entry) {
    536    if (entry.id >= 256) {
    537      throw new Error("ERROR: Not enough buckets in histogram");
    538    }
    539    if (entry.id >= 0) {
    540      printVal += entry.id + ", ";
    541    }
    542  } else {
    543    printVal += "-1, ";
    544  }
    545  printVal += "&kPinset_" + entry.pins;
    546  printVal += " },\n";
    547  writeString(printVal);
    548 }
    549 
    550 function writeDomainList(chromeImportedEntries) {
    551  writeString("/* Sort hostnames for binary search. */\n");
    552  writeString(
    553    "static const TransportSecurityPreload " +
    554      "kPublicKeyPinningPreloadList[] = {\n"
    555  );
    556  let count = 0;
    557  let mozillaDomains = {};
    558  gStaticPins.entries.forEach(function (entry) {
    559    mozillaDomains[entry.name] = true;
    560  });
    561  // For any domain for which we have set pins, exclude them from
    562  // chromeImportedEntries.
    563  for (let i = chromeImportedEntries.length - 1; i >= 0; i--) {
    564    if (mozillaDomains[chromeImportedEntries[i].name]) {
    565      dump(
    566        "Skipping duplicate pinset for domain " +
    567          JSON.stringify(chromeImportedEntries[i], undefined, 2) +
    568          "\n"
    569      );
    570      chromeImportedEntries.splice(i, 1);
    571    }
    572  }
    573  let sortedEntries = gStaticPins.entries;
    574  sortedEntries.push.apply(sortedEntries, chromeImportedEntries);
    575  for (let entry of sortedEntries.sort(compareByName)) {
    576    count++;
    577    writeEntry(entry);
    578  }
    579  writeString("};\n");
    580 
    581  writeString("\n// Pinning Preload List Length = " + count + ";\n");
    582  writeString("\nstatic const int32_t kUnknownId = -1;\n");
    583 }
    584 
    585 function writeFile(
    586  certNameToSKD,
    587  certSKDToName,
    588  chromeImportedPinsets,
    589  chromeImportedEntries
    590 ) {
    591  // Compute used pins from both Chrome's and our pinsets, so we can output
    592  // them later.
    593  let usedFingerprints = {};
    594  let mozillaPins = {};
    595  gStaticPins.pinsets.forEach(function (pinset) {
    596    mozillaPins[pinset.name] = true;
    597    pinset.sha256_hashes.forEach(function (name) {
    598      usedFingerprints[name] = true;
    599    });
    600  });
    601  for (let key in chromeImportedPinsets) {
    602    let pinset = chromeImportedPinsets[key];
    603    pinset.sha256_hashes.forEach(function (name) {
    604      usedFingerprints[name] = true;
    605    });
    606  }
    607 
    608  writeString(FILE_HEADER);
    609 
    610  // Write actual fingerprints.
    611  Object.keys(usedFingerprints)
    612    .sort()
    613    .forEach(function (certName) {
    614      if (certName) {
    615        writeString("/* " + certName + " */\n");
    616        writeString("static const char " + nameToAlias(certName) + "[] =\n");
    617        writeString('  "' + certNameToSKD[certName] + '";\n');
    618        writeString("\n");
    619      }
    620    });
    621 
    622  // Write the pinsets
    623  writeString(PINSETDEF);
    624  writeString("/* PreloadedHPKPins.json pinsets */\n");
    625  gStaticPins.pinsets.sort(compareByName).forEach(function (pinset) {
    626    writeFullPinset(certNameToSKD, certSKDToName, pinset);
    627  });
    628  writeString("/* Chrome static pinsets */\n");
    629  for (let key in chromeImportedPinsets) {
    630    if (mozillaPins[key]) {
    631      dump("Skipping duplicate pinset " + key + "\n");
    632    } else {
    633      dump("Writing pinset " + key + "\n");
    634      writeFullPinset(certNameToSKD, certSKDToName, chromeImportedPinsets[key]);
    635    }
    636  }
    637 
    638  // Write the domainlist entries.
    639  writeString(DOMAINHEADER);
    640  writeDomainList(chromeImportedEntries);
    641  writeString("\n");
    642  writeString(genExpirationTime());
    643 }
    644 
    645 function loadExtraCertificates(certStringList) {
    646  let constructedCerts = [];
    647  for (let certString of certStringList) {
    648    constructedCerts.push(gCertDB.constructX509FromBase64(certString));
    649  }
    650  return constructedCerts;
    651 }
    652 
    653 var extraCertificates = loadExtraCertificates(gStaticPins.extra_certificates);
    654 var [certNameToSKD, certSKDToName] = loadNSSCertinfo(extraCertificates);
    655 var [chromeNameToHash, chromeNameToMozName] = downloadAndParseChromeCerts(
    656  gStaticPins.chromium_data.cert_file_url,
    657  certNameToSKD,
    658  certSKDToName
    659 );
    660 var [chromeImportedPinsets, chromeImportedEntries] = downloadAndParseChromePins(
    661  gStaticPins.chromium_data.json_file_url,
    662  chromeNameToHash,
    663  chromeNameToMozName,
    664  certNameToSKD,
    665  certSKDToName
    666 );
    667 
    668 writeFile(
    669  certNameToSKD,
    670  certSKDToName,
    671  chromeImportedPinsets,
    672  chromeImportedEntries
    673 );
    674 
    675 FileUtils.closeSafeFileOutputStream(gFileOutputStream);