QUIC - enable persisting of QUICServerInfo (server config) to disk
[chromium-blink-merge.git] / net / socket / ssl_session_cache_openssl_unittest.cc
blob54b1e3be5d6431e85a08170c7da816433934d66b
1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "net/socket/ssl_session_cache_openssl.h"
7 #include <openssl/ssl.h>
9 #include "base/lazy_instance.h"
10 #include "base/logging.h"
11 #include "base/strings/stringprintf.h"
12 #include "crypto/openssl_util.h"
13 #include "crypto/scoped_openssl_types.h"
15 #include "testing/gtest/include/gtest/gtest.h"
17 // This is an internal OpenSSL function that can be used to create a new
18 // session for an existing SSL object. This shall force a call to the
19 // 'generate_session_id' callback from the SSL's session context.
20 // |s| is the target SSL connection handle.
21 // |session| is non-0 to ask for the creation of a new session. If 0,
22 // this will set an empty session with no ID instead.
23 extern "C" int ssl_get_new_session(SSL* s, int session);
25 // This is an internal OpenSSL function which is used internally to add
26 // a new session to the cache. It is normally triggered by a succesful
27 // connection. However, this unit test does not use the network at all.
28 extern "C" void ssl_update_cache(SSL* s, int mode);
30 namespace net {
32 namespace {
34 typedef crypto::ScopedOpenSSL<SSL, SSL_free>::Type ScopedSSL;
35 typedef crypto::ScopedOpenSSL<SSL_CTX, SSL_CTX_free>::Type ScopedSSL_CTX;
37 // Helper class used to associate arbitrary std::string keys with SSL objects.
38 class SSLKeyHelper {
39 public:
40 // Return the string associated with a given SSL handle |ssl|, or the
41 // empty string if none exists.
42 static std::string Get(const SSL* ssl) {
43 return GetInstance()->GetValue(ssl);
46 // Associate a string with a given SSL handle |ssl|.
47 static void Set(SSL* ssl, const std::string& value) {
48 GetInstance()->SetValue(ssl, value);
51 static SSLKeyHelper* GetInstance() {
52 static base::LazyInstance<SSLKeyHelper>::Leaky s_instance =
53 LAZY_INSTANCE_INITIALIZER;
54 return s_instance.Pointer();
57 SSLKeyHelper() {
58 ex_index_ = SSL_get_ex_new_index(0, NULL, NULL, KeyDup, KeyFree);
59 CHECK_NE(-1, ex_index_);
62 std::string GetValue(const SSL* ssl) {
63 std::string* value =
64 reinterpret_cast<std::string*>(SSL_get_ex_data(ssl, ex_index_));
65 if (!value)
66 return std::string();
67 return *value;
70 void SetValue(SSL* ssl, const std::string& value) {
71 int ret = SSL_set_ex_data(ssl, ex_index_, new std::string(value));
72 CHECK_EQ(1, ret);
75 // Called when an SSL object is copied through SSL_dup(). This needs to copy
76 // the value as well.
77 static int KeyDup(CRYPTO_EX_DATA* to,
78 const CRYPTO_EX_DATA* from,
79 void** from_fd,
80 int idx,
81 long argl,
82 void* argp) {
83 // |from_fd| is really the address of a temporary pointer. On input, it
84 // points to the value from the original SSL object. The function must
85 // update it to the address of a copy.
86 std::string** ptr = reinterpret_cast<std::string**>(from_fd);
87 std::string* old_string = *ptr;
88 std::string* new_string = new std::string(*old_string);
89 *ptr = new_string;
90 return 0; // Ignored by the implementation.
93 // Called to destroy the value associated with an SSL object.
94 static void KeyFree(void* parent,
95 void* ptr,
96 CRYPTO_EX_DATA* ad,
97 int index,
98 long argl,
99 void* argp) {
100 std::string* value = reinterpret_cast<std::string*>(ptr);
101 delete value;
104 int ex_index_;
107 } // namespace
109 class SSLSessionCacheOpenSSLTest : public testing::Test {
110 public:
111 SSLSessionCacheOpenSSLTest() {
112 crypto::EnsureOpenSSLInit();
113 ctx_.reset(SSL_CTX_new(SSLv23_client_method()));
114 cache_.Reset(ctx_.get(), kDefaultConfig);
117 // Reset cache configuration.
118 void ResetConfig(const SSLSessionCacheOpenSSL::Config& config) {
119 cache_.Reset(ctx_.get(), config);
122 // Helper function to create a new SSL connection object associated with
123 // a given unique |cache_key|. This does _not_ add the session to the cache.
124 // Caller must free the object with SSL_free().
125 SSL* NewSSL(const std::string& cache_key) {
126 SSL* ssl = SSL_new(ctx_.get());
127 if (!ssl)
128 return NULL;
130 SSLKeyHelper::Set(ssl, cache_key); // associate cache key.
131 ResetSessionID(ssl); // create new unique session ID.
132 return ssl;
135 // Reset the session ID of a given SSL object. This creates a new session
136 // with a new unique random ID. Does not add it to the cache.
137 static void ResetSessionID(SSL* ssl) { ssl_get_new_session(ssl, 1); }
139 // Add a given SSL object and its session to the cache.
140 void AddToCache(SSL* ssl) {
141 ssl_update_cache(ssl, ctx_.get()->session_cache_mode);
144 static const SSLSessionCacheOpenSSL::Config kDefaultConfig;
146 protected:
147 ScopedSSL_CTX ctx_;
148 // |cache_| must be destroyed before |ctx_| and thus appears after it.
149 SSLSessionCacheOpenSSL cache_;
152 // static
153 const SSLSessionCacheOpenSSL::Config
154 SSLSessionCacheOpenSSLTest::kDefaultConfig = {
155 &SSLKeyHelper::Get, // key_func
156 1024, // max_entries
157 256, // expiration_check_count
158 60 * 60, // timeout_seconds
161 TEST_F(SSLSessionCacheOpenSSLTest, EmptyCacheCreation) {
162 EXPECT_EQ(0U, cache_.size());
165 TEST_F(SSLSessionCacheOpenSSLTest, CacheOneSession) {
166 ScopedSSL ssl(NewSSL("hello"));
168 EXPECT_EQ(0U, cache_.size());
169 AddToCache(ssl.get());
170 EXPECT_EQ(1U, cache_.size());
171 ssl.reset(NULL);
172 EXPECT_EQ(1U, cache_.size());
175 TEST_F(SSLSessionCacheOpenSSLTest, CacheMultipleSessions) {
176 const size_t kNumItems = 100;
177 int local_id = 1;
179 // Add kNumItems to the cache.
180 for (size_t n = 0; n < kNumItems; ++n) {
181 std::string local_id_string = base::StringPrintf("%d", local_id++);
182 ScopedSSL ssl(NewSSL(local_id_string));
183 AddToCache(ssl.get());
184 EXPECT_EQ(n + 1, cache_.size());
188 TEST_F(SSLSessionCacheOpenSSLTest, Flush) {
189 const size_t kNumItems = 100;
190 int local_id = 1;
192 // Add kNumItems to the cache.
193 for (size_t n = 0; n < kNumItems; ++n) {
194 std::string local_id_string = base::StringPrintf("%d", local_id++);
195 ScopedSSL ssl(NewSSL(local_id_string));
196 AddToCache(ssl.get());
198 EXPECT_EQ(kNumItems, cache_.size());
200 cache_.Flush();
201 EXPECT_EQ(0U, cache_.size());
204 TEST_F(SSLSessionCacheOpenSSLTest, SetSSLSession) {
205 const std::string key("hello");
206 ScopedSSL ssl(NewSSL(key));
208 // First call should fail because the session is not in the cache.
209 EXPECT_FALSE(cache_.SetSSLSession(ssl.get()));
210 SSL_SESSION* session = ssl.get()->session;
211 EXPECT_TRUE(session);
212 EXPECT_EQ(1, session->references);
214 AddToCache(ssl.get());
215 EXPECT_EQ(2, session->references);
217 // Mark the session as good, so that it is re-used for the second connection.
218 cache_.MarkSSLSessionAsGood(ssl.get());
220 ssl.reset(NULL);
221 EXPECT_EQ(1, session->references);
223 // Second call should find the session ID and associate it with |ssl2|.
224 ScopedSSL ssl2(NewSSL(key));
225 EXPECT_TRUE(cache_.SetSSLSession(ssl2.get()));
227 EXPECT_EQ(session, ssl2.get()->session);
228 EXPECT_EQ(2, session->references);
231 TEST_F(SSLSessionCacheOpenSSLTest, SetSSLSessionWithKey) {
232 const std::string key("hello");
233 ScopedSSL ssl(NewSSL(key));
234 AddToCache(ssl.get());
235 cache_.MarkSSLSessionAsGood(ssl.get());
236 ssl.reset(NULL);
238 ScopedSSL ssl2(NewSSL(key));
239 EXPECT_TRUE(cache_.SetSSLSessionWithKey(ssl2.get(), key));
242 TEST_F(SSLSessionCacheOpenSSLTest, CheckSessionReplacement) {
243 // Check that if two SSL connections have the same key, only one
244 // corresponding session can be stored in the cache.
245 const std::string common_key("common-key");
246 ScopedSSL ssl1(NewSSL(common_key));
247 ScopedSSL ssl2(NewSSL(common_key));
249 AddToCache(ssl1.get());
250 EXPECT_EQ(1U, cache_.size());
251 EXPECT_EQ(2, ssl1.get()->session->references);
253 // This ends up calling OnSessionAdded which will discover that there is
254 // already one session ID associated with the key, and will replace it.
255 AddToCache(ssl2.get());
256 EXPECT_EQ(1U, cache_.size());
257 EXPECT_EQ(1, ssl1.get()->session->references);
258 EXPECT_EQ(2, ssl2.get()->session->references);
261 // Check that when two connections have the same key, a new session is created
262 // if the existing session has not yet been marked "good". Further, after the
263 // first session completes, if the second session has replaced it in the cache,
264 // new sessions should continue to fail until the currently cached session
265 // succeeds.
266 TEST_F(SSLSessionCacheOpenSSLTest, CheckSessionReplacementWhenNotGood) {
267 const std::string key("hello");
268 ScopedSSL ssl(NewSSL(key));
270 // First call should fail because the session is not in the cache.
271 EXPECT_FALSE(cache_.SetSSLSession(ssl.get()));
272 SSL_SESSION* session = ssl.get()->session;
273 ASSERT_TRUE(session);
274 EXPECT_EQ(1, session->references);
276 AddToCache(ssl.get());
277 EXPECT_EQ(2, session->references);
279 // Second call should find the session ID, but because it is not yet good,
280 // fail to associate it with |ssl2|.
281 ScopedSSL ssl2(NewSSL(key));
282 EXPECT_FALSE(cache_.SetSSLSession(ssl2.get()));
283 SSL_SESSION* session2 = ssl2.get()->session;
284 ASSERT_TRUE(session2);
285 EXPECT_EQ(1, session2->references);
287 EXPECT_NE(session, session2);
289 // Add the second connection to the cache. It should replace the first
290 // session, and the cache should hold on to the second session.
291 AddToCache(ssl2.get());
292 EXPECT_EQ(1, session->references);
293 EXPECT_EQ(2, session2->references);
295 // Mark the first session as good, simulating it completing.
296 cache_.MarkSSLSessionAsGood(ssl.get());
298 // Third call should find the session ID, but because the second session (the
299 // current cache entry) is not yet good, fail to associate it with |ssl3|.
300 ScopedSSL ssl3(NewSSL(key));
301 EXPECT_FALSE(cache_.SetSSLSession(ssl3.get()));
302 EXPECT_NE(session, ssl3.get()->session);
303 EXPECT_NE(session2, ssl3.get()->session);
304 EXPECT_EQ(1, ssl3.get()->session->references);
307 TEST_F(SSLSessionCacheOpenSSLTest, CheckEviction) {
308 const size_t kMaxItems = 20;
309 int local_id = 1;
311 SSLSessionCacheOpenSSL::Config config = kDefaultConfig;
312 config.max_entries = kMaxItems;
313 ResetConfig(config);
315 // Add kMaxItems to the cache.
316 for (size_t n = 0; n < kMaxItems; ++n) {
317 std::string local_id_string = base::StringPrintf("%d", local_id++);
318 ScopedSSL ssl(NewSSL(local_id_string));
320 AddToCache(ssl.get());
321 EXPECT_EQ(n + 1, cache_.size());
324 // Continue adding new items to the cache, check that old ones are
325 // evicted.
326 for (size_t n = 0; n < kMaxItems; ++n) {
327 std::string local_id_string = base::StringPrintf("%d", local_id++);
328 ScopedSSL ssl(NewSSL(local_id_string));
330 AddToCache(ssl.get());
331 EXPECT_EQ(kMaxItems, cache_.size());
335 // Check that session expiration works properly.
336 TEST_F(SSLSessionCacheOpenSSLTest, CheckExpiration) {
337 const size_t kMaxCheckCount = 10;
338 const size_t kNumEntries = 20;
340 SSLSessionCacheOpenSSL::Config config = kDefaultConfig;
341 config.expiration_check_count = kMaxCheckCount;
342 config.timeout_seconds = 1000;
343 ResetConfig(config);
345 // Add |kNumItems - 1| session entries with crafted time values.
346 for (size_t n = 0; n < kNumEntries - 1U; ++n) {
347 std::string key = base::StringPrintf("%d", static_cast<int>(n));
348 ScopedSSL ssl(NewSSL(key));
349 // Cheat a little: Force the session |time| value, this guarantees that they
350 // are expired, given that ::time() will always return a value that is
351 // past the first 100 seconds after the Unix epoch.
352 ssl.get()->session->time = static_cast<long>(n);
353 AddToCache(ssl.get());
355 EXPECT_EQ(kNumEntries - 1U, cache_.size());
357 // Add nother session which will get the current time, and thus not be
358 // expirable until 1000 seconds have passed.
359 ScopedSSL good_ssl(NewSSL("good-key"));
360 AddToCache(good_ssl.get());
361 good_ssl.reset(NULL);
362 EXPECT_EQ(kNumEntries, cache_.size());
364 // Call SetSSLSession() |kMaxCheckCount - 1| times, this shall not expire
365 // any session
366 for (size_t n = 0; n < kMaxCheckCount - 1U; ++n) {
367 ScopedSSL ssl(NewSSL("unknown-key"));
368 cache_.SetSSLSession(ssl.get());
369 EXPECT_EQ(kNumEntries, cache_.size());
372 // Call SetSSLSession another time, this shall expire all sessions except
373 // the last one.
374 ScopedSSL bad_ssl(NewSSL("unknown-key"));
375 cache_.SetSSLSession(bad_ssl.get());
376 bad_ssl.reset(NULL);
377 EXPECT_EQ(1U, cache_.size());
380 } // namespace net