]> git.ipfire.org Git - thirdparty/squid.git/blobdiff - src/security/PeerConnector.cc
SourceFormat Enforcement
[thirdparty/squid.git] / src / security / PeerConnector.cc
index b3332088aeccf39b4e44f77fb2d27361fd6feae9..4a20ae457bac3adb1da7ea7a8d8aa40c7c00a298 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 1996-2016 The Squid Software Foundation and contributors
+ * Copyright (C) 1996-2017 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
@@ -57,8 +60,8 @@ Security::PeerConnector::start()
     AsyncJob::start();
 
     Security::SessionPointer tmp;
-    if (prepareSocket() && initializeTls(tmp))
-        negotiateSsl();
+    if (prepareSocket() && initialize(tmp))
+        negotiate();
     else
         mustStop("Security::PeerConnector TLS socket initialize failed");
 }
@@ -94,16 +97,18 @@ Security::PeerConnector::prepareSocket()
 }
 
 bool
-Security::PeerConnector::initializeTls(Security::SessionPointer &serverSession)
+Security::PeerConnector::initialize(Security::SessionPointer &serverSession)
 {
 #if USE_OPENSSL
-    Security::ContextPtr sslContext(getSslContext());
-    assert(sslContext);
+    Security::ContextPointer ctx(getTlsContext());
+    assert(ctx);
 
-    if (!Ssl::CreateClient(sslContext, serverConnection(), "server https start")) {
+    if (!Ssl::CreateClient(ctx, serverConnection(), "server https start")) {
+        const auto xerrno = errno;
+        const auto ssl_error = ERR_get_error();
         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;
+        debugs(83, DBG_IMPORTANT, "Error allocating TLS handle: " << Security::ErrorString(ssl_error));
         noteNegotiationDone(anErr);
         bail(anErr);
         return false;
@@ -148,30 +153,33 @@ 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
+    const int result = SSL_connect(fd_table[fd].ssl.get());
 #else
     const int result = -1;
 #endif
@@ -194,20 +202,19 @@ 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
         // 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.get();
+        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));
@@ -245,13 +252,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)
@@ -282,19 +293,18 @@ 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;
     }
 
-    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);
@@ -304,7 +314,7 @@ 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));
+                check->sslErrors = new Security::CertErrors(Security::CertError(i->error_no, i->cert, i->error_depth));
                 if (check->fastCheck() == ACCESS_ALLOWED)
                     allowed = true;
             }
@@ -316,7 +326,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);
             }
@@ -327,9 +337,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;
@@ -342,9 +352,9 @@ Security::PeerConnector::sslCrtvdCheckForErrors(Ssl::CertValidationResponse cons
 void
 Security::PeerConnector::NegotiateSsl(int, void *data)
 {
-    PeerConnector *pc = static_cast<PeerConnector*>(data);
+    PeerConnector *pc = static_cast<Security::PeerConnector *>(data);
     // Use job calls to add done() checks and other job logic/protections.
-    CallJobHere(83, 7, pc, Security::PeerConnector, negotiateSsl);
+    CallJobHere(83, 7, pc, Security::PeerConnector, negotiate);
 }
 
 void
@@ -353,8 +363,8 @@ 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);
+    Security::SessionPointer session(fd_table[fd].ssl);
+    const int ssl_error = SSL_get_error(session.get(), ret);
 
     switch (ssl_error) {
     case SSL_ERROR_WANT_READ:
@@ -377,15 +387,37 @@ Security::PeerConnector::handleNegotiateError(const int ret)
 
     // Log connection details, if any
     recordNegotiationDetails();
-    noteSslNegotiationError(ret, ssl_error, ssl_lib_error);
+    noteNegotiationError(ret, ssl_error, ssl_lib_error);
 #endif
 }
 
 void
 Security::PeerConnector::noteWantRead()
 {
-    setReadTimeout();
     const int fd = serverConnection()->fd;
+#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
+            Security::PeerConnector::NegotiateSsl(fd, this);
+            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
+            Security::PeerConnector::NegotiateSsl(fd, this);
+            return;
+        }
+    }
+#endif
+    setReadTimeout();
     Comm::SetSelect(fd, COMM_SELECT_READ, &NegotiateSsl, this, 0);
 }
 
@@ -398,7 +430,7 @@ Security::PeerConnector::noteWantWrite()
 }
 
 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)
@@ -413,7 +445,7 @@ Security::PeerConnector::noteSslNegotiationError(const int ret, const int ssl_er
 
     const int fd = serverConnection()->fd;
     debugs(83, DBG_IMPORTANT, "Error negotiating SSL on FD " << fd <<
-           ": " << ERR_error_string(ssl_lib_error, NULL) << " (" <<
+           ": " << Security::ErrorString(ssl_lib_error) << " (" <<
            ssl_error << "/" << ret << "/" << errno << ")");
 
     ErrorState *anErr = NULL;
@@ -423,8 +455,8 @@ Security::PeerConnector::noteSslNegotiationError(const int ret, const int ssl_er
         anErr = new ErrorState(ERR_SECURE_CONNECT_FAIL, Http::scServiceUnavailable, NULL);
     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);
+    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.
@@ -432,7 +464,7 @@ 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);
     }
@@ -509,9 +541,112 @@ 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, 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));
+        const Security::CertList &certsList = srvBio->serverCertificatesIfAny();
+        if (const char *issuerUri = Ssl::uriOfIssuerIfMissing(cert,  certsList)) {
+            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);
+    Security::PeerConnector::NegotiateSsl(serverConnection()->fd, this);
+}
+
+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");
+        Ssl::missingChainCertificatesUrls(urlsOfMissingCerts, certs);
+        if (urlsOfMissingCerts.size()) {
+            startCertDownloading(urlsOfMissingCerts.front());
+            urlsOfMissingCerts.pop();
+            return true;
+        }
+    }
+
+    return false;
+}
+#endif //USE_OPENSSL
+