diff options
Diffstat (limited to 'security/manager/tools')
-rw-r--r-- | security/manager/tools/PreloadedHPKPins.json | 222 | ||||
-rw-r--r-- | security/manager/tools/genHPKPStaticPins.js | 630 | ||||
-rw-r--r-- | security/manager/tools/getHSTSPreloadList.js | 458 |
3 files changed, 0 insertions, 1310 deletions
diff --git a/security/manager/tools/PreloadedHPKPins.json b/security/manager/tools/PreloadedHPKPins.json deleted file mode 100644 index d9c394a1d..000000000 --- a/security/manager/tools/PreloadedHPKPins.json +++ /dev/null @@ -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": [] -} diff --git a/security/manager/tools/genHPKPStaticPins.js b/security/manager/tools/genHPKPStaticPins.js deleted file mode 100644 index f2b9dbdda..000000000 --- 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); diff --git a/security/manager/tools/getHSTSPreloadList.js b/security/manager/tools/getHSTSPreloadList.js deleted file mode 100644 index 42d4da067..000000000 --- a/security/manager/tools/getHSTSPreloadList.js +++ /dev/null @@ -1,458 +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/. */ -"use strict"; - -// 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]/getHSTSPreloadlist.js \ -// [absolute path to]/nsSTSPreloadlist.inc' -// Note: Running this file outputs a new nsSTSPreloadlist.inc in the current -// working directory. - -var Cc = Components.classes; -var Ci = Components.interfaces; -var Cu = Components.utils; -var Cr = Components.results; - -Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource://gre/modules/FileUtils.jsm"); -Cu.import("resource:///modules/XPCOMUtils.jsm"); - -const SOURCE = "https://chromium.googlesource.com/chromium/src/net/+/master/http/transport_security_state_static.json?format=TEXT"; -const TOOL_IDENTIFIER = "UXP HSTS preload list verifier" -const OUTPUT = "nsSTSPreloadList.inc"; -const ERROR_OUTPUT = "nsSTSPreloadList.errors"; -const MINIMUM_REQUIRED_MAX_AGE = 60 * 60 * 24 * 7 * 18; -const MAX_CONCURRENT_REQUESTS = 15; -const MAX_RETRIES = 2; -const REQUEST_TIMEOUT = 10 * 1000; -const ERROR_NONE = "no error"; -const ERROR_CONNECTING_TO_HOST = "could not connect to host"; -const ERROR_NO_HSTS_HEADER = "did not receive HSTS header"; -const ERROR_MAX_AGE_TOO_LOW = "max-age too low: "; -const 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" + -"/* nsSiteSecurityService.cpp, you shouldn't be #including it. */\n" + -"/*****************************************************************************/\n" + -"\n" + -"#include <stdint.h>\n"; -const PREFIX = "\n" + -"class nsSTSPreload\n" + -"{\n" + -" public:\n" + -" const char *mHost;\n" + -" const bool mIncludeSubdomains;\n" + -"};\n" + -"\n" + -"static const nsSTSPreload kSTSPreloadList[] = {\n"; -const POSTFIX = "};\n"; - -function download() { - var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"] - .createInstance(Ci.nsIXMLHttpRequest); - req.open("GET", SOURCE, false); // doing the request synchronously - try { - req.send(); - } - catch (e) { - throw new Error(`ERROR: problem downloading '${SOURCE}': ${e}`); - } - - if (req.status != 200) { - throw new Error("ERROR: problem downloading '" + SOURCE + "': status " + - req.status); - } - - var resultDecoded; - try { - resultDecoded = atob(req.responseText); - } - catch (e) { - throw new Error("ERROR: could not decode data as base64 from '" + SOURCE + - "': " + e); - } - - // we have to filter out '//' comments, while not mangling the json - var result = resultDecoded.replace(/^(\s*)?\/\/[^\n]*\n/mg, ""); - var data = null; - try { - data = JSON.parse(result); - } - catch (e) { - throw new Error(`ERROR: could not parse data from '${SOURCE}': ${e}`); - } - return data; -} - -function getHosts(rawdata) { - var hosts = []; - - if (!rawdata || !rawdata.entries) { - throw new Error("ERROR: source data not formatted correctly: 'entries' " + - "not found"); - } - - for (let entry of rawdata.entries) { - if (entry.mode && entry.mode == "force-https") { - if (entry.name) { - // We trim the entry name here to avoid malformed URI exceptions when we - // later try to connect to the domain. - entry.name = entry.name.trim(); - entry.retries = MAX_RETRIES; - entry.originalIncludeSubdomains = entry.include_subdomains; - hosts.push(entry); - } else { - throw new Error("ERROR: entry not formatted correctly: no name found"); - } - } - } - - return hosts; -} - -var gSSService = Cc["@mozilla.org/ssservice;1"] - .getService(Ci.nsISiteSecurityService); - -function processStsHeader(host, header, status, securityInfo) { - var maxAge = { value: 0 }; - var includeSubdomains = { value: false }; - var error = ERROR_NONE; - if (header != null && securityInfo != null) { - try { - var uri = Services.io.newURI("https://" + host.name, null, null); - var sslStatus = securityInfo.QueryInterface(Ci.nsISSLStatusProvider) - .SSLStatus; - gSSService.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, - uri, header, sslStatus, 0, maxAge, - includeSubdomains); - } - catch (e) { - dump("ERROR: could not process header '" + header + "' from " + - host.name + ": " + e + "\n"); - error = e; - } - } else if (status == 0) { - error = ERROR_CONNECTING_TO_HOST; - } else { - error = ERROR_NO_HSTS_HEADER; - } - - let forceInclude = (host.forceInclude || host.pins == "google"); - - if (error == ERROR_NONE && maxAge.value < MINIMUM_REQUIRED_MAX_AGE) { - error = ERROR_MAX_AGE_TOO_LOW; - } - - return { name: host.name, - maxAge: maxAge.value, - includeSubdomains: includeSubdomains.value, - error: error, - retries: host.retries - 1, - forceInclude: forceInclude, - originalIncludeSubdomains: host.originalIncludeSubdomains }; -} - -// RedirectAndAuthStopper prevents redirects and HTTP authentication -function RedirectAndAuthStopper() {} - -RedirectAndAuthStopper.prototype = { - // nsIChannelEventSink - asyncOnChannelRedirect: function(oldChannel, newChannel, flags, callback) { - throw new Error(Cr.NS_ERROR_ENTITY_CHANGED); - }, - - // nsIAuthPrompt2 - promptAuth: function(channel, level, authInfo) { - return false; - }, - - asyncPromptAuth: function(channel, callback, context, level, authInfo) { - throw new Error(Cr.NS_ERROR_NOT_IMPLEMENTED); - }, - - getInterface: function(iid) { - return this.QueryInterface(iid); - }, - - QueryInterface: XPCOMUtils.generateQI([Ci.nsIChannelEventSink, - Ci.nsIAuthPrompt2]) -}; - -function getHSTSStatus(host, resultList) { - var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"] - .createInstance(Ci.nsIXMLHttpRequest); - var inResultList = false; - var uri = "https://" + host.name + "/"; - req.open("HEAD", uri, true); - req.setRequestHeader("X-Automated-Tool", TOOL_IDENTIFIER); - req.timeout = REQUEST_TIMEOUT; - - let errorhandler = (evt) => { - dump(`ERROR: error making request to ${host.name} (type=${evt.type})\n`); - if (!inResultList) { - inResultList = true; - resultList.push(processStsHeader(host, null, req.status, - req.channel.securityInfo)); - } - }; - req.onerror = errorhandler; - req.ontimeout = errorhandler; - req.onabort = errorhandler; - - req.onload = function(event) { - if (!inResultList) { - inResultList = true; - var header = req.getResponseHeader("strict-transport-security"); - resultList.push(processStsHeader(host, header, req.status, - req.channel.securityInfo)); - } - }; - - try { - req.channel.notificationCallbacks = new RedirectAndAuthStopper(); - req.send(); - } - catch (e) { - dump("ERROR: exception making request to " + host.name + ": " + e + "\n"); - } -} - -function compareHSTSStatus(a, b) { - if (a.name > b.name) { - return 1; - } - if (a.name < b.name) { - return -1; - } - return 0; -} - -function writeTo(string, fos) { - fos.write(string, string.length); -} - -// Determines and returns a string representing a declaration of when this -// preload list should no longer be used. -// This is the current time plus MINIMUM_REQUIRED_MAX_AGE. -function getExpirationTimeString() { - var now = new Date(); - var nowMillis = now.getTime(); - // MINIMUM_REQUIRED_MAX_AGE is in seconds, so convert to milliseconds - var expirationMillis = nowMillis + (MINIMUM_REQUIRED_MAX_AGE * 1000); - var expirationMicros = expirationMillis * 1000; - return "const PRTime gPreloadListExpirationTime = INT64_C(" + expirationMicros + ");\n"; -} - -function errorToString(status) { - return (status.error == ERROR_MAX_AGE_TOO_LOW - ? status.error + status.maxAge - : status.error); -} - -function writeEntry(status, outputStream) { - let incSubdomainsBool = (status.forceInclude && status.error != ERROR_NONE - ? status.originalIncludeSubdomains - : status.includeSubdomains); - let includeSubdomains = (incSubdomainsBool ? "true" : "false"); - writeTo(" { \"" + status.name + "\", " + includeSubdomains + " },\n", - outputStream); -} - -function output(sortedStatuses, currentList) { - try { - var file = FileUtils.getFile("CurWorkD", [OUTPUT]); - var errorFile = FileUtils.getFile("CurWorkD", [ERROR_OUTPUT]); - var fos = FileUtils.openSafeFileOutputStream(file); - var eos = FileUtils.openSafeFileOutputStream(errorFile); - writeTo(HEADER, fos); - writeTo(getExpirationTimeString(), fos); - writeTo(PREFIX, fos); - - for (let status in sortedStatuses) { - // If we've encountered an error for this entry (other than the site not - // sending an HSTS header), be safe and remove it from the list - // (preventing stale entries from accumulating). - if (status.error != ERROR_NONE && - status.error != ERROR_NO_HSTS_HEADER && - status.error != ERROR_MAX_AGE_TOO_LOW && - status.name in currentList) { - dump("INFO: error connecting to or processing " + status.name + " - dropping from list\n"); - writeTo(status.name + ": " + errorToString(status) + "\n", eos); - status.maxAge = 0; - } - } - - // Filter out entries we aren't including. - var includedStatuses = sortedStatuses.filter(function (status) { - if (status.maxAge < MINIMUM_REQUIRED_MAX_AGE && !status.forceInclude) { - dump("INFO: " + status.name + " NOT ON the preload list\n"); - writeTo(status.name + ": " + errorToString(status) + "\n", eos); - return false; - } - - dump("INFO: " + status.name + " ON the preload list\n"); - if (status.forceInclude && status.error != ERROR_NONE) { - writeTo(status.name + ": " + errorToString(status) + " (error " - + "ignored - included regardless)\n", eos); - } - return true; - }); - - for (var status of includedStatuses) { - writeEntry(status, fos); - } - writeTo(POSTFIX, fos); - FileUtils.closeSafeFileOutputStream(fos); - FileUtils.closeSafeFileOutputStream(eos); - } - catch (e) { - dump("ERROR: problem writing output to '" + OUTPUT + "': " + e + "\n"); - } -} - -function shouldRetry(response) { - return (response.error != ERROR_NO_HSTS_HEADER && - response.error != ERROR_MAX_AGE_TOO_LOW && - response.error != ERROR_NONE && response.retries > 0); -} - -function getHSTSStatuses(inHosts, outStatuses) { - var expectedOutputLength = inHosts.length; - var tmpOutput = []; - var procCount = 0; - for (var i = 0; i < MAX_CONCURRENT_REQUESTS && inHosts.length > 0; i++) { - let host = inHosts.shift(); - dump("spinning off request to '" + host.name + "' (remaining retries: " + - host.retries + ")\n"); - getHSTSStatus(host, tmpOutput); - } - - while (outStatuses.length != expectedOutputLength) { - procCount++; - if (procCount % 200 == 0) gc(); - waitForAResponse(tmpOutput); - var response = tmpOutput.shift(); - dump("request to '" + response.name + "' finished\n"); - if (shouldRetry(response)) { - inHosts.push(response); - } else { - outStatuses.push(response); - } - - if (inHosts.length > 0) { - let host = inHosts.shift(); - dump("[" + procCount + "] spinning off request to '" + host.name + "' (remaining retries: " + - host.retries + ")\n"); - getHSTSStatus(host, tmpOutput); - } - } -} - -// Since all events are processed on the main thread, and since event -// handlers are not preemptible, there shouldn't be any concurrency issues. -function waitForAResponse(outputList) { - // From <https://developer.mozilla.org/en/XPConnect/xpcshell/HOWTO> - var threadManager = Cc["@mozilla.org/thread-manager;1"] - .getService(Ci.nsIThreadManager); - var mainThread = threadManager.currentThread; - while (outputList.length == 0) { - mainThread.processNextEvent(true); - } -} - -function readCurrentList(filename) { - var currentHosts = {}; - var file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile); - file.initWithPath(filename); - var fis = Cc["@mozilla.org/network/file-input-stream;1"] - .createInstance(Ci.nsILineInputStream); - fis.init(file, -1, -1, Ci.nsIFileInputStream.CLOSE_ON_EOF); - var line = {}; - var entryRegex = / { "([^"]*)", (true|false) },/; - while (fis.readLine(line)) { - var match = entryRegex.exec(line.value); - if (match) { - currentHosts[match[1]] = (match[2] == "true"); - } - } - return currentHosts; -} - -function combineLists(newHosts, currentHosts) { - let newHostsSet = new Set(); - - for (let newHost of newHosts) { - newHostsSet.add(newHost.name); - } - - for (let currentHost in currentHosts) { - if (!newHostsSet.has(currentHost)) { - newHosts.push({ name: currentHost, retries: MAX_RETRIES }); - } - } -} - -const TEST_ENTRIES = [ - { name: "includesubdomains.preloaded.test", includeSubdomains: true }, - { name: "includesubdomains2.preloaded.test", includeSubdomains: true }, - { name: "noincludesubdomains.preloaded.test", includeSubdomains: false }, -]; - -function deleteTestHosts(currentHosts) { - for (let testEntry of TEST_ENTRIES) { - delete currentHosts[testEntry.name]; - } -} - -function insertTestHosts(hstsStatuses) { - for (let testEntry of TEST_ENTRIES) { - hstsStatuses.push({ - name: testEntry.name, - maxAge: MINIMUM_REQUIRED_MAX_AGE, - includeSubdomains: testEntry.includeSubdomains, - error: ERROR_NONE, - // This deliberately doesn't have a value for `retries` (because we should - // never attempt to connect to this host). - forceInclude: true, - originalIncludeSubdomains: testEntry.includeSubdomains, - }); - } -} - -// **************************************************************************** -// This is where the action happens: -if (arguments.length != 1) { - throw new Error("Usage: getHSTSPreloadList.js " + - "<absolute path to current nsSTSPreloadList.inc>"); -} -// get the current preload list -var currentHosts = readCurrentList(arguments[0]); -// delete any hosts we use in tests so we don't actually connect to them -deleteTestHosts(currentHosts); -// disable the current preload list so it won't interfere with requests we make -Services.prefs.setBoolPref("network.stricttransportsecurity.preloadlist", false); -// download and parse the raw json file from the Chromium source -var rawdata = download(); -// get just the hosts with mode: "force-https" -var hosts = getHosts(rawdata); -// add hosts in the current list to the new list (avoiding duplicates) -combineLists(hosts, currentHosts); -// get the HSTS status of each host -var hstsStatuses = []; -getHSTSStatuses(hosts, hstsStatuses); -// add the hosts we use in tests -insertTestHosts(hstsStatuses); -// sort the hosts alphabetically -hstsStatuses.sort(compareHSTSStatus); -// write the results to a file (this is where we filter out hosts that we -// either couldn't connect to, didn't receive an HSTS header from, couldn't -// parse the header, or had a header with too short a max-age) -output(hstsStatuses, currentHosts); -// **************************************************************************** |