Issue #1280 - Part 1: Remove HPKP components.

This also removes leftover plumbing for storing preload information
in SiteSecurityService since no service still uses it.
This commit is contained in:
wolfbeast 2020-03-28 01:06:56 +01:00 committed by Roy Tam
commit cf5f069080
11 changed files with 32 additions and 2657 deletions

View file

@ -1,222 +0,0 @@
// -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
// 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/.
// The top-level element is a dictionary with two keys: "pinsets" maps details
// of certificate pinning to a name and "entries" contains the HPKP details for
// each host.
//
// "pinsets" is a list of objects. Each object has the following members:
// name: (string) the name of the pinset
// sha256_hashes: (list of strings) the set of allowed SPKIs hashes
//
// For a given pinset, a certificate is accepted if at least one of the
// Subject Public Key Infos (SPKIs) is found in the chain. SPKIs are specified
// as names, which must match up with the name given in the Mozilla root store.
//
// "entries" is a list of objects. Each object has the following members:
// name: (string) the DNS name of the host in question
// include_subdomains: (optional bool) whether subdomains of |name| are also covered
// pins: (string) the |name| member of an object in |pinsets|
//
// "extra_certs" is a list of base64-encoded certificates. These are used in
// pinsets that reference certificates not in our root program (for example,
// Facebook).
// equifax -> aus3
// Geotrust Primary -> www.mozilla.org
// Geotrust Global -> *. addons.mozilla.org
{
"chromium_data" : {
"cert_file_url": "https://chromium.googlesource.com/chromium/src/net/+/master/http/transport_security_state_static.pins?format=TEXT",
"json_file_url": "https://chromium.googlesource.com/chromium/src/net/+/master/http/transport_security_state_static.json?format=TEXT",
"substitute_pinsets": {
// Use the larger google_root_pems pinset instead of google
"google": "google_root_pems"
},
"production_pinsets": [
"google_root_pems",
"facebook"
],
"production_domains": [
// Chrome's test domains.
"pinningtest.appspot.com",
"pinning-test.badssl.com",
// Dropbox
"dropbox.com",
"www.dropbox.com",
// Twitter
"api.twitter.com",
"business.twitter.com",
"dev.twitter.com",
"mobile.twitter.com",
"oauth.twitter.com",
"platform.twitter.com",
"twimg.com",
"www.twitter.com",
// Tor
"torproject.org",
"blog.torproject.org",
"check.torproject.org",
"dist.torproject.org",
"www.torproject.org",
// SpiderOak
"spideroak.com"
],
"exclude_domains" : [
// Chrome's entry for twitter.com doesn't include subdomains, so replace
// it with our own entry below which also uses an expanded pinset.
"twitter.com"
]
},
"pinsets": [
{
// From bug 772756, mozilla uses GeoTrust, Digicert and Thawte. Our
// cdn sites use Verisign and Baltimore. We exclude 1024-bit root certs
// from all providers. geotrust ca info:
// http://www.geotrust.com/resources/root-certificates/index.html
"name": "mozilla",
"sha256_hashes": [
"Baltimore CyberTrust Root",
"DigiCert Assured ID Root CA",
"DigiCert Global Root CA",
"DigiCert High Assurance EV Root CA",
"GeoTrust Global CA",
"GeoTrust Global CA 2",
"GeoTrust Primary Certification Authority",
"GeoTrust Primary Certification Authority - G2",
"GeoTrust Primary Certification Authority - G3",
"GeoTrust Universal CA",
"GeoTrust Universal CA 2",
"thawte Primary Root CA",
"thawte Primary Root CA - G2",
"thawte Primary Root CA - G3",
"Verisign Class 1 Public Primary Certification Authority - G3",
"Verisign Class 2 Public Primary Certification Authority - G3",
"Verisign Class 3 Public Primary Certification Authority - G3",
"VeriSign Class 3 Public Primary Certification Authority - G4",
"VeriSign Class 3 Public Primary Certification Authority - G5",
// "Verisign Class 4 Public Primary Certification Authority - G3",
"VeriSign Universal Root Certification Authority"
]
},
{
"name": "mozilla_services",
"sha256_hashes": [
"DigiCert Global Root CA"
]
},
// For pinning tests on pinning.example.com, the certificate must be 'End
// Entity Test Cert'
{
"name": "mozilla_test",
"sha256_hashes": [
"End Entity Test Cert"
]
},
// Google's root PEMs. Chrome pins only to their intermediate certs, but
// they'd like us to be more liberal. For the initial list, we are using
// the certs from http://pki.google.com/roots.pem.
// We have no built-in for commented out CAs.
{
"name": "google_root_pems",
"sha256_hashes": [
"AddTrust External Root",
"AddTrust Low-Value Services Root",
"AddTrust Public Services Root",
"AddTrust Qualified Certificates Root",
"AffirmTrust Commercial",
"AffirmTrust Networking",
"AffirmTrust Premium",
"AffirmTrust Premium ECC",
"Baltimore CyberTrust Root",
"Comodo AAA Services root",
"COMODO Certification Authority",
"COMODO ECC Certification Authority",
"COMODO RSA Certification Authority",
"Comodo Secure Services root",
"Comodo Trusted Services root",
"Cybertrust Global Root",
"DigiCert Assured ID Root CA",
"DigiCert Assured ID Root G2",
"DigiCert Assured ID Root G3",
"DigiCert Global Root CA",
"DigiCert Global Root G2",
"DigiCert Global Root G3",
"DigiCert High Assurance EV Root CA",
"DigiCert Trusted Root G4",
"Entrust Root Certification Authority",
"Entrust Root Certification Authority - EC1",
"Entrust Root Certification Authority - G2",
"Entrust.net Premium 2048 Secure Server CA",
// "Equifax Secure Certificate Authority",
"GeoTrust Global CA",
"GeoTrust Global CA 2",
"GeoTrust Primary Certification Authority",
"GeoTrust Primary Certification Authority - G2",
"GeoTrust Primary Certification Authority - G3",
"GeoTrust Universal CA",
"GeoTrust Universal CA 2",
"GlobalSign ECC Root CA - R4",
"GlobalSign ECC Root CA - R5",
"GlobalSign Root CA",
"GlobalSign Root CA - R2",
"GlobalSign Root CA - R3",
"Go Daddy Class 2 CA",
"Go Daddy Root Certificate Authority - G2",
"Starfield Class 2 CA",
"Starfield Root Certificate Authority - G2",
"thawte Primary Root CA",
"thawte Primary Root CA - G2",
"thawte Primary Root CA - G3",
"USERTrust ECC Certification Authority",
"USERTrust RSA Certification Authority",
"UTN USERFirst Hardware Root CA",
"Verisign Class 3 Public Primary Certification Authority - G3",
"VeriSign Class 3 Public Primary Certification Authority - G4",
"VeriSign Class 3 Public Primary Certification Authority - G5",
"VeriSign Universal Root Certification Authority"
]
}
],
"entries": [
// Only domains that are operationally crucial to Firefox can have per-host
// telemetry reporting (the "id") field
{ "name": "addons.mozilla.org", "include_subdomains": true,
"pins": "mozilla", "test_mode": false, "id": 1 },
{ "name": "addons.mozilla.net", "include_subdomains": true,
"pins": "mozilla", "test_mode": false, "id": 2 },
{ "name": "aus4.mozilla.org", "include_subdomains": true,
"pins": "mozilla", "test_mode": true, "id": 3 },
{ "name": "accounts.firefox.com", "include_subdomains": true,
"pins": "mozilla_services", "test_mode": false, "id": 4 },
{ "name": "api.accounts.firefox.com", "include_subdomains": true,
"pins": "mozilla_services", "test_mode": false, "id": 5 },
{ "name": "cdn.mozilla.net", "include_subdomains": true,
"pins": "mozilla", "test_mode": false },
{ "name": "cdn.mozilla.org", "include_subdomains": true,
"pins": "mozilla", "test_mode": false },
{ "name": "services.mozilla.com", "include_subdomains": true,
"pins": "mozilla_services", "test_mode": false, "id": 6 },
{ "name": "include-subdomains.pinning.example.com",
"include_subdomains": true, "pins": "mozilla_test",
"test_mode": false },
// Example domain to collect per-host stats for telemetry tests.
{ "name": "exclude-subdomains.pinning.example.com",
"include_subdomains": false, "pins": "mozilla_test",
"test_mode": false, "id": 0 },
{ "name": "test-mode.pinning.example.com", "include_subdomains": true,
"pins": "mozilla_test", "test_mode": true },
// Expand twitter's pinset to include all of *.twitter.com and use
// twitterCDN. More specific rules take precedence because we search for
// exact domain name first.
{ "name": "twitter.com", "include_subdomains": true,
"pins": "twitterCDN", "test_mode": false },
{ "name": "aus5.mozilla.org", "include_subdomains": true,
"pins": "mozilla", "test_mode": true, "id": 7 }
],
"extra_certificates": []
}

View file

@ -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);