root/net/ssl/client_cert_store_mac.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. CopyCertChain
  2. IsIssuedByInKeychain
  3. GetClientCertsImpl
  4. GetClientCerts
  5. SelectClientCertsForTesting
  6. SelectClientCertsGivenPreferredForTesting

// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "net/ssl/client_cert_store_mac.h"

#include <CommonCrypto/CommonDigest.h>
#include <CoreFoundation/CFArray.h>
#include <CoreServices/CoreServices.h>
#include <Security/SecBase.h>
#include <Security/Security.h>

#include <algorithm>
#include <string>

#include "base/callback.h"
#include "base/logging.h"
#include "base/mac/mac_logging.h"
#include "base/mac/scoped_cftyperef.h"
#include "base/strings/sys_string_conversions.h"
#include "base/synchronization/lock.h"
#include "crypto/mac_security_services_lock.h"
#include "net/base/host_port_pair.h"
#include "net/cert/x509_util.h"
#include "net/cert/x509_util_mac.h"

using base::ScopedCFTypeRef;

namespace net {

namespace {

// Gets the issuer for a given cert, starting with the cert itself and
// including the intermediate and finally root certificates (if any).
// This function calls SecTrust but doesn't actually pay attention to the trust
// result: it shouldn't be used to determine trust, just to traverse the chain.
// Caller is responsible for releasing the value stored into *out_cert_chain.
OSStatus CopyCertChain(SecCertificateRef cert_handle,
                       CFArrayRef* out_cert_chain) {
  DCHECK(cert_handle);
  DCHECK(out_cert_chain);

  // Create an SSL policy ref configured for client cert evaluation.
  SecPolicyRef ssl_policy;
  OSStatus result = x509_util::CreateSSLClientPolicy(&ssl_policy);
  if (result)
    return result;
  ScopedCFTypeRef<SecPolicyRef> scoped_ssl_policy(ssl_policy);

  // Create a SecTrustRef.
  ScopedCFTypeRef<CFArrayRef> input_certs(CFArrayCreate(
      NULL, const_cast<const void**>(reinterpret_cast<void**>(&cert_handle)),
      1, &kCFTypeArrayCallBacks));
  SecTrustRef trust_ref = NULL;
  {
    base::AutoLock lock(crypto::GetMacSecurityServicesLock());
    result = SecTrustCreateWithCertificates(input_certs, ssl_policy,
                                            &trust_ref);
  }
  if (result)
    return result;
  ScopedCFTypeRef<SecTrustRef> trust(trust_ref);

  // Evaluate trust, which creates the cert chain.
  SecTrustResultType status;
  CSSM_TP_APPLE_EVIDENCE_INFO* status_chain;
  {
    base::AutoLock lock(crypto::GetMacSecurityServicesLock());
    result = SecTrustEvaluate(trust, &status);
  }
  if (result)
    return result;
  {
    base::AutoLock lock(crypto::GetMacSecurityServicesLock());
    result = SecTrustGetResult(trust, &status, out_cert_chain, &status_chain);
  }
  return result;
}

// Returns true if |*cert| is issued by an authority in |valid_issuers|
// according to Keychain Services, rather than using |cert|'s intermediate
// certificates. If it is, |*cert| is updated to point to the completed
// certificate
bool IsIssuedByInKeychain(const std::vector<std::string>& valid_issuers,
                          scoped_refptr<X509Certificate>* cert) {
  DCHECK(cert);
  DCHECK(cert->get());

  X509Certificate::OSCertHandle cert_handle = (*cert)->os_cert_handle();
  CFArrayRef cert_chain = NULL;
  OSStatus result = CopyCertChain(cert_handle, &cert_chain);
  if (result) {
    OSSTATUS_LOG(ERROR, result) << "CopyCertChain error";
    return false;
  }

  if (!cert_chain)
    return false;

  X509Certificate::OSCertHandles intermediates;
  for (CFIndex i = 1, chain_count = CFArrayGetCount(cert_chain);
       i < chain_count; ++i) {
    SecCertificateRef cert = reinterpret_cast<SecCertificateRef>(
        const_cast<void*>(CFArrayGetValueAtIndex(cert_chain, i)));
    intermediates.push_back(cert);
  }

  scoped_refptr<X509Certificate> new_cert(X509Certificate::CreateFromHandle(
      cert_handle, intermediates));
  CFRelease(cert_chain);  // Also frees |intermediates|.

  if (!new_cert->IsIssuedByEncoded(valid_issuers))
    return false;

  cert->swap(new_cert);
  return true;
}

// Examines the certificates in |preferred_cert| and |regular_certs| to find
// all certificates that match the client certificate request in |request|,
// storing the matching certificates in |selected_certs|.
// If |query_keychain| is true, Keychain Services will be queried to construct
// full certificate chains. If it is false, only the the certificates and their
// intermediates (available via X509Certificate::GetIntermediateCertificates())
// will be considered.
void GetClientCertsImpl(const scoped_refptr<X509Certificate>& preferred_cert,
                        const CertificateList& regular_certs,
                        const SSLCertRequestInfo& request,
                        bool query_keychain,
                        CertificateList* selected_certs) {
  CertificateList preliminary_list;
  if (preferred_cert.get())
    preliminary_list.push_back(preferred_cert);
  preliminary_list.insert(preliminary_list.end(), regular_certs.begin(),
                          regular_certs.end());

  selected_certs->clear();
  for (size_t i = 0; i < preliminary_list.size(); ++i) {
    scoped_refptr<X509Certificate>& cert = preliminary_list[i];
    if (cert->HasExpired() || !cert->SupportsSSLClientAuth())
      continue;

    // Skip duplicates (a cert may be in multiple keychains).
    const SHA1HashValue& fingerprint = cert->fingerprint();
    size_t pos;
    for (pos = 0; pos < selected_certs->size(); ++pos) {
      if ((*selected_certs)[pos]->fingerprint().Equals(fingerprint))
        break;
    }
    if (pos < selected_certs->size())
      continue;

    // Check if the certificate issuer is allowed by the server.
    if (request.cert_authorities.empty() ||
        cert->IsIssuedByEncoded(request.cert_authorities) ||
        (query_keychain &&
         IsIssuedByInKeychain(request.cert_authorities, &cert))) {
      selected_certs->push_back(cert);
    }
  }

  // Preferred cert should appear first in the ui, so exclude it from the
  // sorting.
  CertificateList::iterator sort_begin = selected_certs->begin();
  CertificateList::iterator sort_end = selected_certs->end();
  if (preferred_cert.get() && sort_begin != sort_end &&
      sort_begin->get() == preferred_cert.get()) {
    ++sort_begin;
  }
  sort(sort_begin, sort_end, x509_util::ClientCertSorter());
}

}  // namespace

ClientCertStoreMac::ClientCertStoreMac() {}

ClientCertStoreMac::~ClientCertStoreMac() {}

void ClientCertStoreMac::GetClientCerts(const SSLCertRequestInfo& request,
                                         CertificateList* selected_certs,
                                         const base::Closure& callback) {
  std::string server_domain = request.host_and_port.host();

  ScopedCFTypeRef<SecIdentityRef> preferred_identity;
  if (!server_domain.empty()) {
    // See if there's an identity preference for this domain:
    ScopedCFTypeRef<CFStringRef> domain_str(
        base::SysUTF8ToCFStringRef("https://" + server_domain));
    SecIdentityRef identity = NULL;
    // While SecIdentityCopyPreferences appears to take a list of CA issuers
    // to restrict the identity search to, within Security.framework the
    // argument is ignored and filtering unimplemented. See
    // SecIdentity.cpp in libsecurity_keychain, specifically
    // _SecIdentityCopyPreferenceMatchingName().
    {
      base::AutoLock lock(crypto::GetMacSecurityServicesLock());
      if (SecIdentityCopyPreference(domain_str, 0, NULL, &identity) == noErr)
        preferred_identity.reset(identity);
    }
  }

  // Now enumerate the identities in the available keychains.
  scoped_refptr<X509Certificate> preferred_cert = NULL;
  CertificateList regular_certs;

  SecIdentitySearchRef search = NULL;
  OSStatus err;
  {
    base::AutoLock lock(crypto::GetMacSecurityServicesLock());
    err = SecIdentitySearchCreate(NULL, CSSM_KEYUSE_SIGN, &search);
  }
  if (err) {
    selected_certs->clear();
    callback.Run();
    return;
  }
  ScopedCFTypeRef<SecIdentitySearchRef> scoped_search(search);
  while (!err) {
    SecIdentityRef identity = NULL;
    {
      base::AutoLock lock(crypto::GetMacSecurityServicesLock());
      err = SecIdentitySearchCopyNext(search, &identity);
    }
    if (err)
      break;
    ScopedCFTypeRef<SecIdentityRef> scoped_identity(identity);

    SecCertificateRef cert_handle;
    err = SecIdentityCopyCertificate(identity, &cert_handle);
    if (err != noErr)
      continue;
    ScopedCFTypeRef<SecCertificateRef> scoped_cert_handle(cert_handle);

    scoped_refptr<X509Certificate> cert(
        X509Certificate::CreateFromHandle(cert_handle,
                                          X509Certificate::OSCertHandles()));

    if (preferred_identity && CFEqual(preferred_identity, identity)) {
      // Only one certificate should match.
      DCHECK(!preferred_cert.get());
      preferred_cert = cert;
    } else {
      regular_certs.push_back(cert);
    }
  }

  if (err != errSecItemNotFound) {
    OSSTATUS_LOG(ERROR, err) << "SecIdentitySearch error";
    selected_certs->clear();
    callback.Run();
    return;
  }

  GetClientCertsImpl(preferred_cert, regular_certs, request, true,
                     selected_certs);
  callback.Run();
}

bool ClientCertStoreMac::SelectClientCertsForTesting(
    const CertificateList& input_certs,
    const SSLCertRequestInfo& request,
    CertificateList* selected_certs) {
  GetClientCertsImpl(NULL, input_certs, request, false, selected_certs);
  return true;
}

bool ClientCertStoreMac::SelectClientCertsGivenPreferredForTesting(
    const scoped_refptr<X509Certificate>& preferred_cert,
    const CertificateList& regular_certs,
    const SSLCertRequestInfo& request,
    CertificateList* selected_certs) {
  GetClientCertsImpl(
      preferred_cert, regular_certs, request, false, selected_certs);
  return true;
}

}  // namespace net

/* [<][>][^][v][top][bottom][index][help] */