PeerConnector.cc
Go to the documentation of this file.
1 /*
2  * Copyright (C) 1996-2023 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/AsyncCallbacks.h"
14 #include "base/IoManip.h"
15 #include "CachePeer.h"
16 #include "comm/Loops.h"
17 #include "comm/Read.h"
18 #include "Downloader.h"
19 #include "errorpage.h"
20 #include "fde.h"
21 #include "FwdState.h"
22 #include "http/Stream.h"
23 #include "HttpRequest.h"
24 #include "neighbors.h"
25 #include "pconn.h"
26 #include "security/Certificate.h"
27 #include "security/Io.h"
29 #include "security/PeerConnector.h"
30 #include "SquidConfig.h"
31 #if USE_OPENSSL
32 #include "ssl/bio.h"
34 #include "ssl/Config.h"
35 #include "ssl/helper.h"
36 
37 #include <optional>
38 #endif
39 
40 Security::PeerConnector::PeerConnector(const Comm::ConnectionPointer &aServerConn, const AsyncCallback<EncryptorAnswer> &aCallback, const AccessLogEntryPointer &alp, const time_t timeout):
41  AsyncJob("Security::PeerConnector"),
42  noteFwdPconnUse(false),
43  serverConn(aServerConn),
44  al(alp),
45  callback(aCallback),
46  negotiationTimeout(timeout),
47  startTime(squid_curtime),
48  useCertValidator_(true),
49  certsDownloads(0)
50 {
51  debugs(83, 5, serverConn);
52 
53  // watch for external connection closures
55  Must(!fd_table[serverConn->fd].closing());
59 }
60 
62 
64 {
65  return (!callback || callback->canceled()) && AsyncJob::doneAll();
66 }
67 
69 void
71 {
73  debugs(83, 5, "this=" << (void*)this);
74 
75  // we own this Comm::Connection object and its fd exclusively, but must bail
76  // if others started closing the socket while we were waiting to start()
77  assert(Comm::IsConnOpen(serverConn));
78  if (fd_table[serverConn->fd].closing()) {
79  bail(new ErrorState(ERR_CONNECT_FAIL, Http::scBadGateway, request.getRaw(), al));
80  return;
81  }
82 
84  if (initialize(tmp))
85  negotiate();
86  else
87  mustStop("Security::PeerConnector TLS socket initialize failed");
88 }
89 
90 void
92 {
93  if (!checklist.al)
94  checklist.al = al;
95  checklist.syncAle(request.getRaw(), nullptr);
96  // checklist.fd(fd); XXX: need client FD here
97 
98 #if USE_OPENSSL
99  if (!checklist.serverCert) {
100  if (const auto session = fd_table[serverConnection()->fd].ssl.get())
101  checklist.serverCert.resetWithoutLocking(SSL_get_peer_certificate(session));
102  }
103 #else
104  // checklist.serverCert is not maintained in other builds
105 #endif
106 }
107 
108 void
110 {
111  debugs(83, 5, "FD " << params.fd << ", Security::PeerConnector=" << params.data);
112 
113  closeHandler = nullptr;
114 
115  const auto err = new ErrorState(ERR_SECURE_CONNECT_FAIL, Http::scServiceUnavailable, request.getRaw(), al);
116  static const auto d = MakeNamedErrorDetail("TLS_CONNECT_CLOSE");
117  err->detailError(d);
118 
119  if (serverConn) {
120  countFailingConnection();
121  serverConn->noteClosure();
122  serverConn = nullptr;
123  }
124 
125  bail(err);
126 }
127 
128 void
130 {
131  debugs(83, 5, serverConnection() << " timedout. this=" << (void*)this);
132  const auto err = new ErrorState(ERR_SECURE_CONNECT_FAIL, Http::scGatewayTimeout, request.getRaw(), al);
133  static const auto d = MakeNamedErrorDetail("TLS_CONNECT_TIMEOUT");
134  err->detailError(d);
135  bail(err);
136 }
137 
138 bool
140 {
141  Must(Comm::IsConnOpen(serverConnection()));
142 
143  const auto ctx = peerContext();
144  debugs(83, 5, serverConnection() << ", ctx=" << ctx);
145 
146  if (!ctx || !Security::CreateClientSession(*ctx, serverConnection(), "server https start")) {
147  const auto xerrno = errno;
148  if (!ctx) {
149  debugs(83, DBG_IMPORTANT, "ERROR: initializing TLS connection: No security context.");
150  } // else CreateClientSession() did the appropriate debugs() already
151  const auto anErr = new ErrorState(ERR_SOCKET_FAILURE, Http::scInternalServerError, request.getRaw(), al);
152  anErr->xerrno = xerrno;
153  noteNegotiationDone(anErr);
154  bail(anErr);
155  return false;
156  }
157 
158  // A TLS/SSL session has now been created for the connection and stored in fd_table
159  serverSession = fd_table[serverConnection()->fd].ssl;
160  debugs(83, 5, serverConnection() << ", session=" << (void*)serverSession.get());
161 
162 #if USE_OPENSSL
163  // If CertValidation Helper used do not lookup checklist for errors,
164  // but keep a list of errors to send it to CertValidator
165  if (!Ssl::TheConfig.ssl_crt_validator) {
166  // Create the ACL check list now, while we have access to more info.
167  // The list is used in ssl_verify_cb() and is freed in ssl_free().
168  // XXX: This info may change, especially if we fetch missing certs.
169  // TODO: Remove ACLFilledChecklist::sslErrors and other pre-computed
170  // state in favor of the ACLs accessing current/fresh info directly.
171  if (acl_access *acl = ::Config.ssl_client.cert_error) {
172  auto check = ACLFilledChecklist::Make(acl, request.getRaw());
173  fillChecklist(*check);
174  SSL_set_ex_data(serverSession.get(), ssl_ex_index_cert_error_check, check.release());
175  }
176  }
177 
178  // Protect from cycles in the certificate dependency graph: TLS site S1 is
179  // missing certificate C1 located at TLS site S2. TLS site S2 is missing
180  // certificate C2 located at [...] TLS site S1.
181  const auto cycle = certDownloadNestingLevel() >= MaxNestedDownloads;
182  if (cycle)
183  debugs(83, 3, "will not fetch any missing certificates; suspecting cycle: " << certDownloadNestingLevel() << '/' << MaxNestedDownloads);
184  const auto sessData = Ssl::VerifyCallbackParameters::New(*serverSession);
185  // when suspecting a cycle, break it by not fetching any missing certs
186  sessData->callerHandlesMissingCertificates = !cycle;
187 #endif
188 
189  return true;
190 }
191 
192 void
194 {
195  Must(Comm::IsConnOpen(serverConnection()));
196 
197  const int fd = serverConnection()->fd;
198  Security::SessionPointer session(fd_table[fd].ssl);
199 
200  // retrieve TLS server negotiated information if any
201  serverConnection()->tlsNegotiations()->retrieveNegotiatedInfo(session);
202 
203 #if USE_OPENSSL
204  // retrieve TLS parsed extra info
205  BIO *b = SSL_get_rbio(session.get());
206  Ssl::ServerBio *bio = static_cast<Ssl::ServerBio *>(BIO_get_data(b));
207  if (const Security::TlsDetails::Pointer &details = bio->receivedHelloDetails())
208  serverConnection()->tlsNegotiations()->retrieveParsedInfo(details);
209 #endif
210 }
211 
212 void
214 {
215  Must(Comm::IsConnOpen(serverConnection()));
216 
217  const int fd = serverConnection()->fd;
218  if (fd_table[fd].closing())
219  return;
220 
221  const auto result = Security::Connect(*serverConnection());
222 
223 #if USE_OPENSSL
224  auto &sconn = *fd_table[fd].ssl;
225 
226  // log ASAP, even if the handshake has not completed (or failed)
227  keyLogger.checkpoint(sconn, *this);
228 
229  // OpenSSL v1 APIs do not allow unthreaded applications like Squid to fetch
230  // missing certificates _during_ OpenSSL certificate validation. Our
231  // handling of X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY (abbreviated
232  // here as EUNABLE) approximates what would happen if we did (attempt to)
233  // fetch any missing certificates during OpenSSL certificate validation.
234  // * We did not hide EUNABLE; SSL_connect() was successful: Handle success.
235  // * We did not hide EUNABLE; SSL_connect() reported some error E: Honor E.
236  // * We hid EUNABLE; SSL_connect() was successful: Remember success and try
237  // to fetch the missing certificates. If all goes well, honor success.
238  // * We hid EUNABLE; SSL_connect() reported EUNABLE: Warn but honor EUNABLE.
239  // * We hid EUNABLE; SSL_connect() reported some EOTHER: Remember EOTHER and
240  // try to fetch the missing certificates. If all goes well, honor EOTHER.
241  // If fetching or post-fetching validation fails, then honor that failure
242  // because EOTHER would not have happened if we fetched during validation.
243  if (auto &hidMissingIssuer = Ssl::VerifyCallbackParameters::At(sconn).hidMissingIssuer) {
244  hidMissingIssuer = false; // prep for the next SSL_connect()
245 
246  if (result.category == IoResult::ioSuccess ||
247  !(result.errorDetail && result.errorDetail->errorNo() == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY))
248  return handleMissingCertificates(result);
249 
250  debugs(83, DBG_IMPORTANT, "ERROR: Squid BUG: Honoring unexpected SSL_connect() failure: X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY");
251  // fall through to regular error handling
252  }
253 #endif
254 
255  handleNegotiationResult(result);
256 }
257 
258 void
260 {
261  switch (result.category) {
263  recordNegotiationDetails();
264  if (sslFinalized() && callback)
265  sendSuccess();
266  return; // we may be gone by now
267 
269  noteWantRead();
270  return;
271 
273  noteWantWrite();
274  return;
275 
277  break; // fall through to error handling
278  }
279 
280  // TODO: Honor result.important when working in a reverse proxy role?
281  debugs(83, 2, "ERROR: Cannot establish a TLS connection" <<
282  Debug::Extra << "problem: " << WithExtras(result) <<
283  Debug::Extra << "connection: " << serverConnection());
284  recordNegotiationDetails();
285  noteNegotiationError(result.errorDetail);
286 }
287 
288 bool
290 {
291 #if USE_OPENSSL
292  if (Ssl::TheConfig.ssl_crt_validator && useCertValidator_) {
293  Must(Comm::IsConnOpen(serverConnection()));
294  const int fd = serverConnection()->fd;
295  Security::SessionPointer session(fd_table[fd].ssl);
296 
297  Ssl::CertValidationRequest validationRequest;
298  // WARNING: Currently we do not use any locking for 'errors' member
299  // of the Ssl::CertValidationRequest class. In this code the
300  // Ssl::CertValidationRequest object used only to pass data to
301  // Ssl::CertValidationHelper::submit method.
302  validationRequest.ssl = session;
303  if (SBuf *dName = (SBuf *)SSL_get_ex_data(session.get(), ssl_ex_index_server))
304  validationRequest.domainName = dName->c_str();
305  if (Security::CertErrors *errs = static_cast<Security::CertErrors *>(SSL_get_ex_data(session.get(), ssl_ex_index_ssl_errors)))
306  // validationRequest disappears on return so no need to cbdataReference
307  validationRequest.errors = errs;
308  try {
309  debugs(83, 5, "Sending SSL certificate for validation to ssl_crtvd.");
310  const auto call = asyncCallback(83, 5, Security::PeerConnector::sslCrtvdHandleReply, this);
311  Ssl::CertValidationHelper::Submit(validationRequest, call);
312  return false;
313  } catch (const std::exception &e) {
314  debugs(83, DBG_IMPORTANT, "ERROR: Failed to compose ssl_crtvd " <<
315  "request for " << validationRequest.domainName <<
316  " certificate: " << e.what() << "; will now block to " <<
317  "validate that certificate.");
318  // fall through to do blocking in-process generation.
319  const auto anErr = new ErrorState(ERR_GATEWAY_FAILURE, Http::scInternalServerError, request.getRaw(), al);
320 
321  noteNegotiationDone(anErr);
322  bail(anErr);
323  return true;
324  }
325  }
326 #endif
327 
328  noteNegotiationDone(nullptr);
329  return true;
330 }
331 
332 #if USE_OPENSSL
333 void
335 {
336  Must(validationResponse != nullptr);
337  Must(Comm::IsConnOpen(serverConnection()));
338 
339  ErrorDetail::Pointer errDetails;
340  bool validatorFailed = false;
341 
342  if (Debug::Enabled(83, 5)) {
343  Security::SessionPointer ssl(fd_table[serverConnection()->fd].ssl);
344  SBuf *server = static_cast<SBuf *>(SSL_get_ex_data(ssl.get(), ssl_ex_index_server));
345  debugs(83, 5, "cert validation result: " << validationResponse->resultCode << RawPointer(" host: ", server));
346  }
347 
348  if (validationResponse->resultCode == ::Helper::Error) {
349  if (Security::CertErrors *errs = sslCrtvdCheckForErrors(*validationResponse, errDetails)) {
350  Security::SessionPointer session(fd_table[serverConnection()->fd].ssl);
351  Security::CertErrors *oldErrs = static_cast<Security::CertErrors*>(SSL_get_ex_data(session.get(), ssl_ex_index_ssl_errors));
352  SSL_set_ex_data(session.get(), ssl_ex_index_ssl_errors, (void *)errs);
353  delete oldErrs;
354  }
355  } else if (validationResponse->resultCode != ::Helper::Okay)
356  validatorFailed = true;
357 
358  if (!errDetails && !validatorFailed) {
359  noteNegotiationDone(nullptr);
360  if (callback)
361  sendSuccess();
362  return;
363  }
364 
365  ErrorState *anErr = nullptr;
366  if (validatorFailed) {
367  anErr = new ErrorState(ERR_GATEWAY_FAILURE, Http::scInternalServerError, request.getRaw(), al);
368  } else {
369  anErr = new ErrorState(ERR_SECURE_CONNECT_FAIL, Http::scServiceUnavailable, request.getRaw(), al);
370  anErr->detailError(errDetails);
371  /*anErr->xerrno= Should preserved*/
372  }
373 
374  noteNegotiationDone(anErr);
375  bail(anErr);
376  return;
377 }
378 #endif
379 
380 #if USE_OPENSSL
386 {
387  Must(Comm::IsConnOpen(serverConnection()));
388 
389  Security::SessionPointer session(fd_table[serverConnection()->fd].ssl);
390 
391  std::optional<ACLFilledChecklist> check;
392  if (acl_access *acl = ::Config.ssl_client.cert_error) {
393  check.emplace(acl, request.getRaw());
394  fillChecklist(*check);
395  }
396 
397  Security::CertErrors *errs = nullptr;
398  typedef Ssl::CertValidationResponse::RecvdErrors::const_iterator SVCRECI;
399  for (SVCRECI i = resp.errors.begin(); i != resp.errors.end(); ++i) {
400  debugs(83, 7, "Error item: " << i->error_no << " " << i->error_reason);
401 
402  assert(i->error_no != SSL_ERROR_NONE);
403 
404  if (!errDetails) {
405  bool allowed = false;
406  if (check) {
407  const auto sslErrors = std::make_unique<Security::CertErrors>(Security::CertError(i->error_no, i->cert, i->error_depth));
408  check->sslErrors = sslErrors.get();
409  if (check->fastCheck().allowed())
410  allowed = true;
411  check->sslErrors.clear();
412  }
413  // else the Config.ssl_client.cert_error access list is not defined
414  // and the first error will cause the error page
415 
416  if (allowed) {
417  debugs(83, 3, "bypassing SSL error " << i->error_no << " in " << "buffer");
418  } else {
419  debugs(83, 5, "confirming SSL error " << i->error_no);
420  const auto &brokenCert = i->cert;
421  Security::CertPointer peerCert(SSL_get_peer_certificate(session.get()));
422  const char *aReason = i->error_reason.empty() ? nullptr : i->error_reason.c_str();
423  errDetails = new ErrorDetail(i->error_no, peerCert, brokenCert, aReason);
424  }
425  }
426 
427  if (!errs)
428  errs = new Security::CertErrors(Security::CertError(i->error_no, i->cert, i->error_depth));
429  else
430  errs->push_back_unique(Security::CertError(i->error_no, i->cert, i->error_depth));
431  }
432 
433  return errs;
434 }
435 #endif
436 
438 void
440 {
441  const auto pc = static_cast<PeerConnector::Pointer*>(data);
442  if (pc->valid())
443  (*pc)->negotiateSsl();
444  delete pc;
445 }
446 
448 void
450 {
451  // Use job calls to add done() checks and other job logic/protections.
452  CallJobHere(83, 7, this, Security::PeerConnector, negotiate);
453 }
454 
455 void
457 {
458  debugs(83, 5, serverConnection());
459 
460  Must(Comm::IsConnOpen(serverConnection()));
461  const int fd = serverConnection()->fd;
462 
463  // read timeout to avoid getting stuck while reading from a silent server
465  AsyncCall::Pointer timeoutCall = JobCallback(83, 5,
466  TimeoutDialer, this, Security::PeerConnector::commTimeoutHandler);
467  const auto timeout = Comm::MortalReadTimeout(startTime, negotiationTimeout);
468  commSetConnTimeout(serverConnection(), timeout, timeoutCall);
469 
470  Comm::SetSelect(fd, COMM_SELECT_READ, &NegotiateSsl, new Pointer(this), 0);
471 }
472 
473 void
475 {
476  debugs(83, 5, serverConnection());
477  Must(Comm::IsConnOpen(serverConnection()));
478 
479  const int fd = serverConnection()->fd;
480  Comm::SetSelect(fd, COMM_SELECT_WRITE, &NegotiateSsl, new Pointer(this), 0);
481  return;
482 }
483 
484 void
486 {
487  const auto anErr = ErrorState::NewForwarding(ERR_SECURE_CONNECT_FAIL, request, al);
488  if (detail) {
489  anErr->xerrno = detail->sysError();
490  anErr->detailError(detail);
491  }
492  noteNegotiationDone(anErr);
493  bail(anErr);
494 }
495 
498 {
499  assert(callback);
500  return callback.answer();
501 }
502 
503 void
505 {
506  Must(error); // or the recipient will not know there was a problem
507  answer().error = error;
508 
509  if (const auto failingConnection = serverConn) {
510  countFailingConnection();
511  disconnect();
512  failingConnection->close();
513  }
514 
515  callBack();
516 }
517 
518 void
520 {
521  assert(Comm::IsConnOpen(serverConn));
522  answer().conn = serverConn;
523  disconnect();
524  callBack();
525 }
526 
527 void
529 {
530  assert(serverConn);
531  NoteOutgoingConnectionFailure(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  ScheduleCallHere(callback.release());
559  Assure(done());
560 }
561 
562 void
564 {
565  // XXX: unregister fd-closure monitoring and CommSetSelect interest, if any
567 
568  if (callback) {
569  // job-ending emergencies like handleStopRequest() or callException()
570  const auto anErr = new ErrorState(ERR_GATEWAY_FAILURE, Http::scInternalServerError, request.getRaw(), al);
571  bail(anErr);
572  assert(!callback);
573  return;
574  }
575 }
576 
577 const char *
579 {
580  static MemBuf buf;
581  buf.reset();
582 
583  // TODO: redesign AsyncJob::status() API to avoid this
584  // id and stop reason reporting duplication.
585  buf.append(" [", 2);
586  if (stopReason != nullptr) {
587  buf.append("Stopped, reason:", 16);
588  buf.appendf("%s",stopReason);
589  }
590  if (Comm::IsConnOpen(serverConn))
591  buf.appendf(" FD %d", serverConn->fd);
592  buf.appendf(" %s%u]", id.prefix(), id.value);
593  buf.terminate();
594 
595  return buf.content();
596 }
597 
598 #if USE_OPENSSL
599 unsigned int
602 {
603  if (request) {
604  // Nesting level increases when a PeerConnector (at level L) creates a
605  // Downloader (which is assigned level L+1). If we were initiated by
606  // such a Downloader, then their nesting level is our nesting level.
607  if (const auto previousDownloader = request->downloader.get())
608  return previousDownloader->nestedLevel();
609  }
610  return 0; // no other PeerConnector job waits for us
611 }
612 
613 void
615 {
616  const auto certCallback = asyncCallback(81, 4, Security::PeerConnector::certDownloadingDone, this);
617  const auto dl = new Downloader(url, certCallback,
618  MasterXaction::MakePortless<XactionInitiator::initCertFetcher>(),
619  certDownloadNestingLevel() + 1);
620  certDownloadWait.start(dl, certCallback);
621 }
622 
623 void
625 {
626  certDownloadWait.finish();
627 
628  ++certsDownloads;
629  debugs(81, 5, "outcome: " << downloaderAnswer.outcome << "; certificate size: " << downloaderAnswer.resource.length());
630 
631  Must(Comm::IsConnOpen(serverConnection()));
632  const auto &sconn = *fd_table[serverConnection()->fd].ssl;
633 
634  // XXX: Do not parse the response when the download has failed.
635  // Parse Certificate. Assume that it is in DER format.
636  // According to RFC 4325:
637  // The server must provide a DER encoded certificate or a collection
638  // collection of certificates in a "certs-only" CMS message.
639  // The applications MUST accept DER encoded certificates and SHOULD
640  // be able to accept collection of certificates.
641  // TODO: support collection of certificates
642  auto raw = reinterpret_cast<const unsigned char*>(downloaderAnswer.resource.rawContent());
643  if (auto cert = d2i_X509(nullptr, &raw, downloaderAnswer.resource.length())) {
644  debugs(81, 5, "Retrieved certificate: " << *cert);
645 
646  if (!downloadedCerts)
647  downloadedCerts.reset(sk_X509_new_null());
648  sk_X509_push(downloadedCerts.get(), cert);
649 
650  const auto ctx = peerContext()->raw;
651  const auto certsList = SSL_get_peer_cert_chain(&sconn);
652  if (!Ssl::findIssuerCertificate(cert, certsList, ctx)) {
653  if (const auto issuerUri = Ssl::findIssuerUri(cert)) {
654  debugs(81, 5, "certificate " << *cert <<
655  " points to its missing issuer certificate at " << issuerUri);
656  urlsOfMissingCerts.push(SBuf(issuerUri));
657  } else {
658  debugs(81, 3, "found a certificate with no IAI, " <<
659  "signed by a missing issuer certificate: " << *cert);
660  // We could short-circuit here, proceeding to chain validation
661  // that is likely to fail. Instead, we keep going because we
662  // hope that if we find at least one certificate to fetch, it
663  // will complete the chain (that contained extra certificates).
664  }
665  }
666  }
667 
668  // Check if there are URIs to download from and if yes start downloading
669  // the first in queue.
670  if (urlsOfMissingCerts.size() && certsDownloads <= MaxCertsDownloads) {
671  startCertDownloading(urlsOfMissingCerts.front());
672  urlsOfMissingCerts.pop();
673  return;
674  }
675 
676  resumeNegotiation();
677 }
678 
679 void
681 {
682  Must(Comm::IsConnOpen(serverConnection()));
683  auto &sconn = *fd_table[serverConnection()->fd].ssl;
684 
685  // We download the missing certificate(s) once. We would prefer to clear
686  // this right after the first validation, but that ideal place is _inside_
687  // OpenSSL if validation is triggered by SSL_connect(). That function and
688  // our OpenSSL verify_callback function (\ref OpenSSL_vcb_disambiguation)
689  // may be called multiple times, so we cannot reset there.
690  auto &callerHandlesMissingCertificates = Ssl::VerifyCallbackParameters::At(sconn).callerHandlesMissingCertificates;
691  Must(callerHandlesMissingCertificates);
692  callerHandlesMissingCertificates = false;
693 
694  suspendNegotiation(ioResult);
695 
696  if (!computeMissingCertificateUrls(sconn))
697  return resumeNegotiation();
698 
699  assert(!urlsOfMissingCerts.empty());
700  startCertDownloading(urlsOfMissingCerts.front());
701  urlsOfMissingCerts.pop();
702 }
703 
705 bool
707 {
708  const auto certs = SSL_get_peer_cert_chain(&sconn);
709  if (!certs) {
710  debugs(83, 3, "nothing to bootstrap the fetch with");
711  return false;
712  }
713  debugs(83, 5, "server certificates: " << sk_X509_num(certs));
714 
715  const auto optionalContext = peerContext();
716  if (!optionalContext) {
717  debugs(83, 3, "cannot compute due to disabled TLS support");
718  return false;
719  }
720  const auto ctx = optionalContext->raw;
721  if (!Ssl::missingChainCertificatesUrls(urlsOfMissingCerts, *certs, ctx))
722  return false; // missingChainCertificatesUrls() reports the exact reason
723 
724  debugs(83, 5, "URLs: " << urlsOfMissingCerts.size());
725  assert(!urlsOfMissingCerts.empty());
726  return true;
727 }
728 
729 void
731 {
732  debugs(83, 5, "after " << ioResult);
733  Must(!isSuspended());
734  suspendedError_ = new Security::IoResult(ioResult);
735  Must(isSuspended());
736  // negotiations resume with a resumeNegotiation() call
737 }
738 
739 void
741 {
742  Must(isSuspended());
743 
744  auto lastError = suspendedError_; // may be reset below
745  suspendedError_ = nullptr;
746 
747  auto &sconn = *fd_table[serverConnection()->fd].ssl;
748  if (!Ssl::VerifyConnCertificates(sconn, downloadedCerts)) {
749  // simulate an earlier SSL_connect() failure with a new error
750  // TODO: When we can use Security::ErrorDetail, we should resume with a
751  // detailed _validation_ error, not just a generic SSL_ERROR_SSL!
752  const ErrorDetail::Pointer errorDetail = new ErrorDetail(SQUID_TLS_ERR_CONNECT, SSL_ERROR_SSL, 0);
753  lastError = new Security::IoResult(errorDetail);
754  }
755 
756  handleNegotiationResult(*lastError);
757 }
758 
759 #endif //USE_OPENSSL
760 
@ ERR_SECURE_CONNECT_FAIL
Definition: forward.h:31
AsyncCall::Pointer comm_add_close_handler(int fd, CLCB *handler, void *data)
Definition: comm.cc:952
Security::SessionPointer ssl
void commUnsetConnTimeout(const Comm::ConnectionPointer &conn)
Definition: comm.cc:616
void terminate()
Definition: MemBuf.cc:241
void appendf(const char *fmt,...) PRINTF_FORMAT_ARG2
Append operation with printf-style arguments.
Definition: Packable.h:61
@ SQUID_TLS_ERR_CONNECT
failure to establish a connection with a TLS server
Definition: forward.h:234
bool missingChainCertificatesUrls(std::queue< SBuf > &URIs, const STACK_OF(X509) &serverCertificates, const Security::ContextPointer &context)
Definition: support.cc:1324
virtual bool initialize(Security::SessionPointer &)
@ ERR_GATEWAY_FAILURE
Definition: forward.h:67
PeerConnector(const Comm::ConnectionPointer &aServerConn, const AsyncCallback< EncryptorAnswer > &, const AccessLogEntryPointer &alp, const time_t timeout=0)
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:61
void sslCrtvdHandleReply(Ssl::CertValidationResponsePointer &)
Process response from cert validator helper.
ErrorDetailPointer errorDetail
ioError case details (or nil)
Definition: Io.h:43
acl_access * cert_error
Definition: SquidConfig.h:512
#define ScheduleCallHere(call)
Definition: AsyncCall.h:166
void commTimeoutHandler(const CommTimeoutCbParams &)
The connection read timeout callback handler.
void error(char *format,...)
Definition: SBuf.h:93
a smart AsyncCall pointer for delivery of future results
RecvdErrors errors
The list of parsed errors.
static void Submit(const Ssl::CertValidationRequest &, const Callback &)
Submit crtd request message to external crtd server.
Definition: helper.cc:298
static VerifyCallbackParameters & At(Security::Connection &)
Definition: support.cc:642
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:545
void swanSong() override
int fd
FD which the call was about. Set by the async call creator.
Definition: CommCalls.h:85
static VerifyCallbackParameters * New(Security::Connection &)
Definition: support.cc:630
void detailError(const ErrorDetail::Pointer &dCode)
set error type-specific detail code
Definition: errorpage.h:111
AccessLogEntry::Pointer al
info for the future access.log, and external ACL
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
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
void syncAle(HttpRequest *adaptedRequest, const char *logUri) const override
assigns uninitialized adapted_request and url ALE components
const char * status() const override
internal cleanup; do not call directly
struct SquidConfig::@106 ssl_client
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:75
void disconnect()
a bail(), sendSuccess() helper: stops monitoring the connection
Http::StatusCode outcome
Definition: Downloader.h:36
@ scGatewayTimeout
Definition: StatusCode.h:77
void handleMissingCertificates(const Security::IoResult &lastError)
Either initiates fetching of missing certificates or bails with an error.
static MakingPointer Make(const acl_access *a, HttpRequest *r)
void certDownloadingDone(DownloaderAnswer &)
Called by Downloader after a certificate object downloaded.
void append(const char *c, int sz) override
Definition: MemBuf.cc:209
Security::CertPointer findIssuerCertificate(X509 *cert, const STACK_OF(X509) *serverCertificates, const Security::ContextPointer &context)
Definition: support.cc:1298
Security::CertPointer serverCert
virtual bool doneAll() const
whether positive goal has been reached
Definition: AsyncJob.cc:112
const char * rawContent() const
Definition: SBuf.cc:509
int sysError() const
Definition: ErrorDetail.h:70
Category category
primary outcome classification
Definition: Io.h:45
bool VerifyConnCertificates(Security::Connection &, const Ssl::X509_STACK_Pointer &extraCerts)
Definition: support.cc:532
void suspendNegotiation(const Security::IoResult &lastError)
@ scBadGateway
Definition: StatusCode.h:75
Definition: MemBuf.h:23
virtual void noteNegotiationError(const Security::ErrorDetailPointer &)
Called when the SSL_connect function aborts with an SSL negotiation error.
void NoteOutgoingConnectionFailure(CachePeer *const peer)
Definition: CachePeer.h:246
#define CallJobHere(debugSection, debugLevel, job, Class, method)
Definition: AsyncJobCalls.h:59
const char * findIssuerUri(X509 *cert)
finds certificate issuer URI in the Authority Info Access extension
Definition: support.cc:1181
Config TheConfig
Definition: Config.cc:12
void countFailingConnection()
updates connection usage history before the connection is closed
#define assert(EX)
Definition: assert.h:17
@ Okay
Definition: ResultCode.h:18
SSL Connection
Definition: Session.h:49
Security::CertErrors * sslCrtvdCheckForErrors(Ssl::CertValidationResponse const &, ErrorDetailPointer &)
Check SSL errors returned from cert validator against sslproxy_cert_error access list.
@ scServiceUnavailable
Definition: StatusCode.h:76
#define COMM_SELECT_READ
Definition: defines.h:24
void negotiateSsl()
Comm::SetSelect() callback. Direct calls tickle/resume negotiations.
#define Assure(condition)
Definition: Assure.h:35
#define JobCallback(dbgSection, dbgLevel, Dialer, job, method)
Convenience macro to create a Dialer-based job callback.
Definition: AsyncJobCalls.h:70
@ scInternalServerError
Definition: StatusCode.h:73
#define asyncCallback(dbgSection, dbgLevel, method, object)
size_type length() const
Returns the number of bytes stored in SBuf.
Definition: SBuf.h:419
CbDataList< Security::CertError > CertErrors
Holds a list of X.509 certificate errors.
Definition: forward.h:74
time_t squid_curtime
Definition: stub_libtime.cc:20
static std::ostream & Extra(std::ostream &)
Definition: debug.cc:1316
bool doneAll() const override
whether positive goal has been reached
int ssl_ex_index_server
#define fd_table
Definition: fde.h:189
bool push_back_unique(C const &element)
Definition: CbDataList.h:87
void start() override
Preps connection and SSL state. Calls negotiate().
std::shared_ptr< SSL > SessionPointer
Definition: Session.h:53
void commSetConnTimeout(const Comm::ConnectionPointer &conn, time_t timeout, AsyncCall::Pointer &callback)
Definition: comm.cc:592
void handleNegotiationResult(const Security::IoResult &)
Called after each negotiation step to handle the result.
bool CreateClientSession(FuturePeerContext &, const Comm::ConnectionPointer &, const char *squidCtx)
Definition: Session.cc:183
Helps prints T object using object's T::printWithExtras() method.
Definition: IoManip.h:293
void SetSelect(int, unsigned int, PF *, void *, time_t)
Mark an FD to be watched for its IO status.
Definition: ModDevPoll.cc:220
static ErrorState * NewForwarding(err_type, HttpRequestPointer &, const AccessLogEntryPointer &)
Creates a general request forwarding error with the right http_status.
Definition: errorpage.cc:691
char * content()
start of the added data
Definition: MemBuf.h:41
static char server[MAXLINE]
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:226
@ ERR_CONNECT_FAIL
Definition: forward.h:30
void fillChecklist(ACLFilledChecklist &) const override
configure the given checklist (to reflect the current transaction state)
virtual void start()
called by AsyncStart; do not call directly
Definition: AsyncJob.cc:59
#define Must(condition)
Definition: TextException.h:75
#define DBG_IMPORTANT
Definition: Stream.h:38
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
@ 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:73
virtual void noteWantWrite()
#define debugs(SECTION, LEVEL, CONTENT)
Definition: Stream.h:192
#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:981
class SquidConfig Config
Definition: SquidConfig.cc:12
void sendSuccess()
sends the encrypted connection to the initiator
download result
Definition: Downloader.h:28
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