proxygen
SSLSessionCacheManager.cpp
Go to the documentation of this file.
1 /*
2  * Copyright 2017-present Facebook, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
17 
19 #include <wangle/ssl/SSLStats.h>
20 #include <wangle/ssl/SSLUtil.h>
21 
22 #include <folly/fibers/Fiber.h>
26 
27 using folly::SSLContext;
28 using folly::EventBase;
31 using std::string;
32 using std::shared_ptr;
33 
34 namespace {
35 
36 const uint32_t NUM_CACHE_BUCKETS = 16;
37 
38 // We use the default ID generator which fills the maximum ID length
39 // for the protocol. 16 bytes for SSLv2 or 32 for SSLv3+
40 const int MIN_SESSION_ID_LENGTH = 16;
41 
42 }
43 
44 DEFINE_bool(dcache_unit_test, false, "All VIPs share one session cache");
45 
46 namespace wangle {
47 
48 using namespace folly::ssl;
49 
50 
52 shared_ptr<ShardedLocalSSLSessionCache> SSLSessionCacheManager::sCache_;
54 
56  uint32_t cacheCullSize)
57  : sessionCache(maxCacheSize, cacheCullSize) {
60  this, std::placeholders::_1,
61  std::placeholders::_2));
62 }
63 
64 void LocalSSLSessionCache::pruneSessionCallback(const string& sessionId,
65  SSL_SESSION* session) {
66  VLOG(4) << "Free SSL session from local cache; id="
67  << SSLUtil::hexlify(sessionId);
68  SSL_SESSION_free(session);
70 }
71 
72 // ShardedLocalSSLSessionCache Implementation
74  uint32_t n_buckets,
75  uint32_t maxCacheSize,
76  uint32_t cacheCullSize) {
77  CHECK(n_buckets > 0);
78  maxCacheSize = (uint32_t)(((double)maxCacheSize) / n_buckets);
79  cacheCullSize = (uint32_t)(((double)cacheCullSize) / n_buckets);
80  if (maxCacheSize == 0) {
81  maxCacheSize = 1;
82  }
83  if (cacheCullSize == 0) {
84  cacheCullSize = 1;
85  }
86  for (uint32_t i = 0; i < n_buckets; i++) {
87  caches_.push_back(std::unique_ptr<LocalSSLSessionCache>(
88  new LocalSSLSessionCache(maxCacheSize, cacheCullSize)));
89  }
90 }
91 
93  const std::string& sessionId) {
94  size_t bucket = hash(sessionId);
95  SSL_SESSION* session = nullptr;
96  std::lock_guard<std::mutex> g(caches_[bucket]->lock);
97 
98  auto itr = caches_[bucket]->sessionCache.find(sessionId);
99  if (itr != caches_[bucket]->sessionCache.end()) {
100  session = itr->second;
101  }
102 
103  if (session) {
104  SSL_SESSION_up_ref(session);
105  }
106  return session;
107 }
108 
110  const std::string& sessionId,
111  SSL_SESSION* session,
112  SSLStats* stats) {
113  size_t bucket = hash(sessionId);
114  SSL_SESSION* oldSession = nullptr;
115  std::lock_guard<std::mutex> g(caches_[bucket]->lock);
116 
117  auto itr = caches_[bucket]->sessionCache.find(sessionId);
118  if (itr != caches_[bucket]->sessionCache.end()) {
119  oldSession = itr->second;
120  }
121 
122  if (oldSession) {
123  // LRUCacheMap doesn't free on overwrite, so 2x the work for us
124  // This can happen in race conditions
125  SSL_SESSION_free(oldSession);
126  }
127  caches_[bucket]->removedSessions_ = 0;
128  caches_[bucket]->sessionCache.set(sessionId, session, true);
129  if (stats) {
130  stats->recordSSLSessionFree(caches_[bucket]->removedSessions_);
131  }
132 }
133 
135  size_t bucket = hash(sessionId);
136  std::lock_guard<std::mutex> g(caches_[bucket]->lock);
137  caches_[bucket]->sessionCache.erase(sessionId);
138 }
139 
140 // SSLSessionCacheManager implementation
141 
143  uint32_t maxCacheSize,
144  uint32_t cacheCullSize,
145  SSLContext* ctx,
146  const string& context,
147  SSLStats* stats,
148  const std::shared_ptr<SSLCacheProvider>& externalCache):
149  ctx_(ctx),
150  stats_(stats),
151  externalCache_(externalCache) {
152 
153  SSL_CTX* sslCtx = ctx->getSSLCtx();
154 
156 
157  SSL_CTX_set_ex_data(sslCtx, sExDataIndex_, this);
158  SSL_CTX_sess_set_new_cb(sslCtx, SSLSessionCacheManager::newSessionCallback);
159  SSL_CTX_sess_set_get_cb(sslCtx, SSLSessionCacheManager::getSessionCallback);
160  SSL_CTX_sess_set_remove_cb(sslCtx,
162  if (!FLAGS_dcache_unit_test && !context.empty()) {
163  // Use the passed in context
164  ctx->setSessionCacheContext(context);
165  }
166 
167  SSL_CTX_set_session_cache_mode(sslCtx, SSL_SESS_CACHE_NO_INTERNAL
168  | SSL_SESS_CACHE_SERVER);
169 
171  cacheCullSize);
172 }
173 
175 }
176 
178  std::lock_guard<std::mutex> g(sCacheLock_);
179  sCache_.reset();
180 }
181 
182 shared_ptr<ShardedLocalSSLSessionCache> SSLSessionCacheManager::getLocalCache(
183  uint32_t maxCacheSize,
184  uint32_t cacheCullSize) {
185 
186  std::lock_guard<std::mutex> g(sCacheLock_);
187  if (!sCache_) {
188  sCache_.reset(new ShardedLocalSSLSessionCache(NUM_CACHE_BUCKETS,
189  maxCacheSize,
190  cacheCullSize));
191  }
192  return sCache_;
193 }
194 
195 int SSLSessionCacheManager::newSessionCallback(SSL* ssl, SSL_SESSION* session) {
196  SSLSessionCacheManager* manager = nullptr;
197  SSL_CTX* ctx = SSL_get_SSL_CTX(ssl);
198  manager = (SSLSessionCacheManager *)SSL_CTX_get_ex_data(ctx, sExDataIndex_);
199 
200  if (manager == nullptr) {
201  LOG(FATAL) << "Null SSLSessionCacheManager in callback";
202  }
203  return manager->newSession(ssl, session);
204 }
205 
206 
207 int SSLSessionCacheManager::newSession(SSL*, SSL_SESSION* session) {
208  unsigned int sessIdLen = 0;
209  const unsigned char* sessId = SSL_SESSION_get_id(session, &sessIdLen);
210  string sessionId((char*) sessId, sessIdLen);
211  VLOG(4) << "New SSL session; id=" << SSLUtil::hexlify(sessionId);
212 
213  if (stats_) {
214  stats_->recordSSLSession(true /* new session */, false, false);
215  }
216 
217  localCache_->storeSession(sessionId, session, stats_);
218 
219  if (externalCache_) {
220  VLOG(4) << "New SSL session: send session to external cache; id=" <<
221  SSLUtil::hexlify(sessionId);
222  storeCacheRecord(sessionId, session);
223  }
224 
225  return 1;
226 }
227 
229  SSL_SESSION* session) {
230  SSLSessionCacheManager* manager = nullptr;
231  manager = (SSLSessionCacheManager *)SSL_CTX_get_ex_data(ctx, sExDataIndex_);
232 
233  if (manager == nullptr) {
234  LOG(FATAL) << "Null SSLSessionCacheManager in callback";
235  }
236  return manager->removeSession(ctx, session);
237 }
238 
240  SSL_SESSION* session) {
241  unsigned int sessIdLen = 0;
242  const unsigned char* sessId = SSL_SESSION_get_id(session, &sessIdLen);
243  string sessionId((char*) sessId, sessIdLen);
244 
245  // This hook is only called from SSL when the internal session cache needs to
246  // flush sessions. Since we run with the internal cache disabled, this should
247  // never be called
248  VLOG(3) << "Remove SSL session; id=" << SSLUtil::hexlify(sessionId);
249 
250  localCache_->removeSession(sessionId);
251 
252  if (stats_) {
254  }
255 }
256 
258  SSL* ssl,
260  int id_len,
261  int* copyflag) {
262  SSLSessionCacheManager* manager = nullptr;
263  SSL_CTX* ctx = SSL_get_SSL_CTX(ssl);
264  manager = (SSLSessionCacheManager*)SSL_CTX_get_ex_data(ctx, sExDataIndex_);
265 
266  if (manager == nullptr) {
267  LOG(FATAL) << "Null SSLSessionCacheManager in callback";
268  }
269  return manager->getSession(ssl, (unsigned char*)sess_id, id_len, copyflag);
270 }
271 
273  SSL* ssl,
274  unsigned char* session_id,
275  int id_len,
276  int* copyflag) {
277  VLOG(7) << "SSL get session callback";
279  bool foreign = false;
280  std::string missReason;
281 
282  if (id_len < MIN_SESSION_ID_LENGTH) {
283  // We didn't generate this session so it's going to be a miss.
284  // This doesn't get logged or counted in the stats.
285  return nullptr;
286  }
287  string sessionId((char*)session_id, id_len);
288 
289  AsyncSSLSocket* sslSocket = AsyncSSLSocket::getFromSSL(ssl);
290 
291  assert(sslSocket != nullptr);
292 
293  // look it up in the local cache first
294  session.reset(localCache_->lookupSession(sessionId));
295 #ifdef SSL_SESSION_CB_WOULD_BLOCK
296  if (session == nullptr && externalCache_) {
297  // external cache might have the session
298  foreign = true;
299  if (!SSL_want_sess_cache_lookup(ssl)) {
300  missReason = "reason: No async cache support;";
301  } else {
302  PendingLookupMap::iterator pit = pendingLookups_.find(sessionId);
303  if (pit == pendingLookups_.end()) {
304  auto result = pendingLookups_.emplace(sessionId, PendingLookup());
305  // initiate fetch
306  VLOG(4) << "Get SSL session [Pending]: Initiate Fetch; fd=" <<
307  sslSocket->getFd() << " id=" << SSLUtil::hexlify(sessionId);
308  if (lookupCacheRecord(sessionId, sslSocket)) {
309  // response is pending
310  *copyflag = SSL_SESSION_CB_WOULD_BLOCK;
311  return nullptr;
312  } else {
313  missReason = "reason: failed to send lookup request;";
314  pendingLookups_.erase(result.first);
315  }
316  } else {
317  // A lookup was already initiated from this thread
318  if (pit->second.request_in_progress) {
319  // Someone else initiated the request, attach
320  VLOG(4) << "Get SSL session [Pending]: Request in progess: attach; "
321  "fd=" << sslSocket->getFd() << " id=" <<
322  SSLUtil::hexlify(sessionId);
323  std::unique_ptr<DelayedDestruction::DestructorGuard> dg(
324  new DelayedDestruction::DestructorGuard(sslSocket));
325  pit->second.waiters.emplace_back(sslSocket, std::move(dg));
326  *copyflag = SSL_SESSION_CB_WOULD_BLOCK;
327  return nullptr;
328  }
329  // request is complete
330  session.reset(
331  pit->second.session); // nullptr if our friend didn't have it
332  if (session) {
333  SSL_SESSION_up_ref(session.get());
334  }
335  }
336  }
337  }
338 #elif FOLLY_OPENSSL_IS_110
339  if (session == nullptr && externalCache_) {
340  foreign = true;
341  DCHECK(folly::fibers::onFiber());
342 
343  try {
344  session = externalCache_->getFuture(sessionId).get();
345  } catch (const std::exception& e) {
346  missReason = folly::to<std::string>("reason: ", e.what(), ";");
347  }
348 
349  if (session) {
350  localCache_->storeSession(sessionId, session.get(), stats_);
351  SSL_SESSION_up_ref(session.get());
352  }
353  }
354 #endif
355 
356  bool hit = (session != nullptr);
357  if (stats_) {
358  stats_->recordSSLSession(false, hit, foreign);
359  }
360  if (hit) {
361  sslSocket->setSessionIDResumed(true);
362  }
363 
364  VLOG(4) << "Get SSL session [" << ((hit) ? "Hit" : "Miss")
365  << "]: " << ((foreign) ? "external" : "local") << " cache; "
366  << missReason << "fd=" << sslSocket->getFd()
367  << " id=" << SSLUtil::hexlify(sessionId);
368 
369  // We already bumped the refcount
370  *copyflag = 0;
371 
372  return session.release();
373 }
374 
375 bool SSLSessionCacheManager::storeCacheRecord(const string& sessionId,
376  SSL_SESSION* session) {
377  std::string sessionString;
378  uint32_t sessionLen = i2d_SSL_SESSION(session, nullptr);
379  sessionString.resize(sessionLen);
380  uint8_t* cp = (uint8_t *)sessionString.data();
381  i2d_SSL_SESSION(session, &cp);
382  size_t expiration = SSL_CTX_get_timeout(ctx_->getSSLCtx());
383  return externalCache_->setAsync(sessionId, sessionString,
384  std::chrono::seconds(expiration));
385 }
386 
387 bool SSLSessionCacheManager::lookupCacheRecord(const string& sessionId,
388  AsyncSSLSocket* sslSocket) {
389  auto cacheCtx = new SSLCacheProvider::CacheContext();
390  cacheCtx->sessionId = sessionId;
391  cacheCtx->session = nullptr;
392  cacheCtx->sslSocket = sslSocket;
393  cacheCtx->guard.reset(
394  new DelayedDestruction::DestructorGuard(cacheCtx->sslSocket));
395  cacheCtx->manager = this;
396  bool res = externalCache_->getAsync(sessionId, cacheCtx);
397  if (!res) {
398  delete cacheCtx;
399  }
400  return res;
401 }
402 
404  const SSLCacheProvider::CacheContext* cacheCtx) {
405  PendingLookupMap::iterator pit = pendingLookups_.find(cacheCtx->sessionId);
406  CHECK(pit != pendingLookups_.end());
407  pit->second.request_in_progress = false;
408  pit->second.session = cacheCtx->session;
409  VLOG(7) << "Restart SSL accept";
410  cacheCtx->sslSocket->restartSSLAccept();
411  for (const auto& attachedLookup: pit->second.waiters) {
412  // Wake up anyone else who was waiting for this session
413  VLOG(4) << "Restart SSL accept (waiters) for fd=" <<
414  attachedLookup.first->getFd();
415  attachedLookup.first->restartSSLAccept();
416  }
417  pendingLookups_.erase(pit);
418 }
419 
422  const uint8_t* data,
423  size_t length) {
424  cacheCtx->session = d2i_SSL_SESSION(nullptr, &data, length);
425  restartSSLAccept(cacheCtx);
426 
427  /* Insert in the LRU after restarting all clients. The stats logic
428  * in getSession would treat this as a local hit otherwise.
429  */
430  localCache_->storeSession(cacheCtx->sessionId, cacheCtx->session, stats_);
431  delete cacheCtx;
432 }
433 
436  const std::string& value) {
437  restoreSession(cacheCtx, (uint8_t*)value.data(), value.length());
438 }
439 
442  std::unique_ptr<folly::IOBuf> valueBuf) {
443  if (!valueBuf) {
444  return;
445  }
446  valueBuf->coalesce();
447  restoreSession(cacheCtx, valueBuf->data(), valueBuf->length());
448 }
449 
451  SSLCacheProvider::CacheContext* cacheCtx) {
452  restartSSLAccept(cacheCtx);
453  delete cacheCtx;
454 }
455 
456 } // namespace wangle
static SSL_SESSION * getSessionCallback(SSL *ssl, session_callback_arg_session_id_t session_id, int id_len, int *copyflag)
SSLSessionCacheManager(uint32_t maxCacheSize, uint32_t cacheCullSize, folly::SSLContext *ctx, const std::string &context, SSLStats *stats, const std::shared_ptr< SSLCacheProvider > &externalCache)
ByteRange coalesce()
Definition: IOBuf.h:1095
context
Definition: CMakeCache.txt:563
void restartSSLAccept(const SSLCacheProvider::CacheContext *cacheCtx)
constexpr detail::Map< Move > move
Definition: Base-inl.h:2567
static void removeSessionCallback(SSL_CTX *ctx, SSL_SESSION *session)
void onGetFailure(SSLCacheProvider::CacheContext *cacheCtx)
const uint8_t * data() const
Definition: IOBuf.h:499
void pruneSessionCallback(const std::string &sessionId, SSL_SESSION *session)
int newSession(SSL *ssl, SSL_SESSION *session)
static std::shared_ptr< ShardedLocalSSLSessionCache > getLocalCache(uint32_t maxCacheSize, uint32_t cacheCullSize)
void setSessionIDResumed(bool resumed)
bool storeCacheRecord(const std::string &sessionId, SSL_SESSION *session)
ShardedLocalSSLSessionCache(uint32_t n_buckets, uint32_t maxCacheSize, uint32_t cacheCullSize)
static void getSSLCtxExIndex(int *pindex)
Definition: SSLUtil.h:74
static void expiration()
std::shared_ptr< FizzServerContext > ctx_
void removeSession(SSL_CTX *ctx, SSL_SESSION *session)
void onGetSuccess(SSLCacheProvider::CacheContext *cacheCtx, const std::string &value)
virtual void recordSSLSession(bool sessionNew, bool sessionHit, bool foreign) noexcept=0
LocalSSLSessionCache(uint32_t maxCacheSize, uint32_t cacheCullSize)
SSL_CTX * getSSLCtx() const
Definition: SSLContext.h:503
static std::shared_ptr< ShardedLocalSSLSessionCache > sCache_
SSL_SESSION * getSession(SSL *ssl, unsigned char *session_id, int id_len, int *copyflag)
std::size_t length() const
Definition: IOBuf.h:533
virtual int getFd() const
Definition: AsyncSocket.h:335
SSL_SESSION * lookupSession(const std::string &sessionId)
void storeSession(const std::string &sessionId, SSL_SESSION *session, SSLStats *stats)
virtual void recordSSLSessionRemove() noexcept=0
static const char *const value
Definition: Conv.cpp:50
void removeSession(const std::string &sessionId)
DEFINE_bool(dcache_unit_test, false,"All VIPs share one session cache")
static int newSessionCallback(SSL *ssl, SSL_SESSION *session)
std::shared_ptr< ShardedLocalSSLSessionCache > localCache_
void setSessionCacheContext(const std::string &context)
Definition: SSLContext.cpp:526
std::mutex mutex
const char * string
Definition: Conv.cpp:212
int bind(NetworkSocket s, const sockaddr *name, socklen_t namelen)
Definition: NetOps.cpp:76
g_t g(f_t)
static std::string hexlify(const std::string &binary)
Definition: SSLUtil.h:150
std::unique_ptr< SSL_SESSION, SSLSessionDeleter > SSLSessionUniquePtr
void restoreSession(SSLCacheProvider::CacheContext *cacheCtx, const uint8_t *data, size_t length)
std::shared_ptr< SSLCacheProvider > externalCache_
virtual void recordSSLSessionFree(uint32_t freed) noexcept=0
bool lookupCacheRecord(const std::string &sessionId, folly::AsyncSSLSocket *sslSock)
static constexpr uint64_t data[1]
Definition: Fingerprint.cpp:43
static AsyncSSLSocket * getFromSSL(const SSL *ssl)
void setPruneHook(PruneHookCall pruneHook)