summaryrefslogtreecommitdiffstats
path: root/security/manager/tools/makeCNNICHashes.js
diff options
context:
space:
mode:
Diffstat (limited to 'security/manager/tools/makeCNNICHashes.js')
-rw-r--r--security/manager/tools/makeCNNICHashes.js282
1 files changed, 282 insertions, 0 deletions
diff --git a/security/manager/tools/makeCNNICHashes.js b/security/manager/tools/makeCNNICHashes.js
new file mode 100644
index 000000000..1b558949e
--- /dev/null
+++ b/security/manager/tools/makeCNNICHashes.js
@@ -0,0 +1,282 @@
+/* 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 CNNIC-issued certificates to be whitelisted]
+// 2. [obtain firefox source code]
+// 3. [build/obtain firefox binaries]
+// 4. run `[path to]/run-mozilla.sh [path to]/xpcshell makeCNNICHashes.js \
+// [path to]/intermediatesFile
+// [path to]/certlist'
+// Where |intermediatesFile| is a file containing PEM encoded intermediate
+// certificates that the certificates in |certlist| may be issued by.
+// where certlist is a file containing a list of paths to certificates to
+// be included in the whitelist
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+
+var gCertDB = Cc["@mozilla.org/security/x509certdb;1"]
+ .getService(Ci.nsIX509CertDB);
+
+var { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {});
+var { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
+
+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 file was automatically generated by makeCNNICHashes.js. It shouldn't\n" +
+"// need to be manually edited.\n" +
+"//***************************************************************************\n" +
+"\n";
+
+const PREAMBLE = "#define CNNIC_WHITELIST_HASH_LEN 32\n\n" +
+"struct WhitelistedCNNICHash {\n" +
+" const uint8_t hash[CNNIC_WHITELIST_HASH_LEN];\n" +
+"};\n\n" +
+"static const struct WhitelistedCNNICHash WhitelistedCNNICHashes[] = {\n";
+
+const POSTAMBLE = "};\n";
+
+function writeString(fos, string) {
+ fos.write(string, string.length);
+}
+
+// fingerprint is in the form "00:11:22:..."
+function hexSlice(fingerprint, start, end) {
+ let hexBytes = fingerprint.split(":");
+ let ret = "";
+ for (let i = start; i < end; i++) {
+ let hex = hexBytes[i];
+ ret += "0x" + hex;
+ if (i < end - 1) {
+ ret += ", ";
+ }
+ }
+ return ret;
+}
+
+// Write the C++ header file
+function writeHashes(certs, lastValidTime, fos) {
+ writeString(fos, HEADER);
+ writeString(fos, `// This file may be removed after ${new Date(lastValidTime)}\n\n`);
+ writeString(fos, PREAMBLE);
+
+ certs.forEach(function(cert) {
+ writeString(fos, " {\n");
+ writeString(fos, " { " + hexSlice(cert.sha256Fingerprint, 0, 16) + ",\n");
+ writeString(fos, " " + hexSlice(cert.sha256Fingerprint, 16, 32) + " },\n");
+
+ writeString(fos, " },\n");
+ });
+ writeString(fos, POSTAMBLE);
+}
+
+function readFileContents(file) {
+ let fstream = Cc["@mozilla.org/network/file-input-stream;1"]
+ .createInstance(Ci.nsIFileInputStream);
+ fstream.init(file, -1, 0, 0);
+ let data = NetUtil.readInputStreamToString(fstream, fstream.available());
+ fstream.close();
+ return data;
+}
+
+function relativePathToFile(path) {
+ let currentDirectory = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties)
+ .get("CurWorkD", Ci.nsILocalFile);
+ let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
+ file.initWithPath(currentDirectory.path + "/" + path);
+ return file;
+}
+
+function pathToFile(path) {
+ let file = relativePathToFile(path);
+ if (!file.exists()) {
+ // Fall back to trying absolute path
+ file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
+ file.initWithPath(path);
+ }
+ return file;
+}
+
+// punt on dealing with leap-years
+const sixYearsInMilliseconds = 6 * 366 * 24 * 60 * 60 * 1000;
+
+function loadCertificates(certFile, currentWhitelist) {
+ let nowInMilliseconds = (new Date()).getTime();
+ // months are 0-indexed, so April is month 3 :(
+ let april1InMilliseconds = (new Date(2015, 3, 1)).getTime();
+ let latestNotAfter = nowInMilliseconds;
+ let certs = [];
+ let certMap = {};
+ let invalidCerts = [];
+ let paths = readFileContents(certFile).split("\n");
+ for (let path of paths) {
+ if (!path) {
+ continue;
+ }
+ let certData = readFileContents(pathToFile(path));
+ let cert = null;
+ try {
+ cert = gCertDB.constructX509FromBase64(certData);
+ } catch (e) {}
+ if (!cert) {
+ cert = gCertDB.constructX509(certData, certData.length);
+ }
+ // Don't add multiple copies of any particular certificate.
+ if (cert.sha256Fingerprint in certMap) {
+ continue;
+ }
+ certMap[cert.sha256Fingerprint] = true;
+ // If we can't verify the certificate, don't include it. Unfortunately, if
+ // a CNNIC-issued certificate wasn't previously on the whitelist but it
+ // otherwise verifies successfully, verifyCertNow will return
+ // SEC_ERROR_REVOKED_CERTIFICATE, so we count that as verifying
+ // successfully. If the certificate is later revoked by CNNIC, the user
+ // will see that when they attempt to connect to a site using it and we do
+ // normal revocation checking.
+ let errorCode = gCertDB.verifyCertNow(cert, 2 /* SSL Server */,
+ Ci.nsIX509CertDB.LOCAL_ONLY, null,
+ {}, {});
+ if (errorCode != 0 &&
+ errorCode != -8180 /* SEC_ERROR_REVOKED_CERTIFICATE */) {
+ continue;
+ }
+ let durationMilliseconds = (cert.validity.notAfter - cert.validity.notBefore) / 1000;
+ let notBeforeMilliseconds = cert.validity.notBefore / 1000;
+ let notAfterMilliseconds = cert.validity.notAfter / 1000;
+ // Only consider certificates that were issued before 1 April 2015, haven't
+ // expired, and have a validity period shorter than 6 years (there is a
+ // delegated OCSP responder certificate with a validity period of 6 years
+ // that should be on the whitelist).
+ // Also only consider certificates that were already on the whitelist.
+ if (notBeforeMilliseconds < april1InMilliseconds &&
+ notAfterMilliseconds > nowInMilliseconds &&
+ durationMilliseconds < sixYearsInMilliseconds &&
+ currentWhitelist[cert.sha256Fingerprint]) {
+ certs.push(cert);
+ if (notAfterMilliseconds > latestNotAfter) {
+ latestNotAfter = notAfterMilliseconds;
+ }
+ }
+ if (durationMilliseconds >= sixYearsInMilliseconds) {
+ invalidCerts.push(cert);
+ }
+ }
+ return { certs: certs,
+ lastValidTime: latestNotAfter,
+ invalidCerts: invalidCerts };
+}
+
+// Expects something like "00:11:22:...", returns a string of bytes.
+function hexToBinaryString(hexString) {
+ let hexBytes = hexString.split(":");
+ let result = "";
+ for (let hexByte of hexBytes) {
+ result += String.fromCharCode(parseInt(hexByte, 16));
+ }
+ return result;
+}
+
+function compareCertificatesByHash(certA, certB) {
+ let aBin = hexToBinaryString(certA.sha256Fingerprint);
+ let bBin = hexToBinaryString(certB.sha256Fingerprint);
+
+ if (aBin < bBin) {
+ return -1;
+ }
+ if (aBin > bBin) {
+ return 1;
+ }
+ return 0;
+}
+
+function certToPEM(cert) {
+ let der = cert.getRawDER({});
+ let derString = '';
+ for (let i = 0; i < der.length; i++) {
+ derString += String.fromCharCode(der[i]);
+ }
+ let base64Lines = btoa(derString).replace(/(.{64})/g, "$1\n");
+ let output = "-----BEGIN CERTIFICATE-----\n";
+ for (let line of base64Lines.split("\n")) {
+ if (line.length > 0) {
+ output += line + "\n";
+ }
+ }
+ output += "-----END CERTIFICATE-----";
+ return output;
+}
+
+function loadIntermediates(intermediatesFile) {
+ let pem = readFileContents(intermediatesFile);
+ let intermediates = [];
+ let currentPEM = "";
+ for (let line of pem.split("\r\n")) {
+ if (line == "-----END CERTIFICATE-----") {
+ if (currentPEM) {
+ intermediates.push(gCertDB.constructX509FromBase64(currentPEM));
+ }
+ currentPEM = "";
+ continue;
+ }
+ if (line != "-----BEGIN CERTIFICATE-----") {
+ currentPEM += line;
+ }
+ }
+ return intermediates;
+}
+
+function readCurrentWhitelist(currentWhitelistFile) {
+ let contents = readFileContents(currentWhitelistFile).replace(/[\r\n ]/g, "");
+ let split = contents.split(/((?:0x[0-9A-F][0-9A-F],){31}0x[0-9A-F][0-9A-F])/);
+ // The hashes will be every odd-indexed element of the array.
+ let currentWhitelist = {};
+ for (let i = 1; i < split.length && i < split.length - 1; i += 2) {
+ let hash = split[i].replace(/0x/g, "").replace(/,/g, ":");
+ currentWhitelist[hash] = true;
+ }
+ return currentWhitelist;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+if (arguments.length != 3) {
+ throw new Error("Usage: makeCNNICHashes.js <PEM intermediates file> " +
+ "<path to list of certificates> <path to current whitelist file>");
+}
+
+Services.prefs.setIntPref("security.OCSP.enabled", 0);
+var intermediatesFile = pathToFile(arguments[0]);
+var intermediates = loadIntermediates(intermediatesFile);
+var certFile = pathToFile(arguments[1]);
+var currentWhitelistFile = pathToFile(arguments[2]);
+var currentWhitelist = readCurrentWhitelist(currentWhitelistFile);
+var { certs, lastValidTime, invalidCerts } = loadCertificates(certFile, currentWhitelist);
+
+dump("The following certificates were not included due to overlong validity periods:\n");
+for (let cert of invalidCerts) {
+ dump(certToPEM(cert) + "\n");
+}
+
+// Sort the key hashes to allow for binary search.
+certs.sort(compareCertificatesByHash);
+
+// Write the output file.
+var outFile = relativePathToFile("CNNICHashWhitelist.inc");
+if (!outFile.exists()) {
+ outFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644);
+}
+var outStream = Cc["@mozilla.org/network/file-output-stream;1"]
+ .createInstance(Ci.nsIFileOutputStream);
+outStream.init(outFile, -1, 0, 0);
+writeHashes(certs, lastValidTime, outStream);
+outStream.close();