2 * Copyright (C) 1996-2023 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"
15 #include "debug/Stream.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
);
39 int i
= SSL_read(session
, buf
, len
);
41 int i
= gnutls_record_recv(session
, buf
, len
);
45 debugs(83, 8, "TLS FD " << fd
<< " session=" << (void*)session
<< " " << i
<< " bytes");
46 (void)VALGRIND_MAKE_MEM_DEFINED(buf
, i
);
50 if (i
> 0 && SSL_pending(session
) > 0) {
52 if (i
> 0 && gnutls_record_check_pending(session
) > 0) {
54 debugs(83, 2, "TLS FD " << fd
<< " is pending");
55 fd_table
[fd
].flags
.read_pending
= true;
57 fd_table
[fd
].flags
.read_pending
= false;
63 tls_write_method(int fd
, const char *buf
, int len
)
65 auto session
= fd_table
[fd
].ssl
.get();
66 debugs(83, 3, "started for session=" << (void*)session
);
69 if (!SSL_is_init_finished(session
)) {
76 int i
= SSL_write(session
, buf
, len
);
78 int i
= gnutls_record_send(session
, buf
, len
);
82 debugs(83, 8, "TLS FD " << fd
<< " session=" << (void*)session
<< " " << i
<< " bytes");
89 Security::SessionPointer
90 Security::NewSessionObject(const Security::ContextPointer
&ctx
)
92 Security::SessionPointer
session(SSL_new(ctx
.get()), [](SSL
*p
) {
93 debugs(83, 5, "SSL_free session=" << (void*)p
);
96 debugs(83, 5, "SSL_new session=" << (void*)session
.get());
102 CreateSession(const Security::ContextPointer
&ctx
, const Comm::ConnectionPointer
&conn
, Security::PeerOptions
&opts
, Security::Io::Type type
, const char *squidCtx
)
104 if (!Comm::IsConnOpen(conn
)) {
105 debugs(83, DBG_IMPORTANT
, "Gone connection");
109 #if USE_OPENSSL || USE_GNUTLS
111 const char *errAction
= "with no TLS/SSL library";
112 Security::LibErrorCode errCode
= 0;
114 Security::SessionPointer
session(Security::NewSessionObject(ctx
));
116 errCode
= ERR_get_error();
117 errAction
= "failed to allocate handle";
118 debugs(83, DBG_IMPORTANT
, "ERROR: TLS failure: " << errAction
<< ": " << Security::ErrorString(errCode
));
121 gnutls_session_t tmp
;
122 errCode
= gnutls_init(&tmp
, static_cast<unsigned int>(type
) | GNUTLS_NONBLOCK
);
123 Security::SessionPointer
session(tmp
, [](gnutls_session_t p
) {
124 debugs(83, 5, "gnutls_deinit session=" << (void*)p
);
127 debugs(83, 5, "gnutls_init " << (type
== Security::Io::BIO_TO_SERVER
? "client" : "server" )<< " session=" << (void*)session
.get());
128 if (errCode
!= GNUTLS_E_SUCCESS
) {
130 errAction
= "failed to initialize session";
131 debugs(83, DBG_IMPORTANT
, "ERROR: TLS failure: " << errAction
<< ": " << Security::ErrorString(errCode
));
133 #endif /* USE_GNUTLS */
136 const int fd
= conn
->fd
;
139 // without BIO, we would call SSL_set_fd(ssl.get(), fd) instead
140 if (BIO
*bio
= Ssl::Bio::Create(fd
, type
)) {
141 Ssl::Bio::Link(session
.get(), bio
); // cannot fail
143 errCode
= gnutls_credentials_set(session
.get(), GNUTLS_CRD_CERTIFICATE
, ctx
.get());
144 if (errCode
== GNUTLS_E_SUCCESS
) {
146 opts
.updateSessionOptions(session
);
148 // NP: GnuTLS does not yet support the BIO operations
149 // this does the equivalent of SSL_set_fd() for now.
150 gnutls_transport_set_int(session
.get(), fd
);
151 gnutls_handshake_set_timeout(session
.get(), GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT
);
152 #endif /* USE_GNUTLS */
154 debugs(83, 5, "link FD " << fd
<< " to TLS session=" << (void*)session
.get());
156 fd_table
[fd
].ssl
= session
;
157 fd_table
[fd
].useBufferedIo(&tls_read_method
, &tls_write_method
);
158 fd_note(fd
, squidCtx
);
163 errCode
= ERR_get_error();
164 errAction
= "failed to initialize I/O";
167 errAction
= "failed to assign credentials";
171 debugs(83, DBG_IMPORTANT
, "ERROR: " << squidCtx
<< ' ' << errAction
<<
172 ": " << (errCode
!= 0 ? Security::ErrorString(errCode
) : ""));
178 #endif /* USE_OPENSSL || USE_GNUTLS */
183 Security::CreateClientSession(const Security::ContextPointer
&ctx
, const Comm::ConnectionPointer
&c
, const char *squidCtx
)
185 if (!c
|| !c
->getPeer())
186 return CreateSession(ctx
, c
, Security::ProxyOutgoingConfig
, Security::Io::BIO_TO_SERVER
, squidCtx
);
188 auto *peer
= c
->getPeer();
189 return CreateSession(ctx
, c
, peer
->secure
, Security::Io::BIO_TO_SERVER
, squidCtx
);
193 Security::CreateServerSession(const Security::ContextPointer
&ctx
, const Comm::ConnectionPointer
&c
, Security::PeerOptions
&o
, const char *squidCtx
)
195 return CreateSession(ctx
, c
, o
, Security::Io::BIO_TO_CLIENT
, squidCtx
);
199 Security::SessionSendGoodbye(const Security::SessionPointer
&s
)
201 debugs(83, 5, "session=" << (void*)s
.get());
204 SSL_shutdown(s
.get());
206 gnutls_bye(s
.get(), GNUTLS_SHUT_RDWR
);
212 Security::SessionIsResumed(const Security::SessionPointer
&s
)
216 result
= SSL_session_reused(s
.get()) == 1;
218 result
= gnutls_session_is_resumed(s
.get()) != 0;
220 debugs(83, 7, "session=" << (void*)s
.get() << ", query? answer: " << (result
? 'T' : 'F') );
225 Security::MaybeGetSessionResumeData(const Security::SessionPointer
&s
, Security::SessionStatePointer
&data
)
227 if (!SessionIsResumed(s
)) {
229 // nil is valid for SSL_get1_session(), it cannot fail.
230 data
.reset(SSL_get1_session(s
.get()));
232 gnutls_datum_t
*tmp
= nullptr;
233 const auto x
= gnutls_session_get_data2(s
.get(), tmp
);
234 if (x
!= GNUTLS_E_SUCCESS
) {
235 debugs(83, 3, "session=" << (void*)s
.get() << " error: " << Security::ErrorString(x
));
239 debugs(83, 5, "session=" << (void*)s
.get() << " data=" << (void*)data
.get());
241 debugs(83, 5, "session=" << (void*)s
.get() << " data=" << (void*)data
.get() << ", do nothing.");
246 Security::SetSessionResumeData(const Security::SessionPointer
&s
, const Security::SessionStatePointer
&data
)
250 if (!SSL_set_session(s
.get(), data
.get())) {
251 const auto ssl_error
= ERR_get_error();
252 debugs(83, 3, "session=" << (void*)s
.get() << " data=" << (void*)data
.get() <<
253 " resume error: " << Security::ErrorString(ssl_error
));
256 const auto x
= gnutls_session_set_data(s
.get(), data
->data
, data
->size
);
257 if (x
!= GNUTLS_E_SUCCESS
) {
258 debugs(83, 3, "session=" << (void*)s
.get() << " data=" << (void*)data
.get() <<
259 " resume error: " << Security::ErrorString(x
));
262 // critical because, how did it get here?
263 debugs(83, DBG_CRITICAL
, "no TLS library. session=" << (void*)s
.get() << " data=" << (void*)data
.get());
265 debugs(83, 5, "session=" << (void*)s
.get() << " data=" << (void*)data
.get());
267 debugs(83, 5, "session=" << (void*)s
.get() << " no resume data");
274 for (AnyP::PortCfgPointer s
= HttpPortList
; s
!= nullptr; s
= s
->next
) {
275 if (s
->secure
.encryptTransport
)
277 if (s
->flags
.tunnelSslBumping
)
286 store_session_cb(SSL
*, SSL_SESSION
*session
)
291 debugs(83, 5, "Request to store SSL_SESSION");
293 SSL_SESSION_set_timeout(session
, Config
.SSL
.session_ttl
);
296 const unsigned char *id
= SSL_SESSION_get_id(session
, &idlen
);
297 // XXX: the other calls [to openForReading()] do not copy the sessionId to a char buffer, does this really have to?
298 unsigned char key
[MEMMAP_SLOT_KEY_SIZE
];
299 // Session ids are of size 32bytes. They should always fit to a
301 assert(idlen
<= MEMMAP_SLOT_KEY_SIZE
);
302 memset(key
, 0, sizeof(key
));
303 memcpy(key
, id
, idlen
);
305 if (auto slotW
= SessionCache
->openForWriting(static_cast<const cache_key
*>(key
), pos
)) {
306 int lenRequired
= i2d_SSL_SESSION(session
, nullptr);
307 if (lenRequired
< MEMMAP_SLOT_DATA_SIZE
) {
308 unsigned char *p
= static_cast<unsigned char *>(slotW
->p
);
309 lenRequired
= i2d_SSL_SESSION(session
, &p
);
310 slotW
->set(key
, nullptr, lenRequired
, squid_curtime
+ Config
.SSL
.session_ttl
);
312 SessionCache
->closeForWriting(pos
);
313 debugs(83, 5, "wrote an SSL_SESSION entry of size " << lenRequired
<< " at pos " << pos
);
319 remove_session_cb(SSL_CTX
*, SSL_SESSION
*sessionID
)
324 debugs(83, 5, "Request to remove corrupted or not valid SSL_SESSION");
326 if (SessionCache
->openForReading(reinterpret_cast<const cache_key
*>(sessionID
), pos
)) {
327 SessionCache
->closeForReading(pos
);
329 // What if we are not able to remove the session?
330 // Maybe schedule a job to remove it later?
331 // For now we just have an invalid entry in cache until will be expired
332 // The OpenSSL library will reject it when we try to use it
333 SessionCache
->free(pos
);
338 #if SQUID_USE_CONST_SSL_SESSION_CBID
339 get_session_cb(SSL
*, const unsigned char *sessionID
, int len
, int *copy
)
341 get_session_cb(SSL
*, unsigned char *sessionID
, int len
, int *copy
)
347 const unsigned int *p
= reinterpret_cast<const unsigned int *>(sessionID
);
348 debugs(83, 5, "Request to search for SSL_SESSION of len: " <<
349 len
<< p
[0] << ":" << p
[1]);
351 SSL_SESSION
*session
= nullptr;
353 if (const auto slot
= SessionCache
->openForReading(static_cast<const cache_key
*>(sessionID
), pos
)) {
354 if (slot
->expire
> squid_curtime
) {
355 const unsigned char *ptr
= slot
->p
;
356 session
= d2i_SSL_SESSION(nullptr, &ptr
, slot
->pSize
);
357 debugs(83, 5, "SSL_SESSION retrieved from cache at pos " << pos
);
359 debugs(83, 5, "SSL_SESSION in cache expired");
360 SessionCache
->closeForReading(pos
);
364 debugs(83, 5, "Failed to retrieve SSL_SESSION from cache");
366 // With the parameter copy the callback can require the SSL engine
367 // to increment the reference count of the SSL_SESSION object, Normally
368 // the reference count is not incremented and therefore the session must
369 // not be explicitly freed with SSL_SESSION_free(3).
375 Security::SetSessionCacheCallbacks(Security::ContextPointer
&ctx
)
378 SSL_CTX_set_session_cache_mode(ctx
.get(), SSL_SESS_CACHE_SERVER
|SSL_SESS_CACHE_NO_INTERNAL
);
379 SSL_CTX_sess_set_new_cb(ctx
.get(), store_session_cb
);
380 SSL_CTX_sess_set_remove_cb(ctx
.get(), remove_session_cb
);
381 SSL_CTX_sess_set_get_cb(ctx
.get(), get_session_cb
);
384 #endif /* USE_OPENSSL */
388 initializeSessionCache()
390 // Check if the MemMap keys and data are enough big to hold
391 // session ids and session data
392 assert(SSL_SESSION_ID_SIZE
>= MEMMAP_SLOT_KEY_SIZE
);
393 assert(SSL_SESSION_MAX_SIZE
>= MEMMAP_SLOT_DATA_SIZE
);
395 int configuredItems
= ::Config
.SSL
.sessionCacheSize
/ sizeof(Ipc::MemMap::Slot
);
396 if (IamWorkerProcess() && configuredItems
)
397 SessionCache
= new Ipc::MemMap(SessionCacheName
);
399 SessionCache
= nullptr;
403 for (AnyP::PortCfgPointer s
= HttpPortList
; s
!= nullptr; s
= s
->next
) {
404 if (s
->secure
.staticContext
)
405 Security::SetSessionCacheCallbacks(s
->secure
.staticContext
);
410 /// initializes shared memory segments used by MemStore
411 class SharedSessionCacheRr
: public Ipc::Mem::RegisteredRunner
414 /* RegisteredRunner API */
415 SharedSessionCacheRr(): owner(nullptr) {}
416 void useConfig() override
;
417 ~SharedSessionCacheRr() override
;
420 void create() override
;
423 Ipc::MemMap::Owner
*owner
;
426 RunnerRegistrationEntry(SharedSessionCacheRr
);
429 SharedSessionCacheRr::useConfig()
432 if (SessionCache
|| !isTlsServer()) // no need to configure SSL_SESSION* cache.
435 Ipc::Mem::RegisteredRunner::useConfig();
436 initializeSessionCache();
441 SharedSessionCacheRr::create()
443 if (!isTlsServer()) // no need to configure SSL_SESSION* cache.
447 if (int items
= Config
.SSL
.sessionCacheSize
/ sizeof(Ipc::MemMap::Slot
))
448 owner
= Ipc::MemMap::Init(SessionCacheName
, items
);
452 SharedSessionCacheRr::~SharedSessionCacheRr()
454 // XXX: Enable after testing to reduce at-exit memory "leaks".
455 // delete SessionCache;