]>
Commit | Line | Data |
---|---|---|
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 |
27 | static Ipc::MemMap *SessionCache = nullptr; | |
28 | static const char *SessionCacheName = "tls_session_cache"; | |
29 | #endif | |
30 | ||
087b94cb AJ |
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(); | |
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 | ||
69 | static int | |
70 | tls_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 |
96 | Security::SessionPointer | |
97 | Security::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 |
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 | ||
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 | ||
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 | ||
087b94cb | 196 | void |
03e0e0e4 | 197 | Security::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 |
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) { | |
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 | ||
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() << | |
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 |
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 | ||
301a17d1 AJ |
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 (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 | ||
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\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 | ||
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 | ||
824d4656 AJ |
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) | |
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 | |
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 | { | |
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 | ||
443 | void | |
444 | SharedSessionCacheRr::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 | ||
455 | SharedSessionCacheRr::~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 |