Bug 1869043 allow a device to be specified with MediaTrackGraph::NotifyWhenDeviceStar...
[gecko.git] / security / manager / tools / genRootCAHashes.js
blobbe0233a0c09479daab139e97270f23306df8c70c
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/. */
4 "use strict";
6 // How to run this file:
7 // 1. [obtain firefox source code]
8 // 2. [build/obtain firefox binaries]
9 // 3. run `[path to]/run-mozilla.sh [path to]/xpcshell genRootCAHashes.js \
10 //                                  [absolute path to]/RootHashes.inc'
12 const nsX509CertDB = "@mozilla.org/security/x509certdb;1";
13 const CertDb = Cc[nsX509CertDB].getService(Ci.nsIX509CertDB);
15 const { FileUtils } = ChromeUtils.importESModule(
16   "resource://gre/modules/FileUtils.sys.mjs"
18 const { NetUtil } = ChromeUtils.importESModule(
19   "resource://gre/modules/NetUtil.sys.mjs"
21 const { CommonUtils } = ChromeUtils.importESModule(
22   "resource://services-common/utils.sys.mjs"
25 const FILENAME_OUTPUT = "RootHashes.inc";
26 const FILENAME_TRUST_ANCHORS = "KnownRootHashes.json";
27 const ROOT_NOT_ASSIGNED = -1;
29 const JSON_HEADER = `// This Source Code Form is subject to the terms of the Mozilla Public
30 // License, v. 2.0. If a copy of the MPL was not distributed with this
31 // file, You can obtain one at http://mozilla.org/MPL/2.0/. */
33 //***************************************************************************
34 // This is an automatically generated file. It's used to maintain state for
35 // runs of genRootCAHashes.js; you should never need to manually edit it
36 //***************************************************************************
38 // Notes:
39 // binNumber 1 used to be for "GTE_CyberTrust_Global_Root", but that root was
40 // removed from the built-in roots module, so now it is used to indicate that
41 // the certificate is not a built-in and was found in the softoken (cert9.db).
43 // binNumber 2 used to be for "Thawte_Server_CA", but that root was removed from
44 // the built-in roots module, so now it is used to indicate that the certificate
45 // is not a built-in and was found on an external PKCS#11 token.
47 // binNumber 3 used to be for "Thawte_Premium_Server_CA", but that root was
48 // removed from the built-in roots module, so now it is used to indicate that
49 // the certificate is not a built-in and was temporarily imported from the OS as
50 // part of the "Enterprise Roots" feature.
54 const FILE_HEADER =
55   "/* This Source Code Form is subject to the terms of the Mozilla Public\n" +
56   " * License, v. 2.0. If a copy of the MPL was not distributed with this\n" +
57   " * file, You can obtain one at http://mozilla.org/MPL/2.0/. */\n" +
58   "\n" +
59   "/*****************************************************************************/\n" +
60   "/* This is an automatically generated file. If you're not                    */\n" +
61   "/* RootCertificateTelemetryUtils.cpp, you shouldn't be #including it.        */\n" +
62   "/*****************************************************************************/\n" +
63   "\n" +
64   "#define HASH_LEN 32\n";
66 const FP_PREAMBLE =
67   "struct CertAuthorityHash {\n" +
68   "  // See bug 1338873 about making these fields const.\n" +
69   "  uint8_t hash[HASH_LEN];\n" +
70   "  int32_t binNumber;\n" +
71   "};\n\n" +
72   "static const struct CertAuthorityHash ROOT_TABLE[] = {\n";
74 const FP_POSTAMBLE = "};\n";
76 // Helper
77 function writeString(fos, string) {
78   fos.write(string, string.length);
81 // Remove all colons from a string
82 function stripColons(hexString) {
83   return hexString.replace(/:/g, "");
86 // Expect an array of bytes and make it C-formatted
87 function hexSlice(bytes, start, end) {
88   let ret = "";
89   for (let i = start; i < end; i++) {
90     let hex = (0 + bytes.charCodeAt(i).toString(16)).slice(-2).toUpperCase();
91     ret += "0x" + hex;
92     if (i < end - 1) {
93       ret += ", ";
94     }
95   }
96   return ret;
99 function stripComments(buf) {
100   let lines = buf.split("\n");
101   let entryRegex = /^\s*\/\//;
102   let data = "";
103   for (let i = 0; i < lines.length; i++) {
104     let match = entryRegex.exec(lines[i]);
105     if (!match) {
106       data = data + lines[i];
107     }
108   }
109   return data;
112 // Load the trust anchors JSON object from disk
113 function loadTrustAnchors(file) {
114   if (file.exists()) {
115     let stream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(
116       Ci.nsIFileInputStream
117     );
118     stream.init(file, -1, 0, 0);
119     let buf = NetUtil.readInputStreamToString(stream, stream.available());
120     return JSON.parse(stripComments(buf));
121   }
122   // If there's no input file, bootstrap.
123   return { roots: [], maxBin: 0 };
126 // Saves our persistence file so that we don't lose track of the mapping
127 // between bin numbers and the CA-hashes, even as CAs come and go.
128 function writeTrustAnchors(file) {
129   let fos = FileUtils.openSafeFileOutputStream(file);
131   let serializedData = JSON.stringify(gTrustAnchors, null, "  ");
132   fos.write(JSON_HEADER, JSON_HEADER.length);
133   fos.write(serializedData, serializedData.length);
135   FileUtils.closeSafeFileOutputStream(fos);
138 // Write the C++ header file
139 function writeRootHashes(fos) {
140   try {
141     writeString(fos, FILE_HEADER);
143     // Output the sorted gTrustAnchors
144     writeString(fos, FP_PREAMBLE);
145     gTrustAnchors.roots.forEach(function (fp) {
146       let fpBytes = atob(fp.sha256Fingerprint);
148       writeString(fos, "  {\n");
149       writeString(fos, "    /* " + fp.label + " */\n");
150       writeString(fos, "    { " + hexSlice(fpBytes, 0, 16) + ",\n");
151       writeString(fos, "      " + hexSlice(fpBytes, 16, 32) + " },\n");
152       writeString(fos, "      " + fp.binNumber + " /* Bin Number */\n");
154       writeString(fos, "  },\n");
155     });
156     writeString(fos, FP_POSTAMBLE);
158     writeString(fos, "\n");
159   } catch (e) {
160     dump("ERROR: problem writing output: " + e + "\n");
161   }
164 // Scan our list (linearly) for the given fingerprint string
165 function findTrustAnchorByFingerprint(sha256Fingerprint) {
166   for (let i = 0; i < gTrustAnchors.roots.length; i++) {
167     if (sha256Fingerprint == gTrustAnchors.roots[i].sha256Fingerprint) {
168       return i;
169     }
170   }
171   return ROOT_NOT_ASSIGNED;
174 // Get a clean label for a given certificate; usually the common name.
175 function getLabelForCert(cert) {
176   let label = cert.commonName;
178   if (label.length < 5) {
179     label = cert.subjectName;
180   }
182   // replace non-ascii characters
183   label = label.replace(/[^[:ascii:]]/g, "_");
184   // replace non-word characters
185   label = label.replace(/[^A-Za-z0-9]/g, "_");
186   return label;
189 // Fill in the gTrustAnchors list with trust anchors from the database.
190 function insertTrustAnchorsFromDatabase() {
191   // We only want CA certs for SSL
192   const CERT_TYPE = Ci.nsIX509Cert.CA_CERT;
193   const TRUST_TYPE = Ci.nsIX509CertDB.TRUSTED_SSL;
195   // Iterate through the whole Cert DB
196   for (let cert of CertDb.getCerts()) {
197     // Find the certificate in our existing list. Do it here because we need to check if
198     // it's untrusted too.
200     // If this is a trusted cert
201     if (CertDb.isCertTrusted(cert, CERT_TYPE, TRUST_TYPE)) {
202       // Base64 encode the hex string
203       let binaryFingerprint = CommonUtils.hexToBytes(
204         stripColons(cert.sha256Fingerprint)
205       );
206       let encodedFingerprint = btoa(binaryFingerprint);
208       // Scan to see if this is already in the database.
209       if (
210         findTrustAnchorByFingerprint(encodedFingerprint) == ROOT_NOT_ASSIGNED
211       ) {
212         // Let's get a usable name; some old certs do not have CN= filled out
213         let label = getLabelForCert(cert);
215         // Add to list
216         gTrustAnchors.maxBin += 1;
217         gTrustAnchors.roots.push({
218           label,
219           binNumber: gTrustAnchors.maxBin,
220           sha256Fingerprint: encodedFingerprint,
221         });
222       }
223     }
224   }
228 //  PRIMARY LOGIC
231 if (arguments.length != 1) {
232   throw new Error(
233     "Usage: genRootCAHashes.js <absolute path to current RootHashes.inc>"
234   );
237 var trustAnchorsFile = new FileUtils.File(
238   PathUtils.join(
239     Services.dirsvc.get("CurWorkD", Ci.nsIFile).path,
240     FILENAME_TRUST_ANCHORS
241   )
243 var rootHashesFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
244 rootHashesFile.initWithPath(arguments[0]);
246 // Open the known hashes file; this is to ensure stable bin numbers.
247 var gTrustAnchors = loadTrustAnchors(trustAnchorsFile);
249 // Collect all certificate entries
250 insertTrustAnchorsFromDatabase();
252 // Update known hashes before we sort
253 writeTrustAnchors(trustAnchorsFile);
255 // Sort all trust anchors before writing, as AccumulateRootCA.cpp
256 // will perform binary searches
257 gTrustAnchors.roots.sort(function (a, b) {
258   // We need to work from the binary values, not the base64 values.
259   let aBin = atob(a.sha256Fingerprint);
260   let bBin = atob(b.sha256Fingerprint);
262   if (aBin < bBin) {
263     return -1;
264   }
265   if (aBin > bBin) {
266     return 1;
267   }
268   return 0;
271 // Write the output file.
272 var rootHashesFileOutputStream =
273   FileUtils.openSafeFileOutputStream(rootHashesFile);
274 writeRootHashes(rootHashesFileOutputStream);
275 FileUtils.closeSafeFileOutputStream(rootHashesFileOutputStream);