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

 

Introduction

Documentation

Support

Miscellaneous