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");
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
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.
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.
29 function readCertPrefs(aPrefBranch) {
30 if (Services.prefs.getBranch(aPrefBranch).getChildList("").length == 0)
36 let prefBranchCert = Services.prefs.getBranch(aPrefBranch + counter + ".");
37 let prefCertAttrs = prefBranchCert.getChildList("");
38 if (prefCertAttrs.length == 0)
42 for (let prefCertAttr of prefCertAttrs)
43 certAttrs[prefCertAttr] = prefBranchCert.getCharPref(prefCertAttr);
45 certs.push(certAttrs);
53 * Verifies that an nsIX509Cert matches the expected certificate attribute
57 * The nsIX509Cert to compare to the expected attributes.
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
68 function validateCert(aCertificate, aCerts) {
69 // If there are no certificate requirements then just exit
70 if (!aCerts || aCerts.length == 0)
74 const missingCertErr = "A required certificate was not present.";
75 Cu.reportError(missingCertErr);
76 throw new Ce(missingCertErr, Cr.NS_ERROR_ILLEGAL_VALUE);
80 for (var i = 0; i < aCerts.length; ++i) {
82 var certAttrs = aCerts[i];
83 for (var name in certAttrs) {
84 if (!(name in aCertificate)) {
86 errors.push("Expected attribute '" + name + "' not present in " +
90 if (aCertificate[name] != certAttrs[name]) {
92 errors.push("Expected certificate attribute '" + name + "' " +
93 "value incorrect, expected: '" + certAttrs[name] +
94 "', got: '" + aCertificate[name] + "'.");
104 errors.forEach(Cu.reportError.bind(Cu));
105 const certCheckErr = "Certificate checks failed. See previous errors " +
107 Cu.reportError(certCheckErr);
108 throw new Ce(certCheckErr, Cr.NS_ERROR_ILLEGAL_VALUE);
113 * Checks if the connection must be HTTPS and if so, only allows built-in
114 * certificates and validates application specified certificate attribute
116 * See bug 340198 and bug 544442.
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
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.
135 function checkCert(aChannel, aAllowNonBuiltInCerts, aCerts) {
136 if (!aChannel.originalURI.schemeIs("https")) {
137 // Require https if there are certificate values to verify
139 throw new Ce("SSL is required and URI scheme is not https.",
140 Cr.NS_ERROR_UNEXPECTED);
145 let secInfo = aChannel.securityInfo.QueryInterface(Ci.nsITransportSecurityInfo);
146 let cert = secInfo.serverCert;
148 validateCert(cert, aCerts);
150 if (aAllowNonBuiltInCerts === true) {
154 let issuerCert = null;
155 for (issuerCert of secInfo.succeededCertChain.getEnumerator());
157 const certNotBuiltInErr = "Certificate issuer is not built-in.";
159 throw new Ce(certNotBuiltInErr, Cr.NS_ERROR_ABORT);
162 if (!issuerCert.isBuiltInRoot) {
163 throw new Ce(certNotBuiltInErr, Cr.NS_ERROR_ABORT);
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.
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);
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);
197 // nsIInterfaceRequestor
199 return this.QueryInterface(iid);
203 QueryInterface: ChromeUtils.generateQI(["nsIChannelEventSink",
204 "nsIInterfaceRequestor"]),