]> git.ipfire.org Git - thirdparty/squid.git/blame - src/security/Session.cc
TLS: Move the remaining SSL_SESSION cache callbacks to security/Session.*.
[thirdparty/squid.git] / src / security / Session.cc
CommitLineData
824d4656 1/*
4ac4a490 2 * Copyright (C) 1996-2017 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
AJ
108static bool
109CreateSession(const Security::ContextPointer &ctx, const Comm::ConnectionPointer &conn, Security::Io::Type type, const char *squidCtx)
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";
125 }
126#elif USE_GNUTLS
127 gnutls_session_t tmp;
bd649d05 128 errCode = gnutls_init(&tmp, static_cast<unsigned int>(type) | GNUTLS_NONBLOCK);
9c8549cf 129 Security::SessionPointer session(tmp, [](gnutls_session_t p) {
ed5f5120
SM
130 debugs(83, 5, "gnutls_deinit session=" << (void*)p);
131 gnutls_deinit(p);
9c8549cf 132 });
bd649d05 133 debugs(83, 5, "gnutls_init " << (type == Security::Io::BIO_TO_SERVER ? "client" : "server" )<< " session=" << (void*)session.get());
087b94cb
AJ
134 if (errCode != GNUTLS_E_SUCCESS) {
135 session.reset();
136 errAction = "failed to initialize session";
137 }
138#endif
139
140 if (session) {
86f77270 141 const int fd = conn->fd;
087b94cb
AJ
142
143#if USE_OPENSSL
d0aafe66 144 // without BIO, we would call SSL_set_fd(ssl.get(), fd) instead
86f77270 145 if (BIO *bio = Ssl::Bio::Create(fd, type)) {
087b94cb
AJ
146 Ssl::Bio::Link(session.get(), bio); // cannot fail
147#elif USE_GNUTLS
148 errCode = gnutls_credentials_set(session.get(), GNUTLS_CRD_CERTIFICATE, ctx.get());
149 if (errCode == GNUTLS_E_SUCCESS) {
fd25ebc0
AJ
150
151 if (auto *peer = conn->getPeer())
152 peer->secure.updateSessionOptions(session);
153 else
154 Security::ProxyOutgoingConfig.updateSessionOptions(session);
155
156 // NP: GnuTLS does not yet support the BIO operations
157 // this does the equivalent of SSL_set_fd() for now.
158 gnutls_transport_set_int(session.get(), fd);
d48f33e3 159 gnutls_handshake_set_timeout(session.get(), GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT);
087b94cb 160#endif
86f77270 161
9c8549cf 162 debugs(83, 5, "link FD " << fd << " to TLS session=" << (void*)session.get());
087b94cb
AJ
163 fd_table[fd].ssl = session;
164 fd_table[fd].read_method = &tls_read_method;
165 fd_table[fd].write_method = &tls_write_method;
86f77270
AJ
166 fd_note(fd, squidCtx);
167 return true;
168 }
087b94cb
AJ
169
170#if USE_OPENSSL
86f77270
AJ
171 errCode = ERR_get_error();
172 errAction = "failed to initialize I/O";
087b94cb
AJ
173#elif USE_GNUTLS
174 errAction = "failed to assign credentials";
86f77270 175#endif
087b94cb
AJ
176 }
177
e0cf4700
AJ
178 debugs(83, DBG_IMPORTANT, "ERROR: " << squidCtx << ' ' << errAction <<
179 ": " << (errCode != 0 ? Security::ErrorString(errCode) : ""));
087b94cb 180#endif
86f77270
AJ
181 return false;
182}
183
184bool
185Security::CreateClientSession(const Security::ContextPointer &ctx, const Comm::ConnectionPointer &c, const char *squidCtx)
186{
187 return CreateSession(ctx, c, Security::Io::BIO_TO_SERVER, squidCtx);
188}
189
190bool
191Security::CreateServerSession(const Security::ContextPointer &ctx, const Comm::ConnectionPointer &c, const char *squidCtx)
192{
193 return CreateSession(ctx, c, Security::Io::BIO_TO_CLIENT, squidCtx);
194}
195
087b94cb 196void
03e0e0e4 197Security::SessionSendGoodbye(const Security::SessionPointer &s)
087b94cb
AJ
198{
199 debugs(83, 5, "session=" << (void*)s.get());
03e0e0e4 200 if (s) {
087b94cb
AJ
201#if USE_OPENSSL
202 SSL_shutdown(s.get());
203#elif USE_GNUTLS
204 gnutls_bye(s.get(), GNUTLS_SHUT_RDWR);
9c8549cf 205#endif
6a0f6036 206 }
087b94cb
AJ
207}
208
5d9a65df
AJ
209bool
210Security::SessionIsResumed(const Security::SessionPointer &s)
211{
212 bool result = false;
213#if USE_OPENSSL
214 result = SSL_session_reused(s.get()) == 1;
215#elif USE_GNUTLS
216 result = gnutls_session_is_resumed(s.get()) != 0;
217#endif
218 debugs(83, 7, "session=" << (void*)s.get() << ", query? answer: " << (result ? 'T' : 'F') );
219 return result;
220}
221
222void
223Security::MaybeGetSessionResumeData(const Security::SessionPointer &s, Security::SessionStatePointer &data)
224{
225 if (!SessionIsResumed(s)) {
226#if USE_OPENSSL
227 // nil is valid for SSL_get1_session(), it cannot fail.
228 data.reset(SSL_get1_session(s.get()));
229#elif USE_GNUTLS
230 gnutls_datum_t *tmp = nullptr;
231 const auto x = gnutls_session_get_data2(s.get(), tmp);
232 if (x != GNUTLS_E_SUCCESS) {
ea574635 233 debugs(83, 3, "session=" << (void*)s.get() << " error: " << Security::ErrorString(x));
5d9a65df
AJ
234 }
235 data.reset(tmp);
236#endif
237 debugs(83, 5, "session=" << (void*)s.get() << " data=" << (void*)data.get());
238 } else {
239 debugs(83, 5, "session=" << (void*)s.get() << " data=" << (void*)data.get() << ", do nothing.");
240 }
241}
242
243void
244Security::SetSessionResumeData(const Security::SessionPointer &s, const Security::SessionStatePointer &data)
245{
246 if (data) {
247#if USE_OPENSSL
248 if (!SSL_set_session(s.get(), data.get())) {
249 const auto ssl_error = ERR_get_error();
250 debugs(83, 3, "session=" << (void*)s.get() << " data=" << (void*)data.get() <<
ea574635 251 " resume error: " << Security::ErrorString(ssl_error));
5d9a65df
AJ
252 }
253#elif USE_GNUTLS
254 const auto x = gnutls_session_set_data(s.get(), data->data, data->size);
255 if (x != GNUTLS_E_SUCCESS) {
256 debugs(83, 3, "session=" << (void*)s.get() << " data=" << (void*)data.get() <<
ea574635 257 " resume error: " << Security::ErrorString(x));
5d9a65df
AJ
258 }
259#else
260 // critical because, how did it get here?
261 debugs(83, DBG_CRITICAL, "no TLS library. session=" << (void*)s.get() << " data=" << (void*)data.get());
262#endif
263 debugs(83, 5, "session=" << (void*)s.get() << " data=" << (void*)data.get());
264 } else {
265 debugs(83, 5, "session=" << (void*)s.get() << " no resume data");
266 }
267}
268
824d4656
AJ
269static bool
270isTlsServer()
271{
272 for (AnyP::PortCfgPointer s = HttpPortList; s != nullptr; s = s->next) {
273 if (s->secure.encryptTransport)
274 return true;
275 if (s->flags.tunnelSslBumping)
276 return true;
277 }
278
279 return false;
280}
281
301a17d1
AJ
282#if USE_OPENSSL
283static int
284store_session_cb(SSL *ssl, SSL_SESSION *session)
285{
286 if (!SessionCache)
287 return 0;
288
289 debugs(83, 5, "Request to store SSL_SESSION");
290
291 SSL_SESSION_set_timeout(session, Config.SSL.session_ttl);
292
293#if HAVE_LIBSSL_SSL_SESSION_GET_ID
294 unsigned int idlen;
295 const unsigned char *id = SSL_SESSION_get_id(session, &idlen);
296#else
297 unsigned char *id = session->session_id;
298 unsigned int idlen = session->session_id_length;
299#endif
300 // XXX: the other calls [to openForReading()] do not copy the sessionId to a char buffer, does this really have to?
301 unsigned char key[MEMMAP_SLOT_KEY_SIZE];
302 // Session ids are of size 32bytes. They should always fit to a
303 // MemMap::Slot::key
304 assert(idlen <= MEMMAP_SLOT_KEY_SIZE);
305 memset(key, 0, sizeof(key));
306 memcpy(key, id, idlen);
307 int pos;
308 if (auto slotW = SessionCache->openForWriting(static_cast<const cache_key*>(key), pos)) {
309 int lenRequired = i2d_SSL_SESSION(session, nullptr);
310 if (lenRequired < MEMMAP_SLOT_DATA_SIZE) {
311 unsigned char *p = static_cast<unsigned char *>(slotW->p);
312 lenRequired = i2d_SSL_SESSION(session, &p);
313 slotW->set(key, nullptr, lenRequired, squid_curtime + Config.SSL.session_ttl);
314 }
315 SessionCache->closeForWriting(pos);
316 debugs(83, 5, "wrote an SSL_SESSION entry of size " << lenRequired << " at pos " << pos);
317 }
318 return 0;
319}
320
321static void
322remove_session_cb(SSL_CTX *, SSL_SESSION *sessionID)
323{
324 if (!SessionCache)
325 return;
326
327 debugs(83, 5, "Request to remove corrupted or not valid SSL_SESSION");
328 int pos;
329 if (const auto slot = SessionCache->openForReading(reinterpret_cast<const cache_key*>(sessionID), pos)) {
330 SessionCache->closeForReading(pos);
331 // TODO:
332 // What if we are not able to remove the session?
333 // Maybe schedule a job to remove it later?
334 // For now we just have an invalid entry in cache until will be expired
335 // The OpenSSL library will reject it when we try to use it
336 SessionCache->free(pos);
337 }
338}
339
340static SSL_SESSION *
341#if SQUID_USE_CONST_SSL_SESSION_CBID
342get_session_cb(SSL *, const unsigned char *sessionID, int len, int *copy)
343#else
344get_session_cb(SSL *, unsigned char *sessionID, int len, int *copy)
345#endif
346{
347 if (!SessionCache)
348 return nullptr;
349
350 const unsigned int *p = reinterpret_cast<const unsigned int *>(sessionID);
351 debugs(83, 5, "Request to search for SSL_SESSION of len: " <<
352 len << p[0] << ":" << p[1]);
353
354 SSL_SESSION *session = nullptr;
355 int pos;
356 if (const auto slot = SessionCache->openForReading(static_cast<const cache_key*>(sessionID), pos)) {
357 if (slot->expire > squid_curtime) {
358 const unsigned char *ptr = slot->p;
359 session = d2i_SSL_SESSION(nullptr, &ptr, slot->pSize);
360 debugs(83, 5, "SSL_SESSION retrieved from cache at pos " << pos);
361 } else
362 debugs(83, 5, "SSL_SESSION in cache expired");
363 SessionCache->closeForReading(pos);
364 }
365
366 if (!session)
367 debugs(83, 5, "Failed to retrieve SSL_SESSION from cache\n");
368
369 // With the parameter copy the callback can require the SSL engine
370 // to increment the reference count of the SSL_SESSION object, Normally
371 // the reference count is not incremented and therefore the session must
372 // not be explicitly freed with SSL_SESSION_free(3).
373 *copy = 0;
374 return session;
375}
376
377void
378Security::SetSessionCacheCallbacks(Security::ContextPointer &ctx)
379{
380 if (SessionCache) {
381 SSL_CTX_set_session_cache_mode(ctx.get(), SSL_SESS_CACHE_SERVER|SSL_SESS_CACHE_NO_INTERNAL);
382 SSL_CTX_sess_set_new_cb(ctx.get(), store_session_cb);
383 SSL_CTX_sess_set_remove_cb(ctx.get(), remove_session_cb);
384 SSL_CTX_sess_set_get_cb(ctx.get(), get_session_cb);
385 }
386}
387#endif /* USE_OPENSSL */
388
824d4656
AJ
389void
390initializeSessionCache()
391{
392#if USE_OPENSSL
393 // Check if the MemMap keys and data are enough big to hold
394 // session ids and session data
395 assert(SSL_SESSION_ID_SIZE >= MEMMAP_SLOT_KEY_SIZE);
396 assert(SSL_SESSION_MAX_SIZE >= MEMMAP_SLOT_DATA_SIZE);
397
398 int configuredItems = ::Config.SSL.sessionCacheSize / sizeof(Ipc::MemMap::Slot);
399 if (IamWorkerProcess() && configuredItems)
301a17d1 400 SessionCache = new Ipc::MemMap(SessionCacheName);
824d4656 401 else {
301a17d1 402 SessionCache = nullptr;
824d4656
AJ
403 return;
404 }
405
406 for (AnyP::PortCfgPointer s = HttpPortList; s != nullptr; s = s->next) {
b23f5f9c 407 if (s->secure.staticContext)
301a17d1 408 Security::SetSessionCacheCallbacks(s->secure.staticContext);
824d4656
AJ
409 }
410#endif
411}
412
413/// initializes shared memory segments used by MemStore
414class SharedSessionCacheRr: public Ipc::Mem::RegisteredRunner
415{
416public:
417 /* RegisteredRunner API */
418 SharedSessionCacheRr(): owner(nullptr) {}
419 virtual void useConfig();
420 virtual ~SharedSessionCacheRr();
421
422protected:
423 virtual void create();
424
425private:
426 Ipc::MemMap::Owner *owner;
427};
428
429RunnerRegistrationEntry(SharedSessionCacheRr);
430
431void
432SharedSessionCacheRr::useConfig()
433{
301a17d1
AJ
434#if USE_OPENSSL
435 if (SessionCache || !isTlsServer()) // no need to configure SSL_SESSION* cache.
824d4656
AJ
436 return;
437
438 Ipc::Mem::RegisteredRunner::useConfig();
439 initializeSessionCache();
440#endif
441}
442
443void
444SharedSessionCacheRr::create()
445{
301a17d1 446 if (!isTlsServer()) // no need to configure SSL_SESSION* cache.
824d4656
AJ
447 return;
448
301a17d1 449#if USE_OPENSSL
824d4656 450 if (int items = Config.SSL.sessionCacheSize / sizeof(Ipc::MemMap::Slot))
301a17d1 451 owner = Ipc::MemMap::Init(SessionCacheName, items);
824d4656
AJ
452#endif
453}
454
455SharedSessionCacheRr::~SharedSessionCacheRr()
456{
457 // XXX: Enable after testing to reduce at-exit memory "leaks".
301a17d1 458 // delete SessionCache;
824d4656
AJ
459
460 delete owner;
461}
462