Loading security/manager/tools/genHPKPStaticPins.js +32 −72 Original line number Diff line number Diff line Loading @@ -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 " + Loading @@ -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_"; Loading Loading @@ -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); Loading @@ -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. Loading @@ -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. Loading @@ -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-----"; Loading @@ -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; Loading @@ -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; Loading Loading @@ -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 { Loading @@ -325,7 +312,6 @@ function downloadAndParseChromeCerts(filename, certSKDToName) { // { // pinset_name : { // // Array of names with entries in certNameToSKD // sha1_hashes: [], // sha256_hashes: [] // } // } Loading @@ -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]) { Loading Loading @@ -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) { Loading Loading @@ -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; }); Loading Loading @@ -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); Loading Loading
security/manager/tools/genHPKPStaticPins.js +32 −72 Original line number Diff line number Diff line Loading @@ -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 " + Loading @@ -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_"; Loading Loading @@ -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); Loading @@ -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. Loading @@ -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. Loading @@ -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-----"; Loading @@ -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; Loading @@ -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; Loading Loading @@ -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 { Loading @@ -325,7 +312,6 @@ function downloadAndParseChromeCerts(filename, certSKDToName) { // { // pinset_name : { // // Array of names with entries in certNameToSKD // sha1_hashes: [], // sha256_hashes: [] // } // } Loading @@ -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]) { Loading Loading @@ -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) { Loading Loading @@ -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; }); Loading Loading @@ -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); Loading