]>
Commit | Line | Data |
---|---|---|
a23223bf | 1 | /* |
5b74111a | 2 | * Copyright (C) 1996-2018 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()); |
2a268a06 | 169 | Ssl::ServerBio *bio = static_cast<Ssl::ServerBio *>(BIO_get_data(b)); |
36698640 CT |
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 | 191 | auto session = fd_table[fd].ssl.get(); |
9c8549cf | 192 | const int result = gnutls_handshake(session); |
3dc8e8ed | 193 | debugs(83, 5, "gnutls_handshake session=" << (void*)session << ", result=" << result); |
0d9f9284 | 194 | |
3dc8e8ed AJ |
195 | if (result == GNUTLS_E_SUCCESS) { |
196 | char *desc = gnutls_session_get_desc(session); | |
197 | debugs(83, 2, serverConnection() << " TLS Session info: " << desc); | |
198 | gnutls_free(desc); | |
199 | } | |
0d9f9284 | 200 | |
9c8549cf | 201 | if (result != GNUTLS_E_SUCCESS) { |
d193e738 | 202 | // debug the TLS session state so far |
e0f4cecb | 203 | auto descIn = gnutls_handshake_get_last_in(session); |
d193e738 | 204 | debugs(83, 2, "handshake IN: " << gnutls_handshake_description_get_name(descIn)); |
e0f4cecb | 205 | auto descOut = gnutls_handshake_get_last_out(session); |
d193e738 | 206 | debugs(83, 2, "handshake OUT: " << gnutls_handshake_description_get_name(descOut)); |
a72b6e88 | 207 | #else |
9c8549cf | 208 | if (const int result = -1) { |
a72b6e88 | 209 | #endif |
a23223bf CT |
210 | handleNegotiateError(result); |
211 | return; // we might be gone by now | |
212 | } | |
213 | ||
36698640 CT |
214 | recordNegotiationDetails(); |
215 | ||
c91d4d4e CT |
216 | if (!sslFinalized()) |
217 | return; | |
218 | ||
219 | callBack(); | |
220 | } | |
221 | ||
222 | bool | |
a72b6e88 | 223 | Security::PeerConnector::sslFinalized() |
c91d4d4e | 224 | { |
a72b6e88 | 225 | #if USE_OPENSSL |
1b091aec CT |
226 | if (Ssl::TheConfig.ssl_crt_validator && useCertValidator_) { |
227 | const int fd = serverConnection()->fd; | |
ad23e748 | 228 | Security::SessionPointer session(fd_table[fd].ssl); |
a23223bf | 229 | |
a23223bf | 230 | Ssl::CertValidationRequest validationRequest; |
9c8549cf AJ |
231 | // WARNING: Currently we do not use any locking for 'errors' member |
232 | // of the Ssl::CertValidationRequest class. In this code the | |
a23223bf CT |
233 | // Ssl::CertValidationRequest object used only to pass data to |
234 | // Ssl::CertValidationHelper::submit method. | |
9c8549cf | 235 | validationRequest.ssl = session; |
cb171ead CT |
236 | if (SBuf *dName = (SBuf *)SSL_get_ex_data(session.get(), ssl_ex_index_server)) |
237 | validationRequest.domainName = dName->c_str(); | |
ad23e748 | 238 | if (Security::CertErrors *errs = static_cast<Security::CertErrors *>(SSL_get_ex_data(session.get(), ssl_ex_index_ssl_errors))) |
a23223bf CT |
239 | // validationRequest disappears on return so no need to cbdataReference |
240 | validationRequest.errors = errs; | |
a23223bf CT |
241 | try { |
242 | debugs(83, 5, "Sending SSL certificate for validation to ssl_crtvd."); | |
a72b6e88 | 243 | AsyncCall::Pointer call = asyncCall(83,5, "Security::PeerConnector::sslCrtvdHandleReply", Ssl::CertValidationHelper::CbDialer(this, &Security::PeerConnector::sslCrtvdHandleReply, nullptr)); |
0e208dad | 244 | Ssl::CertValidationHelper::GetInstance()->sslSubmit(validationRequest, call); |
c91d4d4e | 245 | return false; |
a23223bf CT |
246 | } catch (const std::exception &e) { |
247 | debugs(83, DBG_IMPORTANT, "ERROR: Failed to compose ssl_crtvd " << | |
248 | "request for " << validationRequest.domainName << | |
249 | " certificate: " << e.what() << "; will now block to " << | |
250 | "validate that certificate."); | |
251 | // fall through to do blocking in-process generation. | |
252 | ErrorState *anErr = new ErrorState(ERR_GATEWAY_FAILURE, Http::scInternalServerError, request.getRaw()); | |
1b091aec CT |
253 | |
254 | noteNegotiationDone(anErr); | |
a23223bf | 255 | bail(anErr); |
a23223bf | 256 | serverConn->close(); |
c91d4d4e | 257 | return true; |
a23223bf CT |
258 | } |
259 | } | |
a72b6e88 | 260 | #endif |
1b091aec CT |
261 | |
262 | noteNegotiationDone(NULL); | |
c91d4d4e | 263 | return true; |
a23223bf CT |
264 | } |
265 | ||
a72b6e88 | 266 | #if USE_OPENSSL |
a23223bf | 267 | void |
a72b6e88 | 268 | Security::PeerConnector::sslCrtvdHandleReply(Ssl::CertValidationResponse::Pointer validationResponse) |
a23223bf | 269 | { |
0e208dad | 270 | Must(validationResponse != NULL); |
a23223bf | 271 | |
a23223bf CT |
272 | Ssl::ErrorDetail *errDetails = NULL; |
273 | bool validatorFailed = false; | |
274 | if (!Comm::IsConnOpen(serverConnection())) { | |
275 | return; | |
276 | } | |
277 | ||
19ed078f CT |
278 | if (Debug::Enabled(83, 5)) { |
279 | Security::SessionPointer ssl(fd_table[serverConnection()->fd].ssl); | |
280 | SBuf *server = static_cast<SBuf *>(SSL_get_ex_data(ssl.get(), ssl_ex_index_server)); | |
cb171ead | 281 | debugs(83,5, RawPointer("host", server) << " cert validation result: " << validationResponse->resultCode); |
19ed078f | 282 | } |
a23223bf | 283 | |
088f0761 | 284 | if (validationResponse->resultCode == ::Helper::Error) { |
92e3827b | 285 | if (Security::CertErrors *errs = sslCrtvdCheckForErrors(*validationResponse, errDetails)) { |
ad23e748 AJ |
286 | Security::SessionPointer session(fd_table[serverConnection()->fd].ssl); |
287 | Security::CertErrors *oldErrs = static_cast<Security::CertErrors*>(SSL_get_ex_data(session.get(), ssl_ex_index_ssl_errors)); | |
288 | SSL_set_ex_data(session.get(), ssl_ex_index_ssl_errors, (void *)errs); | |
088f0761 CT |
289 | delete oldErrs; |
290 | } | |
291 | } else if (validationResponse->resultCode != ::Helper::Okay) | |
a23223bf CT |
292 | validatorFailed = true; |
293 | ||
294 | if (!errDetails && !validatorFailed) { | |
1b091aec CT |
295 | noteNegotiationDone(NULL); |
296 | callBack(); | |
a23223bf CT |
297 | return; |
298 | } | |
299 | ||
300 | ErrorState *anErr = NULL; | |
301 | if (validatorFailed) { | |
302 | anErr = new ErrorState(ERR_GATEWAY_FAILURE, Http::scInternalServerError, request.getRaw()); | |
303 | } else { | |
a23223bf CT |
304 | anErr = new ErrorState(ERR_SECURE_CONNECT_FAIL, Http::scServiceUnavailable, request.getRaw()); |
305 | anErr->detail = errDetails; | |
306 | /*anErr->xerrno= Should preserved*/ | |
307 | } | |
308 | ||
1b091aec | 309 | noteNegotiationDone(anErr); |
a23223bf | 310 | bail(anErr); |
a23223bf CT |
311 | serverConn->close(); |
312 | return; | |
313 | } | |
a72b6e88 | 314 | #endif |
a23223bf | 315 | |
a72b6e88 | 316 | #if USE_OPENSSL |
a23223bf CT |
317 | /// Checks errors in the cert. validator response against sslproxy_cert_error. |
318 | /// The first honored error, if any, is returned via errDetails parameter. | |
92e3827b AJ |
319 | /// The method returns all seen errors except SSL_ERROR_NONE as Security::CertErrors. |
320 | Security::CertErrors * | |
a72b6e88 | 321 | Security::PeerConnector::sslCrtvdCheckForErrors(Ssl::CertValidationResponse const &resp, Ssl::ErrorDetail *& errDetails) |
a23223bf | 322 | { |
a23223bf | 323 | ACLFilledChecklist *check = NULL; |
d4ddb3e6 | 324 | if (acl_access *acl = ::Config.ssl_client.cert_error) { |
a23223bf | 325 | check = new ACLFilledChecklist(acl, request.getRaw(), dash_str); |
d4ddb3e6 CT |
326 | check->al = al; |
327 | } | |
a23223bf | 328 | |
92e3827b | 329 | Security::CertErrors *errs = nullptr; |
ad23e748 | 330 | Security::SessionPointer session(fd_table[serverConnection()->fd].ssl); |
a23223bf CT |
331 | typedef Ssl::CertValidationResponse::RecvdErrors::const_iterator SVCRECI; |
332 | for (SVCRECI i = resp.errors.begin(); i != resp.errors.end(); ++i) { | |
333 | debugs(83, 7, "Error item: " << i->error_no << " " << i->error_reason); | |
334 | ||
335 | assert(i->error_no != SSL_ERROR_NONE); | |
336 | ||
337 | if (!errDetails) { | |
338 | bool allowed = false; | |
339 | if (check) { | |
92e3827b | 340 | check->sslErrors = new Security::CertErrors(Security::CertError(i->error_no, i->cert, i->error_depth)); |
06bf5384 | 341 | if (check->fastCheck().allowed()) |
a23223bf CT |
342 | allowed = true; |
343 | } | |
344 | // else the Config.ssl_client.cert_error access list is not defined | |
345 | // and the first error will cause the error page | |
346 | ||
347 | if (allowed) { | |
348 | debugs(83, 3, "bypassing SSL error " << i->error_no << " in " << "buffer"); | |
349 | } else { | |
350 | debugs(83, 5, "confirming SSL error " << i->error_no); | |
351 | X509 *brokenCert = i->cert.get(); | |
ad23e748 | 352 | Security::CertPointer peerCert(SSL_get_peer_certificate(session.get())); |
a23223bf CT |
353 | const char *aReason = i->error_reason.empty() ? NULL : i->error_reason.c_str(); |
354 | errDetails = new Ssl::ErrorDetail(i->error_no, peerCert.get(), brokenCert, aReason); | |
355 | } | |
356 | if (check) { | |
357 | delete check->sslErrors; | |
358 | check->sslErrors = NULL; | |
359 | } | |
360 | } | |
361 | ||
362 | if (!errs) | |
92e3827b | 363 | errs = new Security::CertErrors(Security::CertError(i->error_no, i->cert, i->error_depth)); |
a23223bf | 364 | else |
92e3827b | 365 | errs->push_back_unique(Security::CertError(i->error_no, i->cert, i->error_depth)); |
a23223bf CT |
366 | } |
367 | if (check) | |
368 | delete check; | |
369 | ||
370 | return errs; | |
371 | } | |
a72b6e88 | 372 | #endif |
a23223bf CT |
373 | |
374 | /// A wrapper for Comm::SetSelect() notifications. | |
375 | void | |
a72b6e88 | 376 | Security::PeerConnector::NegotiateSsl(int, void *data) |
a23223bf | 377 | { |
0166128b | 378 | PeerConnector *pc = static_cast<Security::PeerConnector *>(data); |
a23223bf | 379 | // Use job calls to add done() checks and other job logic/protections. |
0166128b | 380 | CallJobHere(83, 7, pc, Security::PeerConnector, negotiate); |
a23223bf CT |
381 | } |
382 | ||
383 | void | |
a72b6e88 | 384 | Security::PeerConnector::handleNegotiateError(const int ret) |
a23223bf CT |
385 | { |
386 | const int fd = serverConnection()->fd; | |
9c8549cf AJ |
387 | const Security::SessionPointer session(fd_table[fd].ssl); |
388 | unsigned long ssl_lib_error = ret; | |
389 | ||
390 | #if USE_OPENSSL | |
ad23e748 | 391 | const int ssl_error = SSL_get_error(session.get(), ret); |
a23223bf | 392 | |
e2849af8 | 393 | switch (ssl_error) { |
e2849af8 | 394 | case SSL_ERROR_WANT_READ: |
1b091aec | 395 | noteWantRead(); |
e2849af8 | 396 | return; |
a23223bf | 397 | |
e2849af8 | 398 | case SSL_ERROR_WANT_WRITE: |
1b091aec | 399 | noteWantWrite(); |
e2849af8 | 400 | return; |
a23223bf | 401 | |
e2849af8 A |
402 | case SSL_ERROR_SSL: |
403 | case SSL_ERROR_SYSCALL: | |
404 | ssl_lib_error = ERR_get_error(); | |
1b091aec CT |
405 | // proceed to the general error handling code |
406 | break; | |
407 | default: | |
408 | // no special error handling for all other errors | |
9c8549cf | 409 | ssl_lib_error = SSL_ERROR_NONE; |
1b091aec CT |
410 | break; |
411 | } | |
36698640 | 412 | |
087b94cb | 413 | #elif USE_GNUTLS |
9c8549cf AJ |
414 | const int ssl_error = ret; |
415 | ||
416 | switch (ret) { | |
417 | case GNUTLS_E_WARNING_ALERT_RECEIVED: { | |
ed5f5120 SM |
418 | auto alert = gnutls_alert_get(session.get()); |
419 | debugs(83, DBG_IMPORTANT, "TLS ALERT: " << gnutls_alert_get_name(alert)); | |
420 | } | |
421 | // drop through to next case | |
9c8549cf AJ |
422 | |
423 | case GNUTLS_E_AGAIN: | |
424 | case GNUTLS_E_INTERRUPTED: | |
425 | if (gnutls_record_get_direction(session.get()) == 0) | |
9c8549cf | 426 | noteWantRead(); |
3dc8e8ed AJ |
427 | else |
428 | noteWantWrite(); | |
087b94cb | 429 | return; |
9c8549cf AJ |
430 | |
431 | default: | |
432 | // no special error handling for all other errors | |
433 | break; | |
087b94cb | 434 | } |
9c8549cf AJ |
435 | |
436 | #else | |
437 | // this avoids unused variable compiler warnings. | |
438 | Must(!session); | |
439 | const int ssl_error = ret; | |
a72b6e88 | 440 | #endif |
9c8549cf AJ |
441 | |
442 | // Log connection details, if any | |
443 | recordNegotiationDetails(); | |
444 | noteNegotiationError(ret, ssl_error, ssl_lib_error); | |
1b091aec | 445 | } |
a23223bf | 446 | |
1b091aec | 447 | void |
a72b6e88 | 448 | Security::PeerConnector::noteWantRead() |
1b091aec | 449 | { |
1b091aec | 450 | const int fd = serverConnection()->fd; |
ca2526bd | 451 | debugs(83, 5, serverConnection()); |
212e5aee | 452 | #if USE_OPENSSL |
ad23e748 AJ |
453 | Security::SessionPointer session(fd_table[fd].ssl); |
454 | BIO *b = SSL_get_rbio(session.get()); | |
2a268a06 | 455 | Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(BIO_get_data(b)); |
55369ae6 AR |
456 | if (srvBio->holdRead()) { |
457 | if (srvBio->gotHello()) { | |
458 | if (checkForMissingCertificates()) | |
459 | return; // Wait to download certificates before proceed. | |
460 | ||
461 | srvBio->holdRead(false); | |
168d2b30 | 462 | // schedule a negotiateSSl to allow openSSL parse received data |
212e5aee | 463 | Security::PeerConnector::NegotiateSsl(fd, this); |
55369ae6 AR |
464 | return; |
465 | } else if (srvBio->gotHelloFailed()) { | |
466 | srvBio->holdRead(false); | |
467 | debugs(83, DBG_IMPORTANT, "Error parsing SSL Server Hello Message on FD " << fd); | |
168d2b30 | 468 | // schedule a negotiateSSl to allow openSSL parse received data |
212e5aee | 469 | Security::PeerConnector::NegotiateSsl(fd, this); |
55369ae6 AR |
470 | return; |
471 | } | |
472 | } | |
212e5aee | 473 | #endif |
55369ae6 | 474 | setReadTimeout(); |
1b091aec CT |
475 | Comm::SetSelect(fd, COMM_SELECT_READ, &NegotiateSsl, this, 0); |
476 | } | |
7f4e9b73 | 477 | |
1b091aec | 478 | void |
a72b6e88 | 479 | Security::PeerConnector::noteWantWrite() |
1b091aec CT |
480 | { |
481 | const int fd = serverConnection()->fd; | |
ca2526bd | 482 | debugs(83, 5, serverConnection()); |
1b091aec CT |
483 | Comm::SetSelect(fd, COMM_SELECT_WRITE, &NegotiateSsl, this, 0); |
484 | return; | |
485 | } | |
a23223bf | 486 | |
1b091aec | 487 | void |
0166128b | 488 | Security::PeerConnector::noteNegotiationError(const int ret, const int ssl_error, const int ssl_lib_error) |
1b091aec | 489 | { |
a72b6e88 | 490 | #if defined(EPROTO) |
1b091aec CT |
491 | int sysErrNo = EPROTO; |
492 | #else | |
493 | int sysErrNo = EACCES; | |
494 | #endif | |
a23223bf | 495 | |
9c8549cf | 496 | #if USE_OPENSSL |
1b091aec CT |
497 | // store/report errno when ssl_error is SSL_ERROR_SYSCALL, ssl_lib_error is 0, and ret is -1 |
498 | if (ssl_error == SSL_ERROR_SYSCALL && ret == -1 && ssl_lib_error == 0) | |
499 | sysErrNo = errno; | |
9c8549cf | 500 | #endif |
ac139956 | 501 | int xerr = errno; |
a23223bf | 502 | |
1b091aec | 503 | const int fd = serverConnection()->fd; |
9c8549cf | 504 | debugs(83, DBG_IMPORTANT, "ERROR: negotiating TLS on FD " << fd << |
ea574635 | 505 | ": " << Security::ErrorString(ssl_lib_error) << " (" << |
ac139956 | 506 | ssl_error << "/" << ret << "/" << xerr << ")"); |
a23223bf | 507 | |
4c7c97f3 | 508 | ErrorState *anErr = ErrorState::NewForwarding(ERR_SECURE_CONNECT_FAIL, request); |
a23223bf CT |
509 | anErr->xerrno = sysErrNo; |
510 | ||
9c8549cf | 511 | #if USE_OPENSSL |
ad23e748 AJ |
512 | Security::SessionPointer session(fd_table[fd].ssl); |
513 | Ssl::ErrorDetail *errFromFailure = static_cast<Ssl::ErrorDetail *>(SSL_get_ex_data(session.get(), ssl_ex_index_ssl_error_detail)); | |
a23223bf CT |
514 | if (errFromFailure != NULL) { |
515 | // The errFromFailure is attached to the ssl object | |
516 | // and will be released when ssl object destroyed. | |
517 | // Copy errFromFailure to a new Ssl::ErrorDetail object | |
518 | anErr->detail = new Ssl::ErrorDetail(*errFromFailure); | |
519 | } else { | |
520 | // server_cert can be NULL here | |
ad23e748 | 521 | X509 *server_cert = SSL_get_peer_certificate(session.get()); |
a23223bf CT |
522 | anErr->detail = new Ssl::ErrorDetail(SQUID_ERR_SSL_HANDSHAKE, server_cert, NULL); |
523 | X509_free(server_cert); | |
524 | } | |
525 | ||
526 | if (ssl_lib_error != SSL_ERROR_NONE) | |
527 | anErr->detail->setLibError(ssl_lib_error); | |
9c8549cf | 528 | #endif |
a23223bf | 529 | |
1b091aec | 530 | noteNegotiationDone(anErr); |
a23223bf CT |
531 | bail(anErr); |
532 | } | |
533 | ||
534 | void | |
a72b6e88 | 535 | Security::PeerConnector::bail(ErrorState *error) |
a23223bf CT |
536 | { |
537 | Must(error); // or the recepient will not know there was a problem | |
a23223bf CT |
538 | Must(callback != NULL); |
539 | CbDialer *dialer = dynamic_cast<CbDialer*>(callback->getDialer()); | |
540 | Must(dialer); | |
541 | dialer->answer().error = error; | |
542 | ||
543 | callBack(); | |
544 | // Our job is done. The callabck recepient will probably close the failed | |
545 | // peer connection and try another peer or go direct (if possible). We | |
546 | // can close the connection ourselves (our error notification would reach | |
547 | // the recepient before the fd-closure notification), but we would rather | |
548 | // minimize the number of fd-closure notifications and let the recepient | |
549 | // manage the TCP state of the connection. | |
550 | } | |
551 | ||
552 | void | |
a72b6e88 | 553 | Security::PeerConnector::callBack() |
a23223bf | 554 | { |
9c8549cf AJ |
555 | debugs(83, 5, "TLS setup ended for " << serverConnection()); |
556 | ||
a23223bf CT |
557 | AsyncCall::Pointer cb = callback; |
558 | // Do this now so that if we throw below, swanSong() assert that we _tried_ | |
559 | // to call back holds. | |
560 | callback = NULL; // this should make done() true | |
561 | ||
562 | // remove close handler | |
563 | comm_remove_close_handler(serverConnection()->fd, closeHandler); | |
564 | ||
565 | CbDialer *dialer = dynamic_cast<CbDialer*>(cb->getDialer()); | |
566 | Must(dialer); | |
567 | dialer->answer().conn = serverConnection(); | |
568 | ScheduleCallHere(cb); | |
569 | } | |
570 | ||
a23223bf | 571 | void |
a72b6e88 | 572 | Security::PeerConnector::swanSong() |
a23223bf CT |
573 | { |
574 | // XXX: unregister fd-closure monitoring and CommSetSelect interest, if any | |
575 | AsyncJob::swanSong(); | |
566f8310 AR |
576 | if (callback != NULL) { // paranoid: we have left the caller waiting |
577 | debugs(83, DBG_IMPORTANT, "BUG: Unexpected state while connecting to a cache_peer or origin server"); | |
578 | ErrorState *anErr = new ErrorState(ERR_GATEWAY_FAILURE, Http::scInternalServerError, request.getRaw()); | |
579 | bail(anErr); | |
580 | assert(!callback); | |
581 | return; | |
582 | } | |
a23223bf CT |
583 | } |
584 | ||
585 | const char * | |
a72b6e88 | 586 | Security::PeerConnector::status() const |
a23223bf CT |
587 | { |
588 | static MemBuf buf; | |
589 | buf.reset(); | |
590 | ||
591 | // TODO: redesign AsyncJob::status() API to avoid this | |
592 | // id and stop reason reporting duplication. | |
593 | buf.append(" [", 2); | |
594 | if (stopReason != NULL) { | |
07e6d76e AJ |
595 | buf.append("Stopped, reason:", 16); |
596 | buf.appendf("%s",stopReason); | |
a23223bf CT |
597 | } |
598 | if (serverConn != NULL) | |
07e6d76e | 599 | buf.appendf(" FD %d", serverConn->fd); |
f2e41480 | 600 | buf.appendf(" %s%u]", id.prefix(), id.value); |
a23223bf CT |
601 | buf.terminate(); |
602 | ||
603 | return buf.content(); | |
604 | } | |
605 | ||
212e5aee | 606 | #if USE_OPENSSL |
6cae08c9 | 607 | /// CallDialer to allow use Downloader objects within PeerConnector class. |
4cab96c5 | 608 | class PeerConnectorCertDownloaderDialer: public Downloader::CbDialer |
55369ae6 AR |
609 | { |
610 | public: | |
212e5aee | 611 | typedef void (Security::PeerConnector::*Method)(SBuf &object, int status); |
55369ae6 | 612 | |
212e5aee | 613 | PeerConnectorCertDownloaderDialer(Method method, Security::PeerConnector *pc): |
55369ae6 AR |
614 | method_(method), |
615 | peerConnector_(pc) {} | |
616 | ||
617 | /* CallDialer API */ | |
618 | virtual bool canDial(AsyncCall &call) { return peerConnector_.valid(); } | |
4cab96c5 | 619 | virtual void dial(AsyncCall &call) { ((&(*peerConnector_))->*method_)(object, status); } |
212e5aee CT |
620 | Method method_; ///< The Security::PeerConnector method to dial |
621 | CbcPointer<Security::PeerConnector> peerConnector_; ///< The Security::PeerConnector object | |
55369ae6 AR |
622 | }; |
623 | ||
624 | void | |
212e5aee | 625 | Security::PeerConnector::startCertDownloading(SBuf &url) |
55369ae6 AR |
626 | { |
627 | AsyncCall::Pointer certCallback = asyncCall(81, 4, | |
3945c91d SM |
628 | "Security::PeerConnector::certDownloadingDone", |
629 | PeerConnectorCertDownloaderDialer(&Security::PeerConnector::certDownloadingDone, this)); | |
55369ae6 | 630 | |
19ed078f | 631 | const Downloader *csd = (request ? dynamic_cast<const Downloader*>(request->downloader.valid()) : nullptr); |
5ceaee75 | 632 | Downloader *dl = new Downloader(url, certCallback, XactionInitiator::initCertFetcher, csd ? csd->nestedLevel() + 1 : 1); |
55369ae6 AR |
633 | AsyncJob::Start(dl); |
634 | } | |
635 | ||
636 | void | |
212e5aee | 637 | Security::PeerConnector::certDownloadingDone(SBuf &obj, int downloadStatus) |
55369ae6 | 638 | { |
4b5ea8a6 | 639 | ++certsDownloads; |
7b4984f7 | 640 | debugs(81, 5, "Certificate downloading status: " << downloadStatus << " certificate size: " << obj.length()); |
55369ae6 | 641 | |
168d2b30 | 642 | // get ServerBio from SSL object |
55369ae6 | 643 | const int fd = serverConnection()->fd; |
ad23e748 AJ |
644 | Security::SessionPointer session(fd_table[fd].ssl); |
645 | BIO *b = SSL_get_rbio(session.get()); | |
2a268a06 | 646 | Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(BIO_get_data(b)); |
55369ae6 | 647 | |
25d0ea14 | 648 | // Parse Certificate. Assume that it is in DER format. |
c31381d0 CT |
649 | // According to RFC 4325: |
650 | // The server must provide a DER encoded certificate or a collection | |
651 | // collection of certificates in a "certs-only" CMS message. | |
652 | // The applications MUST accept DER encoded certificates and SHOULD | |
653 | // be able to accept collection of certificates. | |
654 | // TODO: support collection of certificates | |
55369ae6 AR |
655 | const unsigned char *raw = (const unsigned char*)obj.rawContent(); |
656 | if (X509 *cert = d2i_X509(NULL, &raw, obj.length())) { | |
657 | char buffer[1024]; | |
658 | debugs(81, 5, "Retrieved certificate: " << X509_NAME_oneline(X509_get_subject_name(cert), buffer, 1024)); | |
9fdbe165 | 659 | ContextPointer ctx(getTlsContext()); |
a34d1d2d | 660 | const Security::CertList &certsList = srvBio->serverCertificatesIfAny(); |
9fdbe165 | 661 | if (const char *issuerUri = Ssl::uriOfIssuerIfMissing(cert, certsList, ctx)) { |
55369ae6 AR |
662 | urlsOfMissingCerts.push(SBuf(issuerUri)); |
663 | } | |
ad23e748 | 664 | Ssl::SSL_add_untrusted_cert(session.get(), cert); |
55369ae6 AR |
665 | } |
666 | ||
4b5ea8a6 CT |
667 | // Check if there are URIs to download from and if yes start downloading |
668 | // the first in queue. | |
4e526b93 | 669 | if (urlsOfMissingCerts.size() && certsDownloads <= MaxCertsDownloads) { |
55369ae6 AR |
670 | startCertDownloading(urlsOfMissingCerts.front()); |
671 | urlsOfMissingCerts.pop(); | |
672 | return; | |
673 | } | |
674 | ||
675 | srvBio->holdRead(false); | |
212e5aee | 676 | Security::PeerConnector::NegotiateSsl(serverConnection()->fd, this); |
55369ae6 AR |
677 | } |
678 | ||
679 | bool | |
212e5aee | 680 | Security::PeerConnector::checkForMissingCertificates() |
55369ae6 | 681 | { |
4e526b93 CT |
682 | // Check for nested SSL certificates downloads. For example when the |
683 | // certificate located in an SSL site which requires to download a | |
168d2b30 | 684 | // a missing certificate (... from an SSL site which requires to ...). |
cda7024f | 685 | |
19ed078f | 686 | const Downloader *csd = (request ? request->downloader.get() : nullptr); |
4e526b93 CT |
687 | if (csd && csd->nestedLevel() >= MaxNestedDownloads) |
688 | return false; | |
689 | ||
55369ae6 | 690 | const int fd = serverConnection()->fd; |
ad23e748 AJ |
691 | Security::SessionPointer session(fd_table[fd].ssl); |
692 | BIO *b = SSL_get_rbio(session.get()); | |
2a268a06 | 693 | Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(BIO_get_data(b)); |
a34d1d2d | 694 | const Security::CertList &certs = srvBio->serverCertificatesIfAny(); |
55369ae6 | 695 | |
a34d1d2d CT |
696 | if (certs.size()) { |
697 | debugs(83, 5, "SSL server sent " << certs.size() << " certificates"); | |
9fdbe165 CT |
698 | ContextPointer ctx(getTlsContext()); |
699 | Ssl::missingChainCertificatesUrls(urlsOfMissingCerts, certs, ctx); | |
55369ae6 AR |
700 | if (urlsOfMissingCerts.size()) { |
701 | startCertDownloading(urlsOfMissingCerts.front()); | |
702 | urlsOfMissingCerts.pop(); | |
703 | return true; | |
704 | } | |
705 | } | |
706 | ||
707 | return false; | |
708 | } | |
212e5aee | 709 | #endif //USE_OPENSSL |
3945c91d | 710 |