]> git.ipfire.org Git - thirdparty/squid.git/commitdiff
Fetch missing certificates.
authorAlex Rousskov <rousskov@measurement-factory.com>
Thu, 22 Oct 2015 18:34:42 +0000 (12:34 -0600)
committerAlex Rousskov <rousskov@measurement-factory.com>
Thu, 22 Oct 2015 18:34:42 +0000 (12:34 -0600)
Many web servers do not have complete certificate chains. Many browsers
use certificate extensions of the server certificate and download the
missing intermediate certificates automatically from the Internet.

This patch adds a similar feature to Squid:
   - Parse Server Hello messages and extract certificates chain.
   - Check whether the issuers of each certificate exist in the chain.
   - If not, retrieve the issuer certificate URI from Authority Info
     extension of the certificate (if it is provided) and download the
     certificate.
   - Store downloaded certificates in Squid object cache, just like any
     other HTTP object.

Implementation highlights:
  - A new Downloader class allows Squid subsystems to download objects
    via HTTP. These downloads are not backed by a proxy user.
  - Add support for an internal database of intermediate pre-loaded
    certificates to be used to complete incomplete chains.
  - Ssl::HandshakeParser parses TLS records and TLS Handshake messages.
  - Ssl::PeerConnector now uses the Downloader objects to download
    missing certificates.

16 files changed:
src/Downloader.cc [new file with mode: 0644]
src/Downloader.h [new file with mode: 0644]
src/Makefile.am
src/SquidConfig.h
src/cache_cf.cc
src/cf.data.pre
src/client_side.cc
src/client_side.h
src/client_side_reply.cc
src/client_side_request.cc
src/ssl/PeerConnector.cc
src/ssl/PeerConnector.h
src/ssl/bio.cc
src/ssl/bio.h
src/ssl/support.cc
src/ssl/support.h

diff --git a/src/Downloader.cc b/src/Downloader.cc
new file mode 100644 (file)
index 0000000..2571683
--- /dev/null
@@ -0,0 +1,221 @@
+#include "squid.h"
+#include "client_side.h"
+#include "client_side_request.h"
+#include "client_side_reply.h"
+#include "Downloader.h"
+#include "http/one/RequestParser.h"
+
+CBDATA_CLASS_INIT(Downloader);
+
+Downloader::Downloader(SBuf &url, const MasterXaction::Pointer &xact, AsyncCall::Pointer &aCallback):
+    AsyncJob("Downloader"),
+    ConnStateData(xact),
+    url_(url),
+    callback(aCallback),
+    status(Http::scNone)
+{
+    maxObjectSize = 512*1024;
+}
+
+Downloader::~Downloader()
+{
+    debugs(33 , 2, "Downloader Finished");
+}
+
+void
+Downloader::callException(const std::exception &e)
+{
+    debugs(33 , 2, "Downloader caught:" << e.what());
+    AsyncJob::callException(e);
+}
+
+bool
+Downloader::doneAll() const
+{
+    return (!callback || callback->canceled()) && AsyncJob::doneAll();
+}
+
+void
+Downloader::start()
+{
+    BodyProducer::start();
+    HttpControlMsgSink::start();
+    if (ClientSocketContext *context = parseOneRequest()) {
+        context->registerWithConn();
+        processParsedRequest(context);
+
+        /**/
+        if (context->flags.deferred) {
+            if (context != context->http->getConn()->getCurrentContext().getRaw())
+                context->deferRecipientForLater(context->deferredparams.node, context->deferredparams.rep, context->deferredparams.queuedBuffer);
+            else
+                context->http->getConn()->handleReply(context->deferredparams.rep, context->deferredparams.queuedBuffer); 
+        }
+        /**/
+
+    }
+    
+}
+
+void
+Downloader::noteMoreBodySpaceAvailable(BodyPipe::Pointer)
+{
+    // This method required only if we need to support uploading data to server
+    // Currently only GET requests are supported
+    assert(0);
+}
+
+void
+Downloader::noteBodyConsumerAborted(BodyPipe::Pointer)
+{
+    // This method required only if we need to support uploading data to server
+    // Currently only GET requests are supported
+    assert(0);
+}
+
+ClientSocketContext *
+Downloader::parseOneRequest()
+{ 
+    const HttpRequestMethod method = Http::METHOD_GET;
+
+    char *uri = strdup(url_.c_str());
+    HttpRequest *const request = HttpRequest::CreateFromUrlAndMethod(uri, method);
+    if (!request) {
+        debugs(33, 5, "Invalid FTP URL: " << uri);
+        safe_free(uri);
+        return NULL; //earlyError(...)
+    }
+    request->http_ver = Http::ProtocolVersion();
+    request->header.putStr(Http::HdrType::HOST, request->url.host());
+    request->header.putTime(Http::HdrType::DATE, squid_curtime);
+
+    ClientHttpRequest *const http = new ClientHttpRequest(this);
+    http->request = request;
+    HTTPMSGLOCK(http->request);
+    http->req_sz = 0;
+    http->uri = uri;
+
+    ClientSocketContext *const context = new ClientSocketContext(NULL, http);
+    StoreIOBuffer tempBuffer;
+    tempBuffer.data = context->reqbuf;
+    tempBuffer.length = HTTP_REQBUF_SZ;
+
+    ClientStreamData newServer = new clientReplyContext(http);
+    ClientStreamData newClient = context;
+    clientStreamInit(&http->client_stream, clientGetMoreData, clientReplyDetach,
+                     clientReplyStatus, newServer, clientSocketRecipient,
+                     clientSocketDetach, newClient, tempBuffer);
+
+    context->flags.parsed_ok = 1;
+    return context;
+}
+
+void
+Downloader::processParsedRequest(ClientSocketContext *context)
+{
+    Must(context != NULL);
+    Must(getConcurrentRequestCount() == 1);
+
+    ClientHttpRequest *const http = context->http;
+    assert(http != NULL);
+
+    debugs(33, 4, "forwarding request to server side");
+    assert(http->storeEntry() == NULL);
+    clientProcessRequest(this, Http1::RequestParserPointer(), context);
+}
+
+time_t
+Downloader::idleTimeout() const
+{
+    // No need to be implemented for connection-less ConnStateData object.
+    assert(0);
+    return 0;
+}
+
+void
+Downloader::writeControlMsgAndCall(ClientSocketContext *context, HttpReply *rep, AsyncCall::Pointer &call)
+{
+}
+
+void
+Downloader::handleReply(HttpReply *reply, StoreIOBuffer receivedData)
+{
+    bool existingContent = reply ? reply->content_length : 0;
+    bool exceedSize = (getCurrentContext()->startOfOutput() && existingContent > -1 && (size_t)existingContent > maxObjectSize) || 
+        ((object.length() + receivedData.length) > maxObjectSize);
+
+    if (exceedSize) {
+        status = Http::scInternalServerError;
+        callBack();
+        return;
+    }
+
+    debugs(33, 4, "Received " << receivedData.length <<
+           " object data, offset: " << receivedData.offset <<
+           " error flag:" << receivedData.flags.error);
+
+    if (receivedData.length > 0) {
+        object.append(receivedData.data, receivedData.length);
+        getCurrentContext()->http->out.size += receivedData.length;
+        getCurrentContext()->noteSentBodyBytes(receivedData.length);
+    }
+
+    switch (getCurrentContext()->socketState()) {
+    case STREAM_NONE:
+         debugs(33, 3, "Get more data");
+        getCurrentContext()->pullData();
+        break;
+    case STREAM_COMPLETE:
+        debugs(33, 3, "Object data transfer successfully complete");
+        status = Http::scOkay;
+        callBack();
+        break;
+    case STREAM_UNPLANNED_COMPLETE:
+        debugs(33, 3, "Object data transfer failed: STREAM_UNPLANNED_COMPLETE");
+        status = Http::scInternalServerError;
+        callBack();
+        break;
+    case STREAM_FAILED:
+        debugs(33, 3, "Object data transfer failed: STREAM_FAILED");
+        status = Http::scInternalServerError;
+        callBack();
+        break;
+    default:
+        fatal("unreachable code");
+    }
+}
+
+void
+Downloader::downloadFinished()
+{
+    debugs(33, 3, "fake call, to just delete the Downloader");
+
+    // Not really needed. Squid will delete this object because "doneAll" is true.
+    //deleteThis("completed");
+}
+
+void
+Downloader::callBack()
+{
+     CbDialer *dialer = dynamic_cast<CbDialer*>(callback->getDialer());
+     Must(dialer);
+     dialer->status = status;
+     if (status == Http::scOkay)
+         dialer->object = object;
+     ScheduleCallHere(callback);
+     callback = NULL;
+     // Calling deleteThis method here to finish Downloader
+     // may result to squid crash.
+     // This method called by handleReply method which maybe called
+     // by ClientHttpRequest::doCallouts. The doCallouts after this object deleted
+     // may operate on non valid objects.
+     // Schedule a fake call here just to force squid to delete this object
+     CallJobHere(33, 7, CbcPointer<Downloader>(this), Downloader, downloadFinished);
+}
+
+bool
+Downloader::isOpen() const
+{
+    return cbdataReferenceValid(this) && // XXX: checking "this" in a method
+        callback != NULL;
+}
diff --git a/src/Downloader.h b/src/Downloader.h
new file mode 100644 (file)
index 0000000..a698b60
--- /dev/null
@@ -0,0 +1,57 @@
+#ifndef SQUID_DOWNLOADER_H
+#define SQUID_DOWNLOADER_H
+
+#include "client_side.h"
+#include "cbdata.h"
+
+class Downloader: public ConnStateData
+{
+    CBDATA_CLASS(Downloader);
+    // XXX CBDATA_CLASS expands to nonvirtual toCbdata, AsyncJob::toCbdata
+    //     is pure virtual. breaks build on clang if override is used
+
+public:
+    class CbDialer {
+    public:
+        CbDialer(): status(Http::scNone) {}
+        virtual ~CbDialer() {}
+        SBuf object;
+        Http::StatusCode status;
+    };
+
+    explicit Downloader(SBuf &url, const MasterXaction::Pointer &xact, AsyncCall::Pointer &aCallback);
+    virtual ~Downloader();
+    void downloadFinished();
+    
+    /* ConnStateData API */
+    virtual bool isOpen() const;
+
+    /* AsyncJob API */
+    virtual void callException(const std::exception &e);
+    virtual bool doneAll() const;
+
+    /*Bodypipe API*/
+    virtual void noteMoreBodySpaceAvailable(BodyPipe::Pointer);
+    virtual void noteBodyConsumerAborted(BodyPipe::Pointer);
+
+protected:
+    /* ConnStateData API */
+    virtual ClientSocketContext *parseOneRequest();
+    virtual void processParsedRequest(ClientSocketContext *context);
+    virtual time_t idleTimeout() const;
+    virtual void writeControlMsgAndCall(ClientSocketContext *context, HttpReply *rep, AsyncCall::Pointer &call);
+    virtual void handleReply(HttpReply *header, StoreIOBuffer receivedData);
+
+    /* AsyncJob API */
+    virtual void start();
+
+private:
+    void callBack();
+    SBuf url_;
+    AsyncCall::Pointer callback;
+    Http::StatusCode status;
+    SBuf object; //object data
+    size_t maxObjectSize;
+};
+
+#endif
index 66a3b3e08a2fd86bb6729e27d44a87bedb17492c..99ea95cd2f0e5ab8f269759dd9ef69379685c9db 100644 (file)
@@ -285,6 +285,8 @@ squid_SOURCES = \
        dlink.h \
        dlink.cc \
        $(DNSSOURCE) \
+       Downloader.h \
+       Downloader.cc \
        enums.h \
        err_type.h \
        err_detail_type.h \
index 9d5e7e14e219c2f1242163e3bbf2621730b8c82c..181049e3d2918f5350183807c81c65d5ec7fe17f 100644 (file)
@@ -19,6 +19,9 @@
 #include "Notes.h"
 #include "security/forward.h"
 #include "SquidTime.h"
+#if USE_OPENSSL
+#include "ssl/support.h"
+#endif
 #include "YesNoNone.h"
 
 #if USE_OPENSSL
@@ -494,6 +497,7 @@ public:
     struct {
         Security::ContextPointer sslContext;
 #if USE_OPENSSL
+        char *untrustedCertsPath;
         acl_access *cert_error;
         sslproxy_cert_sign *cert_sign;
         sslproxy_cert_adapt *cert_adapt;
index 7b4c04fd31674dae7148818f55741e0a275af761..a7b9a55d47f4bd170c7b6bd71bcca810b94a82c4 100644 (file)
@@ -865,6 +865,11 @@ configDoConfigure(void)
         Config2.effectiveGroupID = grp->gr_gid;
     }
 
+#if USE_OPENSSL
+    if (Config.ssl_client.untrustedCertsPath)
+        Ssl::loadSquidUntrusted(Config.ssl_client.untrustedCertsPath);
+#endif
+
     if (Security::ProxyOutgoingConfig.encryptTransport) {
         debugs(3, DBG_IMPORTANT, "Initializing https:// proxy context");
         Config.ssl_client.sslContext = Security::ProxyOutgoingConfig.createClientContext(false);
@@ -875,6 +880,9 @@ configDoConfigure(void)
             debugs(3, DBG_IMPORTANT, "ERROR: proxying https:// currently still requires --with-openssl");
 #endif
         }
+#if USE_OPENSSL
+        Ssl::useSquidUntrusted(Config.ssl_client.sslContext);
+#endif
     }
 
     for (CachePeer *p = Config.peers; p != NULL; p = p->next) {
@@ -3825,6 +3833,7 @@ configFreeMemory(void)
     free_all();
 #if USE_OPENSSL
     SSL_CTX_free(Config.ssl_client.sslContext);
+    Ssl::unloadSquidUntrusted();
 #endif
 }
 
index 091410cf6a24561415b912376b643483c91d7920..30a0b17d8084017bace08dbeb2800d851d513392 100644 (file)
@@ -2559,6 +2559,20 @@ DOC_START
         Sets the cache size to use for ssl session
 DOC_END
 
+NAME: sslproxy_untrusted_certs
+IFDEF: USE_OPENSSL
+DEFAULT: none
+LOC: Config.ssl_client.untrustedCertsPath
+TYPE: string
+DOC_START
+       Squid uses the intermediate certificates pre-loaded from the specified
+       file to validate origin server certificate chains. Squid receives many 
+       incomplete chains (i.e., chains with intermediate certificates missing).
+       The file is expected to contain zero or more PEM-encoded intermediate
+       certificates. These certificates are not treated as trusted root
+       certificates.
+DOC_END
+
 NAME: sslproxy_cert_sign_hash
 IFDEF: USE_OPENSSL
 DEFAULT: none
index 7e9143de736ba9ad69ff697f9eeb92435eca036b..e21c2abb024c2d7497074f60d0a5bc20a1a04745 100644 (file)
@@ -810,7 +810,8 @@ ConnStateData::swanSong()
     debugs(33, 2, HERE << clientConnection);
     flags.readMore = false;
     DeregisterRunner(this);
-    clientdbEstablished(clientConnection->remote, -1);  /* decrement */
+    if (clientConnection != NULL)
+        clientdbEstablished(clientConnection->remote, -1);  /* decrement */
     assert(areAllContextsForThisConnection());
     freeAllContexts();
 
@@ -1422,9 +1423,14 @@ clientSocketRecipient(clientStreamNode * node, ClientHttpRequest * http,
                       HttpReply * rep, StoreIOBuffer receivedData)
 {
     // dont tryt to deliver if client already ABORTED
-    if (!http->getConn() || !cbdataReferenceValid(http->getConn()) || !Comm::IsConnOpen(http->getConn()->clientConnection))
+    if (!http->getConn() || !cbdataReferenceValid(http->getConn()))
         return;
 
+    // If it is not connectionless and connection is closed return  
+    if (!http->getConn()->connectionless() && !Comm::IsConnOpen(http->getConn()->clientConnection))
+        return;
+
+
     /* Test preconditions */
     assert(node != NULL);
     PROF_start(clientSocketRecipient);
@@ -2547,10 +2553,12 @@ clientProcessRequest(ConnStateData *conn, const Http1::RequestParserPointer &hp,
 
     request->flags.accelerated = http->flags.accel;
     request->flags.sslBumped=conn->switchedToHttps();
-    request->flags.ignoreCc = conn->port->ignore_cc;
-    // TODO: decouple http->flags.accel from request->flags.sslBumped
-    request->flags.noDirect = (request->flags.accelerated && !request->flags.sslBumped) ?
-                              !conn->port->allow_direct : 0;
+    if (!conn->connectionless()) {
+        request->flags.ignoreCc = conn->port->ignore_cc;
+        // TODO: decouple http->flags.accel from request->flags.sslBumped
+        request->flags.noDirect = (request->flags.accelerated && !request->flags.sslBumped) ?
+            !conn->port->allow_direct : 0;
+    }
 #if USE_AUTH
     if (request->flags.sslBumped) {
         if (conn->getAuth() != NULL)
@@ -2593,14 +2601,16 @@ clientProcessRequest(ConnStateData *conn, const Http1::RequestParserPointer &hp,
 
     request->flags.internal = http->flags.internal;
     setLogUri (http, urlCanonicalClean(request.getRaw()));
-    request->client_addr = conn->clientConnection->remote; // XXX: remove reuest->client_addr member.
+    if (!conn->connectionless()) {
+        request->client_addr = conn->clientConnection->remote; // XXX: remove reuest->client_addr member.
 #if FOLLOW_X_FORWARDED_FOR
     // indirect client gets stored here because it is an HTTP header result (from X-Forwarded-For:)
     // not a details about teh TCP connection itself
-    request->indirect_client_addr = conn->clientConnection->remote;
+        request->indirect_client_addr = conn->clientConnection->remote;
 #endif /* FOLLOW_X_FORWARDED_FOR */
-    request->my_addr = conn->clientConnection->local;
-    request->myportname = conn->port->name;
+        request->my_addr = conn->clientConnection->local;
+        request->myportname = conn->port->name;
+    }
 
     if (!isFtp) {
         // XXX: for non-HTTP messages instantiate a different HttpMsg child type
@@ -3409,8 +3419,10 @@ ConnStateData::ConnStateData(const MasterXaction::Pointer &xact) :
     // store the details required for creating more MasterXaction objects as new requests come in
     clientConnection = xact->tcpClient;
     port = xact->squidPort;
-    transferProtocol = port->transport; // default to the *_port protocol= setting. may change later.
-    log_addr = xact->tcpClient->remote;
+    if (port != NULL)
+        transferProtocol = port->transport; // default to the *_port protocol= setting. may change later.
+    if (xact->tcpClient != NULL)
+        log_addr = xact->tcpClient->remote;
     log_addr.applyMask(Config.Addrs.client_netmask);
 
     // register to receive notice of Squid signal events
index bd58c3359eaa70ca896c8c36b13954483548a194..78e4fae6ef6b3e813612f9dc365d3b98145f6fbf 100644 (file)
@@ -185,7 +185,7 @@ public:
     ClientSocketContext::Pointer getCurrentContext() const;
     void addContextToQueue(ClientSocketContext * context);
     int getConcurrentRequestCount() const;
-    bool isOpen() const;
+    virtual bool isOpen() const;
 
     /// Update flags and timeout after the first byte received
     void receivedFirstByte();
@@ -267,6 +267,9 @@ public:
     /// Squid listening port details where this connection arrived.
     AnyP::PortCfgPointer port;
 
+    /// If the port is not set then it is a connection-less object 
+    /// created by an internal squid subsystem
+    bool connectionless() const { return port == NULL; }
     bool transparent() const;
     bool reading() const;
     void stopReading(); ///< cancels comm_read if it is scheduled
index 9029510c60e65d1903ccf3c10abf6dec1ef918eb..139e3bda9f38cca8151af1e26e4127825e8d57df 100644 (file)
@@ -1354,7 +1354,7 @@ clientReplyContext::buildReplyHeader()
         if (EBIT_TEST(http->storeEntry()->flags, ENTRY_SPECIAL)) {
             hdr->delById(Http::HdrType::DATE);
             hdr->putTime(Http::HdrType::DATE, squid_curtime);
-        } else if (http->getConn() && http->getConn()->port->actAsOrigin) {
+        } else if (http->getConn() &&  !http->getConn()->connectionless() && http->getConn()->port->actAsOrigin) {
             // Swap the Date: header to current time if we are simulating an origin
             HttpHeaderEntry *h = hdr->findEntry(Http::HdrType::DATE);
             if (h)
@@ -1526,7 +1526,10 @@ clientReplyContext::buildReplyHeader()
         request->flags.proxyKeepalive = false;
     } else if (http->getConn()) {
         ConnStateData * conn = http->getConn();
-        if (!Comm::IsConnOpen(conn->port->listenConn)) {
+        if (conn->connectionless()) {
+            debugs(88, 3, "connection-less object, close after finished");
+            request->flags.proxyKeepalive = false;
+        } else if (!Comm::IsConnOpen(conn->port->listenConn)) {
             // The listening port closed because of a reconfigure
             debugs(88, 3, "listening port closed");
             request->flags.proxyKeepalive = false;
index 3576242e3d9229c8a3b1f69df54bf944e4d45c08..ef99a4e2644f2de1e351f492f89d5d3c3fd23bec 100644 (file)
@@ -996,6 +996,10 @@ clientCheckPinning(ClientHttpRequest * http)
     if (!http_conn)
         return;
 
+    // Internal requests such as those from Doenloader does not have local port
+    if (http_conn->port == NULL)
+        return;
+
     request->flags.connectionAuthDisabled = http_conn->port->connection_auth_disabled;
     if (!request->flags.connectionAuthDisabled) {
         if (Comm::IsConnOpen(http_conn->pinning.serverConnection)) {
index c265473f430fb808e97e902c186bb1939c208354..1496598b16d297e0c2eb30493806d22f658926c1 100644 (file)
@@ -14,6 +14,7 @@
 #include "CachePeer.h"
 #include "client_side.h"
 #include "comm/Loops.h"
+#include "Downloader.h"
 #include "errorpage.h"
 #include "fde.h"
 #include "globals.h"
@@ -278,7 +279,6 @@ Ssl::PeekingPeerConnector::checkForPeekAndSpliceMatched(const Ssl::BumpMode acti
     } else if (finalAction != Ssl::bumpSplice) {
         //Allow write, proceed with the connection
         srvBio->holdWrite(false);
-        srvBio->recordInput(false);
         debugs(83,5, "Retry the fwdNegotiateSSL on FD " << serverConn->fd);
         Ssl::PeerConnector::noteWantWrite();
     } else {
@@ -455,8 +455,29 @@ Ssl::PeerConnector::handleNegotiateError(const int ret)
 void
 Ssl::PeerConnector::noteWantRead()
 {
-    setReadTimeout();
     const int fd = serverConnection()->fd;
+    SSL *ssl = fd_table[fd].ssl;
+    BIO *b = SSL_get_rbio(ssl);
+    Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(b->ptr);
+    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
+            Ssl::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
+            Ssl::PeerConnector::NegotiateSsl(fd, this);
+            return;
+        }
+    }
+
+    setReadTimeout();
     Comm::SetSelect(fd, COMM_SELECT_READ, &NegotiateSsl, this, 0);
 }
 
@@ -589,6 +610,94 @@ Ssl::PeerConnector::status() const
     return buf.content();
 }
 
+class PeerConnectorCertDownloaderDialer: public CallDialer, public Downloader::CbDialer
+{
+public:
+    typedef void (Ssl::PeerConnector::*Method)(SBuf &object, int status);
+
+    PeerConnectorCertDownloaderDialer(Method method, Ssl::PeerConnector *pc):
+        method_(method),
+        peerConnector_(pc) {}
+
+    /* CallDialer API */
+    virtual bool canDial(AsyncCall &call) { return peerConnector_.valid(); }
+    void dial(AsyncCall &call) { ((&(*peerConnector_))->*method_)(object, status); }
+    virtual void print(std::ostream &os) const {
+        os << '(' << peerConnector_.get() << ", Http Status:" << status << ')';
+    }
+
+    Method method_;
+    CbcPointer<Ssl::PeerConnector> peerConnector_;
+};
+
+void
+Ssl::PeerConnector::startCertDownloading(SBuf &url)
+{
+    AsyncCall::Pointer certCallback = asyncCall(81, 4,
+                                            "Ssl::PeerConnector::certDownloadingDone",
+                                            PeerConnectorCertDownloaderDialer(&Ssl::PeerConnector::certDownloadingDone, this));
+
+    MasterXaction *xaction = new MasterXaction;
+    Downloader *dl = new Downloader(url, xaction, certCallback);
+    AsyncJob::Start(dl);
+}
+
+void
+Ssl::PeerConnector::certDownloadingDone(SBuf &obj, int downloadStatus)
+{
+    debugs(81, 5, "OK! certificate downloaded, status: " << downloadStatus << " data size: " << obj.length());
+
+    // Get ServerBio from SSL object
+    const int fd = serverConnection()->fd;
+    SSL *ssl = fd_table[fd].ssl;
+    BIO *b = SSL_get_rbio(ssl);
+    Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(b->ptr);
+
+    // Parse Certificate. Assume that it is in DER format. Probably we should handle PEM or other formats too
+    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 Ssl::X509_STACK_Pointer &certsList = srvBio->serverCertificates();
+        if (const char *issuerUri = Ssl::uriOfIssuerIfMissing(cert,  certsList)) {
+            urlsOfMissingCerts.push(SBuf(issuerUri));
+        }
+        Ssl::SSL_add_untrusted_cert(ssl, cert);
+    }
+
+    // Check if has uri to donwload and add it to urlsOfMissingCerts
+    if (urlsOfMissingCerts.size()) {
+        startCertDownloading(urlsOfMissingCerts.front());
+        urlsOfMissingCerts.pop();
+        return;
+    }
+
+    srvBio->holdRead(false);
+    Ssl::PeerConnector::NegotiateSsl(serverConnection()->fd, this);
+}
+
+bool
+Ssl::PeerConnector::checkForMissingCertificates ()
+{
+    const int fd = serverConnection()->fd;
+    SSL *ssl = fd_table[fd].ssl;
+    BIO *b = SSL_get_rbio(ssl);
+    Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(b->ptr);
+    const Ssl::X509_STACK_Pointer &certs = srvBio->serverCertificates();
+
+    if (sk_X509_num(certs.get())) {
+        debugs(83, 5, "SSL server sent " << sk_X509_num(certs.get()) << " certificates");
+        Ssl::missingChainCertificatesUrls(urlsOfMissingCerts, certs);
+        if (urlsOfMissingCerts.size()) {
+            startCertDownloading(urlsOfMissingCerts.front());
+            urlsOfMissingCerts.pop();
+            return true;
+        }
+    }
+
+    return false;
+}
+
 SSL_CTX *
 Ssl::BlindPeerConnector::getSslContext()
 {
@@ -705,7 +814,6 @@ Ssl::PeekingPeerConnector::initializeSsl()
                     Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(b->ptr);
                     // Inherite client features, like SSL version, SNI and other
                     srvBio->setClientFeatures(features);
-                    srvBio->recordInput(true);
                     srvBio->mode(csd->sslBumpMode);
                 }
             }
index e70fc175b1b146110ecc328a7c587544658d9bc6..8bec0943d0a594a808b2ec43d88e4eb3b93cc94a 100644 (file)
@@ -15,6 +15,7 @@
 #include "security/EncryptorAnswer.h"
 #include "ssl/support.h"
 #include <iosfwd>
+#include <queue>
 
 class HttpRequest;
 class ErrorState;
@@ -119,6 +120,12 @@ protected:
     /// Squid COMM_SELECT_READ handler.
     void noteWantRead();
 
+    bool checkForMissingCertificates();
+
+    void startCertDownloading(SBuf &url);
+
+    void certDownloadingDone(SBuf &object, int status);
+
     /// Called when the openSSL SSL_connect function needs to write data to
     /// the remote SSL server. Sets the Squid COMM_SELECT_WRITE handler.
     virtual void noteWantWrite();
@@ -175,6 +182,8 @@ private:
     time_t negotiationTimeout; ///< the SSL connection timeout to use
     time_t startTime; ///< when the peer connector negotiation started
     bool useCertValidator_; ///< whether the certificate validator should bypassed
+
+    std::queue<SBuf> urlsOfMissingCerts;
 };
 
 /// A simple PeerConnector for SSL/TLS cache_peers. No SslBump capabilities.
index d09905f79be65e410b57fa0e06e70577af27a295..a4e8301dacb4f21da0a800d379383a478a777e80 100644 (file)
@@ -128,18 +128,18 @@ Ssl::Bio::read(char *buf, int size, BIO *table)
 }
 
 int
-Ssl::Bio::readAndBuffer(char *buf, int size, BIO *table, const char *description)
+Ssl::Bio::readAndBuffer(BIO *table, const char *description)
 {
     prepReadBuf();
-
-    size = min((int)rbuf.potentialSpaceSize(), size);
+    char buf[SQUID_TCP_SO_RCVBUF ];
+    size_t size = min(rbuf.potentialSpaceSize(), (mb_size_t)SQUID_TCP_SO_RCVBUF);
     if (size <= 0) {
         debugs(83, DBG_IMPORTANT, "Not enough space to hold " <<
                rbuf.contentSize() << "+ byte " << description);
         return -1;
     }
 
-    const int bytes = Ssl::Bio::read(buf, size, table);
+    const int bytes = Ssl::Bio::read(buf, sizeof(buf), table);
     debugs(83, 5, "read " << bytes << " out of " << size << " bytes"); // move to Ssl::Bio::read()
 
     if (bytes > 0) {
@@ -219,7 +219,7 @@ int
 Ssl::ClientBio::read(char *buf, int size, BIO *table)
 {
     if (helloState < atHelloReceived) {
-        int bytes = readAndBuffer(buf, size, table, "TLS client Hello");
+        int bytes = readAndBuffer(table, "TLS client Hello");
         if (bytes <= 0)
             return bytes;
     }
@@ -282,11 +282,51 @@ Ssl::ServerBio::setClientFeatures(const Ssl::Bio::sslFeatures &features)
     clientFeatures = features;
 };
 
+int
+Ssl::ServerBio::readAndBufferServerHelloMsg(BIO *table, const char *description)
+{
+
+    int ret = readAndBuffer(table, description);
+    if (ret <= 0)
+        return ret;
+
+    if (!parser_.parseServerHello((const unsigned char *)rbuf.content(), rbuf.contentSize())) {
+        if (!parser_.parseError) 
+            BIO_set_retry_read(table);
+        return -1;
+    }
+
+    return 1;
+}
+
 int
 Ssl::ServerBio::read(char *buf, int size, BIO *table)
 {
-    return record_ ?
-           readAndBuffer(buf, size, table, "TLS server Hello") : Ssl::Bio::read(buf, size, table);
+    if (parser_.state < Ssl::HandshakeParser::atHelloDoneReceived) {
+        int ret = readAndBufferServerHelloMsg(table, "TLS server Hello");
+        if (ret <= 0)
+            return ret;
+    }
+
+    if (holdRead_) {
+        debugs(83, 7, "Hold flag is set on ServerBio, retry latter. (Hold " << size << "bytes)");
+        BIO_set_retry_read(table);
+        return -1;
+    }
+
+    if (parser_.parseDone && !parser_.parseError) {
+        int unsent = rbuf.contentSize() - rbufConsumePos;
+        if (unsent > 0) {
+            int bytes = (size <= unsent ? size : unsent);
+            memcpy(buf, rbuf.content()+rbufConsumePos, bytes);
+            rbufConsumePos += bytes;
+            debugs(83, 7, "Pass " << bytes << " bytes to openSSL as read");
+            return bytes;
+        } else
+            return Ssl::Bio::read(buf, size, table);
+    }
+
+    return -1;
 }
 
 // This function makes the required checks to examine if the client hello
@@ -505,19 +545,18 @@ Ssl::ServerBio::flush(BIO *table)
 bool
 Ssl::ServerBio::resumingSession()
 {
-    if (!serverFeatures.initialized_)
-        serverFeatures.get(rbuf, false);
-
-    if (!clientFeatures.sessionId.isEmpty() && !serverFeatures.sessionId.isEmpty())
-        return clientFeatures.sessionId == serverFeatures.sessionId;
+    return parser_.ressumingSession;
+}
 
-    // is this a session resuming attempt using TLS tickets?
-    if (clientFeatures.hasTlsTicket &&
-            serverFeatures.tlsTicketsExtension &&
-            serverFeatures.hasCcsOrNst)
-        return true;
+const Ssl::X509_STACK_Pointer &
+Ssl::ServerBio::serverCertificates()
+{
+    if (!serverCertificates_.get()) {
+        serverCertificates_.reset(sk_X509_new_null());
+        parser_.parseServerCertificates(serverCertificates_, (const unsigned char *)rbuf.content(), rbuf.contentSize());
+    }
 
-    return false;
+    return serverCertificates_;
 }
 
 /// initializes BIO table after allocation
@@ -639,7 +678,7 @@ squid_ssl_info(const SSL *ssl, int where, int ret)
     }
 }
 
-Ssl::Bio::sslFeatures::sslFeatures(): sslVersion(-1), compressMethod(-1), helloMsgSize(0), unknownCiphers(false), doHeartBeats(true), tlsTicketsExtension(false), hasTlsTicket(false), tlsStatusRequest(false), hasCcsOrNst(false), initialized_(false)
+Ssl::Bio::sslFeatures::sslFeatures(): sslVersion(-1), compressMethod(-1), helloMsgSize(0), unknownCiphers(false), doHeartBeats(true), tlsTicketsExtension(false), hasTlsTicket(false), tlsStatusRequest(false), initialized_(false)
 {
     memset(client_random, 0, SSL3_RANDOM_SIZE);
 }
@@ -794,39 +833,6 @@ Ssl::Bio::sslFeatures::parseMsgHead(const MemBuf &buf)
     return helloMsgSize;
 }
 
-bool
-Ssl::Bio::sslFeatures::checkForCcsOrNst(const unsigned char *msg, size_t size)
-{
-    while (size > 5) {
-        const int msgType = msg[0];
-        const int msgSslVersion = (msg[1] << 8) | msg[2];
-        debugs(83, 7, "SSL Message Version :" << std::hex << std::setw(8) << std::setfill('0') << msgSslVersion);
-        // Check for Change Cipher Spec message
-        // RFC5246 section 6.2.1
-        if (msgType == 0x14) {// Change Cipher Spec message found
-            debugs(83, 7, "SSL  Change Cipher Spec message found");
-            return true;
-        }
-        // Check for New Session Ticket message
-        // RFC5077 section 3.3
-        if (msgType == 0x04) {// New Session Ticket message found
-            debugs(83, 7, "TLS  New Session Ticket message found");
-            return true;
-        }
-        // The hello message size exist in 4th and 5th bytes
-        size_t msgLength = (msg[3] << 8) + msg[4];
-        debugs(83, 7, "SSL Message Size: " << msgLength);
-        msgLength += 5;
-
-        if (msgLength <= size) {
-            msg += msgLength;
-            size -= msgLength;
-        } else
-            size = 0;
-    }
-    return false;
-}
-
 bool
 Ssl::Bio::sslFeatures::get(const MemBuf &buf, bool record)
 {
@@ -858,10 +864,7 @@ Ssl::Bio::sslFeatures::get(const MemBuf &buf, bool record)
         // The type 2 is a ServerHello, the type 1 is a ClientHello
         // RFC5246 section 7.4
         if (msg[5] == 0x2) { // ServerHello message
-            if (parseV3ServerHello(msg, (size_t)msgSize)) {
-                hasCcsOrNst = checkForCcsOrNst(msg + msgSize,  buf.contentSize() - msgSize);
-                return true;
-            }
+            return parseV3ServerHello(msg, (size_t)msgSize);
         } else if (msg[5] == 0x1) // ClientHello message,
             return parseV3Hello(msg, (size_t)msgSize);
     }
@@ -1197,5 +1200,229 @@ Ssl::Bio::sslFeatures::print(std::ostream &os) const
            " opaquePrf:" << opaquePrf;
 }
 
+bool
+Ssl::HandshakeParser::parseNextContentRecord(const unsigned char *msg, size_t size)
+{
+    if (unParsedContent)
+        return true;
+
+    if (parsingPos >= size)
+        return false;
+
+    msg += parsingPos;
+    size -= parsingPos;
+
+    if (size < 5)
+        return false;
+
+    const unsigned int contentType = msg[0];
+    // The hello message size exist in 4th and 5th bytes
+    size_t contentLength = (msg[3] << 8) + msg[4];
+    if (contentLength > size - 5)
+        return false; //missing message data?
+
+    unParsedContent = contentLength;
+    currentContentType = (ContentType)contentType;
+    parsingPos += 5;
+
+    currentMsg = 0;
+    currentMsgSize = 0;
+    return true;
+}
+
+bool
+Ssl::HandshakeParser::skipContentDataRecord(const unsigned char *msg, size_t size)
+{
+    if (size < parsingPos) {
+        parseDone = true;
+        parseError = true;
+        return false;
+    }
+    parsingPos += unParsedContent;
+    unParsedContent = 0;
+    currentContentType = ctNone;
+
+    currentMsg = 0;
+    currentMsgSize = 0;
+
+    return true;
+}
+
+Ssl::HandshakeParser::HandshakeType
+Ssl::HandshakeParser::parseNextHandshakeMessage(const unsigned char *msg, size_t size)
+{
+    if (!unParsedContent) {
+        return hskNone; // No data to parse
+    }
+
+    if (currentContentType != ctHandshake) {
+        parseError = true;
+        parseDone = true;
+        return hskNone;
+    }
+
+    msg += parsingPos;
+    size -= parsingPos;
+
+    const HandshakeType type = (HandshakeType)msg[0];
+    size_t msgLength = (msg[1] << 16) | (msg[2] << 8) | msg[3];
+
+    if (msgLength > size + 4 ||
+        msgLength > unParsedContent + 4) {
+        // The parseNextContentData call assure that we have all handshake data
+        // before parse this handshake message. So this is looks like a
+        // parse error
+        parseError = true;
+        parseDone = true;
+        return hskNone;
+    }
+
+    currentMsg = parsingPos + 4;
+    currentMsgSize = msgLength;
+
+    unParsedContent -= msgLength + 4;
+    parsingPos += msgLength + 4;
+
+    return type;
+}
+
+bool
+Ssl::HandshakeParser::parseServerHello(const unsigned char *data, size_t dataSize)
+{
+    while(!parseDone && !parseError) {
+        switch(state) {
+        case atHelloNone:
+        case atHelloStarted:
+            if (!parseNextContentRecord(data, dataSize))
+                return false;
+            {
+                HandshakeType type = parseNextHandshakeMessage(data, dataSize);
+                if (type == hskNone)
+                    return false; //probably the message does not received yet
+                if (type != hskServerHello) { // parse error; Expecting Server hello
+                    parseError = true;
+                    parseDone = true;
+                    return false;
+                }
+            }
+            state = atHelloReceived;
+            break;
+
+        case atHelloReceived:
+            if (!parseNextContentRecord(data, dataSize))
+                return false;
+
+            if (currentContentType == ctChangeCipherSpec) {
+                state = atCcsReceived;
+                skipContentDataRecord(data, dataSize); // Skipe to next Record
+            } else {
+                HandshakeType type = parseNextHandshakeMessage(data, dataSize);
+                if (type == hskNone)
+                    return false; // probably an error;
+                else if (type == hskCertificate) {
+                    state = atCertificatesReceived;
+                    certificatesMsgPos = currentMsg;
+                    certificatesMsgSize = currentMsgSize;
+                } else if (type == shkNewSessionTicket)
+                    state = atNstReceived;
+            }
+            break;
+
+        case atNstReceived:
+            if (!parseNextContentRecord(data, dataSize))
+                return false;
+
+            if (currentContentType == ctChangeCipherSpec)
+                state = atCcsReceived;
+            skipContentDataRecord(data, dataSize); // Skipe to next Record
+            break;
+
+        case atCertificatesReceived: {
+            if (!parseNextContentRecord(data, dataSize))
+                return false;
+
+            HandshakeType type = parseNextHandshakeMessage(data, dataSize);
+            if (type == hskNone)
+                return false;
+            if (type == hskServerHelloDone)
+                state = atHelloDoneReceived;
+        }
+            break;
+
+        case atCcsReceived: {
+            if (!parseNextContentRecord(data, dataSize))
+                return false;
+
+            ressumingSession = true;
+
+            // To parse a shkFinished handshake message:
+            // HandshakeType type = parseNextHandshakeMessage(data, dataSize);
+            // if (type == hskNone)
+            //     return false;
+            // if (type == shkFinished)
+            //     helloState = Ssl::Bio::atFinishReceived;
+            //
+            // However this message may arrived encrypted.
+            // We are accepting any handshake message for now.
+            if (currentContentType == ctHandshake && unParsedContent != 0)
+                state = atFinishReceived;
+        }
+            break;
+        case atHelloDoneReceived:
+        case atFinishReceived:
+            return (parseDone = true);
+            break;
+        }
+    }
+
+    return parseDone;
+}
+
+static X509 *
+extractCertificate(const unsigned char *currentpos, size_t size,  const unsigned char **next, size_t *nextSize)
+{
+    size_t certLen = currentpos[0] << 16 | currentpos[1] << 8 | currentpos[2]; 
+    if (certLen + 3 > size)
+        return NULL;
+    *next = currentpos + 3 + certLen;
+    *nextSize = size - certLen - 3;
+
+    const unsigned char *raw = (const unsigned char*)(currentpos + 3);
+    return d2i_X509(NULL, &(raw), certLen);
+}
+
+bool
+Ssl::HandshakeParser::parseServerCertificates(Ssl::X509_STACK_Pointer &serverCertificates, const unsigned char *msg, size_t size)
+{
+    if (!serverCertificates.get())
+        return false; // No struct to store it? should we assert?
+
+    if (!certificatesMsgPos)
+        return false; // There is no certificates message
+
+    // We are checking for this while parsing handshake messages
+    assert(certificatesMsgPos <= size - 3);
+
+    const unsigned char *certMsg = msg + certificatesMsgPos;
+    // First 3 bytes are the size of certificates
+    size_t certsLength = ((certMsg[0] << 16) | (certMsg[1] << 8) | certMsg[2]);
+    if (certsLength > size - certificatesMsgPos) {
+        debugs(83, 2, "Error parsing certificates Handshake Message. Parsed certificates length: " << certsLength << 
+               " server hello length: " << size);
+        return false;
+    }
+    certMsg += 3; // Point to raw certificates data, in the form [certLength cert]*
+
+    const unsigned char *next = certMsg;
+    size_t nextLen = certsLength;
+    while(nextLen) {
+        X509 *cert = NULL;
+        if (!(cert = extractCertificate(next, nextLen, &next, &nextLen)))
+            break;
+        sk_X509_push(serverCertificates.get(), cert);
+    }
+    return true;
+}
+
 #endif /* USE_SSL */
 
index 0ae6e4403ed5782b6a78a7664b7bd296840b63a2..eac0a8bcdde05ad9ce8a7332088603bf68ad98bd 100644 (file)
 
 namespace Ssl
 {
+class HandshakeParser {
+public:
+    /// The parsing states
+    typedef enum {atHelloNone = 0, atHelloStarted, atHelloReceived, atCertificatesReceived, atHelloDoneReceived, atNstReceived, atCcsReceived, atFinishReceived} ParserState;
+
+    /// TLS record protocol, content types, RFC5246 section 6.2.1
+    typedef enum {ctNone = 0, ctChangeCipherSpec = 20, ctAlert = 21, ctHandshake = 22, ctApplicationData} ContentType;
+    /// TLS Handshake protocol, handshake types, RFC5246  section 7.4
+    typedef enum {hskNone = 0,  hskServerHello = 2, shkNewSessionTicket = 4, hskCertificate = 11, hskServerHelloDone = 14, hskFinished = 20} HandshakeType;
+
+    HandshakeParser(): state(atHelloNone), currentContentType(ctNone), unParsedContent(0), parsingPos(0), currentMsg(0), currentMsgSize(0), certificatesMsgPos(0), certificatesMsgSize(0), ressumingSession(false), parseDone(false), parseError(false) {}
+
+    /// Parses the SSL Server Hello records stored in data.
+    /// Return false if the hello messages are not complete (HelloDone 
+    /// or Finished handshake messages are not received)
+    /// On parse error, return false and sets the parseError member to true.
+    bool parseServerHello(const unsigned char *data, size_t dataSize);
+
+    /// Parse server certificates message and store the certificate to serverCertificates list
+    bool parseServerCertificates(Ssl::X509_STACK_Pointer &serverCertificates, const unsigned char *msg, size_t size);
+
+    ParserState state; ///< current parsing state.
+
+    ContentType currentContentType; ///< The current SSL record content type
+    size_t unParsedContent; ///< The size of current SSL record, which is not parsed yet
+    size_t parsingPos; ///< The parsing position from the beginning of parsed data
+    size_t currentMsg; ///< The current handshake message possition from the beginning of parsed data
+    size_t currentMsgSize; ///< The current handshake message size.
+
+    size_t certificatesMsgPos; ///< The possition of certificates message from the beggining of parsed data
+    size_t certificatesMsgSize; ///< The size of certificates message
+    bool ressumingSession; ///< True if this is a resumming session
+
+    bool parseDone; ///< The parser finishes its job
+    bool parseError; ///< Set to tru by parse on parse error.
+
+private:
+    /// Do nothing if there are unparsed data from existing SSL record
+    /// else parses the next SSL record.
+    /// Return false if the next SSL record is not complete.
+    bool parseNextContentRecord(const unsigned char *msg, size_t size);
+    /// Consumes the current SSL record and set the parsingPos to the next
+    bool skipContentDataRecord(const unsigned char *msg, size_t size);
+    /// Parses the next handshake message in current SSL record
+    HandshakeType parseNextHandshakeMessage(const unsigned char *msg, size_t size);
+};
 
 /// BIO source and sink node, handling socket I/O and monitoring SSL state
 class Bio
@@ -57,9 +103,6 @@ public:
         /// \retval 0 if the contents of the buffer are not enough
         /// \retval <0 if the contents of buf are not SSLv3 or TLS hello message
         int parseMsgHead(const MemBuf &);
-        /// Parses msg buffer and return true if one of the Change Cipher Spec
-        /// or New Session Ticket messages found
-        bool checkForCcsOrNst(const unsigned char *msg, size_t size);
     public:
         int sslVersion; ///< The requested/used SSL version
         int compressMethod; ///< The requested/used compressed  method
@@ -75,9 +118,6 @@ public:
         bool hasTlsTicket; ///< whether a TLS ticket is included
         bool tlsStatusRequest; ///< whether the TLS status request extension is set
         SBuf tlsAppLayerProtoNeg; ///< The value of the TLS application layer protocol extension if it is enabled
-        /// whether Change Cipher Spec message included in ServerHello
-        /// handshake message
-        bool hasCcsOrNst;
         /// The client random number
         unsigned char client_random[SSL3_RANDOM_SIZE];
         SBuf sessionId;
@@ -114,7 +154,7 @@ public:
     void prepReadBuf();
 
     /// Reads data from socket and record them to a buffer
-    int readAndBuffer(char *buf, int size, BIO *table, const char *description);
+    int readAndBuffer(BIO *table, const char *description);
 
     const MemBuf &rBufData() {return rbuf;}
 protected:
@@ -180,7 +220,7 @@ private:
 class ServerBio: public Bio
 {
 public:
-    explicit ServerBio(const int anFd): Bio(anFd), helloMsgSize(0), helloBuild(false), allowSplice(false), allowBump(false), holdWrite_(false), record_(false), bumpMode_(bumpNone) {}
+    explicit ServerBio(const int anFd): Bio(anFd), helloMsgSize(0), helloBuild(false), allowSplice(false), allowBump(false), holdWrite_(false), holdRead_(true), bumpMode_(bumpNone), rbufConsumePos(0) {}
     /// The ServerBio version of the Ssl::Bio::stateChanged method
     virtual void stateChanged(const SSL *ssl, int where, int ret);
     /// The ServerBio version of the Ssl::Bio::write method
@@ -198,12 +238,19 @@ public:
     void setClientFeatures(const sslFeatures &features);
 
     bool resumingSession();
+
+    /// Reads Server hello message+certificates+ServerHelloDone message sent
+    /// by server and buffer it to rbuf member
+    int readAndBufferServerHelloMsg(BIO *table, const char *description);
+
     /// The write hold state
     bool holdWrite() const {return holdWrite_;}
     /// Enables or disables the write hold state
     void holdWrite(bool h) {holdWrite_ = h;}
-    /// Enables or disables the input data recording, for internal analysis.
-    void recordInput(bool r) {record_ = r;}
+    /// The read hold state
+    bool holdRead() const {return holdRead_;}
+    /// Enables or disables the read hold state
+    void holdRead(bool h) {holdRead_ = h;}
     /// Whether we can splice or not the SSL stream
     bool canSplice() {return allowSplice;}
     /// Whether we can bump or not the SSL stream
@@ -211,17 +258,29 @@ public:
     /// The bumping mode
     void mode(Ssl::BumpMode m) {bumpMode_ = m;}
     Ssl::BumpMode bumpMode() {return bumpMode_;} ///< return the bumping mode
+
+    /// Return true if the Server hello message received
+    bool gotHello() const { return (parser_.parseDone && !parser_.parseError); }
+
+    /// Return true if the Server Hello parsing failed
+    bool gotHelloFailed() const { return (parser_.parseDone && parser_.parseError); }
+
+    const Ssl::X509_STACK_Pointer &serverCertificates();
 private:
     sslFeatures clientFeatures; ///< SSL client features extracted from ClientHello message or SSL object
-    sslFeatures serverFeatures; ///< SSL server features extracted from ServerHello message
     SBuf helloMsg; ///< Used to buffer output data.
     mb_size_t  helloMsgSize;
     bool helloBuild; ///< True if the client hello message sent to the server
     bool allowSplice; ///< True if the SSL stream can be spliced
     bool allowBump;  ///< True if the SSL stream can be bumped
     bool holdWrite_;  ///< The write hold state of the bio.
-    bool record_; ///< If true the input data recorded to rbuf for internal use
+    bool holdRead_;  ///< The read hold state of the bio.
     Ssl::BumpMode bumpMode_;
+
+    ///< The size of data stored in rbuf which passed to the openSSL
+    size_t rbufConsumePos;
+    HandshakeParser parser_; ///< The SSL messages parser.
+    Ssl::X509_STACK_Pointer serverCertificates_; ///< The certificates chain sent by the SSL server
 };
 
 inline
index 2ee7e21c80e11e82e432ef79ba93afca20b263e6..6bc7dd426fe2eace2235ccd76ed85a16a5b19481 100644 (file)
 
 #include <cerrno>
 
+// TODO: Move ssl_ex_index_* global variables from global.cc here.
+int ssl_ex_index_ssl_untrusted_chain = -1;
+
 static void setSessionCallbacks(SSL_CTX *ctx);
 Ipc::MemMap *SslSessionCache = NULL;
 const char *SslSessionCacheName = "ssl_session_cache";
 
+static Ssl::CertsIndexedList SquidUntrustedCerts;
+
 const EVP_MD *Ssl::DefaultSignHash = NULL;
 
 const char *Ssl::BumpModeStr[] = {
@@ -469,6 +474,27 @@ ssl_initialize(void)
     ssl_ex_index_ssl_errors =  SSL_get_ex_new_index(0, (void *) "ssl_errors", NULL, NULL, &ssl_free_SslErrors);
     ssl_ex_index_ssl_cert_chain = SSL_get_ex_new_index(0, (void *) "ssl_cert_chain", NULL, NULL, &ssl_free_CertChain);
     ssl_ex_index_ssl_validation_counter = SSL_get_ex_new_index(0, (void *) "ssl_validation_counter", NULL, NULL, &ssl_free_int);
+    ssl_ex_index_ssl_untrusted_chain = SSL_get_ex_new_index(0, (void *) "ssl_untrusted_chain", NULL, NULL, &ssl_free_CertChain);
+}
+
+bool
+Ssl::loadCerts(const char *certsFile, Ssl::CertsIndexedList &list)
+{
+    BIO *in = BIO_new_file(certsFile, "r");
+    if (!in) {
+        debugs(83, DBG_IMPORTANT, "Failed to open '" << certsFile << "' to load certificates");
+        return false;
+    }
+    
+    X509 *aCert;
+    while((aCert = PEM_read_bio_X509(in, NULL, NULL, NULL))) {
+        static char buffer[2048];
+        X509_NAME_oneline(X509_get_subject_name(aCert), buffer, sizeof(buffer));
+        list.insert(std::pair<SBuf, X509 *>(SBuf(buffer), aCert));
+    }
+    debugs(83, 4, "Loaded " << list.size() << " certificates from file: '" << certsFile << "'");
+    BIO_free(in);
+    return true;
 }
 
 #if defined(SSL3_FLAGS_NO_RENEGOTIATE_CIPHERS)
@@ -1118,6 +1144,200 @@ void Ssl::addChainToSslContext(SSL_CTX *sslContext, STACK_OF(X509) *chain)
     }
 }
 
+static const char *
+hasAuthorityInfoAccessCaIssuers(X509 *cert)
+{
+    AUTHORITY_INFO_ACCESS *info;
+    if (!cert)
+        return NULL;
+    info = (AUTHORITY_INFO_ACCESS *)X509_get_ext_d2i(cert, NID_info_access, NULL, NULL);
+    if (!info)
+        return NULL;
+
+    static char uri[MAX_URL];
+    uri[0] = '\0';
+
+    for (int i = 0; i < sk_ACCESS_DESCRIPTION_num(info); i++) {
+        ACCESS_DESCRIPTION *ad = sk_ACCESS_DESCRIPTION_value(info, i);
+        if (OBJ_obj2nid(ad->method) == NID_ad_ca_issuers) {
+            if (ad->location->type == GEN_URI) {
+                xstrncpy(uri, (char *)ASN1_STRING_data(ad->location->d.uniformResourceIdentifier), sizeof(uri));
+            }
+            break;
+        }
+    }
+    AUTHORITY_INFO_ACCESS_free(info);
+    return uri[0] != '\0' ? uri : NULL;
+}
+
+/// quickly find a certificate with a given issuer in Ssl::CertsIndexedList.
+static X509 *
+findCertByIssuerFast(Ssl::CertsIndexedList &list, X509 *cert)
+{
+    static char buffer[2048];
+
+    if (X509_NAME *issuerName = X509_get_issuer_name(cert))
+        X509_NAME_oneline(issuerName, buffer, sizeof(buffer));
+    else
+        return NULL;
+
+    const auto ret = list.equal_range(SBuf(buffer));
+    for (Ssl::CertsIndexedList::iterator it = ret.first; it != ret.second; ++it) {
+        X509 *issuer = it->second;
+        if (X509_check_issued(cert, issuer)) {
+            return issuer;
+        }
+    }
+    return NULL;
+}
+
+/// slowly find a certificate with a given issuer using linear search
+static X509 *
+findCertByIssuerSlowly(STACK_OF(X509) *sk, X509 *cert)
+{
+    if (!sk)
+        return NULL;
+
+    const int skItemsNum = sk_X509_num(sk);
+    for (int i = 0; i < skItemsNum; ++i) {
+        X509 *issuer = sk_X509_value(sk, i);
+        if (X509_check_issued(cert, issuer) == X509_V_OK)
+            return issuer;
+    }
+    return NULL;
+}
+
+const char *
+Ssl::uriOfIssuerIfMissing(X509 *cert,  Ssl::X509_STACK_Pointer const &serverCertificates)
+{
+    if (!cert || !serverCertificates.get())
+        return NULL;
+
+    if (!findCertByIssuerSlowly(serverCertificates.get(), cert)) {
+        //if issuer is missing ...
+        if (!findCertByIssuerFast(SquidUntrustedCerts, cert)) {
+            // and issuer not found in local untrusted certificates database 
+            if (const char *issuerUri = hasAuthorityInfoAccessCaIssuers(cert)) {
+                // There is a URI where we can download a certificate.
+                // Check to see if this is required
+                return issuerUri;
+            }
+        }
+    }
+    return NULL;
+}
+
+void
+Ssl::missingChainCertificatesUrls(std::queue<SBuf> &URIs, Ssl::X509_STACK_Pointer const &serverCertificates)
+{
+    if (!serverCertificates.get())
+        return;
+
+    for (int i = 0; i < sk_X509_num(serverCertificates.get()); ++i) {
+        X509 *cert = sk_X509_value(serverCertificates.get(), i);
+        if (const char *issuerUri = uriOfIssuerIfMissing(cert, serverCertificates))
+            URIs.push(SBuf(issuerUri));
+    }
+}
+
+void
+Ssl::SSL_add_untrusted_cert(SSL *ssl, X509 *cert)
+{
+    STACK_OF(X509) *untrustedStack = static_cast <STACK_OF(X509) *>(SSL_get_ex_data(ssl, ssl_ex_index_ssl_untrusted_chain));
+    if (!untrustedStack) {
+        untrustedStack = sk_X509_new_null();
+        if (!SSL_set_ex_data(ssl, ssl_ex_index_ssl_untrusted_chain, untrustedStack)) {
+            sk_X509_pop_free(untrustedStack, X509_free); //?? print an error?
+            return;
+        }
+    }
+    sk_X509_push(untrustedStack, cert);
+}
+
+/// add missing issuer certificates to untrustedCerts
+static void
+completeIssuers(X509_STORE_CTX *ctx, STACK_OF(X509) *untrustedCerts)
+{
+    debugs(83, 2,  "completing " << sk_X509_num(untrustedCerts) << " OpenSSL untrusted certs using " << SquidUntrustedCerts.size() << " configured untrusted certificates");
+
+    int depth = ctx->param->depth;
+    X509 *current = ctx->cert;
+    int i = 0;
+    for (i = 0; current && (i < depth); ++i) {
+        if (X509_check_issued(current, current)) {
+            // either ctx->cert is itself self-signed or untrustedCerts
+            // aready contain the self-signed current certificate
+            break;
+        }
+
+        // untrustedCerts is short, not worth indexing
+        X509 *issuer = findCertByIssuerSlowly(untrustedCerts, current);
+        if (!issuer) {
+            if ((issuer = findCertByIssuerFast(SquidUntrustedCerts, current)))
+                sk_X509_push(untrustedCerts, issuer);
+        }
+        current = issuer;
+    }
+
+    if (i >= depth)
+        debugs(83, 2,  "exceeded the maximum certificate chain length: " << depth);
+}
+
+/// OpenSSL certificate validation callback.
+static int
+untrustedToStoreCtx_cb(X509_STORE_CTX *ctx,void *data)
+{
+    debugs(83, 4,  "Try to use pre-downloaded intermediate certificates\n");
+
+    SSL *ssl = (SSL *)X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
+    STACK_OF(X509) *sslUntrustedStack = static_cast <STACK_OF(X509) *>(SSL_get_ex_data(ssl, ssl_ex_index_ssl_untrusted_chain));
+
+    // OpenSSL already maintains ctx->untrusted but we cannot modify
+    // internal OpenSSL list directly. We have to give OpenSSL our own
+    // list, but it must include certificates on the OpenSSL ctx->untrusted
+    STACK_OF(X509) *oldUntrusted = ctx->untrusted;
+    STACK_OF(X509) *sk = sk_X509_dup(oldUntrusted); // oldUntrusted is always not NULL
+
+    for (int i = 0; i < sk_X509_num(sslUntrustedStack); ++i) {
+        X509 *cert = sk_X509_value(sslUntrustedStack, i);
+        sk_X509_push(sk, cert);
+    }
+
+    // If the local untrusted certificates internal database is used
+    // run completeIssuers to add missing certificates if possible.
+    if (SquidUntrustedCerts.size() > 0)
+        completeIssuers(ctx, sk);
+
+    X509_STORE_CTX_set_chain(ctx, sk); // No locking/unlocking, just sets ctx->untrusted
+    int ret = X509_verify_cert(ctx);
+    X509_STORE_CTX_set_chain(ctx, oldUntrusted); // Set back the old untrusted list
+    sk_X509_free(sk); // Release sk list
+    return ret;
+}
+
+void
+Ssl::useSquidUntrusted(SSL_CTX *sslContext)
+{
+    SSL_CTX_set_cert_verify_callback(sslContext, untrustedToStoreCtx_cb, NULL);
+}
+
+bool
+Ssl::loadSquidUntrusted(const char *path)
+{
+    return Ssl::loadCerts(path, SquidUntrustedCerts);
+}
+
+void
+Ssl::unloadSquidUntrusted()
+{
+    if (SquidUntrustedCerts.size()) {
+        for (Ssl::CertsIndexedList::iterator it = SquidUntrustedCerts.begin(); it != SquidUntrustedCerts.end(); ++it) {
+            X509_free(it->second);
+        }
+        SquidUntrustedCerts.clear();
+    }
+}
+
 /**
  \ingroup ServerProtocolSSLInternal
  * Read certificate from file.
index 949fea79a20746947f68ce4817b3d01ff934a30b..67772492020a26f843b1b85798c1334ebe426977 100644 (file)
@@ -12,6 +12,7 @@
 #define SQUID_SSL_SUPPORT_H
 
 #include "base/CbDataList.h"
+#include "SBuf.h"
 #include "security/forward.h"
 #include "ssl/gadgets.h"
 
@@ -24,6 +25,9 @@
 #if HAVE_OPENSSL_ENGINE_H
 #include <openssl/engine.h>
 #endif
+#include <queue>
+#include <map>
+
 
 /**
  \defgroup ServerProtocolSSLAPI Server-Side SSL API
@@ -155,6 +159,52 @@ inline const char *bumpMode(int bm)
     return (0 <= bm && bm < Ssl::bumpEnd) ? Ssl::BumpModeStr[bm] : NULL;
 }
 
+/// certificates indexed by issuer name
+typedef std::multimap<SBuf, X509 *> CertsIndexedList;
+
+/**
+ \ingroup ServerProtocolSSLAPI
+ * Load PEM-encoded certificates from the given file.
+ */
+bool loadCerts(const char *certsFile, Ssl::CertsIndexedList &list);
+
+/**
+ \ingroup ServerProtocolSSLAPI
+ * Load PEM-encoded certificates to the squid untrusteds certificates
+ * internal DB from the given file.
+ */
+bool loadSquidUntrusted(const char *path);
+
+/**
+ \ingroup ServerProtocolSSLAPI
+ * Removes all certificates from squid untrusteds certificates
+ * internal DB and frees all memory
+ */
+void unloadSquidUntrusted();
+
+/**
+ \ingroup ServerProtocolSSLAPI
+ * Add the certificate cert to ssl object untrusted certificates.
+ * Squid uses an attached to SSL object list of untrusted certificates,
+ * with certificates which can be used to complete incomplete chains sent
+ * by the SSL server.
+ */
+void SSL_add_untrusted_cert(SSL *ssl, X509 *cert);
+
+/**
+ \ingroup ServerProtocolSSLAPI
+ * Searches in serverCertificates list for the cert issuer and if not found
+ * and Authority Info Access of cert provides a URI return it.
+ */
+const char *uriOfIssuerIfMissing(X509 *cert,  Ssl::X509_STACK_Pointer const &serverCertificates);
+
+/**
+ \ingroup ServerProtocolSSLAPI
+ * Fill URIs queue with the uris of missing certificates from serverCertificate chain
+ * if this information provided by Authority Info Access.
+ */
+void missingChainCertificatesUrls(std::queue<SBuf> &URIs, Ssl::X509_STACK_Pointer const &serverCertificates);
+
 /**
   \ingroup ServerProtocolSSLAPI
   * Generate a certificate to be used as untrusted signing certificate, based on a trusted CA
@@ -209,6 +259,13 @@ bool configureSSLUsingPkeyAndCertFromMemory(SSL *ssl, const char *data, AnyP::Po
  */
 void addChainToSslContext(SSL_CTX *sslContext, STACK_OF(X509) *certList);
 
+/**
+  \ingroup ServerProtocolSSLAPI
+  * Configures sslContext to use squid untrusted certificates internal list
+  * to complete certificate chains when verifies SSL servers certificates.
+ */
+void useSquidUntrusted(SSL_CTX *sslContext);
+
 /**
  \ingroup ServerProtocolSSLAPI
  *  Read certificate, private key and any certificates which must be chained from files.