]>
Commit | Line | Data |
---|---|---|
a23223bf | 1 | /* |
ef57eb7b | 2 | * Copyright (C) 1996-2016 The Squid Software Foundation and contributors |
a23223bf | 3 | * |
bbc27441 AJ |
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. | |
a23223bf CT |
7 | */ |
8 | ||
32f1ca3f | 9 | /* DEBUG: section 83 TLS Server/Peer negotiation */ |
bbc27441 | 10 | |
a23223bf CT |
11 | #include "squid.h" |
12 | #include "acl/FilledChecklist.h" | |
a23223bf | 13 | #include "comm/Loops.h" |
55369ae6 | 14 | #include "Downloader.h" |
a23223bf CT |
15 | #include "errorpage.h" |
16 | #include "fde.h" | |
54fb1cbf | 17 | #include "http/Stream.h" |
a23223bf | 18 | #include "HttpRequest.h" |
36698640 | 19 | #include "security/NegotiationHistory.h" |
a72b6e88 | 20 | #include "security/PeerConnector.h" |
7f4e9b73 | 21 | #include "SquidConfig.h" |
a72b6e88 | 22 | #if USE_OPENSSL |
31855516 | 23 | #include "ssl/bio.h" |
a23223bf CT |
24 | #include "ssl/cert_validate_message.h" |
25 | #include "ssl/Config.h" | |
a23223bf | 26 | #include "ssl/helper.h" |
a72b6e88 | 27 | #endif |
a23223bf | 28 | |
a72b6e88 | 29 | CBDATA_NAMESPACED_CLASS_INIT(Security, PeerConnector); |
a23223bf | 30 | |
a72b6e88 AJ |
31 | Security::PeerConnector::PeerConnector(const Comm::ConnectionPointer &aServerConn, AsyncCall::Pointer &aCallback, const AccessLogEntryPointer &alp, const time_t timeout) : |
32 | AsyncJob("Security::PeerConnector"), | |
f53969cc | 33 | serverConn(aServerConn), |
d4ddb3e6 | 34 | al(alp), |
f53969cc SM |
35 | callback(aCallback), |
36 | negotiationTimeout(timeout), | |
37 | startTime(squid_curtime), | |
54fb1cbf | 38 | useCertValidator_(true), |
4e526b93 | 39 | certsDownloads(0) |
a23223bf | 40 | { |
a72b6e88 | 41 | debugs(83, 5, "Security::PeerConnector constructed, this=" << (void*)this); |
a23223bf CT |
42 | // if this throws, the caller's cb dialer is not our CbDialer |
43 | Must(dynamic_cast<CbDialer*>(callback->getDialer())); | |
44 | } | |
45 | ||
a72b6e88 | 46 | Security::PeerConnector::~PeerConnector() |
a23223bf | 47 | { |
a72b6e88 | 48 | debugs(83, 5, "Security::PeerConnector destructed, this=" << (void*)this); |
a23223bf CT |
49 | } |
50 | ||
a72b6e88 | 51 | bool Security::PeerConnector::doneAll() const |
a23223bf CT |
52 | { |
53 | return (!callback || callback->canceled()) && AsyncJob::doneAll(); | |
54 | } | |
55 | ||
56 | /// Preps connection and SSL state. Calls negotiate(). | |
57 | void | |
a72b6e88 | 58 | Security::PeerConnector::start() |
a23223bf CT |
59 | { |
60 | AsyncJob::start(); | |
9c8549cf | 61 | debugs(83, 5, "this=" << (void*)this); |
a23223bf | 62 | |
eba8d9bb | 63 | Security::SessionPointer tmp; |
0166128b AJ |
64 | if (prepareSocket() && initialize(tmp)) |
65 | negotiate(); | |
ae3ac744 AJ |
66 | else |
67 | mustStop("Security::PeerConnector TLS socket initialize failed"); | |
a23223bf CT |
68 | } |
69 | ||
70 | void | |
a72b6e88 | 71 | Security::PeerConnector::commCloseHandler(const CommCloseCbParams ¶ms) |
a23223bf | 72 | { |
a72b6e88 AJ |
73 | debugs(83, 5, "FD " << params.fd << ", Security::PeerConnector=" << params.data); |
74 | connectionClosed("Security::PeerConnector::commCloseHandler"); | |
a23223bf CT |
75 | } |
76 | ||
77 | void | |
a72b6e88 | 78 | Security::PeerConnector::connectionClosed(const char *reason) |
a23223bf | 79 | { |
9c8549cf | 80 | debugs(83, 5, reason << " socket closed/closing. this=" << (void*)this); |
a23223bf CT |
81 | mustStop(reason); |
82 | callback = NULL; | |
83 | } | |
84 | ||
85 | bool | |
a72b6e88 | 86 | Security::PeerConnector::prepareSocket() |
a23223bf | 87 | { |
9c8549cf AJ |
88 | debugs(83, 5, serverConnection() << ", this=" << (void*)this); |
89 | if (!Comm::IsConnOpen(serverConnection()) || fd_table[serverConnection()->fd].closing()) { | |
a72b6e88 | 90 | connectionClosed("Security::PeerConnector::prepareSocket"); |
a23223bf CT |
91 | return false; |
92 | } | |
93 | ||
9c8549cf AJ |
94 | debugs(83, 5, serverConnection()); |
95 | ||
a23223bf | 96 | // watch for external connection closures |
a72b6e88 AJ |
97 | typedef CommCbMemFunT<Security::PeerConnector, CommCloseCbParams> Dialer; |
98 | closeHandler = JobCallback(9, 5, Dialer, this, Security::PeerConnector::commCloseHandler); | |
9c8549cf | 99 | comm_add_close_handler(serverConnection()->fd, closeHandler); |
a23223bf CT |
100 | return true; |
101 | } | |
102 | ||
eba8d9bb | 103 | bool |
0166128b | 104 | Security::PeerConnector::initialize(Security::SessionPointer &serverSession) |
a23223bf | 105 | { |
b23f5f9c | 106 | Security::ContextPointer ctx(getTlsContext()); |
9c8549cf | 107 | debugs(83, 5, serverConnection() << ", ctx=" << (void*)ctx.get()); |
a23223bf | 108 | |
2e0d4c02 | 109 | if (!ctx || !Security::CreateClientSession(ctx, serverConnection(), "server https start")) { |
ea574635 | 110 | const auto xerrno = errno; |
2e0d4c02 AJ |
111 | if (!ctx) { |
112 | debugs(83, DBG_IMPORTANT, "Error initializing TLS connection: No security context."); | |
113 | } // else CreateClientSession() did the appropriate debugs() already | |
a23223bf | 114 | ErrorState *anErr = new ErrorState(ERR_SOCKET_FAILURE, Http::scInternalServerError, request.getRaw()); |
ea574635 | 115 | anErr->xerrno = xerrno; |
1b091aec CT |
116 | noteNegotiationDone(anErr); |
117 | bail(anErr); | |
eba8d9bb | 118 | return false; |
a23223bf CT |
119 | } |
120 | ||
157c5ace | 121 | // A TLS/SSL session has now been created for the connection and stored in fd_table |
eba8d9bb | 122 | serverSession = fd_table[serverConnection()->fd].ssl; |
9c8549cf | 123 | debugs(83, 5, serverConnection() << ", session=" << (void*)serverSession.get()); |
157c5ace | 124 | |
2e0d4c02 | 125 | #if USE_OPENSSL |
a23223bf CT |
126 | // If CertValidation Helper used do not lookup checklist for errors, |
127 | // but keep a list of errors to send it to CertValidator | |
128 | if (!Ssl::TheConfig.ssl_crt_validator) { | |
129 | // Create the ACL check list now, while we have access to more info. | |
130 | // The list is used in ssl_verify_cb() and is freed in ssl_free(). | |
131 | if (acl_access *acl = ::Config.ssl_client.cert_error) { | |
132 | ACLFilledChecklist *check = new ACLFilledChecklist(acl, request.getRaw(), dash_str); | |
d4ddb3e6 | 133 | check->al = al; |
a23223bf | 134 | // check->fd(fd); XXX: need client FD here |
eba8d9bb | 135 | SSL_set_ex_data(serverSession.get(), ssl_ex_index_cert_error_check, check); |
a23223bf CT |
136 | } |
137 | } | |
2e0d4c02 | 138 | #endif |
a72b6e88 | 139 | |
eba8d9bb | 140 | return true; |
a23223bf CT |
141 | } |
142 | ||
8aec3e1b | 143 | void |
a72b6e88 | 144 | Security::PeerConnector::setReadTimeout() |
8aec3e1b CT |
145 | { |
146 | int timeToRead; | |
147 | if (negotiationTimeout) { | |
148 | const int timeUsed = squid_curtime - startTime; | |
149 | const int timeLeft = max(0, static_cast<int>(negotiationTimeout - timeUsed)); | |
150 | timeToRead = min(static_cast<int>(::Config.Timeout.read), timeLeft); | |
151 | } else | |
152 | timeToRead = ::Config.Timeout.read; | |
153 | AsyncCall::Pointer nil; | |
154 | commSetConnTimeout(serverConnection(), timeToRead, nil); | |
155 | } | |
156 | ||
36698640 | 157 | void |
a72b6e88 | 158 | Security::PeerConnector::recordNegotiationDetails() |
36698640 CT |
159 | { |
160 | const int fd = serverConnection()->fd; | |
ad23e748 | 161 | Security::SessionPointer session(fd_table[fd].ssl); |
36698640 CT |
162 | |
163 | // retrieve TLS server negotiated information if any | |
ad23e748 AJ |
164 | serverConnection()->tlsNegotiations()->retrieveNegotiatedInfo(session); |
165 | ||
166 | #if USE_OPENSSL | |
36698640 | 167 | // retrieve TLS parsed extra info |
ad23e748 | 168 | BIO *b = SSL_get_rbio(session.get()); |
36698640 CT |
169 | Ssl::ServerBio *bio = static_cast<Ssl::ServerBio *>(b->ptr); |
170 | if (const Security::TlsDetails::Pointer &details = bio->receivedHelloDetails()) | |
171 | serverConnection()->tlsNegotiations()->retrieveParsedInfo(details); | |
a72b6e88 | 172 | #endif |
36698640 CT |
173 | } |
174 | ||
a23223bf | 175 | void |
0166128b | 176 | Security::PeerConnector::negotiate() |
a23223bf | 177 | { |
0166128b | 178 | if (!Comm::IsConnOpen(serverConnection())) |
a23223bf CT |
179 | return; |
180 | ||
181 | const int fd = serverConnection()->fd; | |
0166128b AJ |
182 | if (fd_table[fd].closing()) |
183 | return; | |
184 | ||
185 | #if USE_OPENSSL | |
9c8549cf AJ |
186 | auto session = fd_table[fd].ssl.get(); |
187 | debugs(83, 5, "SSL_connect session=" << (void*)session); | |
188 | const int result = SSL_connect(session); | |
189 | if (result <= 0) { | |
087b94cb | 190 | #elif USE_GNUTLS |
9c8549cf AJ |
191 | auto session = fd_table[fd].ssl.get(); |
192 | debugs(83, 5, "gnutls_handshake session=" << (void*)session); | |
193 | const int result = gnutls_handshake(session); | |
194 | if (result != GNUTLS_E_SUCCESS) { | |
195 | debugs(83, 5, "gnutls_handshake session=" << (void*)session << ", result=" << result); | |
a72b6e88 | 196 | #else |
9c8549cf | 197 | if (const int result = -1) { |
a72b6e88 | 198 | #endif |
a23223bf CT |
199 | handleNegotiateError(result); |
200 | return; // we might be gone by now | |
201 | } | |
202 | ||
36698640 CT |
203 | recordNegotiationDetails(); |
204 | ||
c91d4d4e CT |
205 | if (!sslFinalized()) |
206 | return; | |
207 | ||
208 | callBack(); | |
209 | } | |
210 | ||
211 | bool | |
a72b6e88 | 212 | Security::PeerConnector::sslFinalized() |
c91d4d4e | 213 | { |
a72b6e88 | 214 | #if USE_OPENSSL |
1b091aec CT |
215 | if (Ssl::TheConfig.ssl_crt_validator && useCertValidator_) { |
216 | const int fd = serverConnection()->fd; | |
ad23e748 | 217 | Security::SessionPointer session(fd_table[fd].ssl); |
a23223bf | 218 | |
a23223bf | 219 | Ssl::CertValidationRequest validationRequest; |
9c8549cf AJ |
220 | // WARNING: Currently we do not use any locking for 'errors' member |
221 | // of the Ssl::CertValidationRequest class. In this code the | |
a23223bf CT |
222 | // Ssl::CertValidationRequest object used only to pass data to |
223 | // Ssl::CertValidationHelper::submit method. | |
9c8549cf | 224 | validationRequest.ssl = session; |
cb171ead CT |
225 | if (SBuf *dName = (SBuf *)SSL_get_ex_data(session.get(), ssl_ex_index_server)) |
226 | validationRequest.domainName = dName->c_str(); | |
ad23e748 | 227 | if (Security::CertErrors *errs = static_cast<Security::CertErrors *>(SSL_get_ex_data(session.get(), ssl_ex_index_ssl_errors))) |
a23223bf CT |
228 | // validationRequest disappears on return so no need to cbdataReference |
229 | validationRequest.errors = errs; | |
a23223bf CT |
230 | try { |
231 | debugs(83, 5, "Sending SSL certificate for validation to ssl_crtvd."); | |
a72b6e88 | 232 | AsyncCall::Pointer call = asyncCall(83,5, "Security::PeerConnector::sslCrtvdHandleReply", Ssl::CertValidationHelper::CbDialer(this, &Security::PeerConnector::sslCrtvdHandleReply, nullptr)); |
0e208dad | 233 | Ssl::CertValidationHelper::GetInstance()->sslSubmit(validationRequest, call); |
c91d4d4e | 234 | return false; |
a23223bf CT |
235 | } catch (const std::exception &e) { |
236 | debugs(83, DBG_IMPORTANT, "ERROR: Failed to compose ssl_crtvd " << | |
237 | "request for " << validationRequest.domainName << | |
238 | " certificate: " << e.what() << "; will now block to " << | |
239 | "validate that certificate."); | |
240 | // fall through to do blocking in-process generation. | |
241 | ErrorState *anErr = new ErrorState(ERR_GATEWAY_FAILURE, Http::scInternalServerError, request.getRaw()); | |
1b091aec CT |
242 | |
243 | noteNegotiationDone(anErr); | |
a23223bf | 244 | bail(anErr); |
a23223bf | 245 | serverConn->close(); |
c91d4d4e | 246 | return true; |
a23223bf CT |
247 | } |
248 | } | |
a72b6e88 | 249 | #endif |
1b091aec CT |
250 | |
251 | noteNegotiationDone(NULL); | |
c91d4d4e | 252 | return true; |
a23223bf CT |
253 | } |
254 | ||
a72b6e88 | 255 | #if USE_OPENSSL |
a23223bf | 256 | void |
a72b6e88 | 257 | Security::PeerConnector::sslCrtvdHandleReply(Ssl::CertValidationResponse::Pointer validationResponse) |
a23223bf | 258 | { |
0e208dad | 259 | Must(validationResponse != NULL); |
a23223bf | 260 | |
a23223bf CT |
261 | Ssl::ErrorDetail *errDetails = NULL; |
262 | bool validatorFailed = false; | |
263 | if (!Comm::IsConnOpen(serverConnection())) { | |
264 | return; | |
265 | } | |
266 | ||
19ed078f CT |
267 | if (Debug::Enabled(83, 5)) { |
268 | Security::SessionPointer ssl(fd_table[serverConnection()->fd].ssl); | |
269 | SBuf *server = static_cast<SBuf *>(SSL_get_ex_data(ssl.get(), ssl_ex_index_server)); | |
cb171ead | 270 | debugs(83,5, RawPointer("host", server) << " cert validation result: " << validationResponse->resultCode); |
19ed078f | 271 | } |
a23223bf | 272 | |
088f0761 | 273 | if (validationResponse->resultCode == ::Helper::Error) { |
92e3827b | 274 | if (Security::CertErrors *errs = sslCrtvdCheckForErrors(*validationResponse, errDetails)) { |
ad23e748 AJ |
275 | Security::SessionPointer session(fd_table[serverConnection()->fd].ssl); |
276 | Security::CertErrors *oldErrs = static_cast<Security::CertErrors*>(SSL_get_ex_data(session.get(), ssl_ex_index_ssl_errors)); | |
277 | SSL_set_ex_data(session.get(), ssl_ex_index_ssl_errors, (void *)errs); | |
088f0761 CT |
278 | delete oldErrs; |
279 | } | |
280 | } else if (validationResponse->resultCode != ::Helper::Okay) | |
a23223bf CT |
281 | validatorFailed = true; |
282 | ||
283 | if (!errDetails && !validatorFailed) { | |
1b091aec CT |
284 | noteNegotiationDone(NULL); |
285 | callBack(); | |
a23223bf CT |
286 | return; |
287 | } | |
288 | ||
289 | ErrorState *anErr = NULL; | |
290 | if (validatorFailed) { | |
291 | anErr = new ErrorState(ERR_GATEWAY_FAILURE, Http::scInternalServerError, request.getRaw()); | |
292 | } else { | |
a23223bf CT |
293 | anErr = new ErrorState(ERR_SECURE_CONNECT_FAIL, Http::scServiceUnavailable, request.getRaw()); |
294 | anErr->detail = errDetails; | |
295 | /*anErr->xerrno= Should preserved*/ | |
296 | } | |
297 | ||
1b091aec | 298 | noteNegotiationDone(anErr); |
a23223bf | 299 | bail(anErr); |
a23223bf CT |
300 | serverConn->close(); |
301 | return; | |
302 | } | |
a72b6e88 | 303 | #endif |
a23223bf | 304 | |
a72b6e88 | 305 | #if USE_OPENSSL |
a23223bf CT |
306 | /// Checks errors in the cert. validator response against sslproxy_cert_error. |
307 | /// The first honored error, if any, is returned via errDetails parameter. | |
92e3827b AJ |
308 | /// The method returns all seen errors except SSL_ERROR_NONE as Security::CertErrors. |
309 | Security::CertErrors * | |
a72b6e88 | 310 | Security::PeerConnector::sslCrtvdCheckForErrors(Ssl::CertValidationResponse const &resp, Ssl::ErrorDetail *& errDetails) |
a23223bf | 311 | { |
a23223bf | 312 | ACLFilledChecklist *check = NULL; |
d4ddb3e6 | 313 | if (acl_access *acl = ::Config.ssl_client.cert_error) { |
a23223bf | 314 | check = new ACLFilledChecklist(acl, request.getRaw(), dash_str); |
d4ddb3e6 CT |
315 | check->al = al; |
316 | } | |
a23223bf | 317 | |
92e3827b | 318 | Security::CertErrors *errs = nullptr; |
ad23e748 | 319 | Security::SessionPointer session(fd_table[serverConnection()->fd].ssl); |
a23223bf CT |
320 | typedef Ssl::CertValidationResponse::RecvdErrors::const_iterator SVCRECI; |
321 | for (SVCRECI i = resp.errors.begin(); i != resp.errors.end(); ++i) { | |
322 | debugs(83, 7, "Error item: " << i->error_no << " " << i->error_reason); | |
323 | ||
324 | assert(i->error_no != SSL_ERROR_NONE); | |
325 | ||
326 | if (!errDetails) { | |
327 | bool allowed = false; | |
328 | if (check) { | |
92e3827b | 329 | check->sslErrors = new Security::CertErrors(Security::CertError(i->error_no, i->cert, i->error_depth)); |
a23223bf CT |
330 | if (check->fastCheck() == ACCESS_ALLOWED) |
331 | allowed = true; | |
332 | } | |
333 | // else the Config.ssl_client.cert_error access list is not defined | |
334 | // and the first error will cause the error page | |
335 | ||
336 | if (allowed) { | |
337 | debugs(83, 3, "bypassing SSL error " << i->error_no << " in " << "buffer"); | |
338 | } else { | |
339 | debugs(83, 5, "confirming SSL error " << i->error_no); | |
340 | X509 *brokenCert = i->cert.get(); | |
ad23e748 | 341 | Security::CertPointer peerCert(SSL_get_peer_certificate(session.get())); |
a23223bf CT |
342 | const char *aReason = i->error_reason.empty() ? NULL : i->error_reason.c_str(); |
343 | errDetails = new Ssl::ErrorDetail(i->error_no, peerCert.get(), brokenCert, aReason); | |
344 | } | |
345 | if (check) { | |
346 | delete check->sslErrors; | |
347 | check->sslErrors = NULL; | |
348 | } | |
349 | } | |
350 | ||
351 | if (!errs) | |
92e3827b | 352 | errs = new Security::CertErrors(Security::CertError(i->error_no, i->cert, i->error_depth)); |
a23223bf | 353 | else |
92e3827b | 354 | errs->push_back_unique(Security::CertError(i->error_no, i->cert, i->error_depth)); |
a23223bf CT |
355 | } |
356 | if (check) | |
357 | delete check; | |
358 | ||
359 | return errs; | |
360 | } | |
a72b6e88 | 361 | #endif |
a23223bf CT |
362 | |
363 | /// A wrapper for Comm::SetSelect() notifications. | |
364 | void | |
a72b6e88 | 365 | Security::PeerConnector::NegotiateSsl(int, void *data) |
a23223bf | 366 | { |
0166128b | 367 | PeerConnector *pc = static_cast<Security::PeerConnector *>(data); |
a23223bf | 368 | // Use job calls to add done() checks and other job logic/protections. |
0166128b | 369 | CallJobHere(83, 7, pc, Security::PeerConnector, negotiate); |
a23223bf CT |
370 | } |
371 | ||
372 | void | |
a72b6e88 | 373 | Security::PeerConnector::handleNegotiateError(const int ret) |
a23223bf CT |
374 | { |
375 | const int fd = serverConnection()->fd; | |
9c8549cf AJ |
376 | const Security::SessionPointer session(fd_table[fd].ssl); |
377 | unsigned long ssl_lib_error = ret; | |
378 | ||
379 | #if USE_OPENSSL | |
ad23e748 | 380 | const int ssl_error = SSL_get_error(session.get(), ret); |
a23223bf | 381 | |
e2849af8 | 382 | switch (ssl_error) { |
e2849af8 | 383 | case SSL_ERROR_WANT_READ: |
1b091aec | 384 | noteWantRead(); |
e2849af8 | 385 | return; |
a23223bf | 386 | |
e2849af8 | 387 | case SSL_ERROR_WANT_WRITE: |
1b091aec | 388 | noteWantWrite(); |
e2849af8 | 389 | return; |
a23223bf | 390 | |
e2849af8 A |
391 | case SSL_ERROR_SSL: |
392 | case SSL_ERROR_SYSCALL: | |
393 | ssl_lib_error = ERR_get_error(); | |
1b091aec CT |
394 | // proceed to the general error handling code |
395 | break; | |
396 | default: | |
397 | // no special error handling for all other errors | |
9c8549cf | 398 | ssl_lib_error = SSL_ERROR_NONE; |
1b091aec CT |
399 | break; |
400 | } | |
36698640 | 401 | |
087b94cb | 402 | #elif USE_GNUTLS |
9c8549cf AJ |
403 | const int ssl_error = ret; |
404 | ||
405 | switch (ret) { | |
406 | case GNUTLS_E_WARNING_ALERT_RECEIVED: { | |
407 | auto alert = gnutls_alert_get(session.get()); | |
408 | debugs(83, DBG_IMPORTANT, "TLS ALERT: " << gnutls_alert_get_name(alert)); | |
409 | } | |
410 | // drop through to next case | |
411 | ||
412 | case GNUTLS_E_AGAIN: | |
413 | case GNUTLS_E_INTERRUPTED: | |
414 | if (gnutls_record_get_direction(session.get()) == 0) | |
415 | noteWantWrite(); | |
416 | else | |
417 | noteWantRead(); | |
087b94cb | 418 | return; |
9c8549cf AJ |
419 | |
420 | default: | |
421 | // no special error handling for all other errors | |
422 | break; | |
087b94cb | 423 | } |
9c8549cf AJ |
424 | |
425 | #else | |
426 | // this avoids unused variable compiler warnings. | |
427 | Must(!session); | |
428 | const int ssl_error = ret; | |
a72b6e88 | 429 | #endif |
9c8549cf AJ |
430 | |
431 | // Log connection details, if any | |
432 | recordNegotiationDetails(); | |
433 | noteNegotiationError(ret, ssl_error, ssl_lib_error); | |
1b091aec | 434 | } |
a23223bf | 435 | |
1b091aec | 436 | void |
a72b6e88 | 437 | Security::PeerConnector::noteWantRead() |
1b091aec | 438 | { |
1b091aec | 439 | const int fd = serverConnection()->fd; |
212e5aee | 440 | #if USE_OPENSSL |
ad23e748 AJ |
441 | Security::SessionPointer session(fd_table[fd].ssl); |
442 | BIO *b = SSL_get_rbio(session.get()); | |
55369ae6 AR |
443 | Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(b->ptr); |
444 | if (srvBio->holdRead()) { | |
445 | if (srvBio->gotHello()) { | |
446 | if (checkForMissingCertificates()) | |
447 | return; // Wait to download certificates before proceed. | |
448 | ||
449 | srvBio->holdRead(false); | |
168d2b30 | 450 | // schedule a negotiateSSl to allow openSSL parse received data |
212e5aee | 451 | Security::PeerConnector::NegotiateSsl(fd, this); |
55369ae6 AR |
452 | return; |
453 | } else if (srvBio->gotHelloFailed()) { | |
454 | srvBio->holdRead(false); | |
455 | debugs(83, DBG_IMPORTANT, "Error parsing SSL Server Hello Message on FD " << fd); | |
168d2b30 | 456 | // schedule a negotiateSSl to allow openSSL parse received data |
212e5aee | 457 | Security::PeerConnector::NegotiateSsl(fd, this); |
55369ae6 AR |
458 | return; |
459 | } | |
460 | } | |
212e5aee | 461 | #endif |
55369ae6 | 462 | setReadTimeout(); |
1b091aec CT |
463 | Comm::SetSelect(fd, COMM_SELECT_READ, &NegotiateSsl, this, 0); |
464 | } | |
7f4e9b73 | 465 | |
1b091aec | 466 | void |
a72b6e88 | 467 | Security::PeerConnector::noteWantWrite() |
1b091aec CT |
468 | { |
469 | const int fd = serverConnection()->fd; | |
470 | Comm::SetSelect(fd, COMM_SELECT_WRITE, &NegotiateSsl, this, 0); | |
471 | return; | |
472 | } | |
a23223bf | 473 | |
1b091aec | 474 | void |
0166128b | 475 | Security::PeerConnector::noteNegotiationError(const int ret, const int ssl_error, const int ssl_lib_error) |
1b091aec | 476 | { |
a72b6e88 | 477 | #if defined(EPROTO) |
1b091aec CT |
478 | int sysErrNo = EPROTO; |
479 | #else | |
480 | int sysErrNo = EACCES; | |
481 | #endif | |
a23223bf | 482 | |
9c8549cf | 483 | #if USE_OPENSSL |
1b091aec CT |
484 | // store/report errno when ssl_error is SSL_ERROR_SYSCALL, ssl_lib_error is 0, and ret is -1 |
485 | if (ssl_error == SSL_ERROR_SYSCALL && ret == -1 && ssl_lib_error == 0) | |
486 | sysErrNo = errno; | |
9c8549cf | 487 | #endif |
a23223bf | 488 | |
1b091aec | 489 | const int fd = serverConnection()->fd; |
9c8549cf | 490 | debugs(83, DBG_IMPORTANT, "ERROR: negotiating TLS on FD " << fd << |
ea574635 | 491 | ": " << Security::ErrorString(ssl_lib_error) << " (" << |
1b091aec | 492 | ssl_error << "/" << ret << "/" << errno << ")"); |
a23223bf | 493 | |
1b091aec CT |
494 | ErrorState *anErr = NULL; |
495 | if (request != NULL) | |
496 | anErr = ErrorState::NewForwarding(ERR_SECURE_CONNECT_FAIL, request.getRaw()); | |
497 | else | |
498 | anErr = new ErrorState(ERR_SECURE_CONNECT_FAIL, Http::scServiceUnavailable, NULL); | |
a23223bf CT |
499 | anErr->xerrno = sysErrNo; |
500 | ||
9c8549cf | 501 | #if USE_OPENSSL |
ad23e748 AJ |
502 | Security::SessionPointer session(fd_table[fd].ssl); |
503 | Ssl::ErrorDetail *errFromFailure = static_cast<Ssl::ErrorDetail *>(SSL_get_ex_data(session.get(), ssl_ex_index_ssl_error_detail)); | |
a23223bf CT |
504 | if (errFromFailure != NULL) { |
505 | // The errFromFailure is attached to the ssl object | |
506 | // and will be released when ssl object destroyed. | |
507 | // Copy errFromFailure to a new Ssl::ErrorDetail object | |
508 | anErr->detail = new Ssl::ErrorDetail(*errFromFailure); | |
509 | } else { | |
510 | // server_cert can be NULL here | |
ad23e748 | 511 | X509 *server_cert = SSL_get_peer_certificate(session.get()); |
a23223bf CT |
512 | anErr->detail = new Ssl::ErrorDetail(SQUID_ERR_SSL_HANDSHAKE, server_cert, NULL); |
513 | X509_free(server_cert); | |
514 | } | |
515 | ||
516 | if (ssl_lib_error != SSL_ERROR_NONE) | |
517 | anErr->detail->setLibError(ssl_lib_error); | |
9c8549cf | 518 | #endif |
a23223bf | 519 | |
1b091aec | 520 | noteNegotiationDone(anErr); |
a23223bf CT |
521 | bail(anErr); |
522 | } | |
523 | ||
524 | void | |
a72b6e88 | 525 | Security::PeerConnector::bail(ErrorState *error) |
a23223bf CT |
526 | { |
527 | Must(error); // or the recepient will not know there was a problem | |
a23223bf CT |
528 | Must(callback != NULL); |
529 | CbDialer *dialer = dynamic_cast<CbDialer*>(callback->getDialer()); | |
530 | Must(dialer); | |
531 | dialer->answer().error = error; | |
532 | ||
533 | callBack(); | |
534 | // Our job is done. The callabck recepient will probably close the failed | |
535 | // peer connection and try another peer or go direct (if possible). We | |
536 | // can close the connection ourselves (our error notification would reach | |
537 | // the recepient before the fd-closure notification), but we would rather | |
538 | // minimize the number of fd-closure notifications and let the recepient | |
539 | // manage the TCP state of the connection. | |
9c8549cf AJ |
540 | |
541 | #if USE_GNUTLS | |
542 | // but we do need to release the bad TLS related details in fd_table | |
543 | // ... or GnuTLS will SEGFAULT. | |
544 | const int fd = serverConnection()->fd; | |
545 | Security::SessionClose(fd_table[fd].ssl, fd); | |
546 | #endif | |
a23223bf CT |
547 | } |
548 | ||
549 | void | |
a72b6e88 | 550 | Security::PeerConnector::callBack() |
a23223bf | 551 | { |
9c8549cf AJ |
552 | debugs(83, 5, "TLS setup ended for " << serverConnection()); |
553 | ||
a23223bf CT |
554 | AsyncCall::Pointer cb = callback; |
555 | // Do this now so that if we throw below, swanSong() assert that we _tried_ | |
556 | // to call back holds. | |
557 | callback = NULL; // this should make done() true | |
558 | ||
559 | // remove close handler | |
560 | comm_remove_close_handler(serverConnection()->fd, closeHandler); | |
561 | ||
562 | CbDialer *dialer = dynamic_cast<CbDialer*>(cb->getDialer()); | |
563 | Must(dialer); | |
564 | dialer->answer().conn = serverConnection(); | |
565 | ScheduleCallHere(cb); | |
566 | } | |
567 | ||
a23223bf | 568 | void |
a72b6e88 | 569 | Security::PeerConnector::swanSong() |
a23223bf CT |
570 | { |
571 | // XXX: unregister fd-closure monitoring and CommSetSelect interest, if any | |
572 | AsyncJob::swanSong(); | |
566f8310 AR |
573 | if (callback != NULL) { // paranoid: we have left the caller waiting |
574 | debugs(83, DBG_IMPORTANT, "BUG: Unexpected state while connecting to a cache_peer or origin server"); | |
575 | ErrorState *anErr = new ErrorState(ERR_GATEWAY_FAILURE, Http::scInternalServerError, request.getRaw()); | |
576 | bail(anErr); | |
577 | assert(!callback); | |
578 | return; | |
579 | } | |
a23223bf CT |
580 | } |
581 | ||
582 | const char * | |
a72b6e88 | 583 | Security::PeerConnector::status() const |
a23223bf CT |
584 | { |
585 | static MemBuf buf; | |
586 | buf.reset(); | |
587 | ||
588 | // TODO: redesign AsyncJob::status() API to avoid this | |
589 | // id and stop reason reporting duplication. | |
590 | buf.append(" [", 2); | |
591 | if (stopReason != NULL) { | |
07e6d76e AJ |
592 | buf.append("Stopped, reason:", 16); |
593 | buf.appendf("%s",stopReason); | |
a23223bf CT |
594 | } |
595 | if (serverConn != NULL) | |
07e6d76e | 596 | buf.appendf(" FD %d", serverConn->fd); |
f2e41480 | 597 | buf.appendf(" %s%u]", id.prefix(), id.value); |
a23223bf CT |
598 | buf.terminate(); |
599 | ||
600 | return buf.content(); | |
601 | } | |
602 | ||
212e5aee | 603 | #if USE_OPENSSL |
6cae08c9 | 604 | /// CallDialer to allow use Downloader objects within PeerConnector class. |
4cab96c5 | 605 | class PeerConnectorCertDownloaderDialer: public Downloader::CbDialer |
55369ae6 AR |
606 | { |
607 | public: | |
212e5aee | 608 | typedef void (Security::PeerConnector::*Method)(SBuf &object, int status); |
55369ae6 | 609 | |
212e5aee | 610 | PeerConnectorCertDownloaderDialer(Method method, Security::PeerConnector *pc): |
55369ae6 AR |
611 | method_(method), |
612 | peerConnector_(pc) {} | |
613 | ||
614 | /* CallDialer API */ | |
615 | virtual bool canDial(AsyncCall &call) { return peerConnector_.valid(); } | |
4cab96c5 | 616 | virtual void dial(AsyncCall &call) { ((&(*peerConnector_))->*method_)(object, status); } |
212e5aee CT |
617 | Method method_; ///< The Security::PeerConnector method to dial |
618 | CbcPointer<Security::PeerConnector> peerConnector_; ///< The Security::PeerConnector object | |
55369ae6 AR |
619 | }; |
620 | ||
621 | void | |
212e5aee | 622 | Security::PeerConnector::startCertDownloading(SBuf &url) |
55369ae6 AR |
623 | { |
624 | AsyncCall::Pointer certCallback = asyncCall(81, 4, | |
3945c91d SM |
625 | "Security::PeerConnector::certDownloadingDone", |
626 | PeerConnectorCertDownloaderDialer(&Security::PeerConnector::certDownloadingDone, this)); | |
55369ae6 | 627 | |
19ed078f | 628 | const Downloader *csd = (request ? dynamic_cast<const Downloader*>(request->downloader.valid()) : nullptr); |
cda7024f | 629 | Downloader *dl = new Downloader(url, certCallback, csd ? csd->nestedLevel() + 1 : 1); |
55369ae6 AR |
630 | AsyncJob::Start(dl); |
631 | } | |
632 | ||
633 | void | |
212e5aee | 634 | Security::PeerConnector::certDownloadingDone(SBuf &obj, int downloadStatus) |
55369ae6 | 635 | { |
4b5ea8a6 | 636 | ++certsDownloads; |
7b4984f7 | 637 | debugs(81, 5, "Certificate downloading status: " << downloadStatus << " certificate size: " << obj.length()); |
55369ae6 | 638 | |
168d2b30 | 639 | // get ServerBio from SSL object |
55369ae6 | 640 | const int fd = serverConnection()->fd; |
ad23e748 AJ |
641 | Security::SessionPointer session(fd_table[fd].ssl); |
642 | BIO *b = SSL_get_rbio(session.get()); | |
55369ae6 AR |
643 | Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(b->ptr); |
644 | ||
25d0ea14 | 645 | // Parse Certificate. Assume that it is in DER format. |
c31381d0 CT |
646 | // According to RFC 4325: |
647 | // The server must provide a DER encoded certificate or a collection | |
648 | // collection of certificates in a "certs-only" CMS message. | |
649 | // The applications MUST accept DER encoded certificates and SHOULD | |
650 | // be able to accept collection of certificates. | |
651 | // TODO: support collection of certificates | |
55369ae6 AR |
652 | const unsigned char *raw = (const unsigned char*)obj.rawContent(); |
653 | if (X509 *cert = d2i_X509(NULL, &raw, obj.length())) { | |
654 | char buffer[1024]; | |
655 | debugs(81, 5, "Retrieved certificate: " << X509_NAME_oneline(X509_get_subject_name(cert), buffer, 1024)); | |
a34d1d2d | 656 | const Security::CertList &certsList = srvBio->serverCertificatesIfAny(); |
55369ae6 AR |
657 | if (const char *issuerUri = Ssl::uriOfIssuerIfMissing(cert, certsList)) { |
658 | urlsOfMissingCerts.push(SBuf(issuerUri)); | |
659 | } | |
ad23e748 | 660 | Ssl::SSL_add_untrusted_cert(session.get(), cert); |
55369ae6 AR |
661 | } |
662 | ||
4b5ea8a6 CT |
663 | // Check if there are URIs to download from and if yes start downloading |
664 | // the first in queue. | |
4e526b93 | 665 | if (urlsOfMissingCerts.size() && certsDownloads <= MaxCertsDownloads) { |
55369ae6 AR |
666 | startCertDownloading(urlsOfMissingCerts.front()); |
667 | urlsOfMissingCerts.pop(); | |
668 | return; | |
669 | } | |
670 | ||
671 | srvBio->holdRead(false); | |
212e5aee | 672 | Security::PeerConnector::NegotiateSsl(serverConnection()->fd, this); |
55369ae6 AR |
673 | } |
674 | ||
675 | bool | |
212e5aee | 676 | Security::PeerConnector::checkForMissingCertificates() |
55369ae6 | 677 | { |
4e526b93 CT |
678 | // Check for nested SSL certificates downloads. For example when the |
679 | // certificate located in an SSL site which requires to download a | |
168d2b30 | 680 | // a missing certificate (... from an SSL site which requires to ...). |
cda7024f | 681 | |
19ed078f | 682 | const Downloader *csd = (request ? request->downloader.get() : nullptr); |
4e526b93 CT |
683 | if (csd && csd->nestedLevel() >= MaxNestedDownloads) |
684 | return false; | |
685 | ||
55369ae6 | 686 | const int fd = serverConnection()->fd; |
ad23e748 AJ |
687 | Security::SessionPointer session(fd_table[fd].ssl); |
688 | BIO *b = SSL_get_rbio(session.get()); | |
55369ae6 | 689 | Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(b->ptr); |
a34d1d2d | 690 | const Security::CertList &certs = srvBio->serverCertificatesIfAny(); |
55369ae6 | 691 | |
a34d1d2d CT |
692 | if (certs.size()) { |
693 | debugs(83, 5, "SSL server sent " << certs.size() << " certificates"); | |
55369ae6 AR |
694 | Ssl::missingChainCertificatesUrls(urlsOfMissingCerts, certs); |
695 | if (urlsOfMissingCerts.size()) { | |
696 | startCertDownloading(urlsOfMissingCerts.front()); | |
697 | urlsOfMissingCerts.pop(); | |
698 | return true; | |
699 | } | |
700 | } | |
701 | ||
702 | return false; | |
703 | } | |
212e5aee | 704 | #endif //USE_OPENSSL |
3945c91d | 705 |