#400: partial Bug 1233328 part 2, pull genHPKPStaticPins.js to esr52

This commit is contained in:
Cameron Kaiser 2017-05-29 14:53:29 -07:00
parent eb0e2c9c01
commit 2ba51bda6d
2 changed files with 56 additions and 104 deletions

View File

@ -95,21 +95,17 @@ EvalCert(const CERTCertificate* cert, const StaticFingerprints* fingerprints,
/* /*
* Sets certListIntersectsPinset to true if a given chain matches any * Sets certListIntersectsPinset to true if a given chain matches any
* fingerprints from the given pinset or the dynamicFingerprints array, or to * fingerprints from the given static fingerprints or the
* false otherwise. * dynamicFingerprints array, or to false otherwise.
*/ */
static nsresult static nsresult
EvalChain(const CERTCertList* certList, const StaticPinset* pinset, EvalChain(const CERTCertList* certList, const StaticFingerprints* fingerprints,
const nsTArray<nsCString>* dynamicFingerprints, const nsTArray<nsCString>* dynamicFingerprints,
/*out*/ bool& certListIntersectsPinset) /*out*/ bool& certListIntersectsPinset)
{ {
certListIntersectsPinset = false; certListIntersectsPinset = false;
CERTCertificate* currentCert; CERTCertificate* currentCert;
const StaticFingerprints* fingerprints = nullptr;
if (pinset) {
fingerprints = pinset->sha256;
}
if (!fingerprints && !dynamicFingerprints) { if (!fingerprints && !dynamicFingerprints) {
MOZ_ASSERT(false, "Must pass in at least one type of pinset"); MOZ_ASSERT(false, "Must pass in at least one type of pinset");
return NS_ERROR_FAILURE; return NS_ERROR_FAILURE;

View File

@ -10,12 +10,13 @@
// [absolute path to]/PreloadedHPKPins.json \ // [absolute path to]/PreloadedHPKPins.json \
// [an unused argument - see bug 1205406] \ // [an unused argument - see bug 1205406] \
// [absolute path to]/StaticHPKPins.h // [absolute path to]/StaticHPKPins.h
"use strict";
if (arguments.length != 3) { if (arguments.length != 3) {
throw "Usage: genHPKPStaticPins.js " + throw new Error("Usage: genHPKPStaticPins.js " +
"<absolute path to PreloadedHPKPins.json> " + "<absolute path to PreloadedHPKPins.json> " +
"<an unused argument - see bug 1205406> " + "<an unused argument - see bug 1205406> " +
"<absolute path to StaticHPKPins.h>"; "<absolute path to StaticHPKPins.h>");
} }
var { 'classes': Cc, 'interfaces': Ci, 'utils': Cu, 'results': Cr } = Components; var { 'classes': Cc, 'interfaces': Ci, 'utils': Cu, 'results': Cr } = Components;
@ -28,7 +29,6 @@ var gCertDB = Cc["@mozilla.org/security/x509certdb;1"]
.getService(Ci.nsIX509CertDB); .getService(Ci.nsIX509CertDB);
const BUILT_IN_NICK_PREFIX = "Builtin Object Token:"; const BUILT_IN_NICK_PREFIX = "Builtin Object Token:";
const SHA1_PREFIX = "sha1/";
const SHA256_PREFIX = "sha256/"; const SHA256_PREFIX = "sha256/";
const GOOGLE_PIN_PREFIX = "GOOGLE_PIN_"; const GOOGLE_PIN_PREFIX = "GOOGLE_PIN_";
@ -53,17 +53,13 @@ const DOMAINHEADER = "/* Domainlist */\n" +
" const bool mTestMode;\n" + " const bool mTestMode;\n" +
" const bool mIsMoz;\n" + " const bool mIsMoz;\n" +
" const int32_t mId;\n" + " const int32_t mId;\n" +
" const StaticPinset *pinset;\n" + " const StaticFingerprints* pinset;\n" +
"};\n\n"; "};\n\n";
const PINSETDEF = "/* Pinsets are each an ordered list by the actual value of the fingerprint */\n" + const PINSETDEF = "/* Pinsets are each an ordered list by the actual value of the fingerprint */\n" +
"struct StaticFingerprints {\n" + "struct StaticFingerprints {\n" +
" const size_t size;\n" + " const size_t size;\n" +
" const char* const* data;\n" + " const char* const* data;\n" +
"};\n\n" +
"struct StaticPinset {\n" +
" const StaticFingerprints* sha1;\n" +
" const StaticFingerprints* sha256;\n" +
"};\n\n"; "};\n\n";
// Command-line arguments // Command-line arguments
@ -126,12 +122,12 @@ function download(filename) {
req.send(); req.send();
} }
catch (e) { catch (e) {
throw "ERROR: problem downloading '" + filename + "': " + e; throw new Error(`ERROR: problem downloading '${filename}': ${e}`);
} }
if (req.status != 200) { if (req.status != 200) {
throw("ERROR: problem downloading '" + filename + "': status " + throw new Error("ERROR: problem downloading '" + filename + "': status " +
req.status); req.status);
} }
let resultDecoded; let resultDecoded;
@ -139,7 +135,8 @@ function download(filename) {
resultDecoded = atob(req.responseText); resultDecoded = atob(req.responseText);
} }
catch (e) { catch (e) {
throw "ERROR: could not decode data as base64 from '" + filename + "': " + e; throw new Error("ERROR: could not decode data as base64 from '" + filename +
"': " + e);
} }
return resultDecoded; return resultDecoded;
} }
@ -152,7 +149,7 @@ function downloadAsJson(filename) {
data = JSON.parse(result); data = JSON.parse(result);
} }
catch (e) { catch (e) {
throw "ERROR: could not parse data from '" + filename + "': " + e; throw new Error("ERROR: could not parse data from '" + filename + "': " + e);
} }
return data; return data;
} }
@ -164,19 +161,19 @@ function getSKDFromPem(pem) {
} }
/** /**
* Hashes |input| using the SHA1 algorithm in the following manner: * Hashes |input| using the SHA-256 algorithm in the following manner:
* btoa(sha1(atob(input))) * btoa(sha256(atob(input)))
* *
* @argument {String} input Base64 string to decode and return the hash of. * @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; let decodedValue;
try { try {
decodedValue = atob(input); decodedValue = atob(input);
} }
catch (e) { catch (e) {
throw `ERROR: could not decode as base64: '${input}': ${e}`; throw new Error(`ERROR: could not decode as base64: '${input}': ${e}`);
} }
// Convert |decodedValue| to an array so that it can be hashed by the // Convert |decodedValue| to an array so that it can be hashed by the
@ -191,7 +188,7 @@ function sha1Base64(input) {
let hasher = Cc["@mozilla.org/security/hash;1"] let hasher = Cc["@mozilla.org/security/hash;1"]
.createInstance(Ci.nsICryptoHash); .createInstance(Ci.nsICryptoHash);
hasher.init(hasher.SHA1); hasher.init(hasher.SHA256);
hasher.update(data, data.length); hasher.update(data, data.length);
// true is passed so that the hasher returns a Base64 encoded string. // true is passed so that the hasher returns a Base64 encoded string.
@ -201,11 +198,11 @@ function sha1Base64(input) {
// Downloads the static certs file and tries to map Google Chrome nicknames // 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 // 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 // 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 // the pin followed either by a hash in the format "sha256/" + base64(hash),
// hash in the format "sha256/" + base64(hash), a PEM encoded public key, or // a PEM encoded public key, or a PEM encoded certificate.
// a PEM encoded certificate. For certificates that we have in our database, // For certificates that we have in our database,
// return a map of Google's nickname to ours. For ones that aren't return a // 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 // https://github.com/agl/transport-security-state-generate, which doesn't
// live in the Chromium repo because go is not an official language in // live in the Chromium repo because go is not an official language in
// Chromium. // Chromium.
@ -216,7 +213,7 @@ function sha1Base64(input) {
// and stick the hash in certSKDToName // and stick the hash in certSKDToName
// We MUST be able to find a corresponding cert nickname for the Chrome names, // We MUST be able to find a corresponding cert nickname for the Chrome names,
// otherwise we skip all pinsets referring to that Chrome name. // otherwise we skip all pinsets referring to that Chrome name.
function downloadAndParseChromeCerts(filename, certSKDToName) { function downloadAndParseChromeCerts(filename, certNameToSKD, certSKDToName) {
// Prefixes that we care about. // Prefixes that we care about.
const BEGIN_CERT = "-----BEGIN CERTIFICATE-----"; const BEGIN_CERT = "-----BEGIN CERTIFICATE-----";
const END_CERT = "-----END CERTIFICATE-----"; const END_CERT = "-----END CERTIFICATE-----";
@ -238,30 +235,20 @@ function downloadAndParseChromeCerts(filename, certSKDToName) {
let chromeNameToHash = {}; let chromeNameToHash = {};
let chromeNameToMozName = {}; let chromeNameToMozName = {};
let chromeName; let chromeName;
for (let i = 0; i < lines.length; ++i) { for (let line of lines) {
let line = lines[i];
// Skip comments and newlines. // Skip comments and newlines.
if (line.length == 0 || line[0] == '#') { if (line.length == 0 || line[0] == '#') {
continue; continue;
} }
switch(state) { switch (state) {
case PRE_NAME: case PRE_NAME:
chromeName = line; chromeName = line;
state = POST_NAME; state = POST_NAME;
break; break;
case POST_NAME: case POST_NAME:
// TODO(bug 1229284): Chromium no longer uses SHA1 hashes, so remove if (line.startsWith(SHA256_PREFIX)) {
// code like this supporting SHA1. hash = line.substring(SHA256_PREFIX.length);
if (line.startsWith(SHA1_PREFIX) || chromeNameToHash[chromeName] = hash;
line.startsWith(SHA256_PREFIX)) {
if (line.startsWith(SHA1_PREFIX)) {
hash = line.substring(SHA1_PREFIX.length);
} else 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;
certNameToSKD[chromeName] = hash; certNameToSKD[chromeName] = hash;
certSKDToName[hash] = chromeName; certSKDToName[hash] = chromeName;
state = PRE_NAME; state = PRE_NAME;
@ -270,7 +257,8 @@ function downloadAndParseChromeCerts(filename, certSKDToName) {
} else if (line.startsWith(BEGIN_PUB_KEY)) { } else if (line.startsWith(BEGIN_PUB_KEY)) {
state = IN_PUB_KEY; state = IN_PUB_KEY;
} else { } else {
throw "ERROR: couldn't parse Chrome certificate file " + line; throw new Error("ERROR: couldn't parse Chrome certificate file " +
"line: " + line);
} }
break; break;
case IN_CERT: case IN_CERT:
@ -298,12 +286,9 @@ function downloadAndParseChromeCerts(filename, certSKDToName) {
case IN_PUB_KEY: case IN_PUB_KEY:
if (line.startsWith(END_PUB_KEY)) { if (line.startsWith(END_PUB_KEY)) {
state = PRE_NAME; state = PRE_NAME;
// TODO(bug 1229284): Switch to SHA-256 instead. SHA1 is used here hash = sha256Base64(pemPubKey);
// mainly to confirm that the changes made to support public keys are
// correct and don't change any hashes.
hash = sha1Base64(pemPubKey);
pemPubKey = ""; pemPubKey = "";
chromeNameToHash[chromeName] = SHA1_PREFIX + hash; chromeNameToHash[chromeName] = hash;
certNameToSKD[chromeName] = hash; certNameToSKD[chromeName] = hash;
certSKDToName[hash] = chromeName; certSKDToName[hash] = chromeName;
} else { } else {
@ -311,7 +296,7 @@ function downloadAndParseChromeCerts(filename, certSKDToName) {
} }
break; break;
default: default:
throw "ERROR: couldn't parse Chrome certificate file " + line; throw new Error("ERROR: couldn't parse Chrome certificate file " + line);
} }
} }
return [ chromeNameToHash, chromeNameToMozName ]; return [ chromeNameToHash, chromeNameToMozName ];
@ -325,7 +310,6 @@ function downloadAndParseChromeCerts(filename, certSKDToName) {
// { // {
// pinset_name : { // pinset_name : {
// // Array of names with entries in certNameToSKD // // Array of names with entries in certNameToSKD
// sha1_hashes: [],
// sha256_hashes: [] // sha256_hashes: []
// } // }
// } // }
@ -344,24 +328,17 @@ function downloadAndParseChromePins(filename,
chromePins.forEach(function(pin) { chromePins.forEach(function(pin) {
let valid = true; 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 // Translate the Chrome pinset format to ours
pin.static_spki_hashes.forEach(function(name) { pin.static_spki_hashes.forEach(function(name) {
if (name in chromeNameToHash) { if (name in chromeNameToHash) {
let hash = chromeNameToHash[name]; let hash = chromeNameToHash[name];
if (hash.startsWith(SHA1_PREFIX)) { pinset.sha256_hashes.push(certSKDToName[hash]);
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 // We should have already added hashes for all of these when we
// imported the certificate file. // imported the certificate file.
if (!certNameToSKD[name]) { if (!certNameToSKD[name]) {
throw("No hash for name: " + name); throw new Error("ERROR: No hash for name: " + name);
} }
} else if (name in chromeNameToMozName) { } else if (name in chromeNameToMozName) {
pinset.sha256_hashes.push(chromeNameToMozName[name]); pinset.sha256_hashes.push(chromeNameToMozName[name]);
@ -457,9 +434,9 @@ function parseJson(filename) {
function nameToAlias(certName) { function nameToAlias(certName) {
// change the name to a string valid as a c identifier // change the name to a string valid as a c identifier
// remove non-ascii characters // remove non-ascii characters
certName = certName.replace( /[^[:ascii:]]/g, "_"); certName = certName.replace(/[^[:ascii:]]/g, "_");
// replace non word characters // replace non word characters
certName = certName.replace(/[^A-Za-z0-9]/g ,"_"); certName = certName.replace(/[^A-Za-z0-9]/g, "_");
return "k" + certName + "Fingerprint"; return "k" + certName + "Fingerprint";
} }
@ -474,35 +451,25 @@ function genExpirationTime() {
let expirationMillis = nowMillis + (PINNING_MINIMUM_REQUIRED_MAX_AGE * 1000); let expirationMillis = nowMillis + (PINNING_MINIMUM_REQUIRED_MAX_AGE * 1000);
let expirationMicros = expirationMillis * 1000; let expirationMicros = expirationMillis * 1000;
return "static const PRTime kPreloadPKPinsExpirationTime = INT64_C(" + return "static const PRTime kPreloadPKPinsExpirationTime = INT64_C(" +
expirationMicros +");\n"; expirationMicros + ");\n";
} }
function writeFullPinset(certNameToSKD, certSKDToName, pinset) { function writeFullPinset(certNameToSKD, certSKDToName, pinset) {
// We aren't guaranteed to have sha1 hashes in our own imported pins.
let prefix = "kPinset_" + pinset.name; let prefix = "kPinset_" + pinset.name;
let sha1Name = "nullptr"; if (!pinset.sha256_hashes || pinset.sha256_hashes.length == 0) {
let sha256Name = "nullptr"; throw new Error(`ERROR: Pinset ${pinset.name} does not contain any hashes`);
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) { writeFingerprints(certNameToSKD, certSKDToName, pinset.name,
writeFingerprints(certNameToSKD, certSKDToName, pinset.name, pinset.sha256_hashes);
pinset.sha256_hashes, "sha256");
sha256Name = "&" + prefix + "_sha256";
}
writeString("static const StaticPinset " + prefix + " = {\n" +
" " + sha1Name + ",\n " + sha256Name + "\n};\n\n");
} }
function writeFingerprints(certNameToSKD, certSKDToName, name, hashes, type) { function writeFingerprints(certNameToSKD, certSKDToName, name, hashes) {
let varPrefix = "kPinset_" + name + "_" + type; let varPrefix = "kPinset_" + name;
writeString("static const char* " + varPrefix + "_Data[] = {\n"); writeString("static const char* const " + varPrefix + "_Data[] = {\n");
let SKDList = []; let SKDList = [];
for (let certName of hashes) { for (let certName of hashes) {
if (!(certName in certNameToSKD)) { if (!(certName in certNameToSKD)) {
throw "Can't find " + certName + " in certNameToSKD"; throw new Error(`ERROR: Can't find '${certName}' in certNameToSKD`);
} }
SKDList.push(certNameToSKD[certName]); SKDList.push(certNameToSKD[certName]);
} }
@ -544,7 +511,7 @@ function writeEntry(entry) {
} }
if ("id" in entry) { if ("id" in entry) {
if (entry.id >= 256) { if (entry.id >= 256) {
throw("Not enough buckets in histogram"); throw new Error("ERROR: Not enough buckets in histogram");
} }
if (entry.id >= 0) { if (entry.id >= 0) {
printVal += entry.id + ", "; printVal += entry.id + ", ";
@ -595,23 +562,12 @@ function writeFile(certNameToSKD, certSKDToName,
let mozillaPins = {}; let mozillaPins = {};
gStaticPins.pinsets.forEach(function(pinset) { gStaticPins.pinsets.forEach(function(pinset) {
mozillaPins[pinset.name] = true; mozillaPins[pinset.name] = true;
// We aren't guaranteed to have sha1_hashes in our own JSON. pinset.sha256_hashes.forEach(function (name) {
if (pinset.sha1_hashes) { usedFingerprints[name] = true;
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) { for (let key in chromeImportedPinsets) {
let pinset = chromeImportedPinsets[key]; let pinset = chromeImportedPinsets[key];
pinset.sha1_hashes.forEach(function(name) {
usedFingerprints[name] = true;
});
pinset.sha256_hashes.forEach(function(name) { pinset.sha256_hashes.forEach(function(name) {
usedFingerprints[name] = true; usedFingerprints[name] = true;
}); });
@ -663,7 +619,7 @@ function loadExtraCertificates(certStringList) {
var extraCertificates = loadExtraCertificates(gStaticPins.extra_certificates); var extraCertificates = loadExtraCertificates(gStaticPins.extra_certificates);
var [ certNameToSKD, certSKDToName ] = loadNSSCertinfo(extraCertificates); var [ certNameToSKD, certSKDToName ] = loadNSSCertinfo(extraCertificates);
var [ chromeNameToHash, chromeNameToMozName ] = downloadAndParseChromeCerts( var [ chromeNameToHash, chromeNameToMozName ] = downloadAndParseChromeCerts(
gStaticPins.chromium_data.cert_file_url, certSKDToName); gStaticPins.chromium_data.cert_file_url, certNameToSKD, certSKDToName);
var [ chromeImportedPinsets, chromeImportedEntries ] = var [ chromeImportedPinsets, chromeImportedEntries ] =
downloadAndParseChromePins(gStaticPins.chromium_data.json_file_url, downloadAndParseChromePins(gStaticPins.chromium_data.json_file_url,
chromeNameToHash, chromeNameToMozName, certNameToSKD, certSKDToName); chromeNameToHash, chromeNameToMozName, certNameToSKD, certSKDToName);