]> git.ipfire.org Git - thirdparty/squid.git/blame - src/security/Session.cc
Source Format Enforcement (#532)
[thirdparty/squid.git] / src / security / Session.cc
CommitLineData
824d4656 1/*
77b1029d 2 * Copyright (C) 1996-2020 The Squid Software Foundation and contributors
824d4656
AJ
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
5d9a65df
AJ
9/* DEBUG: section 83 TLS session management */
10
824d4656
AJ
11#include "squid.h"
12#include "anyp/PortCfg.h"
13#include "base/RunnersRegistry.h"
fd25ebc0 14#include "CachePeer.h"
5d9a65df 15#include "Debug.h"
087b94cb 16#include "fd.h"
86f77270 17#include "fde.h"
824d4656
AJ
18#include "ipc/MemMap.h"
19#include "security/Session.h"
20#include "SquidConfig.h"
86f77270 21#include "ssl/bio.h"
824d4656
AJ
22
23#define SSL_SESSION_ID_SIZE 32
24#define SSL_SESSION_MAX_SIZE 10*1024
25
301a17d1
AJ
26#if USE_OPENSSL
27static Ipc::MemMap *SessionCache = nullptr;
28static const char *SessionCacheName = "tls_session_cache";
29#endif
30
087b94cb
AJ
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();
c824e69a 36 debugs(83, 3, "started for session=" << (void*)session);
087b94cb
AJ
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
fd25ebc0 50
087b94cb 51 if (i > 0) {
9c8549cf 52 debugs(83, 8, "TLS FD " << fd << " session=" << (void*)session << " " << i << " bytes");
087b94cb
AJ
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
69static int
70tls_write_method(int fd, const char *buf, int len)
71{
72 auto session = fd_table[fd].ssl.get();
f5ee00c0 73 debugs(83, 3, "started for session=" << (void*)session);
087b94cb
AJ
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
9c8549cf
AJ
88 if (i > 0) {
89 debugs(83, 8, "TLS FD " << fd << " session=" << (void*)session << " " << i << " bytes");
90 }
087b94cb
AJ
91 return i;
92}
93#endif
94
c96b5508
AJ
95#if USE_OPENSSL
96Security::SessionPointer
97Security::NewSessionObject(const Security::ContextPointer &ctx)
98{
99 Security::SessionPointer session(SSL_new(ctx.get()), [](SSL *p) {
ed5f5120
SM
100 debugs(83, 5, "SSL_free session=" << (void*)p);
101 SSL_free(p);
102 });
c96b5508
AJ
103 debugs(83, 5, "SSL_new session=" << (void*)session.get());
104 return session;
105}
106#endif
107
86f77270 108static bool
60fcfadf 109CreateSession(const Security::ContextPointer &ctx, const Comm::ConnectionPointer &conn, Security::PeerOptions &opts, Security::Io::Type type, const char *squidCtx)
86f77270
AJ
110{
111 if (!Comm::IsConnOpen(conn)) {
112 debugs(83, DBG_IMPORTANT, "Gone connection");
113 return false;
114 }
115
087b94cb
AJ
116#if USE_OPENSSL || USE_GNUTLS
117
86f77270 118 const char *errAction = "with no TLS/SSL library";
86f77270 119 int errCode = 0;
e0cf4700 120#if USE_OPENSSL
c96b5508 121 Security::SessionPointer session(Security::NewSessionObject(ctx));
087b94cb
AJ
122 if (!session) {
123 errCode = ERR_get_error();
124 errAction = "failed to allocate handle";
60fcfadf 125 debugs(83, DBG_IMPORTANT, "TLS error: " << errAction << ": " << Security::ErrorString(errCode));
087b94cb
AJ
126 }
127#elif USE_GNUTLS
128 gnutls_session_t tmp;
bd649d05 129 errCode = gnutls_init(&tmp, static_cast<unsigned int>(type) | GNUTLS_NONBLOCK);
9c8549cf 130 Security::SessionPointer session(tmp, [](gnutls_session_t p) {
ed5f5120
SM
131 debugs(83, 5, "gnutls_deinit session=" << (void*)p);
132 gnutls_deinit(p);
9c8549cf 133 });
bd649d05 134 debugs(83, 5, "gnutls_init " << (type == Security::Io::BIO_TO_SERVER ? "client" : "server" )<< " session=" << (void*)session.get());
087b94cb
AJ
135 if (errCode != GNUTLS_E_SUCCESS) {
136 session.reset();
137 errAction = "failed to initialize session";
60fcfadf 138 debugs(83, DBG_IMPORTANT, "TLS error: " << errAction << ": " << Security::ErrorString(errCode));
087b94cb
AJ
139 }
140#endif
141
142 if (session) {
86f77270 143 const int fd = conn->fd;
087b94cb
AJ
144
145#if USE_OPENSSL
d0aafe66 146 // without BIO, we would call SSL_set_fd(ssl.get(), fd) instead
86f77270 147 if (BIO *bio = Ssl::Bio::Create(fd, type)) {
087b94cb
AJ
148 Ssl::Bio::Link(session.get(), bio); // cannot fail
149#elif USE_GNUTLS
150 errCode = gnutls_credentials_set(session.get(), GNUTLS_CRD_CERTIFICATE, ctx.get());
151 if (errCode == GNUTLS_E_SUCCESS) {
fd25ebc0 152
60fcfadf 153 opts.updateSessionOptions(session);
fd25ebc0
AJ
154
155 // NP: GnuTLS does not yet support the BIO operations
156 // this does the equivalent of SSL_set_fd() for now.
157 gnutls_transport_set_int(session.get(), fd);
d48f33e3 158 gnutls_handshake_set_timeout(session.get(), GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT);
087b94cb 159#endif
86f77270 160
9c8549cf 161 debugs(83, 5, "link FD " << fd << " to TLS session=" << (void*)session.get());
ed4c6863 162
087b94cb 163 fd_table[fd].ssl = session;
ed4c6863 164 fd_table[fd].useBufferedIo(&tls_read_method, &tls_write_method);
86f77270
AJ
165 fd_note(fd, squidCtx);
166 return true;
167 }
087b94cb
AJ
168
169#if USE_OPENSSL
86f77270
AJ
170 errCode = ERR_get_error();
171 errAction = "failed to initialize I/O";
087b94cb
AJ
172#elif USE_GNUTLS
173 errAction = "failed to assign credentials";
86f77270 174#endif
087b94cb
AJ
175 }
176
e0cf4700
AJ
177 debugs(83, DBG_IMPORTANT, "ERROR: " << squidCtx << ' ' << errAction <<
178 ": " << (errCode != 0 ? Security::ErrorString(errCode) : ""));
087b94cb 179#endif
86f77270
AJ
180 return false;
181}
182
183bool
184Security::CreateClientSession(const Security::ContextPointer &ctx, const Comm::ConnectionPointer &c, const char *squidCtx)
185{
60fcfadf
AJ
186 if (!c || !c->getPeer())
187 return CreateSession(ctx, c, Security::ProxyOutgoingConfig, Security::Io::BIO_TO_SERVER, squidCtx);
188
189 auto *peer = c->getPeer();
190 return CreateSession(ctx, c, peer->secure, Security::Io::BIO_TO_SERVER, squidCtx);
86f77270
AJ
191}
192
193bool
60fcfadf 194Security::CreateServerSession(const Security::ContextPointer &ctx, const Comm::ConnectionPointer &c, Security::PeerOptions &o, const char *squidCtx)
86f77270 195{
60fcfadf 196 return CreateSession(ctx, c, o, Security::Io::BIO_TO_CLIENT, squidCtx);
86f77270
AJ
197}
198
087b94cb 199void
03e0e0e4 200Security::SessionSendGoodbye(const Security::SessionPointer &s)
087b94cb
AJ
201{
202 debugs(83, 5, "session=" << (void*)s.get());
03e0e0e4 203 if (s) {
087b94cb
AJ
204#if USE_OPENSSL
205 SSL_shutdown(s.get());
206#elif USE_GNUTLS
207 gnutls_bye(s.get(), GNUTLS_SHUT_RDWR);
9c8549cf 208#endif
6a0f6036 209 }
087b94cb
AJ
210}
211
5d9a65df
AJ
212bool
213Security::SessionIsResumed(const Security::SessionPointer &s)
214{
215 bool result = false;
216#if USE_OPENSSL
217 result = SSL_session_reused(s.get()) == 1;
218#elif USE_GNUTLS
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
225void
226Security::MaybeGetSessionResumeData(const Security::SessionPointer &s, Security::SessionStatePointer &data)
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 USE_GNUTLS
233 gnutls_datum_t *tmp = nullptr;
234 const auto x = gnutls_session_get_data2(s.get(), tmp);
235 if (x != GNUTLS_E_SUCCESS) {
ea574635 236 debugs(83, 3, "session=" << (void*)s.get() << " error: " << Security::ErrorString(x));
5d9a65df
AJ
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
246void
247Security::SetSessionResumeData(const Security::SessionPointer &s, const Security::SessionStatePointer &data)
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() <<
ea574635 254 " resume error: " << Security::ErrorString(ssl_error));
5d9a65df
AJ
255 }
256#elif USE_GNUTLS
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() <<
ea574635 260 " resume error: " << Security::ErrorString(x));
5d9a65df
AJ
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
824d4656
AJ
272static bool
273isTlsServer()
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
301a17d1
AJ
285#if USE_OPENSSL
286static int
287store_session_cb(SSL *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
301a17d1
AJ
296 unsigned int idlen;
297 const unsigned char *id = SSL_SESSION_get_id(session, &idlen);
301a17d1
AJ
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 }
313 SessionCache->closeForWriting(pos);
314 debugs(83, 5, "wrote an SSL_SESSION entry of size " << lenRequired << " at pos " << pos);
315 }
316 return 0;
317}
318
319static void
320remove_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;
828545d1 327 if (SessionCache->openForReading(reinterpret_cast<const cache_key*>(sessionID), pos)) {
301a17d1
AJ
328 SessionCache->closeForReading(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
338static SSL_SESSION *
339#if SQUID_USE_CONST_SSL_SESSION_CBID
340get_session_cb(SSL *, const unsigned char *sessionID, int len, int *copy)
341#else
342get_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");
361 SessionCache->closeForReading(pos);
362 }
363
364 if (!session)
34492237 365 debugs(83, 5, "Failed to retrieve SSL_SESSION from cache");
301a17d1
AJ
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
375void
376Security::SetSessionCacheCallbacks(Security::ContextPointer &ctx)
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
824d4656
AJ
387void
388initializeSessionCache()
389{
390#if USE_OPENSSL
391 // Check if the MemMap keys and data are enough big to hold
392 // session ids and session data
393 assert(SSL_SESSION_ID_SIZE >= MEMMAP_SLOT_KEY_SIZE);
394 assert(SSL_SESSION_MAX_SIZE >= MEMMAP_SLOT_DATA_SIZE);
395
396 int configuredItems = ::Config.SSL.sessionCacheSize / sizeof(Ipc::MemMap::Slot);
397 if (IamWorkerProcess() && configuredItems)
301a17d1 398 SessionCache = new Ipc::MemMap(SessionCacheName);
824d4656 399 else {
301a17d1 400 SessionCache = nullptr;
824d4656
AJ
401 return;
402 }
403
404 for (AnyP::PortCfgPointer s = HttpPortList; s != nullptr; s = s->next) {
b23f5f9c 405 if (s->secure.staticContext)
301a17d1 406 Security::SetSessionCacheCallbacks(s->secure.staticContext);
824d4656
AJ
407 }
408#endif
409}
410
411/// initializes shared memory segments used by MemStore
412class SharedSessionCacheRr: public Ipc::Mem::RegisteredRunner
413{
414public:
415 /* RegisteredRunner API */
416 SharedSessionCacheRr(): owner(nullptr) {}
417 virtual void useConfig();
418 virtual ~SharedSessionCacheRr();
419
420protected:
421 virtual void create();
422
423private:
424 Ipc::MemMap::Owner *owner;
425};
426
427RunnerRegistrationEntry(SharedSessionCacheRr);
428
429void
430SharedSessionCacheRr::useConfig()
431{
301a17d1
AJ
432#if USE_OPENSSL
433 if (SessionCache || !isTlsServer()) // no need to configure SSL_SESSION* cache.
824d4656
AJ
434 return;
435
436 Ipc::Mem::RegisteredRunner::useConfig();
437 initializeSessionCache();
438#endif
439}
440
441void
442SharedSessionCacheRr::create()
443{
301a17d1 444 if (!isTlsServer()) // no need to configure SSL_SESSION* cache.
824d4656
AJ
445 return;
446
301a17d1 447#if USE_OPENSSL
824d4656 448 if (int items = Config.SSL.sessionCacheSize / sizeof(Ipc::MemMap::Slot))
301a17d1 449 owner = Ipc::MemMap::Init(SessionCacheName, items);
824d4656
AJ
450#endif
451}
452
453SharedSessionCacheRr::~SharedSessionCacheRr()
454{
455 // XXX: Enable after testing to reduce at-exit memory "leaks".
301a17d1 456 // delete SessionCache;
824d4656
AJ
457
458 delete owner;
459}
460