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