1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/modules/CertUtils.jsm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,225 @@ 1.4 +#if 0 1.5 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 +#endif 1.10 +this.EXPORTED_SYMBOLS = [ "BadCertHandler", "checkCert", "readCertPrefs", "validateCert" ]; 1.11 + 1.12 +const Ce = Components.Exception; 1.13 +const Ci = Components.interfaces; 1.14 +const Cr = Components.results; 1.15 +const Cu = Components.utils; 1.16 + 1.17 +Components.utils.import("resource://gre/modules/Services.jsm"); 1.18 + 1.19 +/** 1.20 + * Reads a set of expected certificate attributes from preferences. The returned 1.21 + * array can be passed to validateCert or checkCert to validate that a 1.22 + * certificate matches the expected attributes. The preferences should look like 1.23 + * this: 1.24 + * prefix.1.attribute1 1.25 + * prefix.1.attribute2 1.26 + * prefix.2.attribute1 1.27 + * etc. 1.28 + * Each numeric branch contains a set of required attributes for a single 1.29 + * certificate. Having multiple numeric branches means that multiple 1.30 + * certificates would be accepted by validateCert. 1.31 + * 1.32 + * @param aPrefBranch 1.33 + * The prefix for all preferences, should end with a ".". 1.34 + * @return An array of JS objects with names / values corresponding to the 1.35 + * expected certificate's attribute names / values. 1.36 + */ 1.37 +this.readCertPrefs = 1.38 + function readCertPrefs(aPrefBranch) { 1.39 + if (Services.prefs.getBranch(aPrefBranch).getChildList("").length == 0) 1.40 + return null; 1.41 + 1.42 + let certs = []; 1.43 + let counter = 1; 1.44 + while (true) { 1.45 + let prefBranchCert = Services.prefs.getBranch(aPrefBranch + counter + "."); 1.46 + let prefCertAttrs = prefBranchCert.getChildList(""); 1.47 + if (prefCertAttrs.length == 0) 1.48 + break; 1.49 + 1.50 + let certAttrs = {}; 1.51 + for each (let prefCertAttr in prefCertAttrs) 1.52 + certAttrs[prefCertAttr] = prefBranchCert.getCharPref(prefCertAttr); 1.53 + 1.54 + certs.push(certAttrs); 1.55 + counter++; 1.56 + } 1.57 + 1.58 + return certs; 1.59 +} 1.60 + 1.61 +/** 1.62 + * Verifies that an nsIX509Cert matches the expected certificate attribute 1.63 + * values. 1.64 + * 1.65 + * @param aCertificate 1.66 + * The nsIX509Cert to compare to the expected attributes. 1.67 + * @param aCerts 1.68 + * An array of JS objects with names / values corresponding to the 1.69 + * expected certificate's attribute names / values. If this is null or 1.70 + * an empty array then no checks are performed. 1.71 + * @throws NS_ERROR_ILLEGAL_VALUE if a certificate attribute name from the 1.72 + * aCerts param does not exist or the value for a certificate attribute 1.73 + * from the aCerts param is different than the expected value or 1.74 + * aCertificate wasn't specified and aCerts is not null or an empty 1.75 + * array. 1.76 + */ 1.77 +this.validateCert = 1.78 + function validateCert(aCertificate, aCerts) { 1.79 + // If there are no certificate requirements then just exit 1.80 + if (!aCerts || aCerts.length == 0) 1.81 + return; 1.82 + 1.83 + if (!aCertificate) { 1.84 + const missingCertErr = "A required certificate was not present."; 1.85 + Cu.reportError(missingCertErr); 1.86 + throw new Ce(missingCertErr, Cr.NS_ERROR_ILLEGAL_VALUE); 1.87 + } 1.88 + 1.89 + var errors = []; 1.90 + for (var i = 0; i < aCerts.length; ++i) { 1.91 + var error = false; 1.92 + var certAttrs = aCerts[i]; 1.93 + for (var name in certAttrs) { 1.94 + if (!(name in aCertificate)) { 1.95 + error = true; 1.96 + errors.push("Expected attribute '" + name + "' not present in " + 1.97 + "certificate."); 1.98 + break; 1.99 + } 1.100 + if (aCertificate[name] != certAttrs[name]) { 1.101 + error = true; 1.102 + errors.push("Expected certificate attribute '" + name + "' " + 1.103 + "value incorrect, expected: '" + certAttrs[name] + 1.104 + "', got: '" + aCertificate[name] + "'."); 1.105 + break; 1.106 + } 1.107 + } 1.108 + 1.109 + if (!error) 1.110 + break; 1.111 + } 1.112 + 1.113 + if (error) { 1.114 + errors.forEach(Cu.reportError.bind(Cu)); 1.115 + const certCheckErr = "Certificate checks failed. See previous errors " + 1.116 + "for details."; 1.117 + Cu.reportError(certCheckErr); 1.118 + throw new Ce(certCheckErr, Cr.NS_ERROR_ILLEGAL_VALUE); 1.119 + } 1.120 +} 1.121 + 1.122 +/** 1.123 + * Checks if the connection must be HTTPS and if so, only allows built-in 1.124 + * certificates and validates application specified certificate attribute 1.125 + * values. 1.126 + * See bug 340198 and bug 544442. 1.127 + * 1.128 + * @param aChannel 1.129 + * The nsIChannel that will have its certificate checked. 1.130 + * @param aAllowNonBuiltInCerts (optional) 1.131 + * When true certificates that aren't builtin are allowed. When false 1.132 + * or not specified the certificate must be a builtin certificate. 1.133 + * @param aCerts (optional) 1.134 + * An array of JS objects with names / values corresponding to the 1.135 + * channel's expected certificate's attribute names / values. If it 1.136 + * isn't null or not specified the the scheme for the channel's 1.137 + * originalURI must be https. 1.138 + * @throws NS_ERROR_UNEXPECTED if a certificate is expected and the URI scheme 1.139 + * is not https. 1.140 + * NS_ERROR_ILLEGAL_VALUE if a certificate attribute name from the 1.141 + * aCerts param does not exist or the value for a certificate attribute 1.142 + * from the aCerts param is different than the expected value. 1.143 + * NS_ERROR_ABORT if the certificate issuer is not built-in. 1.144 + */ 1.145 +this.checkCert = 1.146 + function checkCert(aChannel, aAllowNonBuiltInCerts, aCerts) { 1.147 + if (!aChannel.originalURI.schemeIs("https")) { 1.148 + // Require https if there are certificate values to verify 1.149 + if (aCerts) { 1.150 + throw new Ce("SSL is required and URI scheme is not https.", 1.151 + Cr.NS_ERROR_UNEXPECTED); 1.152 + } 1.153 + return; 1.154 + } 1.155 + 1.156 + var cert = 1.157 + aChannel.securityInfo.QueryInterface(Ci.nsISSLStatusProvider). 1.158 + SSLStatus.QueryInterface(Ci.nsISSLStatus).serverCert; 1.159 + 1.160 + validateCert(cert, aCerts); 1.161 + 1.162 + if (aAllowNonBuiltInCerts === true) 1.163 + return; 1.164 + 1.165 + var issuerCert = cert; 1.166 + while (issuerCert.issuer && !issuerCert.issuer.equals(issuerCert)) 1.167 + issuerCert = issuerCert.issuer; 1.168 + 1.169 + const certNotBuiltInErr = "Certificate issuer is not built-in."; 1.170 + if (!issuerCert) 1.171 + throw new Ce(certNotBuiltInErr, Cr.NS_ERROR_ABORT); 1.172 + 1.173 + issuerCert = issuerCert.QueryInterface(Ci.nsIX509Cert3); 1.174 + var tokenNames = issuerCert.getAllTokenNames({}); 1.175 + 1.176 + if (!tokenNames || !tokenNames.some(isBuiltinToken)) 1.177 + throw new Ce(certNotBuiltInErr, Cr.NS_ERROR_ABORT); 1.178 +} 1.179 + 1.180 +function isBuiltinToken(tokenName) { 1.181 + return tokenName == "Builtin Object Token"; 1.182 +} 1.183 + 1.184 +/** 1.185 + * This class implements nsIBadCertListener. Its job is to prevent "bad cert" 1.186 + * security dialogs from being shown to the user. It is better to simply fail 1.187 + * if the certificate is bad. See bug 304286. 1.188 + * 1.189 + * @param aAllowNonBuiltInCerts (optional) 1.190 + * When true certificates that aren't builtin are allowed. When false 1.191 + * or not specified the certificate must be a builtin certificate. 1.192 + */ 1.193 +this.BadCertHandler = 1.194 + function BadCertHandler(aAllowNonBuiltInCerts) { 1.195 + this.allowNonBuiltInCerts = aAllowNonBuiltInCerts; 1.196 +} 1.197 +BadCertHandler.prototype = { 1.198 + 1.199 + // nsIChannelEventSink 1.200 + asyncOnChannelRedirect: function(oldChannel, newChannel, flags, callback) { 1.201 + if (this.allowNonBuiltInCerts) { 1.202 + callback.onRedirectVerifyCallback(Components.results.NS_OK); 1.203 + return; 1.204 + } 1.205 + 1.206 + // make sure the certificate of the old channel checks out before we follow 1.207 + // a redirect from it. See bug 340198. 1.208 + // Don't call checkCert for internal redirects. See bug 569648. 1.209 + if (!(flags & Ci.nsIChannelEventSink.REDIRECT_INTERNAL)) 1.210 + checkCert(oldChannel); 1.211 + 1.212 + callback.onRedirectVerifyCallback(Components.results.NS_OK); 1.213 + }, 1.214 + 1.215 + // nsIInterfaceRequestor 1.216 + getInterface: function(iid) { 1.217 + return this.QueryInterface(iid); 1.218 + }, 1.219 + 1.220 + // nsISupports 1.221 + QueryInterface: function(iid) { 1.222 + if (!iid.equals(Ci.nsIChannelEventSink) && 1.223 + !iid.equals(Ci.nsIInterfaceRequestor) && 1.224 + !iid.equals(Ci.nsISupports)) 1.225 + throw Cr.NS_ERROR_NO_INTERFACE; 1.226 + return this; 1.227 + } 1.228 +};