From 010e27ec98de24f68648b8c3ac68f3408f0578c0 Mon Sep 17 00:00:00 2001 From: "hawk@chromium.org" Date: Thu, 27 Aug 2009 17:49:41 +0000 Subject: 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 --- net/base/x509_certificate.cc | 13 +- net/base/x509_certificate.h | 12 + net/base/x509_certificate_mac.cc | 408 +++++++++++++++++++++++++++++-- net/socket/ssl_client_socket_mac.cc | 109 +++++---- net/socket/ssl_client_socket_mac.h | 12 +- net/socket/ssl_client_socket_unittest.cc | 40 +-- net/socket/ssl_test_util.cc | 50 ++++ 7 files changed, 547 insertions(+), 97 deletions(-) (limited to 'net') diff --git a/net/base/x509_certificate.cc b/net/base/x509_certificate.cc index 7a5a669..e8db7c7 100644 --- a/net/base/x509_certificate.cc +++ b/net/base/x509_certificate.cc @@ -169,7 +169,11 @@ X509Certificate* X509Certificate::CreateFromBytes(const char* data, } 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 @@ X509Certificate::X509Certificate(const std::string& subject, 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::~X509Certificate() { 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..4ae6554 100644 --- a/net/base/x509_certificate.h +++ b/net/base/x509_certificate.h @@ -209,6 +209,12 @@ class X509Certificate : public base::RefCountedThreadSafe { // 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 @@ class X509Certificate : public base::RefCountedThreadSafe { // 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 2e75489..d55a770 100644 --- a/net/base/x509_certificate_mac.cc +++ b/net/base/x509_certificate_mac.cc @@ -7,23 +7,170 @@ #include #include +#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 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; + + // 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 test_certificate_; + + // The trusted cache may be accessed from multiple threads. + mutable Lock lock_; + + DISALLOW_COPY_AND_ASSIGN(MacTrustedCertificates); +}; + +void SetMacTestCertificate(X509Certificate* cert) { + Singleton::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* 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::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 common_names, locality_names, state_names, @@ -262,26 +409,257 @@ void X509Certificate::GetDNSNames(std::vector* dns_names) const { 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 + 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 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(&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 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( + const_cast(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 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::get()->CopyTrustedCertificateArray(); + scoped_cftyperef 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(&tp_action_data), + sizeof(tp_action_data)); + if (!action_data_ref) + return ERR_OUT_OF_MEMORY; + scoped_cftyperef 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 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 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( + 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 @@ SSLClientSocketMac::SSLClientSocketMac(ClientSocket* transport_socket, 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 @@ int SSLClientSocketMac::Connect(CompletionCallback* callback) { 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 @@ void SSLClientSocketMac::GetSSLInfo(SSLInfo* ssl_info) { 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 @@ int SSLClientSocketMac::DoLoop(int last_io_result) { // 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::DoLoop(int last_io_result) { 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 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 scoped_certs(certs); + CFIndex certs_length = CFArrayGetCount(certs); + for (CFIndex i = 1; i < certs_length; ++i) { + SecCertificateRef cert_ref = reinterpret_cast( + const_cast(CFArrayGetValueAtIndex(certs, i))); + server_cert_->AddIntermediateCertificate(cert_ref); } - return net_error; + // 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 906bde5..a447055 100644 --- a/net/socket/ssl_client_socket_mac.h +++ b/net/socket/ssl_client_socket_mac.h @@ -11,12 +11,15 @@ #include #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 @@ class SSLClientSocketMac : public SSLClientSocket { int DoPayloadRead(); int DoPayloadWrite(); int DoHandshake(); + int DoVerifyCert(); + int DoVerifyCertComplete(int result); int DoReadComplete(int result); void OnWriteComplete(int result); @@ -79,14 +84,17 @@ class SSLClientSocketMac : public SSLClientSocket { 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 server_cert_; - int server_cert_status_; + std::vector > intermediate_certs_; + scoped_ptr 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 d565ab6..aa94ff8 100644 --- a/net/socket/ssl_client_socket_unittest.cc +++ b/net/socket/ssl_client_socket_unittest.cc @@ -56,35 +56,7 @@ class SSLClientSocketTest : public PlatformTest { //----------------------------------------------------------------------------- -#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 @@ TEST_F(SSLClientSocketTest, MAYBE_Connect) { EXPECT_FALSE(sock->IsConnected()); } -TEST_F(SSLClientSocketTest, MAYBE_ConnectExpired) { +TEST_F(SSLClientSocketTest, ConnectExpired) { StartExpiredServer(); net::AddressList addr; @@ -157,7 +129,7 @@ TEST_F(SSLClientSocketTest, MAYBE_ConnectExpired) { // leave it connected. } -TEST_F(SSLClientSocketTest, MAYBE_ConnectMismatched) { +TEST_F(SSLClientSocketTest, ConnectMismatched) { StartMismatchedServer(); net::AddressList addr; @@ -199,7 +171,7 @@ TEST_F(SSLClientSocketTest, MAYBE_ConnectMismatched) { // - 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) { } } -TEST_F(SSLClientSocketTest, MAYBE_Read_SmallChunks) { +TEST_F(SSLClientSocketTest, Read_SmallChunks) { StartOKServer(); net::AddressList addr; @@ -314,7 +286,7 @@ TEST_F(SSLClientSocketTest, MAYBE_Read_SmallChunks) { } } -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 #undef Lock #include "base/nss_init.h" +#elif defined(OS_MACOSX) +#include +#include "base/scoped_cftyperef.h" +#include "net/base/x509_certificate.h" #endif #include "base/file_util.h" @@ -81,10 +85,48 @@ static CERTCertificate* LoadTemporaryCert(const FilePath& filename) { } #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(rawcert.data()), + static_cast(rawcert.size())); + if (!pem) + return NULL; + scoped_cftyperef 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 scoped_cert_array(cert_array); + + if (!CFArrayGetCount(cert_array)) + return NULL; + + SecCertificateRef cert_ref = static_cast( + const_cast(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 @@ TestServerLauncher::~TestServerLauncher() { #if defined(OS_LINUX) if (cert_) CERT_DestroyCertificate(reinterpret_cast(cert_)); +#elif defined(OS_MACOSX) + SetMacTestCertificate(NULL); #endif Stop(); } @@ -353,6 +397,12 @@ bool TestServerLauncher::LoadTestRootCert() { 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 -- cgit v1.1