PeerConnector.cc
Go to the documentation of this file.
1 /*
2  * Copyright (C) 1996-2022 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 "base/IoManip.h"
14 #include "comm/Loops.h"
15 #include "comm/Read.h"
16 #include "Downloader.h"
17 #include "errorpage.h"
18 #include "fde.h"
19 #include "FwdState.h"
20 #include "http/Stream.h"
21 #include "HttpRequest.h"
22 #include "neighbors.h"
23 #include "pconn.h"
24 #include "security/Io.h"
25 #include "security/Certificate.h"
27 #include "security/PeerConnector.h"
28 #include "SquidConfig.h"
29 #if USE_OPENSSL
30 #include "ssl/bio.h"
32 #include "ssl/Config.h"
33 #include "ssl/helper.h"
34 #endif
35 
37 
38 Security::PeerConnector::PeerConnector(const Comm::ConnectionPointer &aServerConn, AsyncCall::Pointer &aCallback, const AccessLogEntryPointer &alp, const time_t timeout) :
39  AsyncJob("Security::PeerConnector"),
40  noteFwdPconnUse(false),
41  serverConn(aServerConn),
42  al(alp),
43  callback(aCallback),
44  negotiationTimeout(timeout),
45  startTime(squid_curtime),
46  useCertValidator_(true),
47  certsDownloads(0)
48 {
49  debugs(83, 5, serverConn);
50 
51  // if this throws, the caller's cb dialer is not our CbDialer
52  Must(dynamic_cast<CbDialer*>(callback->getDialer()));
53 
54  // watch for external connection closures
56  Must(!fd_table[serverConn->fd].closing());
60 }
61 
63 
65 {
66  return (!callback || callback->canceled()) && AsyncJob::doneAll();
67 }
68 
70 void
72 {
74  debugs(83, 5, "this=" << (void*)this);
75 
76  // we own this Comm::Connection object and its fd exclusively, but must bail
77  // if others started closing the socket while we were waiting to start()
78  assert(Comm::IsConnOpen(serverConn));
79  if (fd_table[serverConn->fd].closing()) {
80  bail(new ErrorState(ERR_CONNECT_FAIL, Http::scBadGateway, request.getRaw(), al));
81  return;
82  }
83 
85  if (initialize(tmp))
86  negotiate();
87  else
88  mustStop("Security::PeerConnector TLS socket initialize failed");
89 }
90 
91 void
93 {
94  if (!checklist.al)
95  checklist.al = al;
96  checklist.syncAle(request.getRaw(), nullptr);
97  // checklist.fd(fd); XXX: need client FD here
98 
99 #if USE_OPENSSL
100  if (!checklist.serverCert) {
101  if (const auto session = fd_table[serverConnection()->fd].ssl.get())
102  checklist.serverCert.resetWithoutLocking(SSL_get_peer_certificate(session));
103  }
104 #else
105  // checklist.serverCert is not maintained in other builds
106 #endif
107 }
108 
109 void
111 {
112  debugs(83, 5, "FD " << params.fd << ", Security::PeerConnector=" << params.data);
113 
114  closeHandler = nullptr;
115  if (serverConn) {
116  countFailingConnection();
117  serverConn->noteClosure();
118  serverConn = nullptr;
119  }
120 
121  const auto err = new ErrorState(ERR_SECURE_CONNECT_FAIL, Http::scServiceUnavailable, request.getRaw(), al);
122  static const auto d = MakeNamedErrorDetail("TLS_CONNECT_CLOSE");
123  err->detailError(d);
124  bail(err);
125 }
126 
127 void
129 {
130  debugs(83, 5, serverConnection() << " timedout. this=" << (void*)this);
131  const auto err = new ErrorState(ERR_SECURE_CONNECT_FAIL, Http::scGatewayTimeout, request.getRaw(), al);
132  static const auto d = MakeNamedErrorDetail("TLS_CONNECT_TIMEOUT");
133  err->detailError(d);
134  bail(err);
135 }
136 
137 bool
139 {
140  Must(Comm::IsConnOpen(serverConnection()));
141 
142  Security::ContextPointer ctx(getTlsContext());
143  debugs(83, 5, serverConnection() << ", ctx=" << (void*)ctx.get());
144 
145  if (!ctx || !Security::CreateClientSession(ctx, serverConnection(), "server https start")) {
146  const auto xerrno = errno;
147  if (!ctx) {
148  debugs(83, DBG_IMPORTANT, "ERROR: initializing TLS connection: No security context.");
149  } // else CreateClientSession() did the appropriate debugs() already
150  const auto anErr = new ErrorState(ERR_SOCKET_FAILURE, Http::scInternalServerError, request.getRaw(), al);
151  anErr->xerrno = xerrno;
152  noteNegotiationDone(anErr);
153  bail(anErr);
154  return false;
155  }
156 
157  // A TLS/SSL session has now been created for the connection and stored in fd_table
158  serverSession = fd_table[serverConnection()->fd].ssl;
159  debugs(83, 5, serverConnection() << ", session=" << (void*)serverSession.get());
160 
161 #if USE_OPENSSL
162  // If CertValidation Helper used do not lookup checklist for errors,
163  // but keep a list of errors to send it to CertValidator
164  if (!Ssl::TheConfig.ssl_crt_validator) {
165  // Create the ACL check list now, while we have access to more info.
166  // The list is used in ssl_verify_cb() and is freed in ssl_free().
167  // XXX: This info may change, especially if we fetch missing certs.
168  // TODO: Remove ACLFilledChecklist::sslErrors and other pre-computed
169  // state in favor of the ACLs accessing current/fresh info directly.
170  if (acl_access *acl = ::Config.ssl_client.cert_error) {
171  ACLFilledChecklist *check = new ACLFilledChecklist(acl, request.getRaw(), dash_str);
172  fillChecklist(*check);
173  SSL_set_ex_data(serverSession.get(), ssl_ex_index_cert_error_check, check);
174  }
175  }
176 
177  // Protect from cycles in the certificate dependency graph: TLS site S1 is
178  // missing certificate C1 located at TLS site S2. TLS site S2 is missing
179  // certificate C2 located at [...] TLS site S1.
180  const auto cycle = certDownloadNestingLevel() >= MaxNestedDownloads;
181  if (cycle)
182  debugs(83, 3, "will not fetch any missing certificates; suspecting cycle: " << certDownloadNestingLevel() << '/' << MaxNestedDownloads);
183  const auto sessData = Ssl::VerifyCallbackParameters::New(*serverSession);
184  // when suspecting a cycle, break it by not fetching any missing certs
185  sessData->callerHandlesMissingCertificates = !cycle;
186 #endif
187 
188  return true;
189 }
190 
191 void
193 {
194  Must(Comm::IsConnOpen(serverConnection()));
195 
196  const int fd = serverConnection()->fd;
197  Security::SessionPointer session(fd_table[fd].ssl);
198 
199  // retrieve TLS server negotiated information if any
200  serverConnection()->tlsNegotiations()->retrieveNegotiatedInfo(session);
201 
202 #if USE_OPENSSL
203  // retrieve TLS parsed extra info
204  BIO *b = SSL_get_rbio(session.get());
205  Ssl::ServerBio *bio = static_cast<Ssl::ServerBio *>(BIO_get_data(b));
206  if (const Security::TlsDetails::Pointer &details = bio->receivedHelloDetails())
207  serverConnection()->tlsNegotiations()->retrieveParsedInfo(details);
208 #endif
209 }
210 
211 void
213 {
214  Must(Comm::IsConnOpen(serverConnection()));
215 
216  const int fd = serverConnection()->fd;
217  if (fd_table[fd].closing())
218  return;
219 
220  const auto result = Security::Connect(*serverConnection());
221 
222 #if USE_OPENSSL
223  auto &sconn = *fd_table[fd].ssl;
224 
225  // log ASAP, even if the handshake has not completed (or failed)
226  keyLogger.checkpoint(sconn, *this);
227 
228  // OpenSSL v1 APIs do not allow unthreaded applications like Squid to fetch
229  // missing certificates _during_ OpenSSL certificate validation. Our
230  // handling of X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY (abbreviated
231  // here as EUNABLE) approximates what would happen if we did (attempt to)
232  // fetch any missing certificates during OpenSSL certificate validation.
233  // * We did not hide EUNABLE; SSL_connect() was successful: Handle success.
234  // * We did not hide EUNABLE; SSL_connect() reported some error E: Honor E.
235  // * We hid EUNABLE; SSL_connect() was successful: Remember success and try
236  // to fetch the missing certificates. If all goes well, honor success.
237  // * We hid EUNABLE; SSL_connect() reported EUNABLE: Warn but honor EUNABLE.
238  // * We hid EUNABLE; SSL_connect() reported some EOTHER: Remember EOTHER and
239  // try to fetch the missing certificates. If all goes well, honor EOTHER.
240  // If fetching or post-fetching validation fails, then honor that failure
241  // because EOTHER would not have happened if we fetched during validation.
242  if (auto &hidMissingIssuer = Ssl::VerifyCallbackParameters::At(sconn).hidMissingIssuer) {
243  hidMissingIssuer = false; // prep for the next SSL_connect()
244 
245  if (result.category == IoResult::ioSuccess ||
246  !(result.errorDetail && result.errorDetail->errorNo() == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY))
247  return handleMissingCertificates(result);
248 
249  debugs(83, DBG_IMPORTANT, "ERROR: Squid BUG: Honoring unexpected SSL_connect() failure: X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY");
250  // fall through to regular error handling
251  }
252 #endif
253 
254  handleNegotiationResult(result);
255 }
256 
257 void
259 {
260  switch (result.category) {
262  recordNegotiationDetails();
263  if (sslFinalized() && callback)
264  sendSuccess();
265  return; // we may be gone by now
266 
268  noteWantRead();
269  return;
270 
272  noteWantWrite();
273  return;
274 
276  break; // fall through to error handling
277  }
278 
279  // TODO: Honor result.important when working in a reverse proxy role?
280  debugs(83, 2, "ERROR: Cannot establish a TLS connection to " << serverConnection() << ':' <<
281  Debug::Extra << "problem: " << result.errorDescription <<
282  RawPointer("detail: ", result.errorDetail).asExtra());
283  recordNegotiationDetails();
284  noteNegotiationError(result.errorDetail);
285 }
286 
287 bool
289 {
290 #if USE_OPENSSL
291  if (Ssl::TheConfig.ssl_crt_validator && useCertValidator_) {
292  Must(Comm::IsConnOpen(serverConnection()));
293  const int fd = serverConnection()->fd;
294  Security::SessionPointer session(fd_table[fd].ssl);
295 
296  Ssl::CertValidationRequest validationRequest;
297  // WARNING: Currently we do not use any locking for 'errors' member
298  // of the Ssl::CertValidationRequest class. In this code the
299  // Ssl::CertValidationRequest object used only to pass data to
300  // Ssl::CertValidationHelper::submit method.
301  validationRequest.ssl = session;
302  if (SBuf *dName = (SBuf *)SSL_get_ex_data(session.get(), ssl_ex_index_server))
303  validationRequest.domainName = dName->c_str();
304  if (Security::CertErrors *errs = static_cast<Security::CertErrors *>(SSL_get_ex_data(session.get(), ssl_ex_index_ssl_errors)))
305  // validationRequest disappears on return so no need to cbdataReference
306  validationRequest.errors = errs;
307  try {
308  debugs(83, 5, "Sending SSL certificate for validation to ssl_crtvd.");
309  AsyncCall::Pointer call = asyncCall(83,5, "Security::PeerConnector::sslCrtvdHandleReply", Ssl::CertValidationHelper::CbDialer(this, &Security::PeerConnector::sslCrtvdHandleReply, nullptr));
310  Ssl::CertValidationHelper::Submit(validationRequest, call);
311  return false;
312  } catch (const std::exception &e) {
313  debugs(83, DBG_IMPORTANT, "ERROR: Failed to compose ssl_crtvd " <<
314  "request for " << validationRequest.domainName <<
315  " certificate: " << e.what() << "; will now block to " <<
316  "validate that certificate.");
317  // fall through to do blocking in-process generation.
318  const auto anErr = new ErrorState(ERR_GATEWAY_FAILURE, Http::scInternalServerError, request.getRaw(), al);
319 
320  noteNegotiationDone(anErr);
321  bail(anErr);
322  return true;
323  }
324  }
325 #endif
326 
327  noteNegotiationDone(NULL);
328  return true;
329 }
330 
331 #if USE_OPENSSL
332 void
334 {
335  Must(validationResponse != NULL);
336  Must(Comm::IsConnOpen(serverConnection()));
337 
338  ErrorDetail::Pointer errDetails;
339  bool validatorFailed = false;
340 
341  if (Debug::Enabled(83, 5)) {
342  Security::SessionPointer ssl(fd_table[serverConnection()->fd].ssl);
343  SBuf *server = static_cast<SBuf *>(SSL_get_ex_data(ssl.get(), ssl_ex_index_server));
344  debugs(83, 5, "cert validation result: " << validationResponse->resultCode << RawPointer(" host: ", server));
345  }
346 
347  if (validationResponse->resultCode == ::Helper::Error) {
348  if (Security::CertErrors *errs = sslCrtvdCheckForErrors(*validationResponse, errDetails)) {
349  Security::SessionPointer session(fd_table[serverConnection()->fd].ssl);
350  Security::CertErrors *oldErrs = static_cast<Security::CertErrors*>(SSL_get_ex_data(session.get(), ssl_ex_index_ssl_errors));
351  SSL_set_ex_data(session.get(), ssl_ex_index_ssl_errors, (void *)errs);
352  delete oldErrs;
353  }
354  } else if (validationResponse->resultCode != ::Helper::Okay)
355  validatorFailed = true;
356 
357  if (!errDetails && !validatorFailed) {
358  noteNegotiationDone(NULL);
359  if (callback)
360  sendSuccess();
361  return;
362  }
363 
364  ErrorState *anErr = NULL;
365  if (validatorFailed) {
367  } else {
369  anErr->detailError(errDetails);
370  /*anErr->xerrno= Should preserved*/
371  }
372 
373  noteNegotiationDone(anErr);
374  bail(anErr);
375  return;
376 }
377 #endif
378 
379 #if USE_OPENSSL
385 {
386  Must(Comm::IsConnOpen(serverConnection()));
387 
388  ACLFilledChecklist *check = NULL;
389  Security::SessionPointer session(fd_table[serverConnection()->fd].ssl);
390 
391  if (acl_access *acl = ::Config.ssl_client.cert_error) {
392  check = new ACLFilledChecklist(acl, request.getRaw(), dash_str);
393  fillChecklist(*check);
394  }
395 
396  Security::CertErrors *errs = nullptr;
397  typedef Ssl::CertValidationResponse::RecvdErrors::const_iterator SVCRECI;
398  for (SVCRECI i = resp.errors.begin(); i != resp.errors.end(); ++i) {
399  debugs(83, 7, "Error item: " << i->error_no << " " << i->error_reason);
400 
401  assert(i->error_no != SSL_ERROR_NONE);
402 
403  if (!errDetails) {
404  bool allowed = false;
405  if (check) {
406  check->sslErrors = new Security::CertErrors(Security::CertError(i->error_no, i->cert, i->error_depth));
407  if (check->fastCheck().allowed())
408  allowed = true;
409  }
410  // else the Config.ssl_client.cert_error access list is not defined
411  // and the first error will cause the error page
412 
413  if (allowed) {
414  debugs(83, 3, "bypassing SSL error " << i->error_no << " in " << "buffer");
415  } else {
416  debugs(83, 5, "confirming SSL error " << i->error_no);
417  const auto &brokenCert = i->cert;
418  Security::CertPointer peerCert(SSL_get_peer_certificate(session.get()));
419  const char *aReason = i->error_reason.empty() ? NULL : i->error_reason.c_str();
420  errDetails = new ErrorDetail(i->error_no, peerCert, brokenCert, aReason);
421  }
422  if (check) {
423  delete check->sslErrors;
424  check->sslErrors = NULL;
425  }
426  }
427 
428  if (!errs)
429  errs = new Security::CertErrors(Security::CertError(i->error_no, i->cert, i->error_depth));
430  else
431  errs->push_back_unique(Security::CertError(i->error_no, i->cert, i->error_depth));
432  }
433  if (check)
434  delete check;
435 
436  return errs;
437 }
438 #endif
439 
441 void
443 {
444  const auto pc = static_cast<PeerConnector::Pointer*>(data);
445  if (pc->valid())
446  (*pc)->negotiateSsl();
447  delete pc;
448 }
449 
451 void
453 {
454  // Use job calls to add done() checks and other job logic/protections.
455  CallJobHere(83, 7, this, Security::PeerConnector, negotiate);
456 }
457 
458 void
460 {
461  debugs(83, 5, serverConnection());
462 
463  Must(Comm::IsConnOpen(serverConnection()));
464  const int fd = serverConnection()->fd;
465 
466  // read timeout to avoid getting stuck while reading from a silent server
468  AsyncCall::Pointer timeoutCall = JobCallback(83, 5,
469  TimeoutDialer, this, Security::PeerConnector::commTimeoutHandler);
470  const auto timeout = Comm::MortalReadTimeout(startTime, negotiationTimeout);
471  commSetConnTimeout(serverConnection(), timeout, timeoutCall);
472 
473  Comm::SetSelect(fd, COMM_SELECT_READ, &NegotiateSsl, new Pointer(this), 0);
474 }
475 
476 void
478 {
479  debugs(83, 5, serverConnection());
480  Must(Comm::IsConnOpen(serverConnection()));
481 
482  const int fd = serverConnection()->fd;
483  Comm::SetSelect(fd, COMM_SELECT_WRITE, &NegotiateSsl, new Pointer(this), 0);
484  return;
485 }
486 
487 void
489 {
491  if (detail) {
492  anErr->xerrno = detail->sysError();
493  anErr->detailError(detail);
494  }
495  noteNegotiationDone(anErr);
496  bail(anErr);
497 }
498 
501 {
502  assert(callback);
503  const auto dialer = dynamic_cast<CbDialer*>(callback->getDialer());
504  assert(dialer);
505  return dialer->answer();
506 }
507 
508 void
510 {
511  Must(error); // or the recipient will not know there was a problem
512  answer().error = error;
513 
514  if (const auto failingConnection = serverConn) {
515  countFailingConnection();
516  disconnect();
517  failingConnection->close();
518  }
519 
520  callBack();
521 }
522 
523 void
525 {
526  assert(Comm::IsConnOpen(serverConn));
527  answer().conn = serverConn;
528  disconnect();
529  callBack();
530 }
531 
532 void
534 {
535  assert(serverConn);
536  if (const auto p = serverConn->getPeer())
538  // TODO: Calling PconnPool::noteUses() should not be our responsibility.
539  if (noteFwdPconnUse && serverConn->isOpen())
540  fwdPconnPool->noteUses(fd_table[serverConn->fd].pconn.uses);
541 }
542 
543 void
545 {
546  const auto stillOpen = Comm::IsConnOpen(serverConn);
547 
548  if (closeHandler) {
549  if (stillOpen)
550  comm_remove_close_handler(serverConn->fd, closeHandler);
551  closeHandler = nullptr;
552  }
553 
554  if (stillOpen)
555  commUnsetConnTimeout(serverConn);
556 
557  serverConn = nullptr;
558 }
559 
560 void
562 {
563  debugs(83, 5, "TLS setup ended for " << answer().conn);
564 
565  AsyncCall::Pointer cb = callback;
566  // Do this now so that if we throw below, swanSong() assert that we _tried_
567  // to call back holds.
568  callback = NULL; // this should make done() true
569  ScheduleCallHere(cb);
570 }
571 
572 void
574 {
575  // XXX: unregister fd-closure monitoring and CommSetSelect interest, if any
577 
578  if (callback) {
579  // job-ending emergencies like handleStopRequest() or callException()
580  const auto anErr = new ErrorState(ERR_GATEWAY_FAILURE, Http::scInternalServerError, request.getRaw(), al);
581  bail(anErr);
582  assert(!callback);
583  return;
584  }
585 }
586 
587 const char *
589 {
590  static MemBuf buf;
591  buf.reset();
592 
593  // TODO: redesign AsyncJob::status() API to avoid this
594  // id and stop reason reporting duplication.
595  buf.append(" [", 2);
596  if (stopReason != NULL) {
597  buf.append("Stopped, reason:", 16);
598  buf.appendf("%s",stopReason);
599  }
600  if (Comm::IsConnOpen(serverConn))
601  buf.appendf(" FD %d", serverConn->fd);
602  buf.appendf(" %s%u]", id.prefix(), id.value);
603  buf.terminate();
604 
605  return buf.content();
606 }
607 
608 #if USE_OPENSSL
611 {
612 public:
613  typedef void (Security::PeerConnector::*Method)(SBuf &object, int status);
614 
616  method_(method),
617  peerConnector_(pc) {}
618 
619  /* CallDialer API */
620  virtual bool canDial(AsyncCall &) { return peerConnector_.valid(); }
621  virtual void dial(AsyncCall &) { ((&(*peerConnector_))->*method_)(object, status); }
624 };
625 
627 unsigned int
629 {
630  if (request) {
631  // Nesting level increases when a PeerConnector (at level L) creates a
632  // Downloader (which is assigned level L+1). If we were initiated by
633  // such a Downloader, then their nesting level is our nesting level.
634  if (const auto previousDownloader = request->downloader.get())
635  return previousDownloader->nestedLevel();
636  }
637  return 0; // no other PeerConnector job waits for us
638 }
639 
640 void
642 {
643  AsyncCall::Pointer certCallback = asyncCall(81, 4,
644  "Security::PeerConnector::certDownloadingDone",
646 
647  const auto dl = new Downloader(url, certCallback,
648  MasterXaction::MakePortless<XactionInitiator::initCertFetcher>(),
649  certDownloadNestingLevel() + 1);
650  certDownloadWait.start(dl, certCallback);
651 }
652 
653 void
655 {
656  certDownloadWait.finish();
657 
658  ++certsDownloads;
659  debugs(81, 5, "Certificate downloading status: " << downloadStatus << " certificate size: " << obj.length());
660 
661  Must(Comm::IsConnOpen(serverConnection()));
662  const auto &sconn = *fd_table[serverConnection()->fd].ssl;
663 
664  // Parse Certificate. Assume that it is in DER format.
665  // According to RFC 4325:
666  // The server must provide a DER encoded certificate or a collection
667  // collection of certificates in a "certs-only" CMS message.
668  // The applications MUST accept DER encoded certificates and SHOULD
669  // be able to accept collection of certificates.
670  // TODO: support collection of certificates
671  const unsigned char *raw = (const unsigned char*)obj.rawContent();
672  if (X509 *cert = d2i_X509(NULL, &raw, obj.length())) {
673  debugs(81, 5, "Retrieved certificate: " << *cert);
674 
675  if (!downloadedCerts)
676  downloadedCerts.reset(sk_X509_new_null());
677  sk_X509_push(downloadedCerts.get(), cert);
678 
679  ContextPointer ctx(getTlsContext());
680  const auto certsList = SSL_get_peer_cert_chain(&sconn);
681  if (!Ssl::findIssuerCertificate(cert, certsList, ctx)) {
682  if (const auto issuerUri = Ssl::findIssuerUri(cert)) {
683  debugs(81, 5, "certificate " << *cert <<
684  " points to its missing issuer certificate at " << issuerUri);
685  urlsOfMissingCerts.push(SBuf(issuerUri));
686  } else {
687  debugs(81, 3, "found a certificate with no IAI, " <<
688  "signed by a missing issuer certificate: " << *cert);
689  // We could short-circuit here, proceeding to chain validation
690  // that is likely to fail. Instead, we keep going because we
691  // hope that if we find at least one certificate to fetch, it
692  // will complete the chain (that contained extra certificates).
693  }
694  }
695  }
696 
697  // Check if there are URIs to download from and if yes start downloading
698  // the first in queue.
699  if (urlsOfMissingCerts.size() && certsDownloads <= MaxCertsDownloads) {
700  startCertDownloading(urlsOfMissingCerts.front());
701  urlsOfMissingCerts.pop();
702  return;
703  }
704 
705  resumeNegotiation();
706 }
707 
708 void
710 {
711  Must(Comm::IsConnOpen(serverConnection()));
712  auto &sconn = *fd_table[serverConnection()->fd].ssl;
713 
714  // We download the missing certificate(s) once. We would prefer to clear
715  // this right after the first validation, but that ideal place is _inside_
716  // OpenSSL if validation is triggered by SSL_connect(). That function and
717  // our OpenSSL verify_callback function (\ref OpenSSL_vcb_disambiguation)
718  // may be called multiple times, so we cannot reset there.
719  auto &callerHandlesMissingCertificates = Ssl::VerifyCallbackParameters::At(sconn).callerHandlesMissingCertificates;
720  Must(callerHandlesMissingCertificates);
721  callerHandlesMissingCertificates = false;
722 
723  suspendNegotiation(ioResult);
724 
725  if (!computeMissingCertificateUrls(sconn))
726  return resumeNegotiation();
727 
728  assert(!urlsOfMissingCerts.empty());
729  startCertDownloading(urlsOfMissingCerts.front());
730  urlsOfMissingCerts.pop();
731 }
732 
734 bool
736 {
737  const auto certs = SSL_get_peer_cert_chain(&sconn);
738  if (!certs) {
739  debugs(83, 3, "nothing to bootstrap the fetch with");
740  return false;
741  }
742  debugs(83, 5, "server certificates: " << sk_X509_num(certs));
743 
744  const auto ctx = getTlsContext();
745  if (!Ssl::missingChainCertificatesUrls(urlsOfMissingCerts, *certs, ctx))
746  return false; // missingChainCertificatesUrls() reports the exact reason
747 
748  debugs(83, 5, "URLs: " << urlsOfMissingCerts.size());
749  assert(!urlsOfMissingCerts.empty());
750  return true;
751 }
752 
753 void
755 {
756  debugs(83, 5, "after " << ioResult);
757  Must(!isSuspended());
758  suspendedError_ = new Security::IoResult(ioResult);
759  Must(isSuspended());
760  // negotiations resume with a resumeNegotiation() call
761 }
762 
763 void
765 {
766  Must(isSuspended());
767 
768  auto lastError = suspendedError_; // may be reset below
769  suspendedError_ = nullptr;
770 
771  auto &sconn = *fd_table[serverConnection()->fd].ssl;
772  if (!Ssl::VerifyConnCertificates(sconn, downloadedCerts)) {
773  // simulate an earlier SSL_connect() failure with a new error
774  // TODO: When we can use Security::ErrorDetail, we should resume with a
775  // detailed _validation_ error, not just a generic SSL_ERROR_SSL!
776  const ErrorDetail::Pointer errorDetail = new ErrorDetail(SQUID_TLS_ERR_CONNECT, SSL_ERROR_SSL, 0);
777  lastError = new Security::IoResult(errorDetail);
778  }
779 
780  handleNegotiationResult(*lastError);
781 }
782 
783 #endif //USE_OPENSSL
784 
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:921
Security::SessionPointer ssl
void terminate()
Definition: MemBuf.cc:241
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.
void peerConnectFailed(CachePeer *p)
Definition: neighbors.cc:1298
bool missingChainCertificatesUrls(std::queue< SBuf > &URIs, const STACK_OF(X509) &serverCertificates, const Security::ContextPointer &context)
Definition: support.cc:1222
std::shared_ptr< SSL_CTX > ContextPointer
Definition: Context.h:29
virtual bool initialize(Security::SessionPointer &)
@ ERR_GATEWAY_FAILURE
Definition: forward.h:67
static std::ostream & Extra(std::ostream &os)
prefixes each grouped debugs() line after the first one in the group
Definition: Stream.h:117
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:525
#define ScheduleCallHere(call)
Definition: AsyncCall.h:164
void commTimeoutHandler(const CommTimeoutCbParams &)
The connection read timeout callback handler.
void error(char *format,...)
Definition: SBuf.h:94
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:563
static VerifyCallbackParameters & At(Security::Connection &)
Definition: support.cc:551
PconnPool * fwdPconnPool
a collection of previously used persistent Squid-to-peer HTTP(S) connections
Definition: FwdState.cc:77
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:539
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:209
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
@ SQUID_TLS_ERR_CONNECT
failure to establish a connection with a TLS server
Definition: forward.h:220
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 &)
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: Stream.h:79
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:1196
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
int sysError() const
Definition: ErrorDetail.h:66
Category category
primary outcome classification
Definition: Io.h:40
bool VerifyConnCertificates(Security::Connection &, const Ssl::X509_STACK_Pointer &extraCerts)
Definition: support.cc:441
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:1079
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
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:589
size_type length() const
Returns the number of bytes stored in SBuf.
Definition: SBuf.h:415
CbDataList< Security::CertError > CertErrors
Holds a list of X.509 certificate errors.
Definition: forward.h:68
time_t squid_curtime
Definition: stub_libtime.cc:20
int ssl_ex_index_server
#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
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:223
static ErrorState * NewForwarding(err_type, HttpRequestPointer &, const AccessLogEntryPointer &)
Creates a general request forwarding error with the right http_status.
Definition: errorpage.cc:675
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)
Definition: TextException.h:71
Method method_
The Security::PeerConnector method to dial.
CallDialer to allow use Downloader objects within PeerConnector class.
struct SquidConfig::@121 ssl_client
#define DBG_IMPORTANT
Definition: Stream.h:41
AsyncCall * asyncCall(int aDebugSection, int aDebugLevel, const char *aName, const Dialer &aDialer)
Definition: AsyncCall.h:154
static void Submit(Ssl::CertValidationRequest const &request, AsyncCall::Pointer &)
Submit crtd request message to external crtd server.
Definition: helper.cc:301
void reset()
Definition: MemBuf.cc:129
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
RawPointerT< Pointer > RawPointer(const char *label, const Pointer &ptr)
convenience wrapper for creating RawPointerT<> objects
Definition: IoManip.h:36
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:36
Network/connection security abstraction layer.
Definition: Connection.h:34
struct _request * request(char *urlin)
Definition: tcp-banger2.c:291
virtual void noteWantWrite()
#define debugs(SECTION, LEVEL, CONTENT)
Definition: Stream.h:196
#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:950
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