Bumping gaia.json for 1 gaia revision(s) a=gaia-bump
[gecko.git] / toolkit / modules / CertUtils.jsm
blob00a2c52935dbfb5ae3ab32bd8692463183429ceb
1 #if 0
2 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #endif
7 this.EXPORTED_SYMBOLS = [ "BadCertHandler", "checkCert", "readCertPrefs", "validateCert" ];
9 const Ce = Components.Exception;
10 const Ci = Components.interfaces;
11 const Cr = Components.results;
12 const Cu = Components.utils;
14 Components.utils.import("resource://gre/modules/Services.jsm");
16 /**
17  * Reads a set of expected certificate attributes from preferences. The returned
18  * array can be passed to validateCert or checkCert to validate that a
19  * certificate matches the expected attributes. The preferences should look like
20  * this:
21  *   prefix.1.attribute1
22  *   prefix.1.attribute2
23  *   prefix.2.attribute1
24  *   etc.
25  * Each numeric branch contains a set of required attributes for a single
26  * certificate. Having multiple numeric branches means that multiple
27  * certificates would be accepted by validateCert.
28  *
29  * @param  aPrefBranch
30  *         The prefix for all preferences, should end with a ".".
31  * @return An array of JS objects with names / values corresponding to the
32  *         expected certificate's attribute names / values.
33  */
34 this.readCertPrefs =
35   function readCertPrefs(aPrefBranch) {
36   if (Services.prefs.getBranch(aPrefBranch).getChildList("").length == 0)
37     return null;
39   let certs = [];
40   let counter = 1;
41   while (true) {
42     let prefBranchCert = Services.prefs.getBranch(aPrefBranch + counter + ".");
43     let prefCertAttrs = prefBranchCert.getChildList("");
44     if (prefCertAttrs.length == 0)
45       break;
47     let certAttrs = {};
48     for each (let prefCertAttr in prefCertAttrs)
49       certAttrs[prefCertAttr] = prefBranchCert.getCharPref(prefCertAttr);
51     certs.push(certAttrs);
52     counter++;
53   }
55   return certs;
58 /**
59  * Verifies that an nsIX509Cert matches the expected certificate attribute
60  * values.
61  *
62  * @param  aCertificate
63  *         The nsIX509Cert to compare to the expected attributes.
64  * @param  aCerts
65  *         An array of JS objects with names / values corresponding to the
66  *         expected certificate's attribute names / values. If this is null or
67  *         an empty array then no checks are performed.
68  * @throws NS_ERROR_ILLEGAL_VALUE if a certificate attribute name from the
69  *         aCerts param does not exist or the value for a certificate attribute
70  *         from the aCerts param is different than the expected value or
71  *         aCertificate wasn't specified and aCerts is not null or an empty
72  *         array.
73  */
74 this.validateCert =
75   function validateCert(aCertificate, aCerts) {
76   // If there are no certificate requirements then just exit
77   if (!aCerts || aCerts.length == 0)
78     return;
80   if (!aCertificate) {
81     const missingCertErr = "A required certificate was not present.";
82     Cu.reportError(missingCertErr);
83     throw new Ce(missingCertErr, Cr.NS_ERROR_ILLEGAL_VALUE);
84   }
86   var errors = [];
87   for (var i = 0; i < aCerts.length; ++i) {
88     var error = false;
89     var certAttrs = aCerts[i];
90     for (var name in certAttrs) {
91       if (!(name in aCertificate)) {
92         error = true;
93         errors.push("Expected attribute '" + name + "' not present in " +
94                     "certificate.");
95         break;
96       }
97       if (aCertificate[name] != certAttrs[name]) {
98         error = true;
99         errors.push("Expected certificate attribute '" + name + "' " +
100                     "value incorrect, expected: '" + certAttrs[name] +
101                     "', got: '" + aCertificate[name] + "'.");
102         break;
103       }
104     }
106     if (!error)
107       break;
108   }
110   if (error) {
111     errors.forEach(Cu.reportError.bind(Cu));
112     const certCheckErr = "Certificate checks failed. See previous errors " +
113                          "for details.";
114     Cu.reportError(certCheckErr);
115     throw new Ce(certCheckErr, Cr.NS_ERROR_ILLEGAL_VALUE);
116   }
120  * Checks if the connection must be HTTPS and if so, only allows built-in
121  * certificates and validates application specified certificate attribute
122  * values.
123  * See bug 340198 and bug 544442.
125  * @param  aChannel
126  *         The nsIChannel that will have its certificate checked.
127  * @param  aAllowNonBuiltInCerts (optional)
128  *         When true certificates that aren't builtin are allowed. When false
129  *         or not specified the certificate must be a builtin certificate.
130  * @param  aCerts (optional)
131  *         An array of JS objects with names / values corresponding to the
132  *         channel's expected certificate's attribute names / values. If it
133  *         isn't null or not specified the the scheme for the channel's
134  *         originalURI must be https.
135  * @throws NS_ERROR_UNEXPECTED if a certificate is expected and the URI scheme
136  *         is not https.
137  *         NS_ERROR_ILLEGAL_VALUE if a certificate attribute name from the
138  *         aCerts param does not exist or the value for a certificate attribute
139  *         from the aCerts  param is different than the expected value.
140  *         NS_ERROR_ABORT if the certificate issuer is not built-in.
141  */
142 this.checkCert =
143   function checkCert(aChannel, aAllowNonBuiltInCerts, aCerts) {
144   if (!aChannel.originalURI.schemeIs("https")) {
145     // Require https if there are certificate values to verify
146     if (aCerts) {
147       throw new Ce("SSL is required and URI scheme is not https.",
148                    Cr.NS_ERROR_UNEXPECTED);
149     }
150     return;
151   }
153   var cert =
154       aChannel.securityInfo.QueryInterface(Ci.nsISSLStatusProvider).
155       SSLStatus.QueryInterface(Ci.nsISSLStatus).serverCert;
157   validateCert(cert, aCerts);
159   if (aAllowNonBuiltInCerts ===  true)
160     return;
162   var issuerCert = cert;
163   while (issuerCert.issuer && !issuerCert.issuer.equals(issuerCert))
164     issuerCert = issuerCert.issuer;
166   const certNotBuiltInErr = "Certificate issuer is not built-in.";
167   if (!issuerCert)
168     throw new Ce(certNotBuiltInErr, Cr.NS_ERROR_ABORT);
170   var tokenNames = issuerCert.getAllTokenNames({});
172   if (!tokenNames || !tokenNames.some(isBuiltinToken))
173     throw new Ce(certNotBuiltInErr, Cr.NS_ERROR_ABORT);
176 function isBuiltinToken(tokenName) {
177   return tokenName == "Builtin Object Token";
181  * This class implements nsIBadCertListener.  Its job is to prevent "bad cert"
182  * security dialogs from being shown to the user.  It is better to simply fail
183  * if the certificate is bad. See bug 304286.
185  * @param  aAllowNonBuiltInCerts (optional)
186  *         When true certificates that aren't builtin are allowed. When false
187  *         or not specified the certificate must be a builtin certificate.
188  */
189 this.BadCertHandler =
190   function BadCertHandler(aAllowNonBuiltInCerts) {
191   this.allowNonBuiltInCerts = aAllowNonBuiltInCerts;
193 BadCertHandler.prototype = {
195   // nsIChannelEventSink
196   asyncOnChannelRedirect: function(oldChannel, newChannel, flags, callback) {
197     if (this.allowNonBuiltInCerts) {
198       callback.onRedirectVerifyCallback(Components.results.NS_OK);
199       return;
200     }
202     // make sure the certificate of the old channel checks out before we follow
203     // a redirect from it.  See bug 340198.
204     // Don't call checkCert for internal redirects. See bug 569648.
205     if (!(flags & Ci.nsIChannelEventSink.REDIRECT_INTERNAL))
206       checkCert(oldChannel);
207     
208     callback.onRedirectVerifyCallback(Components.results.NS_OK);
209   },
211   // nsIInterfaceRequestor
212   getInterface: function(iid) {
213     return this.QueryInterface(iid);
214   },
216   // nsISupports
217   QueryInterface: function(iid) {
218     if (!iid.equals(Ci.nsIChannelEventSink) &&
219         !iid.equals(Ci.nsIInterfaceRequestor) &&
220         !iid.equals(Ci.nsISupports))
221       throw Cr.NS_ERROR_NO_INTERFACE;
222     return this;
223   }