2 * Copyright (c) 2015, Facebook, Inc.
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the root directory of this source tree. An additional grant
7 * of patent rights can be found in the PATENTS file in the same directory.
10 #include <folly/wangle/ssl/SSLSessionCacheManager.h>
12 #include <folly/wangle/ssl/SSLCacheProvider.h>
13 #include <folly/wangle/ssl/SSLStats.h>
14 #include <folly/wangle/ssl/SSLUtil.h>
16 #include <folly/io/async/EventBase.h>
19 #include <gflags/gflags.h>
23 using std::shared_ptr;
27 const uint32_t NUM_CACHE_BUCKETS = 16;
29 // We use the default ID generator which fills the maximum ID length
30 // for the protocol. 16 bytes for SSLv2 or 32 for SSLv3+
31 const int MIN_SESSION_ID_LENGTH = 16;
36 DEFINE_bool(dcache_unit_test, false, "All VIPs share one session cache");
38 const bool FLAGS_dcache_unit_test = false;
44 int SSLSessionCacheManager::sExDataIndex_ = -1;
45 shared_ptr<ShardedLocalSSLSessionCache> SSLSessionCacheManager::sCache_;
46 std::mutex SSLSessionCacheManager::sCacheLock_;
48 LocalSSLSessionCache::LocalSSLSessionCache(uint32_t maxCacheSize,
49 uint32_t cacheCullSize)
50 : sessionCache(maxCacheSize, cacheCullSize) {
51 sessionCache.setPruneHook(std::bind(
52 &LocalSSLSessionCache::pruneSessionCallback,
53 this, std::placeholders::_1,
54 std::placeholders::_2));
57 void LocalSSLSessionCache::pruneSessionCallback(const string& sessionId,
58 SSL_SESSION* session) {
59 VLOG(4) << "Free SSL session from local cache; id="
60 << SSLUtil::hexlify(sessionId);
61 SSL_SESSION_free(session);
66 // SSLSessionCacheManager implementation
68 SSLSessionCacheManager::SSLSessionCacheManager(
69 uint32_t maxCacheSize,
70 uint32_t cacheCullSize,
72 const folly::SocketAddress& sockaddr,
73 const string& context,
76 const std::shared_ptr<SSLCacheProvider>& externalCache):
79 externalCache_(externalCache) {
81 SSL_CTX* sslCtx = ctx->getSSLCtx();
83 SSLUtil::getSSLCtxExIndex(&sExDataIndex_);
85 SSL_CTX_set_ex_data(sslCtx, sExDataIndex_, this);
86 SSL_CTX_sess_set_new_cb(sslCtx, SSLSessionCacheManager::newSessionCallback);
87 SSL_CTX_sess_set_get_cb(sslCtx, SSLSessionCacheManager::getSessionCallback);
88 SSL_CTX_sess_set_remove_cb(sslCtx,
89 SSLSessionCacheManager::removeSessionCallback);
90 if (!FLAGS_dcache_unit_test && !context.empty()) {
91 // Use the passed in context
92 SSL_CTX_set_session_id_context(sslCtx, (const uint8_t *)context.data(),
93 std::min((int)context.length(),
94 SSL_MAX_SSL_SESSION_ID_LENGTH));
97 SSL_CTX_set_session_cache_mode(sslCtx, SSL_SESS_CACHE_NO_INTERNAL
98 | SSL_SESS_CACHE_SERVER);
100 localCache_ = SSLSessionCacheManager::getLocalCache(maxCacheSize,
103 VLOG(2) << "On VipID=" << sockaddr.describe() << " context=" << context;
106 SSLSessionCacheManager::~SSLSessionCacheManager() {
109 void SSLSessionCacheManager::shutdown() {
110 std::lock_guard<std::mutex> g(sCacheLock_);
114 shared_ptr<ShardedLocalSSLSessionCache> SSLSessionCacheManager::getLocalCache(
115 uint32_t maxCacheSize,
116 uint32_t cacheCullSize) {
118 std::lock_guard<std::mutex> g(sCacheLock_);
120 sCache_.reset(new ShardedLocalSSLSessionCache(NUM_CACHE_BUCKETS,
127 int SSLSessionCacheManager::newSessionCallback(SSL* ssl, SSL_SESSION* session) {
128 SSLSessionCacheManager* manager = nullptr;
129 SSL_CTX* ctx = SSL_get_SSL_CTX(ssl);
130 manager = (SSLSessionCacheManager *)SSL_CTX_get_ex_data(ctx, sExDataIndex_);
132 if (manager == nullptr) {
133 LOG(FATAL) << "Null SSLSessionCacheManager in callback";
136 return manager->newSession(ssl, session);
140 int SSLSessionCacheManager::newSession(SSL* ssl, SSL_SESSION* session) {
141 string sessionId((char*)session->session_id, session->session_id_length);
142 VLOG(4) << "New SSL session; id=" << SSLUtil::hexlify(sessionId);
145 stats_->recordSSLSession(true /* new session */, false, false);
148 localCache_->storeSession(sessionId, session, stats_);
150 if (externalCache_) {
151 VLOG(4) << "New SSL session: send session to external cache; id=" <<
152 SSLUtil::hexlify(sessionId);
153 storeCacheRecord(sessionId, session);
159 void SSLSessionCacheManager::removeSessionCallback(SSL_CTX* ctx,
160 SSL_SESSION* session) {
161 SSLSessionCacheManager* manager = nullptr;
162 manager = (SSLSessionCacheManager *)SSL_CTX_get_ex_data(ctx, sExDataIndex_);
164 if (manager == nullptr) {
165 LOG(FATAL) << "Null SSLSessionCacheManager in callback";
168 return manager->removeSession(ctx, session);
171 void SSLSessionCacheManager::removeSession(SSL_CTX* ctx,
172 SSL_SESSION* session) {
173 string sessionId((char*)session->session_id, session->session_id_length);
175 // This hook is only called from SSL when the internal session cache needs to
176 // flush sessions. Since we run with the internal cache disabled, this should
178 VLOG(3) << "Remove SSL session; id=" << SSLUtil::hexlify(sessionId);
180 localCache_->removeSession(sessionId);
183 stats_->recordSSLSessionRemove();
187 SSL_SESSION* SSLSessionCacheManager::getSessionCallback(SSL* ssl,
188 unsigned char* sess_id,
191 SSLSessionCacheManager* manager = nullptr;
192 SSL_CTX* ctx = SSL_get_SSL_CTX(ssl);
193 manager = (SSLSessionCacheManager *)SSL_CTX_get_ex_data(ctx, sExDataIndex_);
195 if (manager == nullptr) {
196 LOG(FATAL) << "Null SSLSessionCacheManager in callback";
199 return manager->getSession(ssl, sess_id, id_len, copyflag);
202 SSL_SESSION* SSLSessionCacheManager::getSession(SSL* ssl,
203 unsigned char* session_id,
206 VLOG(7) << "SSL get session callback";
207 SSL_SESSION* session = nullptr;
208 bool foreign = false;
209 char const* missReason = nullptr;
211 if (id_len < MIN_SESSION_ID_LENGTH) {
212 // We didn't generate this session so it's going to be a miss.
213 // This doesn't get logged or counted in the stats.
216 string sessionId((char*)session_id, id_len);
218 AsyncSSLSocket* sslSocket = AsyncSSLSocket::getFromSSL(ssl);
220 assert(sslSocket != nullptr);
222 // look it up in the local cache first
223 session = localCache_->lookupSession(sessionId);
224 #ifdef SSL_SESSION_CB_WOULD_BLOCK
225 if (session == nullptr && externalCache_) {
226 // external cache might have the session
228 if (!SSL_want_sess_cache_lookup(ssl)) {
229 missReason = "reason: No async cache support;";
231 PendingLookupMap::iterator pit = pendingLookups_.find(sessionId);
232 if (pit == pendingLookups_.end()) {
233 auto result = pendingLookups_.emplace(sessionId, PendingLookup());
235 VLOG(4) << "Get SSL session [Pending]: Initiate Fetch; fd=" <<
236 sslSocket->getFd() << " id=" << SSLUtil::hexlify(sessionId);
237 if (lookupCacheRecord(sessionId, sslSocket)) {
238 // response is pending
239 *copyflag = SSL_SESSION_CB_WOULD_BLOCK;
242 missReason = "reason: failed to send lookup request;";
243 pendingLookups_.erase(result.first);
246 // A lookup was already initiated from this thread
247 if (pit->second.request_in_progress) {
248 // Someone else initiated the request, attach
249 VLOG(4) << "Get SSL session [Pending]: Request in progess: attach; "
250 "fd=" << sslSocket->getFd() << " id=" <<
251 SSLUtil::hexlify(sessionId);
252 std::unique_ptr<DelayedDestruction::DestructorGuard> dg(
253 new DelayedDestruction::DestructorGuard(sslSocket));
254 pit->second.waiters.emplace_back(sslSocket, std::move(dg));
255 *copyflag = SSL_SESSION_CB_WOULD_BLOCK;
258 // request is complete
259 session = pit->second.session; // nullptr if our friend didn't have it
260 if (session != nullptr) {
261 CRYPTO_add(&session->references, 1, CRYPTO_LOCK_SSL_SESSION);
268 bool hit = (session != nullptr);
270 stats_->recordSSLSession(false, hit, foreign);
273 sslSocket->setSessionIDResumed(true);
276 VLOG(4) << "Get SSL session [" <<
277 ((hit) ? "Hit" : "Miss") << "]: " <<
278 ((foreign) ? "external" : "local") << " cache; " <<
279 ((missReason != nullptr) ? missReason : "") << "fd=" <<
280 sslSocket->getFd() << " id=" << SSLUtil::hexlify(sessionId);
282 // We already bumped the refcount
288 bool SSLSessionCacheManager::storeCacheRecord(const string& sessionId,
289 SSL_SESSION* session) {
290 std::string sessionString;
291 uint32_t sessionLen = i2d_SSL_SESSION(session, nullptr);
292 sessionString.resize(sessionLen);
293 uint8_t* cp = (uint8_t *)sessionString.data();
294 i2d_SSL_SESSION(session, &cp);
295 size_t expiration = SSL_CTX_get_timeout(ctx_->getSSLCtx());
296 return externalCache_->setAsync(sessionId, sessionString,
297 std::chrono::seconds(expiration));
300 bool SSLSessionCacheManager::lookupCacheRecord(const string& sessionId,
301 AsyncSSLSocket* sslSocket) {
302 auto cacheCtx = new SSLCacheProvider::CacheContext();
303 cacheCtx->sessionId = sessionId;
304 cacheCtx->session = nullptr;
305 cacheCtx->sslSocket = sslSocket;
306 cacheCtx->guard.reset(
307 new DelayedDestruction::DestructorGuard(cacheCtx->sslSocket));
308 cacheCtx->manager = this;
309 bool res = externalCache_->getAsync(sessionId, cacheCtx);
316 void SSLSessionCacheManager::restartSSLAccept(
317 const SSLCacheProvider::CacheContext* cacheCtx) {
318 PendingLookupMap::iterator pit = pendingLookups_.find(cacheCtx->sessionId);
319 CHECK(pit != pendingLookups_.end());
320 pit->second.request_in_progress = false;
321 pit->second.session = cacheCtx->session;
322 VLOG(7) << "Restart SSL accept";
323 cacheCtx->sslSocket->restartSSLAccept();
324 for (const auto& attachedLookup: pit->second.waiters) {
325 // Wake up anyone else who was waiting for this session
326 VLOG(4) << "Restart SSL accept (waiters) for fd=" <<
327 attachedLookup.first->getFd();
328 attachedLookup.first->restartSSLAccept();
330 pendingLookups_.erase(pit);
333 void SSLSessionCacheManager::onGetSuccess(
334 SSLCacheProvider::CacheContext* cacheCtx,
335 const std::string& value) {
336 const uint8_t* cp = (uint8_t*)value.data();
337 cacheCtx->session = d2i_SSL_SESSION(nullptr, &cp, value.length());
338 restartSSLAccept(cacheCtx);
340 /* Insert in the LRU after restarting all clients. The stats logic
341 * in getSession would treat this as a local hit otherwise.
343 localCache_->storeSession(cacheCtx->sessionId, cacheCtx->session, stats_);
347 void SSLSessionCacheManager::onGetFailure(
348 SSLCacheProvider::CacheContext* cacheCtx) {
349 restartSSLAccept(cacheCtx);