Bug 1518618 - Add custom classes to the selectors for matches, attributes and pseudoc...
[gecko.git] / toolkit / modules / CertUtils.jsm
blob8f0741660401286d9965b6946cb64f6bd8a97a16
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 var EXPORTED_SYMBOLS = ["CertUtils"];
7 const Ce = Components.Exception;
9 ChromeUtils.import("resource://gre/modules/Services.jsm");
11 /**
12  * Reads a set of expected certificate attributes from preferences. The returned
13  * array can be passed to validateCert or checkCert to validate that a
14  * certificate matches the expected attributes. The preferences should look like
15  * this:
16  *   prefix.1.attribute1
17  *   prefix.1.attribute2
18  *   prefix.2.attribute1
19  *   etc.
20  * Each numeric branch contains a set of required attributes for a single
21  * certificate. Having multiple numeric branches means that multiple
22  * certificates would be accepted by validateCert.
23  *
24  * @param  aPrefBranch
25  *         The prefix for all preferences, should end with a ".".
26  * @return An array of JS objects with names / values corresponding to the
27  *         expected certificate's attribute names / values.
28  */
29 function readCertPrefs(aPrefBranch) {
30   if (Services.prefs.getBranch(aPrefBranch).getChildList("").length == 0)
31     return null;
33   let certs = [];
34   let counter = 1;
35   while (true) {
36     let prefBranchCert = Services.prefs.getBranch(aPrefBranch + counter + ".");
37     let prefCertAttrs = prefBranchCert.getChildList("");
38     if (prefCertAttrs.length == 0)
39       break;
41     let certAttrs = {};
42     for (let prefCertAttr of prefCertAttrs)
43       certAttrs[prefCertAttr] = prefBranchCert.getCharPref(prefCertAttr);
45     certs.push(certAttrs);
46     counter++;
47   }
49   return certs;
52 /**
53  * Verifies that an nsIX509Cert matches the expected certificate attribute
54  * values.
55  *
56  * @param  aCertificate
57  *         The nsIX509Cert to compare to the expected attributes.
58  * @param  aCerts
59  *         An array of JS objects with names / values corresponding to the
60  *         expected certificate's attribute names / values. If this is null or
61  *         an empty array then no checks are performed.
62  * @throws NS_ERROR_ILLEGAL_VALUE if a certificate attribute name from the
63  *         aCerts param does not exist or the value for a certificate attribute
64  *         from the aCerts param is different than the expected value or
65  *         aCertificate wasn't specified and aCerts is not null or an empty
66  *         array.
67  */
68 function validateCert(aCertificate, aCerts) {
69   // If there are no certificate requirements then just exit
70   if (!aCerts || aCerts.length == 0)
71     return;
73   if (!aCertificate) {
74     const missingCertErr = "A required certificate was not present.";
75     Cu.reportError(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("Expected attribute '" + name + "' not present in " +
87                     "certificate.");
88         break;
89       }
90       if (aCertificate[name] != certAttrs[name]) {
91         error = true;
92         errors.push("Expected certificate attribute '" + name + "' " +
93                     "value incorrect, expected: '" + certAttrs[name] +
94                     "', got: '" + aCertificate[name] + "'.");
95         break;
96       }
97     }
99     if (!error)
100       break;
101   }
103   if (error) {
104     errors.forEach(Cu.reportError.bind(Cu));
105     const certCheckErr = "Certificate checks failed. See previous errors " +
106                          "for details.";
107     Cu.reportError(certCheckErr);
108     throw new Ce(certCheckErr, Cr.NS_ERROR_ILLEGAL_VALUE);
109   }
113  * Checks if the connection must be HTTPS and if so, only allows built-in
114  * certificates and validates application specified certificate attribute
115  * values.
116  * See bug 340198 and bug 544442.
118  * @param  aChannel
119  *         The nsIChannel that will have its certificate checked.
120  * @param  aAllowNonBuiltInCerts (optional)
121  *         When true certificates that aren't builtin are allowed. When false
122  *         or not specified the certificate must be a builtin certificate.
123  * @param  aCerts (optional)
124  *         An array of JS objects with names / values corresponding to the
125  *         channel's expected certificate's attribute names / values. If it
126  *         isn't null or not specified the the scheme for the channel's
127  *         originalURI must be https.
128  * @throws NS_ERROR_UNEXPECTED if a certificate is expected and the URI scheme
129  *         is not https.
130  *         NS_ERROR_ILLEGAL_VALUE if a certificate attribute name from the
131  *         aCerts param does not exist or the value for a certificate attribute
132  *         from the aCerts  param is different than the expected value.
133  *         NS_ERROR_ABORT if the certificate issuer is not built-in.
134  */
135 function checkCert(aChannel, aAllowNonBuiltInCerts, aCerts) {
136   if (!aChannel.originalURI.schemeIs("https")) {
137     // Require https if there are certificate values to verify
138     if (aCerts) {
139       throw new Ce("SSL is required and URI scheme is not https.",
140                    Cr.NS_ERROR_UNEXPECTED);
141     }
142     return;
143   }
145   let secInfo = aChannel.securityInfo.QueryInterface(Ci.nsITransportSecurityInfo);
146   let cert = secInfo.serverCert;
148   validateCert(cert, aCerts);
150   if (aAllowNonBuiltInCerts === true) {
151     return;
152   }
154   let issuerCert = null;
155   for (issuerCert of secInfo.succeededCertChain.getEnumerator());
157   const certNotBuiltInErr = "Certificate issuer is not built-in.";
158   if (!issuerCert) {
159     throw new Ce(certNotBuiltInErr, Cr.NS_ERROR_ABORT);
160   }
162   if (!issuerCert.isBuiltInRoot) {
163     throw new Ce(certNotBuiltInErr, Cr.NS_ERROR_ABORT);
164   }
168  * This class implements nsIBadCertListener.  Its job is to prevent "bad cert"
169  * security dialogs from being shown to the user.  It is better to simply fail
170  * if the certificate is bad. See bug 304286.
172  * @param  aAllowNonBuiltInCerts (optional)
173  *         When true certificates that aren't builtin are allowed. When false
174  *         or not specified the certificate must be a builtin certificate.
175  */
176 function BadCertHandler(aAllowNonBuiltInCerts) {
177   this.allowNonBuiltInCerts = aAllowNonBuiltInCerts;
179 BadCertHandler.prototype = {
181   // nsIChannelEventSink
182   asyncOnChannelRedirect(oldChannel, newChannel, flags, callback) {
183     if (this.allowNonBuiltInCerts) {
184       callback.onRedirectVerifyCallback(Cr.NS_OK);
185       return;
186     }
188     // make sure the certificate of the old channel checks out before we follow
189     // a redirect from it.  See bug 340198.
190     // Don't call checkCert for internal redirects. See bug 569648.
191     if (!(flags & Ci.nsIChannelEventSink.REDIRECT_INTERNAL))
192       checkCert(oldChannel);
194     callback.onRedirectVerifyCallback(Cr.NS_OK);
195   },
197   // nsIInterfaceRequestor
198   getInterface(iid) {
199     return this.QueryInterface(iid);
200   },
202   // nsISupports
203   QueryInterface: ChromeUtils.generateQI(["nsIChannelEventSink",
204                                           "nsIInterfaceRequestor"]),
207 var CertUtils = {
208   BadCertHandler,
209   checkCert,
210   readCertPrefs,
211   validateCert,