summaryrefslogtreecommitdiffstats
path: root/net/socket
diff options
context:
space:
mode:
authorwtc@chromium.org <wtc@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-11-04 16:21:33 +0000
committerwtc@chromium.org <wtc@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-11-04 16:21:33 +0000
commit4a842345af02ac3ae84015683383539a84f66d8c (patch)
tree9b9b005ddea77d514f2bf0b32f83bf0e5f2bd746 /net/socket
parent1fd26d8cdd166ec84c06bb7d4a8557293428fc0e (diff)
downloadchromium_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.cc7
-rw-r--r--net/socket/ssl_client_socket_nss.cc185
-rw-r--r--net/socket/ssl_client_socket_nss.h8
-rw-r--r--net/socket/ssl_client_socket_nss_factory.cc12
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());
}