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 @@
'socket/ssl_server_socket_nss.cc',
'socket/ssl_server_socket_nss.h',
'socket/ssl_server_socket_openssl.cc',
'socket/ssl_session_cache_openssl.cc',
'socket/ssl_session_cache_openssl.h',
'socket/ssl_socket.h',
'socket/stream_listen_socket.cc',
'socket/stream_listen_socket.h',
......@@ -1343,6 +1345,8 @@
'socket/ssl_client_socket_openssl.cc',
'socket/ssl_client_socket_openssl.h',
'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.h',
],
......@@ -1883,6 +1887,7 @@
'socket/ssl_client_socket_pool_unittest.cc',
'socket/ssl_client_socket_unittest.cc',
'socket/ssl_server_socket_unittest.cc',
'socket/ssl_session_cache_openssl_unittest.cc',
'socket/tcp_client_socket_unittest.cc',
'socket/tcp_listen_socket_unittest.cc',
'socket/tcp_listen_socket_unittest.h',
......@@ -2040,6 +2045,13 @@
'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', {
'dependencies': [
'../build/linux/system.gyp:ssl',
......@@ -2048,8 +2060,9 @@
'sources!': [
'cert/nss_cert_database_unittest.cc',
],
},
}],
],
}],
[ 'toolkit_uses_gtk == 1', {
'dependencies': [
'../build/linux/system.gyp:gtk',
......@@ -2102,6 +2115,7 @@
'cert/x509_util_openssl_unittest.cc',
'quic/test_tools/crypto_test_utils_openssl.cc',
'socket/ssl_client_socket_openssl_unittest.cc',
'socket/ssl_session_cache_openssl_unittest.cc',
'ssl/openssl_client_key_store_unittest.cc',
],
},
......
......@@ -23,6 +23,7 @@
#include "net/cert/single_request_cert_verifier.h"
#include "net/cert/x509_certificate_net_log_param.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/ssl_cert_request_info.h"
#include "net/ssl/ssl_connection_status_flags.h"
......@@ -41,9 +42,6 @@ namespace {
#define GotoState(s) next_handshake_state_ = s
#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
// overlap with any value of the net::Error range, including net::OK).
const int kNoPendingReadResult = 1;
......@@ -218,108 +216,6 @@ int NoOpVerifyCallback(X509_STORE_CTX*, void *) {
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
// options and mode configuration functions. (SSL_set_options etc)
struct SslSetClearMask {
......@@ -333,15 +229,24 @@ struct SslSetClearMask {
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
class SSLClientSocketOpenSSL::SSLContext {
public:
static SSLContext* GetInstance() { return Singleton<SSLContext>::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);
SSLClientSocketOpenSSL* socket = static_cast<SSLClientSocketOpenSSL*>(
SSL_get_ex_data(ssl, ssl_socket_data_index_));
......@@ -361,12 +266,8 @@ class SSLClientSocketOpenSSL::SSLContext {
ssl_socket_data_index_ = SSL_get_ex_new_index(0, 0, 0, 0, 0);
DCHECK_NE(ssl_socket_data_index_, -1);
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_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_channel_id_cb(ssl_ctx_.get(), ChannelIDCallback);
#if defined(OPENSSL_NPN_NEGOTIATED)
......@@ -378,26 +279,13 @@ class SSLClientSocketOpenSSL::SSLContext {
#endif
}
static int NewSessionCallbackStatic(SSL* ssl, SSL_SESSION* session) {
return GetInstance()->NewSessionCallback(ssl, session);
}
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);
static std::string GetSessionCacheKey(const SSL* ssl) {
SSLClientSocketOpenSSL* socket = GetInstance()->GetClientSocketFromSSL(ssl);
DCHECK(socket);
return GetSocketSessionCacheKey(*socket);
}
void RemoveSessionCallback(SSL_CTX* ctx, SSL_SESSION* session) {
DCHECK(ctx == ssl_ctx());
session_cache_.OnSessionRemoved(session);
}
static SSLSessionCacheOpenSSL::Config kDefaultSessionCacheConfig;
static int ClientCertCallback(SSL* ssl, X509** x509, EVP_PKEY** pkey) {
SSLClientSocketOpenSSL* socket = GetInstance()->GetClientSocketFromSSL(ssl);
......@@ -423,11 +311,18 @@ class SSLClientSocketOpenSSL::SSLContext {
// SSLClientSocketOpenSSL object from an SSL instance.
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_;
// |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
......@@ -756,9 +651,8 @@ bool SSLClientSocketOpenSSL::Init() {
if (!SSL_set_tlsext_host_name(ssl_, host_and_port_.host().c_str()))
return false;
trying_cached_session_ =
context->session_cache()->SetSSLSession(ssl_, host_and_port_,
ssl_session_cache_shard_);
trying_cached_session_ = context->session_cache()->SetSSLSessionWithKey(
ssl_, GetSocketSessionCacheKey(*this));
BIO* ssl_bio = NULL;
// 0 => use default buffer sizes.
......
This diff is collapsed.
// 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
This diff is collapsed.
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