Bug 1875768 - Call the appropriate postfork handler on MacOS r=glandium
[gecko.git] / toolkit / modules / CertUtils.sys.mjs
blob2fc3b5cb503fa391dabf798c5c1685df78b4239f
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 const Ce = Components.Exception;
7 /**
8  * Reads a set of expected certificate attributes from preferences. The returned
9  * array can be passed to validateCert or checkCert to validate that a
10  * certificate matches the expected attributes. The preferences should look like
11  * this:
12  *   prefix.1.attribute1
13  *   prefix.1.attribute2
14  *   prefix.2.attribute1
15  *   etc.
16  * Each numeric branch contains a set of required attributes for a single
17  * certificate. Having multiple numeric branches means that multiple
18  * certificates would be accepted by validateCert.
19  *
20  * @param  aPrefBranch
21  *         The prefix for all preferences, should end with a ".".
22  * @return An array of JS objects with names / values corresponding to the
23  *         expected certificate's attribute names / values.
24  */
25 function readCertPrefs(aPrefBranch) {
26   if (!Services.prefs.getBranch(aPrefBranch).getChildList("").length) {
27     return null;
28   }
30   let certs = [];
31   let counter = 1;
32   while (true) {
33     let prefBranchCert = Services.prefs.getBranch(aPrefBranch + counter + ".");
34     let prefCertAttrs = prefBranchCert.getChildList("");
35     if (!prefCertAttrs.length) {
36       break;
37     }
39     let certAttrs = {};
40     for (let prefCertAttr of prefCertAttrs) {
41       certAttrs[prefCertAttr] = prefBranchCert.getCharPref(prefCertAttr);
42     }
44     certs.push(certAttrs);
45     counter++;
46   }
48   return certs;
51 /**
52  * Verifies that an nsIX509Cert matches the expected certificate attribute
53  * values.
54  *
55  * @param  aCertificate
56  *         The nsIX509Cert to compare to the expected attributes.
57  * @param  aCerts
58  *         An array of JS objects with names / values corresponding to the
59  *         expected certificate's attribute names / values. If this is null or
60  *         an empty array then no checks are performed.
61  * @throws NS_ERROR_ILLEGAL_VALUE if a certificate attribute name from the
62  *         aCerts param does not exist or the value for a certificate attribute
63  *         from the aCerts param is different than the expected value or
64  *         aCertificate wasn't specified and aCerts is not null or an empty
65  *         array.
66  */
67 function validateCert(aCertificate, aCerts) {
68   // If there are no certificate requirements then just exit
69   if (!aCerts || !aCerts.length) {
70     return;
71   }
73   if (!aCertificate) {
74     const missingCertErr = "A required certificate was not present.";
75     console.error(missingCertErr);
76     throw new Ce(missingCertErr, Cr.NS_ERROR_ILLEGAL_VALUE);
77   }
79   var errors = [];
80   for (var i = 0; i < aCerts.length; ++i) {
81     var error = false;
82     var certAttrs = aCerts[i];
83     for (var name in certAttrs) {
84       if (!(name in aCertificate)) {
85         error = true;
86         errors.push(
87           "Expected attribute '" + name + "' not present in certificate."
88         );
89         break;
90       }
91       if (aCertificate[name] != certAttrs[name]) {
92         error = true;
93         errors.push(
94           "Expected certificate attribute '" +
95             name +
96             "' " +
97             "value incorrect, expected: '" +
98             certAttrs[name] +
99             "', got: '" +
100             aCertificate[name] +
101             "'."
102         );
103         break;
104       }
105     }
107     if (!error) {
108       break;
109     }
110   }
112   if (error) {
113     errors.forEach(Cu.reportError.bind(Cu));
114     const certCheckErr =
115       "Certificate checks failed. See previous errors for details.";
116     console.error(certCheckErr);
117     throw new Ce(certCheckErr, Cr.NS_ERROR_ILLEGAL_VALUE);
118   }
122  * Checks if the connection must be HTTPS and if so, only allows built-in
123  * certificates and validates application specified certificate attribute
124  * values.
125  * See bug 340198 and bug 544442.
127  * @param  aChannel
128  *         The nsIChannel that will have its certificate checked.
129  * @param  aAllowNonBuiltInCerts (optional)
130  *         When true certificates that aren't builtin are allowed. When false
131  *         or not specified the certificate must be a builtin certificate.
132  * @param  aCerts (optional)
133  *         An array of JS objects with names / values corresponding to the
134  *         channel's expected certificate's attribute names / values. If it
135  *         isn't null or not specified the the scheme for the channel's
136  *         originalURI must be https.
137  * @throws NS_ERROR_UNEXPECTED if a certificate is expected and the URI scheme
138  *         is not https.
139  *         NS_ERROR_ILLEGAL_VALUE if a certificate attribute name from the
140  *         aCerts param does not exist or the value for a certificate attribute
141  *         from the aCerts  param is different than the expected value.
142  *         NS_ERROR_ABORT if the certificate issuer is not built-in.
143  */
144 function checkCert(aChannel, aAllowNonBuiltInCerts, aCerts) {
145   if (!aChannel.originalURI.schemeIs("https")) {
146     // Require https if there are certificate values to verify
147     if (aCerts) {
148       throw new Ce(
149         "SSL is required and URI scheme is not https.",
150         Cr.NS_ERROR_UNEXPECTED
151       );
152     }
153     return;
154   }
156   let secInfo = aChannel.securityInfo;
157   let cert = secInfo.serverCert;
159   validateCert(cert, aCerts);
161   if (aAllowNonBuiltInCerts === true) {
162     return;
163   }
164   const certNotBuiltInErr = "Certificate issuer is not built-in.";
165   if (!secInfo.isBuiltCertChainRootBuiltInRoot) {
166     throw new Ce(certNotBuiltInErr, Cr.NS_ERROR_ABORT);
167   }
171  * This class implements nsIChannelEventSink. Its job is to perform extra checks
172  * on the certificates used for some connections when those connections
173  * redirect.
175  * @param  aAllowNonBuiltInCerts (optional)
176  *         When true certificates that aren't builtin are allowed. When false
177  *         or not specified the certificate must be a builtin certificate.
178  */
179 function BadCertHandler(aAllowNonBuiltInCerts) {
180   this.allowNonBuiltInCerts = aAllowNonBuiltInCerts;
182 BadCertHandler.prototype = {
183   // nsIChannelEventSink
184   asyncOnChannelRedirect(oldChannel, newChannel, flags, callback) {
185     if (this.allowNonBuiltInCerts) {
186       callback.onRedirectVerifyCallback(Cr.NS_OK);
187       return;
188     }
190     // make sure the certificate of the old channel checks out before we follow
191     // a redirect from it.  See bug 340198.
192     // Don't call checkCert for internal redirects. See bug 569648.
193     if (!(flags & Ci.nsIChannelEventSink.REDIRECT_INTERNAL)) {
194       checkCert(oldChannel);
195     }
197     callback.onRedirectVerifyCallback(Cr.NS_OK);
198   },
200   // nsIInterfaceRequestor
201   getInterface(iid) {
202     return this.QueryInterface(iid);
203   },
205   // nsISupports
206   QueryInterface: ChromeUtils.generateQI([
207     "nsIChannelEventSink",
208     "nsIInterfaceRequestor",
209   ]),
212 export var CertUtils = {
213   BadCertHandler,
214   checkCert,
215   readCertPrefs,
216   validateCert,