]> git.ipfire.org Git - thirdparty/squid.git/blob - src/security/Session.cc
Docs: Copyright updates for 2018 (#114)
[thirdparty/squid.git] / src / security / Session.cc
1 /*
2 * Copyright (C) 1996-2018 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::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 }
126 #elif USE_GNUTLS
127 gnutls_session_t tmp;
128 errCode = gnutls_init(&tmp, static_cast<unsigned int>(type) | GNUTLS_NONBLOCK);
129 Security::SessionPointer session(tmp, [](gnutls_session_t p) {
130 debugs(83, 5, "gnutls_deinit session=" << (void*)p);
131 gnutls_deinit(p);
132 });
133 debugs(83, 5, "gnutls_init " << (type == Security::Io::BIO_TO_SERVER ? "client" : "server" )<< " session=" << (void*)session.get());
134 if (errCode != GNUTLS_E_SUCCESS) {
135 session.reset();
136 errAction = "failed to initialize session";
137 }
138 #endif
139
140 if (session) {
141 const int fd = conn->fd;
142
143 #if USE_OPENSSL
144 // without BIO, we would call SSL_set_fd(ssl.get(), fd) instead
145 if (BIO *bio = Ssl::Bio::Create(fd, type)) {
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) {
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);
159 gnutls_handshake_set_timeout(session.get(), GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT);
160 #endif
161
162 debugs(83, 5, "link FD " << fd << " to TLS session=" << (void*)session.get());
163 fd_table[fd].ssl = session;
164 fd_table[fd].read_method = &tls_read_method;
165 fd_table[fd].write_method = &tls_write_method;
166 fd_note(fd, squidCtx);
167 return true;
168 }
169
170 #if USE_OPENSSL
171 errCode = ERR_get_error();
172 errAction = "failed to initialize I/O";
173 #elif USE_GNUTLS
174 errAction = "failed to assign credentials";
175 #endif
176 }
177
178 debugs(83, DBG_IMPORTANT, "ERROR: " << squidCtx << ' ' << errAction <<
179 ": " << (errCode != 0 ? Security::ErrorString(errCode) : ""));
180 #endif
181 return false;
182 }
183
184 bool
185 Security::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
190 bool
191 Security::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
196 void
197 Security::SessionSendGoodbye(const Security::SessionPointer &s)
198 {
199 debugs(83, 5, "session=" << (void*)s.get());
200 if (s) {
201 #if USE_OPENSSL
202 SSL_shutdown(s.get());
203 #elif USE_GNUTLS
204 gnutls_bye(s.get(), GNUTLS_SHUT_RDWR);
205 #endif
206 }
207 }
208
209 bool
210 Security::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
222 void
223 Security::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) {
233 debugs(83, 3, "session=" << (void*)s.get() << " error: " << Security::ErrorString(x));
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
243 void
244 Security::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() <<
251 " resume error: " << Security::ErrorString(ssl_error));
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() <<
257 " resume error: " << Security::ErrorString(x));
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
269 static bool
270 isTlsServer()
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
282 #if USE_OPENSSL
283 static int
284 store_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
321 static void
322 remove_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 (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
340 static SSL_SESSION *
341 #if SQUID_USE_CONST_SSL_SESSION_CBID
342 get_session_cb(SSL *, const unsigned char *sessionID, int len, int *copy)
343 #else
344 get_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");
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
377 void
378 Security::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
389 void
390 initializeSessionCache()
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)
400 SessionCache = new Ipc::MemMap(SessionCacheName);
401 else {
402 SessionCache = nullptr;
403 return;
404 }
405
406 for (AnyP::PortCfgPointer s = HttpPortList; s != nullptr; s = s->next) {
407 if (s->secure.staticContext)
408 Security::SetSessionCacheCallbacks(s->secure.staticContext);
409 }
410 #endif
411 }
412
413 /// initializes shared memory segments used by MemStore
414 class SharedSessionCacheRr: public Ipc::Mem::RegisteredRunner
415 {
416 public:
417 /* RegisteredRunner API */
418 SharedSessionCacheRr(): owner(nullptr) {}
419 virtual void useConfig();
420 virtual ~SharedSessionCacheRr();
421
422 protected:
423 virtual void create();
424
425 private:
426 Ipc::MemMap::Owner *owner;
427 };
428
429 RunnerRegistrationEntry(SharedSessionCacheRr);
430
431 void
432 SharedSessionCacheRr::useConfig()
433 {
434 #if USE_OPENSSL
435 if (SessionCache || !isTlsServer()) // no need to configure SSL_SESSION* cache.
436 return;
437
438 Ipc::Mem::RegisteredRunner::useConfig();
439 initializeSessionCache();
440 #endif
441 }
442
443 void
444 SharedSessionCacheRr::create()
445 {
446 if (!isTlsServer()) // no need to configure SSL_SESSION* cache.
447 return;
448
449 #if USE_OPENSSL
450 if (int items = Config.SSL.sessionCacheSize / sizeof(Ipc::MemMap::Slot))
451 owner = Ipc::MemMap::Init(SessionCacheName, items);
452 #endif
453 }
454
455 SharedSessionCacheRr::~SharedSessionCacheRr()
456 {
457 // XXX: Enable after testing to reduce at-exit memory "leaks".
458 // delete SessionCache;
459
460 delete owner;
461 }
462