2 * Copyright (C) 1996-2020 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::PeerOptions
&opts
, 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";
125 debugs(83, DBG_IMPORTANT
, "TLS error: " << errAction
<< ": " << Security::ErrorString(errCode
));
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
);
134 debugs(83, 5, "gnutls_init " << (type
== Security::Io::BIO_TO_SERVER
? "client" : "server" )<< " session=" << (void*)session
.get());
135 if (errCode
!= GNUTLS_E_SUCCESS
) {
137 errAction
= "failed to initialize session";
138 debugs(83, DBG_IMPORTANT
, "TLS error: " << errAction
<< ": " << Security::ErrorString(errCode
));
143 const int fd
= conn
->fd
;
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
150 errCode
= gnutls_credentials_set(session
.get(), GNUTLS_CRD_CERTIFICATE
, ctx
.get());
151 if (errCode
== GNUTLS_E_SUCCESS
) {
153 opts
.updateSessionOptions(session
);
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
);
161 debugs(83, 5, "link FD " << fd
<< " to TLS session=" << (void*)session
.get());
163 fd_table
[fd
].ssl
= session
;
164 fd_table
[fd
].useBufferedIo(&tls_read_method
, &tls_write_method
);
165 fd_note(fd
, squidCtx
);
170 errCode
= ERR_get_error();
171 errAction
= "failed to initialize I/O";
173 errAction
= "failed to assign credentials";
177 debugs(83, DBG_IMPORTANT
, "ERROR: " << squidCtx
<< ' ' << errAction
<<
178 ": " << (errCode
!= 0 ? Security::ErrorString(errCode
) : ""));
184 Security::CreateClientSession(const Security::ContextPointer
&ctx
, const Comm::ConnectionPointer
&c
, const char *squidCtx
)
186 if (!c
|| !c
->getPeer())
187 return CreateSession(ctx
, c
, Security::ProxyOutgoingConfig
, Security::Io::BIO_TO_SERVER
, squidCtx
);
189 auto *peer
= c
->getPeer();
190 return CreateSession(ctx
, c
, peer
->secure
, Security::Io::BIO_TO_SERVER
, squidCtx
);
194 Security::CreateServerSession(const Security::ContextPointer
&ctx
, const Comm::ConnectionPointer
&c
, Security::PeerOptions
&o
, const char *squidCtx
)
196 return CreateSession(ctx
, c
, o
, Security::Io::BIO_TO_CLIENT
, squidCtx
);
200 Security::SessionSendGoodbye(const Security::SessionPointer
&s
)
202 debugs(83, 5, "session=" << (void*)s
.get());
205 SSL_shutdown(s
.get());
207 gnutls_bye(s
.get(), GNUTLS_SHUT_RDWR
);
213 Security::SessionIsResumed(const Security::SessionPointer
&s
)
217 result
= SSL_session_reused(s
.get()) == 1;
219 result
= gnutls_session_is_resumed(s
.get()) != 0;
221 debugs(83, 7, "session=" << (void*)s
.get() << ", query? answer: " << (result
? 'T' : 'F') );
226 Security::MaybeGetSessionResumeData(const Security::SessionPointer
&s
, Security::SessionStatePointer
&data
)
228 if (!SessionIsResumed(s
)) {
230 // nil is valid for SSL_get1_session(), it cannot fail.
231 data
.reset(SSL_get1_session(s
.get()));
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
));
240 debugs(83, 5, "session=" << (void*)s
.get() << " data=" << (void*)data
.get());
242 debugs(83, 5, "session=" << (void*)s
.get() << " data=" << (void*)data
.get() << ", do nothing.");
247 Security::SetSessionResumeData(const Security::SessionPointer
&s
, const Security::SessionStatePointer
&data
)
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
));
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
));
263 // critical because, how did it get here?
264 debugs(83, DBG_CRITICAL
, "no TLS library. session=" << (void*)s
.get() << " data=" << (void*)data
.get());
266 debugs(83, 5, "session=" << (void*)s
.get() << " data=" << (void*)data
.get());
268 debugs(83, 5, "session=" << (void*)s
.get() << " no resume data");
275 for (AnyP::PortCfgPointer s
= HttpPortList
; s
!= nullptr; s
= s
->next
) {
276 if (s
->secure
.encryptTransport
)
278 if (s
->flags
.tunnelSslBumping
)
287 store_session_cb(SSL
*ssl
, SSL_SESSION
*session
)
292 debugs(83, 5, "Request to store SSL_SESSION");
294 SSL_SESSION_set_timeout(session
, Config
.SSL
.session_ttl
);
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
302 assert(idlen
<= MEMMAP_SLOT_KEY_SIZE
);
303 memset(key
, 0, sizeof(key
));
304 memcpy(key
, id
, idlen
);
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
);
313 SessionCache
->closeForWriting(pos
);
314 debugs(83, 5, "wrote an SSL_SESSION entry of size " << lenRequired
<< " at pos " << pos
);
320 remove_session_cb(SSL_CTX
*, SSL_SESSION
*sessionID
)
325 debugs(83, 5, "Request to remove corrupted or not valid SSL_SESSION");
327 if (SessionCache
->openForReading(reinterpret_cast<const cache_key
*>(sessionID
), pos
)) {
328 SessionCache
->closeForReading(pos
);
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
);
339 #if SQUID_USE_CONST_SSL_SESSION_CBID
340 get_session_cb(SSL
*, const unsigned char *sessionID
, int len
, int *copy
)
342 get_session_cb(SSL
*, unsigned char *sessionID
, int len
, int *copy
)
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]);
352 SSL_SESSION
*session
= nullptr;
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
);
360 debugs(83, 5, "SSL_SESSION in cache expired");
361 SessionCache
->closeForReading(pos
);
365 debugs(83, 5, "Failed to retrieve SSL_SESSION from cache");
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).
376 Security::SetSessionCacheCallbacks(Security::ContextPointer
&ctx
)
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
);
385 #endif /* USE_OPENSSL */
388 initializeSessionCache()
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
);
396 int configuredItems
= ::Config
.SSL
.sessionCacheSize
/ sizeof(Ipc::MemMap::Slot
);
397 if (IamWorkerProcess() && configuredItems
)
398 SessionCache
= new Ipc::MemMap(SessionCacheName
);
400 SessionCache
= nullptr;
404 for (AnyP::PortCfgPointer s
= HttpPortList
; s
!= nullptr; s
= s
->next
) {
405 if (s
->secure
.staticContext
)
406 Security::SetSessionCacheCallbacks(s
->secure
.staticContext
);
411 /// initializes shared memory segments used by MemStore
412 class SharedSessionCacheRr
: public Ipc::Mem::RegisteredRunner
415 /* RegisteredRunner API */
416 SharedSessionCacheRr(): owner(nullptr) {}
417 virtual void useConfig();
418 virtual ~SharedSessionCacheRr();
421 virtual void create();
424 Ipc::MemMap::Owner
*owner
;
427 RunnerRegistrationEntry(SharedSessionCacheRr
);
430 SharedSessionCacheRr::useConfig()
433 if (SessionCache
|| !isTlsServer()) // no need to configure SSL_SESSION* cache.
436 Ipc::Mem::RegisteredRunner::useConfig();
437 initializeSessionCache();
442 SharedSessionCacheRr::create()
444 if (!isTlsServer()) // no need to configure SSL_SESSION* cache.
448 if (int items
= Config
.SSL
.sessionCacheSize
/ sizeof(Ipc::MemMap::Slot
))
449 owner
= Ipc::MemMap::Init(SessionCacheName
, items
);
453 SharedSessionCacheRr::~SharedSessionCacheRr()
455 // XXX: Enable after testing to reduce at-exit memory "leaks".
456 // delete SessionCache;