diff options
author | wtc@chromium.org <wtc@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-10-15 21:23:37 +0000 |
---|---|---|
committer | wtc@chromium.org <wtc@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-10-15 21:23:37 +0000 |
commit | d84b3729c73df5ca7679bf827a348c97810fc4b3 (patch) | |
tree | cb4f46567a9c04294219adbb05fee92a72c41026 | |
parent | d7519fc7cbb59eef660f8d331d00df9a04878968 (diff) | |
download | chromium_src-d84b3729c73df5ca7679bf827a348c97810fc4b3.zip chromium_src-d84b3729c73df5ca7679bf827a348c97810fc4b3.tar.gz chromium_src-d84b3729c73df5ca7679bf827a348c97810fc4b3.tar.bz2 |
Provides a certificate for SSL client authentication on NSS sockets.
GUI is still missing, so certificates and private keys have to be
stored manually, p.e.:
$ pk12util -d sql:$HOME/.pki/nssdb -i PKCS12_file.p12
Adds --auto-ssl-client-auth command-line option to enable this feature.
Patch contributed by Jaime Soriano <jsorianopastor@gmail.com>.
Original review URL: http://codereview.chromium.org/220009
R=wtc
BUG=16830
TEST=Try to connect to a web page that requires SSL authentication and
confirm that it connects if and only if a valid certificate is stored in
the ~/.pki/nssdb database.
Review URL: http://codereview.chromium.org/276037
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@29188 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | AUTHORS | 1 | ||||
-rw-r--r-- | chrome/browser/renderer_host/resource_dispatcher_host.cc | 9 | ||||
-rw-r--r-- | chrome/common/chrome_switches.cc | 10 | ||||
-rw-r--r-- | chrome/common/chrome_switches.h | 4 | ||||
-rw-r--r-- | net/socket/ssl_client_socket_nss.cc | 122 | ||||
-rw-r--r-- | net/socket/ssl_client_socket_nss.h | 13 |
6 files changed, 155 insertions, 4 deletions
@@ -51,3 +51,4 @@ Philippe Beauchamp <philippe.beauchamp@gmail.com> Vedran Šajatović <vedran.sajatovic@gmail.com> Randy Posynick <randy.posynick@gmail.com> Bruno Calvignac <brunocalvignac@gmail.com> +Jaime Soriano Pastor <jsorianopastor@gmail.com> diff --git a/chrome/browser/renderer_host/resource_dispatcher_host.cc b/chrome/browser/renderer_host/resource_dispatcher_host.cc index e1d89fe..597c931 100644 --- a/chrome/browser/renderer_host/resource_dispatcher_host.cc +++ b/chrome/browser/renderer_host/resource_dispatcher_host.cc @@ -1052,6 +1052,14 @@ void ResourceDispatcherHost::OnCertificateRequested( net::SSLCertRequestInfo* cert_request_info) { DCHECK(request); +#if defined(OS_LINUX) + bool select_first_cert = CommandLine::ForCurrentProcess()->HasSwitch( + switches::kAutoSSLClientAuth); + net::X509Certificate* cert = + select_first_cert && !cert_request_info->client_certs.empty() ? + cert_request_info->client_certs[0] : NULL; + request->ContinueWithCertificate(cert); +#else if (cert_request_info->client_certs.empty()) { // No need to query the user if there are no certs to choose from. request->ContinueWithCertificate(NULL); @@ -1064,6 +1072,7 @@ void ResourceDispatcherHost::OnCertificateRequested( info->set_ssl_client_auth_handler( new SSLClientAuthHandler(request, cert_request_info, io_loop_, ui_loop_)); info->ssl_client_auth_handler()->SelectCertificate(); +#endif } void ResourceDispatcherHost::OnSSLCertificateError( diff --git a/chrome/common/chrome_switches.cc b/chrome/common/chrome_switches.cc index bb11493..4003003 100644 --- a/chrome/common/chrome_switches.cc +++ b/chrome/common/chrome_switches.cc @@ -266,6 +266,16 @@ const char kWinHttpProxyResolver[] = "winhttp-proxy-resolver"; extern const char kDnsLogDetails[] = "dns-log-details"; extern const char kDnsPrefetchDisable[] = "dns-prefetch-disable"; +#if defined(OS_LINUX) +// A temporary switch before we implement the client certificate selection UI. +// When an SSL server requests client authentication, select a client +// certificate automatically. +// WARNING: This switch has privacy issues because it reveals the user's +// identity to any server that requests a client certificate without the +// user's consent. +const char kAutoSSLClientAuth[] = "auto-ssl-client-auth"; +#endif + // Enables support to debug printing subsystem. const char kDebugPrint[] = "debug-print"; diff --git a/chrome/common/chrome_switches.h b/chrome/common/chrome_switches.h index 48c9142..1b54673c 100644 --- a/chrome/common/chrome_switches.h +++ b/chrome/common/chrome_switches.h @@ -93,6 +93,10 @@ extern const char kPrint[]; extern const char kDnsLogDetails[]; extern const char kDnsPrefetchDisable[]; +#if defined(OS_LINUX) +extern const char kAutoSSLClientAuth[]; +#endif + extern const char kDisableDevTools[]; extern const char kAlwaysEnableDevTools[]; extern const char kEnableExtensionTimelineApi[]; diff --git a/net/socket/ssl_client_socket_nss.cc b/net/socket/ssl_client_socket_nss.cc index ca04857..c91cfb4 100644 --- a/net/socket/ssl_client_socket_nss.cc +++ b/net/socket/ssl_client_socket_nss.cc @@ -51,6 +51,7 @@ #include "net/socket/ssl_client_socket_nss.h" #include <certdb.h> +#include <keyhi.h> #include <nspr.h> #include <nss.h> #include <secerr.h> @@ -69,6 +70,7 @@ #include "net/base/cert_verifier.h" #include "net/base/io_buffer.h" #include "net/base/net_errors.h" +#include "net/base/ssl_cert_request_info.h" #include "net/base/ssl_info.h" #include "net/ocsp/nss_ocsp.h" @@ -164,6 +166,8 @@ int NetErrorFromNSPRError(PRErrorCode err) { case SEC_ERROR_UNTRUSTED_CERT: case SEC_ERROR_UNTRUSTED_ISSUER: return ERR_CERT_AUTHORITY_INVALID; + case SSL_ERROR_HANDSHAKE_FAILURE_ALERT: + return ERR_SSL_PROTOCOL_ERROR; default: { if (IS_SSL_ERROR(err)) { @@ -205,6 +209,8 @@ SSLClientSocketNSS::SSLClientSocketNSS(ClientSocket* transport_socket, user_write_callback_(NULL), user_read_buf_len_(0), user_write_buf_len_(0), + client_auth_ca_names_(NULL), + client_auth_cert_needed_(false), completed_handshake_(false), next_handshake_state_(STATE_NONE), nss_fd_(NULL), @@ -318,6 +324,10 @@ int SSLClientSocketNSS::Connect(CompletionCallback* callback) { if (rv != SECSuccess) return ERR_UNEXPECTED; + rv = SSL_GetClientAuthDataHook(nss_fd_, ClientAuthHandler, this); + if (rv != SECSuccess) + return ERR_UNEXPECTED; + rv = SSL_HandshakeCallback(nss_fd_, HandshakeCallback, this); if (rv != SECSuccess) return ERR_UNEXPECTED; @@ -373,6 +383,11 @@ void SSLClientSocketNSS::Disconnect() { server_cert_verify_result_.Reset(); completed_handshake_ = false; nss_bufs_ = NULL; + if (client_auth_ca_names_) { + CERT_FreeDistNames(client_auth_ca_names_); + client_auth_ca_names_ = NULL; + } + client_auth_cert_needed_ = false; LeaveFunction(""); } @@ -508,7 +523,41 @@ void SSLClientSocketNSS::GetSSLInfo(SSLInfo* ssl_info) { void SSLClientSocketNSS::GetSSLCertRequestInfo( SSLCertRequestInfo* cert_request_info) { - // TODO(wtc): implement this. + EnterFunction(""); + cert_request_info->host_and_port = hostname_; + cert_request_info->client_certs.clear(); + + void* wincx = SSL_RevealPinArg(nss_fd_); + + CERTCertNicknames* names = CERT_GetCertNicknames( + CERT_GetDefaultCertDB(), SEC_CERT_NICKNAMES_USER, wincx); + + if (names) { + for (int i = 0; i < names->numnicknames; ++i) { + CERTCertificate* cert = CERT_FindUserCertByUsage( + CERT_GetDefaultCertDB(), names->nicknames[i], + certUsageSSLClient, PR_FALSE, wincx); + if (!cert) + continue; + // Only check unexpired certs. + if (CERT_CheckCertValidTimes(cert, PR_Now(), PR_TRUE) == + secCertTimeValid && + NSS_CmpCertChainWCANames(cert, client_auth_ca_names_) == + SECSuccess) { + SECKEYPrivateKey* privkey = PK11_FindKeyByAnyCert(cert, wincx); + if (privkey) { + X509Certificate* x509_cert = X509Certificate::CreateFromHandle( + cert, X509Certificate::SOURCE_LONE_CERT_IMPORT); + cert_request_info->client_certs.push_back(x509_cert); + SECKEY_DestroyPrivateKey(privkey); + continue; + } + } + CERT_DestroyCertificate(cert); + } + CERT_FreeNicknames(names); + } + LeaveFunction(cert_request_info->client_certs.size()); } void SSLClientSocketNSS::DoReadCallback(int rv) { @@ -821,6 +870,55 @@ SECStatus SSLClientSocketNSS::OwnAuthCertHandler(void* arg, } // 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; + + // Second pass: a client certificate should have been selected. + if (that->ssl_config_.send_client_cert) { + if (that->ssl_config_.client_cert) { + void* wincx = SSL_RevealPinArg(socket); + CERTCertificate* cert = CERT_DupCertificate( + that->ssl_config_.client_cert->os_cert_handle()); + SECKEYPrivateKey* privkey = PK11_FindKeyByAnyCert(cert, wincx); + if (privkey) { + // TODO(jsorianopastor): We should wait for server certificate + // verification before sending our credentials. See + // http://crbug.com/13934. + *result_certificate = cert; + *result_private_key = privkey; + return SECSuccess; + } + LOG(WARNING) << "Client cert found without private key"; + } + // Send no client certificate. + return SECFailure; + } + + PRArenaPool* arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + CERTDistNames* ca_names_copy = PORT_ArenaZNew(arena, CERTDistNames); + + ca_names_copy->arena = arena; + ca_names_copy->head = NULL; + ca_names_copy->nnames = ca_names->nnames; + ca_names_copy->names = PORT_ArenaZNewArray(arena, SECItem, + ca_names->nnames); + for (int i = 0; i < ca_names->nnames; ++i) + SECITEM_CopyItem(arena, &ca_names_copy->names[i], &ca_names->names[i]); + + that->client_auth_ca_names_ = ca_names_copy; + return SECFailure; +} + +// static // NSS calls this when handshake is completed. // After the SSL handshake is finished, use CertVerifier to verify // the saved server certificate. @@ -834,9 +932,19 @@ void SSLClientSocketNSS::HandshakeCallback(PRFileDesc* socket, int SSLClientSocketNSS::DoHandshake() { EnterFunction(""); int net_error = net::OK; - int rv = SSL_ForceHandshake(nss_fd_); - - if (rv == SECSuccess) { + SECStatus rv = SSL_ForceHandshake(nss_fd_); + + if (client_auth_cert_needed_) { + net_error = ERR_SSL_CLIENT_AUTH_CERT_NEEDED; + // If the handshake already succeeded (because the server requests but + // doesn't require a client cert), we need to invalidate the SSL session + // so that we won't try to resume the non-client-authenticated session in + // the next handshake. This will cause the server to ask for a client + // cert again. + if (rv == SECSuccess && SSL_InvalidateSession(nss_fd_) != SECSuccess) { + LOG(WARNING) << "Couldn't invalidate SSL session: " << PR_GetError(); + } + } else if (rv == SECSuccess) { // SSL handshake is completed. Let's verify the certificate. GotoState(STATE_VERIFY_CERT); // Done! @@ -946,6 +1054,12 @@ int SSLClientSocketNSS::DoPayloadRead() { DCHECK(user_read_buf_); DCHECK(user_read_buf_len_ > 0); int rv = PR_Read(nss_fd_, user_read_buf_->data(), user_read_buf_len_); + if (client_auth_cert_needed_) { + // We don't need to invalidate the non-client-authenticated SSL session + // because the server will renegotiate anyway. + LeaveFunction(""); + return ERR_SSL_CLIENT_AUTH_CERT_NEEDED; + } if (rv >= 0) { LogData(user_read_buf_->data(), rv); LeaveFunction(""); diff --git a/net/socket/ssl_client_socket_nss.h b/net/socket/ssl_client_socket_nss.h index 1535e04f..73e63d0 100644 --- a/net/socket/ssl_client_socket_nss.h +++ b/net/socket/ssl_client_socket_nss.h @@ -10,8 +10,10 @@ #define Lock FOO_NSS_Lock #include <certt.h> #undef Lock +#include <keyt.h> #include <nspr.h> #include <nss.h> + #include <string> #include "base/scoped_ptr.h" @@ -85,6 +87,12 @@ class SSLClientSocketNSS : public SSLClientSocket { // argument. static SECStatus OwnAuthCertHandler(void* arg, PRFileDesc* socket, PRBool checksig, PRBool is_server); + // NSS calls this when client authentication is requested. + static SECStatus ClientAuthHandler(void* arg, + PRFileDesc* socket, + CERTDistNames* ca_names, + CERTCertificate** result_certificate, + SECKEYPrivateKey** result_private_key); // NSS calls this when handshake is completed. We pass 'this' as the second // argument. static void HandshakeCallback(PRFileDesc* socket, void* arg); @@ -116,6 +124,11 @@ class SSLClientSocketNSS : public SSLClientSocket { scoped_refptr<X509Certificate> server_cert_; CertVerifyResult server_cert_verify_result_; + // Stores client authentication information between ClientAuthHandler and + // GetSSLCertRequestInfo calls. + CERTDistNames* client_auth_ca_names_; + bool client_auth_cert_needed_; + scoped_ptr<CertVerifier> verifier_; bool completed_handshake_; |