security/manager/tools/genHPKPStaticPins.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/security/manager/tools/genHPKPStaticPins.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,576 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +// How to run this file:
     1.9 +// 1. [obtain firefox source code]
    1.10 +// 2. [build/obtain firefox binaries]
    1.11 +// 3. run `[path to]/run-mozilla.sh [path to]/xpcshell \
    1.12 +//                                  [path to]/genHPKPStaticpins.js \
    1.13 +//                                  [absolute path to]/PreloadedHPKPins.json \
    1.14 +//                                  [absolute path to]/default-ee.der \
    1.15 +//                                  [absolute path to]/StaticHPKPins.h
    1.16 +
    1.17 +if (arguments.length != 3) {
    1.18 +  throw "Usage: genHPKPStaticPins.js " +
    1.19 +        "<absolute path to PreloadedHPKPins.json> " +
    1.20 +        "<absolute path to default-ee.der> " +
    1.21 +        "<absolute path to StaticHPKPins.h>";
    1.22 +}
    1.23 +
    1.24 +const { 'classes': Cc, 'interfaces': Ci, 'utils': Cu, 'results': Cr } = Components;
    1.25 +
    1.26 +let { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {});
    1.27 +let { FileUtils } = Cu.import("resource://gre/modules/FileUtils.jsm", {});
    1.28 +let { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
    1.29 +
    1.30 +let gCertDB = Cc["@mozilla.org/security/x509certdb;1"]
    1.31 +                .getService(Ci.nsIX509CertDB);
    1.32 +
    1.33 +const BUILT_IN_NICK_PREFIX = "Builtin Object Token:";
    1.34 +const SHA1_PREFIX = "sha1/";
    1.35 +const SHA256_PREFIX = "sha256/";
    1.36 +const GOOGLE_PIN_PREFIX = "GOOGLE_PIN_";
    1.37 +
    1.38 +// Pins expire in 14 weeks (6 weeks on Beta + 8 weeks on stable)
    1.39 +const PINNING_MINIMUM_REQUIRED_MAX_AGE = 60 * 60 * 24 * 7 * 14;
    1.40 +
    1.41 +const FILE_HEADER = "/* This Source Code Form is subject to the terms of the Mozilla Public\n" +
    1.42 +" * License, v. 2.0. If a copy of the MPL was not distributed with this\n" +
    1.43 +" * file, You can obtain one at http://mozilla.org/MPL/2.0/. */\n" +
    1.44 +"\n" +
    1.45 +"/*****************************************************************************/\n" +
    1.46 +"/* This is an automatically generated file. If you're not                    */\n" +
    1.47 +"/* PublicKeyPinningService.cpp, you shouldn't be #including it.              */\n" +
    1.48 +"/*****************************************************************************/\n" +
    1.49 +"#include <stdint.h>" +
    1.50 +"\n";
    1.51 +
    1.52 +const DOMAINHEADER = "/* Domainlist */\n" +
    1.53 +  "struct TransportSecurityPreload {\n" +
    1.54 +  "  const char* mHost;\n" +
    1.55 +  "  const bool mIncludeSubdomains;\n" +
    1.56 +  "  const bool mTestMode;\n" +
    1.57 +  "  const bool mIsMoz;\n" +
    1.58 +  "  const int32_t mId;\n" +
    1.59 +  "  const StaticPinset *pinset;\n" +
    1.60 +  "};\n\n";
    1.61 +
    1.62 +const PINSETDEF = "/* Pinsets are each an ordered list by the actual value of the fingerprint */\n" +
    1.63 +  "struct StaticFingerprints {\n" +
    1.64 +  "  const size_t size;\n" +
    1.65 +  "  const char* const* data;\n" +
    1.66 +  "};\n\n" +
    1.67 +  "struct StaticPinset {\n" +
    1.68 +  "  const StaticFingerprints* sha1;\n" +
    1.69 +  "  const StaticFingerprints* sha256;\n" +
    1.70 +  "};\n\n";
    1.71 +
    1.72 +// Command-line arguments
    1.73 +var gStaticPins = parseJson(arguments[0]);
    1.74 +var gTestCertFile = arguments[1];
    1.75 +
    1.76 +// Open the output file.
    1.77 +let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
    1.78 +file.initWithPath(arguments[2]);
    1.79 +let gFileOutputStream = FileUtils.openSafeFileOutputStream(file);
    1.80 +
    1.81 +function writeString(string) {
    1.82 +  gFileOutputStream.write(string, string.length);
    1.83 +}
    1.84 +
    1.85 +function readFileToString(filename) {
    1.86 +  let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
    1.87 +  file.initWithPath(filename);
    1.88 +  let stream = Cc["@mozilla.org/network/file-input-stream;1"]
    1.89 +                 .createInstance(Ci.nsIFileInputStream);
    1.90 +  stream.init(file, -1, 0, 0);
    1.91 +  let buf = NetUtil.readInputStreamToString(stream, stream.available());
    1.92 +  return buf;
    1.93 +}
    1.94 +
    1.95 +function stripComments(buf) {
    1.96 +  var lines = buf.split("\n");
    1.97 +  let entryRegex = /^\s*\/\//;
    1.98 +  let data = "";
    1.99 +  for (let i = 0; i < lines.length; ++i) {
   1.100 +    let match = entryRegex.exec(lines[i]);
   1.101 +    if (!match) {
   1.102 +      data = data + lines[i];
   1.103 +    }
   1.104 +  }
   1.105 +  return data;
   1.106 +}
   1.107 +
   1.108 +function isBuiltinToken(tokenName) {
   1.109 +  return tokenName == "Builtin Object Token";
   1.110 +}
   1.111 +
   1.112 +function isCertBuiltIn(cert) {
   1.113 +  let tokenNames = cert.getAllTokenNames({});
   1.114 +  if (!tokenNames) {
   1.115 +    return false;
   1.116 +  }
   1.117 +  if (tokenNames.some(isBuiltinToken)) {
   1.118 +    return true;
   1.119 +  }
   1.120 +  return false;
   1.121 +}
   1.122 +
   1.123 +function download(filename) {
   1.124 +  var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
   1.125 +              .createInstance(Ci.nsIXMLHttpRequest);
   1.126 +  req.open("GET", filename, false); // doing the request synchronously
   1.127 +  try {
   1.128 +    req.send();
   1.129 +  }
   1.130 +  catch (e) {
   1.131 +    throw "ERROR: problem downloading '" + filename + "': " + e;
   1.132 +  }
   1.133 +
   1.134 +  if (req.status != 200) {
   1.135 +    throw("ERROR: problem downloading '" + filename + "': status " +
   1.136 +          req.status);
   1.137 +  }
   1.138 +  return req.responseText;
   1.139 +}
   1.140 +
   1.141 +function downloadAsJson(filename) {
   1.142 +  // we have to filter out '//' comments
   1.143 +  var result = download(filename).replace(/\/\/[^\n]*\n/g, "");
   1.144 +  var data = null;
   1.145 +  try {
   1.146 +    data = JSON.parse(result);
   1.147 +  }
   1.148 +  catch (e) {
   1.149 +    throw "ERROR: could not parse data from '" + filename + "': " + e;
   1.150 +  }
   1.151 +  return data;
   1.152 +}
   1.153 +
   1.154 +// Returns a Subject Public Key Digest from the given pem, if it exists.
   1.155 +function getSKDFromPem(pem) {
   1.156 +  let cert = gCertDB.constructX509FromBase64(pem, pem.length);
   1.157 +  return cert.sha256SubjectPublicKeyInfoDigest;
   1.158 +}
   1.159 +
   1.160 +// Downloads the static certs file and tries to map Google Chrome nicknames
   1.161 +// to Mozilla nicknames, as well as storing any hashes for pins for which we
   1.162 +// don't have root PEMs. Each entry consists of a line containing the name of
   1.163 +// the pin followed either by a hash in the format "sha1/" + base64(hash), or
   1.164 +// a PEM encoded certificate. For certificates that we have in our database,
   1.165 +// return a map of Google's nickname to ours. For ones that aren't return a
   1.166 +// map of Google's nickname to sha1 values. This code is modeled after agl's
   1.167 +// https://github.com/agl/transport-security-state-generate, which doesn't
   1.168 +// live in the Chromium repo because go is not an official language in
   1.169 +// Chromium.
   1.170 +// For all of the entries in this file:
   1.171 +// - If the entry has a hash format, find the Mozilla pin name (cert nickname)
   1.172 +// and stick the hash into certSKDToName
   1.173 +// - If the entry has a PEM format, parse the PEM, find the Mozilla pin name
   1.174 +// and stick the hash in certSKDToName
   1.175 +// We MUST be able to find a corresponding cert nickname for the Chrome names,
   1.176 +// otherwise we skip all pinsets referring to that Chrome name.
   1.177 +function downloadAndParseChromeCerts(filename, certSKDToName) {
   1.178 +  // Prefixes that we care about.
   1.179 +  const BEGIN_CERT = "-----BEGIN CERTIFICATE-----";
   1.180 +  const END_CERT = "-----END CERTIFICATE-----";
   1.181 +
   1.182 +  // Parsing states.
   1.183 +  const PRE_NAME = 0;
   1.184 +  const POST_NAME = 1;
   1.185 +  const IN_CERT = 2;
   1.186 +  let state = PRE_NAME;
   1.187 +
   1.188 +  let lines = download(filename).split("\n");
   1.189 +  let name = "";
   1.190 +  let pemCert = "";
   1.191 +  let hash = "";
   1.192 +  let chromeNameToHash = {};
   1.193 +  let chromeNameToMozName = {}
   1.194 +  for (let i = 0; i < lines.length; ++i) {
   1.195 +    let line = lines[i];
   1.196 +    // Skip comments and newlines.
   1.197 +    if (line.length == 0 || line[0] == '#') {
   1.198 +      continue;
   1.199 +    }
   1.200 +    switch(state) {
   1.201 +      case PRE_NAME:
   1.202 +        chromeName = line;
   1.203 +        state = POST_NAME;
   1.204 +        break;
   1.205 +      case POST_NAME:
   1.206 +        if (line.startsWith(SHA1_PREFIX) ||
   1.207 +            line.startsWith(SHA256_PREFIX)) {
   1.208 +          if (line.startsWith(SHA1_PREFIX)) {
   1.209 +            hash = line.substring(SHA1_PREFIX.length);
   1.210 +          } else if (line.startsWith(SHA256_PREFIX)) {
   1.211 +            hash = line.substring(SHA256_PREFIX);
   1.212 +          }
   1.213 +          // Store the entire prefixed hash, so we can disambiguate sha1 from
   1.214 +          // sha256 later.
   1.215 +          chromeNameToHash[chromeName] = line;
   1.216 +          certNameToSKD[chromeName] = hash;
   1.217 +          certSKDToName[hash] = chromeName;
   1.218 +          state = PRE_NAME;
   1.219 +        } else if (line.startsWith(BEGIN_CERT)) {
   1.220 +          state = IN_CERT;
   1.221 +        } else {
   1.222 +          throw "ERROR: couldn't parse Chrome certificate file " + line;
   1.223 +        }
   1.224 +        break;
   1.225 +      case IN_CERT:
   1.226 +        if (line.startsWith(END_CERT)) {
   1.227 +          state = PRE_NAME;
   1.228 +          hash = getSKDFromPem(pemCert);
   1.229 +          pemCert = "";
   1.230 +          if (hash in certSKDToName) {
   1.231 +            mozName = certSKDToName[hash];
   1.232 +          } else {
   1.233 +            // Not one of our built-in certs. Prefix the name with
   1.234 +            // GOOGLE_PIN_.
   1.235 +            mozName = GOOGLE_PIN_PREFIX + chromeName;
   1.236 +            dump("Can't find hash in builtin certs for Chrome nickname " +
   1.237 +                 chromeName + ", inserting " + mozName + "\n");
   1.238 +            certSKDToName[hash] = mozName;
   1.239 +            certNameToSKD[mozName] = hash;
   1.240 +          }
   1.241 +          chromeNameToMozName[chromeName] = mozName;
   1.242 +        } else {
   1.243 +          pemCert += line;
   1.244 +        }
   1.245 +        break;
   1.246 +      default:
   1.247 +        throw "ERROR: couldn't parse Chrome certificate file " + line;
   1.248 +    }
   1.249 +  }
   1.250 +  return [ chromeNameToHash, chromeNameToMozName ];
   1.251 +}
   1.252 +
   1.253 +// We can only import pinsets from chrome if for every name in the pinset:
   1.254 +// - We have a hash from Chrome's static certificate file
   1.255 +// - We have a builtin cert
   1.256 +// If the pinset meets these requirements, we store a map array of pinset
   1.257 +// objects:
   1.258 +// {
   1.259 +//   pinset_name : {
   1.260 +//     // Array of names with entries in certNameToSKD
   1.261 +//     sha1_hashes: [],
   1.262 +//     sha256_hashes: []
   1.263 +//   }
   1.264 +// }
   1.265 +// and an array of imported pinset entries:
   1.266 +// { name: string, include_subdomains: boolean, test_mode: boolean,
   1.267 +//   pins: pinset_name }
   1.268 +function downloadAndParseChromePins(filename,
   1.269 +                                    chromeNameToHash,
   1.270 +                                    chromeNameToMozName,
   1.271 +                                    certNameToSKD,
   1.272 +                                    certSKDToName) {
   1.273 +  let chromePreloads = downloadAsJson(filename);
   1.274 +  let chromePins = chromePreloads.pinsets;
   1.275 +  let chromeImportedPinsets = {};
   1.276 +  let chromeImportedEntries = [];
   1.277 +
   1.278 +  chromePins.forEach(function(pin) {
   1.279 +    let valid = true;
   1.280 +    let pinset = { name: pin.name, sha1_hashes: [], sha256_hashes: [] };
   1.281 +    // Translate the Chrome pinset format to ours
   1.282 +    pin.static_spki_hashes.forEach(function(name) {
   1.283 +      if (name in chromeNameToHash) {
   1.284 +        let hash = chromeNameToHash[name];
   1.285 +        if (hash.startsWith(SHA1_PREFIX)) {
   1.286 +          hash = hash.substring(SHA1_PREFIX.length);
   1.287 +          pinset.sha1_hashes.push(certSKDToName[hash]);
   1.288 +        } else if (hash.startsWith(SHA256_PREFIX)) {
   1.289 +          hash = hash.substring(SHA256_PREFIX.length);
   1.290 +          pinset.sha256_hashes.push(certSKDToName[hash]);
   1.291 +        } else {
   1.292 +          throw("Unsupported hash type: " + chromeNameToHash[name]);
   1.293 +        }
   1.294 +        // We should have already added hashes for all of these when we
   1.295 +        // imported the certificate file.
   1.296 +        if (!certNameToSKD[name]) {
   1.297 +          throw("No hash for name: " + name);
   1.298 +        }
   1.299 +      } else if (name in chromeNameToMozName) {
   1.300 +        pinset.sha256_hashes.push(chromeNameToMozName[name]);
   1.301 +      } else {
   1.302 +        dump("Skipping Chrome pinset " + pinset.name + ", couldn't find " +
   1.303 +             "builtin " + name + " from cert file\n");
   1.304 +        valid = false;
   1.305 +      }
   1.306 +    });
   1.307 +    if (valid) {
   1.308 +      chromeImportedPinsets[pinset.name] = pinset;
   1.309 +    }
   1.310 +  });
   1.311 +
   1.312 +  // Grab the domain entry lists. Chrome's entry format is similar to
   1.313 +  // ours, except theirs includes a HSTS mode.
   1.314 +  const cData = gStaticPins.chromium_data;
   1.315 +  let entries = chromePreloads.entries;
   1.316 +  entries.forEach(function(entry) {
   1.317 +    let pinsetName = cData.substitute_pinsets[entry.pins];
   1.318 +    if (!pinsetName) {
   1.319 +      pinsetName = entry.pins;
   1.320 +    }
   1.321 +    let isProductionDomain =
   1.322 +      (cData.production_domains.indexOf(entry.name) != -1);
   1.323 +    let isProductionPinset =
   1.324 +      (cData.production_pinsets.indexOf(pinsetName) != -1);
   1.325 +    let excludeDomain =
   1.326 +      (cData.exclude_domains.indexOf(entry.name) != -1);
   1.327 +    let isTestMode = !isProductionPinset && !isProductionDomain;
   1.328 +    if (entry.pins && !excludeDomain && chromeImportedPinsets[entry.pins]) {
   1.329 +      chromeImportedEntries.push({
   1.330 +        name: entry.name,
   1.331 +        include_subdomains: entry.include_subdomains,
   1.332 +        test_mode: isTestMode,
   1.333 +        is_moz: false,
   1.334 +        pins: pinsetName });
   1.335 +    }
   1.336 +  });
   1.337 +  return [ chromeImportedPinsets, chromeImportedEntries ];
   1.338 +}
   1.339 +
   1.340 +// Returns a pair of maps [certNameToSKD, certSKDToName] between cert
   1.341 +// nicknames and digests of the SPKInfo for the mozilla trust store
   1.342 +function loadNSSCertinfo(derTestFile, extraCertificates) {
   1.343 +  let allCerts = gCertDB.getCerts();
   1.344 +  let enumerator = allCerts.getEnumerator();
   1.345 +  let certNameToSKD = {};
   1.346 +  let certSKDToName = {};
   1.347 +  while (enumerator.hasMoreElements()) {
   1.348 +    let cert = enumerator.getNext().QueryInterface(Ci.nsIX509Cert);
   1.349 +    if (!isCertBuiltIn(cert)) {
   1.350 +      continue;
   1.351 +    }
   1.352 +    let name = cert.nickname.substr(BUILT_IN_NICK_PREFIX.length);
   1.353 +    let SKD = cert.sha256SubjectPublicKeyInfoDigest;
   1.354 +    certNameToSKD[name] = SKD;
   1.355 +    certSKDToName[SKD] = name;
   1.356 +  }
   1.357 +
   1.358 +  for (let cert of extraCertificates) {
   1.359 +    let name = cert.commonName;
   1.360 +    let SKD = cert.sha256SubjectPublicKeyInfoDigest;
   1.361 +    certNameToSKD[name] = SKD;
   1.362 +    certSKDToName[SKD] = name;
   1.363 +  }
   1.364 +
   1.365 +  {
   1.366 +    // A certificate for *.example.com.
   1.367 +    let der = readFileToString(derTestFile);
   1.368 +    let testCert = gCertDB.constructX509(der, der.length);
   1.369 +    // We can't include this cert in the previous loop, because it skips
   1.370 +    // non-builtin certs and the nickname is not built-in to the cert.
   1.371 +    let name = "End Entity Test Cert";
   1.372 +    let SKD  = testCert.sha256SubjectPublicKeyInfoDigest;
   1.373 +    certNameToSKD[name] = SKD;
   1.374 +    certSKDToName[SKD] = name;
   1.375 +  }
   1.376 +  return [certNameToSKD, certSKDToName];
   1.377 +}
   1.378 +
   1.379 +function parseJson(filename) {
   1.380 +  let json = stripComments(readFileToString(filename));
   1.381 +  return JSON.parse(json);
   1.382 +}
   1.383 +
   1.384 +function nameToAlias(certName) {
   1.385 +  // change the name to a string valid as a c identifier
   1.386 +  // remove  non-ascii characters
   1.387 +  certName = certName.replace( /[^[:ascii:]]/g, "_");
   1.388 +  // replace non word characters
   1.389 +  certName = certName.replace(/[^A-Za-z0-9]/g ,"_");
   1.390 +
   1.391 +  return "k" + certName + "Fingerprint";
   1.392 +}
   1.393 +
   1.394 +function compareByName (a, b) {
   1.395 +  return a.name.localeCompare(b.name);
   1.396 +}
   1.397 +
   1.398 +function genExpirationTime() {
   1.399 +  let now = new Date();
   1.400 +  let nowMillis = now.getTime();
   1.401 +  let expirationMillis = nowMillis + (PINNING_MINIMUM_REQUIRED_MAX_AGE * 1000);
   1.402 +  let expirationMicros = expirationMillis * 1000;
   1.403 +  return "static const PRTime kPreloadPKPinsExpirationTime = INT64_C(" +
   1.404 +         expirationMicros +");\n";
   1.405 +}
   1.406 +
   1.407 +function writeFullPinset(certNameToSKD, certSKDToName, pinset) {
   1.408 +  // We aren't guaranteed to have sha1 hashes in our own imported pins.
   1.409 +  let prefix = "kPinset_" + pinset.name;
   1.410 +  let sha1Name = "nullptr";
   1.411 +  let sha256Name = "nullptr";
   1.412 +  if (pinset.sha1_hashes && pinset.sha1_hashes.length > 0) {
   1.413 +    writeFingerprints(certNameToSKD, certSKDToName, pinset.name,
   1.414 +                      pinset.sha1_hashes, "sha1");
   1.415 +    sha1Name = "&" + prefix + "_sha1";
   1.416 +  }
   1.417 +  if (pinset.sha256_hashes && pinset.sha256_hashes.length > 0) {
   1.418 +    writeFingerprints(certNameToSKD, certSKDToName, pinset.name,
   1.419 +                      pinset.sha256_hashes, "sha256");
   1.420 +    sha256Name = "&" + prefix + "_sha256";
   1.421 +  }
   1.422 +  writeString("static const StaticPinset " + prefix + " = {\n" +
   1.423 +          "  " + sha1Name + ",\n  " + sha256Name + "\n};\n\n");
   1.424 +}
   1.425 +
   1.426 +function writeFingerprints(certNameToSKD, certSKDToName, name, hashes, type) {
   1.427 +  let varPrefix = "kPinset_" + name + "_" + type;
   1.428 +  writeString("static const char* " + varPrefix + "_Data[] = {\n");
   1.429 +  let SKDList = [];
   1.430 +  for (let certName of hashes) {
   1.431 +    if (!(certName in certNameToSKD)) {
   1.432 +      throw "Can't find " + certName + " in certNameToSKD";
   1.433 +    }
   1.434 +    SKDList.push(certNameToSKD[certName]);
   1.435 +  }
   1.436 +  for (let skd of SKDList.sort()) {
   1.437 +    writeString("  " + nameToAlias(certSKDToName[skd]) + ",\n");
   1.438 +  }
   1.439 +  if (hashes.length == 0) {
   1.440 +    // ANSI C requires that an initialiser list be non-empty.
   1.441 +    writeString("  0\n");
   1.442 +  }
   1.443 +  writeString("};\n");
   1.444 +  writeString("static const StaticFingerprints " + varPrefix + " = {\n  " +
   1.445 +    "sizeof(" + varPrefix + "_Data) / sizeof(const char*),\n  " + varPrefix +
   1.446 +    "_Data\n};\n\n");
   1.447 +}
   1.448 +
   1.449 +function writeEntry(entry) {
   1.450 +  let printVal = "  { \"" + entry.name + "\",\ ";
   1.451 +  if (entry.include_subdomains) {
   1.452 +    printVal += "true, ";
   1.453 +  } else {
   1.454 +    printVal += "false, ";
   1.455 +  }
   1.456 +  // Default to test mode if not specified.
   1.457 +  let testMode = true;
   1.458 +  if (entry.hasOwnProperty("test_mode")) {
   1.459 +    testMode = entry.test_mode;
   1.460 +  }
   1.461 +  if (testMode) {
   1.462 +    printVal += "true, ";
   1.463 +  } else {
   1.464 +    printVal += "false, ";
   1.465 +  }
   1.466 +  if (entry.is_moz || (entry.pins == "mozilla")) {
   1.467 +    printVal += "true, ";
   1.468 +  } else {
   1.469 +    printVal += "false, ";
   1.470 +  }
   1.471 +  if (entry.id >= 256) {
   1.472 +    throw("Not enough buckets in histogram");
   1.473 +  }
   1.474 +  if (entry.id >= 0) {
   1.475 +    printVal += entry.id + ", ";
   1.476 +  } else {
   1.477 +    printVal += "-1, ";
   1.478 +  }
   1.479 +  printVal += "&kPinset_" + entry.pins;
   1.480 +  printVal += " },\n";
   1.481 +  writeString(printVal);
   1.482 +}
   1.483 +
   1.484 +function writeDomainList(chromeImportedEntries) {
   1.485 +  writeString("/* Sort hostnames for binary search. */\n");
   1.486 +  writeString("static const TransportSecurityPreload " +
   1.487 +          "kPublicKeyPinningPreloadList[] = {\n");
   1.488 +  let count = 0;
   1.489 +  let sortedEntries = gStaticPins.entries;
   1.490 +  sortedEntries.push.apply(sortedEntries, chromeImportedEntries);
   1.491 +  for (let entry of sortedEntries.sort(compareByName)) {
   1.492 +    count++;
   1.493 +    writeEntry(entry);
   1.494 +  }
   1.495 +  writeString("};\n");
   1.496 +
   1.497 +  writeString("\n// Pinning Preload List Length = " + count + ";\n");
   1.498 +  writeString("\nstatic const int32_t kUnknownId = -1;\n");
   1.499 +}
   1.500 +
   1.501 +function writeFile(certNameToSKD, certSKDToName,
   1.502 +                   chromeImportedPinsets, chromeImportedEntries) {
   1.503 +  // Compute used pins from both Chrome's and our pinsets, so we can output
   1.504 +  // them later.
   1.505 +  usedFingerprints = {};
   1.506 +  gStaticPins.pinsets.forEach(function(pinset) {
   1.507 +    // We aren't guaranteed to have sha1_hashes in our own JSON.
   1.508 +    if (pinset.sha1_hashes) {
   1.509 +      pinset.sha1_hashes.forEach(function(name) {
   1.510 +        usedFingerprints[name] = true;
   1.511 +      });
   1.512 +    }
   1.513 +    if (pinset.sha256_hashes) {
   1.514 +      pinset.sha256_hashes.forEach(function(name) {
   1.515 +        usedFingerprints[name] = true;
   1.516 +      });
   1.517 +    }
   1.518 +  });
   1.519 +  for (let key in chromeImportedPinsets) {
   1.520 +    let pinset = chromeImportedPinsets[key];
   1.521 +    pinset.sha1_hashes.forEach(function(name) {
   1.522 +      usedFingerprints[name] = true;
   1.523 +    });
   1.524 +    pinset.sha256_hashes.forEach(function(name) {
   1.525 +      usedFingerprints[name] = true;
   1.526 +    });
   1.527 +  }
   1.528 +
   1.529 +  writeString(FILE_HEADER);
   1.530 +
   1.531 +  // Write actual fingerprints.
   1.532 +  Object.keys(usedFingerprints).sort().forEach(function(certName) {
   1.533 +    if (certName) {
   1.534 +      writeString("/* " + certName + " */\n");
   1.535 +      writeString("static const char " + nameToAlias(certName) + "[] =\n");
   1.536 +      writeString("  \"" + certNameToSKD[certName] + "\";\n");
   1.537 +      writeString("\n");
   1.538 +    }
   1.539 +  });
   1.540 +
   1.541 +  // Write the pinsets
   1.542 +  writeString(PINSETDEF);
   1.543 +  writeString("/* PreloadedHPKPins.json pinsets */\n");
   1.544 +  gStaticPins.pinsets.sort(compareByName).forEach(function(pinset) {
   1.545 +    writeFullPinset(certNameToSKD, certSKDToName, pinset);
   1.546 +  });
   1.547 +  writeString("/* Chrome static pinsets */\n");
   1.548 +  for (let key in chromeImportedPinsets) {
   1.549 +    writeFullPinset(certNameToSKD, certSKDToName, chromeImportedPinsets[key]);
   1.550 +  }
   1.551 +
   1.552 +  // Write the domainlist entries.
   1.553 +  writeString(DOMAINHEADER);
   1.554 +  writeDomainList(chromeImportedEntries);
   1.555 +  writeString("\n");
   1.556 +  writeString(genExpirationTime());
   1.557 +}
   1.558 +
   1.559 +function loadExtraCertificates(certStringList) {
   1.560 +  let constructedCerts = [];
   1.561 +  for (let certString of certStringList) {
   1.562 +    constructedCerts.push(gCertDB.constructX509FromBase64(certString));
   1.563 +  }
   1.564 +  return constructedCerts;
   1.565 +}
   1.566 +
   1.567 +let extraCertificates = loadExtraCertificates(gStaticPins.extra_certificates);
   1.568 +let [ certNameToSKD, certSKDToName ] = loadNSSCertinfo(gTestCertFile,
   1.569 +                                                       extraCertificates);
   1.570 +let [ chromeNameToHash, chromeNameToMozName ] = downloadAndParseChromeCerts(
   1.571 +  gStaticPins.chromium_data.cert_file_url, certSKDToName);
   1.572 +let [ chromeImportedPinsets, chromeImportedEntries ] =
   1.573 +  downloadAndParseChromePins(gStaticPins.chromium_data.json_file_url,
   1.574 +    chromeNameToHash, chromeNameToMozName, certNameToSKD, certSKDToName);
   1.575 +
   1.576 +writeFile(certNameToSKD, certSKDToName, chromeImportedPinsets,
   1.577 +          chromeImportedEntries);
   1.578 +
   1.579 +FileUtils.closeSafeFileOutputStream(gFileOutputStream);

mercurial