Commit 1f486ad8 authored by Cykesiopka's avatar Cykesiopka Committed by Georg Koppen
Browse files

Bug 1229284 - Remove support for SHA-1 hashes in genHPKPStaticPins.js. r=keeler

parent 8dce2ff4
Loading
Loading
Loading
Loading
+32 −72
Original line number Diff line number Diff line
@@ -10,6 +10,7 @@
//                                  [absolute path to]/PreloadedHPKPins.json \
//                                  [an unused argument - see bug 1205406] \
//                                  [absolute path to]/StaticHPKPins.h
"use strict";

if (arguments.length != 3) {
  throw "Usage: genHPKPStaticPins.js " +
@@ -28,7 +29,6 @@ var gCertDB = Cc["@mozilla.org/security/x509certdb;1"]
                .getService(Ci.nsIX509CertDB);

const BUILT_IN_NICK_PREFIX = "Builtin Object Token:";
const SHA1_PREFIX = "sha1/";
const SHA256_PREFIX = "sha256/";
const GOOGLE_PIN_PREFIX = "GOOGLE_PIN_";

@@ -164,13 +164,13 @@ function getSKDFromPem(pem) {
}

/**
 * Hashes |input| using the SHA1 algorithm in the following manner:
 *   btoa(sha1(atob(input)))
 * Hashes |input| using the SHA-256 algorithm in the following manner:
 *   btoa(sha256(atob(input)))
 *
 * @argument {String} input Base64 string to decode and return the hash of.
 * @returns {String} Base64 encoded SHA1 hash.
 * @returns {String} Base64 encoded SHA-256 hash.
 */
function sha1Base64(input) {
function sha256Base64(input) {
  let decodedValue;
  try {
    decodedValue = atob(input);
@@ -191,7 +191,7 @@ function sha1Base64(input) {

  let hasher = Cc["@mozilla.org/security/hash;1"]
                 .createInstance(Ci.nsICryptoHash);
  hasher.init(hasher.SHA1);
  hasher.init(hasher.SHA256);
  hasher.update(data, data.length);

  // true is passed so that the hasher returns a Base64 encoded string.
@@ -201,11 +201,11 @@ function sha1Base64(input) {
// Downloads the static certs file and tries to map Google Chrome nicknames
// to Mozilla nicknames, as well as storing any hashes for pins for which we
// don't have root PEMs. Each entry consists of a line containing the name of
// the pin followed either by a hash in the format "sha1/" + base64(hash), a
// hash in the format "sha256/" + base64(hash), a PEM encoded public key, or
// a PEM encoded certificate. For certificates that we have in our database,
// the pin followed either by a hash in the format "sha256/" + base64(hash),
// a PEM encoded public key, or a PEM encoded certificate.
// For certificates that we have in our database,
// return a map of Google's nickname to ours. For ones that aren't return a
// map of Google's nickname to sha1 values. This code is modeled after agl's
// map of Google's nickname to SHA-256 values. This code is modeled after agl's
// https://github.com/agl/transport-security-state-generate, which doesn't
// live in the Chromium repo because go is not an official language in
// Chromium.
@@ -216,7 +216,7 @@ function sha1Base64(input) {
// and stick the hash in certSKDToName
// We MUST be able to find a corresponding cert nickname for the Chrome names,
// otherwise we skip all pinsets referring to that Chrome name.
function downloadAndParseChromeCerts(filename, certSKDToName) {
function downloadAndParseChromeCerts(filename, certNameToSKD, certSKDToName) {
  // Prefixes that we care about.
  const BEGIN_CERT = "-----BEGIN CERTIFICATE-----";
  const END_CERT = "-----END CERTIFICATE-----";
@@ -238,8 +238,7 @@ function downloadAndParseChromeCerts(filename, certSKDToName) {
  let chromeNameToHash = {};
  let chromeNameToMozName = {};
  let chromeName;
  for (let i = 0; i < lines.length; ++i) {
    let line = lines[i];
  for (let line of lines) {
    // Skip comments and newlines.
    if (line.length == 0 || line[0] == '#') {
      continue;
@@ -250,18 +249,9 @@ function downloadAndParseChromeCerts(filename, certSKDToName) {
        state = POST_NAME;
        break;
      case POST_NAME:
        // TODO(bug 1229284): Chromium no longer uses SHA1 hashes, so remove
        // code like this supporting SHA1.
        if (line.startsWith(SHA1_PREFIX) ||
            line.startsWith(SHA256_PREFIX)) {
          if (line.startsWith(SHA1_PREFIX)) {
            hash = line.substring(SHA1_PREFIX.length);
          } else if (line.startsWith(SHA256_PREFIX)) {
        if (line.startsWith(SHA256_PREFIX)) {
          hash = line.substring(SHA256_PREFIX.length);
          }
          // Store the entire prefixed hash, so we can disambiguate sha1 from
          // sha256 later.
          chromeNameToHash[chromeName] = line;
          chromeNameToHash[chromeName] = hash;
          certNameToSKD[chromeName] = hash;
          certSKDToName[hash] = chromeName;
          state = PRE_NAME;
@@ -298,12 +288,9 @@ function downloadAndParseChromeCerts(filename, certSKDToName) {
      case IN_PUB_KEY:
        if (line.startsWith(END_PUB_KEY)) {
          state = PRE_NAME;
          // TODO(bug 1229284): Switch to SHA-256 instead. SHA1 is used here
          // mainly to confirm that the changes made to support public keys are
          // correct and don't change any hashes.
          hash = sha1Base64(pemPubKey);
          hash = sha256Base64(pemPubKey);
          pemPubKey = "";
          chromeNameToHash[chromeName] = SHA1_PREFIX + hash;
          chromeNameToHash[chromeName] = hash;
          certNameToSKD[chromeName] = hash;
          certSKDToName[hash] = chromeName;
        } else {
@@ -325,7 +312,6 @@ function downloadAndParseChromeCerts(filename, certSKDToName) {
// {
//   pinset_name : {
//     // Array of names with entries in certNameToSKD
//     sha1_hashes: [],
//     sha256_hashes: []
//   }
// }
@@ -344,20 +330,13 @@ function downloadAndParseChromePins(filename,

  chromePins.forEach(function(pin) {
    let valid = true;
    let pinset = { name: pin.name, sha1_hashes: [], sha256_hashes: [] };
    let pinset = { name: pin.name, sha256_hashes: [] };
    // Translate the Chrome pinset format to ours
    pin.static_spki_hashes.forEach(function(name) {
      if (name in chromeNameToHash) {
        let hash = chromeNameToHash[name];
        if (hash.startsWith(SHA1_PREFIX)) {
          hash = hash.substring(SHA1_PREFIX.length);
          pinset.sha1_hashes.push(certSKDToName[hash]);
        } else if (hash.startsWith(SHA256_PREFIX)) {
          hash = hash.substring(SHA256_PREFIX.length);
        pinset.sha256_hashes.push(certSKDToName[hash]);
        } else {
          throw("Unsupported hash type: " + chromeNameToHash[name]);
        }

        // We should have already added hashes for all of these when we
        // imported the certificate file.
        if (!certNameToSKD[name]) {
@@ -478,26 +457,18 @@ function genExpirationTime() {
}

function writeFullPinset(certNameToSKD, certSKDToName, pinset) {
  // We aren't guaranteed to have sha1 hashes in our own imported pins.
  let prefix = "kPinset_" + pinset.name;
  let sha1Name = "nullptr";
  let sha256Name = "nullptr";
  if (pinset.sha1_hashes && pinset.sha1_hashes.length > 0) {
    writeFingerprints(certNameToSKD, certSKDToName, pinset.name,
                      pinset.sha1_hashes, "sha1");
    sha1Name = "&" + prefix + "_sha1";
  if (!pinset.sha256_hashes || pinset.sha256_hashes.length == 0) {
    throw `ERROR: Pinset ${pinset.name} does not contain any hashes.`;
  }
  if (pinset.sha256_hashes && pinset.sha256_hashes.length > 0) {
  writeFingerprints(certNameToSKD, certSKDToName, pinset.name,
                      pinset.sha256_hashes, "sha256");
    sha256Name = "&" + prefix + "_sha256";
  }
                    pinset.sha256_hashes);
  writeString("static const StaticPinset " + prefix + " = {\n" +
          "  " + sha1Name + ",\n  " + sha256Name + "\n};\n\n");
              "  nullptr,\n  &" + prefix + "_sha256\n};\n\n");
}

function writeFingerprints(certNameToSKD, certSKDToName, name, hashes, type) {
  let varPrefix = "kPinset_" + name + "_" + type;
function writeFingerprints(certNameToSKD, certSKDToName, name, hashes) {
  let varPrefix = "kPinset_" + name + "_sha256";
  writeString("static const char* " + varPrefix + "_Data[] = {\n");
  let SKDList = [];
  for (let certName of hashes) {
@@ -595,23 +566,12 @@ function writeFile(certNameToSKD, certSKDToName,
  let mozillaPins = {};
  gStaticPins.pinsets.forEach(function(pinset) {
    mozillaPins[pinset.name] = true;
    // We aren't guaranteed to have sha1_hashes in our own JSON.
    if (pinset.sha1_hashes) {
      pinset.sha1_hashes.forEach(function(name) {
        usedFingerprints[name] = true;
      });
    }
    if (pinset.sha256_hashes) {
    pinset.sha256_hashes.forEach(function (name) {
      usedFingerprints[name] = true;
    });
    }
  });
  for (let key in chromeImportedPinsets) {
    let pinset = chromeImportedPinsets[key];
    pinset.sha1_hashes.forEach(function(name) {
      usedFingerprints[name] = true;
    });
    pinset.sha256_hashes.forEach(function(name) {
      usedFingerprints[name] = true;
    });
@@ -663,7 +623,7 @@ function loadExtraCertificates(certStringList) {
var extraCertificates = loadExtraCertificates(gStaticPins.extra_certificates);
var [ certNameToSKD, certSKDToName ] = loadNSSCertinfo(extraCertificates);
var [ chromeNameToHash, chromeNameToMozName ] = downloadAndParseChromeCerts(
  gStaticPins.chromium_data.cert_file_url, certSKDToName);
  gStaticPins.chromium_data.cert_file_url, certNameToSKD, certSKDToName);
var [ chromeImportedPinsets, chromeImportedEntries ] =
  downloadAndParseChromePins(gStaticPins.chromium_data.json_file_url,
    chromeNameToHash, chromeNameToMozName, certNameToSKD, certSKDToName);