Enable SSLClientSocketTest unit tests on Mac OS X by implementing our own certificate validation code. This gives us proper hostname matching, multiple error codes (e.g., before a certificate could be marked as expired or untrusted, but not both), revocation checking, and EV certificate checking.

BUG=19286,10910,14733
TEST=https://www.paypal.com should work without warning. https://paypal.com should get a warning about a hostname mismatch. https://test-ssev.verisign.com:1443/test-SSEV-expired-verisign.html should give a warning about an expired certificate.
Review URL: http://codereview.chromium.org/174102

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@24625 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/net/base/x509_certificate.cc b/net/base/x509_certificate.cc
index 7a5a6691..e8db7c7 100644
--- a/net/base/x509_certificate.cc
+++ b/net/base/x509_certificate.cc
@@ -169,7 +169,11 @@
 }
 
 X509Certificate::X509Certificate(OSCertHandle cert_handle, Source source)
-    : cert_handle_(cert_handle), source_(source) {
+    : cert_handle_(cert_handle),
+#if defined(OS_MACOSX)
+      intermediate_ca_certs_(NULL),
+#endif
+      source_(source) {
   Initialize();
 }
 
@@ -182,6 +186,9 @@
       valid_start_(start_date),
       valid_expiry_(expiration_date),
       cert_handle_(NULL),
+#if defined(OS_MACOSX)
+      intermediate_ca_certs_(NULL),
+#endif
       source_(SOURCE_UNUSED) {
   memset(fingerprint_.data, 0, sizeof(fingerprint_.data));
 }
@@ -191,6 +198,10 @@
   X509Certificate::Cache::GetInstance()->Remove(this);
   if (cert_handle_)
     FreeOSCertHandle(cert_handle_);
+#if defined(OS_MACOSX)
+  if (intermediate_ca_certs_)
+    CFRelease(intermediate_ca_certs_);
+#endif
 }
 
 bool X509Certificate::HasExpired() const {
diff --git a/net/base/x509_certificate.h b/net/base/x509_certificate.h
index 1e2419e..4ae65546 100644
--- a/net/base/x509_certificate.h
+++ b/net/base/x509_certificate.h
@@ -209,6 +209,12 @@
   // now.
   bool HasExpired() const;
 
+#if defined(OS_MACOSX)
+    // Adds an untrusted intermediate certificate that may be needed for
+    // chain building.
+    void AddIntermediateCertificate(SecCertificateRef cert);
+#endif
+
   // Verifies the certificate against the given hostname.  Returns OK if
   // successful or an error code upon failure.
   //
@@ -299,6 +305,12 @@
   // A handle to the certificate object in the underlying crypto library.
   OSCertHandle cert_handle_;
 
+#if defined(OS_MACOSX)
+    // Untrusted intermediate certificates associated with this certificate
+    // that may be needed for chain building.
+    CFMutableArrayRef intermediate_ca_certs_;
+#endif
+
   // Where the certificate comes from.
   Source source_;
 
diff --git a/net/base/x509_certificate_mac.cc b/net/base/x509_certificate_mac.cc
index 2e754899..d55a770 100644
--- a/net/base/x509_certificate_mac.cc
+++ b/net/base/x509_certificate_mac.cc
@@ -7,23 +7,170 @@
 #include <CommonCrypto/CommonDigest.h>
 #include <time.h>
 
+#include "base/scoped_cftyperef.h"
 #include "base/logging.h"
 #include "base/pickle.h"
 #include "net/base/cert_status_flags.h"
-#include "net/base/ev_root_ca_metadata.h"
+#include "net/base/cert_verify_result.h"
 #include "net/base/net_errors.h"
 
 using base::Time;
 
 namespace net {
 
+class MacTrustedCertificates {
+ public:
+  // Sets the trusted root certificate used by tests. Call with |cert| set
+  // to NULL to clear the test certificate.
+  void SetTestCertificate(X509Certificate* cert) {
+    AutoLock lock(lock_);
+    test_certificate_ = cert;
+  }
+
+  // Returns an array containing the trusted certificates for use with
+  // SecTrustSetAnchorCertificates(). Returns NULL if the system-supplied
+  // list of trust anchors is acceptable (that is, there is not test
+  // certificate available). Ownership follows the Create Rule (caller
+  // is responsible for calling CFRelease on the non-NULL result).
+  CFArrayRef CopyTrustedCertificateArray() {
+    AutoLock lock(lock_);
+
+    if (!test_certificate_)
+      return NULL;
+
+    // Failure to copy the anchor certificates or add the test certificate
+    // is non-fatal; SecTrustEvaluate() will use the system anchors instead.
+    CFArrayRef anchor_array;
+    OSStatus status = SecTrustCopyAnchorCertificates(&anchor_array);
+    if (status)
+      return NULL;
+    scoped_cftyperef<CFArrayRef> scoped_anchor_array(anchor_array);
+    CFMutableArrayRef merged_array = CFArrayCreateMutableCopy(
+        kCFAllocatorDefault, 0, anchor_array);
+    if (!merged_array)
+      return NULL;
+    CFArrayAppendValue(merged_array, test_certificate_->os_cert_handle());
+
+    return merged_array;
+  }
+ private:
+  friend struct DefaultSingletonTraits<MacTrustedCertificates>;
+
+  // Obtain an instance of MacTrustedCertificates via the singleton
+  // interface.
+  MacTrustedCertificates() : test_certificate_(NULL) { }
+
+  // An X509Certificate object that may be appended to the list of
+  // system trusted anchors.
+  scoped_refptr<X509Certificate> test_certificate_;
+
+  // The trusted cache may be accessed from multiple threads.
+  mutable Lock lock_;
+
+  DISALLOW_COPY_AND_ASSIGN(MacTrustedCertificates);
+};
+
+void SetMacTestCertificate(X509Certificate* cert) {
+  Singleton<MacTrustedCertificates>::get()->SetTestCertificate(cert);
+}
+
 namespace {
 
+typedef OSStatus (*SecTrustCopyExtendedResultFuncPtr)(SecTrustRef,
+                                                      CFDictionaryRef*);
+
 inline bool CSSMOIDEqual(const CSSM_OID* oid1, const CSSM_OID* oid2) {
   return oid1->Length == oid2->Length &&
       (memcmp(oid1->Data, oid2->Data, oid1->Length) == 0);
 }
 
+int NetErrorFromOSStatus(OSStatus status) {
+  switch (status) {
+    case noErr:
+      return OK;
+    case errSecNotAvailable:
+    case errSecNoCertificateModule:
+    case errSecNoPolicyModule:
+      return ERR_NOT_IMPLEMENTED;
+    case errSecAuthFailed:
+      return ERR_ACCESS_DENIED;
+    default:
+      LOG(ERROR) << "Unknown error " << status << " mapped to net::ERR_FAILED";
+      return ERR_FAILED;
+  }
+}
+
+int CertStatusFromOSStatus(OSStatus status) {
+  switch (status) {
+    case noErr:
+      return 0;
+
+    case CSSMERR_TP_INVALID_ANCHOR_CERT:
+    case CSSMERR_TP_NOT_TRUSTED:
+    case CSSMERR_TP_INVALID_CERT_AUTHORITY:
+      return CERT_STATUS_AUTHORITY_INVALID;
+
+    case CSSMERR_TP_CERT_EXPIRED:
+    case CSSMERR_TP_CERT_NOT_VALID_YET:
+      // "Expired" and "not yet valid" collapse into a single status.
+      return CERT_STATUS_DATE_INVALID;
+
+    case CSSMERR_TP_CERT_REVOKED:
+    case CSSMERR_TP_CERT_SUSPENDED:
+      return CERT_STATUS_REVOKED;
+
+    case CSSMERR_APPLETP_HOSTNAME_MISMATCH:
+      return CERT_STATUS_COMMON_NAME_INVALID;
+
+    case CSSMERR_APPLETP_CRL_NOT_FOUND:
+    case CSSMERR_APPLETP_INCOMPLETE_REVOCATION_CHECK:
+      return CERT_STATUS_NO_REVOCATION_MECHANISM;
+
+    case CSSMERR_APPLETP_CRL_NOT_TRUSTED:
+    case CSSMERR_APPLETP_CRL_SERVER_DOWN:
+    case CSSMERR_APPLETP_CRL_NOT_VALID_YET:
+    case CSSMERR_APPLETP_NETWORK_FAILURE:
+    case CSSMERR_APPLETP_OCSP_UNAVAILABLE:
+    case CSSMERR_APPLETP_OCSP_BAD_RESPONSE:
+    case CSSMERR_APPLETP_OCSP_RESP_UNAUTHORIZED:
+    case CSSMERR_APPLETP_OCSP_RESP_SIG_REQUIRED:
+    case CSSMERR_APPLETP_OCSP_RESP_MALFORMED_REQ:
+    case CSSMERR_APPLETP_OCSP_RESP_INTERNAL_ERR:
+    case CSSMERR_APPLETP_OCSP_RESP_TRY_LATER:
+      // We asked for a revocation check, but didn't get it.
+      return CERT_STATUS_UNABLE_TO_CHECK_REVOCATION;
+
+    default:
+      // Failure was due to something Chromium doesn't define a
+      // specific status for (such as basic constraints violation, or
+      // unknown critical extension)
+      return CERT_STATUS_INVALID;
+  }
+}
+
+bool OverrideHostnameMismatch(const std::string& hostname,
+                              std::vector<std::string>* dns_names) {
+  // SecTrustEvaluate() does not check dotted IP addresses. If
+  // hostname is provided as, say, 127.0.0.1, then the error
+  // CSSMERR_APPLETP_HOSTNAME_MISMATCH will always be returned,
+  // even if the certificate contains 127.0.0.1 as one of its names.
+  // We, however, want to allow that behavior. SecTrustEvaluate()
+  // only checks for digits and dots when considering whether a
+  // hostname is an IP address, so IPv6 and hex addresses go through
+  // its normal comparison.
+  bool is_dotted_ip = true;
+  bool override_hostname_mismatch = false;
+  for (std::string::const_iterator c = hostname.begin();
+       c != hostname.end() && is_dotted_ip; ++c)
+    is_dotted_ip = (*c >= '0' && *c <= '9') || *c == '.';
+  if (is_dotted_ip) {
+    for (std::vector<std::string>::const_iterator name = dns_names->begin();
+         name != dns_names->end() && !override_hostname_mismatch; ++name)
+      override_hostname_mismatch = (*name == hostname);
+  }
+  return override_hostname_mismatch;
+}
+
 void ParsePrincipal(const CSSM_X509_NAME* name,
                     X509Certificate::Principal* principal) {
   std::vector<std::string> common_names, locality_names, state_names,
@@ -262,26 +409,257 @@
     dns_names->push_back(subject_.common_name);
 }
 
-int X509Certificate::Verify(const std::string& hostname,
-                            int flags, CertVerifyResult* verify_result) const {
-  NOTIMPLEMENTED();
-  return ERR_NOT_IMPLEMENTED;
+int X509Certificate::Verify(const std::string& hostname, int flags,
+                            CertVerifyResult* verify_result) const {
+  verify_result->Reset();
+
+  // Create an SSL SecPolicyRef, and configure it to perform hostname
+  // validation. The hostname check does 99% of what we want, with the
+  // exception of dotted IPv4 addreses, which we handle ourselves below.
+  SecPolicySearchRef ssl_policy_search_ref = NULL;
+  OSStatus status = SecPolicySearchCreate(CSSM_CERT_X_509v3,
+                                          &CSSMOID_APPLE_TP_SSL,
+                                          NULL,
+                                          &ssl_policy_search_ref);
+  if (status)
+    return NetErrorFromOSStatus(status);
+  scoped_cftyperef<SecPolicySearchRef>
+      scoped_ssl_policy_search_ref(ssl_policy_search_ref);
+  SecPolicyRef ssl_policy = NULL;
+  status = SecPolicySearchCopyNext(ssl_policy_search_ref, &ssl_policy);
+  if (status)
+    return NetErrorFromOSStatus(status);
+  scoped_cftyperef<SecPolicyRef> scoped_ssl_policy(ssl_policy);
+  CSSM_APPLE_TP_SSL_OPTIONS tp_ssl_options = { CSSM_APPLE_TP_SSL_OPTS_VERSION };
+  tp_ssl_options.ServerName = hostname.data();
+  tp_ssl_options.ServerNameLen = hostname.size();
+  CSSM_DATA tp_ssl_options_data_value;
+  tp_ssl_options_data_value.Data = reinterpret_cast<uint8*>(&tp_ssl_options);
+  tp_ssl_options_data_value.Length = sizeof(tp_ssl_options);
+  status = SecPolicySetValue(ssl_policy, &tp_ssl_options_data_value);
+  if (status)
+    return NetErrorFromOSStatus(status);
+
+  // Create and configure a SecTrustRef, which takes our certificate(s)
+  // and our SSL SecPolicyRef. SecTrustCreateWithCertificates() takes an
+  // array of certificates, the first of which is the certificate we're
+  // verifying, and the subsequent (optional) certificates are used for
+  // chain building.
+  CFMutableArrayRef cert_array = CFArrayCreateMutable(kCFAllocatorDefault, 0,
+                                                      &kCFTypeArrayCallBacks);
+  if (!cert_array)
+    return ERR_OUT_OF_MEMORY;
+  scoped_cftyperef<CFArrayRef> scoped_cert_array(cert_array);
+  CFArrayAppendValue(cert_array, cert_handle_);
+  if (intermediate_ca_certs_) {
+    CFIndex intermediate_count = CFArrayGetCount(intermediate_ca_certs_);
+    for (CFIndex i = 0; i < intermediate_count; ++i) {
+      SecCertificateRef intermediate_cert = static_cast<SecCertificateRef>(
+          const_cast<void*>(CFArrayGetValueAtIndex(intermediate_ca_certs_, i)));
+      CFArrayAppendValue(cert_array, intermediate_cert);
+    }
+  }
+
+  SecTrustRef trust_ref = NULL;
+  status = SecTrustCreateWithCertificates(cert_array, ssl_policy, &trust_ref);
+  if (status)
+    return NetErrorFromOSStatus(status);
+  scoped_cftyperef<SecTrustRef> scoped_trust_ref(trust_ref);
+
+  // Set the trusted anchor certificates for the SecTrustRef by merging the
+  // system trust anchors and the test root certificate.
+  CFArrayRef anchor_array =
+      Singleton<MacTrustedCertificates>::get()->CopyTrustedCertificateArray();
+  scoped_cftyperef<CFArrayRef> scoped_anchor_array(anchor_array);
+  if (anchor_array) {
+    status = SecTrustSetAnchorCertificates(trust_ref, anchor_array);
+    if (status)
+      return NetErrorFromOSStatus(status);
+  }
+
+  if (flags & VERIFY_REV_CHECKING_ENABLED) {
+    // When called with VERIFY_REV_CHECKING_ENABLED, we ask SecTrustEvaluate()
+    // to apply OCSP and CRL checking, but we're still subject to the global
+    // settings, which are configured in the Keychain Access application (in
+    // the Certificates tab of the Preferences dialog). If the user has
+    // revocation disabled (which is the default), then we will get
+    // kSecTrustResultRecoverableTrustFailure back from SecTrustEvaluate()
+    // with one of a number of sub error codes indicating that revocation
+    // checking did not occur. In that case, we'll set our own result to include
+    // CERT_STATUS_UNABLE_TO_CHECK_REVOCATION (note that this does not apply
+    // to EV certificates, which always get revocation checks regardless of the
+    // global settings).
+    verify_result->cert_status |= CERT_STATUS_REV_CHECKING_ENABLED;
+    CSSM_APPLE_TP_ACTION_DATA tp_action_data = { CSSM_APPLE_TP_ACTION_VERSION };
+    tp_action_data.ActionFlags = CSSM_TP_ACTION_REQUIRE_REV_PER_CERT;
+    CFDataRef action_data_ref =
+        CFDataCreate(NULL, reinterpret_cast<UInt8*>(&tp_action_data),
+                     sizeof(tp_action_data));
+    if (!action_data_ref)
+      return ERR_OUT_OF_MEMORY;
+    scoped_cftyperef<CFDataRef> scoped_action_data_ref(action_data_ref);
+    status = SecTrustSetParameters(trust_ref, CSSM_TP_ACTION_DEFAULT,
+                                   action_data_ref);
+    if (status)
+      return NetErrorFromOSStatus(status);
+  } else {
+    // EV requires revocation checking.
+    flags &= ~VERIFY_EV_CERT;
+  }
+
+  // Verify the certificate. A non-zero result from SecTrustGetResult()
+  // indicates that some fatal error occurred and the chain couldn't be
+  // processed, not that the chain contains no errors. We need to examine the
+  // output of SecTrustGetResult() to determine that.
+  SecTrustResultType trust_result;
+  status = SecTrustEvaluate(trust_ref, &trust_result);
+  if (status)
+    return NetErrorFromOSStatus(status);
+  CFArrayRef completed_chain = NULL;
+  CSSM_TP_APPLE_EVIDENCE_INFO* chain_info;
+  status = SecTrustGetResult(trust_ref, &trust_result, &completed_chain,
+                             &chain_info);
+  if (status)
+    return NetErrorFromOSStatus(status);
+  scoped_cftyperef<CFArrayRef> scoped_completed_chain(completed_chain);
+
+  // Evaluate the results
+  OSStatus cssm_result;
+  bool got_certificate_error = false;
+  switch (trust_result) {
+    case kSecTrustResultUnspecified:
+    case kSecTrustResultProceed:
+      // Certificate chain is valid and trusted ("unspecified" indicates that
+      // the user has not explicitly set a trust setting)
+      break;
+
+    case kSecTrustResultDeny:
+    case kSecTrustResultConfirm:
+      // Certificate chain is explicitly untrusted. For kSecTrustResultConfirm,
+      // we're following what Secure Transport does and treating it as
+      // "deny".
+      verify_result->cert_status |= CERT_STATUS_AUTHORITY_INVALID;
+      break;
+
+    case kSecTrustResultRecoverableTrustFailure:
+      // Certificate chain has a failure that can be overridden by the user.
+      status = SecTrustGetCssmResultCode(trust_ref, &cssm_result);
+      if (status)
+        return NetErrorFromOSStatus(status);
+      switch (cssm_result) {
+        case CSSMERR_TP_NOT_TRUSTED:
+        case CSSMERR_TP_INVALID_ANCHOR_CERT:
+          verify_result->cert_status |= CERT_STATUS_AUTHORITY_INVALID;
+          break;
+        case CSSMERR_TP_CERT_EXPIRED:
+        case CSSMERR_TP_CERT_NOT_VALID_YET:
+          verify_result->cert_status |= CERT_STATUS_DATE_INVALID;
+          break;
+        case CSSMERR_TP_CERT_REVOKED:
+        case CSSMERR_TP_CERT_SUSPENDED:
+          verify_result->cert_status |= CERT_STATUS_REVOKED;
+          break;
+        default:
+          // Look for specific per-certificate errors below.
+          break;
+      }
+      // Walk the chain of error codes in the CSSM_TP_APPLE_EVIDENCE_INFO
+      // structure which can catch multiple errors from each certificate.
+      for (CFIndex index = 0, chain_count = CFArrayGetCount(completed_chain);
+           index < chain_count; ++index) {
+        if (chain_info[index].StatusBits & CSSM_CERT_STATUS_EXPIRED ||
+            chain_info[index].StatusBits & CSSM_CERT_STATUS_NOT_VALID_YET)
+          verify_result->cert_status |= CERT_STATUS_DATE_INVALID;
+        for (uint32 status_code_index = 0;
+             status_code_index < chain_info[index].NumStatusCodes;
+             ++status_code_index) {
+          got_certificate_error = true;
+          int cert_status = CertStatusFromOSStatus(cssm_result);
+          if (cert_status == CERT_STATUS_COMMON_NAME_INVALID) {
+            std::vector<std::string> names;
+            GetDNSNames(&names);
+            if (OverrideHostnameMismatch(hostname, &names)) {
+              cert_status = 0;
+            }
+          }
+          verify_result->cert_status |= cert_status;
+        }
+      }
+      // Be paranoid and ensure that we recorded at least one certificate
+      // status on receiving kSecTrustResultRecoverableTrustFailure. The
+      // call to SecTrustGetCssmResultCode() should pick up when the chain
+      // is not trusted and the loop through CSSM_TP_APPLE_EVIDENCE_INFO
+      // should pick up everything else, but let's be safe.
+      if (!verify_result->cert_status && !got_certificate_error) {
+        verify_result->cert_status |= CERT_STATUS_INVALID;
+        NOTREACHED();
+      }
+      break;
+
+    default:
+      status = SecTrustGetCssmResultCode(trust_ref, &cssm_result);
+      if (status)
+        return NetErrorFromOSStatus(status);
+      verify_result->cert_status |= CertStatusFromOSStatus(cssm_result);
+      if (!verify_result->cert_status) {
+        verify_result->cert_status |= CERT_STATUS_INVALID;
+      }
+      break;
+  }
+
+  if (IsCertStatusError(verify_result->cert_status))
+    return MapCertStatusToNetError(verify_result->cert_status);
+
+  if (flags & VERIFY_EV_CERT) {
+    // Determine the certificate's EV status using SecTrustCopyExtendedResult(),
+    // which we need to look up because the function wasn't added until
+    // Mac OS X 10.5.7.
+    CFBundleRef bundle =
+        CFBundleGetBundleWithIdentifier(CFSTR("com.apple.security"));
+    if (bundle) {
+      SecTrustCopyExtendedResultFuncPtr copy_extended_result =
+          reinterpret_cast<SecTrustCopyExtendedResultFuncPtr>(
+              CFBundleGetFunctionPointerForName(bundle,
+                  CFSTR("SecTrustCopyExtendedResult")));
+      if (copy_extended_result) {
+        CFDictionaryRef ev_dict = NULL;
+        status = copy_extended_result(trust_ref, &ev_dict);
+        if (!status && ev_dict) {
+          // The returned dictionary contains the EV organization name from the
+          // server certificate, which we don't need at this point (and we
+          // have other ways to access, anyway). All we care is that
+          // SecTrustCopyExtendedResult() returned noErr and a non-NULL
+          // dictionary.
+          CFRelease(ev_dict);
+          verify_result->cert_status |= CERT_STATUS_IS_EV;
+        }
+      }
+    }
+  }
+
+  return OK;
 }
 
-// Returns true if the certificate is an extended-validation certificate.
-//
-// The certificate has already been verified by the HTTP library.  cert_status
-// represents the result of that verification.  This function performs
-// additional checks of the certificatePolicies extensions of the certificates
-// in the certificate chain according to Section 7 (pp. 11-12) of the EV
-// Certificate Guidelines Version 1.0 at
-// http://cabforum.org/EV_Certificate_Guidelines.pdf.
 bool X509Certificate::VerifyEV() const {
-  // TODO(avi): implement this
-  NOTIMPLEMENTED();
+  // We don't call this private method, but we do need to implement it because
+  // it's defined in x509_certificate.h. We perform EV checking in the
+  // Verify() above.
+  NOTREACHED();
   return false;
 }
 
+void X509Certificate::AddIntermediateCertificate(SecCertificateRef cert) {
+  if (cert) {
+    if (!intermediate_ca_certs_) {
+      intermediate_ca_certs_ = CFArrayCreateMutable(kCFAllocatorDefault, 0,
+                                                    &kCFTypeArrayCallBacks);
+    }
+    if (intermediate_ca_certs_) {
+      CFArrayAppendValue(intermediate_ca_certs_, cert);
+    }
+  }
+}
+
 // static
 X509Certificate::OSCertHandle X509Certificate::CreateOSCertHandleFromBytes(
     const char* data, int length) {
diff --git a/net/socket/ssl_client_socket_mac.cc b/net/socket/ssl_client_socket_mac.cc
index 6684398..56b19d1 100644
--- a/net/socket/ssl_client_socket_mac.cc
+++ b/net/socket/ssl_client_socket_mac.cc
@@ -4,8 +4,10 @@
 
 #include "net/socket/ssl_client_socket_mac.h"
 
+#include "base/scoped_cftyperef.h"
 #include "base/singleton.h"
 #include "base/string_util.h"
+#include "net/base/cert_verifier.h"
 #include "net/base/io_buffer.h"
 #include "net/base/net_errors.h"
 #include "net/base/ssl_info.h"
@@ -276,7 +278,6 @@
       user_callback_(NULL),
       next_state_(STATE_NONE),
       next_io_state_(STATE_NONE),
-      server_cert_status_(0),
       completed_handshake_(false),
       ssl_context_(NULL),
       pending_send_error_(OK),
@@ -325,23 +326,11 @@
   if (status)
     return NetErrorFromOSStatus(status);
 
-  if (ssl_config_.allowed_bad_certs.empty()) {
-    // We're going to use the default certificate verification that the system
-    // does, and accept its answer for the cert status.
-    status = SSLSetPeerDomainName(ssl_context_, hostname_.data(),
-                                  hostname_.length());
-    if (status)
-      return NetErrorFromOSStatus(status);
-
-    // TODO(wtc): for now, always check revocation.
-    server_cert_status_ = CERT_STATUS_REV_CHECKING_ENABLED;
-  } else {
-    // Disable certificate chain validation.  We will only allow the certs in
-    // ssl_config_.allowed_bad_certs.
-    status = SSLSetEnableCertVerify(ssl_context_, false);
-    if (status)
-      return NetErrorFromOSStatus(status);
-  }
+  // Disable certificate verification within Secure Transport; we'll
+  // be handling that ourselves.
+  status = SSLSetEnableCertVerify(ssl_context_, false);
+  if (status)
+    return NetErrorFromOSStatus(status);
 
   next_state_ = STATE_HANDSHAKE;
   int rv = DoLoop(OK);
@@ -430,7 +419,7 @@
   ssl_info->cert = server_cert_;
 
   // update status
-  ssl_info->cert_status = server_cert_status_;
+  ssl_info->cert_status = server_cert_verify_result_.cert_status;
 
   // security info
   SSLCipherSuite suite;
@@ -483,6 +472,14 @@
         // Do the SSL/TLS handshake.
         rv = DoHandshake();
         break;
+      case STATE_VERIFY_CERT:
+        // Kick off server certificate validation.
+        rv = DoVerifyCert();
+        break;
+      case STATE_VERIFY_CERT_COMPLETE:
+        // Check the results of the  server certificate validation.
+        rv = DoVerifyCertComplete(rv);
+        break;
       case STATE_READ_COMPLETE:
         // A read off the network is complete; do the paperwork.
         rv = DoReadComplete(rv);
@@ -506,38 +503,60 @@
 
 int SSLClientSocketMac::DoHandshake() {
   OSStatus status = SSLHandshake(ssl_context_);
-  int net_error = NetErrorFromOSStatus(status);
 
-  if (status == errSSLWouldBlock) {
+  if (status == errSSLWouldBlock)
     next_state_ = STATE_HANDSHAKE;
-  } else if (status == noErr) {
-    completed_handshake_ = true;  // We have a connection.
 
+  if (status == noErr) {
     server_cert_ = GetServerCert(ssl_context_);
-    DCHECK(server_cert_);
-    if (!ssl_config_.allowed_bad_certs.empty()) {
-      // Check server_cert_ because SecureTransport didn't verify it.
-      // TODO(wtc): If server_cert_ is not one of the allowed bad certificates,
-      // we should verify server_cert_ ourselves.  Since we don't know how to
-      // do that yet, treat it as an invalid certificate.
-      net_error = ERR_CERT_INVALID;
-      server_cert_status_ |= CERT_STATUS_INVALID;
-
-      for (size_t i = 0; i < ssl_config_.allowed_bad_certs.size(); ++i) {
-        if (server_cert_ == ssl_config_.allowed_bad_certs[i].cert) {
-          net_error = OK;
-          server_cert_status_ = ssl_config_.allowed_bad_certs[i].cert_status;
-          break;
-        }
-      }
-    }
-  } else if (IsCertificateError(net_error)) {
-    server_cert_ = GetServerCert(ssl_context_);
-    DCHECK(server_cert_);
-    server_cert_status_ |= MapNetErrorToCertStatus(net_error);
+    if (!server_cert_)
+      return ERR_UNEXPECTED;
+    next_state_ = STATE_VERIFY_CERT;
   }
 
-  return net_error;
+  return NetErrorFromOSStatus(status);
+}
+
+int SSLClientSocketMac::DoVerifyCert() {
+  next_state_ = STATE_VERIFY_CERT_COMPLETE;
+
+  if (!server_cert_)
+    return ERR_UNEXPECTED;
+
+  // Add each of the intermediate certificates in the server's chain to the
+  // server's X509Certificate object. This makes them available to
+  // X509Certificate::Verify() for chain building.
+  CFArrayRef certs;
+  OSStatus status = SSLCopyPeerCertificates(ssl_context_, &certs);
+  if (status != noErr || !certs)
+    return ERR_UNEXPECTED;
+  scoped_cftyperef<CFArrayRef> scoped_certs(certs);
+  CFIndex certs_length = CFArrayGetCount(certs);
+  for (CFIndex i = 1; i < certs_length; ++i) {
+    SecCertificateRef cert_ref = reinterpret_cast<SecCertificateRef>(
+        const_cast<void*>(CFArrayGetValueAtIndex(certs, i)));
+    server_cert_->AddIntermediateCertificate(cert_ref);
+  }
+
+  // TODO(hawk): set flags based on the SSLConfig, once SSLConfig is
+  // fully fleshed out on Mac OS X.
+  int flags = 0;
+  verifier_.reset(new CertVerifier);
+  return verifier_->Verify(server_cert_, hostname_, flags,
+                           &server_cert_verify_result_, &io_callback_);
+}
+
+int SSLClientSocketMac::DoVerifyCertComplete(int result) {
+  DCHECK(verifier_.get());
+  verifier_.reset();
+
+  if (IsCertificateError(result) && ssl_config_.IsAllowedBadCert(server_cert_))
+    result = OK;
+
+  completed_handshake_ = true;
+  DCHECK(next_state_ == STATE_NONE);
+
+  return result;
 }
 
 int SSLClientSocketMac::DoReadComplete(int result) {
diff --git a/net/socket/ssl_client_socket_mac.h b/net/socket/ssl_client_socket_mac.h
index 906bde59..a447055 100644
--- a/net/socket/ssl_client_socket_mac.h
+++ b/net/socket/ssl_client_socket_mac.h
@@ -11,12 +11,15 @@
 #include <vector>
 
 #include "base/scoped_ptr.h"
+#include "net/base/cert_verify_result.h"
 #include "net/base/completion_callback.h"
 #include "net/base/ssl_config_service.h"
 #include "net/socket/ssl_client_socket.h"
 
 namespace net {
 
+class CertVerifier;
+
 // An SSL client socket implemented with Secure Transport.
 class SSLClientSocketMac : public SSLClientSocket {
  public:
@@ -51,6 +54,8 @@
   int DoPayloadRead();
   int DoPayloadWrite();
   int DoHandshake();
+  int DoVerifyCert();
+  int DoVerifyCertComplete(int result);
   int DoReadComplete(int result);
   void OnWriteComplete(int result);
 
@@ -79,14 +84,17 @@
     STATE_PAYLOAD_READ,
     STATE_PAYLOAD_WRITE,
     STATE_HANDSHAKE,
+    STATE_VERIFY_CERT,
+    STATE_VERIFY_CERT_COMPLETE,
     STATE_READ_COMPLETE,
   };
   State next_state_;
   State next_io_state_;
 
-  // Set when handshake finishes.
   scoped_refptr<X509Certificate> server_cert_;
-  int server_cert_status_;
+  std::vector<scoped_refptr<X509Certificate> > intermediate_certs_;
+  scoped_ptr<CertVerifier> verifier_;
+  CertVerifyResult server_cert_verify_result_;
 
   bool completed_handshake_;
   SSLContextRef ssl_context_;
diff --git a/net/socket/ssl_client_socket_unittest.cc b/net/socket/ssl_client_socket_unittest.cc
index d565ab6f..aa94ff8c 100644
--- a/net/socket/ssl_client_socket_unittest.cc
+++ b/net/socket/ssl_client_socket_unittest.cc
@@ -56,35 +56,7 @@
 
 //-----------------------------------------------------------------------------
 
-#if defined(OS_MACOSX)
-// Status 6/19/09:
-//
-// If these tests are enabled on OSX, we choke at the point
-// SSLHandshake() (Security framework call) is called from
-// SSLClientSocketMac::DoHandshake().  Return value is -9812 (cert
-// valid but root not trusted), but if you don't have the cert in your
-// keychain as documented on
-// http://dev.chromium.org/developers/testing, the -9812 becomes a
-// -9813 (no root cert).
-//
-// See related handshake failures exhibited by disabled tests in
-// net/url_request/url_request_unittest.cc.
-#define MAYBE_Connect DISABLED_Connect
-#define MAYBE_ConnectExpired DISABLED_ConnectExpired
-#define MAYBE_ConnectMismatched DISABLED_ConnectMismatched
-#define MAYBE_Read DISABLED_Read
-#define MAYBE_Read_SmallChunks DISABLED_Read_SmallChunks
-#define MAYBE_Read_Interrupted DISABLED_Read_Interrupted
-#else
-#define MAYBE_Connect Connect
-#define MAYBE_ConnectExpired ConnectExpired
-#define MAYBE_ConnectMismatched ConnectMismatched
-#define MAYBE_Read Read
-#define MAYBE_Read_SmallChunks Read_SmallChunks
-#define MAYBE_Read_Interrupted Read_Interrupted
-#endif
-
-TEST_F(SSLClientSocketTest, MAYBE_Connect) {
+TEST_F(SSLClientSocketTest, Connect) {
   StartOKServer();
 
   net::AddressList addr;
@@ -121,7 +93,7 @@
   EXPECT_FALSE(sock->IsConnected());
 }
 
-TEST_F(SSLClientSocketTest, MAYBE_ConnectExpired) {
+TEST_F(SSLClientSocketTest, ConnectExpired) {
   StartExpiredServer();
 
   net::AddressList addr;
@@ -157,7 +129,7 @@
   // leave it connected.
 }
 
-TEST_F(SSLClientSocketTest, MAYBE_ConnectMismatched) {
+TEST_F(SSLClientSocketTest, ConnectMismatched) {
   StartMismatchedServer();
 
   net::AddressList addr;
@@ -199,7 +171,7 @@
 //   - Server closes the underlying TCP connection directly.
 //   - Server sends data unexpectedly.
 
-TEST_F(SSLClientSocketTest, MAYBE_Read) {
+TEST_F(SSLClientSocketTest, Read) {
   StartOKServer();
 
   net::AddressList addr;
@@ -259,7 +231,7 @@
   }
 }
 
-TEST_F(SSLClientSocketTest, MAYBE_Read_SmallChunks) {
+TEST_F(SSLClientSocketTest, Read_SmallChunks) {
   StartOKServer();
 
   net::AddressList addr;
@@ -314,7 +286,7 @@
   }
 }
 
-TEST_F(SSLClientSocketTest, MAYBE_Read_Interrupted) {
+TEST_F(SSLClientSocketTest, Read_Interrupted) {
   StartOKServer();
 
   net::AddressList addr;
diff --git a/net/socket/ssl_test_util.cc b/net/socket/ssl_test_util.cc
index b0fa20e..7e73f1e 100644
--- a/net/socket/ssl_test_util.cc
+++ b/net/socket/ssl_test_util.cc
@@ -24,6 +24,10 @@
 #include <pk11pub.h>
 #undef Lock
 #include "base/nss_init.h"
+#elif defined(OS_MACOSX)
+#include <Security/Security.h>
+#include "base/scoped_cftyperef.h"
+#include "net/base/x509_certificate.h"
 #endif
 
 #include "base/file_util.h"
@@ -81,10 +85,48 @@
 }
 #endif
 
+#if defined(OS_MACOSX)
+static net::X509Certificate* LoadTemporaryCert(const FilePath& filename) {
+  std::string rawcert;
+  if (!file_util::ReadFileToString(filename.ToWStringHack(), &rawcert)) {
+    LOG(ERROR) << "Can't load certificate " << filename.ToWStringHack();
+    return NULL;
+  }
+
+  CFDataRef pem = CFDataCreate(kCFAllocatorDefault,
+                               reinterpret_cast<const UInt8*>(rawcert.data()),
+                               static_cast<CFIndex>(rawcert.size()));
+  if (!pem)
+    return NULL;
+  scoped_cftyperef<CFDataRef> scoped_pem(pem);
+
+  SecExternalFormat input_format = kSecFormatUnknown;
+  SecExternalItemType item_type = kSecItemTypeUnknown;
+  CFArrayRef cert_array = NULL;
+  if (SecKeychainItemImport(pem, NULL, &input_format, &item_type, 0, NULL, NULL,
+                            &cert_array))
+    return NULL;
+  scoped_cftyperef<CFArrayRef> scoped_cert_array(cert_array);
+
+  if (!CFArrayGetCount(cert_array))
+    return NULL;
+
+  SecCertificateRef cert_ref = static_cast<SecCertificateRef>(
+      const_cast<void*>(CFArrayGetValueAtIndex(cert_array, 0)));
+  CFRetain(cert_ref);
+  return net::X509Certificate::CreateFromHandle(cert_ref,
+      net::X509Certificate::SOURCE_FROM_NETWORK);
+}
+#endif
+
 }  // namespace
 
 namespace net {
 
+#if defined(OS_MACOSX)
+void SetMacTestCertificate(X509Certificate* cert);
+#endif
+
 // static
 const char TestServerLauncher::kHostName[] = "127.0.0.1";
 const char TestServerLauncher::kMismatchedHostName[] = "localhost";
@@ -317,6 +359,8 @@
 #if defined(OS_LINUX)
   if (cert_)
     CERT_DestroyCertificate(reinterpret_cast<CERTCertificate*>(cert_));
+#elif defined(OS_MACOSX)
+  SetMacTestCertificate(NULL);
 #endif
   Stop();
 }
@@ -353,6 +397,12 @@
           LoadTemporaryCert(GetRootCertPath()));
   DCHECK(cert_);
   return (cert_ != NULL);
+#elif defined(OS_MACOSX)
+  X509Certificate* cert = LoadTemporaryCert(GetRootCertPath());
+  if (!cert)
+    return false;
+  SetMacTestCertificate(cert);
+  return true;
 #else
   return true;
 #endif