diff options
author | agl@chromium.org <agl@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-12-12 22:22:19 +0000 |
---|---|---|
committer | agl@chromium.org <agl@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-12-12 22:22:19 +0000 |
commit | c3456bbc289c54765e89d628ba03505425fe372a (patch) | |
tree | 7d027edc8ea0a358bd5dcfb227fbc48ea8ca26ab /net | |
parent | 9b10d2a051ec36c4e7f012d31b8214221db16eaa (diff) | |
download | chromium_src-c3456bbc289c54765e89d628ba03505425fe372a.zip chromium_src-c3456bbc289c54765e89d628ba03505425fe372a.tar.gz chromium_src-c3456bbc289c54765e89d628ba03505425fe372a.tar.bz2 |
net: split the SSL session cache between incognito and normal.
This change causes incognito requests to effectively have a different SSL
session cache from other requests. SSL session information will therefore not
leak into or out of incognito mode.
BUG=30877
TEST=net_unittests
Review URL: http://codereview.chromium.org/8857002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@114098 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net')
25 files changed, 320 insertions, 64 deletions
diff --git a/net/http/http_cache.cc b/net/http/http_cache.cc index 4aa37fa..8d79918 100644 --- a/net/http/http_cache.cc +++ b/net/http/http_cache.cc @@ -49,6 +49,7 @@ HttpNetworkSession* CreateNetworkSession( DnsCertProvenanceChecker* dns_cert_checker, ProxyService* proxy_service, SSLHostInfoFactory* ssl_host_info_factory, + const std::string& ssl_session_cache_shard, SSLConfigService* ssl_config_service, HttpAuthHandlerFactory* http_auth_handler_factory, NetworkDelegate* network_delegate, @@ -62,6 +63,7 @@ HttpNetworkSession* CreateNetworkSession( params.dns_cert_checker = dns_cert_checker; params.proxy_service = proxy_service; params.ssl_host_info_factory = ssl_host_info_factory; + params.ssl_session_cache_shard = ssl_session_cache_shard; params.ssl_config_service = ssl_config_service; params.http_auth_handler_factory = http_auth_handler_factory; params.network_delegate = network_delegate; @@ -321,6 +323,7 @@ HttpCache::HttpCache(HostResolver* host_resolver, TransportSecurityState* transport_security_state, DnsCertProvenanceChecker* dns_cert_checker_, ProxyService* proxy_service, + const std::string& ssl_session_cache_shard, SSLConfigService* ssl_config_service, HttpAuthHandlerFactory* http_auth_handler_factory, NetworkDelegate* network_delegate, @@ -344,6 +347,7 @@ HttpCache::HttpCache(HostResolver* host_resolver, dns_cert_checker_, proxy_service, ssl_host_info_factory_.get(), + ssl_session_cache_shard, ssl_config_service, http_auth_handler_factory, network_delegate, diff --git a/net/http/http_cache.h b/net/http/http_cache.h index 0e12443..62c4668 100644 --- a/net/http/http_cache.h +++ b/net/http/http_cache.h @@ -126,6 +126,7 @@ class NET_EXPORT HttpCache : public HttpTransactionFactory, TransportSecurityState* transport_security_state, DnsCertProvenanceChecker* dns_cert_checker, ProxyService* proxy_service, + const std::string& ssl_session_cache_shard, SSLConfigService* ssl_config_service, HttpAuthHandlerFactory* http_auth_handler_factory, NetworkDelegate* network_delegate, diff --git a/net/http/http_network_session.cc b/net/http/http_network_session.cc index cc3fdd1f..1c56bc5 100644 --- a/net/http/http_network_session.cc +++ b/net/http/http_network_session.cc @@ -43,6 +43,7 @@ HttpNetworkSession::HttpNetworkSession(const Params& params) params.transport_security_state, params.dns_cert_checker, params.ssl_host_info_factory, + params.ssl_session_cache_shard, params.proxy_service, params.ssl_config_service)), spdy_session_pool_(params.host_resolver, diff --git a/net/http/http_network_session.h b/net/http/http_network_session.h index 083d807..5dcf825 100644 --- a/net/http/http_network_session.h +++ b/net/http/http_network_session.h @@ -74,6 +74,7 @@ class NET_EXPORT HttpNetworkSession DnsCertProvenanceChecker* dns_cert_checker; ProxyService* proxy_service; SSLHostInfoFactory* ssl_host_info_factory; + std::string ssl_session_cache_shard; SSLConfigService* ssl_config_service; HttpAuthHandlerFactory* http_auth_handler_factory; NetworkDelegate* network_delegate; diff --git a/net/http/http_network_transaction_unittest.cc b/net/http/http_network_transaction_unittest.cc index 60febd5..72a648f 100644 --- a/net/http/http_network_transaction_unittest.cc +++ b/net/http/http_network_transaction_unittest.cc @@ -371,7 +371,7 @@ CaptureGroupNameSSLSocketPool::CaptureGroupNameSocketPool( HostResolver* host_resolver, CertVerifier* cert_verifier) : SSLClientSocketPool(0, 0, NULL, host_resolver, cert_verifier, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL) {} + NULL, NULL, "", NULL, NULL, NULL, NULL, NULL, NULL) {} //----------------------------------------------------------------------------- diff --git a/net/http/http_proxy_client_socket_pool_unittest.cc b/net/http/http_proxy_client_socket_pool_unittest.cc index d7b56867..e68de73 100644 --- a/net/http/http_proxy_client_socket_pool_unittest.cc +++ b/net/http/http_proxy_client_socket_pool_unittest.cc @@ -71,6 +71,7 @@ class HttpProxyClientSocketPoolTest : public TestWithHttpParam { NULL /* dnsrr_resolver */, NULL /* dns_cert_checker */, NULL /* ssl_host_info_factory */, + "" /* ssl_session_cache_shard */, &socket_factory_, &transport_socket_pool_, NULL, diff --git a/net/http/http_stream_factory_impl_unittest.cc b/net/http/http_stream_factory_impl_unittest.cc index 3b47b1d..0c5a8e4 100644 --- a/net/http/http_stream_factory_impl_unittest.cc +++ b/net/http/http_stream_factory_impl_unittest.cc @@ -270,7 +270,7 @@ template<> CapturePreconnectsSSLSocketPool::CapturePreconnectsSocketPool( HostResolver* host_resolver, CertVerifier* cert_verifier) : SSLClientSocketPool(0, 0, NULL, host_resolver, cert_verifier, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), + NULL, NULL, "", NULL, NULL, NULL, NULL, NULL, NULL), last_num_streams_(-1) {} TEST(HttpStreamFactoryTest, PreconnectDirect) { diff --git a/net/net.gyp b/net/net.gyp index 446c628..033ac21 100644 --- a/net/net.gyp +++ b/net/net.gyp @@ -1147,7 +1147,6 @@ 'socket/socks5_client_socket_unittest.cc', 'socket/socks_client_socket_pool_unittest.cc', 'socket/socks_client_socket_unittest.cc', - 'socket/ssl_client_socket_nss_unittest.cc', 'socket/ssl_client_socket_pool_unittest.cc', 'socket/ssl_client_socket_unittest.cc', 'socket/ssl_server_socket_unittest.cc', @@ -1243,7 +1242,6 @@ 'base/x509_util_nss_unittest.cc', 'base/cert_database_nss_unittest.cc', 'base/dnssec_unittest.cc', - 'socket/ssl_client_socket_nss_unittest.cc', ], }, { # else !use_openssl: remove the unneeded files 'sources!': [ diff --git a/net/socket/client_socket_factory.cc b/net/socket/client_socket_factory.cc index dd7746d..60daef4 100644 --- a/net/socket/client_socket_factory.cc +++ b/net/socket/client_socket_factory.cc @@ -102,22 +102,8 @@ class DefaultClientSocketFactory : public ClientSocketFactory, #endif } - // TODO(rch): This is only implemented for the NSS SSL library, which is the - /// default for Windows, Mac and Linux, but we should implement it everywhere. void ClearSSLSessionCache() { -#if defined(OS_WIN) - if (!g_use_system_ssl) - SSLClientSocketNSS::ClearSessionCache(); -#elif defined(USE_OPENSSL) - // no-op -#elif defined(USE_NSS) - SSLClientSocketNSS::ClearSessionCache(); -#elif defined(OS_MACOSX) - if (!g_use_system_ssl) - SSLClientSocketNSS::ClearSessionCache(); -#else - NOTIMPLEMENTED(); -#endif + SSLClientSocket::ClearSessionCache(); } }; diff --git a/net/socket/client_socket_pool_manager_impl.cc b/net/socket/client_socket_pool_manager_impl.cc index 3159c09..a4f9b72 100644 --- a/net/socket/client_socket_pool_manager_impl.cc +++ b/net/socket/client_socket_pool_manager_impl.cc @@ -41,6 +41,7 @@ ClientSocketPoolManagerImpl::ClientSocketPoolManagerImpl( TransportSecurityState* transport_security_state, DnsCertProvenanceChecker* dns_cert_checker, SSLHostInfoFactory* ssl_host_info_factory, + const std::string& ssl_session_cache_shard, ProxyService* proxy_service, SSLConfigService* ssl_config_service) : net_log_(net_log), @@ -51,6 +52,7 @@ ClientSocketPoolManagerImpl::ClientSocketPoolManagerImpl( transport_security_state_(transport_security_state), dns_cert_checker_(dns_cert_checker), ssl_host_info_factory_(ssl_host_info_factory), + ssl_session_cache_shard_(ssl_session_cache_shard), proxy_service_(proxy_service), ssl_config_service_(ssl_config_service), transport_pool_histograms_("TCP"), @@ -70,6 +72,7 @@ ClientSocketPoolManagerImpl::ClientSocketPoolManagerImpl( transport_security_state, dns_cert_checker, ssl_host_info_factory, + ssl_session_cache_shard, socket_factory, transport_socket_pool_.get(), NULL /* no socks proxy */, @@ -290,6 +293,7 @@ ClientSocketPoolManagerImpl::GetSocketPoolForHTTPProxy( transport_security_state_, dns_cert_checker_, ssl_host_info_factory_, + ssl_session_cache_shard_, socket_factory_, tcp_https_ret.first->second /* https proxy */, NULL /* no socks proxy */, @@ -329,6 +333,7 @@ SSLClientSocketPool* ClientSocketPoolManagerImpl::GetSocketPoolForSSLWithProxy( transport_security_state_, dns_cert_checker_, ssl_host_info_factory_, + ssl_session_cache_shard_, socket_factory_, NULL, /* no tcp pool, we always go through a proxy */ GetSocketPoolForSOCKSProxy(proxy_server), diff --git a/net/socket/client_socket_pool_manager_impl.h b/net/socket/client_socket_pool_manager_impl.h index a4ba519..72c1a0c 100644 --- a/net/socket/client_socket_pool_manager_impl.h +++ b/net/socket/client_socket_pool_manager_impl.h @@ -66,6 +66,7 @@ class ClientSocketPoolManagerImpl : public base::NonThreadSafe, TransportSecurityState* transport_security_state, DnsCertProvenanceChecker* dns_cert_checker, SSLHostInfoFactory* ssl_host_info_factory, + const std::string& ssl_session_cache_shard, ProxyService* proxy_service, SSLConfigService* ssl_config_service); virtual ~ClientSocketPoolManagerImpl(); @@ -112,6 +113,7 @@ class ClientSocketPoolManagerImpl : public base::NonThreadSafe, TransportSecurityState* const transport_security_state_; DnsCertProvenanceChecker* const dns_cert_checker_; SSLHostInfoFactory* const ssl_host_info_factory_; + const std::string ssl_session_cache_shard_; ProxyService* const proxy_service_; const scoped_refptr<SSLConfigService> ssl_config_service_; diff --git a/net/socket/ssl_client_socket.h b/net/socket/ssl_client_socket.h index 433470b..64ccd78 100644 --- a/net/socket/ssl_client_socket.h +++ b/net/socket/ssl_client_socket.h @@ -39,18 +39,24 @@ struct SSLClientSocketContext { OriginBoundCertService* origin_bound_cert_service_arg, TransportSecurityState* transport_security_state_arg, DnsCertProvenanceChecker* dns_cert_checker_arg, - SSLHostInfoFactory* ssl_host_info_factory_arg) + SSLHostInfoFactory* ssl_host_info_factory_arg, + const std::string& ssl_session_cache_shard_arg) : cert_verifier(cert_verifier_arg), origin_bound_cert_service(origin_bound_cert_service_arg), transport_security_state(transport_security_state_arg), dns_cert_checker(dns_cert_checker_arg), - ssl_host_info_factory(ssl_host_info_factory_arg) {} + ssl_host_info_factory(ssl_host_info_factory_arg), + ssl_session_cache_shard(ssl_session_cache_shard_arg) {} CertVerifier* cert_verifier; OriginBoundCertService* origin_bound_cert_service; TransportSecurityState* transport_security_state; DnsCertProvenanceChecker* dns_cert_checker; SSLHostInfoFactory* ssl_host_info_factory; + // ssl_session_cache_shard is an opaque string that identifies a shard of the + // SSL session cache. SSL sockets with the same ssl_session_cache_shard may + // resume each other's SSL sessions but we'll never sessions between shards. + const std::string ssl_session_cache_shard; }; // A client socket that uses SSL as the transport layer. @@ -120,6 +126,10 @@ class NET_EXPORT SSLClientSocket : public SSLSocket { static bool IgnoreCertError(int error, int load_flags); + // ClearSessionCache clears the SSL session cache, used to resume SSL + // sessions. + static void ClearSessionCache(); + virtual bool was_npn_negotiated() const; virtual bool set_was_npn_negotiated(bool negotiated); diff --git a/net/socket/ssl_client_socket_nss.cc b/net/socket/ssl_client_socket_nss.cc index eb8c662..c63d56a 100644 --- a/net/socket/ssl_client_socket_nss.cc +++ b/net/socket/ssl_client_socket_nss.cc @@ -450,6 +450,7 @@ SSLClientSocketNSS::SSLClientSocketNSS(ClientSocketHandle* transport_socket, ob_cert_request_handle_(NULL), handshake_callback_called_(false), completed_handshake_(false), + ssl_session_cache_shard_(context.ssl_session_cache_shard), eset_mitm_detected_(false), kaspersky_mitm_detected_(false), predicted_cert_chain_correct_(false), @@ -471,7 +472,7 @@ SSLClientSocketNSS::~SSLClientSocketNSS() { } // static -void SSLClientSocketNSS::ClearSessionCache() { +void SSLClientSocket::ClearSessionCache() { // SSL_ClearSessionCache can't be called before NSS is initialized. Don't // bother initializing NSS just to clear an empty SSL session cache. if (!NSS_IsInitialized()) @@ -1044,6 +1045,13 @@ int SSLClientSocketNSS::InitializeSSLPeerName() { // SSL tunnel through a proxy -- GetPeerName returns the proxy's address // rather than the destination server's address in that case. std::string peer_id = host_and_port_.ToString(); + // If the ssl_session_cache_shard_ is non-empty, we append it to the peer id. + // This will cause session cache misses between sockets with different values + // of ssl_session_cache_shard_ and this is used to partition the session cache + // for incognito mode. + if (!ssl_session_cache_shard_.empty()) { + peer_id += "/" + ssl_session_cache_shard_; + } SECStatus rv = SSL_SetSockPeerID(nss_fd_, const_cast<char*>(peer_id.c_str())); if (rv != SECSuccess) LogFailedNSSFunction(net_log_, "SSL_SetSockPeerID", peer_id.c_str()); diff --git a/net/socket/ssl_client_socket_nss.h b/net/socket/ssl_client_socket_nss.h index 366aa7f..784b92d 100644 --- a/net/socket/ssl_client_socket_nss.h +++ b/net/socket/ssl_client_socket_nss.h @@ -57,8 +57,6 @@ class SSLClientSocketNSS : public SSLClientSocket { const SSLClientSocketContext& context); virtual ~SSLClientSocketNSS(); - NET_EXPORT_PRIVATE static void ClearSessionCache(); - // SSLClientSocket implementation. virtual void GetSSLInfo(SSLInfo* ssl_info) OVERRIDE; virtual void GetSSLCertRequestInfo( @@ -269,6 +267,11 @@ class SSLClientSocketNSS : public SSLClientSocket { // True if the SSL handshake has been completed. bool completed_handshake_; + // ssl_session_cache_shard_ is an opaque string that partitions the SSL + // session cache. i.e. sessions created with one value will not attempt to + // resume on the socket with a different value. + const std::string ssl_session_cache_shard_; + // True iff we believe that the user has an ESET product intercepting our // HTTPS connections. bool eset_mitm_detected_; diff --git a/net/socket/ssl_client_socket_nss_unittest.cc b/net/socket/ssl_client_socket_nss_unittest.cc deleted file mode 100644 index 2e85d31..0000000 --- a/net/socket/ssl_client_socket_nss_unittest.cc +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "net/socket/ssl_client_socket_nss.h" - -#include "testing/gtest/include/gtest/gtest.h" - -namespace net { - -// Verifies that SSLClientSocketNSS::ClearSessionCache can be called without -// explicit NSS initialization. -TEST(SSLClientSocketNSSTest, ClearSessionCache) { - SSLClientSocketNSS::ClearSessionCache(); -} - -} // namespace net diff --git a/net/socket/ssl_client_socket_openssl.cc b/net/socket/ssl_client_socket_openssl.cc index f933031..547de44 100644 --- a/net/socket/ssl_client_socket_openssl.cc +++ b/net/socket/ssl_client_socket_openssl.cc @@ -200,28 +200,32 @@ int NoOpVerifyCallback(X509_STORE_CTX*, void *) { // OpenSSL manages a cache of SSL_SESSION, this class provides the application // side policy for that cache about session re-use: we retain one session per -// unique HostPortPair. +// unique HostPortPair, per shard. class SSLSessionCache { public: SSLSessionCache() {} - void OnSessionAdded(const HostPortPair& host_and_port, SSL_SESSION* session) { + void OnSessionAdded(const HostPortPair& host_and_port, + const std::string& shard, + SSL_SESSION* session) { // Declare the session cleaner-upper before the lock, so any call into // OpenSSL to free the session will happen after the lock is released. crypto::ScopedOpenSSL<SSL_SESSION, SSL_SESSION_free> session_to_free; base::AutoLock lock(lock_); DCHECK_EQ(0U, session_map_.count(session)); + const std::string cache_key = GetCacheKey(host_and_port, shard); + std::pair<HostPortMap::iterator, bool> res = - host_port_map_.insert(std::make_pair(host_and_port, session)); + host_port_map_.insert(std::make_pair(cache_key, session)); if (!res.second) { // Already exists: replace old entry. session_to_free.reset(res.first->second); session_map_.erase(session_to_free.get()); res.first->second = session; } DVLOG(2) << "Adding session " << session << " => " - << host_and_port.ToString() << ", new entry = " << res.second; - DCHECK(host_port_map_[host_and_port] == session); + << cache_key << ", new entry = " << res.second; + DCHECK(host_port_map_[cache_key] == session); session_map_[session] = res.first; DCHECK_EQ(host_port_map_.size(), session_map_.size()); DCHECK_LE(host_port_map_.size(), kSessionCacheMaxEntires); @@ -236,8 +240,7 @@ class SSLSessionCache { SessionMap::iterator it = session_map_.find(session); if (it == session_map_.end()) return; - DVLOG(2) << "Remove session " << session << " => " - << it->second->first.ToString(); + DVLOG(2) << "Remove session " << session << " => " << it->second->first; DCHECK(it->second->second == session); host_port_map_.erase(it->second); session_map_.erase(it); @@ -247,13 +250,14 @@ class SSLSessionCache { // Looks up the host:port in the cache, and if a session is found it is added // to |ssl|, returning true on success. - bool SetSSLSession(SSL* ssl, const HostPortPair& host_and_port) { + bool SetSSLSession(SSL* ssl, const HostPortPair& host_and_port, + const std::string& shard) { base::AutoLock lock(lock_); - HostPortMap::iterator it = host_port_map_.find(host_and_port); + const std::string cache_key = GetCacheKey(host_and_port, shard); + HostPortMap::iterator it = host_port_map_.find(cache_key); if (it == host_port_map_.end()) return false; - DVLOG(2) << "Lookup session: " << it->second << " => " - << host_and_port.ToString(); + DVLOG(2) << "Lookup session: " << it->second << " => " << cache_key; SSL_SESSION* session = it->second; DCHECK(session); DCHECK(session_map_[session] == it); @@ -265,12 +269,26 @@ class SSLSessionCache { return SSL_set_session(ssl, session) == 1; } + // Flush removes all entries from the cache. This is called when a client + // certificate is added. + void Flush() { + for (HostPortMap::iterator i = host_port_map_.begin(); + i != host_port_map_.end(); i++) { + SSL_SESSION_free(i->second); + } + host_port_map_.clear(); + session_map_.clear(); + } + private: + static std::string GetCacheKey(const HostPortPair& host_and_port, + const std::string& shard) { + return host_and_port.ToString() + "/" + shard; + } + // A pair of maps to allow bi-directional lookups between host:port and an // associated session. - // TODO(joth): When client certificates are implemented we should key the - // cache on the client certificate used in addition to the host-port pair. - typedef std::map<HostPortPair, SSL_SESSION*> HostPortMap; + typedef std::map<std::string, SSL_SESSION*> HostPortMap; typedef std::map<SSL_SESSION*, HostPortMap::iterator> SessionMap; HostPortMap host_port_map_; SessionMap session_map_; @@ -329,7 +347,9 @@ class SSLContext { int NewSessionCallback(SSL* ssl, SSL_SESSION* session) { SSLClientSocketOpenSSL* socket = GetClientSocketFromSSL(ssl); - session_cache_.OnSessionAdded(socket->host_and_port(), session); + session_cache_.OnSessionAdded(socket->host_and_port(), + socket->ssl_session_cache_shard(), + session); return 1; // 1 => We took ownership of |session|. } @@ -360,8 +380,11 @@ class SSLContext { // SSLClientSocketOpenSSL object from an SSL instance. int ssl_socket_data_index_; - crypto::ScopedOpenSSL<SSL_CTX, SSL_CTX_free> ssl_ctx_; + // session_cache_ must appear before |ssl_ctx_| because the destruction of + // |ssl_ctx_| may trigger callbacks into |session_cache_|. Therefore, + // |session_cache_| must be destructed after |ssl_ctx_|. SSLSessionCache session_cache_; + crypto::ScopedOpenSSL<SSL_CTX, SSL_CTX_free> ssl_ctx_; }; // Utility to construct the appropriate set & clear masks for use the OpenSSL @@ -379,6 +402,12 @@ struct SslSetClearMask { } // namespace +// static +void SSLClientSocket::ClearSessionCache() { + SSLContext* context = SSLContext::GetInstance(); + context->session_cache()->Flush(); +} + SSLClientSocketOpenSSL::SSLClientSocketOpenSSL( ClientSocketHandle* transport_socket, const HostPortPair& host_and_port, @@ -394,6 +423,7 @@ SSLClientSocketOpenSSL::SSLClientSocketOpenSSL( transport_(transport_socket), host_and_port_(host_and_port), ssl_config_(ssl_config), + ssl_session_cache_shard_(context.ssl_session_cache_shard), trying_cached_session_(false), npn_status_(kNextProtoUnsupported), net_log_(transport_socket->socket()->NetLog()) { @@ -418,7 +448,8 @@ bool SSLClientSocketOpenSSL::Init() { return false; trying_cached_session_ = - context->session_cache()->SetSSLSession(ssl_, host_and_port_); + context->session_cache()->SetSSLSession(ssl_, host_and_port_, + ssl_session_cache_shard_); BIO* ssl_bio = NULL; // 0 => use default buffer sizes. @@ -651,6 +682,9 @@ int SSLClientSocketOpenSSL::Connect(const CompletionCallback& callback) { void SSLClientSocketOpenSSL::Disconnect() { if (ssl_) { + // Calling SSL_shutdown prevents the session from being marked as + // unresumable. + SSL_shutdown(ssl_); SSL_free(ssl_); ssl_ = NULL; } diff --git a/net/socket/ssl_client_socket_openssl.h b/net/socket/ssl_client_socket_openssl.h index e1e1778..98b3fe1 100644 --- a/net/socket/ssl_client_socket_openssl.h +++ b/net/socket/ssl_client_socket_openssl.h @@ -43,6 +43,9 @@ class SSLClientSocketOpenSSL : public SSLClientSocket { ~SSLClientSocketOpenSSL(); const HostPortPair& host_and_port() const { return host_and_port_; } + const std::string& ssl_session_cache_shard() const { + return ssl_session_cache_shard_; + } // Callback from the SSL layer that indicates the remote server is requesting // a certificate for this client. @@ -151,6 +154,10 @@ class SSLClientSocketOpenSSL : public SSLClientSocket { scoped_ptr<ClientSocketHandle> transport_; const HostPortPair host_and_port_; SSLConfig ssl_config_; + // ssl_session_cache_shard_ is an opaque string that partitions the SSL + // session cache. i.e. sessions created with one value will not attempt to + // resume on the socket with a different value. + const std::string ssl_session_cache_shard_; // Used for session cache diagnostics. bool trying_cached_session_; diff --git a/net/socket/ssl_client_socket_pool.cc b/net/socket/ssl_client_socket_pool.cc index 1ec0453..744eb38 100644 --- a/net/socket/ssl_client_socket_pool.cc +++ b/net/socket/ssl_client_socket_pool.cc @@ -452,6 +452,7 @@ SSLClientSocketPool::SSLClientSocketPool( TransportSecurityState* transport_security_state, DnsCertProvenanceChecker* dns_cert_checker, SSLHostInfoFactory* ssl_host_info_factory, + const std::string& ssl_session_cache_shard, ClientSocketFactory* client_socket_factory, TransportClientSocketPool* transport_pool, SOCKSClientSocketPool* socks_pool, @@ -474,7 +475,8 @@ SSLClientSocketPool::SSLClientSocketPool( origin_bound_cert_service, transport_security_state, dns_cert_checker, - ssl_host_info_factory), + ssl_host_info_factory, + ssl_session_cache_shard), net_log)), ssl_config_service_(ssl_config_service) { if (ssl_config_service_) diff --git a/net/socket/ssl_client_socket_pool.h b/net/socket/ssl_client_socket_pool.h index cec2e25..6b4de78 100644 --- a/net/socket/ssl_client_socket_pool.h +++ b/net/socket/ssl_client_socket_pool.h @@ -183,6 +183,7 @@ class NET_EXPORT_PRIVATE SSLClientSocketPool TransportSecurityState* transport_security_state, DnsCertProvenanceChecker* dns_cert_checker, SSLHostInfoFactory* ssl_host_info_factory, + const std::string& ssl_session_cache_shard, ClientSocketFactory* client_socket_factory, TransportClientSocketPool* transport_pool, SOCKSClientSocketPool* socks_pool, diff --git a/net/socket/ssl_client_socket_pool_unittest.cc b/net/socket/ssl_client_socket_pool_unittest.cc index 7da8aaa..05dd93d 100644 --- a/net/socket/ssl_client_socket_pool_unittest.cc +++ b/net/socket/ssl_client_socket_pool_unittest.cc @@ -98,6 +98,7 @@ class SSLClientSocketPoolTest : public testing::Test { NULL /* dnsrr_resolver */, NULL /* dns_cert_checker */, NULL /* ssl_host_info_factory */, + "" /* ssl_session_cache_shard */, &socket_factory_, transport_pool ? &transport_socket_pool_ : NULL, socks_pool ? &socks_socket_pool_ : NULL, diff --git a/net/socket/ssl_client_socket_unittest.cc b/net/socket/ssl_client_socket_unittest.cc index aadffe1..7b9436d 100644 --- a/net/socket/ssl_client_socket_unittest.cc +++ b/net/socket/ssl_client_socket_unittest.cc @@ -753,3 +753,9 @@ TEST_F(SSLClientSocketTest, ClientSocketHandleNotFromPool) { rv = callback.WaitForResult(); EXPECT_EQ(net::OK, rv); } + +// Verifies that SSLClientSocket::ClearSessionCache can be called without +// explicit NSS initialization. +TEST(SSLClientSocket, ClearSessionCache) { + net::SSLClientSocket::ClearSessionCache(); +} diff --git a/net/test/test_server.cc b/net/test/test_server.cc index c27f73b..07575a26 100644 --- a/net/test/test_server.cc +++ b/net/test/test_server.cc @@ -56,13 +56,15 @@ std::string GetHostname(TestServer::Type type, TestServer::HTTPSOptions::HTTPSOptions() : server_certificate(CERT_OK), request_client_certificate(false), - bulk_ciphers(HTTPSOptions::BULK_CIPHER_ANY) {} + bulk_ciphers(HTTPSOptions::BULK_CIPHER_ANY), + record_resume(false) {} TestServer::HTTPSOptions::HTTPSOptions( TestServer::HTTPSOptions::ServerCertificate cert) : server_certificate(cert), request_client_certificate(false), - bulk_ciphers(HTTPSOptions::BULK_CIPHER_ANY) {} + bulk_ciphers(HTTPSOptions::BULK_CIPHER_ANY), + record_resume(false) {} TestServer::HTTPSOptions::~HTTPSOptions() {} @@ -409,6 +411,9 @@ bool TestServer::AddCommandLineArguments(CommandLine* command_line) const { command_line->AppendArg(kBulkCipherSwitch + "=aes256"); if (https_options_.bulk_ciphers & HTTPSOptions::BULK_CIPHER_3DES) command_line->AppendArg(kBulkCipherSwitch + "=3des"); + + if (https_options_.record_resume) + command_line->AppendArg("--https-record-resume"); } return true; diff --git a/net/test/test_server.h b/net/test/test_server.h index 8d6d8a2..18c1420 100644 --- a/net/test/test_server.h +++ b/net/test/test_server.h @@ -103,6 +103,11 @@ class TestServer { // HTTPS server, or BULK_CIPHER_ANY to indicate that all implemented // ciphers are acceptable. int bulk_ciphers; + + // If true, pass the --https-record-resume argument to testserver.py which + // causes it to log session cache actions and echo the log on + // /ssl-session-cache. + bool record_resume; }; TestServer(Type type, const FilePath& document_root); diff --git a/net/tools/testserver/testserver.py b/net/tools/testserver/testserver.py index 0d4a430..35a08c4 100755 --- a/net/tools/testserver/testserver.py +++ b/net/tools/testserver/testserver.py @@ -68,6 +68,20 @@ def debug(str): debug_output.write(str + "\n") debug_output.flush() +class RecordingSSLSessionCache(object): + """RecordingSSLSessionCache acts as a TLS session cache and maintains a log of + lookups and inserts in order to test session cache behaviours.""" + + def __init__(self): + self.log = [] + + def __getitem__(self, sessionID): + self.log.append(('lookup', sessionID)) + raise KeyError() + + def __setitem__(self, sessionID, session): + self.log.append(('insert', sessionID)) + class StoppableHTTPServer(BaseHTTPServer.HTTPServer): """This is a specialization of of BaseHTTPServer to allow it to be exited cleanly (by setting its "stop" member to True).""" @@ -83,7 +97,8 @@ class HTTPSServer(tlslite.api.TLSSocketServerMixIn, StoppableHTTPServer): """This is a specialization of StoppableHTTPerver that add https support.""" def __init__(self, server_address, request_hander_class, cert_path, - ssl_client_auth, ssl_client_cas, ssl_bulk_ciphers): + ssl_client_auth, ssl_client_cas, ssl_bulk_ciphers, + record_resume_info): s = open(cert_path).read() x509 = tlslite.api.X509() x509.parse(s) @@ -101,7 +116,12 @@ class HTTPSServer(tlslite.api.TLSSocketServerMixIn, StoppableHTTPServer): if ssl_bulk_ciphers is not None: self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers - self.session_cache = tlslite.api.SessionCache() + if record_resume_info: + # If record_resume_info is true then we'll replace the session cache with + # an object that records the lookups and inserts that it sees. + self.session_cache = RecordingSSLSessionCache() + else: + self.session_cache = tlslite.api.SessionCache() StoppableHTTPServer.__init__(self, server_address, request_hander_class) def handshake(self, tlsConnection): @@ -357,6 +377,7 @@ class TestPageHandler(BasePageHandler): self.ClientRedirectHandler, self.MultipartHandler, self.MultipartSlowHandler, + self.GetSSLSessionCacheHandler, self.DefaultResponseHandler] post_handlers = [ self.EchoTitleHandler, @@ -1380,6 +1401,23 @@ class TestPageHandler(BasePageHandler): self.wfile.write('--' + bound + '--') return True + def GetSSLSessionCacheHandler(self): + """Send a reply containing a log of the session cache operations.""" + + if not self._ShouldHandleRequest('/ssl-session-cache'): + return False + + self.send_response(200) + self.send_header('Content-Type', 'text/plain') + self.end_headers() + try: + for (action, sessionID) in self.server.session_cache.log: + self.wfile.write('%s\t%s\n' % (action, sessionID.encode('hex'))) + except AttributeError, e: + self.wfile.write('Pass --https-record-resume in order to use' + + ' this request') + return True + def DefaultResponseHandler(self): """This is the catch-all response handler for requests that aren't handled by one of the special handlers above. @@ -1805,7 +1843,7 @@ def main(options, args): return server = HTTPSServer(('127.0.0.1', port), TestPageHandler, options.cert, options.ssl_client_auth, options.ssl_client_ca, - options.ssl_bulk_cipher) + options.ssl_bulk_cipher, options.record_resume) print 'HTTPS server started on port %d...' % server.server_port else: server = StoppableHTTPServer(('127.0.0.1', port), TestPageHandler) @@ -1921,6 +1959,11 @@ if __name__ == '__main__': help='Specify that https should be used, specify ' 'the path to the cert containing the private key ' 'the server should use.') + option_parser.add_option('', '--https-record-resume', dest='record_resume', + const=True, default=False, action='store_const', + help='Record resumption cache events rather than' + ' resuming as normal. Allows the use of the' + ' /ssl-session-cache request') option_parser.add_option('', '--ssl-client-auth', action='store_true', help='Require SSL client auth on every connection.') option_parser.add_option('', '--ssl-client-ca', action='append', default=[], diff --git a/net/url_request/url_request_unittest.cc b/net/url_request/url_request_unittest.cc index 811f885..8dcdbb1 100644 --- a/net/url_request/url_request_unittest.cc +++ b/net/url_request/url_request_unittest.cc @@ -23,6 +23,7 @@ #include "base/process_util.h" #include "base/string_number_conversions.h" #include "base/string_piece.h" +#include "base/string_split.h" #include "base/string_util.h" #include "base/stringprintf.h" #include "base/utf_string_conversions.h" @@ -41,9 +42,11 @@ #include "net/ftp/ftp_network_layer.h" #include "net/http/http_cache.h" #include "net/http/http_network_layer.h" +#include "net/http/http_network_session.h" #include "net/http/http_request_headers.h" #include "net/http/http_response_headers.h" #include "net/proxy/proxy_service.h" +#include "net/socket/ssl_client_socket.h" #include "net/test/test_server.h" #include "net/url_request/url_request.h" #include "net/url_request/url_request_file_dir_job.h" @@ -1181,6 +1184,152 @@ TEST_F(HTTPSRequestTest, ClientAuthTest) { } } +TEST_F(HTTPSRequestTest, ResumeTest) { + // Test that we attempt a session resume when making two connections to the + // same host. + TestServer::HTTPSOptions https_options; + https_options.record_resume = true; + TestServer test_server(https_options, + FilePath(FILE_PATH_LITERAL("net/data/ssl"))); + ASSERT_TRUE(test_server.Start()); + + SSLClientSocket::ClearSessionCache(); + + { + TestDelegate d; + TestURLRequest r(test_server.GetURL("ssl-session-cache"), &d); + r.set_context(default_context_); + + r.Start(); + EXPECT_TRUE(r.is_pending()); + + MessageLoop::current()->Run(); + + EXPECT_EQ(1, d.response_started_count()); + } + + reinterpret_cast<HttpCache*>(default_context_->http_transaction_factory())-> + CloseAllConnections(); + + { + TestDelegate d; + TestURLRequest r(test_server.GetURL("ssl-session-cache"), &d); + r.set_context(default_context_); + + r.Start(); + EXPECT_TRUE(r.is_pending()); + + MessageLoop::current()->Run(); + + // The response will look like; + // insert abc + // lookup abc + // insert xyz + // + // With a newline at the end which makes the split think that there are + // four lines. + + EXPECT_EQ(1, d.response_started_count()); + std::vector<std::string> lines; + base::SplitString(d.data_received(), '\n', &lines); + ASSERT_EQ(4u, lines.size()) << d.data_received(); + + std::string session_id; + + for (size_t i = 0; i < 2; i++) { + std::vector<std::string> parts; + base::SplitString(lines[i], '\t', &parts); + ASSERT_EQ(2u, parts.size()); + if (i == 0) { + EXPECT_EQ("insert", parts[0]); + session_id = parts[1]; + } else { + EXPECT_EQ("lookup", parts[0]); + EXPECT_EQ(session_id, parts[1]); + } + } + } +} + +TEST_F(HTTPSRequestTest, SSLSessionCacheShardTest) { + // Test that sessions aren't resumed when the value of ssl_session_cache_shard + // differs. + TestServer::HTTPSOptions https_options; + https_options.record_resume = true; + TestServer test_server(https_options, + FilePath(FILE_PATH_LITERAL("net/data/ssl"))); + ASSERT_TRUE(test_server.Start()); + + SSLClientSocket::ClearSessionCache(); + + { + TestDelegate d; + TestURLRequest r(test_server.GetURL("ssl-session-cache"), &d); + r.set_context(default_context_); + + r.Start(); + EXPECT_TRUE(r.is_pending()); + + MessageLoop::current()->Run(); + + EXPECT_EQ(1, d.response_started_count()); + } + + // Now create a new HttpCache with a different ssl_session_cache_shard value. + HttpNetworkSession::Params params; + params.host_resolver = default_context_->host_resolver(); + params.cert_verifier = default_context_->cert_verifier(); + params.proxy_service = default_context_->proxy_service(); + params.ssl_config_service = default_context_->ssl_config_service(); + params.http_auth_handler_factory = + default_context_->http_auth_handler_factory(); + params.network_delegate = default_context_->network_delegate(); + params.http_server_properties = default_context_->http_server_properties(); + params.ssl_session_cache_shard = "alternate"; + + scoped_ptr<net::HttpCache> cache(new net::HttpCache( + new net::HttpNetworkSession(params), + net::HttpCache::DefaultBackend::InMemory(0))); + + default_context_->set_http_transaction_factory(cache.get()); + + { + TestDelegate d; + TestURLRequest r(test_server.GetURL("ssl-session-cache"), &d); + r.set_context(default_context_); + + r.Start(); + EXPECT_TRUE(r.is_pending()); + + MessageLoop::current()->Run(); + + // The response will look like; + // insert abc + // insert xyz + // + // With a newline at the end which makes the split think that there are + // three lines. + + EXPECT_EQ(1, d.response_started_count()); + std::vector<std::string> lines; + base::SplitString(d.data_received(), '\n', &lines); + ASSERT_EQ(3u, lines.size()); + + std::string session_id; + for (size_t i = 0; i < 2; i++) { + std::vector<std::string> parts; + base::SplitString(lines[i], '\t', &parts); + ASSERT_EQ(2u, parts.size()); + EXPECT_EQ("insert", parts[0]); + if (i == 0) { + session_id = parts[1]; + } else { + EXPECT_NE(session_id, parts[1]); + } + } + } +} + TEST_F(URLRequestTestHTTP, CancelTest) { TestDelegate d; { |