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;
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
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.
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.
25 function readCertPrefs(aPrefBranch) {
26 if (!Services.prefs.getBranch(aPrefBranch).getChildList("").length) {
33 let prefBranchCert = Services.prefs.getBranch(aPrefBranch + counter + ".");
34 let prefCertAttrs = prefBranchCert.getChildList("");
35 if (!prefCertAttrs.length) {
40 for (let prefCertAttr of prefCertAttrs) {
41 certAttrs[prefCertAttr] = prefBranchCert.getCharPref(prefCertAttr);
44 certs.push(certAttrs);
52 * Verifies that an nsIX509Cert matches the expected certificate attribute
56 * The nsIX509Cert to compare to the expected attributes.
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
67 function validateCert(aCertificate, aCerts) {
68 // If there are no certificate requirements then just exit
69 if (!aCerts || !aCerts.length) {
74 const missingCertErr = "A required certificate was not present.";
75 console.error(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)) {
87 "Expected attribute '" + name + "' not present in certificate."
91 if (aCertificate[name] != certAttrs[name]) {
94 "Expected certificate attribute '" +
97 "value incorrect, expected: '" +
113 errors.forEach(Cu.reportError.bind(Cu));
115 "Certificate checks failed. See previous errors for details.";
116 console.error(certCheckErr);
117 throw new Ce(certCheckErr, Cr.NS_ERROR_ILLEGAL_VALUE);
122 * Checks if the connection must be HTTPS and if so, only allows built-in
123 * certificates and validates application specified certificate attribute
125 * See bug 340198 and bug 544442.
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
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.
144 function checkCert(aChannel, aAllowNonBuiltInCerts, aCerts) {
145 if (!aChannel.originalURI.schemeIs("https")) {
146 // Require https if there are certificate values to verify
149 "SSL is required and URI scheme is not https.",
150 Cr.NS_ERROR_UNEXPECTED
156 let secInfo = aChannel.securityInfo;
157 let cert = secInfo.serverCert;
159 validateCert(cert, aCerts);
161 if (aAllowNonBuiltInCerts === true) {
164 const certNotBuiltInErr = "Certificate issuer is not built-in.";
165 if (!secInfo.isBuiltCertChainRootBuiltInRoot) {
166 throw new Ce(certNotBuiltInErr, Cr.NS_ERROR_ABORT);
171 * This class implements nsIChannelEventSink. Its job is to perform extra checks
172 * on the certificates used for some connections when those connections
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.
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);
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);
197 callback.onRedirectVerifyCallback(Cr.NS_OK);
200 // nsIInterfaceRequestor
202 return this.QueryInterface(iid);
206 QueryInterface: ChromeUtils.generateQI([
207 "nsIChannelEventSink",
208 "nsIInterfaceRequestor",
212 export var CertUtils = {