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"
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
27static Ipc::MemMap *SessionCache = nullptr;
28static const char *SessionCacheName = "tls_session_cache";
29#endif
30
31#if USE_OPENSSL || USE_GNUTLS
32static int
33tls_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 USE_GNUTLS
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 USE_GNUTLS
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
62static int
63tls_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 USE_GNUTLS
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
101static bool
103{
104 if (!Comm::IsConnOpen(conn)) {
105 debugs(83, DBG_IMPORTANT, "Gone connection");
106 return false;
107 }
108
109#if USE_OPENSSL || USE_GNUTLS
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 USE_GNUTLS
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 /* USE_GNUTLS */
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 USE_GNUTLS
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 /* USE_GNUTLS */
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 USE_GNUTLS
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 || USE_GNUTLS */
179 return false;
180}
181
182bool
184{
185 if (!c || !c->getPeer())
187
188 auto *peer = c->getPeer();
189 return CreateSession(ctx, c, peer->secure, Security::Io::BIO_TO_SERVER, squidCtx);
190}
191
192bool
194{
195 return CreateSession(ctx, c, o, Security::Io::BIO_TO_CLIENT, squidCtx);
196}
197
198void
200{
201 debugs(83, 5, "session=" << (void*)s.get());
202 if (s) {
203#if USE_OPENSSL
204 SSL_shutdown(s.get());
205#elif USE_GNUTLS
206 gnutls_bye(s.get(), GNUTLS_SHUT_RDWR);
207#endif
208 }
209}
210
211bool
213{
214 bool result = false;
215#if USE_OPENSSL
216 result = SSL_session_reused(s.get()) == 1;
217#elif USE_GNUTLS
218 result = gnutls_session_is_resumed(s.get()) != 0;
219#endif
220 debugs(83, 7, "session=" << (void*)s.get() << ", query? answer: " << (result ? 'T' : 'F') );
221 return result;
222}
223
224void
226{
227 if (!SessionIsResumed(s)) {
228#if USE_OPENSSL
229 // nil is valid for SSL_get1_session(), it cannot fail.
230 data.reset(SSL_get1_session(s.get()));
231#elif USE_GNUTLS
232 gnutls_datum_t *tmp = nullptr;
233 const auto x = gnutls_session_get_data2(s.get(), tmp);
234 if (x != GNUTLS_E_SUCCESS) {
235 debugs(83, 3, "session=" << (void*)s.get() << " error: " << Security::ErrorString(x));
236 }
237 data.reset(tmp);
238#endif
239 debugs(83, 5, "session=" << (void*)s.get() << " data=" << (void*)data.get());
240 } else {
241 debugs(83, 5, "session=" << (void*)s.get() << " data=" << (void*)data.get() << ", do nothing.");
242 }
243}
244
245void
247{
248 if (data) {
249#if USE_OPENSSL
250 if (!SSL_set_session(s.get(), data.get())) {
251 const auto ssl_error = ERR_get_error();
252 debugs(83, 3, "session=" << (void*)s.get() << " data=" << (void*)data.get() <<
253 " resume error: " << Security::ErrorString(ssl_error));
254 }
255#elif USE_GNUTLS
256 const auto x = gnutls_session_set_data(s.get(), data->data, data->size);
257 if (x != GNUTLS_E_SUCCESS) {
258 debugs(83, 3, "session=" << (void*)s.get() << " data=" << (void*)data.get() <<
259 " resume error: " << Security::ErrorString(x));
260 }
261#else
262 // critical because, how did it get here?
263 debugs(83, DBG_CRITICAL, "no TLS library. session=" << (void*)s.get() << " data=" << (void*)data.get());
264#endif
265 debugs(83, 5, "session=" << (void*)s.get() << " data=" << (void*)data.get());
266 } else {
267 debugs(83, 5, "session=" << (void*)s.get() << " no resume data");
268 }
269}
270
271static bool
273{
274 for (AnyP::PortCfgPointer s = HttpPortList; s != nullptr; s = s->next) {
275 if (s->secure.encryptTransport)
276 return true;
277 if (s->flags.tunnelSslBumping)
278 return true;
279 }
280
281 return false;
282}
283
284#if USE_OPENSSL
285static int
286store_session_cb(SSL *, SSL_SESSION *session)
287{
288 if (!SessionCache)
289 return 0;
290
291 debugs(83, 5, "Request to store SSL_SESSION");
292
293 SSL_SESSION_set_timeout(session, Config.SSL.session_ttl);
294
295 unsigned int idlen;
296 const unsigned char *id = SSL_SESSION_get_id(session, &idlen);
297 // XXX: the other calls [to openForReading()] do not copy the sessionId to a char buffer, does this really have to?
298 unsigned char key[MEMMAP_SLOT_KEY_SIZE];
299 // Session ids are of size 32bytes. They should always fit to a
300 // MemMap::Slot::key
302 memset(key, 0, sizeof(key));
303 memcpy(key, id, idlen);
304 int pos;
305 if (auto slotW = SessionCache->openForWriting(static_cast<const cache_key*>(key), pos)) {
306 int lenRequired = i2d_SSL_SESSION(session, nullptr);
307 if (lenRequired < MEMMAP_SLOT_DATA_SIZE) {
308 unsigned char *p = static_cast<unsigned char *>(slotW->p);
309 lenRequired = i2d_SSL_SESSION(session, &p);
310 slotW->set(key, nullptr, lenRequired, squid_curtime + Config.SSL.session_ttl);
311 }
313 debugs(83, 5, "wrote an SSL_SESSION entry of size " << lenRequired << " at pos " << pos);
314 }
315 return 0;
316}
317
318static void
319remove_session_cb(SSL_CTX *, SSL_SESSION *sessionID)
320{
321 if (!SessionCache)
322 return;
323
324 debugs(83, 5, "Request to remove corrupted or not valid SSL_SESSION");
325 int pos;
326 if (SessionCache->openForReading(reinterpret_cast<const cache_key*>(sessionID), pos)) {
328 // TODO:
329 // What if we are not able to remove the session?
330 // Maybe schedule a job to remove it later?
331 // For now we just have an invalid entry in cache until will be expired
332 // The OpenSSL library will reject it when we try to use it
333 SessionCache->free(pos);
334 }
335}
336
337static SSL_SESSION *
338#if SQUID_USE_CONST_SSL_SESSION_CBID
339get_session_cb(SSL *, const unsigned char *sessionID, int len, int *copy)
340#else
341get_session_cb(SSL *, unsigned char *sessionID, int len, int *copy)
342#endif
343{
344 if (!SessionCache)
345 return nullptr;
346
347 const unsigned int *p = reinterpret_cast<const unsigned int *>(sessionID);
348 debugs(83, 5, "Request to search for SSL_SESSION of len: " <<
349 len << p[0] << ":" << p[1]);
350
351 SSL_SESSION *session = nullptr;
352 int pos;
353 if (const auto slot = SessionCache->openForReading(static_cast<const cache_key*>(sessionID), pos)) {
354 if (slot->expire > squid_curtime) {
355 const unsigned char *ptr = slot->p;
356 session = d2i_SSL_SESSION(nullptr, &ptr, slot->pSize);
357 debugs(83, 5, "SSL_SESSION retrieved from cache at pos " << pos);
358 } else
359 debugs(83, 5, "SSL_SESSION in cache expired");
361 }
362
363 if (!session)
364 debugs(83, 5, "Failed to retrieve SSL_SESSION from cache");
365
366 // With the parameter copy the callback can require the SSL engine
367 // to increment the reference count of the SSL_SESSION object, Normally
368 // the reference count is not incremented and therefore the session must
369 // not be explicitly freed with SSL_SESSION_free(3).
370 *copy = 0;
371 return session;
372}
373
374void
376{
377 if (SessionCache) {
378 SSL_CTX_set_session_cache_mode(ctx.get(), SSL_SESS_CACHE_SERVER|SSL_SESS_CACHE_NO_INTERNAL);
379 SSL_CTX_sess_set_new_cb(ctx.get(), store_session_cb);
380 SSL_CTX_sess_set_remove_cb(ctx.get(), remove_session_cb);
381 SSL_CTX_sess_set_get_cb(ctx.get(), get_session_cb);
382 }
383}
384#endif /* USE_OPENSSL */
385
386#if USE_OPENSSL
387static void
389{
390 // Check if the MemMap keys and data are enough big to hold
391 // session ids and session data
394
395 int configuredItems = ::Config.SSL.sessionCacheSize / sizeof(Ipc::MemMap::Slot);
396 if (IamWorkerProcess() && configuredItems)
398 else {
399 SessionCache = nullptr;
400 return;
401 }
402
403 for (AnyP::PortCfgPointer s = HttpPortList; s != nullptr; s = s->next) {
404 if (s->secure.staticContext)
405 Security::SetSessionCacheCallbacks(s->secure.staticContext);
406 }
407}
408#endif
409
412{
413public:
414 /* RegisteredRunner API */
416 void useConfig() override;
417 ~SharedSessionCacheRr() override;
418
419protected:
420 void create() override;
421
422private:
424};
425
427
428void
430{
431#if USE_OPENSSL
432 if (SessionCache || !isTlsServer()) // no need to configure SSL_SESSION* cache.
433 return;
434
437#endif
438}
439
440void
442{
443 if (!isTlsServer()) // no need to configure SSL_SESSION* cache.
444 return;
445
446#if USE_OPENSSL
447 if (int items = Config.SSL.sessionCacheSize / sizeof(Ipc::MemMap::Slot))
449#endif
450}
451
453{
454 // XXX: Enable after testing to reduce at-exit memory "leaks".
455 // delete SessionCache;
456
457 delete owner;
458}
459
#define MEMMAP_SLOT_KEY_SIZE
Definition: MemMap.h:29
#define MEMMAP_SLOT_DATA_SIZE
Definition: MemMap.h:30
time_t squid_curtime
Definition: stub_libtime.cc:20
AnyP::PortCfgPointer HttpPortList
list of Squid http(s)_port configured
Definition: PortCfg.cc:22
class SquidConfig Config
Definition: SquidConfig.cc:12
int conn
the current server connection FD
Definition: Transport.cc:26
#define assert(EX)
Definition: assert.h:17
CachePeer * getPeer() const
Definition: Connection.cc:124
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:377
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
Definition: PeerOptions.cc:774
initializes shared memory segments used by MemStore
Definition: Session.cc:412
void useConfig() override
Definition: Session.cc:429
void create() override
called when the runner should create a new memory segment
Definition: Session.cc:441
~SharedSessionCacheRr() override
Definition: Session.cc:452
Ipc::MemMap::Owner * owner
Definition: Session.cc:423
size_t sessionCacheSize
Definition: SquidConfig.h:490
struct SquidConfig::@114 SSL
static void Link(SSL *ssl, BIO *bio)
Tells ssl connection to use BIO and monitor state via stateChanged()
Definition: bio.cc:88
static BIO * Create(const int fd, Security::Io::Type type)
Definition: bio.cc:62
#define DBG_IMPORTANT
Definition: Stream.h:38
#define debugs(SECTION, LEVEL, CONTENT)
Definition: Stream.h:194
#define DBG_CRITICAL
Definition: Stream.h:37
void fd_note(int fd, const char *s)
Definition: fd.cc:216
#define fd_table
Definition: fde.h:189
bool IsConnOpen(const Comm::ConnectionPointer &conn)
Definition: Connection.cc:27
@ BIO_TO_CLIENT
Definition: forward.h:169
@ BIO_TO_SERVER
Definition: forward.h:170
void SetSessionCacheCallbacks(Security::ContextPointer &)
Setup the given TLS context with callbacks used to manage the session cache.
Definition: Session.cc:375
std::shared_ptr< SSL_CTX > ContextPointer
Definition: Context.h:29
bool CreateServerSession(const Security::ContextPointer &, const Comm::ConnectionPointer &, Security::PeerOptions &, const char *squidCtx)
Definition: Session.cc:193
Security::SessionPointer NewSessionObject(const Security::ContextPointer &)
Definition: Session.cc:90
void SetSessionResumeData(const Security::SessionPointer &, const Security::SessionStatePointer &)
Definition: Session.cc:246
std::shared_ptr< SSL > SessionPointer
Definition: Session.h:49
unsigned long LibErrorCode
TLS library-reported non-validation error.
Definition: forward.h:139
bool SessionIsResumed(const Security::SessionPointer &)
whether the session is a resumed one
Definition: Session.cc:212
void SessionSendGoodbye(const Security::SessionPointer &)
send the shutdown/bye notice for an active TLS session.
Definition: Session.cc:199
bool CreateClientSession(const Security::ContextPointer &, const Comm::ConnectionPointer &, const char *squidCtx)
Definition: Session.cc:183
PeerOptions ProxyOutgoingConfig
configuration options for DIRECT server access
Definition: PeerOptions.cc:24
const char * ErrorString(const LibErrorCode code)
converts numeric LibErrorCode into a human-friendlier string
Definition: forward.h:150
std::unique_ptr< SSL_SESSION, HardFun< void, SSL_SESSION *, &SSL_SESSION_free > > SessionStatePointer
Definition: Session.h:51
void MaybeGetSessionResumeData(const Security::SessionPointer &, Security::SessionStatePointer &data)
Definition: Session.cc:225
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:102
static int tls_read_method(int fd, char *buf, int len)
Definition: Session.cc:33
#define SSL_SESSION_ID_SIZE
Definition: Session.cc:23
static void initializeSessionCache()
Definition: Session.cc:388
static void remove_session_cb(SSL_CTX *, SSL_SESSION *sessionID)
Definition: Session.cc:319
static SSL_SESSION * get_session_cb(SSL *, unsigned char *sessionID, int len, int *copy)
Definition: Session.cc:341
static Ipc::MemMap * SessionCache
Definition: Session.cc:27
static int store_session_cb(SSL *, SSL_SESSION *session)
Definition: Session.cc:286
static int tls_write_method(int fd, const char *buf, int len)
Definition: Session.cc:63
DefineRunnerRegistrator(SharedSessionCacheRr)
#define SSL_SESSION_MAX_SIZE
Definition: Session.cc:24
static bool isTlsServer()
Definition: Session.cc:272
static const char * SessionCacheName
Definition: Session.cc:28
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

 

Introduction

Documentation

Support

Miscellaneous

Web Site Translations

Mirrors