diff options
author | snej@chromium.org <snej@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-02-18 22:45:05 +0000 |
---|---|---|
committer | snej@chromium.org <snej@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-02-18 22:45:05 +0000 |
commit | 3e1fc8ea295d450e864b8e156617399f4f2f672f (patch) | |
tree | 25cf9b717968f6236d0ce467ac2fc63411ef7867 /net | |
parent | e5430c9883c5196cf768b4c9998d1e5209ed7606 (diff) | |
download | chromium_src-3e1fc8ea295d450e864b8e156617399f4f2f672f.zip chromium_src-3e1fc8ea295d450e864b8e156617399f4f2f672f.tar.gz chromium_src-3e1fc8ea295d450e864b8e156617399f4f2f672f.tar.bz2 |
Client-side SSL cert support for Mac.
This includes sending an existing identity cert, and asking the user which cert to use. Doesn't yet handle SSL renegotiation, or key-gen.
BUG=16831
TEST=none
Review URL: http://codereview.chromium.org/604067
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@39389 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net')
-rw-r--r-- | net/base/x509_certificate.h | 12 | ||||
-rw-r--r-- | net/base/x509_certificate_mac.cc | 202 | ||||
-rw-r--r-- | net/socket/ssl_client_socket_mac.cc | 177 | ||||
-rw-r--r-- | net/socket/ssl_client_socket_mac.h | 2 |
4 files changed, 317 insertions, 76 deletions
diff --git a/net/base/x509_certificate.h b/net/base/x509_certificate.h index 215489b..b9a3995 100644 --- a/net/base/x509_certificate.h +++ b/net/base/x509_certificate.h @@ -224,6 +224,18 @@ class X509Certificate : public base::RefCountedThreadSafe<X509Certificate> { } #endif +#if defined(OS_MACOSX) + // Creates a security policy for SSL client certificates. + static OSStatus CreateSSLClientPolicy(SecPolicyRef* outPolicy); + + // Adds all available SSL client identity certs to the given vector. + static bool GetSSLClientCertificates( + std::vector<scoped_refptr<X509Certificate> >* certs); + + // Creates the chain of certs to use for this client identity cert. + CFArrayRef CreateClientCertificateChain(); +#endif + // Verifies the certificate against the given hostname. Returns OK if // successful or an error code upon failure. // diff --git a/net/base/x509_certificate_mac.cc b/net/base/x509_certificate_mac.cc index 2b96be4..9487410 100644 --- a/net/base/x509_certificate_mac.cc +++ b/net/base/x509_certificate_mac.cc @@ -356,6 +356,56 @@ void GetCertDateForOID(X509Certificate::OSCertHandle cert_handle, } } +// Returns true if this cert supports a given extended key usage. +bool CertSupportsUsage(SecCertificateRef cert, const CSSM_OID& usage_oid) { + CSSMFields fields; + if (GetCertFields(cert, &fields) != noErr) + return false; + for (unsigned f = 0; f < fields.num_of_fields; ++f) { + const CSSM_FIELD &field = fields.fields[f]; + if (CSSMOIDEqual(&field.FieldOid, &CSSMOID_ExtendedKeyUsage)) { + const CSSM_X509_EXTENSION* ext = + reinterpret_cast<const CSSM_X509_EXTENSION*>(field.FieldValue.Data); + const CE_ExtendedKeyUsage* usage = + reinterpret_cast<const CE_ExtendedKeyUsage*>(ext->value.parsedValue); + for (unsigned p = 0; p < usage->numPurposes; ++p) { + if (CSSMOIDEqual(&usage->purposes[p], &usage_oid)) + return true; + } + } + } + return false; +} + +// Creates a SecPolicyRef for the given OID, with optional value. +OSStatus CreatePolicy(const CSSM_OID* policy_OID, + void* option_data, + size_t option_length, + SecPolicyRef* policy) { + SecPolicySearchRef search; + OSStatus err = SecPolicySearchCreate(CSSM_CERT_X_509v3, policy_OID, NULL, + &search); + if (err) + return err; + err = SecPolicySearchCopyNext(search, policy); + CFRelease(search); + if (err) + return err; + + if (option_data) { + CSSM_DATA options_data = { + option_length, + reinterpret_cast<uint8_t*>(option_data) + }; + err = SecPolicySetValue(*policy, &options_data); + if (err) { + CFRelease(*policy); + return err; + } + } + return noErr; +} + } // namespace void X509Certificate::Initialize() { @@ -419,29 +469,20 @@ int X509Certificate::Verify(const std::string& hostname, int flags, // 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); + CSSM_APPLE_TP_SSL_OPTIONS tp_ssl_options = { + CSSM_APPLE_TP_SSL_OPTS_VERSION, + hostname.size(), + hostname.data(), + 0 + }; + SecPolicyRef ssl_policy; + OSStatus status = CreatePolicy(&CSSMOID_APPLE_TP_SSL, + &tp_ssl_options, + sizeof(tp_ssl_options), + &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 @@ -691,4 +732,125 @@ X509Certificate::Fingerprint X509Certificate::CalculateFingerprint( return sha1; } +// static +OSStatus X509Certificate::CreateSSLClientPolicy(SecPolicyRef* out_policy) { + CSSM_APPLE_TP_SSL_OPTIONS tp_ssl_options = { + CSSM_APPLE_TP_SSL_OPTS_VERSION, + 0, + NULL, + CSSM_APPLE_TP_SSL_CLIENT + }; + return CreatePolicy(&CSSMOID_APPLE_TP_SSL, + &tp_ssl_options, + sizeof(tp_ssl_options), + out_policy); +} + +// static +bool X509Certificate::GetSSLClientCertificates ( + std::vector<scoped_refptr<X509Certificate> >* certs) { + SecIdentitySearchRef search = nil; + OSStatus err = SecIdentitySearchCreate(NULL, CSSM_KEYUSE_SIGN, &search); + fprintf(stderr,"====Created SecIdentitySearch %p\n",search);//TEMP + scoped_cftyperef<SecIdentitySearchRef> scoped_search(search); + while (!err) { + SecIdentityRef identity = NULL; + err = SecIdentitySearchCopyNext(search, &identity); + if (err) + break; + scoped_cftyperef<SecIdentityRef> scoped_identity(identity); + + SecCertificateRef cert_handle; + err = SecIdentityCopyCertificate(identity, &cert_handle); + if (err != noErr) + continue; + + scoped_refptr<X509Certificate> cert( + CreateFromHandle(cert_handle, SOURCE_LONE_CERT_IMPORT)); + // cert_handle is adoped by cert, so I don't need to release it myself. + if (cert->HasExpired() || + !CertSupportsUsage(cert_handle, CSSMOID_ClientAuth)) + continue; + + // Skip duplicates (a cert may be in multiple keychains). + X509Certificate::Fingerprint fingerprint = cert->fingerprint(); + unsigned i; + for (i = 0; i < certs->size(); ++i) { + if ((*certs)[i]->fingerprint().Equals(fingerprint)) + break; + } + if (i < certs->size()) + continue; + + // The cert passes, so add it to the vector. + certs->push_back(cert); + } + + if (err != errSecItemNotFound) { + LOG(ERROR) << "SecIdentitySearch error " << err; + return false; + } + return true; +} + +CFArrayRef X509Certificate::CreateClientCertificateChain() { + // Initialize the result array with just the IdentityRef of the receiver: + OSStatus result; + SecIdentityRef identity; + result = SecIdentityCreateWithCertificate(NULL, cert_handle_, &identity); + if (result) { + LOG(ERROR) << "SecIdentityCreateWithCertificate error " << result; + return NULL; + } + scoped_cftyperef<CFMutableArrayRef> chain( + CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks)); + CFArrayAppendValue(chain, identity); + + { + // Create an SSL policy ref configured for client cert evaluation. + SecPolicyRef ssl_policy; + result = CreateSSLClientPolicy(&ssl_policy); + if (result) + goto exit; + scoped_cftyperef<SecPolicyRef> scoped_ssl_policy(ssl_policy); + + // Use a SecTrust object to find the intermediate certs in the trust chain. + scoped_cftyperef<CFArrayRef> input_certs( + CFArrayCreate(NULL, (const void**)&cert_handle_, 1, + &kCFTypeArrayCallBacks)); + SecTrustRef trust_ref = NULL; + result = SecTrustCreateWithCertificates(input_certs, + ssl_policy, + &trust_ref); + if (result) + goto exit; + scoped_cftyperef<SecTrustRef> trust(trust_ref); + + SecTrustResultType status; + CFArrayRef trust_chain = NULL; + CSSM_TP_APPLE_EVIDENCE_INFO* status_chain; + result = SecTrustEvaluate(trust, &status); + if (result) + goto exit; + result = SecTrustGetResult(trust, &status, &trust_chain, &status_chain); + if (result) + goto exit; + + // Append the intermediate certs from SecTrust to the result array: + if (trust_chain) { + int chain_count = CFArrayGetCount(trust_chain); + if (chain_count > 1) { + CFArrayAppendArray(chain, + trust_chain, + CFRangeMake(1, chain_count - 1)); + } + CFRelease(trust_chain); + } + } +exit: + if (result) + LOG(ERROR) << "CreateIdentityCertificateChain error " << result; + return chain.release(); +} + } // namespace net diff --git a/net/socket/ssl_client_socket_mac.cc b/net/socket/ssl_client_socket_mac.cc index 2fb43d1..c9c2123 100644 --- a/net/socket/ssl_client_socket_mac.cc +++ b/net/socket/ssl_client_socket_mac.cc @@ -13,6 +13,7 @@ #include "net/base/io_buffer.h" #include "net/base/load_log.h" #include "net/base/net_errors.h" +#include "net/base/ssl_cert_request_info.h" #include "net/base/ssl_info.h" // Welcome to Mac SSL. We've been waiting for you. @@ -93,19 +94,21 @@ namespace net { namespace { +#if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_5 // Declarations needed to call the 10.5.7 and later SSLSetSessionOption() // function when building with the 10.5.0 SDK. typedef enum { - kSSLSessionOptionBreakOnServerAuthFlag + kSSLSessionOptionBreakOnServerAuth, + kSSLSessionOptionBreakOnCertRequested, } SSLSetSessionOptionType; enum { - errSSLServerAuthCompletedFlag = -9841 + errSSLServerAuthCompleted = -9841, + errSSLClientCertRequested = -9842, }; // When compiled against the Mac OS X 10.5 SDK, define symbolic constants for // cipher suites added in Mac OS X 10.6. -#if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_5 enum { // ECC cipher suites from RFC 4492. TLS_ECDH_ECDSA_WITH_NULL_SHA = 0xC001, @@ -173,13 +176,17 @@ int NetErrorFromOSStatus(OSStatus status) { case errSSLXCertChainInvalid: case errSSLBadCert: return ERR_CERT_INVALID; - case errSSLPeerCertRevoked: - return ERR_CERT_REVOKED; case errSSLClosedGraceful: case noErr: return OK; + case errSSLPeerCertUnknown...errSSLPeerBadCert: + case errSSLPeerInsufficientSecurity...errSSLPeerUnknownCA: + // (Note that all errSSLPeer* codes indicate errors reported by the + // peer, so the cert-related ones refer to my _client_ cert.) + return ERR_BAD_SSL_CLIENT_AUTH_CERT; + case errSSLBadRecordMac: case errSSLBufferOverflow: case errSSLDecryptionFail: @@ -425,6 +432,16 @@ X509Certificate* GetServerCert(SSLContextRef ssl_context) { return x509_cert; } +// Dynamically look up a pointer to a function exported by a bundle. +template <typename FNTYPE> +FNTYPE LookupFunction(CFStringRef bundleName, CFStringRef fnName) { + CFBundleRef bundle = CFBundleGetBundleWithIdentifier(bundleName); + if (!bundle) + return NULL; + return reinterpret_cast<FNTYPE>( + CFBundleGetFunctionPointerForName(bundle, fnName)); +} + // A class that wraps an array of enabled cipher suites that can be passed to // SSLSetEnabledCiphers. // @@ -634,7 +651,10 @@ void SSLClientSocketMac::GetSSLInfo(SSLInfo* ssl_info) { void SSLClientSocketMac::GetSSLCertRequestInfo( SSLCertRequestInfo* cert_request_info) { - // TODO(wtc): implement this. + // I'm being asked for available client certs (identities). + cert_request_info->host_and_port = hostname_; + cert_request_info->client_certs.clear(); + X509Certificate::GetSSLClientCertificates(&cert_request_info->client_certs); } SSLClientSocket::NextProtoStatus @@ -706,42 +726,46 @@ int SSLClientSocketMac::InitializeSSLContext() { // the session would be cached before we verified the certificate, leaving // the potential for a session in which the certificate failed to validate // to still be able to be resumed. - CFBundleRef bundle = - CFBundleGetBundleWithIdentifier(CFSTR("com.apple.security")); - if (bundle) { - SSLSetSessionOptionFuncPtr ssl_set_session_options = - reinterpret_cast<SSLSetSessionOptionFuncPtr>( - CFBundleGetFunctionPointerForName(bundle, - CFSTR("SSLSetSessionOption"))); - if (ssl_set_session_options) { + SSLSetSessionOptionFuncPtr ssl_set_session_options = + LookupFunction<SSLSetSessionOptionFuncPtr>(CFSTR("com.apple.security"), + CFSTR("SSLSetSessionOption")); + if (ssl_set_session_options) { + status = ssl_set_session_options(ssl_context_, + kSSLSessionOptionBreakOnServerAuth, + true); + if (!status) status = ssl_set_session_options(ssl_context_, - kSSLSessionOptionBreakOnServerAuthFlag, + kSSLSessionOptionBreakOnCertRequested, true); + if (status) + return NetErrorFromOSStatus(status); + + // Concatenate the hostname and peer address to use as the peer ID. To + // resume a session, we must connect to the same server on the same port + // using the same hostname (i.e., localhost and 127.0.0.1 are considered + // different peers, which puts us through certificate validation again + // and catches hostname/certificate name mismatches. + struct sockaddr_storage addr; + socklen_t addr_length = sizeof(struct sockaddr_storage); + memset(&addr, 0, sizeof(addr)); + if (!transport_->GetPeerName(reinterpret_cast<struct sockaddr*>(&addr), + &addr_length)) { + // Assemble the socket hostname and address into a single buffer. + std::vector<char> peer_id(hostname_.begin(), hostname_.end()); + peer_id.insert(peer_id.end(), reinterpret_cast<char*>(&addr), + reinterpret_cast<char*>(&addr) + addr_length); + + // SSLSetPeerID() treats peer_id as a binary blob, and makes its + // own copy. + status = SSLSetPeerID(ssl_context_, &peer_id[0], peer_id.size()); if (status) return NetErrorFromOSStatus(status); - - // Concatenate the hostname and peer address to use as the peer ID. To - // resume a session, we must connect to the same server on the same port - // using the same hostname (i.e., localhost and 127.0.0.1 are considered - // different peers, which puts us through certificate validation again - // and catches hostname/certificate name mismatches. - struct sockaddr_storage addr; - socklen_t addr_length = sizeof(struct sockaddr_storage); - memset(&addr, 0, sizeof(addr)); - if (!transport_->GetPeerName(reinterpret_cast<struct sockaddr*>(&addr), - &addr_length)) { - // Assemble the socket hostname and address into a single buffer. - std::vector<char> peer_id(hostname_.begin(), hostname_.end()); - peer_id.insert(peer_id.end(), reinterpret_cast<char*>(&addr), - reinterpret_cast<char*>(&addr) + addr_length); - - // SSLSetPeerID() treats peer_id as a binary blob, and makes its - // own copy. - status = SSLSetPeerID(ssl_context_, &peer_id[0], peer_id.size()); - if (status) - return NetErrorFromOSStatus(status); - } } + } else { + // If I can't break on cert-requested, then set the cert up-front: + status = SetClientCert(); + if (status) + return NetErrorFromOSStatus(status); } return OK; @@ -877,14 +901,14 @@ int SSLClientSocketMac::DoHandshakeStart() { if (status == errSSLWouldBlock) next_handshake_state_ = STATE_HANDSHAKE_START; - if (status == noErr || status == errSSLServerAuthCompletedFlag) { + if (status == noErr || status == errSSLServerAuthCompleted) { // TODO(hawk): we verify the certificate chain even on resumed sessions // so that we have the certificate status (valid, expired but overridden // by the user, EV, etc.) available. Eliminate this step once we have // a certificate validation result cache. next_handshake_state_ = STATE_VERIFY_CERT; - if (status == errSSLServerAuthCompletedFlag) { - // Override errSSLServerAuthCompletedFlag as it's not actually an error, + if (status == errSSLServerAuthCompleted) { + // Override errSSLServerAuthCompleted as it's not actually an error, // but rather an indication that we're only half way through the // handshake. handshake_interrupted_ = true; @@ -946,18 +970,50 @@ int SSLClientSocketMac::DoVerifyCertComplete(int result) { return result; } -int SSLClientSocketMac::DoHandshakeFinish() { - OSStatus status = SSLHandshake(ssl_context_); +int SSLClientSocketMac::SetClientCert() { + if (!ssl_config_.send_client_cert || !ssl_config_.client_cert) + return noErr; - if (status == errSSLWouldBlock) - next_handshake_state_ = STATE_HANDSHAKE_FINISH; + scoped_cftyperef<CFArrayRef> cert_refs( + ssl_config_.client_cert->CreateClientCertificateChain()); + OSStatus result = SSLSetCertificate(ssl_context_, cert_refs); + if (result) + LOG(ERROR) << "SSLSetCertificate returned OSStatus " << result; + return result; +} - if (status == errSSLClosedGraceful) - return ERR_SSL_PROTOCOL_ERROR; +int SSLClientSocketMac::DoHandshakeFinish() { + OSStatus status = SSLHandshake(ssl_context_); - if (status == noErr) { - completed_handshake_ = true; - DCHECK(next_handshake_state_ == STATE_NONE); + switch (status) { + case errSSLWouldBlock: + next_handshake_state_ = STATE_HANDSHAKE_FINISH; + break; + case errSSLClientCertRequested: + status = SetClientCert(); + next_handshake_state_ = STATE_HANDSHAKE_FINISH; + break; + case errSSLClosedGraceful: + return ERR_SSL_PROTOCOL_ERROR; + case errSSLClosedAbort: + case errSSLPeerHandshakeFail: { + // See if the server aborted due to client cert checking. + SSLClientCertificateState clientState; + if (SSLGetClientCertificateState(ssl_context_, &clientState) == noErr && + clientState > kSSLClientCertNone) { + if (clientState == kSSLClientCertRequested && + !ssl_config_.send_client_cert) + return ERR_SSL_CLIENT_AUTH_CERT_NEEDED; + return ERR_BAD_SSL_CLIENT_AUTH_CERT; + } + break; + } + case noErr: + completed_handshake_ = true; + DCHECK(next_handshake_state_ == STATE_NONE); + break; + default: + break; } return NetErrorFromOSStatus(status); @@ -979,14 +1035,23 @@ int SSLClientSocketMac::DoPayloadRead() { if (processed > 0) return processed; - if (status == errSSLClosedNoNotify) { - // TODO(wtc): Unless we have received the close_notify alert, we need to - // return an error code indicating that the SSL connection ended - // uncleanly, a potential truncation attack. See http://crbug.com/18586. - return OK; - } + switch (status) { + case errSSLClosedNoNotify: + // TODO(wtc): Unless we have received the close_notify alert, we need to + // return an error code indicating that the SSL connection ended + // uncleanly, a potential truncation attack. See http://crbug.com/18586. + return OK; - return NetErrorFromOSStatus(status); + case errSSLServerAuthCompleted: + case errSSLClientCertRequested: + // Server wants to renegotiate, probably to ask for a client cert. + // We don't support this yet, so abort. + // TODO(snej): Work around the SecureTransport issues. + return ERR_BAD_SSL_CLIENT_AUTH_CERT; + + default: + return NetErrorFromOSStatus(status); + } } int SSLClientSocketMac::DoPayloadWrite() { diff --git a/net/socket/ssl_client_socket_mac.h b/net/socket/ssl_client_socket_mac.h index 3176116..6b2eb48 100644 --- a/net/socket/ssl_client_socket_mac.h +++ b/net/socket/ssl_client_socket_mac.h @@ -71,6 +71,8 @@ class SSLClientSocketMac : public SSLClientSocket { int DoVerifyCertComplete(int result); int DoHandshakeFinish(); + int SetClientCert(); + static OSStatus SSLReadCallback(SSLConnectionRef connection, void* data, size_t* data_length); |