2 * Copyright (C) 1996-2018 The Squid Software Foundation and contributors
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.
9 /* DEBUG: section 83 TLS session management */
12 #include "anyp/PortCfg.h"
13 #include "base/RunnersRegistry.h"
14 #include "CachePeer.h"
18 #include "ipc/MemMap.h"
19 #include "security/Session.h"
20 #include "SquidConfig.h"
23 #define SSL_SESSION_ID_SIZE 32
24 #define SSL_SESSION_MAX_SIZE 10*1024
27 static Ipc::MemMap
*SessionCache
= nullptr;
28 static const char *SessionCacheName
= "tls_session_cache";
31 #if USE_OPENSSL || USE_GNUTLS
33 tls_read_method(int fd
, char *buf
, int len
)
35 auto session
= fd_table
[fd
].ssl
.get();
36 debugs(83, 3, "started for session=" << (void*)session
);
38 #if DONT_DO_THIS && USE_OPENSSL
39 if (!SSL_is_init_finished(session
)) {
46 int i
= SSL_read(session
, buf
, len
);
48 int i
= gnutls_record_recv(session
, buf
, len
);
52 debugs(83, 8, "TLS FD " << fd
<< " session=" << (void*)session
<< " " << i
<< " bytes");
53 (void)VALGRIND_MAKE_MEM_DEFINED(buf
, i
);
57 if (i
> 0 && SSL_pending(session
) > 0) {
59 if (i
> 0 && gnutls_record_check_pending(session
) > 0) {
61 debugs(83, 2, "TLS FD " << fd
<< " is pending");
62 fd_table
[fd
].flags
.read_pending
= true;
64 fd_table
[fd
].flags
.read_pending
= false;
70 tls_write_method(int fd
, const char *buf
, int len
)
72 auto session
= fd_table
[fd
].ssl
.get();
73 debugs(83, 3, "started for session=" << (void*)session
);
76 if (!SSL_is_init_finished(session
)) {
83 int i
= SSL_write(session
, buf
, len
);
85 int i
= gnutls_record_send(session
, buf
, len
);
89 debugs(83, 8, "TLS FD " << fd
<< " session=" << (void*)session
<< " " << i
<< " bytes");
96 Security::SessionPointer
97 Security::NewSessionObject(const Security::ContextPointer
&ctx
)
99 Security::SessionPointer
session(SSL_new(ctx
.get()), [](SSL
*p
) {
100 debugs(83, 5, "SSL_free session=" << (void*)p
);
103 debugs(83, 5, "SSL_new session=" << (void*)session
.get());
109 CreateSession(const Security::ContextPointer
&ctx
, const Comm::ConnectionPointer
&conn
, Security::Io::Type type
, const char *squidCtx
)
111 if (!Comm::IsConnOpen(conn
)) {
112 debugs(83, DBG_IMPORTANT
, "Gone connection");
116 #if USE_OPENSSL || USE_GNUTLS
118 const char *errAction
= "with no TLS/SSL library";
121 Security::SessionPointer
session(Security::NewSessionObject(ctx
));
123 errCode
= ERR_get_error();
124 errAction
= "failed to allocate handle";
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
);
133 debugs(83, 5, "gnutls_init " << (type
== Security::Io::BIO_TO_SERVER
? "client" : "server" )<< " session=" << (void*)session
.get());
134 if (errCode
!= GNUTLS_E_SUCCESS
) {
136 errAction
= "failed to initialize session";
141 const int fd
= conn
->fd
;
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
148 errCode
= gnutls_credentials_set(session
.get(), GNUTLS_CRD_CERTIFICATE
, ctx
.get());
149 if (errCode
== GNUTLS_E_SUCCESS
) {
151 if (auto *peer
= conn
->getPeer())
152 peer
->secure
.updateSessionOptions(session
);
154 Security::ProxyOutgoingConfig
.updateSessionOptions(session
);
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
);
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
);
171 errCode
= ERR_get_error();
172 errAction
= "failed to initialize I/O";
174 errAction
= "failed to assign credentials";
178 debugs(83, DBG_IMPORTANT
, "ERROR: " << squidCtx
<< ' ' << errAction
<<
179 ": " << (errCode
!= 0 ? Security::ErrorString(errCode
) : ""));
185 Security::CreateClientSession(const Security::ContextPointer
&ctx
, const Comm::ConnectionPointer
&c
, const char *squidCtx
)
187 return CreateSession(ctx
, c
, Security::Io::BIO_TO_SERVER
, squidCtx
);
191 Security::CreateServerSession(const Security::ContextPointer
&ctx
, const Comm::ConnectionPointer
&c
, const char *squidCtx
)
193 return CreateSession(ctx
, c
, Security::Io::BIO_TO_CLIENT
, squidCtx
);
197 Security::SessionSendGoodbye(const Security::SessionPointer
&s
)
199 debugs(83, 5, "session=" << (void*)s
.get());
202 SSL_shutdown(s
.get());
204 gnutls_bye(s
.get(), GNUTLS_SHUT_RDWR
);
210 Security::SessionIsResumed(const Security::SessionPointer
&s
)
214 result
= SSL_session_reused(s
.get()) == 1;
216 result
= gnutls_session_is_resumed(s
.get()) != 0;
218 debugs(83, 7, "session=" << (void*)s
.get() << ", query? answer: " << (result
? 'T' : 'F') );
223 Security::MaybeGetSessionResumeData(const Security::SessionPointer
&s
, Security::SessionStatePointer
&data
)
225 if (!SessionIsResumed(s
)) {
227 // nil is valid for SSL_get1_session(), it cannot fail.
228 data
.reset(SSL_get1_session(s
.get()));
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
));
237 debugs(83, 5, "session=" << (void*)s
.get() << " data=" << (void*)data
.get());
239 debugs(83, 5, "session=" << (void*)s
.get() << " data=" << (void*)data
.get() << ", do nothing.");
244 Security::SetSessionResumeData(const Security::SessionPointer
&s
, const Security::SessionStatePointer
&data
)
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
));
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
));
260 // critical because, how did it get here?
261 debugs(83, DBG_CRITICAL
, "no TLS library. session=" << (void*)s
.get() << " data=" << (void*)data
.get());
263 debugs(83, 5, "session=" << (void*)s
.get() << " data=" << (void*)data
.get());
265 debugs(83, 5, "session=" << (void*)s
.get() << " no resume data");
272 for (AnyP::PortCfgPointer s
= HttpPortList
; s
!= nullptr; s
= s
->next
) {
273 if (s
->secure
.encryptTransport
)
275 if (s
->flags
.tunnelSslBumping
)
284 store_session_cb(SSL
*ssl
, SSL_SESSION
*session
)
289 debugs(83, 5, "Request to store SSL_SESSION");
291 SSL_SESSION_set_timeout(session
, Config
.SSL
.session_ttl
);
293 #if HAVE_LIBSSL_SSL_SESSION_GET_ID
295 const unsigned char *id
= SSL_SESSION_get_id(session
, &idlen
);
297 unsigned char *id
= session
->session_id
;
298 unsigned int idlen
= session
->session_id_length
;
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
304 assert(idlen
<= MEMMAP_SLOT_KEY_SIZE
);
305 memset(key
, 0, sizeof(key
));
306 memcpy(key
, id
, idlen
);
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
);
315 SessionCache
->closeForWriting(pos
);
316 debugs(83, 5, "wrote an SSL_SESSION entry of size " << lenRequired
<< " at pos " << pos
);
322 remove_session_cb(SSL_CTX
*, SSL_SESSION
*sessionID
)
327 debugs(83, 5, "Request to remove corrupted or not valid SSL_SESSION");
329 if (SessionCache
->openForReading(reinterpret_cast<const cache_key
*>(sessionID
), pos
)) {
330 SessionCache
->closeForReading(pos
);
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
);
341 #if SQUID_USE_CONST_SSL_SESSION_CBID
342 get_session_cb(SSL
*, const unsigned char *sessionID
, int len
, int *copy
)
344 get_session_cb(SSL
*, unsigned char *sessionID
, int len
, int *copy
)
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]);
354 SSL_SESSION
*session
= nullptr;
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
);
362 debugs(83, 5, "SSL_SESSION in cache expired");
363 SessionCache
->closeForReading(pos
);
367 debugs(83, 5, "Failed to retrieve SSL_SESSION from cache");
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).
378 Security::SetSessionCacheCallbacks(Security::ContextPointer
&ctx
)
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
);
387 #endif /* USE_OPENSSL */
390 initializeSessionCache()
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
);
398 int configuredItems
= ::Config
.SSL
.sessionCacheSize
/ sizeof(Ipc::MemMap::Slot
);
399 if (IamWorkerProcess() && configuredItems
)
400 SessionCache
= new Ipc::MemMap(SessionCacheName
);
402 SessionCache
= nullptr;
406 for (AnyP::PortCfgPointer s
= HttpPortList
; s
!= nullptr; s
= s
->next
) {
407 if (s
->secure
.staticContext
)
408 Security::SetSessionCacheCallbacks(s
->secure
.staticContext
);
413 /// initializes shared memory segments used by MemStore
414 class SharedSessionCacheRr
: public Ipc::Mem::RegisteredRunner
417 /* RegisteredRunner API */
418 SharedSessionCacheRr(): owner(nullptr) {}
419 virtual void useConfig();
420 virtual ~SharedSessionCacheRr();
423 virtual void create();
426 Ipc::MemMap::Owner
*owner
;
429 RunnerRegistrationEntry(SharedSessionCacheRr
);
432 SharedSessionCacheRr::useConfig()
435 if (SessionCache
|| !isTlsServer()) // no need to configure SSL_SESSION* cache.
438 Ipc::Mem::RegisteredRunner::useConfig();
439 initializeSessionCache();
444 SharedSessionCacheRr::create()
446 if (!isTlsServer()) // no need to configure SSL_SESSION* cache.
450 if (int items
= Config
.SSL
.sessionCacheSize
/ sizeof(Ipc::MemMap::Slot
))
451 owner
= Ipc::MemMap::Init(SessionCacheName
, items
);
455 SharedSessionCacheRr::~SharedSessionCacheRr()
457 // XXX: Enable after testing to reduce at-exit memory "leaks".
458 // delete SessionCache;