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

 

Introduction

Documentation

Support

Miscellaneous