diff options
author | wtc@chromium.org <wtc@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-11-04 16:21:33 +0000 |
---|---|---|
committer | wtc@chromium.org <wtc@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-11-04 16:21:33 +0000 |
commit | 4a842345af02ac3ae84015683383539a84f66d8c (patch) | |
tree | 9b9b005ddea77d514f2bf0b32f83bf0e5f2bd746 /net/socket | |
parent | 1fd26d8cdd166ec84c06bb7d4a8557293428fc0e (diff) | |
download | chromium_src-4a842345af02ac3ae84015683383539a84f66d8c.zip chromium_src-4a842345af02ac3ae84015683383539a84f66d8c.tar.gz chromium_src-4a842345af02ac3ae84015683383539a84f66d8c.tar.bz2 |
Support for using OS-native certificates for SSL client
auth.
Known Limitations:
- Only SSL3/TLS1.0 handshakes are supported. It's unlikely
SSLv2 will/should ever be implemented. NSS does not yet
support TLS1.1/1.2.
- On Windows, only CryptoAPI keys are supported. Keys that
can only be accessed via CNG will fail.
Technical Notes:
Windows:
- Only the AT_KEYEXCHANGE key is used, per
http://msdn.microsoft.com/en-us/library/aa387461(VS.85).aspx
- CryptSetHashParam is used to directly set the hash value.
This *should* be supported by all CSPs that are compatible
with RSA/SChannel, AFAICT, but testing is needed.
NSS:
- The define NSS_PLATFORM_CLIENT_AUTH is used to guard all
of the new/patched code. The primary implementation
details are in sslplatf.c.
Patch author: Ryan Sleevi <rsleevi@chromium.org>
Original review URL: http://codereview.chromium.org/2828002
BUG=148,37560,45369
TEST=Attempt to authenticate with a site that requires SSL
client authentication (e.g., https://foaf.me/simpleLogin.php
with a FOAF+SSL client certificate).
Review URL: http://codereview.chromium.org/3455019
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@65064 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/socket')
-rw-r--r-- | net/socket/client_socket_factory.cc | 7 | ||||
-rw-r--r-- | net/socket/ssl_client_socket_nss.cc | 185 | ||||
-rw-r--r-- | net/socket/ssl_client_socket_nss.h | 8 | ||||
-rw-r--r-- | net/socket/ssl_client_socket_nss_factory.cc | 12 |
4 files changed, 174 insertions, 38 deletions
diff --git a/net/socket/client_socket_factory.cc b/net/socket/client_socket_factory.cc index 90bcdc5..9a7decf 100644 --- a/net/socket/client_socket_factory.cc +++ b/net/socket/client_socket_factory.cc @@ -14,7 +14,6 @@ #elif defined(USE_NSS) #include "net/socket/ssl_client_socket_nss.h" #elif defined(OS_MACOSX) -#include "net/socket/ssl_client_socket_mac.h" #include "net/socket/ssl_client_socket_nss.h" #endif #include "net/socket/ssl_host_info.h" @@ -38,12 +37,6 @@ SSLClientSocket* DefaultSSLClientSocketFactory( return new SSLClientSocketNSS(transport_socket, hostname, ssl_config, shi.release()); #elif defined(OS_MACOSX) - // TODO(wtc): SSLClientSocketNSS can't do SSL client authentication using - // Mac OS X CDSA/CSSM yet (http://crbug.com/45369), so fall back on - // SSLClientSocketMac. - if (ssl_config.send_client_cert) - return new SSLClientSocketMac(transport_socket, hostname, ssl_config); - return new SSLClientSocketNSS(transport_socket, hostname, ssl_config, shi.release()); #else diff --git a/net/socket/ssl_client_socket_nss.cc b/net/socket/ssl_client_socket_nss.cc index df2ac6a..a26e77d4 100644 --- a/net/socket/ssl_client_socket_nss.cc +++ b/net/socket/ssl_client_socket_nss.cc @@ -50,6 +50,9 @@ #if defined(USE_SYSTEM_SSL) #include <dlfcn.h> #endif +#if defined(OS_MACOSX) +#include <Security/Security.h> +#endif #include <certdb.h> #include <hasht.h> #include <keyhi.h> @@ -214,6 +217,8 @@ int MapNSPRError(PRErrorCode err) { return ERR_INVALID_ARGUMENT; case PR_END_OF_FILE_ERROR: return ERR_CONNECTION_CLOSED; + case PR_NOT_IMPLEMENTED_ERROR: + return ERR_NOT_IMPLEMENTED; case SEC_ERROR_INVALID_ARGS: return ERR_INVALID_ARGUMENT; @@ -800,7 +805,12 @@ int SSLClientSocketNSS::InitializeSSLOptions() { return ERR_UNEXPECTED; } +#if defined(NSS_PLATFORM_CLIENT_AUTH) + rv = SSL_GetPlatformClientAuthDataHook(nss_fd_, PlatformClientAuthHandler, + this); +#else rv = SSL_GetClientAuthDataHook(nss_fd_, ClientAuthHandler, this); +#endif if (rv != SECSuccess) { LogFailedNSSFunction(net_log_, "SSL_GetClientAuthDataHook", ""); return ERR_UNEXPECTED; @@ -1364,6 +1374,8 @@ static PRErrorCode MapErrorToNSS(int result) { case ERR_NETWORK_ACCESS_DENIED: // For connect, this could be mapped to PR_ADDRESS_NOT_SUPPORTED_ERROR. return PR_NO_ACCESS_RIGHTS_ERROR; + case ERR_NOT_IMPLEMENTED: + return PR_NOT_IMPLEMENTED_ERROR; case ERR_INTERNET_DISCONNECTED: // Equivalent to ENETDOWN. return PR_NETWORK_UNREACHABLE_ERROR; // Best approximation. case ERR_CONNECTION_TIMED_OUT: @@ -1650,29 +1662,69 @@ SECStatus SSLClientSocketNSS::OwnAuthCertHandler(void* arg, return SECSuccess; } +#if defined(NSS_PLATFORM_CLIENT_AUTH) // static // NSS calls this if a client certificate is needed. -// Based on Mozilla's NSS_GetClientAuthData. -SECStatus SSLClientSocketNSS::ClientAuthHandler( +SECStatus SSLClientSocketNSS::PlatformClientAuthHandler( void* arg, PRFileDesc* socket, CERTDistNames* ca_names, - CERTCertificate** result_certificate, - SECKEYPrivateKey** result_private_key) { - // NSS passes a null ca_names if SSL 2.0 is used. Just fail rather than - // trying to make this work, as we plan to remove SSL 2.0 support soon. - if (!ca_names) - return SECFailure; - + CERTCertList** result_certs, + void** result_private_key) { SSLClientSocketNSS* that = reinterpret_cast<SSLClientSocketNSS*>(arg); that->client_auth_cert_needed_ = !that->ssl_config_.send_client_cert; - #if defined(OS_WIN) if (that->ssl_config_.send_client_cert) { - // TODO(wtc): SSLClientSocketNSS can't do SSL client authentication using - // CryptoAPI yet (http://crbug.com/37560), so client_cert must be NULL. - DCHECK(!that->ssl_config_.client_cert); + if (that->ssl_config_.client_cert) { + PCCERT_CONTEXT cert_context = + that->ssl_config_.client_cert->os_cert_handle(); + HCRYPTPROV provider = NULL; + DWORD key_spec = AT_KEYEXCHANGE; + BOOL must_free = FALSE; + BOOL acquired_key = CryptAcquireCertificatePrivateKey( + cert_context, + CRYPT_ACQUIRE_CACHE_FLAG | CRYPT_ACQUIRE_COMPARE_KEY_FLAG, + NULL, &provider, &key_spec, &must_free); + if (acquired_key && provider) { + DCHECK_NE(key_spec, CERT_NCRYPT_KEY_SPEC); + + // The certificate cache may have been updated/used, in which case, + // duplicate the existing handle, since NSS will free it when no + // longer in use. + if (!must_free) + CryptContextAddRef(provider, NULL, 0); + + SECItem der_cert; + der_cert.type = siDERCertBuffer; + der_cert.data = cert_context->pbCertEncoded; + der_cert.len = cert_context->cbCertEncoded; + + // TODO(rsleevi): Error checking for NSS allocation errors. + *result_certs = CERT_NewCertList(); + CERTCertDBHandle* db_handle = CERT_GetDefaultCertDB(); + CERTCertificate* user_cert = CERT_NewTempCertificate( + db_handle, &der_cert, NULL, PR_FALSE, PR_TRUE); + CERT_AddCertToListTail(*result_certs, user_cert); + + // Add the intermediates. + X509Certificate::OSCertHandles intermediates = + that->ssl_config_.client_cert->GetIntermediateCertificates(); + for (X509Certificate::OSCertHandles::const_iterator it = + intermediates.begin(); it != intermediates.end(); ++it) { + der_cert.data = (*it)->pbCertEncoded; + der_cert.len = (*it)->cbCertEncoded; + + CERTCertificate* intermediate = CERT_NewTempCertificate( + db_handle, &der_cert, NULL, PR_FALSE, PR_TRUE); + CERT_AddCertToListTail(*result_certs, intermediate); + } + // TODO(wtc): |key_spec| should be passed along with |provider|. + *result_private_key = reinterpret_cast<void*>(provider); + return SECSuccess; + } + LOG(WARNING) << "Client cert found without private key"; + } // Send no client certificate. return SECFailure; } @@ -1733,11 +1785,34 @@ SECStatus SSLClientSocketNSS::ClientAuthHandler( NOTREACHED(); continue; } + + // Copy the rest of the chain to our own store as well. Copying the chain + // stops gracefully if an error is encountered, with the partial chain + // being used as the intermediates, rather than failing to consider the + // client certificate. + net::X509Certificate::OSCertHandles intermediates; + for (DWORD i = 1; i < chain_context->rgpChain[0]->cElement; i++) { + PCCERT_CONTEXT intermediate_copy; + ok = CertAddCertificateContextToStore(X509Certificate::cert_store(), + chain_context->rgpChain[0]->rgpElement[i]->pCertContext, + CERT_STORE_ADD_USE_EXISTING, &intermediate_copy); + if (!ok) { + NOTREACHED(); + break; + } + intermediates.push_back(intermediate_copy); + } + scoped_refptr<X509Certificate> cert = X509Certificate::CreateFromHandle( cert_context2, X509Certificate::SOURCE_LONE_CERT_IMPORT, - X509Certificate::OSCertHandles()); - X509Certificate::FreeOSCertHandle(cert_context2); + intermediates); that->client_certs_.push_back(cert); + + X509Certificate::FreeOSCertHandle(cert_context2); + for (net::X509Certificate::OSCertHandles::iterator it = + intermediates.begin(); it != intermediates.end(); ++it) { + net::X509Certificate::FreeOSCertHandle(*it); + } } BOOL ok = CertCloseStore(my_cert_store, CERT_CLOSE_STORE_CHECK_FLAG); @@ -1748,9 +1823,63 @@ SECStatus SSLClientSocketNSS::ClientAuthHandler( return SECWouldBlock; #elif defined(OS_MACOSX) if (that->ssl_config_.send_client_cert) { - // TODO(wtc): SSLClientSocketNSS can't do SSL client authentication using - // CDSA/CSSM yet (http://crbug.com/45369), so client_cert must be NULL. - DCHECK(!that->ssl_config_.client_cert); + if (that->ssl_config_.client_cert) { + OSStatus os_error = noErr; + SecIdentityRef identity = NULL; + SecKeyRef private_key = NULL; + CFArrayRef chain = + that->ssl_config_.client_cert->CreateClientCertificateChain(); + if (chain) { + identity = reinterpret_cast<SecIdentityRef>( + const_cast<void*>(CFArrayGetValueAtIndex(chain, 0))); + } + if (identity) + os_error = SecIdentityCopyPrivateKey(identity, &private_key); + + if (chain && identity && os_error == noErr) { + // TODO(rsleevi): Error checking for NSS allocation errors. + *result_certs = CERT_NewCertList(); + *result_private_key = reinterpret_cast<void*>(private_key); + + for (CFIndex i = 0; i < CFArrayGetCount(chain); ++i) { + CSSM_DATA cert_data; + SecCertificateRef cert_ref; + if (i == 0) { + cert_ref = that->ssl_config_.client_cert->os_cert_handle(); + } else { + cert_ref = reinterpret_cast<SecCertificateRef>( + const_cast<void*>(CFArrayGetValueAtIndex(chain, i))); + } + os_error = SecCertificateGetData(cert_ref, &cert_data); + if (os_error != noErr) + break; + + SECItem der_cert; + der_cert.type = siDERCertBuffer; + der_cert.data = cert_data.Data; + der_cert.len = cert_data.Length; + CERTCertificate* nss_cert = CERT_NewTempCertificate( + CERT_GetDefaultCertDB(), &der_cert, NULL, PR_FALSE, PR_TRUE); + CERT_AddCertToListTail(*result_certs, nss_cert); + } + } + if (os_error == noErr) { + CFRelease(chain); + return SECSuccess; + } + LOG(WARNING) << "Client cert found, but could not be used: " + << os_error; + if (*result_certs) { + CERT_DestroyCertList(*result_certs); + *result_certs = NULL; + } + if (*result_private_key) + *result_private_key = NULL; + if (private_key) + CFRelease(private_key); + if (chain) + CFRelease(chain); + } // Send no client certificate. return SECFailure; } @@ -1778,6 +1907,24 @@ SECStatus SSLClientSocketNSS::ClientAuthHandler( // handshake by returning ERR_SSL_CLIENT_AUTH_CERT_NEEDED. return SECWouldBlock; #else + return SECFailure; +#endif +} + +#else // NSS_PLATFORM_CLIENT_AUTH + +// static +// NSS calls this if a client certificate is needed. +// Based on Mozilla's NSS_GetClientAuthData. +SECStatus SSLClientSocketNSS::ClientAuthHandler( + void* arg, + PRFileDesc* socket, + CERTDistNames* ca_names, + CERTCertificate** result_certificate, + SECKEYPrivateKey** result_private_key) { + SSLClientSocketNSS* that = reinterpret_cast<SSLClientSocketNSS*>(arg); + + that->client_auth_cert_needed_ = !that->ssl_config_.send_client_cert; void* wincx = SSL_RevealPinArg(socket); // Second pass: a client certificate should have been selected. @@ -1831,8 +1978,8 @@ SECStatus SSLClientSocketNSS::ClientAuthHandler( // Tell NSS to suspend the client authentication. We will then abort the // handshake by returning ERR_SSL_CLIENT_AUTH_CERT_NEEDED. return SECWouldBlock; -#endif } +#endif // NSS_PLATFORM_CLIENT_AUTH // static // NSS calls this when handshake is completed. diff --git a/net/socket/ssl_client_socket_nss.h b/net/socket/ssl_client_socket_nss.h index 0acbb2a..821abe4 100644 --- a/net/socket/ssl_client_socket_nss.h +++ b/net/socket/ssl_client_socket_nss.h @@ -123,11 +123,19 @@ class SSLClientSocketNSS : public SSLClientSocket { static SECStatus OwnAuthCertHandler(void* arg, PRFileDesc* socket, PRBool checksig, PRBool is_server); // NSS calls this when client authentication is requested. +#if defined(NSS_PLATFORM_CLIENT_AUTH) + static SECStatus PlatformClientAuthHandler(void* arg, + PRFileDesc* socket, + CERTDistNames* ca_names, + CERTCertList** result_certs, + void** result_private_key); +#else static SECStatus ClientAuthHandler(void* arg, PRFileDesc* socket, CERTDistNames* ca_names, CERTCertificate** result_certificate, SECKEYPrivateKey** result_private_key); +#endif // NSS calls this when handshake is completed. We pass 'this' as the second // argument. static void HandshakeCallback(PRFileDesc* socket, void* arg); diff --git a/net/socket/ssl_client_socket_nss_factory.cc b/net/socket/ssl_client_socket_nss_factory.cc index efa6e23..a4c87fa 100644 --- a/net/socket/ssl_client_socket_nss_factory.cc +++ b/net/socket/ssl_client_socket_nss_factory.cc @@ -4,12 +4,8 @@ #include "net/socket/client_socket_factory.h" -#include "build/build_config.h" #include "net/socket/ssl_client_socket_nss.h" #include "net/socket/ssl_host_info.h" -#if defined(OS_WIN) -#include "net/socket/ssl_client_socket_win.h" -#endif // This file is only used on platforms where NSS is not the system SSL // library. When compiled, this file is the only object module that pulls @@ -24,14 +20,6 @@ SSLClientSocket* SSLClientSocketNSSFactory( const SSLConfig& ssl_config, SSLHostInfo* ssl_host_info) { scoped_ptr<SSLHostInfo> shi(ssl_host_info); - // TODO(wtc): SSLClientSocketNSS can't do SSL client authentication using - // CryptoAPI yet (http://crbug.com/37560), so we fall back on - // SSLClientSocketWin. -#if defined(OS_WIN) - if (ssl_config.send_client_cert) - return new SSLClientSocketWin(transport_socket, hostname, ssl_config); -#endif - return new SSLClientSocketNSS(transport_socket, hostname, ssl_config, shi.release()); } |