summaryrefslogtreecommitdiff
path: root/security/manager/tools/genHPKPStaticPins.js
diff options
context:
space:
mode:
Diffstat (limited to 'security/manager/tools/genHPKPStaticPins.js')
-rw-r--r--security/manager/tools/genHPKPStaticPins.js630
1 files changed, 0 insertions, 630 deletions
diff --git a/security/manager/tools/genHPKPStaticPins.js b/security/manager/tools/genHPKPStaticPins.js
deleted file mode 100644
index f2b9dbddae..0000000000
--- a/security/manager/tools/genHPKPStaticPins.js
+++ /dev/null
@@ -1,630 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-// How to run this file:
-// 1. [obtain firefox source code]
-// 2. [build/obtain firefox binaries]
-// 3. run `[path to]/run-mozilla.sh [path to]/xpcshell \
-// [path to]/genHPKPStaticpins.js \
-// [absolute path to]/PreloadedHPKPins.json \
-// [an unused argument - see bug 1205406] \
-// [absolute path to]/StaticHPKPins.h
-"use strict";
-
-if (arguments.length != 3) {
- throw new Error("Usage: genHPKPStaticPins.js " +
- "<absolute path to PreloadedHPKPins.json> " +
- "<an unused argument - see bug 1205406> " +
- "<absolute path to StaticHPKPins.h>");
-}
-
-var { 'classes': Cc, 'interfaces': Ci, 'utils': Cu, 'results': Cr } = Components;
-
-var { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {});
-var { FileUtils } = Cu.import("resource://gre/modules/FileUtils.jsm", {});
-var { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
-
-var gCertDB = Cc["@mozilla.org/security/x509certdb;1"]
- .getService(Ci.nsIX509CertDB);
-
-const BUILT_IN_NICK_PREFIX = "Builtin Object Token:";
-const SHA256_PREFIX = "sha256/";
-const GOOGLE_PIN_PREFIX = "GOOGLE_PIN_";
-
-// Pins expire in 14 weeks (6 weeks on Beta + 8 weeks on stable)
-const PINNING_MINIMUM_REQUIRED_MAX_AGE = 60 * 60 * 24 * 7 * 14;
-
-const FILE_HEADER = "/* This Source Code Form is subject to the terms of the Mozilla Public\n" +
-" * License, v. 2.0. If a copy of the MPL was not distributed with this\n" +
-" * file, You can obtain one at http://mozilla.org/MPL/2.0/. */\n" +
-"\n" +
-"/*****************************************************************************/\n" +
-"/* This is an automatically generated file. If you're not */\n" +
-"/* PublicKeyPinningService.cpp, you shouldn't be #including it. */\n" +
-"/*****************************************************************************/\n" +
-"#include <stdint.h>" +
-"\n";
-
-const DOMAINHEADER = "/* Domainlist */\n" +
- "struct TransportSecurityPreload {\n" +
- " const char* mHost;\n" +
- " const bool mIncludeSubdomains;\n" +
- " const bool mTestMode;\n" +
- " const bool mIsMoz;\n" +
- " const int32_t mId;\n" +
- " const StaticFingerprints* pinset;\n" +
- "};\n\n";
-
-const PINSETDEF = "/* Pinsets are each an ordered list by the actual value of the fingerprint */\n" +
- "struct StaticFingerprints {\n" +
- " const size_t size;\n" +
- " const char* const* data;\n" +
- "};\n\n";
-
-// Command-line arguments
-var gStaticPins = parseJson(arguments[0]);
-
-// arguments[1] is ignored for now. See bug 1205406.
-
-// Open the output file.
-var file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
-file.initWithPath(arguments[2]);
-var gFileOutputStream = FileUtils.openSafeFileOutputStream(file);
-
-function writeString(string) {
- gFileOutputStream.write(string, string.length);
-}
-
-function readFileToString(filename) {
- let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
- file.initWithPath(filename);
- let stream = Cc["@mozilla.org/network/file-input-stream;1"]
- .createInstance(Ci.nsIFileInputStream);
- stream.init(file, -1, 0, 0);
- let buf = NetUtil.readInputStreamToString(stream, stream.available());
- return buf;
-}
-
-function stripComments(buf) {
- let lines = buf.split("\n");
- let entryRegex = /^\s*\/\//;
- let data = "";
- for (let i = 0; i < lines.length; ++i) {
- let match = entryRegex.exec(lines[i]);
- if (!match) {
- data = data + lines[i];
- }
- }
- return data;
-}
-
-function isBuiltinToken(tokenName) {
- return tokenName == "Builtin Object Token";
-}
-
-function isCertBuiltIn(cert) {
- let tokenNames = cert.getAllTokenNames({});
- if (!tokenNames) {
- return false;
- }
- if (tokenNames.some(isBuiltinToken)) {
- return true;
- }
- return false;
-}
-
-function download(filename) {
- let req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
- .createInstance(Ci.nsIXMLHttpRequest);
- req.open("GET", filename, false); // doing the request synchronously
- try {
- req.send();
- }
- catch (e) {
- throw new Error(`ERROR: problem downloading '${filename}': ${e}`);
- }
-
- if (req.status != 200) {
- throw new Error("ERROR: problem downloading '" + filename + "': status " +
- req.status);
- }
-
- let resultDecoded;
- try {
- resultDecoded = atob(req.responseText);
- }
- catch (e) {
- throw new Error("ERROR: could not decode data as base64 from '" + filename +
- "': " + e);
- }
- return resultDecoded;
-}
-
-function downloadAsJson(filename) {
- // we have to filter out '//' comments, while not mangling the json
- let result = download(filename).replace(/^(\s*)?\/\/[^\n]*\n/mg, "");
- let data = null;
- try {
- data = JSON.parse(result);
- }
- catch (e) {
- throw new Error("ERROR: could not parse data from '" + filename + "': " + e);
- }
- return data;
-}
-
-// Returns a Subject Public Key Digest from the given pem, if it exists.
-function getSKDFromPem(pem) {
- let cert = gCertDB.constructX509FromBase64(pem, pem.length);
- return cert.sha256SubjectPublicKeyInfoDigest;
-}
-
-/**
- * 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 SHA-256 hash.
- */
-function sha256Base64(input) {
- let decodedValue;
- try {
- decodedValue = atob(input);
- }
- catch (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
- // nsICryptoHash instance below.
- // In most cases across the code base, convertToByteArray() of
- // nsIScriptableUnicodeConverter is used to do this, but the method doesn't
- // seem to work here.
- let data = [];
- for (let i = 0; i < decodedValue.length; i++) {
- data[i] = decodedValue.charCodeAt(i);
- }
-
- let hasher = Cc["@mozilla.org/security/hash;1"]
- .createInstance(Ci.nsICryptoHash);
- hasher.init(hasher.SHA256);
- hasher.update(data, data.length);
-
- // true is passed so that the hasher returns a Base64 encoded string.
- return hasher.finish(true);
-}
-
-// 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 "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 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.
-// For all of the entries in this file:
-// - If the entry has a hash format, find the Mozilla pin name (cert nickname)
-// and stick the hash into certSKDToName
-// - If the entry has a PEM format, parse the PEM, find the Mozilla pin name
-// 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, certNameToSKD, certSKDToName) {
- // Prefixes that we care about.
- const BEGIN_CERT = "-----BEGIN CERTIFICATE-----";
- const END_CERT = "-----END CERTIFICATE-----";
- const BEGIN_PUB_KEY = "-----BEGIN PUBLIC KEY-----";
- const END_PUB_KEY = "-----END PUBLIC KEY-----";
-
- // Parsing states.
- const PRE_NAME = 0;
- const POST_NAME = 1;
- const IN_CERT = 2;
- const IN_PUB_KEY = 3;
- let state = PRE_NAME;
-
- let lines = download(filename).split("\n");
- let name = "";
- let pemCert = "";
- let pemPubKey = "";
- let hash = "";
- let chromeNameToHash = {};
- let chromeNameToMozName = {};
- let chromeName;
- for (let line of lines) {
- // Skip comments and newlines.
- if (line.length == 0 || line[0] == '#') {
- continue;
- }
- switch (state) {
- case PRE_NAME:
- chromeName = line;
- state = POST_NAME;
- break;
- case POST_NAME:
- if (line.startsWith(SHA256_PREFIX)) {
- hash = line.substring(SHA256_PREFIX.length);
- chromeNameToHash[chromeName] = hash;
- certNameToSKD[chromeName] = hash;
- certSKDToName[hash] = chromeName;
- state = PRE_NAME;
- } else if (line.startsWith(BEGIN_CERT)) {
- state = IN_CERT;
- } else if (line.startsWith(BEGIN_PUB_KEY)) {
- state = IN_PUB_KEY;
- } else {
- throw new Error("ERROR: couldn't parse Chrome certificate file " +
- "line: " + line);
- }
- break;
- case IN_CERT:
- if (line.startsWith(END_CERT)) {
- state = PRE_NAME;
- hash = getSKDFromPem(pemCert);
- pemCert = "";
- let mozName;
- if (hash in certSKDToName) {
- mozName = certSKDToName[hash];
- } else {
- // Not one of our built-in certs. Prefix the name with
- // GOOGLE_PIN_.
- mozName = GOOGLE_PIN_PREFIX + chromeName;
- dump("Can't find hash in builtin certs for Chrome nickname " +
- chromeName + ", inserting " + mozName + "\n");
- certSKDToName[hash] = mozName;
- certNameToSKD[mozName] = hash;
- }
- chromeNameToMozName[chromeName] = mozName;
- } else {
- pemCert += line;
- }
- break;
- case IN_PUB_KEY:
- if (line.startsWith(END_PUB_KEY)) {
- state = PRE_NAME;
- hash = sha256Base64(pemPubKey);
- pemPubKey = "";
- chromeNameToHash[chromeName] = hash;
- certNameToSKD[chromeName] = hash;
- certSKDToName[hash] = chromeName;
- } else {
- pemPubKey += line;
- }
- break;
- default:
- throw new Error("ERROR: couldn't parse Chrome certificate file " + line);
- }
- }
- return [ chromeNameToHash, chromeNameToMozName ];
-}
-
-// We can only import pinsets from chrome if for every name in the pinset:
-// - We have a hash from Chrome's static certificate file
-// - We have a builtin cert
-// If the pinset meets these requirements, we store a map array of pinset
-// objects:
-// {
-// pinset_name : {
-// // Array of names with entries in certNameToSKD
-// sha256_hashes: []
-// }
-// }
-// and an array of imported pinset entries:
-// { name: string, include_subdomains: boolean, test_mode: boolean,
-// pins: pinset_name }
-function downloadAndParseChromePins(filename,
- chromeNameToHash,
- chromeNameToMozName,
- certNameToSKD,
- certSKDToName) {
- let chromePreloads = downloadAsJson(filename);
- let chromePins = chromePreloads.pinsets;
- let chromeImportedPinsets = {};
- let chromeImportedEntries = [];
-
- chromePins.forEach(function(pin) {
- let valid = true;
- 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];
- pinset.sha256_hashes.push(certSKDToName[hash]);
-
- // We should have already added hashes for all of these when we
- // imported the certificate file.
- if (!certNameToSKD[name]) {
- throw new Error("ERROR: No hash for name: " + name);
- }
- } else if (name in chromeNameToMozName) {
- pinset.sha256_hashes.push(chromeNameToMozName[name]);
- } else {
- dump("Skipping Chrome pinset " + pinset.name + ", couldn't find " +
- "builtin " + name + " from cert file\n");
- valid = false;
- }
- });
- if (valid) {
- chromeImportedPinsets[pinset.name] = pinset;
- }
- });
-
- // Grab the domain entry lists. Chrome's entry format is similar to
- // ours, except theirs includes a HSTS mode.
- const cData = gStaticPins.chromium_data;
- let entries = chromePreloads.entries;
- entries.forEach(function(entry) {
- // HSTS entry only
- if (!entry.pins) {
- return;
- }
- let pinsetName = cData.substitute_pinsets[entry.pins];
- if (!pinsetName) {
- pinsetName = entry.pins;
- }
-
- // We trim the entry name here to avoid breaking hostname comparisons in the
- // HPKP implementation.
- entry.name = entry.name.trim();
-
- let isProductionDomain =
- (cData.production_domains.indexOf(entry.name) != -1);
- let isProductionPinset =
- (cData.production_pinsets.indexOf(pinsetName) != -1);
- let excludeDomain =
- (cData.exclude_domains.indexOf(entry.name) != -1);
- let isTestMode = !isProductionPinset && !isProductionDomain;
- if (entry.pins && !excludeDomain && chromeImportedPinsets[entry.pins]) {
- chromeImportedEntries.push({
- name: entry.name,
- include_subdomains: entry.include_subdomains,
- test_mode: isTestMode,
- is_moz: false,
- pins: pinsetName });
- }
- });
- return [ chromeImportedPinsets, chromeImportedEntries ];
-}
-
-// Returns a pair of maps [certNameToSKD, certSKDToName] between cert
-// nicknames and digests of the SPKInfo for the mozilla trust store
-function loadNSSCertinfo(extraCertificates) {
- let allCerts = gCertDB.getCerts();
- let enumerator = allCerts.getEnumerator();
- let certNameToSKD = {};
- let certSKDToName = {};
- while (enumerator.hasMoreElements()) {
- let cert = enumerator.getNext().QueryInterface(Ci.nsIX509Cert);
- if (!isCertBuiltIn(cert)) {
- continue;
- }
- let name = cert.nickname.substr(BUILT_IN_NICK_PREFIX.length);
- let SKD = cert.sha256SubjectPublicKeyInfoDigest;
- certNameToSKD[name] = SKD;
- certSKDToName[SKD] = name;
- }
-
- for (let cert of extraCertificates) {
- let name = cert.commonName;
- let SKD = cert.sha256SubjectPublicKeyInfoDigest;
- certNameToSKD[name] = SKD;
- certSKDToName[SKD] = name;
- }
-
- {
- // This is the pinning test certificate. The key hash identifies the
- // default RSA key from pykey.
- let name = "End Entity Test Cert";
- let SKD = "VCIlmPM9NkgFQtrs4Oa5TeFcDu6MWRTKSNdePEhOgD8=";
- certNameToSKD[name] = SKD;
- certSKDToName[SKD] = name;
- }
- return [certNameToSKD, certSKDToName];
-}
-
-function parseJson(filename) {
- let json = stripComments(readFileToString(filename));
- return JSON.parse(json);
-}
-
-function nameToAlias(certName) {
- // change the name to a string valid as a c identifier
- // remove non-ascii characters
- certName = certName.replace(/[^[:ascii:]]/g, "_");
- // replace non word characters
- certName = certName.replace(/[^A-Za-z0-9]/g, "_");
-
- return "k" + certName + "Fingerprint";
-}
-
-function compareByName (a, b) {
- return a.name.localeCompare(b.name);
-}
-
-function genExpirationTime() {
- let now = new Date();
- let nowMillis = now.getTime();
- let expirationMillis = nowMillis + (PINNING_MINIMUM_REQUIRED_MAX_AGE * 1000);
- let expirationMicros = expirationMillis * 1000;
- return "static const PRTime kPreloadPKPinsExpirationTime = INT64_C(" +
- expirationMicros + ");\n";
-}
-
-function writeFullPinset(certNameToSKD, certSKDToName, pinset) {
- let prefix = "kPinset_" + pinset.name;
- if (!pinset.sha256_hashes || pinset.sha256_hashes.length == 0) {
- throw new Error(`ERROR: Pinset ${pinset.name} does not contain any hashes`);
- }
- writeFingerprints(certNameToSKD, certSKDToName, pinset.name,
- pinset.sha256_hashes);
-}
-
-function writeFingerprints(certNameToSKD, certSKDToName, name, hashes) {
- let varPrefix = "kPinset_" + name;
- writeString("static const char* const " + varPrefix + "_Data[] = {\n");
- let SKDList = [];
- for (let certName of hashes) {
- if (!(certName in certNameToSKD)) {
- throw new Error(`ERROR: Can't find '${certName}' in certNameToSKD`);
- }
- SKDList.push(certNameToSKD[certName]);
- }
- for (let skd of SKDList.sort()) {
- writeString(" " + nameToAlias(certSKDToName[skd]) + ",\n");
- }
- if (hashes.length == 0) {
- // ANSI C requires that an initialiser list be non-empty.
- writeString(" 0\n");
- }
- writeString("};\n");
- writeString("static const StaticFingerprints " + varPrefix + " = {\n " +
- "sizeof(" + varPrefix + "_Data) / sizeof(const char*),\n " + varPrefix +
- "_Data\n};\n\n");
-}
-
-function writeEntry(entry) {
- let printVal = " { \"" + entry.name + "\",\ ";
- if (entry.include_subdomains) {
- printVal += "true, ";
- } else {
- printVal += "false, ";
- }
- // Default to test mode if not specified.
- let testMode = true;
- if (entry.hasOwnProperty("test_mode")) {
- testMode = entry.test_mode;
- }
- if (testMode) {
- printVal += "true, ";
- } else {
- printVal += "false, ";
- }
- if (entry.is_moz || (entry.pins.indexOf("mozilla") != -1 &&
- entry.pins != "mozilla_test")) {
- printVal += "true, ";
- } else {
- printVal += "false, ";
- }
- if ("id" in entry) {
- if (entry.id >= 256) {
- throw new Error("ERROR: Not enough buckets in histogram");
- }
- if (entry.id >= 0) {
- printVal += entry.id + ", ";
- }
- } else {
- printVal += "-1, ";
- }
- printVal += "&kPinset_" + entry.pins;
- printVal += " },\n";
- writeString(printVal);
-}
-
-function writeDomainList(chromeImportedEntries) {
- writeString("/* Sort hostnames for binary search. */\n");
- writeString("static const TransportSecurityPreload " +
- "kPublicKeyPinningPreloadList[] = {\n");
- let count = 0;
- let mozillaDomains = {};
- gStaticPins.entries.forEach(function(entry) {
- mozillaDomains[entry.name] = true;
- });
- // For any domain for which we have set pins, exclude them from
- // chromeImportedEntries.
- for (let i = chromeImportedEntries.length - 1; i >= 0; i--) {
- if (mozillaDomains[chromeImportedEntries[i].name]) {
- dump("Skipping duplicate pinset for domain " +
- JSON.stringify(chromeImportedEntries[i], undefined, 2) + "\n");
- chromeImportedEntries.splice(i, 1);
- }
- }
- let sortedEntries = gStaticPins.entries;
- sortedEntries.push.apply(sortedEntries, chromeImportedEntries);
- for (let entry of sortedEntries.sort(compareByName)) {
- count++;
- writeEntry(entry);
- }
- writeString("};\n");
-
- writeString("\n// Pinning Preload List Length = " + count + ";\n");
- writeString("\nstatic const int32_t kUnknownId = -1;\n");
-}
-
-function writeFile(certNameToSKD, certSKDToName,
- chromeImportedPinsets, chromeImportedEntries) {
- // Compute used pins from both Chrome's and our pinsets, so we can output
- // them later.
- let usedFingerprints = {};
- let mozillaPins = {};
- gStaticPins.pinsets.forEach(function(pinset) {
- mozillaPins[pinset.name] = true;
- pinset.sha256_hashes.forEach(function (name) {
- usedFingerprints[name] = true;
- });
- });
- for (let key in chromeImportedPinsets) {
- let pinset = chromeImportedPinsets[key];
- pinset.sha256_hashes.forEach(function(name) {
- usedFingerprints[name] = true;
- });
- }
-
- writeString(FILE_HEADER);
-
- // Write actual fingerprints.
- Object.keys(usedFingerprints).sort().forEach(function(certName) {
- if (certName) {
- writeString("/* " + certName + " */\n");
- writeString("static const char " + nameToAlias(certName) + "[] =\n");
- writeString(" \"" + certNameToSKD[certName] + "\";\n");
- writeString("\n");
- }
- });
-
- // Write the pinsets
- writeString(PINSETDEF);
- writeString("/* PreloadedHPKPins.json pinsets */\n");
- gStaticPins.pinsets.sort(compareByName).forEach(function(pinset) {
- writeFullPinset(certNameToSKD, certSKDToName, pinset);
- });
- writeString("/* Chrome static pinsets */\n");
- for (let key in chromeImportedPinsets) {
- if (mozillaPins[key]) {
- dump("Skipping duplicate pinset " + key + "\n");
- } else {
- dump("Writing pinset " + key + "\n");
- writeFullPinset(certNameToSKD, certSKDToName, chromeImportedPinsets[key]);
- }
- }
-
- // Write the domainlist entries.
- writeString(DOMAINHEADER);
- writeDomainList(chromeImportedEntries);
- writeString("\n");
- writeString(genExpirationTime());
-}
-
-function loadExtraCertificates(certStringList) {
- let constructedCerts = [];
- for (let certString of certStringList) {
- constructedCerts.push(gCertDB.constructX509FromBase64(certString));
- }
- return constructedCerts;
-}
-
-var extraCertificates = loadExtraCertificates(gStaticPins.extra_certificates);
-var [ certNameToSKD, certSKDToName ] = loadNSSCertinfo(extraCertificates);
-var [ chromeNameToHash, chromeNameToMozName ] = downloadAndParseChromeCerts(
- gStaticPins.chromium_data.cert_file_url, certNameToSKD, certSKDToName);
-var [ chromeImportedPinsets, chromeImportedEntries ] =
- downloadAndParseChromePins(gStaticPins.chromium_data.json_file_url,
- chromeNameToHash, chromeNameToMozName, certNameToSKD, certSKDToName);
-
-writeFile(certNameToSKD, certSKDToName, chromeImportedPinsets,
- chromeImportedEntries);
-
-FileUtils.closeSafeFileOutputStream(gFileOutputStream);