]> git.ipfire.org Git - thirdparty/squid.git/blob - src/security/Session.cc
Implement GnuTLS session creation
[thirdparty/squid.git] / src / security / Session.cc
1 /*
2 * Copyright (C) 1996-2016 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 "Debug.h"
15 #include "fd.h"
16 #include "fde.h"
17 #include "ipc/MemMap.h"
18 #include "security/Session.h"
19 #include "SquidConfig.h"
20 #include "ssl/bio.h"
21
22 #define SSL_SESSION_ID_SIZE 32
23 #define SSL_SESSION_MAX_SIZE 10*1024
24
25 #if USE_OPENSSL || USE_GNUTLS
26 static int
27 tls_read_method(int fd, char *buf, int len)
28 {
29 auto session = fd_table[fd].ssl.get();
30
31 #if DONT_DO_THIS && USE_OPENSSL
32 if (!SSL_is_init_finished(session)) {
33 errno = ENOTCONN;
34 return -1;
35 }
36 #endif
37
38 #if USE_OPENSSL
39 int i = SSL_read(session, buf, len);
40 #elif USE_GNUTLS
41 int i = gnutls_record_recv(session, buf, len);
42 #endif
43 if (i > 0) {
44 debugs(83, 8, "TLS FD " << fd << " session=" << (void*)session << " " << i << " bytes");
45 (void)VALGRIND_MAKE_MEM_DEFINED(buf, i);
46 }
47
48 #if USE_OPENSSL
49 if (i > 0 && SSL_pending(session) > 0) {
50 #elif USE_GNUTLS
51 if (i > 0 && gnutls_record_check_pending(session) > 0) {
52 #endif
53 debugs(83, 2, "TLS FD " << fd << " is pending");
54 fd_table[fd].flags.read_pending = true;
55 } else
56 fd_table[fd].flags.read_pending = false;
57
58 return i;
59 }
60
61 static int
62 tls_write_method(int fd, const char *buf, int len)
63 {
64 auto session = fd_table[fd].ssl.get();
65
66 #if USE_OPENSSL
67 if (!SSL_is_init_finished(session)) {
68 errno = ENOTCONN;
69 return -1;
70 }
71 #endif
72
73 #if USE_OPENSSL
74 int i = SSL_write(session, buf, len);
75 #elif USE_GNUTLS
76 int i = gnutls_record_send(session, buf, len);
77 #endif
78
79 if (i > 0) {
80 debugs(83, 8, "TLS FD " << fd << " session=" << (void*)session << " " << i << " bytes");
81 }
82 return i;
83 }
84 #endif
85
86 static bool
87 CreateSession(const Security::ContextPointer &ctx, const Comm::ConnectionPointer &conn, Security::Io::Type type, const char *squidCtx)
88 {
89 if (!Comm::IsConnOpen(conn)) {
90 debugs(83, DBG_IMPORTANT, "Gone connection");
91 return false;
92 }
93
94 #if USE_OPENSSL || USE_GNUTLS
95
96 const char *errAction = "with no TLS/SSL library";
97 int errCode = 0;
98 #if USE_OPENSSL
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 if (!session) {
105 errCode = ERR_get_error();
106 errAction = "failed to allocate handle";
107 }
108 #elif USE_GNUTLS
109 gnutls_session_t tmp;
110 errCode = gnutls_init(&tmp, static_cast<unsigned int>(type));
111 Security::SessionPointer session(tmp, [](gnutls_session_t p) {
112 debugs(83, 5, "gnutls_deinit session=" << (void*)p);
113 gnutls_deinit(p); }
114 });
115 debugs(83, 5, "gnutls_init session=" << (void*)session.get());
116 if (errCode != GNUTLS_E_SUCCESS) {
117 session.reset();
118 errAction = "failed to initialize session";
119 }
120 #endif
121
122 if (session) {
123 const int fd = conn->fd;
124
125 #if USE_OPENSSL
126 // without BIO, we would call SSL_set_fd(ssl.get(), fd) instead
127 if (BIO *bio = Ssl::Bio::Create(fd, type)) {
128 Ssl::Bio::Link(session.get(), bio); // cannot fail
129 #elif USE_GNUTLS
130 errCode = gnutls_credentials_set(session.get(), GNUTLS_CRD_CERTIFICATE, ctx.get());
131 if (errCode == GNUTLS_E_SUCCESS) {
132 #endif
133
134 debugs(83, 5, "link FD " << fd << " to TLS session=" << (void*)session.get());
135 fd_table[fd].ssl = session;
136 fd_table[fd].read_method = &tls_read_method;
137 fd_table[fd].write_method = &tls_write_method;
138 fd_note(fd, squidCtx);
139 return true;
140 }
141
142 #if USE_OPENSSL
143 errCode = ERR_get_error();
144 errAction = "failed to initialize I/O";
145 #elif USE_GNUTLS
146 errAction = "failed to assign credentials";
147 #endif
148 }
149
150 debugs(83, DBG_IMPORTANT, "ERROR: " << squidCtx << ' ' << errAction <<
151 ": " << (errCode != 0 ? Security::ErrorString(errCode) : ""));
152 #endif
153 return false;
154 }
155
156 bool
157 Security::CreateClientSession(const Security::ContextPointer &ctx, const Comm::ConnectionPointer &c, const char *squidCtx)
158 {
159 return CreateSession(ctx, c, Security::Io::BIO_TO_SERVER, squidCtx);
160 }
161
162 bool
163 Security::CreateServerSession(const Security::ContextPointer &ctx, const Comm::ConnectionPointer &c, const char *squidCtx)
164 {
165 return CreateSession(ctx, c, Security::Io::BIO_TO_CLIENT, squidCtx);
166 }
167
168 void
169 Security::SessionClose(const Security::SessionPointer &s, const int fdOnError)
170 {
171 debugs(83, 5, "session=" << (void*)s.get());
172 if (s && fdOnError == -1) {
173 #if USE_OPENSSL
174 SSL_shutdown(s.get());
175 #elif USE_GNUTLS
176 gnutls_bye(s.get(), GNUTLS_SHUT_RDWR);
177 #endif
178 }
179
180 #if USE_GNUTLS
181 // XXX: should probably be done for OpenSSL too, but that needs testing.
182 if (fdOnError != -1) {
183 debugs(83, 5, "unlink FD " << fdOnError << " from TLS session=" << (void*)fd_table[fdOnError].ssl.get());
184 fd_table[fdOnError].ssl.reset();
185 fd_table[fdOnError].read_method = &default_read_method;
186 fd_table[fdOnError].write_method = &default_write_method;
187 fd_note(fdOnError, "TLS error");
188 }
189 #endif
190 }
191
192 bool
193 Security::SessionIsResumed(const Security::SessionPointer &s)
194 {
195 bool result = false;
196 #if USE_OPENSSL
197 result = SSL_session_reused(s.get()) == 1;
198 #elif USE_GNUTLS
199 result = gnutls_session_is_resumed(s.get()) != 0;
200 #endif
201 debugs(83, 7, "session=" << (void*)s.get() << ", query? answer: " << (result ? 'T' : 'F') );
202 return result;
203 }
204
205 void
206 Security::MaybeGetSessionResumeData(const Security::SessionPointer &s, Security::SessionStatePointer &data)
207 {
208 if (!SessionIsResumed(s)) {
209 #if USE_OPENSSL
210 // nil is valid for SSL_get1_session(), it cannot fail.
211 data.reset(SSL_get1_session(s.get()));
212 #elif USE_GNUTLS
213 gnutls_datum_t *tmp = nullptr;
214 const auto x = gnutls_session_get_data2(s.get(), tmp);
215 if (x != GNUTLS_E_SUCCESS) {
216 debugs(83, 3, "session=" << (void*)s.get() << " error: " << Security::ErrorString(x));
217 }
218 data.reset(tmp);
219 #endif
220 debugs(83, 5, "session=" << (void*)s.get() << " data=" << (void*)data.get());
221 } else {
222 debugs(83, 5, "session=" << (void*)s.get() << " data=" << (void*)data.get() << ", do nothing.");
223 }
224 }
225
226 void
227 Security::SetSessionResumeData(const Security::SessionPointer &s, const Security::SessionStatePointer &data)
228 {
229 if (data) {
230 #if USE_OPENSSL
231 if (!SSL_set_session(s.get(), data.get())) {
232 const auto ssl_error = ERR_get_error();
233 debugs(83, 3, "session=" << (void*)s.get() << " data=" << (void*)data.get() <<
234 " resume error: " << Security::ErrorString(ssl_error));
235 }
236 #elif USE_GNUTLS
237 const auto x = gnutls_session_set_data(s.get(), data->data, data->size);
238 if (x != GNUTLS_E_SUCCESS) {
239 debugs(83, 3, "session=" << (void*)s.get() << " data=" << (void*)data.get() <<
240 " resume error: " << Security::ErrorString(x));
241 }
242 #else
243 // critical because, how did it get here?
244 debugs(83, DBG_CRITICAL, "no TLS library. session=" << (void*)s.get() << " data=" << (void*)data.get());
245 #endif
246 debugs(83, 5, "session=" << (void*)s.get() << " data=" << (void*)data.get());
247 } else {
248 debugs(83, 5, "session=" << (void*)s.get() << " no resume data");
249 }
250 }
251
252 static bool
253 isTlsServer()
254 {
255 for (AnyP::PortCfgPointer s = HttpPortList; s != nullptr; s = s->next) {
256 if (s->secure.encryptTransport)
257 return true;
258 if (s->flags.tunnelSslBumping)
259 return true;
260 }
261
262 return false;
263 }
264
265 void
266 initializeSessionCache()
267 {
268 #if USE_OPENSSL
269 // Check if the MemMap keys and data are enough big to hold
270 // session ids and session data
271 assert(SSL_SESSION_ID_SIZE >= MEMMAP_SLOT_KEY_SIZE);
272 assert(SSL_SESSION_MAX_SIZE >= MEMMAP_SLOT_DATA_SIZE);
273
274 int configuredItems = ::Config.SSL.sessionCacheSize / sizeof(Ipc::MemMap::Slot);
275 if (IamWorkerProcess() && configuredItems)
276 Ssl::SessionCache = new Ipc::MemMap(Ssl::SessionCacheName);
277 else {
278 Ssl::SessionCache = nullptr;
279 return;
280 }
281
282 for (AnyP::PortCfgPointer s = HttpPortList; s != nullptr; s = s->next) {
283 if (s->secure.staticContext)
284 Ssl::SetSessionCallbacks(s->secure.staticContext);
285 }
286 #endif
287 }
288
289 /// initializes shared memory segments used by MemStore
290 class SharedSessionCacheRr: public Ipc::Mem::RegisteredRunner
291 {
292 public:
293 /* RegisteredRunner API */
294 SharedSessionCacheRr(): owner(nullptr) {}
295 virtual void useConfig();
296 virtual ~SharedSessionCacheRr();
297
298 protected:
299 virtual void create();
300
301 private:
302 Ipc::MemMap::Owner *owner;
303 };
304
305 RunnerRegistrationEntry(SharedSessionCacheRr);
306
307 void
308 SharedSessionCacheRr::useConfig()
309 {
310 #if USE_OPENSSL // while Ssl:: bits in use
311 if (Ssl::SessionCache || !isTlsServer()) //no need to configure ssl session cache.
312 return;
313
314 Ipc::Mem::RegisteredRunner::useConfig();
315 initializeSessionCache();
316 #endif
317 }
318
319 void
320 SharedSessionCacheRr::create()
321 {
322 if (!isTlsServer()) //no need to configure ssl session cache.
323 return;
324
325 #if USE_OPENSSL // while Ssl:: bits in use
326 if (int items = Config.SSL.sessionCacheSize / sizeof(Ipc::MemMap::Slot))
327 owner = Ipc::MemMap::Init(Ssl::SessionCacheName, items);
328 #endif
329 }
330
331 SharedSessionCacheRr::~SharedSessionCacheRr()
332 {
333 // XXX: Enable after testing to reduce at-exit memory "leaks".
334 // delete Ssl::SessionCache;
335
336 delete owner;
337 }
338