michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: // How to run this file: michael@0: // 1. [obtain firefox source code] michael@0: // 2. [build/obtain firefox binaries] michael@0: // 3. run `[path to]/run-mozilla.sh [path to]/xpcshell \ michael@0: // [path to]/genHPKPStaticpins.js \ michael@0: // [absolute path to]/PreloadedHPKPins.json \ michael@0: // [absolute path to]/default-ee.der \ michael@0: // [absolute path to]/StaticHPKPins.h michael@0: michael@0: if (arguments.length != 3) { michael@0: throw "Usage: genHPKPStaticPins.js " + michael@0: " " + michael@0: " " + michael@0: ""; michael@0: } michael@0: michael@0: const { 'classes': Cc, 'interfaces': Ci, 'utils': Cu, 'results': Cr } = Components; michael@0: michael@0: let { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {}); michael@0: let { FileUtils } = Cu.import("resource://gre/modules/FileUtils.jsm", {}); michael@0: let { Services } = Cu.import("resource://gre/modules/Services.jsm", {}); michael@0: michael@0: let gCertDB = Cc["@mozilla.org/security/x509certdb;1"] michael@0: .getService(Ci.nsIX509CertDB); michael@0: michael@0: const BUILT_IN_NICK_PREFIX = "Builtin Object Token:"; michael@0: const SHA1_PREFIX = "sha1/"; michael@0: const SHA256_PREFIX = "sha256/"; michael@0: const GOOGLE_PIN_PREFIX = "GOOGLE_PIN_"; michael@0: michael@0: // Pins expire in 14 weeks (6 weeks on Beta + 8 weeks on stable) michael@0: const PINNING_MINIMUM_REQUIRED_MAX_AGE = 60 * 60 * 24 * 7 * 14; michael@0: michael@0: const FILE_HEADER = "/* This Source Code Form is subject to the terms of the Mozilla Public\n" + michael@0: " * License, v. 2.0. If a copy of the MPL was not distributed with this\n" + michael@0: " * file, You can obtain one at http://mozilla.org/MPL/2.0/. */\n" + michael@0: "\n" + michael@0: "/*****************************************************************************/\n" + michael@0: "/* This is an automatically generated file. If you're not */\n" + michael@0: "/* PublicKeyPinningService.cpp, you shouldn't be #including it. */\n" + michael@0: "/*****************************************************************************/\n" + michael@0: "#include " + michael@0: "\n"; michael@0: michael@0: const DOMAINHEADER = "/* Domainlist */\n" + michael@0: "struct TransportSecurityPreload {\n" + michael@0: " const char* mHost;\n" + michael@0: " const bool mIncludeSubdomains;\n" + michael@0: " const bool mTestMode;\n" + michael@0: " const bool mIsMoz;\n" + michael@0: " const int32_t mId;\n" + michael@0: " const StaticPinset *pinset;\n" + michael@0: "};\n\n"; michael@0: michael@0: const PINSETDEF = "/* Pinsets are each an ordered list by the actual value of the fingerprint */\n" + michael@0: "struct StaticFingerprints {\n" + michael@0: " const size_t size;\n" + michael@0: " const char* const* data;\n" + michael@0: "};\n\n" + michael@0: "struct StaticPinset {\n" + michael@0: " const StaticFingerprints* sha1;\n" + michael@0: " const StaticFingerprints* sha256;\n" + michael@0: "};\n\n"; michael@0: michael@0: // Command-line arguments michael@0: var gStaticPins = parseJson(arguments[0]); michael@0: var gTestCertFile = arguments[1]; michael@0: michael@0: // Open the output file. michael@0: let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile); michael@0: file.initWithPath(arguments[2]); michael@0: let gFileOutputStream = FileUtils.openSafeFileOutputStream(file); michael@0: michael@0: function writeString(string) { michael@0: gFileOutputStream.write(string, string.length); michael@0: } michael@0: michael@0: function readFileToString(filename) { michael@0: let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile); michael@0: file.initWithPath(filename); michael@0: let stream = Cc["@mozilla.org/network/file-input-stream;1"] michael@0: .createInstance(Ci.nsIFileInputStream); michael@0: stream.init(file, -1, 0, 0); michael@0: let buf = NetUtil.readInputStreamToString(stream, stream.available()); michael@0: return buf; michael@0: } michael@0: michael@0: function stripComments(buf) { michael@0: var lines = buf.split("\n"); michael@0: let entryRegex = /^\s*\/\//; michael@0: let data = ""; michael@0: for (let i = 0; i < lines.length; ++i) { michael@0: let match = entryRegex.exec(lines[i]); michael@0: if (!match) { michael@0: data = data + lines[i]; michael@0: } michael@0: } michael@0: return data; michael@0: } michael@0: michael@0: function isBuiltinToken(tokenName) { michael@0: return tokenName == "Builtin Object Token"; michael@0: } michael@0: michael@0: function isCertBuiltIn(cert) { michael@0: let tokenNames = cert.getAllTokenNames({}); michael@0: if (!tokenNames) { michael@0: return false; michael@0: } michael@0: if (tokenNames.some(isBuiltinToken)) { michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: function download(filename) { michael@0: var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"] michael@0: .createInstance(Ci.nsIXMLHttpRequest); michael@0: req.open("GET", filename, false); // doing the request synchronously michael@0: try { michael@0: req.send(); michael@0: } michael@0: catch (e) { michael@0: throw "ERROR: problem downloading '" + filename + "': " + e; michael@0: } michael@0: michael@0: if (req.status != 200) { michael@0: throw("ERROR: problem downloading '" + filename + "': status " + michael@0: req.status); michael@0: } michael@0: return req.responseText; michael@0: } michael@0: michael@0: function downloadAsJson(filename) { michael@0: // we have to filter out '//' comments michael@0: var result = download(filename).replace(/\/\/[^\n]*\n/g, ""); michael@0: var data = null; michael@0: try { michael@0: data = JSON.parse(result); michael@0: } michael@0: catch (e) { michael@0: throw "ERROR: could not parse data from '" + filename + "': " + e; michael@0: } michael@0: return data; michael@0: } michael@0: michael@0: // Returns a Subject Public Key Digest from the given pem, if it exists. michael@0: function getSKDFromPem(pem) { michael@0: let cert = gCertDB.constructX509FromBase64(pem, pem.length); michael@0: return cert.sha256SubjectPublicKeyInfoDigest; michael@0: } michael@0: michael@0: // Downloads the static certs file and tries to map Google Chrome nicknames michael@0: // to Mozilla nicknames, as well as storing any hashes for pins for which we michael@0: // don't have root PEMs. Each entry consists of a line containing the name of michael@0: // the pin followed either by a hash in the format "sha1/" + base64(hash), or michael@0: // a PEM encoded certificate. For certificates that we have in our database, michael@0: // return a map of Google's nickname to ours. For ones that aren't return a michael@0: // map of Google's nickname to sha1 values. This code is modeled after agl's michael@0: // https://github.com/agl/transport-security-state-generate, which doesn't michael@0: // live in the Chromium repo because go is not an official language in michael@0: // Chromium. michael@0: // For all of the entries in this file: michael@0: // - If the entry has a hash format, find the Mozilla pin name (cert nickname) michael@0: // and stick the hash into certSKDToName michael@0: // - If the entry has a PEM format, parse the PEM, find the Mozilla pin name michael@0: // and stick the hash in certSKDToName michael@0: // We MUST be able to find a corresponding cert nickname for the Chrome names, michael@0: // otherwise we skip all pinsets referring to that Chrome name. michael@0: function downloadAndParseChromeCerts(filename, certSKDToName) { michael@0: // Prefixes that we care about. michael@0: const BEGIN_CERT = "-----BEGIN CERTIFICATE-----"; michael@0: const END_CERT = "-----END CERTIFICATE-----"; michael@0: michael@0: // Parsing states. michael@0: const PRE_NAME = 0; michael@0: const POST_NAME = 1; michael@0: const IN_CERT = 2; michael@0: let state = PRE_NAME; michael@0: michael@0: let lines = download(filename).split("\n"); michael@0: let name = ""; michael@0: let pemCert = ""; michael@0: let hash = ""; michael@0: let chromeNameToHash = {}; michael@0: let chromeNameToMozName = {} michael@0: for (let i = 0; i < lines.length; ++i) { michael@0: let line = lines[i]; michael@0: // Skip comments and newlines. michael@0: if (line.length == 0 || line[0] == '#') { michael@0: continue; michael@0: } michael@0: switch(state) { michael@0: case PRE_NAME: michael@0: chromeName = line; michael@0: state = POST_NAME; michael@0: break; michael@0: case POST_NAME: michael@0: if (line.startsWith(SHA1_PREFIX) || michael@0: line.startsWith(SHA256_PREFIX)) { michael@0: if (line.startsWith(SHA1_PREFIX)) { michael@0: hash = line.substring(SHA1_PREFIX.length); michael@0: } else if (line.startsWith(SHA256_PREFIX)) { michael@0: hash = line.substring(SHA256_PREFIX); michael@0: } michael@0: // Store the entire prefixed hash, so we can disambiguate sha1 from michael@0: // sha256 later. michael@0: chromeNameToHash[chromeName] = line; michael@0: certNameToSKD[chromeName] = hash; michael@0: certSKDToName[hash] = chromeName; michael@0: state = PRE_NAME; michael@0: } else if (line.startsWith(BEGIN_CERT)) { michael@0: state = IN_CERT; michael@0: } else { michael@0: throw "ERROR: couldn't parse Chrome certificate file " + line; michael@0: } michael@0: break; michael@0: case IN_CERT: michael@0: if (line.startsWith(END_CERT)) { michael@0: state = PRE_NAME; michael@0: hash = getSKDFromPem(pemCert); michael@0: pemCert = ""; michael@0: if (hash in certSKDToName) { michael@0: mozName = certSKDToName[hash]; michael@0: } else { michael@0: // Not one of our built-in certs. Prefix the name with michael@0: // GOOGLE_PIN_. michael@0: mozName = GOOGLE_PIN_PREFIX + chromeName; michael@0: dump("Can't find hash in builtin certs for Chrome nickname " + michael@0: chromeName + ", inserting " + mozName + "\n"); michael@0: certSKDToName[hash] = mozName; michael@0: certNameToSKD[mozName] = hash; michael@0: } michael@0: chromeNameToMozName[chromeName] = mozName; michael@0: } else { michael@0: pemCert += line; michael@0: } michael@0: break; michael@0: default: michael@0: throw "ERROR: couldn't parse Chrome certificate file " + line; michael@0: } michael@0: } michael@0: return [ chromeNameToHash, chromeNameToMozName ]; michael@0: } michael@0: michael@0: // We can only import pinsets from chrome if for every name in the pinset: michael@0: // - We have a hash from Chrome's static certificate file michael@0: // - We have a builtin cert michael@0: // If the pinset meets these requirements, we store a map array of pinset michael@0: // objects: michael@0: // { michael@0: // pinset_name : { michael@0: // // Array of names with entries in certNameToSKD michael@0: // sha1_hashes: [], michael@0: // sha256_hashes: [] michael@0: // } michael@0: // } michael@0: // and an array of imported pinset entries: michael@0: // { name: string, include_subdomains: boolean, test_mode: boolean, michael@0: // pins: pinset_name } michael@0: function downloadAndParseChromePins(filename, michael@0: chromeNameToHash, michael@0: chromeNameToMozName, michael@0: certNameToSKD, michael@0: certSKDToName) { michael@0: let chromePreloads = downloadAsJson(filename); michael@0: let chromePins = chromePreloads.pinsets; michael@0: let chromeImportedPinsets = {}; michael@0: let chromeImportedEntries = []; michael@0: michael@0: chromePins.forEach(function(pin) { michael@0: let valid = true; michael@0: let pinset = { name: pin.name, sha1_hashes: [], sha256_hashes: [] }; michael@0: // Translate the Chrome pinset format to ours michael@0: pin.static_spki_hashes.forEach(function(name) { michael@0: if (name in chromeNameToHash) { michael@0: let hash = chromeNameToHash[name]; michael@0: if (hash.startsWith(SHA1_PREFIX)) { michael@0: hash = hash.substring(SHA1_PREFIX.length); michael@0: pinset.sha1_hashes.push(certSKDToName[hash]); michael@0: } else if (hash.startsWith(SHA256_PREFIX)) { michael@0: hash = hash.substring(SHA256_PREFIX.length); michael@0: pinset.sha256_hashes.push(certSKDToName[hash]); michael@0: } else { michael@0: throw("Unsupported hash type: " + chromeNameToHash[name]); michael@0: } michael@0: // We should have already added hashes for all of these when we michael@0: // imported the certificate file. michael@0: if (!certNameToSKD[name]) { michael@0: throw("No hash for name: " + name); michael@0: } michael@0: } else if (name in chromeNameToMozName) { michael@0: pinset.sha256_hashes.push(chromeNameToMozName[name]); michael@0: } else { michael@0: dump("Skipping Chrome pinset " + pinset.name + ", couldn't find " + michael@0: "builtin " + name + " from cert file\n"); michael@0: valid = false; michael@0: } michael@0: }); michael@0: if (valid) { michael@0: chromeImportedPinsets[pinset.name] = pinset; michael@0: } michael@0: }); michael@0: michael@0: // Grab the domain entry lists. Chrome's entry format is similar to michael@0: // ours, except theirs includes a HSTS mode. michael@0: const cData = gStaticPins.chromium_data; michael@0: let entries = chromePreloads.entries; michael@0: entries.forEach(function(entry) { michael@0: let pinsetName = cData.substitute_pinsets[entry.pins]; michael@0: if (!pinsetName) { michael@0: pinsetName = entry.pins; michael@0: } michael@0: let isProductionDomain = michael@0: (cData.production_domains.indexOf(entry.name) != -1); michael@0: let isProductionPinset = michael@0: (cData.production_pinsets.indexOf(pinsetName) != -1); michael@0: let excludeDomain = michael@0: (cData.exclude_domains.indexOf(entry.name) != -1); michael@0: let isTestMode = !isProductionPinset && !isProductionDomain; michael@0: if (entry.pins && !excludeDomain && chromeImportedPinsets[entry.pins]) { michael@0: chromeImportedEntries.push({ michael@0: name: entry.name, michael@0: include_subdomains: entry.include_subdomains, michael@0: test_mode: isTestMode, michael@0: is_moz: false, michael@0: pins: pinsetName }); michael@0: } michael@0: }); michael@0: return [ chromeImportedPinsets, chromeImportedEntries ]; michael@0: } michael@0: michael@0: // Returns a pair of maps [certNameToSKD, certSKDToName] between cert michael@0: // nicknames and digests of the SPKInfo for the mozilla trust store michael@0: function loadNSSCertinfo(derTestFile, extraCertificates) { michael@0: let allCerts = gCertDB.getCerts(); michael@0: let enumerator = allCerts.getEnumerator(); michael@0: let certNameToSKD = {}; michael@0: let certSKDToName = {}; michael@0: while (enumerator.hasMoreElements()) { michael@0: let cert = enumerator.getNext().QueryInterface(Ci.nsIX509Cert); michael@0: if (!isCertBuiltIn(cert)) { michael@0: continue; michael@0: } michael@0: let name = cert.nickname.substr(BUILT_IN_NICK_PREFIX.length); michael@0: let SKD = cert.sha256SubjectPublicKeyInfoDigest; michael@0: certNameToSKD[name] = SKD; michael@0: certSKDToName[SKD] = name; michael@0: } michael@0: michael@0: for (let cert of extraCertificates) { michael@0: let name = cert.commonName; michael@0: let SKD = cert.sha256SubjectPublicKeyInfoDigest; michael@0: certNameToSKD[name] = SKD; michael@0: certSKDToName[SKD] = name; michael@0: } michael@0: michael@0: { michael@0: // A certificate for *.example.com. michael@0: let der = readFileToString(derTestFile); michael@0: let testCert = gCertDB.constructX509(der, der.length); michael@0: // We can't include this cert in the previous loop, because it skips michael@0: // non-builtin certs and the nickname is not built-in to the cert. michael@0: let name = "End Entity Test Cert"; michael@0: let SKD = testCert.sha256SubjectPublicKeyInfoDigest; michael@0: certNameToSKD[name] = SKD; michael@0: certSKDToName[SKD] = name; michael@0: } michael@0: return [certNameToSKD, certSKDToName]; michael@0: } michael@0: michael@0: function parseJson(filename) { michael@0: let json = stripComments(readFileToString(filename)); michael@0: return JSON.parse(json); michael@0: } michael@0: michael@0: function nameToAlias(certName) { michael@0: // change the name to a string valid as a c identifier michael@0: // remove non-ascii characters michael@0: certName = certName.replace( /[^[:ascii:]]/g, "_"); michael@0: // replace non word characters michael@0: certName = certName.replace(/[^A-Za-z0-9]/g ,"_"); michael@0: michael@0: return "k" + certName + "Fingerprint"; michael@0: } michael@0: michael@0: function compareByName (a, b) { michael@0: return a.name.localeCompare(b.name); michael@0: } michael@0: michael@0: function genExpirationTime() { michael@0: let now = new Date(); michael@0: let nowMillis = now.getTime(); michael@0: let expirationMillis = nowMillis + (PINNING_MINIMUM_REQUIRED_MAX_AGE * 1000); michael@0: let expirationMicros = expirationMillis * 1000; michael@0: return "static const PRTime kPreloadPKPinsExpirationTime = INT64_C(" + michael@0: expirationMicros +");\n"; michael@0: } michael@0: michael@0: function writeFullPinset(certNameToSKD, certSKDToName, pinset) { michael@0: // We aren't guaranteed to have sha1 hashes in our own imported pins. michael@0: let prefix = "kPinset_" + pinset.name; michael@0: let sha1Name = "nullptr"; michael@0: let sha256Name = "nullptr"; michael@0: if (pinset.sha1_hashes && pinset.sha1_hashes.length > 0) { michael@0: writeFingerprints(certNameToSKD, certSKDToName, pinset.name, michael@0: pinset.sha1_hashes, "sha1"); michael@0: sha1Name = "&" + prefix + "_sha1"; michael@0: } michael@0: if (pinset.sha256_hashes && pinset.sha256_hashes.length > 0) { michael@0: writeFingerprints(certNameToSKD, certSKDToName, pinset.name, michael@0: pinset.sha256_hashes, "sha256"); michael@0: sha256Name = "&" + prefix + "_sha256"; michael@0: } michael@0: writeString("static const StaticPinset " + prefix + " = {\n" + michael@0: " " + sha1Name + ",\n " + sha256Name + "\n};\n\n"); michael@0: } michael@0: michael@0: function writeFingerprints(certNameToSKD, certSKDToName, name, hashes, type) { michael@0: let varPrefix = "kPinset_" + name + "_" + type; michael@0: writeString("static const char* " + varPrefix + "_Data[] = {\n"); michael@0: let SKDList = []; michael@0: for (let certName of hashes) { michael@0: if (!(certName in certNameToSKD)) { michael@0: throw "Can't find " + certName + " in certNameToSKD"; michael@0: } michael@0: SKDList.push(certNameToSKD[certName]); michael@0: } michael@0: for (let skd of SKDList.sort()) { michael@0: writeString(" " + nameToAlias(certSKDToName[skd]) + ",\n"); michael@0: } michael@0: if (hashes.length == 0) { michael@0: // ANSI C requires that an initialiser list be non-empty. michael@0: writeString(" 0\n"); michael@0: } michael@0: writeString("};\n"); michael@0: writeString("static const StaticFingerprints " + varPrefix + " = {\n " + michael@0: "sizeof(" + varPrefix + "_Data) / sizeof(const char*),\n " + varPrefix + michael@0: "_Data\n};\n\n"); michael@0: } michael@0: michael@0: function writeEntry(entry) { michael@0: let printVal = " { \"" + entry.name + "\",\ "; michael@0: if (entry.include_subdomains) { michael@0: printVal += "true, "; michael@0: } else { michael@0: printVal += "false, "; michael@0: } michael@0: // Default to test mode if not specified. michael@0: let testMode = true; michael@0: if (entry.hasOwnProperty("test_mode")) { michael@0: testMode = entry.test_mode; michael@0: } michael@0: if (testMode) { michael@0: printVal += "true, "; michael@0: } else { michael@0: printVal += "false, "; michael@0: } michael@0: if (entry.is_moz || (entry.pins == "mozilla")) { michael@0: printVal += "true, "; michael@0: } else { michael@0: printVal += "false, "; michael@0: } michael@0: if (entry.id >= 256) { michael@0: throw("Not enough buckets in histogram"); michael@0: } michael@0: if (entry.id >= 0) { michael@0: printVal += entry.id + ", "; michael@0: } else { michael@0: printVal += "-1, "; michael@0: } michael@0: printVal += "&kPinset_" + entry.pins; michael@0: printVal += " },\n"; michael@0: writeString(printVal); michael@0: } michael@0: michael@0: function writeDomainList(chromeImportedEntries) { michael@0: writeString("/* Sort hostnames for binary search. */\n"); michael@0: writeString("static const TransportSecurityPreload " + michael@0: "kPublicKeyPinningPreloadList[] = {\n"); michael@0: let count = 0; michael@0: let sortedEntries = gStaticPins.entries; michael@0: sortedEntries.push.apply(sortedEntries, chromeImportedEntries); michael@0: for (let entry of sortedEntries.sort(compareByName)) { michael@0: count++; michael@0: writeEntry(entry); michael@0: } michael@0: writeString("};\n"); michael@0: michael@0: writeString("\n// Pinning Preload List Length = " + count + ";\n"); michael@0: writeString("\nstatic const int32_t kUnknownId = -1;\n"); michael@0: } michael@0: michael@0: function writeFile(certNameToSKD, certSKDToName, michael@0: chromeImportedPinsets, chromeImportedEntries) { michael@0: // Compute used pins from both Chrome's and our pinsets, so we can output michael@0: // them later. michael@0: usedFingerprints = {}; michael@0: gStaticPins.pinsets.forEach(function(pinset) { michael@0: // We aren't guaranteed to have sha1_hashes in our own JSON. michael@0: if (pinset.sha1_hashes) { michael@0: pinset.sha1_hashes.forEach(function(name) { michael@0: usedFingerprints[name] = true; michael@0: }); michael@0: } michael@0: if (pinset.sha256_hashes) { michael@0: pinset.sha256_hashes.forEach(function(name) { michael@0: usedFingerprints[name] = true; michael@0: }); michael@0: } michael@0: }); michael@0: for (let key in chromeImportedPinsets) { michael@0: let pinset = chromeImportedPinsets[key]; michael@0: pinset.sha1_hashes.forEach(function(name) { michael@0: usedFingerprints[name] = true; michael@0: }); michael@0: pinset.sha256_hashes.forEach(function(name) { michael@0: usedFingerprints[name] = true; michael@0: }); michael@0: } michael@0: michael@0: writeString(FILE_HEADER); michael@0: michael@0: // Write actual fingerprints. michael@0: Object.keys(usedFingerprints).sort().forEach(function(certName) { michael@0: if (certName) { michael@0: writeString("/* " + certName + " */\n"); michael@0: writeString("static const char " + nameToAlias(certName) + "[] =\n"); michael@0: writeString(" \"" + certNameToSKD[certName] + "\";\n"); michael@0: writeString("\n"); michael@0: } michael@0: }); michael@0: michael@0: // Write the pinsets michael@0: writeString(PINSETDEF); michael@0: writeString("/* PreloadedHPKPins.json pinsets */\n"); michael@0: gStaticPins.pinsets.sort(compareByName).forEach(function(pinset) { michael@0: writeFullPinset(certNameToSKD, certSKDToName, pinset); michael@0: }); michael@0: writeString("/* Chrome static pinsets */\n"); michael@0: for (let key in chromeImportedPinsets) { michael@0: writeFullPinset(certNameToSKD, certSKDToName, chromeImportedPinsets[key]); michael@0: } michael@0: michael@0: // Write the domainlist entries. michael@0: writeString(DOMAINHEADER); michael@0: writeDomainList(chromeImportedEntries); michael@0: writeString("\n"); michael@0: writeString(genExpirationTime()); michael@0: } michael@0: michael@0: function loadExtraCertificates(certStringList) { michael@0: let constructedCerts = []; michael@0: for (let certString of certStringList) { michael@0: constructedCerts.push(gCertDB.constructX509FromBase64(certString)); michael@0: } michael@0: return constructedCerts; michael@0: } michael@0: michael@0: let extraCertificates = loadExtraCertificates(gStaticPins.extra_certificates); michael@0: let [ certNameToSKD, certSKDToName ] = loadNSSCertinfo(gTestCertFile, michael@0: extraCertificates); michael@0: let [ chromeNameToHash, chromeNameToMozName ] = downloadAndParseChromeCerts( michael@0: gStaticPins.chromium_data.cert_file_url, certSKDToName); michael@0: let [ chromeImportedPinsets, chromeImportedEntries ] = michael@0: downloadAndParseChromePins(gStaticPins.chromium_data.json_file_url, michael@0: chromeNameToHash, chromeNameToMozName, certNameToSKD, certSKDToName); michael@0: michael@0: writeFile(certNameToSKD, certSKDToName, chromeImportedPinsets, michael@0: chromeImportedEntries); michael@0: michael@0: FileUtils.closeSafeFileOutputStream(gFileOutputStream);