toolkit/modules/CertUtils.jsm

changeset 0
6474c204b198
     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 +};

mercurial