summaryrefslogtreecommitdiffstats
path: root/net
diff options
context:
space:
mode:
authorsnej@chromium.org <snej@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-02-18 22:45:05 +0000
committersnej@chromium.org <snej@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-02-18 22:45:05 +0000
commit3e1fc8ea295d450e864b8e156617399f4f2f672f (patch)
tree25cf9b717968f6236d0ce467ac2fc63411ef7867 /net
parente5430c9883c5196cf768b4c9998d1e5209ed7606 (diff)
downloadchromium_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.h12
-rw-r--r--net/base/x509_certificate_mac.cc202
-rw-r--r--net/socket/ssl_client_socket_mac.cc177
-rw-r--r--net/socket/ssl_client_socket_mac.h2
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);