]> git.ipfire.org Git - thirdparty/squid.git/blobdiff - src/security/PeerConnector.cc
Supply AccessLogEntry (ALE) for more fast ACL checks. (#182)
[thirdparty/squid.git] / src / security / PeerConnector.cc
index 75cedd72ba26554e43dca112cd0c8e2fcdb0ea28..4c63c5d87a37d6d4b7735ba7affeab63f75d8f06 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * 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"
@@ -33,7 +35,8 @@ Security::PeerConnector::PeerConnector(const Comm::ConnectionPointer &aServerCon
     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
@@ -55,10 +58,13 @@ void
 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
@@ -71,6 +77,7 @@ Security::PeerConnector::commCloseHandler(const CommCloseCbParams &params)
 void
 Security::PeerConnector::connectionClosed(const char *reason)
 {
+    debugs(83, 5, reason << " socket closed/closing. this=" << (void*)this);
     mustStop(reason);
     callback = NULL;
 }
@@ -78,30 +85,34 @@ Security::PeerConnector::connectionClosed(const char *reason)
 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;
@@ -109,7 +120,9 @@ Security::PeerConnector::initializeTls(Security::SessionPointer &serverSession)
 
     // 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) {
@@ -118,15 +131,14 @@ Security::PeerConnector::initializeTls(Security::SessionPointer &serverSession)
         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
@@ -146,34 +158,56 @@ Security::PeerConnector::setReadTimeout()
 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
     }
@@ -192,24 +226,23 @@ Security::PeerConnector::sslFinalized()
 #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 " <<
@@ -243,13 +276,17 @@ Security::PeerConnector::sslCrtvdHandleReply(Ssl::CertValidationResponse::Pointe
         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)
@@ -280,19 +317,19 @@ Security::PeerConnector::sslCrtvdHandleReply(Ssl::CertValidationResponse::Pointe
 #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);
@@ -302,8 +339,8 @@ Security::PeerConnector::sslCrtvdCheckForErrors(Ssl::CertValidationResponse cons
         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
@@ -314,7 +351,7 @@ Security::PeerConnector::sslCrtvdCheckForErrors(Ssl::CertValidationResponse cons
             } 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);
             }
@@ -325,9 +362,9 @@ Security::PeerConnector::sslCrtvdCheckForErrors(Ssl::CertValidationResponse cons
         }
 
         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;
@@ -340,19 +377,29 @@ Security::PeerConnector::sslCrtvdCheckForErrors(Ssl::CertValidationResponse cons
 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:
@@ -368,61 +415,113 @@ Security::PeerConnector::handleNegotiateError(const int ret)
         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.
@@ -430,17 +529,17 @@ Security::PeerConnector::noteSslNegotiationError(const int ret, const int ssl_er
         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
@@ -464,6 +563,8 @@ Security::PeerConnector::bail(ErrorState *error)
 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.
@@ -507,9 +608,114 @@ Security::PeerConnector::status() const
     }
     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
+