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