Squid Web Cache master
Loading...
Searching...
No Matches
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"
14#include "CachePeer.h"
15#include "debug/Stream.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
29static Ipc::MemMap *SessionCache = nullptr;
30static const char *SessionCacheName = "tls_session_cache";
31#endif
32
33#if USE_OPENSSL || HAVE_LIBGNUTLS
34static int
35tls_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
79static int
80tls_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
134static 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
215bool
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
226bool
228{
229 return CreateSession(ctx, c, o, Security::Io::BIO_TO_CLIENT, squidCtx);
230}
231
232void
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
245bool
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
258void
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
279void
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
305static 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
319static int
320store_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
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
352static void
353remove_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
371static SSL_SESSION *
372#if SQUID_USE_CONST_SSL_SESSION_CBID
373get_session_cb(SSL *, const unsigned char *sessionID, int len, int *copy)
374#else
375get_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
408void
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
421static 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{
447public:
448 /* RegisteredRunner API */
450 void useConfig() override;
451 ~SharedSessionCacheRr() override;
452
453protected:
454 void create() override;
455
456private:
458};
459
461
462void
464{
465#if USE_OPENSSL
466 if (SessionCache || !isTlsServer()) // no need to configure SSL_SESSION* cache.
467 return;
468
471#endif
472}
473
474void
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
#define MEMMAP_SLOT_KEY_SIZE
Definition MemMap.h:29
#define MEMMAP_SLOT_DATA_SIZE
Definition MemMap.h:30
time_t squid_curtime
AnyP::PortCfgPointer HttpPortList
list of Squid http(s)_port configured
Definition PortCfg.cc:22
#define DefineRunnerRegistrator(ClassName)
class SquidConfig Config
#define assert(EX)
Definition assert.h:17
a MemMap basic element, holding basic shareable memory block info
Definition MemMap.h:34
A map of MemMapSlots indexed by their keys, with read/write slot locking.
Definition MemMap.h:57
void free(const sfileno fileno)
mark the slot as waiting to be freed and, if possible, free it
Definition MemMap.cc:138
void closeForReading(const sfileno fileno)
close slot after reading, decrements read level
Definition MemMap.cc:207
const Slot * openForReading(const cache_key *const key, sfileno &fileno)
open slot for reading, increments read level
Definition MemMap.cc:153
Slot * openForWriting(const cache_key *const key, sfileno &fileno)
Definition MemMap.cc:42
static Owner * Init(const char *const path, const int limit)
initialize shared memory
Definition MemMap.cc:36
void closeForWriting(const sfileno fileno)
successfully finish writing the entry
Definition MemMap.cc:91
MemMapSlot Slot
Definition MemMap.h:59
void useConfig() override
Definition Segment.cc:375
a stream manipulator for printing a system call error (if any)
A combination of PeerOptions and the corresponding Context.
const ContextPointer & raw
TLS context configured using options.
PeerOptions & options
TLS context configuration.
TLS squid.conf settings for a remote server peer.
Definition PeerOptions.h:26
void updateSessionOptions(Security::SessionPointer &)
setup any library-specific options that can be set for the given session
initializes shared memory segments used by MemStore
Definition Session.cc:446
void useConfig() override
Definition Session.cc:463
void create() override
called when the runner should create a new memory segment
Definition Session.cc:475
~SharedSessionCacheRr() override
Definition Session.cc:486
Ipc::MemMap::Owner * owner
Definition Session.cc:457
struct SquidConfig::@97 SSL
size_t sessionCacheSize
static void Link(SSL *ssl, BIO *bio)
Tells ssl connection to use BIO and monitor state via stateChanged()
Definition bio.cc:89
static BIO * Create(const int fd, Security::Io::Type type)
Definition bio.cc:63
#define DBG_IMPORTANT
Definition Stream.h:38
#define debugs(SECTION, LEVEL, CONTENT)
Definition Stream.h:192
#define DBG_CRITICAL
Definition Stream.h:37
void fd_note(int fd, const char *s)
Definition fd.cc:211
#define fd_table
Definition fde.h:189
bool IsConnOpen(const Comm::ConnectionPointer &conn)
Definition Connection.cc:27
void SetSessionCacheCallbacks(Security::ContextPointer &)
Setup the given TLS context with callbacks used to manage the session cache.
Definition Session.cc:409
void PrepForIo()
Definition Io.cc:78
std::shared_ptr< SSL_CTX > ContextPointer
Definition Context.h:29
bool CreateClientSession(FuturePeerContext &, const Comm::ConnectionPointer &, const char *squidCtx)
Definition Session.cc:216
bool CreateServerSession(const Security::ContextPointer &, const Comm::ConnectionPointer &, Security::PeerOptions &, const char *squidCtx)
Definition Session.cc:227
Security::SessionPointer NewSessionObject(const Security::ContextPointer &)
Definition Session.cc:123
void SetSessionResumeData(const Security::SessionPointer &, const Security::SessionStatePointer &)
Definition Session.cc:280
std::shared_ptr< SSL > SessionPointer
Definition Session.h:53
unsigned long LibErrorCode
TLS library-reported non-validation error.
Definition forward.h:141
bool SessionIsResumed(const Security::SessionPointer &)
whether the session is a resumed one
Definition Session.cc:246
void SessionSendGoodbye(const Security::SessionPointer &)
send the shutdown/bye notice for an active TLS session.
Definition Session.cc:233
const char * ErrorString(const LibErrorCode code)
converts numeric LibErrorCode into a human-friendlier string
Definition forward.h:152
std::unique_ptr< SSL_SESSION, HardFun< void, SSL_SESSION *, &SSL_SESSION_free > > SessionStatePointer
Definition Session.h:55
void MaybeGetSessionResumeData(const Security::SessionPointer &, Security::SessionStatePointer &data)
Definition Session.cc:259
void ForgetErrors()
clear any errors that a TLS library has accumulated in its global storage
Definition Io.cc:70
const unsigned char * SSL_SESSION_get_id(const SSL_SESSION *s, unsigned int *len)
Definition openssl.h:147
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 int tls_read_method(int fd, char *buf, int len)
Definition Session.cc:35
#define SSL_SESSION_ID_SIZE
Definition Session.cc:25
static void initializeSessionCache()
Definition Session.cc:422
static void remove_session_cb(SSL_CTX *, SSL_SESSION *sessionID)
Definition Session.cc:353
static SSL_SESSION * get_session_cb(SSL *, unsigned char *sessionID, int len, int *copy)
Definition Session.cc:375
static Ipc::MemMap * SessionCache
Definition Session.cc:29
static int store_session_cb(SSL *, SSL_SESSION *session)
Definition Session.cc:320
static int tls_write_method(int fd, const char *buf, int len)
Definition Session.cc:80
#define SSL_SESSION_MAX_SIZE
Definition Session.cc:26
static bool isTlsServer()
Definition Session.cc:306
static const char * SessionCacheName
Definition Session.cc:30
unsigned char cache_key
Store key.
Definition forward.h:29
bool IamWorkerProcess()
whether the current process handles HTTP transactions and such
Definition stub_tools.cc:47
#define VALGRIND_MAKE_MEM_DEFINED
Definition valgrind.h:27