]>
Commit | Line | Data |
---|---|---|
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 |