Commit 1279de13 authored by digit@chromium.org's avatar digit@chromium.org

net: Implement new SSL session cache for OpenSSL sockets.

This patch reworks the SSL session caching scheme for OpenSSL
client sockets, as an attempt to get rid of the mysterious crashes
described at http://crbug.com/298606

- Move the internal SSLSessionCache class to its own source file,
  while renaming it as SSLSessionCacheOpenSSL.

- Change the session caching logic to:

  - Completely disable OpenSSL's builtin cache.
  - Implement Session ID generation (and uniqueness check).
  - Implement eviction and expiration detection.

- Add a unit test for SSLSessionCacheOpenSSL.

- Modify SSLClientSocketOpenSSL implementation to use the new cache.

BUG=298606
R=rsleevi@chromium.org,agl@chromium.org,wtc@chromium.org

Review URL: https://codereview.chromium.org/89623002

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@238398 0039d316-1c4b-4281-b951-d872f2087c98
parent f3812a7f
...@@ -950,6 +950,8 @@ ...@@ -950,6 +950,8 @@
'socket/ssl_server_socket_nss.cc', 'socket/ssl_server_socket_nss.cc',
'socket/ssl_server_socket_nss.h', 'socket/ssl_server_socket_nss.h',
'socket/ssl_server_socket_openssl.cc', 'socket/ssl_server_socket_openssl.cc',
'socket/ssl_session_cache_openssl.cc',
'socket/ssl_session_cache_openssl.h',
'socket/ssl_socket.h', 'socket/ssl_socket.h',
'socket/stream_listen_socket.cc', 'socket/stream_listen_socket.cc',
'socket/stream_listen_socket.h', 'socket/stream_listen_socket.h',
...@@ -1343,6 +1345,8 @@ ...@@ -1343,6 +1345,8 @@
'socket/ssl_client_socket_openssl.cc', 'socket/ssl_client_socket_openssl.cc',
'socket/ssl_client_socket_openssl.h', 'socket/ssl_client_socket_openssl.h',
'socket/ssl_server_socket_openssl.cc', 'socket/ssl_server_socket_openssl.cc',
'socket/ssl_session_cache_openssl.cc',
'socket/ssl_session_cache_openssl.h',
'ssl/openssl_client_key_store.cc', 'ssl/openssl_client_key_store.cc',
'ssl/openssl_client_key_store.h', 'ssl/openssl_client_key_store.h',
], ],
...@@ -1883,6 +1887,7 @@ ...@@ -1883,6 +1887,7 @@
'socket/ssl_client_socket_pool_unittest.cc', 'socket/ssl_client_socket_pool_unittest.cc',
'socket/ssl_client_socket_unittest.cc', 'socket/ssl_client_socket_unittest.cc',
'socket/ssl_server_socket_unittest.cc', 'socket/ssl_server_socket_unittest.cc',
'socket/ssl_session_cache_openssl_unittest.cc',
'socket/tcp_client_socket_unittest.cc', 'socket/tcp_client_socket_unittest.cc',
'socket/tcp_listen_socket_unittest.cc', 'socket/tcp_listen_socket_unittest.cc',
'socket/tcp_listen_socket_unittest.h', 'socket/tcp_listen_socket_unittest.h',
...@@ -2040,6 +2045,13 @@ ...@@ -2040,6 +2045,13 @@
'net_test_jni_headers', 'net_test_jni_headers',
], ],
}], }],
[ 'use_openssl == 1', {
# Avoid compiling/linking with the system library.
'dependencies': [
'../third_party/openssl/openssl.gyp:openssl',
],
}, { # use_openssl == 0
'conditions': [
[ 'desktop_linux == 1 or chromeos == 1', { [ 'desktop_linux == 1 or chromeos == 1', {
'dependencies': [ 'dependencies': [
'../build/linux/system.gyp:ssl', '../build/linux/system.gyp:ssl',
...@@ -2048,8 +2060,9 @@ ...@@ -2048,8 +2060,9 @@
'sources!': [ 'sources!': [
'cert/nss_cert_database_unittest.cc', 'cert/nss_cert_database_unittest.cc',
], ],
}, }],
], ],
}],
[ 'toolkit_uses_gtk == 1', { [ 'toolkit_uses_gtk == 1', {
'dependencies': [ 'dependencies': [
'../build/linux/system.gyp:gtk', '../build/linux/system.gyp:gtk',
...@@ -2102,6 +2115,7 @@ ...@@ -2102,6 +2115,7 @@
'cert/x509_util_openssl_unittest.cc', 'cert/x509_util_openssl_unittest.cc',
'quic/test_tools/crypto_test_utils_openssl.cc', 'quic/test_tools/crypto_test_utils_openssl.cc',
'socket/ssl_client_socket_openssl_unittest.cc', 'socket/ssl_client_socket_openssl_unittest.cc',
'socket/ssl_session_cache_openssl_unittest.cc',
'ssl/openssl_client_key_store_unittest.cc', 'ssl/openssl_client_key_store_unittest.cc',
], ],
}, },
......
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
#include "net/cert/single_request_cert_verifier.h" #include "net/cert/single_request_cert_verifier.h"
#include "net/cert/x509_certificate_net_log_param.h" #include "net/cert/x509_certificate_net_log_param.h"
#include "net/socket/ssl_error_params.h" #include "net/socket/ssl_error_params.h"
#include "net/socket/ssl_session_cache_openssl.h"
#include "net/ssl/openssl_client_key_store.h" #include "net/ssl/openssl_client_key_store.h"
#include "net/ssl/ssl_cert_request_info.h" #include "net/ssl/ssl_cert_request_info.h"
#include "net/ssl/ssl_connection_status_flags.h" #include "net/ssl/ssl_connection_status_flags.h"
...@@ -41,9 +42,6 @@ namespace { ...@@ -41,9 +42,6 @@ namespace {
#define GotoState(s) next_handshake_state_ = s #define GotoState(s) next_handshake_state_ = s
#endif #endif
const int kSessionCacheTimeoutSeconds = 60 * 60;
const size_t kSessionCacheMaxEntires = 1024;
// This constant can be any non-negative/non-zero value (eg: it does not // This constant can be any non-negative/non-zero value (eg: it does not
// overlap with any value of the net::Error range, including net::OK). // overlap with any value of the net::Error range, including net::OK).
const int kNoPendingReadResult = 1; const int kNoPendingReadResult = 1;
...@@ -218,108 +216,6 @@ int NoOpVerifyCallback(X509_STORE_CTX*, void *) { ...@@ -218,108 +216,6 @@ int NoOpVerifyCallback(X509_STORE_CTX*, void *) {
return 1; return 1;
} }
// 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, per shard.
class SSLSessionCache {
public:
SSLSessionCache() {}
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(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 << " => "
<< 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);
}
void OnSessionRemoved(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_);
SessionMap::iterator it = session_map_.find(session);
if (it == session_map_.end())
return;
DVLOG(2) << "Remove session " << session << " => " << it->second->first;
DCHECK(it->second->second == session);
host_port_map_.erase(it->second);
session_map_.erase(it);
session_to_free.reset(session);
DCHECK_EQ(host_port_map_.size(), session_map_.size());
}
// 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,
const std::string& shard) {
base::AutoLock lock(lock_);
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 << " => " << cache_key;
SSL_SESSION* session = it->second;
DCHECK(session);
DCHECK(session_map_[session] == it);
// Ideally we'd release |lock_| before calling into OpenSSL here, however
// that opens a small risk |session| will go out of scope before it is used.
// Alternatively we would take a temporary local refcount on |session|,
// except OpenSSL does not provide a public API for adding a ref (c.f.
// SSL_SESSION_free which decrements the ref).
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() {
base::AutoLock lock(lock_);
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.
typedef std::map<std::string, SSL_SESSION*> HostPortMap;
typedef std::map<SSL_SESSION*, HostPortMap::iterator> SessionMap;
HostPortMap host_port_map_;
SessionMap session_map_;
// Protects access to both the above maps.
base::Lock lock_;
DISALLOW_COPY_AND_ASSIGN(SSLSessionCache);
};
// Utility to construct the appropriate set & clear masks for use the OpenSSL // Utility to construct the appropriate set & clear masks for use the OpenSSL
// options and mode configuration functions. (SSL_set_options etc) // options and mode configuration functions. (SSL_set_options etc)
struct SslSetClearMask { struct SslSetClearMask {
...@@ -333,15 +229,24 @@ struct SslSetClearMask { ...@@ -333,15 +229,24 @@ struct SslSetClearMask {
long clear_mask; long clear_mask;
}; };
// Compute a unique key string for the SSL session cache. |socket| is an
// input socket object. Return a string.
std::string GetSocketSessionCacheKey(const SSLClientSocketOpenSSL& socket) {
std::string result = socket.host_and_port().ToString();
result.append("/");
result.append(socket.ssl_session_cache_shard());
return result;
}
} // namespace } // namespace
class SSLClientSocketOpenSSL::SSLContext { class SSLClientSocketOpenSSL::SSLContext {
public: public:
static SSLContext* GetInstance() { return Singleton<SSLContext>::get(); } static SSLContext* GetInstance() { return Singleton<SSLContext>::get(); }
SSL_CTX* ssl_ctx() { return ssl_ctx_.get(); } SSL_CTX* ssl_ctx() { return ssl_ctx_.get(); }
SSLSessionCache* session_cache() { return &session_cache_; } SSLSessionCacheOpenSSL* session_cache() { return &session_cache_; }
SSLClientSocketOpenSSL* GetClientSocketFromSSL(SSL* ssl) { SSLClientSocketOpenSSL* GetClientSocketFromSSL(const SSL* ssl) {
DCHECK(ssl); DCHECK(ssl);
SSLClientSocketOpenSSL* socket = static_cast<SSLClientSocketOpenSSL*>( SSLClientSocketOpenSSL* socket = static_cast<SSLClientSocketOpenSSL*>(
SSL_get_ex_data(ssl, ssl_socket_data_index_)); SSL_get_ex_data(ssl, ssl_socket_data_index_));
...@@ -361,12 +266,8 @@ class SSLClientSocketOpenSSL::SSLContext { ...@@ -361,12 +266,8 @@ class SSLClientSocketOpenSSL::SSLContext {
ssl_socket_data_index_ = SSL_get_ex_new_index(0, 0, 0, 0, 0); ssl_socket_data_index_ = SSL_get_ex_new_index(0, 0, 0, 0, 0);
DCHECK_NE(ssl_socket_data_index_, -1); DCHECK_NE(ssl_socket_data_index_, -1);
ssl_ctx_.reset(SSL_CTX_new(SSLv23_client_method())); ssl_ctx_.reset(SSL_CTX_new(SSLv23_client_method()));
session_cache_.Reset(ssl_ctx_.get(), kDefaultSessionCacheConfig);
SSL_CTX_set_cert_verify_callback(ssl_ctx_.get(), NoOpVerifyCallback, NULL); SSL_CTX_set_cert_verify_callback(ssl_ctx_.get(), NoOpVerifyCallback, NULL);
SSL_CTX_set_session_cache_mode(ssl_ctx_.get(), SSL_SESS_CACHE_CLIENT);
SSL_CTX_sess_set_new_cb(ssl_ctx_.get(), NewSessionCallbackStatic);
SSL_CTX_sess_set_remove_cb(ssl_ctx_.get(), RemoveSessionCallbackStatic);
SSL_CTX_set_timeout(ssl_ctx_.get(), kSessionCacheTimeoutSeconds);
SSL_CTX_sess_set_cache_size(ssl_ctx_.get(), kSessionCacheMaxEntires);
SSL_CTX_set_client_cert_cb(ssl_ctx_.get(), ClientCertCallback); SSL_CTX_set_client_cert_cb(ssl_ctx_.get(), ClientCertCallback);
SSL_CTX_set_channel_id_cb(ssl_ctx_.get(), ChannelIDCallback); SSL_CTX_set_channel_id_cb(ssl_ctx_.get(), ChannelIDCallback);
#if defined(OPENSSL_NPN_NEGOTIATED) #if defined(OPENSSL_NPN_NEGOTIATED)
...@@ -378,26 +279,13 @@ class SSLClientSocketOpenSSL::SSLContext { ...@@ -378,26 +279,13 @@ class SSLClientSocketOpenSSL::SSLContext {
#endif #endif
} }
static int NewSessionCallbackStatic(SSL* ssl, SSL_SESSION* session) { static std::string GetSessionCacheKey(const SSL* ssl) {
return GetInstance()->NewSessionCallback(ssl, session); SSLClientSocketOpenSSL* socket = GetInstance()->GetClientSocketFromSSL(ssl);
} DCHECK(socket);
return GetSocketSessionCacheKey(*socket);
int NewSessionCallback(SSL* ssl, SSL_SESSION* session) {
SSLClientSocketOpenSSL* socket = GetClientSocketFromSSL(ssl);
session_cache_.OnSessionAdded(socket->host_and_port(),
socket->ssl_session_cache_shard(),
session);
return 1; // 1 => We took ownership of |session|.
}
static void RemoveSessionCallbackStatic(SSL_CTX* ctx, SSL_SESSION* session) {
return GetInstance()->RemoveSessionCallback(ctx, session);
} }
void RemoveSessionCallback(SSL_CTX* ctx, SSL_SESSION* session) { static SSLSessionCacheOpenSSL::Config kDefaultSessionCacheConfig;
DCHECK(ctx == ssl_ctx());
session_cache_.OnSessionRemoved(session);
}
static int ClientCertCallback(SSL* ssl, X509** x509, EVP_PKEY** pkey) { static int ClientCertCallback(SSL* ssl, X509** x509, EVP_PKEY** pkey) {
SSLClientSocketOpenSSL* socket = GetInstance()->GetClientSocketFromSSL(ssl); SSLClientSocketOpenSSL* socket = GetInstance()->GetClientSocketFromSSL(ssl);
...@@ -423,11 +311,18 @@ class SSLClientSocketOpenSSL::SSLContext { ...@@ -423,11 +311,18 @@ class SSLClientSocketOpenSSL::SSLContext {
// SSLClientSocketOpenSSL object from an SSL instance. // SSLClientSocketOpenSSL object from an SSL instance.
int ssl_socket_data_index_; int ssl_socket_data_index_;
// 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_; crypto::ScopedOpenSSL<SSL_CTX, SSL_CTX_free> ssl_ctx_;
// |session_cache_| must be destroyed before |ssl_ctx_|.
SSLSessionCacheOpenSSL session_cache_;
};
// static
SSLSessionCacheOpenSSL::Config
SSLClientSocketOpenSSL::SSLContext::kDefaultSessionCacheConfig = {
&GetSessionCacheKey, // key_func
1024, // max_entries
256, // expiration_check_count
60 * 60, // timeout_seconds
}; };
// static // static
...@@ -756,9 +651,8 @@ bool SSLClientSocketOpenSSL::Init() { ...@@ -756,9 +651,8 @@ bool SSLClientSocketOpenSSL::Init() {
if (!SSL_set_tlsext_host_name(ssl_, host_and_port_.host().c_str())) if (!SSL_set_tlsext_host_name(ssl_, host_and_port_.host().c_str()))
return false; return false;
trying_cached_session_ = trying_cached_session_ = context->session_cache()->SetSSLSessionWithKey(
context->session_cache()->SetSSLSession(ssl_, host_and_port_, ssl_, GetSocketSessionCacheKey(*this));
ssl_session_cache_shard_);
BIO* ssl_bio = NULL; BIO* ssl_bio = NULL;
// 0 => use default buffer sizes. // 0 => use default buffer sizes.
......
// Copyright 2013 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_session_cache_openssl.h"
#include <list>
#include <map>
#include <openssl/rand.h>
#include <openssl/ssl.h>
#include "base/containers/hash_tables.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/synchronization/lock.h"
namespace net {
namespace {
// A helper class to lazily create a new EX_DATA index to map SSL_CTX handles
// to their corresponding SSLSessionCacheOpenSSLImpl object.
class SSLContextExIndex {
public:
SSLContextExIndex() {
index_ = SSL_CTX_get_ex_new_index(0, 0, 0, 0, 0);
DCHECK_NE(-1, index_);
}
int index() const { return index_; }
private:
int index_;
};
// static
base::LazyInstance<SSLContextExIndex>::Leaky s_ssl_context_ex_instance =
LAZY_INSTANCE_INITIALIZER;
// Retrieve the global EX_DATA index, created lazily on first call, to
// be used with SSL_CTX_set_ex_data() and SSL_CTX_get_ex_data().
static int GetSSLContextExIndex() {
return s_ssl_context_ex_instance.Get().index();
}
// Helper struct used to store session IDs in a SessionIdIndex container
// (see definition below). To save memory each entry only holds a pointer
// to the session ID buffer, which must outlive the entry itself. On the
// other hand, a hash is included to minimize the number of hashing
// computations during cache operations.
struct SessionId {
SessionId(const unsigned char* a_id, unsigned a_id_len)
: id(a_id), id_len(a_id_len), hash(ComputeHash(a_id, a_id_len)) {}
explicit SessionId(const SessionId& other)
: id(other.id), id_len(other.id_len), hash(other.hash) {}
explicit SessionId(SSL_SESSION* session)
: id(session->session_id),
id_len(session->session_id_length),
hash(ComputeHash(session->session_id, session->session_id_length)) {}
bool operator==(const SessionId& other) const {
return hash == other.hash && id_len == other.id_len &&
!memcmp(id, other.id, id_len);
}
const unsigned char* id;
unsigned id_len;
size_t hash;
private:
// Session ID are random strings of bytes. This happens to compute the same
// value as std::hash<std::string> without the extra string copy. See
// base/containers/hash_tables.h. Other hashing computations are possible,
// this one is just simple enough to do the job.
size_t ComputeHash(const unsigned char* id, unsigned id_len) {
size_t result = 0;
for (unsigned n = 0; n < id_len; ++n)
result += 131 * id[n];
return result;
}
};
} // namespace
} // namespace net
namespace BASE_HASH_NAMESPACE {
template <>
struct hash<net::SessionId> {
std::size_t operator()(const net::SessionId& entry) const {
return entry.hash;
}
};
} // namespace BASE_HASH_NAMESPACE
namespace net {
// Implementation of the real SSLSessionCache.
//
// The implementation is inspired by base::MRUCache, except that the deletor
// also needs to remove the entry from other containers. In a nutshell, this
// uses several basic containers:
//
// |ordering_| is a doubly-linked list of SSL_SESSION handles, ordered in
// MRU order.
//
// |key_index_| is a hash table mapping unique cache keys (e.g. host/port
// values) to a single iterator of |ordering_|. It is used to efficiently
// find the cached session associated with a given key.
//
// |id_index_| is a hash table mapping SessionId values to iterators
// of |key_index_|. If is used to efficiently remove sessions from the cache,
// as well as check for the existence of a session ID value in the cache.
//
// SSL_SESSION objects are reference-counted, and owned by the cache. This
// means that their reference count is incremented when they are added, and
// decremented when they are removed.
//
// Assuming an average key size of 100 characters, each node requires the
// following memory usage on 32-bit Android, when linked against STLport:
//
// 12 (ordering_ node, including SSL_SESSION handle)
// 100 (key characters)
// + 24 (std::string header/minimum size)
// + 8 (key_index_ node, excluding the 2 lines above for the key).
// + 20 (id_index_ node)
// --------
// 164 bytes/node
//
// Hence, 41 KiB for a full cache with a maximum of 1024 entries, excluding
// the size of SSL_SESSION objects and heap fragmentation.
//
class SSLSessionCacheOpenSSLImpl {
public:
// Construct new instance. This registers various hooks into the SSL_CTX
// context |ctx|. OpenSSL will call back during SSL connection
// operations. |key_func| is used to map a SSL handle to a unique cache
// string, according to the client's preferences.
SSLSessionCacheOpenSSLImpl(SSL_CTX* ctx,
const SSLSessionCacheOpenSSL::Config& config)
: ctx_(ctx), config_(config), expiration_check_(0) {
DCHECK(ctx);
// NO_INTERNAL_STORE disables OpenSSL's builtin cache, and
// NO_AUTO_CLEAR disables the call to SSL_CTX_flush_sessions
// every 256 connections (this number is hard-coded in the library
// and can't be changed).
SSL_CTX_set_session_cache_mode(ctx_,
SSL_SESS_CACHE_CLIENT |
SSL_SESS_CACHE_NO_INTERNAL_STORE |
SSL_SESS_CACHE_NO_AUTO_CLEAR);
SSL_CTX_sess_set_new_cb(ctx_, NewSessionCallbackStatic);
SSL_CTX_sess_set_remove_cb(ctx_, RemoveSessionCallbackStatic);
SSL_CTX_set_generate_session_id(ctx_, GenerateSessionIdStatic);
SSL_CTX_set_timeout(ctx_, config_.timeout_seconds);
SSL_CTX_set_ex_data(ctx_, GetSSLContextExIndex(), this);
}
// Destroy this instance. Must happen before |ctx_| is destroyed.
~SSLSessionCacheOpenSSLImpl() {
Flush();
SSL_CTX_set_ex_data(ctx_, GetSSLContextExIndex(), NULL);
SSL_CTX_sess_set_new_cb(ctx_, NULL);
SSL_CTX_sess_set_remove_cb(ctx_, NULL);
SSL_CTX_set_generate_session_id(ctx_, NULL);
}
// Return the number of items in this cache.
size_t size() const { return key_index_.size(); }
// Retrieve the cache key from |ssl| and look for a corresponding
// cached session ID. If one is found, call SSL_set_session() to associate
// it with the |ssl| connection.
//
// Will also check for expired sessions every |expiration_check_count|
// calls.
//
// Return true if a cached session ID was found, false otherwise.
bool SetSSLSession(SSL* ssl) {
std::string cache_key = config_.key_func(ssl);
if (cache_key.empty())
return false;
return SetSSLSessionWithKey(ssl, cache_key);
}
// Variant of SetSSLSession to be used when the client already has computed
// the cache key. Avoid a call to the configuration's |key_func| function.
bool SetSSLSessionWithKey(SSL* ssl, const std::string& cache_key) {
base::AutoLock locked(lock_);
DCHECK_EQ(config_.key_func(ssl), cache_key);
if (++expiration_check_ >= config_.expiration_check_count) {
expiration_check_ = 0;
FlushExpiredSessionsLocked();
}
KeyIndex::iterator it = key_index_.find(cache_key);
if (it == key_index_.end())
return false;
SSL_SESSION* session = *it->second;
DCHECK(session);
DVLOG(2) << "Lookup session: " << session << " for " << cache_key;
// Move to front of MRU list.
ordering_.push_front(session);
ordering_.erase(it->second);
it->second = ordering_.begin();
return SSL_set_session(ssl, session) == 1;
}
// Flush all entries from the cache.
void Flush() {
base::AutoLock lock(lock_);
id_index_.clear();
key_index_.clear();
while (!ordering_.empty()) {
SSL_SESSION* session = ordering_.front();
ordering_.pop_front();
SSL_SESSION_free(session);
}
}
private:
// Type for list of SSL_SESSION handles, ordered in MRU order.
typedef std::list<SSL_SESSION*> MRUSessionList;
// Type for a dictionary from unique cache keys to session list nodes.
typedef base::hash_map<std::string, MRUSessionList::iterator> KeyIndex;
// Type for a dictionary from SessionId values to key index nodes.
typedef base::hash_map<SessionId, KeyIndex::iterator> SessionIdIndex;
// Return the key associated with a given session, or the empty string if
// none exist. This shall only be used for debugging.
std::string SessionKey(SSL_SESSION* session) {
if (!session)
return std::string("<null-session>");
if (session->session_id_length == 0)
return std::string("<empty-session-id>");
SessionIdIndex::iterator it = id_index_.find(SessionId(session));
if (it == id_index_.end())
return std::string("<unknown-session>");
return it->second->first;
}
// Remove a given |session| from the cache. Lock must be held.
void RemoveSessionLocked(SSL_SESSION* session) {
lock_.AssertAcquired();
DCHECK(session);
DCHECK_GT(session->session_id_length, 0U);
SessionId session_id(session);
SessionIdIndex::iterator id_it = id_index_.find(session_id);
if (id_it == id_index_.end()) {
LOG(ERROR) << "Trying to remove unknown session from cache: " << session;
return;
}
KeyIndex::iterator key_it = id_it->second;
DCHECK(key_it != key_index_.end());
DCHECK_EQ(session, *key_it->second);
id_index_.erase(session_id);
ordering_.erase(key_it->second);
key_index_.erase(key_it);
SSL_SESSION_free(session);
DCHECK_EQ(key_index_.size(), id_index_.size());
}
// Used internally to flush expired sessions. Lock must be held.
void FlushExpiredSessionsLocked() {
lock_.AssertAcquired();
// Unfortunately, OpenSSL initializes |session->time| with a time()
// timestamps, which makes mocking / unit testing difficult.
long timeout_secs = static_cast<long>(::time(NULL));
MRUSessionList::iterator it = ordering_.begin();
while (it != ordering_.end()) {
SSL_SESSION* session = *it++;
// Important, use <= instead of < here to allow unit testing to
// work properly. That's because unit tests that check the expiration
// behaviour will use a session timeout of 0 seconds.
if (session->time + session->timeout <= timeout_secs) {
DVLOG(2) << "Expiring session " << session << " for "
<< SessionKey(session);
RemoveSessionLocked(session);
}
}
}
// Retrieve the cache associated with a given SSL context |ctx|.
static SSLSessionCacheOpenSSLImpl* GetCache(SSL_CTX* ctx) {
DCHECK(ctx);
void* result = SSL_CTX_get_ex_data(ctx, GetSSLContextExIndex());
DCHECK(result);
return reinterpret_cast<SSLSessionCacheOpenSSLImpl*>(result);
}
// Called by OpenSSL when a new |session| was created and added to a given
// |ssl| connection. Note that the session's reference count was already
// incremented before the function is entered. The function must return 1
// to indicate that it took ownership of the session, i.e. that the caller
// should not decrement its reference count after completion.
static int NewSessionCallbackStatic(SSL* ssl, SSL_SESSION* session) {
GetCache(ssl->ctx)->OnSessionAdded(ssl, session);
return 1;
}
// Called by OpenSSL to indicate that a session must be removed from the
// cache. This happens when SSL_CTX is destroyed.
static void RemoveSessionCallbackStatic(SSL_CTX* ctx, SSL_SESSION* session) {
GetCache(ctx)->OnSessionRemoved(session);
}
// Called by OpenSSL to generate a new session ID. This happens during a
// SSL connection operation, when the SSL object doesn't have a session yet.
//
// A session ID is a random string of bytes used to uniquely identify the
// session between a client and a server.
//
// |ssl| is a SSL connection handle. Ignored here.
// |id| is the target buffer where the ID must be generated.
// |*id_len| is, on input, the size of the desired ID. It will be 16 for
// SSLv2, and 32 for anything else. OpenSSL allows an implementation
// to change it on output, but this will not happen here.
//
// The function must ensure the generated ID is really unique, i.e. that
// another session in the cache doesn't already use the same value. It must
// return 1 to indicate success, or 0 for failure.
static int GenerateSessionIdStatic(const SSL* ssl,
unsigned char* id,
unsigned* id_len) {
if (!GetCache(ssl->ctx)->OnGenerateSessionId(id, *id_len))
return 0;
return 1;
}
// Add |session| to the cache in association with |cache_key|. If a session
// already exists, it is replaced with the new one. This assumes that the
// caller already incremented the session's reference count.
void OnSessionAdded(SSL* ssl, SSL_SESSION* session) {
base::AutoLock locked(lock_);
DCHECK(ssl);
DCHECK_GT(session->session_id_length, 0U);
std::string cache_key = config_.key_func(ssl);
KeyIndex::iterator it = key_index_.find(cache_key);
if (it == key_index_.end()) {
DVLOG(2) << "Add session " << session << " for " << cache_key;
// This is a new session. Add it to the cache.
ordering_.push_front(session);
std::pair<KeyIndex::iterator, bool> ret =
key_index_.insert(std::make_pair(cache_key, ordering_.begin()));
DCHECK(ret.second);
it = ret.first;
DCHECK(it != key_index_.end());
} else {
// An existing session exists for this key, so replace it if needed.
DVLOG(2) << "Replace session " << *it->second << " with " << session
<< " for " << cache_key;
SSL_SESSION* old_session = *it->second;
if (old_session != session) {
id_index_.erase(SessionId(old_session));
SSL_SESSION_free(old_session);
}
ordering_.erase(it->second);
ordering_.push_front(session);
it->second = ordering_.begin();
}
id_index_[SessionId(session)] = it;
if (key_index_.size() > config_.max_entries)
ShrinkCacheLocked();
DCHECK_EQ(key_index_.size(), id_index_.size());
DCHECK_LE(key_index_.size(), config_.max_entries);
}
// Shrink the cache to ensure no more than config_.max_entries entries,
// starting with older entries first. Lock must be acquired.
void ShrinkCacheLocked() {
lock_.AssertAcquired();
DCHECK_EQ(key_index_.size(), ordering_.size());
DCHECK_EQ(key_index_.size(), id_index_.size());
while (key_index_.size() > config_.max_entries) {
MRUSessionList::reverse_iterator it = ordering_.rbegin();
DCHECK(it != ordering_.rend());
SSL_SESSION* session = *it;
DCHECK(session);
DVLOG(2) << "Evicting session " << session << " for "
<< SessionKey(session);
RemoveSessionLocked(session);
}
}
// Remove |session| from the cache.
void OnSessionRemoved(SSL_SESSION* session) {
base::AutoLock locked(lock_);
DVLOG(2) << "Remove session " << session << " for " << SessionKey(session);
RemoveSessionLocked(session);
}
// See GenerateSessionIdStatic for a description of what this function does.
bool OnGenerateSessionId(unsigned char* id, unsigned id_len) {
base::AutoLock locked(lock_);
// This mimics def_generate_session_id() in openssl/ssl/ssl_sess.cc,
// I.e. try to generate a pseudo-random bit string, and check that no
// other entry in the cache has the same value.
const size_t kMaxTries = 10;
for (size_t tries = 0; tries < kMaxTries; ++tries) {
if (RAND_pseudo_bytes(id, id_len) <= 0) {
DLOG(ERROR) << "Couldn't generate " << id_len
<< " pseudo random bytes?";
return false;
}
if (id_index_.find(SessionId(id, id_len)) == id_index_.end())
return true;
}
DLOG(ERROR) << "Couldn't generate unique session ID of " << id_len
<< "bytes after " << kMaxTries << " tries.";
return false;
}
SSL_CTX* ctx_;
SSLSessionCacheOpenSSL::Config config_;
// method to get the index which can later be used with SSL_CTX_get_ex_data()
// or SSL_CTX_set_ex_data().
base::Lock lock_; // Protects access to containers below.
MRUSessionList ordering_;
KeyIndex key_index_;
SessionIdIndex id_index_;
size_t expiration_check_;
};
SSLSessionCacheOpenSSL::~SSLSessionCacheOpenSSL() { delete impl_; }
size_t SSLSessionCacheOpenSSL::size() const { return impl_->size(); }
void SSLSessionCacheOpenSSL::Reset(SSL_CTX* ctx, const Config& config) {
if (impl_)
delete impl_;
impl_ = new SSLSessionCacheOpenSSLImpl(ctx, config);
}
bool SSLSessionCacheOpenSSL::SetSSLSession(SSL* ssl) {
return impl_->SetSSLSession(ssl);
}
bool SSLSessionCacheOpenSSL::SetSSLSessionWithKey(
SSL* ssl,
const std::string& cache_key) {
return impl_->SetSSLSessionWithKey(ssl, cache_key);
}
void SSLSessionCacheOpenSSL::Flush() { impl_->Flush(); }
} // namespace net
// Copyright 2013 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.
#ifndef NET_SOCKET_SSL_SESSION_CACHE_OPENSSL_H
#define NET_SOCKET_SSL_SESSION_CACHE_OPENSSL_H
#include <string>
#include "base/basictypes.h"
#include "net/base/net_export.h"
// Avoid including OpenSSL headers here.
typedef struct ssl_ctx_st SSL_CTX;
typedef struct ssl_st SSL;
namespace net {
class SSLSessionCacheOpenSSLImpl;
// A class used to implement a custom cache of SSL_SESSION objects.
// Usage is as follows:
//
// - Client creates a new cache instance with appropriate configuration,
// associating it with a given SSL_CTX object.
//
// The configuration must include a pointer to a client-provided function
// that can retrieve a unique cache key from an existing SSL handle.
//
// - When creating a new SSL connection, call SetSSLSession() with the newly
// created SSL handle, and a cache key for the current host/port. If a
// session is already in the cache, it will be added to the connection
// through SSL_set_session().
//
// - Otherwise, OpenSSL will create a new SSL_SESSION object during the
// connection, and will pass it to the cache's internal functions,
// transparently to the client.
//
// - Each session has a timeout in seconds, which are checked every N-th call
// to SetSSLSession(), where N is the current configuration's
// |check_expiration_count|. Expired sessions are removed automatically
// from the cache.
//
// - Clients can call Flush() to remove all sessions from the cache, this is
// useful when the system's certificate store has changed.
//
// This class is thread-safe. There shouldn't be any issue with multiple
// SSL connections being performed in parallel in multiple threads.
class NET_EXPORT SSLSessionCacheOpenSSL {
public:
// Type of a function that takes a SSL handle and returns a unique cache
// key string to identify it.
typedef std::string GetSessionKeyFunction(const SSL* ssl);
// A small structure used to configure a cache on creation.
// |key_func| is a function used at runtime to retrieve the unique cache key
// from a given SSL connection handle.
// |max_entries| is the maximum number of entries in the cache.
// |expiration_check_count| is the number of calls to SetSSLSession() that
// will trigger a check for expired sessions.
// |timeout_seconds| is the timeout of new cached sessions in seconds.
struct Config {
GetSessionKeyFunction* key_func;
size_t max_entries;
size_t expiration_check_count;
int timeout_seconds;
};
SSLSessionCacheOpenSSL() : impl_(NULL) {}
// Construct a new cache instance.
// |ctx| is a SSL_CTX context handle that will be associated with this cache.
// |key_func| is a function that will be used at runtime to retrieve the
// unique cache key from a SSL connection handle.
// |max_entries| is the maximum number of entries in the cache.
// |timeout_seconds| is the timeout of new cached sessions in seconds.
// |expiration_check_count| is the number of calls to SetSSLSession() that
// will trigger a check for expired sessions.
SSLSessionCacheOpenSSL(SSL_CTX* ctx, const Config& config) : impl_(NULL) {
Reset(ctx, config);
}
// Destroy this instance. This must be called before the SSL_CTX handle
// is destroyed.
~SSLSessionCacheOpenSSL();
// Reset the cache configuration. This flushes any existing entries.
void Reset(SSL_CTX* ctx, const Config& config);
size_t size() const;
// Lookup the unique cache key associated with |ssl| connection handle,
// and find a cached session for it in the cache. If one is found, associate
// it with the |ssl| connection through SSL_set_session(). Consider using
// SetSSLSessionWithKey() if you already have the key.
//
// Every |check_expiration_count| call to either SetSSLSession() or
// SetSSLSessionWithKey() triggers a check for, and removal of, expired
// sessions.
//
// Return true iff a cached session was associated with the |ssl| connection.
bool SetSSLSession(SSL* ssl);
// A more efficient variant of SetSSLSession() that can be used if the caller
// already has the cache key for the session of interest. The caller must
// ensure that the value of |cache_key| matches the result of calling the
// configuration's |key_func| function with the |ssl| as parameter.
//
// Every |check_expiration_count| call to either SetSSLSession() or
// SetSSLSessionWithKey() triggers a check for, and removal of, expired
// sessions.
//
// Return true iff a cached session was associated with the |ssl| connection.
bool SetSSLSessionWithKey(SSL* ssl, const std::string& cache_key);
// Flush removes all entries from the cache. This is typically called when
// the system's certificate store has changed.
void Flush();
// TODO(digit): Move to client code.
static const int kDefaultTimeoutSeconds = 60 * 60;
static const size_t kMaxEntries = 1024;
static const size_t kMaxExpirationChecks = 256;
private:
DISALLOW_COPY_AND_ASSIGN(SSLSessionCacheOpenSSL);
SSLSessionCacheOpenSSLImpl* impl_;
};
} // namespace net
#endif // NET_SOCKET_SSL_SESSION_CACHE_OPENSSL_H
\ No newline at end of file
// Copyright 2013 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_session_cache_openssl.h"
#include <openssl/ssl.h>
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/strings/stringprintf.h"
#include "crypto/openssl_util.h"
#include "testing/gtest/include/gtest/gtest.h"
// This is an internal OpenSSL function that can be used to create a new
// session for an existing SSL object. This shall force a call to the
// 'generate_session_id' callback from the SSL's session context.
// |s| is the target SSL connection handle.
// |session| is non-0 to ask for the creation of a new session. If 0,
// this will set an empty session with no ID instead.
extern "C" int ssl_get_new_session(SSL* s, int session);
// This is an internal OpenSSL function which is used internally to add
// a new session to the cache. It is normally triggered by a succesful
// connection. However, this unit test does not use the network at all.
extern "C" void ssl_update_cache(SSL* s, int mode);
namespace net {
namespace {
typedef crypto::ScopedOpenSSL<SSL, SSL_free> ScopedSSL;
// Helper class used to associate arbitrary std::string keys with SSL objects.
class SSLKeyHelper {
public:
// Return the string associated with a given SSL handle |ssl|, or the
// empty string if none exists.
static std::string Get(const SSL* ssl) {
return GetInstance()->GetValue(ssl);
}
// Associate a string with a given SSL handle |ssl|.
static void Set(SSL* ssl, const std::string& value) {
GetInstance()->SetValue(ssl, value);
}
static SSLKeyHelper* GetInstance() {
static base::LazyInstance<SSLKeyHelper>::Leaky s_instance =
LAZY_INSTANCE_INITIALIZER;
return s_instance.Pointer();
}
SSLKeyHelper() {
ex_index_ = SSL_get_ex_new_index(0, NULL, NULL, KeyDup, KeyFree);
CHECK_NE(-1, ex_index_);
}
std::string GetValue(const SSL* ssl) {
std::string* value =
reinterpret_cast<std::string*>(SSL_get_ex_data(ssl, ex_index_));
if (!value)
return std::string();
return *value;
}
void SetValue(SSL* ssl, const std::string& value) {
int ret = SSL_set_ex_data(ssl, ex_index_, new std::string(value));
CHECK_EQ(1, ret);
}
// Called when an SSL object is copied through SSL_dup(). This needs to copy
// the value as well.
static int KeyDup(CRYPTO_EX_DATA* to,
CRYPTO_EX_DATA* from,
void* from_fd,
int idx,
long argl,
void* argp) {
// |from_fd| is really the address of a temporary pointer. On input, it
// points to the value from the original SSL object. The function must
// update it to the address of a copy.
std::string** ptr = reinterpret_cast<std::string**>(from_fd);
std::string* old_string = *ptr;
std::string* new_string = new std::string(*old_string);
*ptr = new_string;
return 0; // Ignored by the implementation.
}
// Called to destroy the value associated with an SSL object.
static void KeyFree(void* parent,
void* ptr,
CRYPTO_EX_DATA* ad,
int index,
long argl,
void* argp) {
std::string* value = reinterpret_cast<std::string*>(ptr);
delete value;
}
int ex_index_;
};
} // namespace
class SSLSessionCacheOpenSSLTest : public testing::Test {
public:
SSLSessionCacheOpenSSLTest() {
crypto::EnsureOpenSSLInit();
ctx_.reset(SSL_CTX_new(SSLv23_client_method()));
cache_.Reset(ctx_.get(), kDefaultConfig);
}
// Reset cache configuration.
void ResetConfig(const SSLSessionCacheOpenSSL::Config& config) {
cache_.Reset(ctx_.get(), config);
}
// Helper function to create a new SSL connection object associated with
// a given unique |cache_key|. This does _not_ add the session to the cache.
// Caller must free the object with SSL_free().
SSL* NewSSL(const std::string& cache_key) {
SSL* ssl = SSL_new(ctx_.get());
if (!ssl)
return NULL;
SSLKeyHelper::Set(ssl, cache_key); // associate cache key.
ResetSessionID(ssl); // create new unique session ID.
return ssl;
}
// Reset the session ID of a given SSL object. This creates a new session
// with a new unique random ID. Does not add it to the cache.
static void ResetSessionID(SSL* ssl) { ssl_get_new_session(ssl, 1); }
// Add a given SSL object and its session to the cache.
void AddToCache(SSL* ssl) {
ssl_update_cache(ssl, ctx_.get()->session_cache_mode);
}
static const SSLSessionCacheOpenSSL::Config kDefaultConfig;
protected:
crypto::ScopedOpenSSL<SSL_CTX, SSL_CTX_free> ctx_;
// |cache_| must be destroyed before |ctx_| and thus appears after it.
SSLSessionCacheOpenSSL cache_;
};
// static
const SSLSessionCacheOpenSSL::Config
SSLSessionCacheOpenSSLTest::kDefaultConfig = {
&SSLKeyHelper::Get, // key_func
1024, // max_entries
256, // expiration_check_count
60 * 60, // timeout_seconds
};
TEST_F(SSLSessionCacheOpenSSLTest, EmptyCacheCreation) {
EXPECT_EQ(0U, cache_.size());
}
TEST_F(SSLSessionCacheOpenSSLTest, CacheOneSession) {
ScopedSSL ssl(NewSSL("hello"));
EXPECT_EQ(0U, cache_.size());
AddToCache(ssl.get());
EXPECT_EQ(1U, cache_.size());
ssl.reset(NULL);
EXPECT_EQ(1U, cache_.size());
}
TEST_F(SSLSessionCacheOpenSSLTest, CacheMultipleSessions) {
const size_t kNumItems = 100;
int local_id = 1;
// Add kNumItems to the cache.
for (size_t n = 0; n < kNumItems; ++n) {
std::string local_id_string = base::StringPrintf("%d", local_id++);
ScopedSSL ssl(NewSSL(local_id_string));
AddToCache(ssl.get());
EXPECT_EQ(n + 1, cache_.size());
}
}
TEST_F(SSLSessionCacheOpenSSLTest, Flush) {
const size_t kNumItems = 100;
int local_id = 1;
// Add kNumItems to the cache.
for (size_t n = 0; n < kNumItems; ++n) {
std::string local_id_string = base::StringPrintf("%d", local_id++);
ScopedSSL ssl(NewSSL(local_id_string));
AddToCache(ssl.get());
}
EXPECT_EQ(kNumItems, cache_.size());
cache_.Flush();
EXPECT_EQ(0U, cache_.size());
}
TEST_F(SSLSessionCacheOpenSSLTest, SetSSLSession) {
const std::string key("hello");
ScopedSSL ssl(NewSSL(key));
// First call should fail because the session is not in the cache.
EXPECT_FALSE(cache_.SetSSLSession(ssl.get()));
SSL_SESSION* session = ssl.get()->session;
EXPECT_TRUE(session);
EXPECT_EQ(1, session->references);
AddToCache(ssl.get());
EXPECT_EQ(2, session->references);
ssl.reset(NULL);
EXPECT_EQ(1, session->references);
// Second call should find the session ID and associate it with |ssl2|.
ScopedSSL ssl2(NewSSL(key));
EXPECT_TRUE(cache_.SetSSLSession(ssl2.get()));
EXPECT_EQ(session, ssl2.get()->session);
EXPECT_EQ(2, session->references);
}
TEST_F(SSLSessionCacheOpenSSLTest, SetSSLSessionWithKey) {
const std::string key("hello");
ScopedSSL ssl(NewSSL(key));
AddToCache(ssl.get());
ssl.reset(NULL);
ScopedSSL ssl2(NewSSL(key));
EXPECT_TRUE(cache_.SetSSLSessionWithKey(ssl2.get(), key));
}
TEST_F(SSLSessionCacheOpenSSLTest, CheckSessionReplacement) {
// Check that if two SSL connections have the same key, only one
// corresponding session can be stored in the cache.
const std::string common_key("common-key");
ScopedSSL ssl1(NewSSL(common_key));
ScopedSSL ssl2(NewSSL(common_key));
AddToCache(ssl1.get());
EXPECT_EQ(1U, cache_.size());
EXPECT_EQ(2, ssl1.get()->session->references);
// This ends up calling OnSessionAdded which will discover that there is
// already one session ID associated with the key, and will replace it.
AddToCache(ssl2.get());
EXPECT_EQ(1U, cache_.size());
EXPECT_EQ(1, ssl1.get()->session->references);
EXPECT_EQ(2, ssl2.get()->session->references);
}
TEST_F(SSLSessionCacheOpenSSLTest, CheckEviction) {
const size_t kMaxItems = 20;
int local_id = 1;
SSLSessionCacheOpenSSL::Config config = kDefaultConfig;
config.max_entries = kMaxItems;
ResetConfig(config);
// Add kMaxItems to the cache.
for (size_t n = 0; n < kMaxItems; ++n) {
std::string local_id_string = base::StringPrintf("%d", local_id++);
ScopedSSL ssl(NewSSL(local_id_string));
AddToCache(ssl.get());
EXPECT_EQ(n + 1, cache_.size());
}
// Continue adding new items to the cache, check that old ones are
// evicted.
for (size_t n = 0; n < kMaxItems; ++n) {
std::string local_id_string = base::StringPrintf("%d", local_id++);
ScopedSSL ssl(NewSSL(local_id_string));
AddToCache(ssl.get());
EXPECT_EQ(kMaxItems, cache_.size());
}
}
// Check that session expiration works properly.
TEST_F(SSLSessionCacheOpenSSLTest, CheckExpiration) {
const size_t kMaxCheckCount = 10;
const size_t kNumEntries = 20;
SSLSessionCacheOpenSSL::Config config = kDefaultConfig;
config.expiration_check_count = kMaxCheckCount;
config.timeout_seconds = 1000;
ResetConfig(config);
// Add |kNumItems - 1| session entries with crafted time values.
for (size_t n = 0; n < kNumEntries - 1U; ++n) {
std::string key = base::StringPrintf("%d", static_cast<int>(n));
ScopedSSL ssl(NewSSL(key));
// Cheat a little: Force the session |time| value, this guarantees that they
// are expired, given that ::time() will always return a value that is
// past the first 100 seconds after the Unix epoch.
ssl.get()->session->time = static_cast<long>(n);
AddToCache(ssl.get());
}
EXPECT_EQ(kNumEntries - 1U, cache_.size());
// Add nother session which will get the current time, and thus not be
// expirable until 1000 seconds have passed.
ScopedSSL good_ssl(NewSSL("good-key"));
AddToCache(good_ssl.get());
good_ssl.reset(NULL);
EXPECT_EQ(kNumEntries, cache_.size());
// Call SetSSLSession() |kMaxCheckCount - 1| times, this shall not expire
// any session
for (size_t n = 0; n < kMaxCheckCount - 1U; ++n) {
ScopedSSL ssl(NewSSL("unknown-key"));
cache_.SetSSLSession(ssl.get());
EXPECT_EQ(kNumEntries, cache_.size());
}
// Call SetSSLSession another time, this shall expire all sessions except
// the last one.
ScopedSSL bad_ssl(NewSSL("unknown-key"));
cache_.SetSSLSession(bad_ssl.get());
bad_ssl.reset(NULL);
EXPECT_EQ(1U, cache_.size());
}
} // namespace net
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment