]> git.ipfire.org Git - thirdparty/squid.git/blob - src/security/Session.cc
3499e980b74c4548522a0b6e1fcbb53cb65f7240
[thirdparty/squid.git] / src / security / Session.cc
1 /*
2 * Copyright (C) 1996-2020 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.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 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
50
51 if (i > 0) {
52 debugs(83, 8, "TLS FD " << fd << " session=" << (void*)session << " " << i << " bytes");
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
69 static int
70 tls_write_method(int fd, const char *buf, int len)
71 {
72 auto session = fd_table[fd].ssl.get();
73 debugs(83, 3, "started for session=" << (void*)session);
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
88 if (i > 0) {
89 debugs(83, 8, "TLS FD " << fd << " session=" << (void*)session << " " << i << " bytes");
90 }
91 return i;
92 }
93 #endif
94
95 #if USE_OPENSSL
96 Security::SessionPointer
97 Security::NewSessionObject(const Security::ContextPointer &ctx)
98 {
99 Security::SessionPointer session(SSL_new(ctx.get()), [](SSL *p) {
100 debugs(83, 5, "SSL_free session=" << (void*)p);
101 SSL_free(p);
102 });
103 debugs(83, 5, "SSL_new session=" << (void*)session.get());
104 return session;
105 }
106 #endif
107
108 static bool
109 CreateSession(const Security::ContextPointer &ctx, const Comm::ConnectionPointer &conn, Security::PeerOptions &opts, 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
116 #if USE_OPENSSL || USE_GNUTLS
117
118 const char *errAction = "with no TLS/SSL library";
119 int errCode = 0;
120 #if USE_OPENSSL
121 Security::SessionPointer session(Security::NewSessionObject(ctx));
122 if (!session) {
123 errCode = ERR_get_error();
124 errAction = "failed to allocate handle";
125 debugs(83, DBG_IMPORTANT, "TLS error: " << errAction << ": " << Security::ErrorString(errCode));
126 }
127 #elif USE_GNUTLS
128 gnutls_session_t tmp;
129 errCode = gnutls_init(&tmp, static_cast<unsigned int>(type) | GNUTLS_NONBLOCK);
130 Security::SessionPointer session(tmp, [](gnutls_session_t p) {
131 debugs(83, 5, "gnutls_deinit session=" << (void*)p);
132 gnutls_deinit(p);
133 });
134 debugs(83, 5, "gnutls_init " << (type == Security::Io::BIO_TO_SERVER ? "client" : "server" )<< " session=" << (void*)session.get());
135 if (errCode != GNUTLS_E_SUCCESS) {
136 session.reset();
137 errAction = "failed to initialize session";
138 debugs(83, DBG_IMPORTANT, "TLS error: " << errAction << ": " << Security::ErrorString(errCode));
139 }
140 #endif
141
142 if (session) {
143 const int fd = conn->fd;
144
145 #if USE_OPENSSL
146 // without BIO, we would call SSL_set_fd(ssl.get(), fd) instead
147 if (BIO *bio = Ssl::Bio::Create(fd, type)) {
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) {
152
153 opts.updateSessionOptions(session);
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);
158 gnutls_handshake_set_timeout(session.get(), GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT);
159 #endif
160
161 debugs(83, 5, "link FD " << fd << " to TLS session=" << (void*)session.get());
162
163 fd_table[fd].ssl = session;
164 fd_table[fd].useBufferedIo(&tls_read_method, &tls_write_method);
165 fd_note(fd, squidCtx);
166 return true;
167 }
168
169 #if USE_OPENSSL
170 errCode = ERR_get_error();
171 errAction = "failed to initialize I/O";
172 #elif USE_GNUTLS
173 errAction = "failed to assign credentials";
174 #endif
175 }
176
177 debugs(83, DBG_IMPORTANT, "ERROR: " << squidCtx << ' ' << errAction <<
178 ": " << (errCode != 0 ? Security::ErrorString(errCode) : ""));
179 #endif
180 return false;
181 }
182
183 bool
184 Security::CreateClientSession(const Security::ContextPointer &ctx, const Comm::ConnectionPointer &c, const char *squidCtx)
185 {
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);
191 }
192
193 bool
194 Security::CreateServerSession(const Security::ContextPointer &ctx, const Comm::ConnectionPointer &c, Security::PeerOptions &o, const char *squidCtx)
195 {
196 return CreateSession(ctx, c, o, Security::Io::BIO_TO_CLIENT, squidCtx);
197 }
198
199 void
200 Security::SessionSendGoodbye(const Security::SessionPointer &s)
201 {
202 debugs(83, 5, "session=" << (void*)s.get());
203 if (s) {
204 #if USE_OPENSSL
205 SSL_shutdown(s.get());
206 #elif USE_GNUTLS
207 gnutls_bye(s.get(), GNUTLS_SHUT_RDWR);
208 #endif
209 }
210 }
211
212 bool
213 Security::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
225 void
226 Security::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) {
236 debugs(83, 3, "session=" << (void*)s.get() << " error: " << Security::ErrorString(x));
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
246 void
247 Security::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() <<
254 " resume error: " << Security::ErrorString(ssl_error));
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() <<
260 " resume error: " << Security::ErrorString(x));
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
272 static bool
273 isTlsServer()
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
285 #if USE_OPENSSL
286 static int
287 store_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
296 unsigned int idlen;
297 const unsigned char *id = SSL_SESSION_get_id(session, &idlen);
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
319 static void
320 remove_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;
327 if (SessionCache->openForReading(reinterpret_cast<const cache_key*>(sessionID), pos)) {
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
338 static SSL_SESSION *
339 #if SQUID_USE_CONST_SSL_SESSION_CBID
340 get_session_cb(SSL *, const unsigned char *sessionID, int len, int *copy)
341 #else
342 get_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)
365 debugs(83, 5, "Failed to retrieve SSL_SESSION from cache");
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
375 void
376 Security::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
387 void
388 initializeSessionCache()
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)
398 SessionCache = new Ipc::MemMap(SessionCacheName);
399 else {
400 SessionCache = nullptr;
401 return;
402 }
403
404 for (AnyP::PortCfgPointer s = HttpPortList; s != nullptr; s = s->next) {
405 if (s->secure.staticContext)
406 Security::SetSessionCacheCallbacks(s->secure.staticContext);
407 }
408 #endif
409 }
410
411 /// initializes shared memory segments used by MemStore
412 class SharedSessionCacheRr: public Ipc::Mem::RegisteredRunner
413 {
414 public:
415 /* RegisteredRunner API */
416 SharedSessionCacheRr(): owner(nullptr) {}
417 virtual void useConfig();
418 virtual ~SharedSessionCacheRr();
419
420 protected:
421 virtual void create();
422
423 private:
424 Ipc::MemMap::Owner *owner;
425 };
426
427 RunnerRegistrationEntry(SharedSessionCacheRr);
428
429 void
430 SharedSessionCacheRr::useConfig()
431 {
432 #if USE_OPENSSL
433 if (SessionCache || !isTlsServer()) // no need to configure SSL_SESSION* cache.
434 return;
435
436 Ipc::Mem::RegisteredRunner::useConfig();
437 initializeSessionCache();
438 #endif
439 }
440
441 void
442 SharedSessionCacheRr::create()
443 {
444 if (!isTlsServer()) // no need to configure SSL_SESSION* cache.
445 return;
446
447 #if USE_OPENSSL
448 if (int items = Config.SSL.sessionCacheSize / sizeof(Ipc::MemMap::Slot))
449 owner = Ipc::MemMap::Init(SessionCacheName, items);
450 #endif
451 }
452
453 SharedSessionCacheRr::~SharedSessionCacheRr()
454 {
455 // XXX: Enable after testing to reduce at-exit memory "leaks".
456 // delete SessionCache;
457
458 delete owner;
459 }
460