michael@0: #if 0 michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: #endif michael@0: this.EXPORTED_SYMBOLS = [ "BadCertHandler", "checkCert", "readCertPrefs", "validateCert" ]; michael@0: michael@0: const Ce = Components.Exception; michael@0: const Ci = Components.interfaces; michael@0: const Cr = Components.results; michael@0: const Cu = Components.utils; michael@0: michael@0: Components.utils.import("resource://gre/modules/Services.jsm"); michael@0: michael@0: /** michael@0: * Reads a set of expected certificate attributes from preferences. The returned michael@0: * array can be passed to validateCert or checkCert to validate that a michael@0: * certificate matches the expected attributes. The preferences should look like michael@0: * this: michael@0: * prefix.1.attribute1 michael@0: * prefix.1.attribute2 michael@0: * prefix.2.attribute1 michael@0: * etc. michael@0: * Each numeric branch contains a set of required attributes for a single michael@0: * certificate. Having multiple numeric branches means that multiple michael@0: * certificates would be accepted by validateCert. michael@0: * michael@0: * @param aPrefBranch michael@0: * The prefix for all preferences, should end with a ".". michael@0: * @return An array of JS objects with names / values corresponding to the michael@0: * expected certificate's attribute names / values. michael@0: */ michael@0: this.readCertPrefs = michael@0: function readCertPrefs(aPrefBranch) { michael@0: if (Services.prefs.getBranch(aPrefBranch).getChildList("").length == 0) michael@0: return null; michael@0: michael@0: let certs = []; michael@0: let counter = 1; michael@0: while (true) { michael@0: let prefBranchCert = Services.prefs.getBranch(aPrefBranch + counter + "."); michael@0: let prefCertAttrs = prefBranchCert.getChildList(""); michael@0: if (prefCertAttrs.length == 0) michael@0: break; michael@0: michael@0: let certAttrs = {}; michael@0: for each (let prefCertAttr in prefCertAttrs) michael@0: certAttrs[prefCertAttr] = prefBranchCert.getCharPref(prefCertAttr); michael@0: michael@0: certs.push(certAttrs); michael@0: counter++; michael@0: } michael@0: michael@0: return certs; michael@0: } michael@0: michael@0: /** michael@0: * Verifies that an nsIX509Cert matches the expected certificate attribute michael@0: * values. michael@0: * michael@0: * @param aCertificate michael@0: * The nsIX509Cert to compare to the expected attributes. michael@0: * @param aCerts michael@0: * An array of JS objects with names / values corresponding to the michael@0: * expected certificate's attribute names / values. If this is null or michael@0: * an empty array then no checks are performed. michael@0: * @throws NS_ERROR_ILLEGAL_VALUE if a certificate attribute name from the michael@0: * aCerts param does not exist or the value for a certificate attribute michael@0: * from the aCerts param is different than the expected value or michael@0: * aCertificate wasn't specified and aCerts is not null or an empty michael@0: * array. michael@0: */ michael@0: this.validateCert = michael@0: function validateCert(aCertificate, aCerts) { michael@0: // If there are no certificate requirements then just exit michael@0: if (!aCerts || aCerts.length == 0) michael@0: return; michael@0: michael@0: if (!aCertificate) { michael@0: const missingCertErr = "A required certificate was not present."; michael@0: Cu.reportError(missingCertErr); michael@0: throw new Ce(missingCertErr, Cr.NS_ERROR_ILLEGAL_VALUE); michael@0: } michael@0: michael@0: var errors = []; michael@0: for (var i = 0; i < aCerts.length; ++i) { michael@0: var error = false; michael@0: var certAttrs = aCerts[i]; michael@0: for (var name in certAttrs) { michael@0: if (!(name in aCertificate)) { michael@0: error = true; michael@0: errors.push("Expected attribute '" + name + "' not present in " + michael@0: "certificate."); michael@0: break; michael@0: } michael@0: if (aCertificate[name] != certAttrs[name]) { michael@0: error = true; michael@0: errors.push("Expected certificate attribute '" + name + "' " + michael@0: "value incorrect, expected: '" + certAttrs[name] + michael@0: "', got: '" + aCertificate[name] + "'."); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: if (!error) michael@0: break; michael@0: } michael@0: michael@0: if (error) { michael@0: errors.forEach(Cu.reportError.bind(Cu)); michael@0: const certCheckErr = "Certificate checks failed. See previous errors " + michael@0: "for details."; michael@0: Cu.reportError(certCheckErr); michael@0: throw new Ce(certCheckErr, Cr.NS_ERROR_ILLEGAL_VALUE); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Checks if the connection must be HTTPS and if so, only allows built-in michael@0: * certificates and validates application specified certificate attribute michael@0: * values. michael@0: * See bug 340198 and bug 544442. michael@0: * michael@0: * @param aChannel michael@0: * The nsIChannel that will have its certificate checked. michael@0: * @param aAllowNonBuiltInCerts (optional) michael@0: * When true certificates that aren't builtin are allowed. When false michael@0: * or not specified the certificate must be a builtin certificate. michael@0: * @param aCerts (optional) michael@0: * An array of JS objects with names / values corresponding to the michael@0: * channel's expected certificate's attribute names / values. If it michael@0: * isn't null or not specified the the scheme for the channel's michael@0: * originalURI must be https. michael@0: * @throws NS_ERROR_UNEXPECTED if a certificate is expected and the URI scheme michael@0: * is not https. michael@0: * NS_ERROR_ILLEGAL_VALUE if a certificate attribute name from the michael@0: * aCerts param does not exist or the value for a certificate attribute michael@0: * from the aCerts param is different than the expected value. michael@0: * NS_ERROR_ABORT if the certificate issuer is not built-in. michael@0: */ michael@0: this.checkCert = michael@0: function checkCert(aChannel, aAllowNonBuiltInCerts, aCerts) { michael@0: if (!aChannel.originalURI.schemeIs("https")) { michael@0: // Require https if there are certificate values to verify michael@0: if (aCerts) { michael@0: throw new Ce("SSL is required and URI scheme is not https.", michael@0: Cr.NS_ERROR_UNEXPECTED); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: var cert = michael@0: aChannel.securityInfo.QueryInterface(Ci.nsISSLStatusProvider). michael@0: SSLStatus.QueryInterface(Ci.nsISSLStatus).serverCert; michael@0: michael@0: validateCert(cert, aCerts); michael@0: michael@0: if (aAllowNonBuiltInCerts === true) michael@0: return; michael@0: michael@0: var issuerCert = cert; michael@0: while (issuerCert.issuer && !issuerCert.issuer.equals(issuerCert)) michael@0: issuerCert = issuerCert.issuer; michael@0: michael@0: const certNotBuiltInErr = "Certificate issuer is not built-in."; michael@0: if (!issuerCert) michael@0: throw new Ce(certNotBuiltInErr, Cr.NS_ERROR_ABORT); michael@0: michael@0: issuerCert = issuerCert.QueryInterface(Ci.nsIX509Cert3); michael@0: var tokenNames = issuerCert.getAllTokenNames({}); michael@0: michael@0: if (!tokenNames || !tokenNames.some(isBuiltinToken)) michael@0: throw new Ce(certNotBuiltInErr, Cr.NS_ERROR_ABORT); michael@0: } michael@0: michael@0: function isBuiltinToken(tokenName) { michael@0: return tokenName == "Builtin Object Token"; michael@0: } michael@0: michael@0: /** michael@0: * This class implements nsIBadCertListener. Its job is to prevent "bad cert" michael@0: * security dialogs from being shown to the user. It is better to simply fail michael@0: * if the certificate is bad. See bug 304286. michael@0: * michael@0: * @param aAllowNonBuiltInCerts (optional) michael@0: * When true certificates that aren't builtin are allowed. When false michael@0: * or not specified the certificate must be a builtin certificate. michael@0: */ michael@0: this.BadCertHandler = michael@0: function BadCertHandler(aAllowNonBuiltInCerts) { michael@0: this.allowNonBuiltInCerts = aAllowNonBuiltInCerts; michael@0: } michael@0: BadCertHandler.prototype = { michael@0: michael@0: // nsIChannelEventSink michael@0: asyncOnChannelRedirect: function(oldChannel, newChannel, flags, callback) { michael@0: if (this.allowNonBuiltInCerts) { michael@0: callback.onRedirectVerifyCallback(Components.results.NS_OK); michael@0: return; michael@0: } michael@0: michael@0: // make sure the certificate of the old channel checks out before we follow michael@0: // a redirect from it. See bug 340198. michael@0: // Don't call checkCert for internal redirects. See bug 569648. michael@0: if (!(flags & Ci.nsIChannelEventSink.REDIRECT_INTERNAL)) michael@0: checkCert(oldChannel); michael@0: michael@0: callback.onRedirectVerifyCallback(Components.results.NS_OK); michael@0: }, michael@0: michael@0: // nsIInterfaceRequestor michael@0: getInterface: function(iid) { michael@0: return this.QueryInterface(iid); michael@0: }, michael@0: michael@0: // nsISupports michael@0: QueryInterface: function(iid) { michael@0: if (!iid.equals(Ci.nsIChannelEventSink) && michael@0: !iid.equals(Ci.nsIInterfaceRequestor) && michael@0: !iid.equals(Ci.nsISupports)) michael@0: throw Cr.NS_ERROR_NO_INTERFACE; michael@0: return this; michael@0: } michael@0: };