Session.cc
Go to the documentation of this file.
1 /*
2  * Copyright (C) 1996-2017 The Squid Software Foundation and contributors
3  *
4  * Squid software is distributed under GPLv2+ license and includes
5  * contributions from numerous individuals and organizations.
6  * Please see the COPYING and CONTRIBUTORS files for details.
7  */
8 
9 /* DEBUG: section 83 TLS session management */
10 
11 #include "squid.h"
12 #include "anyp/PortCfg.h"
13 #include "base/RunnersRegistry.h"
14 #include "CachePeer.h"
15 #include "Debug.h"
16 #include "fd.h"
17 #include "fde.h"
18 #include "ipc/MemMap.h"
19 #include "security/Session.h"
20 #include "SquidConfig.h"
21 #include "ssl/bio.h"
22 
23 #define SSL_SESSION_ID_SIZE 32
24 #define SSL_SESSION_MAX_SIZE 10*1024
25 
26 #if USE_OPENSSL
27 static Ipc::MemMap *SessionCache = nullptr;
28 static const char *SessionCacheName = "tls_session_cache";
29 #endif
30 
31 #if USE_OPENSSL || USE_GNUTLS
32 static int
33 tls_read_method(int fd, char *buf, int len)
34 {
35  auto session = fd_table[fd].ssl.get();
36  debugs(83, 3, "started for session=" << (void*)session);
37 
38 #if DONT_DO_THIS && USE_OPENSSL
39  if (!SSL_is_init_finished(session)) {
40  errno = ENOTCONN;
41  return -1;
42  }
43 #endif
44 
45 #if USE_OPENSSL
46  int i = SSL_read(session, buf, len);
47 #elif USE_GNUTLS
48  int i = gnutls_record_recv(session, buf, len);
49 #endif
50 
51  if (i > 0) {
52  debugs(83, 8, "TLS FD " << fd << " session=" << (void*)session << " " << i << " bytes");
53  (void)VALGRIND_MAKE_MEM_DEFINED(buf, i);
54  }
55 
56 #if USE_OPENSSL
57  if (i > 0 && SSL_pending(session) > 0) {
58 #elif USE_GNUTLS
59  if (i > 0 && gnutls_record_check_pending(session) > 0) {
60 #endif
61  debugs(83, 2, "TLS FD " << fd << " is pending");
62  fd_table[fd].flags.read_pending = true;
63  } else
64  fd_table[fd].flags.read_pending = false;
65 
66  return i;
67 }
68 
69 static int
70 tls_write_method(int fd, const char *buf, int len)
71 {
72  auto session = fd_table[fd].ssl.get();
73  debugs(83, 3, "started for session=" << (void*)session);
74 
75 #if USE_OPENSSL
76  if (!SSL_is_init_finished(session)) {
77  errno = ENOTCONN;
78  return -1;
79  }
80 #endif
81 
82 #if USE_OPENSSL
83  int i = SSL_write(session, buf, len);
84 #elif USE_GNUTLS
85  int i = gnutls_record_send(session, buf, len);
86 #endif
87 
88  if (i > 0) {
89  debugs(83, 8, "TLS FD " << fd << " session=" << (void*)session << " " << i << " bytes");
90  }
91  return i;
92 }
93 #endif
94 
95 #if USE_OPENSSL
98 {
99  Security::SessionPointer session(SSL_new(ctx.get()), [](SSL *p) {
100  debugs(83, 5, "SSL_free session=" << (void*)p);
101  SSL_free(p);
102  });
103  debugs(83, 5, "SSL_new session=" << (void*)session.get());
104  return session;
105 }
106 #endif
107 
108 static bool
109 CreateSession(const Security::ContextPointer &ctx, const Comm::ConnectionPointer &conn, Security::Io::Type type, const char *squidCtx)
110 {
111  if (!Comm::IsConnOpen(conn)) {
112  debugs(83, DBG_IMPORTANT, "Gone connection");
113  return false;
114  }
115 
116 #if USE_OPENSSL || USE_GNUTLS
117 
118  const char *errAction = "with no TLS/SSL library";
119  int errCode = 0;
120 #if USE_OPENSSL
122  if (!session) {
123  errCode = ERR_get_error();
124  errAction = "failed to allocate handle";
125  }
126 #elif USE_GNUTLS
127  gnutls_session_t tmp;
128  errCode = gnutls_init(&tmp, static_cast<unsigned int>(type) | GNUTLS_NONBLOCK);
129  Security::SessionPointer session(tmp, [](gnutls_session_t p) {
130  debugs(83, 5, "gnutls_deinit session=" << (void*)p);
131  gnutls_deinit(p);
132  });
133  debugs(83, 5, "gnutls_init " << (type == Security::Io::BIO_TO_SERVER ? "client" : "server" )<< " session=" << (void*)session.get());
134  if (errCode != GNUTLS_E_SUCCESS) {
135  session.reset();
136  errAction = "failed to initialize session";
137  }
138 #endif
139 
140  if (session) {
141  const int fd = conn->fd;
142 
143 #if USE_OPENSSL
144  // without BIO, we would call SSL_set_fd(ssl.get(), fd) instead
145  if (BIO *bio = Ssl::Bio::Create(fd, type)) {
146  Ssl::Bio::Link(session.get(), bio); // cannot fail
147 #elif USE_GNUTLS
148  errCode = gnutls_credentials_set(session.get(), GNUTLS_CRD_CERTIFICATE, ctx.get());
149  if (errCode == GNUTLS_E_SUCCESS) {
150 
151  if (auto *peer = conn->getPeer())
152  peer->secure.updateSessionOptions(session);
153  else
155 
156  // NP: GnuTLS does not yet support the BIO operations
157  // this does the equivalent of SSL_set_fd() for now.
158  gnutls_transport_set_int(session.get(), fd);
159  gnutls_handshake_set_timeout(session.get(), GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT);
160 #endif
161 
162  debugs(83, 5, "link FD " << fd << " to TLS session=" << (void*)session.get());
163  fd_table[fd].ssl = session;
164  fd_table[fd].read_method = &tls_read_method;
165  fd_table[fd].write_method = &tls_write_method;
166  fd_note(fd, squidCtx);
167  return true;
168  }
169 
170 #if USE_OPENSSL
171  errCode = ERR_get_error();
172  errAction = "failed to initialize I/O";
173 #elif USE_GNUTLS
174  errAction = "failed to assign credentials";
175 #endif
176  }
177 
178  debugs(83, DBG_IMPORTANT, "ERROR: " << squidCtx << ' ' << errAction <<
179  ": " << (errCode != 0 ? Security::ErrorString(errCode) : ""));
180 #endif
181  return false;
182 }
183 
184 bool
186 {
187  return CreateSession(ctx, c, Security::Io::BIO_TO_SERVER, squidCtx);
188 }
189 
190 bool
192 {
193  return CreateSession(ctx, c, Security::Io::BIO_TO_CLIENT, squidCtx);
194 }
195 
196 void
198 {
199  debugs(83, 5, "session=" << (void*)s.get());
200  if (s) {
201 #if USE_OPENSSL
202  SSL_shutdown(s.get());
203 #elif USE_GNUTLS
204  gnutls_bye(s.get(), GNUTLS_SHUT_RDWR);
205 #endif
206  }
207 }
208 
209 bool
211 {
212  bool result = false;
213 #if USE_OPENSSL
214  result = SSL_session_reused(s.get()) == 1;
215 #elif USE_GNUTLS
216  result = gnutls_session_is_resumed(s.get()) != 0;
217 #endif
218  debugs(83, 7, "session=" << (void*)s.get() << ", query? answer: " << (result ? 'T' : 'F') );
219  return result;
220 }
221 
222 void
224 {
225  if (!SessionIsResumed(s)) {
226 #if USE_OPENSSL
227  // nil is valid for SSL_get1_session(), it cannot fail.
228  data.reset(SSL_get1_session(s.get()));
229 #elif USE_GNUTLS
230  gnutls_datum_t *tmp = nullptr;
231  const auto x = gnutls_session_get_data2(s.get(), tmp);
232  if (x != GNUTLS_E_SUCCESS) {
233  debugs(83, 3, "session=" << (void*)s.get() << " error: " << Security::ErrorString(x));
234  }
235  data.reset(tmp);
236 #endif
237  debugs(83, 5, "session=" << (void*)s.get() << " data=" << (void*)data.get());
238  } else {
239  debugs(83, 5, "session=" << (void*)s.get() << " data=" << (void*)data.get() << ", do nothing.");
240  }
241 }
242 
243 void
245 {
246  if (data) {
247 #if USE_OPENSSL
248  if (!SSL_set_session(s.get(), data.get())) {
249  const auto ssl_error = ERR_get_error();
250  debugs(83, 3, "session=" << (void*)s.get() << " data=" << (void*)data.get() <<
251  " resume error: " << Security::ErrorString(ssl_error));
252  }
253 #elif USE_GNUTLS
254  const auto x = gnutls_session_set_data(s.get(), data->data, data->size);
255  if (x != GNUTLS_E_SUCCESS) {
256  debugs(83, 3, "session=" << (void*)s.get() << " data=" << (void*)data.get() <<
257  " resume error: " << Security::ErrorString(x));
258  }
259 #else
260  // critical because, how did it get here?
261  debugs(83, DBG_CRITICAL, "no TLS library. session=" << (void*)s.get() << " data=" << (void*)data.get());
262 #endif
263  debugs(83, 5, "session=" << (void*)s.get() << " data=" << (void*)data.get());
264  } else {
265  debugs(83, 5, "session=" << (void*)s.get() << " no resume data");
266  }
267 }
268 
269 static bool
271 {
272  for (AnyP::PortCfgPointer s = HttpPortList; s != nullptr; s = s->next) {
273  if (s->secure.encryptTransport)
274  return true;
275  if (s->flags.tunnelSslBumping)
276  return true;
277  }
278 
279  return false;
280 }
281 
282 #if USE_OPENSSL
283 static int
284 store_session_cb(SSL *ssl, SSL_SESSION *session)
285 {
286  if (!SessionCache)
287  return 0;
288 
289  debugs(83, 5, "Request to store SSL_SESSION");
290 
291  SSL_SESSION_set_timeout(session, Config.SSL.session_ttl);
292 
293 #if HAVE_LIBSSL_SSL_SESSION_GET_ID
294  unsigned int idlen;
295  const unsigned char *id = SSL_SESSION_get_id(session, &idlen);
296 #else
297  unsigned char *id = session->session_id;
298  unsigned int idlen = session->session_id_length;
299 #endif
300  // XXX: the other calls [to openForReading()] do not copy the sessionId to a char buffer, does this really have to?
301  unsigned char key[MEMMAP_SLOT_KEY_SIZE];
302  // Session ids are of size 32bytes. They should always fit to a
303  // MemMap::Slot::key
304  assert(idlen <= MEMMAP_SLOT_KEY_SIZE);
305  memset(key, 0, sizeof(key));
306  memcpy(key, id, idlen);
307  int pos;
308  if (auto slotW = SessionCache->openForWriting(static_cast<const cache_key*>(key), pos)) {
309  int lenRequired = i2d_SSL_SESSION(session, nullptr);
310  if (lenRequired < MEMMAP_SLOT_DATA_SIZE) {
311  unsigned char *p = static_cast<unsigned char *>(slotW->p);
312  lenRequired = i2d_SSL_SESSION(session, &p);
313  slotW->set(key, nullptr, lenRequired, squid_curtime + Config.SSL.session_ttl);
314  }
315  SessionCache->closeForWriting(pos);
316  debugs(83, 5, "wrote an SSL_SESSION entry of size " << lenRequired << " at pos " << pos);
317  }
318  return 0;
319 }
320 
321 static void
322 remove_session_cb(SSL_CTX *, SSL_SESSION *sessionID)
323 {
324  if (!SessionCache)
325  return;
326 
327  debugs(83, 5, "Request to remove corrupted or not valid SSL_SESSION");
328  int pos;
329  if (SessionCache->openForReading(reinterpret_cast<const cache_key*>(sessionID), pos)) {
330  SessionCache->closeForReading(pos);
331  // TODO:
332  // What if we are not able to remove the session?
333  // Maybe schedule a job to remove it later?
334  // For now we just have an invalid entry in cache until will be expired
335  // The OpenSSL library will reject it when we try to use it
336  SessionCache->free(pos);
337  }
338 }
339 
340 static SSL_SESSION *
341 #if SQUID_USE_CONST_SSL_SESSION_CBID
342 get_session_cb(SSL *, const unsigned char *sessionID, int len, int *copy)
343 #else
344 get_session_cb(SSL *, unsigned char *sessionID, int len, int *copy)
345 #endif
346 {
347  if (!SessionCache)
348  return nullptr;
349 
350  const unsigned int *p = reinterpret_cast<const unsigned int *>(sessionID);
351  debugs(83, 5, "Request to search for SSL_SESSION of len: " <<
352  len << p[0] << ":" << p[1]);
353 
354  SSL_SESSION *session = nullptr;
355  int pos;
356  if (const auto slot = SessionCache->openForReading(static_cast<const cache_key*>(sessionID), pos)) {
357  if (slot->expire > squid_curtime) {
358  const unsigned char *ptr = slot->p;
359  session = d2i_SSL_SESSION(nullptr, &ptr, slot->pSize);
360  debugs(83, 5, "SSL_SESSION retrieved from cache at pos " << pos);
361  } else
362  debugs(83, 5, "SSL_SESSION in cache expired");
363  SessionCache->closeForReading(pos);
364  }
365 
366  if (!session)
367  debugs(83, 5, "Failed to retrieve SSL_SESSION from cache");
368 
369  // With the parameter copy the callback can require the SSL engine
370  // to increment the reference count of the SSL_SESSION object, Normally
371  // the reference count is not incremented and therefore the session must
372  // not be explicitly freed with SSL_SESSION_free(3).
373  *copy = 0;
374  return session;
375 }
376 
377 void
379 {
380  if (SessionCache) {
381  SSL_CTX_set_session_cache_mode(ctx.get(), SSL_SESS_CACHE_SERVER|SSL_SESS_CACHE_NO_INTERNAL);
382  SSL_CTX_sess_set_new_cb(ctx.get(), store_session_cb);
383  SSL_CTX_sess_set_remove_cb(ctx.get(), remove_session_cb);
384  SSL_CTX_sess_set_get_cb(ctx.get(), get_session_cb);
385  }
386 }
387 #endif /* USE_OPENSSL */
388 
389 void
391 {
392 #if USE_OPENSSL
393  // Check if the MemMap keys and data are enough big to hold
394  // session ids and session data
397 
398  int configuredItems = ::Config.SSL.sessionCacheSize / sizeof(Ipc::MemMap::Slot);
399  if (IamWorkerProcess() && configuredItems)
400  SessionCache = new Ipc::MemMap(SessionCacheName);
401  else {
402  SessionCache = nullptr;
403  return;
404  }
405 
406  for (AnyP::PortCfgPointer s = HttpPortList; s != nullptr; s = s->next) {
407  if (s->secure.staticContext)
408  Security::SetSessionCacheCallbacks(s->secure.staticContext);
409  }
410 #endif
411 }
412 
415 {
416 public:
417  /* RegisteredRunner API */
419  virtual void useConfig();
420  virtual ~SharedSessionCacheRr();
421 
422 protected:
423  virtual void create();
424 
425 private:
427 };
428 
430 
431 void
433 {
434 #if USE_OPENSSL
435  if (SessionCache || !isTlsServer()) // no need to configure SSL_SESSION* cache.
436  return;
437 
440 #endif
441 }
442 
443 void
445 {
446  if (!isTlsServer()) // no need to configure SSL_SESSION* cache.
447  return;
448 
449 #if USE_OPENSSL
450  if (int items = Config.SSL.sessionCacheSize / sizeof(Ipc::MemMap::Slot))
451  owner = Ipc::MemMap::Init(SessionCacheName, items);
452 #endif
453 }
454 
456 {
457  // XXX: Enable after testing to reduce at-exit memory "leaks".
458  // delete SessionCache;
459 
460  delete owner;
461 }
462 
static BIO * Create(const int fd, Security::Io::Type type)
Definition: bio.cc:66
#define fd_table
Definition: fde.h:157
#define assert(EX)
Definition: assert.h:17
static void remove_session_cb(SSL_CTX *, SSL_SESSION *sessionID)
Definition: Session.cc:322
MemMapSlot Slot
Definition: MemMap.h:59
#define VALGRIND_MAKE_MEM_DEFINED
Definition: valgrind.h:27
void fd_note(int fd, const char *s)
Definition: fd.cc:251
static void Link(SSL *ssl, BIO *bio)
Tells ssl connection to use BIO and monitor state via stateChanged()
Definition: bio.cc:92
int i
Definition: membanger.c:49
static SSL_SESSION * get_session_cb(SSL *, unsigned char *sessionID, int len, int *copy)
Definition: Session.cc:344
static int store_session_cb(SSL *ssl, SSL_SESSION *session)
Definition: Session.cc:284
std::unique_ptr< SSL_SESSION, HardFun< void, SSL_SESSION *,&SSL_SESSION_free > > SessionStatePointer
Definition: Session.h:43
void SessionSendGoodbye(const Security::SessionPointer &)
send the shutdown/bye notice for an active TLS session.
Definition: Session.cc:197
bool SessionIsResumed(const Security::SessionPointer &)
whether the session is a resumed one
Definition: Session.cc:210
#define DBG_CRITICAL
Definition: Debug.h:44
AnyP::PortCfgPointer HttpPortList
list of Squid http(s)_port configured
Definition: PortCfg.cc:21
static int tls_read_method(int fd, char *buf, int len)
Definition: Session.cc:33
static const char * SessionCacheName
Definition: Session.cc:28
bool IamWorkerProcess()
whether the current process handles HTTP transactions and such
Definition: stub_tools.cc:49
time_t squid_curtime
Definition: stub_time.cc:17
static Owner * Init(const char *const path, const int limit)
initialize shared memory
Definition: MemMap.cc:36
const Slot * openForReading(const cache_key *const key, sfileno &fileno)
open slot for reading, increments read level
Definition: MemMap.cc:145
virtual void create()
called when the runner should create a new memory segment
Definition: Session.cc:444
initializes shared memory segments used by MemStore
Definition: Session.cc:414
bool IsConnOpen(const Comm::ConnectionPointer &conn)
Definition: Connection.cc:24
bool CreateServerSession(const Security::ContextPointer &, const Comm::ConnectionPointer &, const char *squidCtx)
Definition: Session.cc:191
#define SSL_SESSION_MAX_SIZE
Definition: Session.cc:24
void const char HLPCB void * data
Definition: stub_helper.cc:16
struct SquidConfig::@121 SSL
#define debugs(SECTION, LEVEL, CONTENT)
Definition: Debug.h:123
#define DBG_IMPORTANT
Definition: Debug.h:45
#define MEMMAP_SLOT_KEY_SIZE
Definition: MemMap.h:29
bool CreateClientSession(const Security::ContextPointer &, const Comm::ConnectionPointer &, const char *squidCtx)
Definition: Session.cc:185
void updateSessionOptions(Security::SessionPointer &)
setup any library-specific options that can be set for the given session
Definition: PeerOptions.cc:710
PeerOptions ProxyOutgoingConfig
configuration options for DIRECT server access
Definition: PeerOptions.cc:22
static int tls_write_method(int fd, const char *buf, int len)
Definition: Session.cc:70
virtual void useConfig()
Definition: Session.cc:432
std::shared_ptr< SSL_CTX > ContextPointer
Definition: Context.h:28
#define MEMMAP_SLOT_DATA_SIZE
Definition: MemMap.h:30
a MemMap basic element, holding basic shareable memory block info
Definition: MemMap.h:33
Slot * openForWriting(const cache_key *const key, sfileno &fileno)
Definition: MemMap.cc:42
CachePeer * getPeer() const
Definition: Connection.cc:97
void free(const sfileno fileno)
mark the slot as waiting to be freed and, if possible, free it
Definition: MemMap.cc:130
#define SSL_SESSION_ID_SIZE
Definition: Session.cc:23
RunnerRegistrationEntry(SharedSessionCacheRr)
A map of MemMapSlots indexed by their keys, with read/write slot locking.
Definition: MemMap.h:56
static bool isTlsServer()
Definition: Session.cc:270
virtual void useConfig()
Definition: Segment.cc:376
void SetSessionResumeData(const Security::SessionPointer &, const Security::SessionStatePointer &)
Definition: Session.cc:244
void initializeSessionCache()
Definition: Session.cc:390
static bool CreateSession(const Security::ContextPointer &ctx, const Comm::ConnectionPointer &conn, Security::Io::Type type, const char *squidCtx)
Definition: Session.cc:109
Ipc::MemMap::Owner * owner
Definition: Session.cc:426
void const cache_key * key
void MaybeGetSessionResumeData(const Security::SessionPointer &, Security::SessionStatePointer &data)
Definition: Session.cc:223
Security::SessionPointer NewSessionObject(const Security::ContextPointer &)
Definition: Session.cc:97
size_t sessionCacheSize
Definition: SquidConfig.h:493
void closeForWriting(const sfileno fileno, bool lockForReading=false)
successfully finish writing the entry
Definition: MemMap.cc:91
const char * ErrorString(const ErrorCode code)
Definition: forward.h:121
void closeForReading(const sfileno fileno)
close slot after reading, decrements read level
Definition: MemMap.cc:199
class SquidConfig Config
Definition: SquidConfig.cc:12
void SetSessionCacheCallbacks(Security::ContextPointer &)
Setup the given TLS context with callbacks used to manage the session cache.
Definition: Session.cc:378
virtual ~SharedSessionCacheRr()
Definition: Session.cc:455
std::shared_ptr< SSL > SessionPointer
Definition: Session.h:41

 

Introduction

Documentation

Support

Miscellaneous

Web Site Translations

Mirrors