PeerConnector.cc
Go to the documentation of this file.
1 /*
2  * Copyright (C) 1996-2021 The Squid Software Foundation and contributors
3  *
4  * Squid software is distributed under GPLv2+ license and includes
5  * contributions from numerous individuals and organizations.
6  * Please see the COPYING and CONTRIBUTORS files for details.
7  */
8 
9 /* DEBUG: section 83 TLS Server/Peer negotiation */
10 
11 #include "squid.h"
12 #include "acl/FilledChecklist.h"
13 #include "comm/Loops.h"
14 #include "comm/Read.h"
15 #include "Downloader.h"
16 #include "errorpage.h"
17 #include "fde.h"
18 #include "FwdState.h"
19 #include "http/Stream.h"
20 #include "HttpRequest.h"
21 #include "neighbors.h"
22 #include "pconn.h"
23 #include "security/Io.h"
25 #include "security/PeerConnector.h"
26 #include "SquidConfig.h"
27 #if USE_OPENSSL
28 #include "ssl/bio.h"
30 #include "ssl/Config.h"
31 #include "ssl/helper.h"
32 #endif
33 
35 
36 Security::PeerConnector::PeerConnector(const Comm::ConnectionPointer &aServerConn, AsyncCall::Pointer &aCallback, const AccessLogEntryPointer &alp, const time_t timeout) :
37  AsyncJob("Security::PeerConnector"),
38  noteFwdPconnUse(false),
39  serverConn(aServerConn),
40  al(alp),
41  callback(aCallback),
42  negotiationTimeout(timeout),
43  startTime(squid_curtime),
44  useCertValidator_(true),
45  certsDownloads(0)
46 {
47  debugs(83, 5, serverConn);
48 
49  // if this throws, the caller's cb dialer is not our CbDialer
50  Must(dynamic_cast<CbDialer*>(callback->getDialer()));
51 
52  // watch for external connection closures
54  Must(!fd_table[serverConn->fd].closing());
58 }
59 
61 
63 {
64  return (!callback || callback->canceled()) && AsyncJob::doneAll();
65 }
66 
68 void
70 {
72  debugs(83, 5, "this=" << (void*)this);
73 
74  // we own this Comm::Connection object and its fd exclusively, but must bail
75  // if others started closing the socket while we were waiting to start()
76  assert(Comm::IsConnOpen(serverConn));
77  if (fd_table[serverConn->fd].closing()) {
78  bail(new ErrorState(ERR_CONNECT_FAIL, Http::scBadGateway, request.getRaw(), al));
79  return;
80  }
81 
83  if (initialize(tmp))
84  negotiate();
85  else
86  mustStop("Security::PeerConnector TLS socket initialize failed");
87 }
88 
89 void
91 {
92  if (!checklist.al)
93  checklist.al = al;
94  checklist.syncAle(request.getRaw(), nullptr);
95  // checklist.fd(fd); XXX: need client FD here
96 
97 #if USE_OPENSSL
98  if (!checklist.serverCert) {
99  if (const auto session = fd_table[serverConnection()->fd].ssl.get())
100  checklist.serverCert.resetWithoutLocking(SSL_get_peer_certificate(session));
101  }
102 #else
103  // checklist.serverCert is not maintained in other builds
104 #endif
105 }
106 
107 void
109 {
110  debugs(83, 5, "FD " << params.fd << ", Security::PeerConnector=" << params.data);
111 
112  closeHandler = nullptr;
113  if (serverConn) {
114  countFailingConnection();
115  serverConn->noteClosure();
116  serverConn = nullptr;
117  }
118 
119  const auto err = new ErrorState(ERR_SECURE_CONNECT_FAIL, Http::scServiceUnavailable, request.getRaw(), al);
120  static const auto d = MakeNamedErrorDetail("TLS_CONNECT_CLOSE");
121  err->detailError(d);
122  bail(err);
123 }
124 
125 void
127 {
128  debugs(83, 5, serverConnection() << " timedout. this=" << (void*)this);
129  const auto err = new ErrorState(ERR_SECURE_CONNECT_FAIL, Http::scGatewayTimeout, request.getRaw(), al);
130  static const auto d = MakeNamedErrorDetail("TLS_CONNECT_TIMEOUT");
131  err->detailError(d);
132  bail(err);
133 }
134 
135 bool
137 {
138  Must(Comm::IsConnOpen(serverConnection()));
139 
140  Security::ContextPointer ctx(getTlsContext());
141  debugs(83, 5, serverConnection() << ", ctx=" << (void*)ctx.get());
142 
143  if (!ctx || !Security::CreateClientSession(ctx, serverConnection(), "server https start")) {
144  const auto xerrno = errno;
145  if (!ctx) {
146  debugs(83, DBG_IMPORTANT, "Error initializing TLS connection: No security context.");
147  } // else CreateClientSession() did the appropriate debugs() already
148  const auto anErr = new ErrorState(ERR_SOCKET_FAILURE, Http::scInternalServerError, request.getRaw(), al);
149  anErr->xerrno = xerrno;
150  noteNegotiationDone(anErr);
151  bail(anErr);
152  return false;
153  }
154 
155  // A TLS/SSL session has now been created for the connection and stored in fd_table
156  serverSession = fd_table[serverConnection()->fd].ssl;
157  debugs(83, 5, serverConnection() << ", session=" << (void*)serverSession.get());
158 
159 #if USE_OPENSSL
160  // If CertValidation Helper used do not lookup checklist for errors,
161  // but keep a list of errors to send it to CertValidator
162  if (!Ssl::TheConfig.ssl_crt_validator) {
163  // Create the ACL check list now, while we have access to more info.
164  // The list is used in ssl_verify_cb() and is freed in ssl_free().
165  if (acl_access *acl = ::Config.ssl_client.cert_error) {
166  ACLFilledChecklist *check = new ACLFilledChecklist(acl, request.getRaw(), dash_str);
167  fillChecklist(*check);
168  SSL_set_ex_data(serverSession.get(), ssl_ex_index_cert_error_check, check);
169  }
170  }
171 
172  // Protect from cycles in the certificate dependency graph: TLS site S1 is
173  // missing certificate C1 located at TLS site S2. TLS site S2 is missing
174  // certificate C2 located at [...] TLS site S1.
175  const auto cycle = certDownloadNestingLevel() >= MaxNestedDownloads;
176  if (cycle)
177  debugs(83, 3, "will not fetch any missing certificates; suspecting cycle: " << certDownloadNestingLevel() << '/' << MaxNestedDownloads);
178  const auto sessData = Ssl::VerifyCallbackParameters::New(*serverSession);
179  // when suspecting a cycle, break it by not fetching any missing certs
180  sessData->callerHandlesMissingCertificates = !cycle;
181 #endif
182 
183  return true;
184 }
185 
186 void
188 {
189  Must(Comm::IsConnOpen(serverConnection()));
190 
191  const int fd = serverConnection()->fd;
192  Security::SessionPointer session(fd_table[fd].ssl);
193 
194  // retrieve TLS server negotiated information if any
195  serverConnection()->tlsNegotiations()->retrieveNegotiatedInfo(session);
196 
197 #if USE_OPENSSL
198  // retrieve TLS parsed extra info
199  BIO *b = SSL_get_rbio(session.get());
200  Ssl::ServerBio *bio = static_cast<Ssl::ServerBio *>(BIO_get_data(b));
201  if (const Security::TlsDetails::Pointer &details = bio->receivedHelloDetails())
202  serverConnection()->tlsNegotiations()->retrieveParsedInfo(details);
203 #endif
204 }
205 
206 void
208 {
209  Must(Comm::IsConnOpen(serverConnection()));
210 
211  const int fd = serverConnection()->fd;
212  if (fd_table[fd].closing())
213  return;
214 
215  const auto result = Security::Connect(*serverConnection());
216 
217 #if USE_OPENSSL
218  auto &sconn = *fd_table[fd].ssl;
219 
220  // log ASAP, even if the handshake has not completed (or failed)
221  keyLogger.checkpoint(sconn, *this);
222 
223  // OpenSSL v1 APIs do not allow unthreaded applications like Squid to fetch
224  // missing certificates _during_ OpenSSL certificate validation. Our
225  // handling of X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY (abbreviated
226  // here as EUNABLE) approximates what would happen if we did (attempt to)
227  // fetch any missing certificates during OpenSSL certificate validation.
228  // * We did not hide EUNABLE; SSL_connect() was successful: Handle success.
229  // * We did not hide EUNABLE; SSL_connect() reported some error E: Honor E.
230  // * We hid EUNABLE; SSL_connect() was successful: Remember success and try
231  // to fetch the missing certificates. If all goes well, honor success.
232  // * We hid EUNABLE; SSL_connect() reported EUNABLE: Warn but honor EUNABLE.
233  // * We hid EUNABLE; SSL_connect() reported some EOTHER: Remember EOTHER and
234  // try to fetch the missing certificates. If all goes well, honor EOTHER.
235  // If fetching or post-fetching validation fails, then honor that failure
236  // because EOTHER would not have happened if we fetched during validation.
237  if (auto &hidMissingIssuer = Ssl::VerifyCallbackParameters::At(sconn).hidMissingIssuer) {
238  hidMissingIssuer = false; // prep for the next SSL_connect()
239 
240  if (result.category == IoResult::ioSuccess ||
241  !(result.errorDetail && result.errorDetail->errorNo() == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY))
242  return handleMissingCertificates(result);
243 
244  debugs(83, DBG_IMPORTANT, "BUG: Honoring unexpected SSL_connect() error: X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY");
245  // fall through to regular error handling
246  }
247 #endif
248 
249  handleNegotiationResult(result);
250 }
251 
252 void
254 {
255  switch (result.category) {
257  recordNegotiationDetails();
258  if (sslFinalized() && callback)
259  sendSuccess();
260  return; // we may be gone by now
261 
263  noteWantRead();
264  return;
265 
267  noteWantWrite();
268  return;
269 
271  break; // fall through to error handling
272  }
273 
274  // TODO: Honor result.important when working in a reverse proxy role?
275  debugs(83, 2, "ERROR: " << result.errorDescription <<
276  " while establishing TLS connection on FD: " << serverConnection()->fd << result.errorDetail);
277  recordNegotiationDetails();
278  noteNegotiationError(result.errorDetail);
279 }
280 
281 bool
283 {
284 #if USE_OPENSSL
285  if (Ssl::TheConfig.ssl_crt_validator && useCertValidator_) {
286  Must(Comm::IsConnOpen(serverConnection()));
287  const int fd = serverConnection()->fd;
288  Security::SessionPointer session(fd_table[fd].ssl);
289 
290  Ssl::CertValidationRequest validationRequest;
291  // WARNING: Currently we do not use any locking for 'errors' member
292  // of the Ssl::CertValidationRequest class. In this code the
293  // Ssl::CertValidationRequest object used only to pass data to
294  // Ssl::CertValidationHelper::submit method.
295  validationRequest.ssl = session;
296  if (SBuf *dName = (SBuf *)SSL_get_ex_data(session.get(), ssl_ex_index_server))
297  validationRequest.domainName = dName->c_str();
298  if (Security::CertErrors *errs = static_cast<Security::CertErrors *>(SSL_get_ex_data(session.get(), ssl_ex_index_ssl_errors)))
299  // validationRequest disappears on return so no need to cbdataReference
300  validationRequest.errors = errs;
301  try {
302  debugs(83, 5, "Sending SSL certificate for validation to ssl_crtvd.");
303  AsyncCall::Pointer call = asyncCall(83,5, "Security::PeerConnector::sslCrtvdHandleReply", Ssl::CertValidationHelper::CbDialer(this, &Security::PeerConnector::sslCrtvdHandleReply, nullptr));
304  Ssl::CertValidationHelper::Submit(validationRequest, call);
305  return false;
306  } catch (const std::exception &e) {
307  debugs(83, DBG_IMPORTANT, "ERROR: Failed to compose ssl_crtvd " <<
308  "request for " << validationRequest.domainName <<
309  " certificate: " << e.what() << "; will now block to " <<
310  "validate that certificate.");
311  // fall through to do blocking in-process generation.
312  const auto anErr = new ErrorState(ERR_GATEWAY_FAILURE, Http::scInternalServerError, request.getRaw(), al);
313 
314  noteNegotiationDone(anErr);
315  bail(anErr);
316  return true;
317  }
318  }
319 #endif
320 
321  noteNegotiationDone(NULL);
322  return true;
323 }
324 
325 #if USE_OPENSSL
326 void
328 {
329  Must(validationResponse != NULL);
330  Must(Comm::IsConnOpen(serverConnection()));
331 
332  ErrorDetail::Pointer errDetails;
333  bool validatorFailed = false;
334 
335  if (Debug::Enabled(83, 5)) {
336  Security::SessionPointer ssl(fd_table[serverConnection()->fd].ssl);
337  SBuf *server = static_cast<SBuf *>(SSL_get_ex_data(ssl.get(), ssl_ex_index_server));
338  debugs(83,5, RawPointer("host", server) << " cert validation result: " << validationResponse->resultCode);
339  }
340 
341  if (validationResponse->resultCode == ::Helper::Error) {
342  if (Security::CertErrors *errs = sslCrtvdCheckForErrors(*validationResponse, errDetails)) {
343  Security::SessionPointer session(fd_table[serverConnection()->fd].ssl);
344  Security::CertErrors *oldErrs = static_cast<Security::CertErrors*>(SSL_get_ex_data(session.get(), ssl_ex_index_ssl_errors));
345  SSL_set_ex_data(session.get(), ssl_ex_index_ssl_errors, (void *)errs);
346  delete oldErrs;
347  }
348  } else if (validationResponse->resultCode != ::Helper::Okay)
349  validatorFailed = true;
350 
351  if (!errDetails && !validatorFailed) {
352  noteNegotiationDone(NULL);
353  if (callback)
354  sendSuccess();
355  return;
356  }
357 
358  ErrorState *anErr = NULL;
359  if (validatorFailed) {
361  } else {
363  anErr->detailError(errDetails);
364  /*anErr->xerrno= Should preserved*/
365  }
366 
367  noteNegotiationDone(anErr);
368  bail(anErr);
369  return;
370 }
371 #endif
372 
373 #if USE_OPENSSL
379 {
380  Must(Comm::IsConnOpen(serverConnection()));
381 
382  ACLFilledChecklist *check = NULL;
383  Security::SessionPointer session(fd_table[serverConnection()->fd].ssl);
384 
385  if (acl_access *acl = ::Config.ssl_client.cert_error) {
386  check = new ACLFilledChecklist(acl, request.getRaw(), dash_str);
387  fillChecklist(*check);
388  }
389 
390  Security::CertErrors *errs = nullptr;
391  typedef Ssl::CertValidationResponse::RecvdErrors::const_iterator SVCRECI;
392  for (SVCRECI i = resp.errors.begin(); i != resp.errors.end(); ++i) {
393  debugs(83, 7, "Error item: " << i->error_no << " " << i->error_reason);
394 
395  assert(i->error_no != SSL_ERROR_NONE);
396 
397  if (!errDetails) {
398  bool allowed = false;
399  if (check) {
400  check->sslErrors = new Security::CertErrors(Security::CertError(i->error_no, i->cert, i->error_depth));
401  if (check->fastCheck().allowed())
402  allowed = true;
403  }
404  // else the Config.ssl_client.cert_error access list is not defined
405  // and the first error will cause the error page
406 
407  if (allowed) {
408  debugs(83, 3, "bypassing SSL error " << i->error_no << " in " << "buffer");
409  } else {
410  debugs(83, 5, "confirming SSL error " << i->error_no);
411  const auto &brokenCert = i->cert;
412  Security::CertPointer peerCert(SSL_get_peer_certificate(session.get()));
413  const char *aReason = i->error_reason.empty() ? NULL : i->error_reason.c_str();
414  errDetails = new ErrorDetail(i->error_no, peerCert, brokenCert, aReason);
415  }
416  if (check) {
417  delete check->sslErrors;
418  check->sslErrors = NULL;
419  }
420  }
421 
422  if (!errs)
423  errs = new Security::CertErrors(Security::CertError(i->error_no, i->cert, i->error_depth));
424  else
425  errs->push_back_unique(Security::CertError(i->error_no, i->cert, i->error_depth));
426  }
427  if (check)
428  delete check;
429 
430  return errs;
431 }
432 #endif
433 
435 void
437 {
438  const auto pc = static_cast<PeerConnector::Pointer*>(data);
439  if (pc->valid())
440  (*pc)->negotiateSsl();
441  delete pc;
442 }
443 
445 void
447 {
448  // Use job calls to add done() checks and other job logic/protections.
449  CallJobHere(83, 7, this, Security::PeerConnector, negotiate);
450 }
451 
452 void
454 {
455  debugs(83, 5, serverConnection());
456 
457  Must(Comm::IsConnOpen(serverConnection()));
458  const int fd = serverConnection()->fd;
459 
460  // read timeout to avoid getting stuck while reading from a silent server
462  AsyncCall::Pointer timeoutCall = JobCallback(83, 5,
463  TimeoutDialer, this, Security::PeerConnector::commTimeoutHandler);
464  const auto timeout = Comm::MortalReadTimeout(startTime, negotiationTimeout);
465  commSetConnTimeout(serverConnection(), timeout, timeoutCall);
466 
467  Comm::SetSelect(fd, COMM_SELECT_READ, &NegotiateSsl, new Pointer(this), 0);
468 }
469 
470 void
472 {
473  debugs(83, 5, serverConnection());
474  Must(Comm::IsConnOpen(serverConnection()));
475 
476  const int fd = serverConnection()->fd;
477  Comm::SetSelect(fd, COMM_SELECT_WRITE, &NegotiateSsl, new Pointer(this), 0);
478  return;
479 }
480 
481 void
483 {
485  if (detail) {
486  anErr->xerrno = detail->sysError();
487  anErr->detailError(detail);
488  }
489  noteNegotiationDone(anErr);
490  bail(anErr);
491 }
492 
495 {
496  assert(callback);
497  const auto dialer = dynamic_cast<CbDialer*>(callback->getDialer());
498  assert(dialer);
499  return dialer->answer();
500 }
501 
502 void
504 {
505  Must(error); // or the recipient will not know there was a problem
506  answer().error = error;
507 
508  if (const auto failingConnection = serverConn) {
509  countFailingConnection();
510  disconnect();
511  failingConnection->close();
512  }
513 
514  callBack();
515 }
516 
517 void
519 {
520  assert(Comm::IsConnOpen(serverConn));
521  answer().conn = serverConn;
522  disconnect();
523  callBack();
524 }
525 
526 void
528 {
529  assert(serverConn);
530  if (const auto p = serverConn->getPeer())
532  // TODO: Calling PconnPool::noteUses() should not be our responsibility.
533  if (noteFwdPconnUse && serverConn->isOpen())
534  fwdPconnPool->noteUses(fd_table[serverConn->fd].pconn.uses);
535 }
536 
537 void
539 {
540  const auto stillOpen = Comm::IsConnOpen(serverConn);
541 
542  if (closeHandler) {
543  if (stillOpen)
544  comm_remove_close_handler(serverConn->fd, closeHandler);
545  closeHandler = nullptr;
546  }
547 
548  if (stillOpen)
549  commUnsetConnTimeout(serverConn);
550 
551  serverConn = nullptr;
552 }
553 
554 void
556 {
557  debugs(83, 5, "TLS setup ended for " << answer().conn);
558 
559  AsyncCall::Pointer cb = callback;
560  // Do this now so that if we throw below, swanSong() assert that we _tried_
561  // to call back holds.
562  callback = NULL; // this should make done() true
563  ScheduleCallHere(cb);
564 }
565 
566 void
568 {
569  // XXX: unregister fd-closure monitoring and CommSetSelect interest, if any
571 
572  if (callback) {
573  // job-ending emergencies like handleStopRequest() or callException()
574  const auto anErr = new ErrorState(ERR_GATEWAY_FAILURE, Http::scInternalServerError, request.getRaw(), al);
575  bail(anErr);
576  assert(!callback);
577  return;
578  }
579 }
580 
581 const char *
583 {
584  static MemBuf buf;
585  buf.reset();
586 
587  // TODO: redesign AsyncJob::status() API to avoid this
588  // id and stop reason reporting duplication.
589  buf.append(" [", 2);
590  if (stopReason != NULL) {
591  buf.append("Stopped, reason:", 16);
592  buf.appendf("%s",stopReason);
593  }
594  if (Comm::IsConnOpen(serverConn))
595  buf.appendf(" FD %d", serverConn->fd);
596  buf.appendf(" %s%u]", id.prefix(), id.value);
597  buf.terminate();
598 
599  return buf.content();
600 }
601 
602 #if USE_OPENSSL
605 {
606 public:
607  typedef void (Security::PeerConnector::*Method)(SBuf &object, int status);
608 
610  method_(method),
611  peerConnector_(pc) {}
612 
613  /* CallDialer API */
614  virtual bool canDial(AsyncCall &) { return peerConnector_.valid(); }
615  virtual void dial(AsyncCall &) { ((&(*peerConnector_))->*method_)(object, status); }
618 };
619 
621 unsigned int
623 {
624  if (request) {
625  // Nesting level increases when a PeerConnector (at level L) creates a
626  // Downloader (which is assigned level L+1). If we were initiated by
627  // such a Downloader, then their nesting level is our nesting level.
628  if (const auto previousDownloader = request->downloader.get())
629  return previousDownloader->nestedLevel();
630  }
631  return 0; // no other PeerConnector job waits for us
632 }
633 
634 void
636 {
637  AsyncCall::Pointer certCallback = asyncCall(81, 4,
638  "Security::PeerConnector::certDownloadingDone",
640 
641  const auto dl = new Downloader(url, certCallback, XactionInitiator::initCertFetcher, certDownloadNestingLevel() + 1);
642  certDownloadWait.start(dl, certCallback);
643 }
644 
645 void
647 {
648  certDownloadWait.finish();
649 
650  ++certsDownloads;
651  debugs(81, 5, "Certificate downloading status: " << downloadStatus << " certificate size: " << obj.length());
652 
653  Must(Comm::IsConnOpen(serverConnection()));
654  const auto &sconn = *fd_table[serverConnection()->fd].ssl;
655 
656  // Parse Certificate. Assume that it is in DER format.
657  // According to RFC 4325:
658  // The server must provide a DER encoded certificate or a collection
659  // collection of certificates in a "certs-only" CMS message.
660  // The applications MUST accept DER encoded certificates and SHOULD
661  // be able to accept collection of certificates.
662  // TODO: support collection of certificates
663  const unsigned char *raw = (const unsigned char*)obj.rawContent();
664  if (X509 *cert = d2i_X509(NULL, &raw, obj.length())) {
665  char buffer[1024];
666  debugs(81, 5, "Retrieved certificate: " << X509_NAME_oneline(X509_get_subject_name(cert), buffer, 1024));
667 
668  if (!downloadedCerts)
669  downloadedCerts.reset(sk_X509_new_null());
670  sk_X509_push(downloadedCerts.get(), cert);
671 
672  ContextPointer ctx(getTlsContext());
673  const auto certsList = SSL_get_peer_cert_chain(&sconn);
674  if (!Ssl::findIssuerCertificate(cert, certsList, ctx)) {
675  if (const auto issuerUri = Ssl::findIssuerUri(cert)) {
676  debugs(81, 5, "certificate " <<
677  X509_NAME_oneline(X509_get_subject_name(cert), buffer, sizeof(buffer)) <<
678  " points to its missing issuer certificate at " << issuerUri);
679  urlsOfMissingCerts.push(SBuf(issuerUri));
680  } else {
681  debugs(81, 3, "found a certificate with no IAI, " <<
682  "signed by a missing issuer certificate: " <<
683  X509_NAME_oneline(X509_get_subject_name(cert), buffer, sizeof(buffer)));
684  // We could short-circuit here, proceeding to chain validation
685  // that is likely to fail. Instead, we keep going because we
686  // hope that if we find at least one certificate to fetch, it
687  // will complete the chain (that contained extra certificates).
688  }
689  }
690  }
691 
692  // Check if there are URIs to download from and if yes start downloading
693  // the first in queue.
694  if (urlsOfMissingCerts.size() && certsDownloads <= MaxCertsDownloads) {
695  startCertDownloading(urlsOfMissingCerts.front());
696  urlsOfMissingCerts.pop();
697  return;
698  }
699 
700  resumeNegotiation();
701 }
702 
703 void
705 {
706  Must(Comm::IsConnOpen(serverConnection()));
707  auto &sconn = *fd_table[serverConnection()->fd].ssl;
708 
709  // We download the missing certificate(s) once. We would prefer to clear
710  // this right after the first validation, but that ideal place is _inside_
711  // OpenSSL if validation is triggered by SSL_connect(). That function and
712  // our OpenSSL verify_callback function (\ref OpenSSL_vcb_disambiguation)
713  // may be called multiple times, so we cannot reset there.
714  auto &callerHandlesMissingCertificates = Ssl::VerifyCallbackParameters::At(sconn).callerHandlesMissingCertificates;
715  Must(callerHandlesMissingCertificates);
716  callerHandlesMissingCertificates = false;
717 
718  suspendNegotiation(ioResult);
719 
720  if (!computeMissingCertificateUrls(sconn))
721  return resumeNegotiation();
722 
723  assert(!urlsOfMissingCerts.empty());
724  startCertDownloading(urlsOfMissingCerts.front());
725  urlsOfMissingCerts.pop();
726 }
727 
729 bool
731 {
732  const auto certs = SSL_get_peer_cert_chain(&sconn);
733  if (!certs) {
734  debugs(83, 3, "nothing to bootstrap the fetch with");
735  return false;
736  }
737  debugs(83, 5, "server certificates: " << sk_X509_num(certs));
738 
739  const auto ctx = getTlsContext();
740  if (!Ssl::missingChainCertificatesUrls(urlsOfMissingCerts, *certs, ctx))
741  return false; // missingChainCertificatesUrls() reports the exact reason
742 
743  debugs(83, 5, "URLs: " << urlsOfMissingCerts.size());
744  assert(!urlsOfMissingCerts.empty());
745  return true;
746 }
747 
748 void
750 {
751  debugs(83, 5, "after " << ioResult);
752  Must(!isSuspended());
753  suspendedError_ = new Security::IoResult(ioResult);
754  Must(isSuspended());
755  // negotiations resume with a resumeNegotiation() call
756 }
757 
758 void
760 {
761  Must(isSuspended());
762 
763  auto lastError = suspendedError_; // may be reset below
764  suspendedError_ = nullptr;
765 
766  auto &sconn = *fd_table[serverConnection()->fd].ssl;
767  if (!Ssl::VerifyConnCertificates(sconn, downloadedCerts)) {
768  // simulate an earlier SSL_connect() failure with a new error
769  // TODO: When we can use Security::ErrorDetail, we should resume with a
770  // detailed _validation_ error, not just a generic SSL_ERROR_SSL!
771  const ErrorDetail::Pointer errorDetail = new ErrorDetail(SQUID_TLS_ERR_CONNECT, SSL_ERROR_SSL, 0);
772  lastError = new Security::IoResult(errorDetail);
773  }
774 
775  handleNegotiationResult(*lastError);
776 }
777 
778 #endif //USE_OPENSSL
779 
bool CreateClientSession(const Security::ContextPointer &, const Comm::ConnectionPointer &, const char *squidCtx)
Definition: Session.cc:183
PeerConnector(const Comm::ConnectionPointer &aServerConn, AsyncCall::Pointer &aCallback, const AccessLogEntryPointer &alp, const time_t timeout=0)
@ ERR_SECURE_CONNECT_FAIL
Definition: forward.h:31
AsyncCall::Pointer comm_add_close_handler(int fd, CLCB *handler, void *data)
Definition: comm.cc:922
Security::SessionPointer ssl
void terminate()
Definition: MemBuf.cc:243
void appendf(const char *fmt,...) PRINTF_FORMAT_ARG2
Append operation with printf-style arguments.
Definition: Packable.h:61
CbcPointer< Security::PeerConnector > peerConnector_
The Security::PeerConnector object.
@ initCertFetcher
Missing intermediate certificates fetching code.
void peerConnectFailed(CachePeer *p)
Definition: neighbors.cc:1299
bool missingChainCertificatesUrls(std::queue< SBuf > &URIs, const STACK_OF(X509) &serverCertificates, const Security::ContextPointer &context)
Definition: support.cc:1230
std::shared_ptr< SSL_CTX > ContextPointer
Definition: Context.h:29
virtual bool initialize(Security::SessionPointer &)
@ ERR_GATEWAY_FAILURE
Definition: forward.h:67
int ssl_ex_index_ssl_errors
EncryptorAnswer & answer()
convenience method to get to the answer fields
static void NegotiateSsl(int fd, void *data)
A wrapper for Comm::SetSelect() notifications.
@ Error
Definition: ResultCode.h:19
void callBack()
a bail(), sendSuccess() helper: sends results to the initiator
Comm::ConnectionPointer serverConn
TCP connection to the peer.
virtual void swanSong()
Definition: AsyncJob.h:59
ErrorDetailPointer errorDetail
ioError case details (or nil)
Definition: Io.h:38
acl_access * cert_error
Definition: SquidConfig.h:526
#define ScheduleCallHere(call)
Definition: AsyncCall.h:166
void commTimeoutHandler(const CommTimeoutCbParams &)
The connection read timeout callback handler.
void error(char *format,...)
Definition: SBuf.h:87
RecvdErrors errors
The list of parsed errors.
void certDownloadingDone(SBuf &object, int status)
Called by Downloader after a certificate object downloaded.
int commSetConnTimeout(const Comm::ConnectionPointer &conn, int timeout, AsyncCall::Pointer &callback)
Definition: comm.cc:564
static VerifyCallbackParameters & At(Security::Connection &)
Definition: support.cc:554
PconnPool * fwdPconnPool
a collection of previously used persistent Squid-to-peer HTTP(S) connections
Definition: FwdState.cc:78
unsigned int certDownloadNestingLevel() const
the number of concurrent PeerConnector jobs waiting for us
void noteUses(int uses)
Definition: pconn.cc:536
virtual CallDialer * getDialer()=0
int fd
FD which the call was about. Set by the async call creator.
Definition: CommCalls.h:90
static VerifyCallbackParameters * New(Security::Connection &)
Definition: support.cc:542
virtual void fillChecklist(ACLFilledChecklist &) const
configure the given checklist (to reflect the current transaction state)
virtual void append(const char *c, int sz)
Definition: MemBuf.cc:211
void detailError(const ErrorDetail::Pointer &dCode)
set error type-specific detail code
Definition: errorpage.h:109
AccessLogEntry::Pointer al
info for the future access.log, and external ACL
Acl::Answer const & fastCheck()
Definition: Checklist.cc:332
bool IsConnOpen(const Comm::ConnectionPointer &conn)
Definition: Connection.cc:27
a summary a TLS I/O operation outcome
Definition: Io.h:19
void * BIO_get_data(BIO *table)
Definition: openssl.h:62
virtual bool canDial(AsyncCall &)
#define DBG_IMPORTANT
Definition: Debug.h:41
AsyncCall::Pointer closeHandler
we call this when the connection closed
std::string domainName
The server name.
void bail(ErrorState *error)
sends the given error to the initiator
const Security::TlsDetails::Pointer & receivedHelloDetails() const
Definition: bio.h:170
static bool Enabled(const int section, const int level)
whether debugging the given section and the given level produces output
Definition: Debug.h:70
void disconnect()
a bail(), sendSuccess() helper: stops monitoring the connection
@ scGatewayTimeout
Definition: StatusCode.h:75
void handleMissingCertificates(const Security::IoResult &lastError)
Either initiates fetching of missing certificates or bails with an error.
Security::CertPointer findIssuerCertificate(X509 *cert, const STACK_OF(X509) *serverCertificates, const Security::ContextPointer &context)
Definition: support.cc:1204
Security::CertPointer serverCert
#define NULL
Definition: types.h:166
virtual bool doneAll() const
whether positive goal has been reached
Definition: AsyncJob.cc:97
const char * rawContent() const
Definition: SBuf.cc:509
@ SQUID_TLS_ERR_CONNECT
failure to establish a connection with a TLS server
Definition: forward.h:212
int sysError() const
Definition: ErrorDetail.h:66
#define debugs(SECTION, LEVEL, CONTENT)
Definition: Debug.h:123
Category category
primary outcome classification
Definition: Io.h:40
bool VerifyConnCertificates(Security::Connection &, const Ssl::X509_STACK_Pointer &extraCerts)
Definition: support.cc:444
virtual void dial(AsyncCall &)
void suspendNegotiation(const Security::IoResult &lastError)
#define true
Definition: GnuRegex.c:234
@ scBadGateway
Definition: StatusCode.h:73
Definition: MemBuf.h:24
virtual void noteNegotiationError(const Security::ErrorDetailPointer &)
Called when the SSL_connect function aborts with an SSL negotiation error.
Callback dialer API to allow PeerConnector to set the answer.
Definition: PeerConnector.h:57
#define CallJobHere(debugSection, debugLevel, job, Class, method)
Definition: AsyncJobCalls.h:58
const char * findIssuerUri(X509 *cert)
finds certificate issuer URI in the Authority Info Access extension
Definition: support.cc:1082
Config TheConfig
Definition: Config.cc:12
void countFailingConnection()
updates connection usage history before the connection is closed
int conn
the current server connection FD
Definition: Transport.cc:26
const char * dash_str
#define assert(EX)
Definition: assert.h:19
@ Okay
Definition: ResultCode.h:18
virtual const char * status() const
internal cleanup; do not call directly
SBuf ErrorDetail
Definition: Esi.h:29
RawPointerT< Pointer > RawPointer(const char *label, const Pointer &ptr)
convenience wrapper for creating RawPointerT<> objects
Definition: Debug.h:239
SSL Connection
Definition: Session.h:45
virtual void syncAle(HttpRequest *adaptedRequest, const char *logUri) const
assigns uninitialized adapted_request and url ALE components
Security::CertErrors * sslCrtvdCheckForErrors(Ssl::CertValidationResponse const &, ErrorDetailPointer &)
Check SSL errors returned from cert validator against sslproxy_cert_error access list.
@ scServiceUnavailable
Definition: StatusCode.h:74
#define COMM_SELECT_READ
Definition: defines.h:24
void negotiateSsl()
Comm::SetSelect() callback. Direct calls tickle/resume negotiations.
CBDATA_NAMESPACED_CLASS_INIT(Security, PeerConnector)
#define JobCallback(dbgSection, dbgLevel, Dialer, job, method)
Convenience macro to create a Dialer-based job callback.
Definition: AsyncJobCalls.h:69
@ scInternalServerError
Definition: StatusCode.h:71
int commUnsetConnTimeout(const Comm::ConnectionPointer &conn)
Definition: comm.cc:590
size_type length() const
Returns the number of bytes stored in SBuf.
Definition: SBuf.h:408
CbDataList< Security::CertError > CertErrors
Holds a list of X.509 certificate errors.
Definition: forward.h:60
time_t squid_curtime
Definition: stub_time.cc:17
int ssl_ex_index_server
struct SquidConfig::@122 ssl_client
#define fd_table
Definition: fde.h:189
bool push_back_unique(C const &element)
Definition: CbDataList.h:87
AsyncCall::Pointer callback
we call this with the results
std::shared_ptr< SSL > SessionPointer
Definition: Session.h:49
bool allowed() const
Definition: Acl.h:149
void handleNegotiationResult(const Security::IoResult &)
Called after each negotiation step to handle the result.
virtual bool doneAll() const
whether positive goal has been reached
const Security::CertErrors * sslErrors
SSL [certificate validation] errors, in undefined order.
const char * errorDescription
a brief description of an error
Definition: Io.h:43
PeerConnectorCertDownloaderDialer(Method method, Security::PeerConnector *pc)
void SetSelect(int, unsigned int, PF *, void *, time_t)
Mark an FD to be watched for its IO status.
Definition: ModDevPoll.cc:224
static ErrorState * NewForwarding(err_type, HttpRequestPointer &, const AccessLogEntryPointer &)
Creates a general request forwarding error with the right http_status.
Definition: errorpage.cc:676
char * content()
start of the added data
Definition: MemBuf.h:41
static char server[MAXLINE]
#define acl_access
Definition: forward.h:45
int ssl_ex_index_cert_error_check
void resumeNegotiation()
Resumes TLS negotiation paused by suspendNegotiation()
IoResult Connect(Comm::Connection &transport)
establish a TLS connection over the specified from-Squid transport connection
Definition: Io.cc:212
@ ERR_CONNECT_FAIL
Definition: forward.h:30
virtual void start()
called by AsyncStart; do not call directly
Definition: AsyncJob.cc:44
#define Must(condition)
Like assert() but throws an exception instead of aborting the process.
Definition: TextException.h:73
Method method_
The Security::PeerConnector method to dial.
CallDialer to allow use Downloader objects within PeerConnector class.
AsyncCall * asyncCall(int aDebugSection, int aDebugLevel, const char *aName, const Dialer &aDialer)
Definition: AsyncCall.h:156
static void Submit(Ssl::CertValidationRequest const &request, AsyncCall::Pointer &)
Submit crtd request message to external crtd server.
Definition: helper.cc:302
void reset()
Definition: MemBuf.cc:131
void commCloseHandler(const CommCloseCbParams &params)
The comm_close callback handler.
time_t MortalReadTimeout(const time_t startTime, const time_t lifetimeLimit)
maximum read delay for readers with limited lifetime
Definition: Read.cc:248
void sslCrtvdHandleReply(Ssl::CertValidationResponsePointer)
Process response from cert validator helper.
@ ERR_SOCKET_FAILURE
Definition: forward.h:32
Security::CertErrors * errors
The list of errors detected.
bool computeMissingCertificateUrls(const Connection &)
finds URLs of (some) missing intermediate certificates or returns false
virtual void start()
Preps connection and SSL state. Calls negotiate().
#define false
Definition: GnuRegex.c:233
Callback data to use with Downloader callbacks.
Definition: Downloader.h:35
Network/connection security abstraction layer.
Definition: Connection.h:34
struct _request * request(char *urlin)
Definition: tcp-banger2.c:291
virtual void noteWantWrite()
#define COMM_SELECT_WRITE
Definition: defines.h:25
void resetWithoutLocking(T *t)
Reset raw pointer - unlock any previous one and save new one without locking.
void comm_remove_close_handler(int fd, CLCB *handler, void *data)
Definition: comm.cc:949
class SquidConfig Config
Definition: SquidConfig.cc:12
void sendSuccess()
sends the encrypted connection to the initiator
ErrorDetail::Pointer MakeNamedErrorDetail(const char *name)
Definition: Detail.cc:54
void startCertDownloading(SBuf &url)
Start downloading procedure for the given URL.

 

Introduction

Documentation

Support

Miscellaneous

Web Site Translations

Mirrors