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