/*
- * Copyright (C) 1996-2016 The Squid Software Foundation and contributors
+ * Copyright (C) 1996-2018 The Squid Software Foundation and contributors
*
* Squid software is distributed under GPLv2+ license and includes
* contributions from numerous individuals and organizations.
#include "squid.h"
#include "acl/FilledChecklist.h"
#include "comm/Loops.h"
+#include "Downloader.h"
#include "errorpage.h"
#include "fde.h"
+#include "http/Stream.h"
#include "HttpRequest.h"
#include "security/NegotiationHistory.h"
#include "security/PeerConnector.h"
callback(aCallback),
negotiationTimeout(timeout),
startTime(squid_curtime),
- useCertValidator_(true)
+ useCertValidator_(true),
+ certsDownloads(0)
{
debugs(83, 5, "Security::PeerConnector constructed, this=" << (void*)this);
// if this throws, the caller's cb dialer is not our CbDialer
Security::PeerConnector::start()
{
AsyncJob::start();
+ debugs(83, 5, "this=" << (void*)this);
Security::SessionPointer tmp;
- if (prepareSocket() && initializeTls(tmp))
- negotiateSsl();
+ if (prepareSocket() && initialize(tmp))
+ negotiate();
+ else
+ mustStop("Security::PeerConnector TLS socket initialize failed");
}
void
void
Security::PeerConnector::connectionClosed(const char *reason)
{
+ debugs(83, 5, reason << " socket closed/closing. this=" << (void*)this);
mustStop(reason);
callback = NULL;
}
bool
Security::PeerConnector::prepareSocket()
{
- const int fd = serverConnection()->fd;
- if (!Comm::IsConnOpen(serverConn) || fd_table[serverConn->fd].closing()) {
+ debugs(83, 5, serverConnection() << ", this=" << (void*)this);
+ if (!Comm::IsConnOpen(serverConnection()) || fd_table[serverConnection()->fd].closing()) {
connectionClosed("Security::PeerConnector::prepareSocket");
return false;
}
+ debugs(83, 5, serverConnection());
+
// watch for external connection closures
typedef CommCbMemFunT<Security::PeerConnector, CommCloseCbParams> Dialer;
closeHandler = JobCallback(9, 5, Dialer, this, Security::PeerConnector::commCloseHandler);
- comm_add_close_handler(fd, closeHandler);
+ comm_add_close_handler(serverConnection()->fd, closeHandler);
return true;
}
bool
-Security::PeerConnector::initializeTls(Security::SessionPointer &serverSession)
+Security::PeerConnector::initialize(Security::SessionPointer &serverSession)
{
-#if USE_OPENSSL
- Security::ContextPtr sslContext(getSslContext());
- assert(sslContext);
-
- if (!Ssl::CreateClient(sslContext, serverConnection(), "server https start")) {
+ Security::ContextPointer ctx(getTlsContext());
+ debugs(83, 5, serverConnection() << ", ctx=" << (void*)ctx.get());
+
+ if (!ctx || !Security::CreateClientSession(ctx, serverConnection(), "server https start")) {
+ const auto xerrno = errno;
+ if (!ctx) {
+ debugs(83, DBG_IMPORTANT, "Error initializing TLS connection: No security context.");
+ } // else CreateClientSession() did the appropriate debugs() already
ErrorState *anErr = new ErrorState(ERR_SOCKET_FAILURE, Http::scInternalServerError, request.getRaw());
- anErr->xerrno = errno;
- debugs(83, DBG_IMPORTANT, "Error allocating SSL handle: " << ERR_error_string(ERR_get_error(), NULL));
+ anErr->xerrno = xerrno;
noteNegotiationDone(anErr);
bail(anErr);
return false;
// A TLS/SSL session has now been created for the connection and stored in fd_table
serverSession = fd_table[serverConnection()->fd].ssl;
+ debugs(83, 5, serverConnection() << ", session=" << (void*)serverSession.get());
+#if USE_OPENSSL
// If CertValidation Helper used do not lookup checklist for errors,
// but keep a list of errors to send it to CertValidator
if (!Ssl::TheConfig.ssl_crt_validator) {
if (acl_access *acl = ::Config.ssl_client.cert_error) {
ACLFilledChecklist *check = new ACLFilledChecklist(acl, request.getRaw(), dash_str);
check->al = al;
+ check->syncAle(request.getRaw(), nullptr);
// check->fd(fd); XXX: need client FD here
SSL_set_ex_data(serverSession.get(), ssl_ex_index_cert_error_check, check);
}
}
+#endif
return true;
-#else
- return false;
-#endif
}
void
void
Security::PeerConnector::recordNegotiationDetails()
{
-#if USE_OPENSSL
const int fd = serverConnection()->fd;
- Security::SessionPtr ssl = fd_table[fd].ssl.get();
+ Security::SessionPointer session(fd_table[fd].ssl);
// retrieve TLS server negotiated information if any
- serverConnection()->tlsNegotiations()->retrieveNegotiatedInfo(ssl);
+ serverConnection()->tlsNegotiations()->retrieveNegotiatedInfo(session);
+
+#if USE_OPENSSL
// retrieve TLS parsed extra info
- BIO *b = SSL_get_rbio(ssl);
- Ssl::ServerBio *bio = static_cast<Ssl::ServerBio *>(b->ptr);
+ BIO *b = SSL_get_rbio(session.get());
+ Ssl::ServerBio *bio = static_cast<Ssl::ServerBio *>(BIO_get_data(b));
if (const Security::TlsDetails::Pointer &details = bio->receivedHelloDetails())
serverConnection()->tlsNegotiations()->retrieveParsedInfo(details);
#endif
}
void
-Security::PeerConnector::negotiateSsl()
+Security::PeerConnector::negotiate()
{
- if (!Comm::IsConnOpen(serverConnection()) || fd_table[serverConnection()->fd].closing())
+ if (!Comm::IsConnOpen(serverConnection()))
return;
-#if USE_OPENSSL
const int fd = serverConnection()->fd;
- Security::SessionPtr ssl = fd_table[fd].ssl.get();
- const int result = SSL_connect(ssl);
+ if (fd_table[fd].closing())
+ return;
+
+#if USE_OPENSSL
+ auto session = fd_table[fd].ssl.get();
+ debugs(83, 5, "SSL_connect session=" << (void*)session);
+ const int result = SSL_connect(session);
+ if (result <= 0) {
+#elif USE_GNUTLS
+ auto session = fd_table[fd].ssl.get();
+ const int result = gnutls_handshake(session);
+ debugs(83, 5, "gnutls_handshake session=" << (void*)session << ", result=" << result);
+
+ if (result == GNUTLS_E_SUCCESS) {
+ char *desc = gnutls_session_get_desc(session);
+ debugs(83, 2, serverConnection() << " TLS Session info: " << desc);
+ gnutls_free(desc);
+ }
+
+ if (result != GNUTLS_E_SUCCESS) {
+ // debug the TLS session state so far
+ auto descIn = gnutls_handshake_get_last_in(session);
+ debugs(83, 2, "handshake IN: " << gnutls_handshake_description_get_name(descIn));
+ auto descOut = gnutls_handshake_get_last_out(session);
+ debugs(83, 2, "handshake OUT: " << gnutls_handshake_description_get_name(descOut));
#else
- const int result = -1;
+ if (const int result = -1) {
#endif
- if (result <= 0) {
handleNegotiateError(result);
return; // we might be gone by now
}
#if USE_OPENSSL
if (Ssl::TheConfig.ssl_crt_validator && useCertValidator_) {
const int fd = serverConnection()->fd;
- Security::SessionPtr ssl = fd_table[fd].ssl.get();
+ Security::SessionPointer session(fd_table[fd].ssl);
Ssl::CertValidationRequest validationRequest;
- // WARNING: Currently we do not use any locking for any of the
- // members of the Ssl::CertValidationRequest class. In this code the
+ // WARNING: Currently we do not use any locking for 'errors' member
+ // of the Ssl::CertValidationRequest class. In this code the
// Ssl::CertValidationRequest object used only to pass data to
// Ssl::CertValidationHelper::submit method.
- validationRequest.ssl = ssl;
- validationRequest.domainName = request->url.host();
- if (Ssl::CertErrors *errs = static_cast<Ssl::CertErrors *>(SSL_get_ex_data(ssl, ssl_ex_index_ssl_errors)))
+ validationRequest.ssl = session;
+ if (SBuf *dName = (SBuf *)SSL_get_ex_data(session.get(), ssl_ex_index_server))
+ validationRequest.domainName = dName->c_str();
+ if (Security::CertErrors *errs = static_cast<Security::CertErrors *>(SSL_get_ex_data(session.get(), ssl_ex_index_ssl_errors)))
// validationRequest disappears on return so no need to cbdataReference
validationRequest.errors = errs;
- else
- validationRequest.errors = NULL;
try {
debugs(83, 5, "Sending SSL certificate for validation to ssl_crtvd.");
AsyncCall::Pointer call = asyncCall(83,5, "Security::PeerConnector::sslCrtvdHandleReply", Ssl::CertValidationHelper::CbDialer(this, &Security::PeerConnector::sslCrtvdHandleReply, nullptr));
- Ssl::CertValidationHelper::GetInstance()->sslSubmit(validationRequest, call);
+ Ssl::CertValidationHelper::Submit(validationRequest, call);
return false;
} catch (const std::exception &e) {
debugs(83, DBG_IMPORTANT, "ERROR: Failed to compose ssl_crtvd " <<
return;
}
- debugs(83,5, request->url.host() << " cert validation result: " << validationResponse->resultCode);
+ if (Debug::Enabled(83, 5)) {
+ Security::SessionPointer ssl(fd_table[serverConnection()->fd].ssl);
+ SBuf *server = static_cast<SBuf *>(SSL_get_ex_data(ssl.get(), ssl_ex_index_server));
+ debugs(83,5, RawPointer("host", server) << " cert validation result: " << validationResponse->resultCode);
+ }
if (validationResponse->resultCode == ::Helper::Error) {
- if (Ssl::CertErrors *errs = sslCrtvdCheckForErrors(*validationResponse, errDetails)) {
- Security::SessionPtr ssl = fd_table[serverConnection()->fd].ssl.get();
- Ssl::CertErrors *oldErrs = static_cast<Ssl::CertErrors*>(SSL_get_ex_data(ssl, ssl_ex_index_ssl_errors));
- SSL_set_ex_data(ssl, ssl_ex_index_ssl_errors, (void *)errs);
+ if (Security::CertErrors *errs = sslCrtvdCheckForErrors(*validationResponse, errDetails)) {
+ Security::SessionPointer session(fd_table[serverConnection()->fd].ssl);
+ Security::CertErrors *oldErrs = static_cast<Security::CertErrors*>(SSL_get_ex_data(session.get(), ssl_ex_index_ssl_errors));
+ SSL_set_ex_data(session.get(), ssl_ex_index_ssl_errors, (void *)errs);
delete oldErrs;
}
} else if (validationResponse->resultCode != ::Helper::Okay)
#if USE_OPENSSL
/// Checks errors in the cert. validator response against sslproxy_cert_error.
/// The first honored error, if any, is returned via errDetails parameter.
-/// The method returns all seen errors except SSL_ERROR_NONE as Ssl::CertErrors.
-Ssl::CertErrors *
+/// The method returns all seen errors except SSL_ERROR_NONE as Security::CertErrors.
+Security::CertErrors *
Security::PeerConnector::sslCrtvdCheckForErrors(Ssl::CertValidationResponse const &resp, Ssl::ErrorDetail *& errDetails)
{
- Ssl::CertErrors *errs = NULL;
-
ACLFilledChecklist *check = NULL;
if (acl_access *acl = ::Config.ssl_client.cert_error) {
check = new ACLFilledChecklist(acl, request.getRaw(), dash_str);
check->al = al;
+ check->syncAle(request.getRaw(), nullptr);
}
- Security::SessionPtr ssl = fd_table[serverConnection()->fd].ssl.get();
+ Security::CertErrors *errs = nullptr;
+ Security::SessionPointer session(fd_table[serverConnection()->fd].ssl);
typedef Ssl::CertValidationResponse::RecvdErrors::const_iterator SVCRECI;
for (SVCRECI i = resp.errors.begin(); i != resp.errors.end(); ++i) {
debugs(83, 7, "Error item: " << i->error_no << " " << i->error_reason);
if (!errDetails) {
bool allowed = false;
if (check) {
- check->sslErrors = new Ssl::CertErrors(Ssl::CertError(i->error_no, i->cert.get(), i->error_depth));
- if (check->fastCheck() == ACCESS_ALLOWED)
+ check->sslErrors = new Security::CertErrors(Security::CertError(i->error_no, i->cert, i->error_depth));
+ if (check->fastCheck().allowed())
allowed = true;
}
// else the Config.ssl_client.cert_error access list is not defined
} else {
debugs(83, 5, "confirming SSL error " << i->error_no);
X509 *brokenCert = i->cert.get();
- Security::CertPointer peerCert(SSL_get_peer_certificate(ssl));
+ Security::CertPointer peerCert(SSL_get_peer_certificate(session.get()));
const char *aReason = i->error_reason.empty() ? NULL : i->error_reason.c_str();
errDetails = new Ssl::ErrorDetail(i->error_no, peerCert.get(), brokenCert, aReason);
}
}
if (!errs)
- errs = new Ssl::CertErrors(Ssl::CertError(i->error_no, i->cert.get(), i->error_depth));
+ errs = new Security::CertErrors(Security::CertError(i->error_no, i->cert, i->error_depth));
else
- errs->push_back_unique(Ssl::CertError(i->error_no, i->cert.get(), i->error_depth));
+ errs->push_back_unique(Security::CertError(i->error_no, i->cert, i->error_depth));
}
if (check)
delete check;
void
Security::PeerConnector::NegotiateSsl(int, void *data)
{
- PeerConnector *pc = static_cast<PeerConnector*>(data);
+ const auto pc = static_cast<PeerConnector::Pointer*>(data);
+ if (pc->valid())
+ (*pc)->negotiateSsl();
+ delete pc;
+}
+
+/// Comm::SetSelect() callback. Direct calls tickle/resume negotiations.
+void
+Security::PeerConnector::negotiateSsl()
+{
// Use job calls to add done() checks and other job logic/protections.
- CallJobHere(83, 7, pc, Security::PeerConnector, negotiateSsl);
+ CallJobHere(83, 7, this, Security::PeerConnector, negotiate);
}
void
Security::PeerConnector::handleNegotiateError(const int ret)
{
-#if USE_OPENSSL
const int fd = serverConnection()->fd;
- unsigned long ssl_lib_error = SSL_ERROR_NONE;
- Security::SessionPtr ssl = fd_table[fd].ssl.get();
- const int ssl_error = SSL_get_error(ssl, ret);
+ const Security::SessionPointer session(fd_table[fd].ssl);
+ unsigned long ssl_lib_error = ret;
+
+#if USE_OPENSSL
+ const int ssl_error = SSL_get_error(session.get(), ret);
switch (ssl_error) {
case SSL_ERROR_WANT_READ:
ssl_lib_error = ERR_get_error();
// proceed to the general error handling code
break;
+ default:
+ // no special error handling for all other errors
+ ssl_lib_error = SSL_ERROR_NONE;
+ break;
+ }
+
+#elif USE_GNUTLS
+ const int ssl_error = ret;
+
+ switch (ret) {
+ case GNUTLS_E_WARNING_ALERT_RECEIVED: {
+ auto alert = gnutls_alert_get(session.get());
+ debugs(83, DBG_IMPORTANT, "TLS ALERT: " << gnutls_alert_get_name(alert));
+ }
+ // drop through to next case
+
+ case GNUTLS_E_AGAIN:
+ case GNUTLS_E_INTERRUPTED:
+ if (gnutls_record_get_direction(session.get()) == 0)
+ noteWantRead();
+ else
+ noteWantWrite();
+ return;
+
default:
// no special error handling for all other errors
break;
}
+#else
+ // this avoids unused variable compiler warnings.
+ Must(!session);
+ const int ssl_error = ret;
+#endif
+
// Log connection details, if any
recordNegotiationDetails();
- noteSslNegotiationError(ret, ssl_error, ssl_lib_error);
-#endif
+ noteNegotiationError(ret, ssl_error, ssl_lib_error);
}
void
Security::PeerConnector::noteWantRead()
{
- setReadTimeout();
const int fd = serverConnection()->fd;
- Comm::SetSelect(fd, COMM_SELECT_READ, &NegotiateSsl, this, 0);
+ debugs(83, 5, serverConnection());
+#if USE_OPENSSL
+ Security::SessionPointer session(fd_table[fd].ssl);
+ BIO *b = SSL_get_rbio(session.get());
+ Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(BIO_get_data(b));
+ if (srvBio->holdRead()) {
+ if (srvBio->gotHello()) {
+ if (checkForMissingCertificates())
+ return; // Wait to download certificates before proceed.
+
+ srvBio->holdRead(false);
+ // schedule a negotiateSSl to allow openSSL parse received data
+ negotiateSsl();
+ return;
+ } else if (srvBio->gotHelloFailed()) {
+ srvBio->holdRead(false);
+ debugs(83, DBG_IMPORTANT, "Error parsing SSL Server Hello Message on FD " << fd);
+ // schedule a negotiateSSl to allow openSSL parse received data
+ negotiateSsl();
+ return;
+ }
+ }
+#endif
+ setReadTimeout();
+ Comm::SetSelect(fd, COMM_SELECT_READ, &NegotiateSsl, new Pointer(this), 0);
}
void
Security::PeerConnector::noteWantWrite()
{
const int fd = serverConnection()->fd;
- Comm::SetSelect(fd, COMM_SELECT_WRITE, &NegotiateSsl, this, 0);
+ debugs(83, 5, serverConnection());
+ Comm::SetSelect(fd, COMM_SELECT_WRITE, &NegotiateSsl, new Pointer(this), 0);
return;
}
void
-Security::PeerConnector::noteSslNegotiationError(const int ret, const int ssl_error, const int ssl_lib_error)
+Security::PeerConnector::noteNegotiationError(const int ret, const int ssl_error, const int ssl_lib_error)
{
-#if USE_OPENSSL // not used unless OpenSSL enabled.
#if defined(EPROTO)
int sysErrNo = EPROTO;
#else
int sysErrNo = EACCES;
#endif
+#if USE_OPENSSL
// store/report errno when ssl_error is SSL_ERROR_SYSCALL, ssl_lib_error is 0, and ret is -1
if (ssl_error == SSL_ERROR_SYSCALL && ret == -1 && ssl_lib_error == 0)
sysErrNo = errno;
+#endif
+ int xerr = errno;
const int fd = serverConnection()->fd;
- debugs(83, DBG_IMPORTANT, "Error negotiating SSL on FD " << fd <<
- ": " << ERR_error_string(ssl_lib_error, NULL) << " (" <<
- ssl_error << "/" << ret << "/" << errno << ")");
+ debugs(83, DBG_IMPORTANT, "ERROR: negotiating TLS on FD " << fd <<
+ ": " << Security::ErrorString(ssl_lib_error) << " (" <<
+ ssl_error << "/" << ret << "/" << xerr << ")");
- ErrorState *anErr = NULL;
- if (request != NULL)
- anErr = ErrorState::NewForwarding(ERR_SECURE_CONNECT_FAIL, request.getRaw());
- else
- anErr = new ErrorState(ERR_SECURE_CONNECT_FAIL, Http::scServiceUnavailable, NULL);
+ ErrorState *anErr = ErrorState::NewForwarding(ERR_SECURE_CONNECT_FAIL, request);
anErr->xerrno = sysErrNo;
- Security::SessionPtr ssl = fd_table[fd].ssl.get();
- Ssl::ErrorDetail *errFromFailure = (Ssl::ErrorDetail *)SSL_get_ex_data(ssl, ssl_ex_index_ssl_error_detail);
+#if USE_OPENSSL
+ Security::SessionPointer session(fd_table[fd].ssl);
+ Ssl::ErrorDetail *errFromFailure = static_cast<Ssl::ErrorDetail *>(SSL_get_ex_data(session.get(), ssl_ex_index_ssl_error_detail));
if (errFromFailure != NULL) {
// The errFromFailure is attached to the ssl object
// and will be released when ssl object destroyed.
anErr->detail = new Ssl::ErrorDetail(*errFromFailure);
} else {
// server_cert can be NULL here
- X509 *server_cert = SSL_get_peer_certificate(ssl);
+ X509 *server_cert = SSL_get_peer_certificate(session.get());
anErr->detail = new Ssl::ErrorDetail(SQUID_ERR_SSL_HANDSHAKE, server_cert, NULL);
X509_free(server_cert);
}
if (ssl_lib_error != SSL_ERROR_NONE)
anErr->detail->setLibError(ssl_lib_error);
+#endif
noteNegotiationDone(anErr);
bail(anErr);
-#endif
}
void
void
Security::PeerConnector::callBack()
{
+ debugs(83, 5, "TLS setup ended for " << serverConnection());
+
AsyncCall::Pointer cb = callback;
// Do this now so that if we throw below, swanSong() assert that we _tried_
// to call back holds.
}
if (serverConn != NULL)
buf.appendf(" FD %d", serverConn->fd);
- buf.appendf(" %s%u]", id.Prefix, id.value);
+ buf.appendf(" %s%u]", id.prefix(), id.value);
buf.terminate();
return buf.content();
}
+#if USE_OPENSSL
+/// CallDialer to allow use Downloader objects within PeerConnector class.
+class PeerConnectorCertDownloaderDialer: public Downloader::CbDialer
+{
+public:
+ typedef void (Security::PeerConnector::*Method)(SBuf &object, int status);
+
+ PeerConnectorCertDownloaderDialer(Method method, Security::PeerConnector *pc):
+ method_(method),
+ peerConnector_(pc) {}
+
+ /* CallDialer API */
+ virtual bool canDial(AsyncCall &call) { return peerConnector_.valid(); }
+ virtual void dial(AsyncCall &call) { ((&(*peerConnector_))->*method_)(object, status); }
+ Method method_; ///< The Security::PeerConnector method to dial
+ CbcPointer<Security::PeerConnector> peerConnector_; ///< The Security::PeerConnector object
+};
+
+void
+Security::PeerConnector::startCertDownloading(SBuf &url)
+{
+ AsyncCall::Pointer certCallback = asyncCall(81, 4,
+ "Security::PeerConnector::certDownloadingDone",
+ PeerConnectorCertDownloaderDialer(&Security::PeerConnector::certDownloadingDone, this));
+
+ const Downloader *csd = (request ? dynamic_cast<const Downloader*>(request->downloader.valid()) : nullptr);
+ Downloader *dl = new Downloader(url, certCallback, XactionInitiator::initCertFetcher, csd ? csd->nestedLevel() + 1 : 1);
+ AsyncJob::Start(dl);
+}
+
+void
+Security::PeerConnector::certDownloadingDone(SBuf &obj, int downloadStatus)
+{
+ ++certsDownloads;
+ debugs(81, 5, "Certificate downloading status: " << downloadStatus << " certificate size: " << obj.length());
+
+ // get ServerBio from SSL object
+ const int fd = serverConnection()->fd;
+ Security::SessionPointer session(fd_table[fd].ssl);
+ BIO *b = SSL_get_rbio(session.get());
+ Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(BIO_get_data(b));
+
+ // Parse Certificate. Assume that it is in DER format.
+ // According to RFC 4325:
+ // The server must provide a DER encoded certificate or a collection
+ // collection of certificates in a "certs-only" CMS message.
+ // The applications MUST accept DER encoded certificates and SHOULD
+ // be able to accept collection of certificates.
+ // TODO: support collection of certificates
+ const unsigned char *raw = (const unsigned char*)obj.rawContent();
+ if (X509 *cert = d2i_X509(NULL, &raw, obj.length())) {
+ char buffer[1024];
+ debugs(81, 5, "Retrieved certificate: " << X509_NAME_oneline(X509_get_subject_name(cert), buffer, 1024));
+ ContextPointer ctx(getTlsContext());
+ const Security::CertList &certsList = srvBio->serverCertificatesIfAny();
+ if (const char *issuerUri = Ssl::uriOfIssuerIfMissing(cert, certsList, ctx)) {
+ urlsOfMissingCerts.push(SBuf(issuerUri));
+ }
+ Ssl::SSL_add_untrusted_cert(session.get(), cert);
+ }
+
+ // Check if there are URIs to download from and if yes start downloading
+ // the first in queue.
+ if (urlsOfMissingCerts.size() && certsDownloads <= MaxCertsDownloads) {
+ startCertDownloading(urlsOfMissingCerts.front());
+ urlsOfMissingCerts.pop();
+ return;
+ }
+
+ srvBio->holdRead(false);
+ negotiateSsl();
+}
+
+bool
+Security::PeerConnector::checkForMissingCertificates()
+{
+ // Check for nested SSL certificates downloads. For example when the
+ // certificate located in an SSL site which requires to download a
+ // a missing certificate (... from an SSL site which requires to ...).
+
+ const Downloader *csd = (request ? request->downloader.get() : nullptr);
+ if (csd && csd->nestedLevel() >= MaxNestedDownloads)
+ return false;
+
+ const int fd = serverConnection()->fd;
+ Security::SessionPointer session(fd_table[fd].ssl);
+ BIO *b = SSL_get_rbio(session.get());
+ Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(BIO_get_data(b));
+ const Security::CertList &certs = srvBio->serverCertificatesIfAny();
+
+ if (certs.size()) {
+ debugs(83, 5, "SSL server sent " << certs.size() << " certificates");
+ ContextPointer ctx(getTlsContext());
+ Ssl::missingChainCertificatesUrls(urlsOfMissingCerts, certs, ctx);
+ if (urlsOfMissingCerts.size()) {
+ startCertDownloading(urlsOfMissingCerts.front());
+ urlsOfMissingCerts.pop();
+ return true;
+ }
+ }
+
+ return false;
+}
+#endif //USE_OPENSSL
+