]> git.ipfire.org Git - thirdparty/squid.git/blob - src/security/Session.cc
Source Format Enforcement (#1234)
[thirdparty/squid.git] / src / security / Session.cc
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"
13 #include "base/RunnersRegistry.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
27 static Ipc::MemMap *SessionCache = nullptr;
28 static const char *SessionCacheName = "tls_session_cache";
29 #endif
30
31 #if USE_OPENSSL || USE_GNUTLS
32 static int
33 tls_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
62 static int
63 tls_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
89 Security::SessionPointer
90 Security::NewSessionObject(const Security::ContextPointer &ctx)
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
101 static bool
102 CreateSession(const Security::ContextPointer &ctx, const Comm::ConnectionPointer &conn, Security::PeerOptions &opts, Security::Io::Type type, const char *squidCtx)
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
114 Security::SessionPointer session(Security::NewSessionObject(ctx));
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
182 bool
183 Security::CreateClientSession(const Security::ContextPointer &ctx, const Comm::ConnectionPointer &c, const char *squidCtx)
184 {
185 if (!c || !c->getPeer())
186 return CreateSession(ctx, c, Security::ProxyOutgoingConfig, Security::Io::BIO_TO_SERVER, squidCtx);
187
188 auto *peer = c->getPeer();
189 return CreateSession(ctx, c, peer->secure, Security::Io::BIO_TO_SERVER, squidCtx);
190 }
191
192 bool
193 Security::CreateServerSession(const Security::ContextPointer &ctx, const Comm::ConnectionPointer &c, Security::PeerOptions &o, const char *squidCtx)
194 {
195 return CreateSession(ctx, c, o, Security::Io::BIO_TO_CLIENT, squidCtx);
196 }
197
198 void
199 Security::SessionSendGoodbye(const Security::SessionPointer &s)
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
211 bool
212 Security::SessionIsResumed(const Security::SessionPointer &s)
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
224 void
225 Security::MaybeGetSessionResumeData(const Security::SessionPointer &s, Security::SessionStatePointer &data)
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
245 void
246 Security::SetSessionResumeData(const Security::SessionPointer &s, const Security::SessionStatePointer &data)
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
271 static bool
272 isTlsServer()
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
285 static int
286 store_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
301 assert(idlen <= MEMMAP_SLOT_KEY_SIZE);
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 }
312 SessionCache->closeForWriting(pos);
313 debugs(83, 5, "wrote an SSL_SESSION entry of size " << lenRequired << " at pos " << pos);
314 }
315 return 0;
316 }
317
318 static void
319 remove_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)) {
327 SessionCache->closeForReading(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
337 static SSL_SESSION *
338 #if SQUID_USE_CONST_SSL_SESSION_CBID
339 get_session_cb(SSL *, const unsigned char *sessionID, int len, int *copy)
340 #else
341 get_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");
360 SessionCache->closeForReading(pos);
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
374 void
375 Security::SetSessionCacheCallbacks(Security::ContextPointer &ctx)
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
387 static void
388 initializeSessionCache()
389 {
390 // Check if the MemMap keys and data are enough big to hold
391 // session ids and session data
392 assert(SSL_SESSION_ID_SIZE >= MEMMAP_SLOT_KEY_SIZE);
393 assert(SSL_SESSION_MAX_SIZE >= MEMMAP_SLOT_DATA_SIZE);
394
395 int configuredItems = ::Config.SSL.sessionCacheSize / sizeof(Ipc::MemMap::Slot);
396 if (IamWorkerProcess() && configuredItems)
397 SessionCache = new Ipc::MemMap(SessionCacheName);
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
410 /// initializes shared memory segments used by MemStore
411 class SharedSessionCacheRr: public Ipc::Mem::RegisteredRunner
412 {
413 public:
414 /* RegisteredRunner API */
415 SharedSessionCacheRr(): owner(nullptr) {}
416 void useConfig() override;
417 ~SharedSessionCacheRr() override;
418
419 protected:
420 void create() override;
421
422 private:
423 Ipc::MemMap::Owner *owner;
424 };
425
426 RunnerRegistrationEntry(SharedSessionCacheRr);
427
428 void
429 SharedSessionCacheRr::useConfig()
430 {
431 #if USE_OPENSSL
432 if (SessionCache || !isTlsServer()) // no need to configure SSL_SESSION* cache.
433 return;
434
435 Ipc::Mem::RegisteredRunner::useConfig();
436 initializeSessionCache();
437 #endif
438 }
439
440 void
441 SharedSessionCacheRr::create()
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))
448 owner = Ipc::MemMap::Init(SessionCacheName, items);
449 #endif
450 }
451
452 SharedSessionCacheRr::~SharedSessionCacheRr()
453 {
454 // XXX: Enable after testing to reduce at-exit memory "leaks".
455 // delete SessionCache;
456
457 delete owner;
458 }
459