1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 // How to run this file:
7 // 1. [obtain firefox source code]
8 // 2. [build/obtain firefox binaries]
9 // 3. run `[path to]/firefox -xpcshell genRootCAHashes.js [absolute path to]/RootHashes.inc'
11 const nsX509CertDB = "@mozilla.org/security/x509certdb;1";
12 const CertDb = Cc[nsX509CertDB].getService(Ci.nsIX509CertDB);
14 const { FileUtils } = ChromeUtils.importESModule(
15 "resource://gre/modules/FileUtils.sys.mjs"
17 const { NetUtil } = ChromeUtils.importESModule(
18 "resource://gre/modules/NetUtil.sys.mjs"
20 const { CommonUtils } = ChromeUtils.importESModule(
21 "resource://services-common/utils.sys.mjs"
24 const FILENAME_OUTPUT = "RootHashes.inc";
25 const FILENAME_TRUST_ANCHORS = "KnownRootHashes.json";
26 const ROOT_NOT_ASSIGNED = -1;
28 const JSON_HEADER = `// This Source Code Form is subject to the terms of the Mozilla Public
29 // License, v. 2.0. If a copy of the MPL was not distributed with this
30 // file, You can obtain one at http://mozilla.org/MPL/2.0/. */
32 //***************************************************************************
33 // This is an automatically generated file. It's used to maintain state for
34 // runs of genRootCAHashes.js; you should never need to manually edit it
35 //***************************************************************************
38 // binNumber 1 used to be for "GTE_CyberTrust_Global_Root", but that root was
39 // removed from the built-in roots module, so now it is used to indicate that
40 // the certificate is not a built-in and was found in the softoken (cert9.db).
42 // binNumber 2 used to be for "Thawte_Server_CA", but that root was removed from
43 // the built-in roots module, so now it is used to indicate that the certificate
44 // is not a built-in and was found on an external PKCS#11 token.
46 // binNumber 3 used to be for "Thawte_Premium_Server_CA", but that root was
47 // removed from the built-in roots module, so now it is used to indicate that
48 // the certificate is not a built-in and was temporarily imported from the OS as
49 // part of the "Enterprise Roots" feature.
54 "/* This Source Code Form is subject to the terms of the Mozilla Public\n" +
55 " * License, v. 2.0. If a copy of the MPL was not distributed with this\n" +
56 " * file, You can obtain one at http://mozilla.org/MPL/2.0/. */\n" +
58 "/*****************************************************************************/\n" +
59 "/* This is an automatically generated file. If you're not */\n" +
60 "/* RootCertificateTelemetryUtils.cpp, you shouldn't be #including it. */\n" +
61 "/*****************************************************************************/\n" +
63 "#define HASH_LEN 32\n";
66 "struct CertAuthorityHash {\n" +
67 " // See bug 1338873 about making these fields const.\n" +
68 " uint8_t hash[HASH_LEN];\n" +
69 " int32_t binNumber;\n" +
71 "static const struct CertAuthorityHash ROOT_TABLE[] = {\n";
73 const FP_POSTAMBLE = "};\n";
76 function writeString(fos, string) {
77 fos.write(string, string.length);
80 // Remove all colons from a string
81 function stripColons(hexString) {
82 return hexString.replace(/:/g, "");
85 // Expect an array of bytes and make it C-formatted
86 function hexSlice(bytes, start, end) {
88 for (let i = start; i < end; i++) {
89 let hex = (0 + bytes.charCodeAt(i).toString(16)).slice(-2).toUpperCase();
98 function stripComments(buf) {
99 let lines = buf.split("\n");
100 let entryRegex = /^\s*\/\//;
102 for (let i = 0; i < lines.length; i++) {
103 let match = entryRegex.exec(lines[i]);
105 data = data + lines[i];
111 // Load the trust anchors JSON object from disk
112 function loadTrustAnchors(file) {
114 let stream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(
115 Ci.nsIFileInputStream
117 stream.init(file, -1, 0, 0);
118 let buf = NetUtil.readInputStreamToString(stream, stream.available());
119 return JSON.parse(stripComments(buf));
121 // If there's no input file, bootstrap.
122 return { roots: [], maxBin: 0 };
125 // Saves our persistence file so that we don't lose track of the mapping
126 // between bin numbers and the CA-hashes, even as CAs come and go.
127 function writeTrustAnchors(file) {
128 let fos = FileUtils.openSafeFileOutputStream(file);
130 let serializedData = JSON.stringify(gTrustAnchors, null, " ");
131 fos.write(JSON_HEADER, JSON_HEADER.length);
132 fos.write(serializedData, serializedData.length);
134 FileUtils.closeSafeFileOutputStream(fos);
137 // Write the C++ header file
138 function writeRootHashes(fos) {
140 writeString(fos, FILE_HEADER);
142 // Output the sorted gTrustAnchors
143 writeString(fos, FP_PREAMBLE);
144 gTrustAnchors.roots.forEach(function (fp) {
145 let fpBytes = atob(fp.sha256Fingerprint);
147 writeString(fos, " {\n");
148 writeString(fos, " /* " + fp.label + " */\n");
149 writeString(fos, " { " + hexSlice(fpBytes, 0, 16) + ",\n");
150 writeString(fos, " " + hexSlice(fpBytes, 16, 32) + " },\n");
151 writeString(fos, " " + fp.binNumber + " /* Bin Number */\n");
153 writeString(fos, " },\n");
155 writeString(fos, FP_POSTAMBLE);
157 writeString(fos, "\n");
159 dump("ERROR: problem writing output: " + e + "\n");
163 // Scan our list (linearly) for the given fingerprint string
164 function findTrustAnchorByFingerprint(sha256Fingerprint) {
165 for (let i = 0; i < gTrustAnchors.roots.length; i++) {
166 if (sha256Fingerprint == gTrustAnchors.roots[i].sha256Fingerprint) {
170 return ROOT_NOT_ASSIGNED;
173 // Get a clean label for a given certificate; usually the common name.
174 function getLabelForCert(cert) {
175 let label = cert.commonName;
177 if (label.length < 5) {
178 label = cert.subjectName;
181 // replace non-ascii characters
182 label = label.replace(/[^[:ascii:]]/g, "_");
183 // replace non-word characters
184 label = label.replace(/[^A-Za-z0-9]/g, "_");
188 // Fill in the gTrustAnchors list with trust anchors from the database.
189 function insertTrustAnchorsFromDatabase() {
190 // We only want CA certs for SSL
191 const CERT_TYPE = Ci.nsIX509Cert.CA_CERT;
192 const TRUST_TYPE = Ci.nsIX509CertDB.TRUSTED_SSL;
194 // Iterate through the whole Cert DB
195 for (let cert of CertDb.getCerts()) {
196 // Find the certificate in our existing list. Do it here because we need to check if
197 // it's untrusted too.
199 // If this is a trusted cert
200 if (CertDb.isCertTrusted(cert, CERT_TYPE, TRUST_TYPE)) {
201 // Base64 encode the hex string
202 let binaryFingerprint = CommonUtils.hexToBytes(
203 stripColons(cert.sha256Fingerprint)
205 let encodedFingerprint = btoa(binaryFingerprint);
207 // Scan to see if this is already in the database.
209 findTrustAnchorByFingerprint(encodedFingerprint) == ROOT_NOT_ASSIGNED
211 // Let's get a usable name; some old certs do not have CN= filled out
212 let label = getLabelForCert(cert);
215 gTrustAnchors.maxBin += 1;
216 gTrustAnchors.roots.push({
218 binNumber: gTrustAnchors.maxBin,
219 sha256Fingerprint: encodedFingerprint,
230 if (arguments.length != 1) {
232 "Usage: genRootCAHashes.js <absolute path to current RootHashes.inc>"
236 var trustAnchorsFile = new FileUtils.File(
238 Services.dirsvc.get("CurWorkD", Ci.nsIFile).path,
239 FILENAME_TRUST_ANCHORS
242 var rootHashesFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
243 rootHashesFile.initWithPath(arguments[0]);
245 // Open the known hashes file; this is to ensure stable bin numbers.
246 var gTrustAnchors = loadTrustAnchors(trustAnchorsFile);
248 // Collect all certificate entries
249 insertTrustAnchorsFromDatabase();
251 // Update known hashes before we sort
252 writeTrustAnchors(trustAnchorsFile);
254 // Sort all trust anchors before writing, as AccumulateRootCA.cpp
255 // will perform binary searches
256 gTrustAnchors.roots.sort(function (a, b) {
257 // We need to work from the binary values, not the base64 values.
258 let aBin = atob(a.sha256Fingerprint);
259 let bBin = atob(b.sha256Fingerprint);
270 // Write the output file.
271 var rootHashesFileOutputStream =
272 FileUtils.openSafeFileOutputStream(rootHashesFile);
273 writeRootHashes(rootHashesFileOutputStream);
274 FileUtils.closeSafeFileOutputStream(rootHashesFileOutputStream);