]> git.ipfire.org Git - thirdparty/squid.git/commitdiff
Hello forwarding patch
authorChristos Tsantilas <chtsanti@users.sourceforge.net>
Mon, 1 Apr 2013 15:37:42 +0000 (18:37 +0300)
committerChristos Tsantilas <chtsanti@users.sourceforge.net>
Mon, 1 Apr 2013 15:37:42 +0000 (18:37 +0300)
Patch general description:
  - Adds the peek-and-splice SSL bumping mode.

  - The squid client side read the ssl client hello message. At this time just
    buffer the hello message and does not pass it to openSSL subsytem.
    The SSL-client-to-squid connection pauses here.

  - Squid extracts the ssl client hello message features and configure the
    squid-to-SSL-server SSL context to have the same features

  - Squid sents the SSL hello message to SSL server and gets the response.
    At this time just buffer the SSL server hello response and does not pass it
    to openSSL subsystem. The squid-to-SSL-server connection pauses here.

  - Squids decides (at this time always) to bump the connection, so it starts
    the squid-to-SSL-server connection. The connection to SSL server
    established.

  - Squids gets the server certificates, builds new based on these certificates
    and configure the SSL-client-to-squid SSL context with the generated
    certificates, and allow the client hello message to enter openSSL subsystem
    and establish the SSL connection with the client.

Technical details:
  - client_side.cc:
     The clientNegotiateSSL split in to two functions the clientNegotiateSSL and
     Squid_SSL_accept, to support the new "pause SSL connection" feature.
     Add three new ConnStateData methods (startPeekAndSplice, startPeekAndSpliceDone
     and doPeekAndSpliceStep) to control SSL-client-to-squid connection pause/start.

  - forward.cc:
    Add code to the FwdState::initiateSSL method to retrieve SSL features from
    client SSL hello message and configure the SSL connectio with the SSL
    server.
    A new method added, the FwdState::checkForPeekAndSplice which called for
    peek-and-splice SSL bumping mode to decide if we need to splice or bump
    the SSL connection.

  - ssl/bio.cc, features added:
    Ssl::ClientBio: Read and buffer the hello message
    Ssl::ServerBio: Hacks the openSSL hello message while the message sent to
                    the server.
                    Buffer the SSL server hello message response
    Ssl::Bio:sslFeatures: A new class which is able to extract and store SSL
                    features from SSL openSSL objects, or from raw SSL hello
                    messages.

13 files changed:
src/cache_cf.cc
src/cf.data.pre
src/client_side.cc
src/client_side.h
src/client_side_request.h
src/forward.cc
src/forward.h
src/ssl/ServerBump.cc
src/ssl/ServerBump.h
src/ssl/bio.cc
src/ssl/bio.h
src/ssl/support.cc
src/ssl/support.h

index e9df3560bc3ac41e19853bee38f32f093950bc69..a7420ffb6cdcfef5714d9e4611d33b34265cbf8e 100644 (file)
@@ -4561,6 +4561,9 @@ static void parse_sslproxy_ssl_bump(acl_access **ssl_bump)
     } else if (strcmp(bm, Ssl::BumpModeStr[Ssl::bumpServerFirst]) == 0) {
         A->allow.kind = Ssl::bumpServerFirst;
         bumpCfgStyleNow = bcsNew;
+    } else if (strcmp(bm, Ssl::BumpModeStr[Ssl::bumpPeekAndSplice]) == 0) {
+        A->allow.kind = Ssl::bumpPeekAndSplice;
+        bumpCfgStyleNow = bcsNew;
     } else if (strcmp(bm, Ssl::BumpModeStr[Ssl::bumpNone]) == 0) {
         A->allow.kind = Ssl::bumpNone;
         bumpCfgStyleNow = bcsNew;
index d04b07a618a7c46b38dc6f377dafb32a97347a1f..3304d8daa241b13095a0329f2524040fbe798c6a 100644 (file)
@@ -2214,6 +2214,10 @@ DOC_START
                the client, using a mimicked server certificate. Works with both
                CONNECT requests and intercepted SSL connections.
 
+          peek-and-splice
+               Decides if the connection should bumped or not based on 
+               client-to-squid and server-to-squid SSL hello messages.
+
            none
                Become a TCP tunnel without decoding the connection.
                Works with both CONNECT requests and intercepted SSL
index 609973ff9e4f589c5255bfbed7bbe6e0d366e0f4..70379490ca9de98db095c5a6969b4bcefb52b854 100644 (file)
 #include "ClientInfo.h"
 #endif
 #if USE_SSL
+#include "ssl/bio.h"
 #include "ssl/ProxyCerts.h"
 #include "ssl/context_storage.h"
 #include "ssl/helper.h"
@@ -3425,7 +3426,7 @@ httpAccept(const CommAcceptCbParams &params)
 static SSL *
 httpsCreate(const Comm::ConnectionPointer &conn, SSL_CTX *sslContext)
 {
-    if (SSL *ssl = Ssl::Create(sslContext, conn->fd, "client https start")) {
+    if (SSL *ssl = Ssl::CreateServer(sslContext, conn->fd, "client https start")) {
         debugs(33, 5, "httpsCreate: will negotate SSL on " << conn);
         return ssl;
     }
@@ -3434,12 +3435,10 @@ httpsCreate(const Comm::ConnectionPointer &conn, SSL_CTX *sslContext)
     return NULL;
 }
 
-/** negotiate an SSL connection */
-static void
-clientNegotiateSSL(int fd, void *data)
+static bool
+Squid_SSL_accept(ConnStateData *conn, PF *callback)
 {
-    ConnStateData *conn = (ConnStateData *)data;
-    X509 *client_cert;
+    int fd = conn->clientConnection->fd;
     SSL *ssl = fd_table[fd].ssl;
     int ret;
 
@@ -3449,48 +3448,61 @@ clientNegotiateSSL(int fd, void *data)
         switch (ssl_error) {
 
         case SSL_ERROR_WANT_READ:
-            Comm::SetSelect(fd, COMM_SELECT_READ, clientNegotiateSSL, conn, 0);
-            return;
+            Comm::SetSelect(fd, COMM_SELECT_READ, callback, conn, 0);
+            return false;
 
         case SSL_ERROR_WANT_WRITE:
-            Comm::SetSelect(fd, COMM_SELECT_WRITE, clientNegotiateSSL, conn, 0);
-            return;
+            Comm::SetSelect(fd, COMM_SELECT_WRITE, callback, conn, 0);
+            return false;
 
         case SSL_ERROR_SYSCALL:
 
             if (ret == 0) {
-                debugs(83, 2, "clientNegotiateSSL: Error negotiating SSL connection on FD " << fd << ": Aborted by client");
+                debugs(83, 2, "Error negotiating SSL connection on FD " << fd << ": Aborted by client: " << ssl_error);
                 comm_close(fd);
-                return;
+                return false;
             } else {
                 int hard = 1;
 
                 if (errno == ECONNRESET)
                     hard = 0;
 
-                debugs(83, hard ? 1 : 2, "clientNegotiateSSL: Error negotiating SSL connection on FD " <<
+                debugs(83, hard ? 1 : 2, "Error negotiating SSL connection on FD " <<
                        fd << ": " << strerror(errno) << " (" << errno << ")");
 
                 comm_close(fd);
 
-                return;
+                return false;
             }
 
         case SSL_ERROR_ZERO_RETURN:
-            debugs(83, DBG_IMPORTANT, "clientNegotiateSSL: Error negotiating SSL connection on FD " << fd << ": Closed by client");
+            debugs(83, DBG_IMPORTANT, "Error negotiating SSL connection on FD " << fd << ": Closed by client");
             comm_close(fd);
-            return;
+            return false;
 
         default:
-            debugs(83, DBG_IMPORTANT, "clientNegotiateSSL: Error negotiating SSL connection on FD " <<
+            debugs(83, DBG_IMPORTANT, "Error negotiating SSL connection on FD " <<
                    fd << ": " << ERR_error_string(ERR_get_error(), NULL) <<
                    " (" << ssl_error << "/" << ret << ")");
             comm_close(fd);
-            return;
+            return false;
         }
 
         /* NOTREACHED */
     }
+    return true;
+}
+
+/** negotiate an SSL connection */
+static void
+clientNegotiateSSL(int fd, void *data)
+{
+    ConnStateData *conn = (ConnStateData *)data;
+    X509 *client_cert;
+    SSL *ssl = fd_table[fd].ssl;
+
+    if (!Squid_SSL_accept(conn, clientNegotiateSSL))
+        return;
 
     if (SSL_session_reused(ssl)) {
         debugs(83, 2, "clientNegotiateSSL: Session " << SSL_get_session(ssl) <<
@@ -3707,8 +3719,16 @@ ConnStateData::sslCrtdHandleReply(const HelperReply &reply)
                 debugs(33, 5, HERE << "Certificate for " << sslConnectHostOrIp << " cannot be generated. ssl_crtd response: " << reply_message.getBody());
             } else {
                 debugs(33, 5, HERE << "Certificate for " << sslConnectHostOrIp << " was successfully recieved from ssl_crtd");
-                SSL_CTX *ctx = Ssl::generateSslContextUsingPkeyAndCertFromMemory(reply_message.getBody().c_str(), *port);
-                getSslContextDone(ctx, true);
+                if (sslServerBump && sslServerBump->mode == Ssl::bumpPeekAndSplice) {
+                    doPeekAndSpliceStep();
+                    SSL *ssl = fd_table[clientConnection->fd].ssl;
+                    bool ret = Ssl::configureSSLUsingPkeyAndCertFromMemory(ssl, reply_message.getBody().c_str(), *port);
+                    if (!ret)
+                        debugs(33, 5, HERE << "Failed to set certificates to ssl object for PeekAndSplice mode");
+                } else {
+                    SSL_CTX *ctx = Ssl::generateSslContextUsingPkeyAndCertFromMemory(reply_message.getBody().c_str(), *port);
+                    getSslContextDone(ctx, true);
+                }
                 return;
             }
         }
@@ -3815,22 +3835,25 @@ ConnStateData::getSslContextStart()
         sslBumpCertKey = certProperties.dbKey().c_str();
         assert(sslBumpCertKey.defined() && sslBumpCertKey[0] != '\0');
 
-        debugs(33, 5, HERE << "Finding SSL certificate for " << sslBumpCertKey << " in cache");
-        Ssl::LocalContextStorage & ssl_ctx_cache(Ssl::TheGlobalContextStorage.getLocalStorage(port->s));
-        SSL_CTX * dynCtx = NULL;
-        Ssl::SSL_CTX_Pointer *cachedCtx = ssl_ctx_cache.get(sslBumpCertKey.termedBuf());
-        if (cachedCtx && (dynCtx = cachedCtx->get())) {
-            debugs(33, 5, HERE << "SSL certificate for " << sslBumpCertKey << " have found in cache");
-            if (Ssl::verifySslCertificate(dynCtx, certProperties)) {
-                debugs(33, 5, HERE << "Cached SSL certificate for " << sslBumpCertKey << " is valid");
-                getSslContextDone(dynCtx);
-                return;
+        // Disable caching for bumpPeekAndSplice mode
+        if (!(sslServerBump && sslServerBump->mode == Ssl::bumpPeekAndSplice)) {
+            debugs(33, 5, HERE << "Finding SSL certificate for " << sslBumpCertKey << " in cache");
+            Ssl::LocalContextStorage & ssl_ctx_cache(Ssl::TheGlobalContextStorage.getLocalStorage(port->s));
+            SSL_CTX * dynCtx = NULL;
+            Ssl::SSL_CTX_Pointer *cachedCtx = ssl_ctx_cache.get(sslBumpCertKey.termedBuf());
+            if (cachedCtx && (dynCtx = cachedCtx->get())) {
+                debugs(33, 5, HERE << "SSL certificate for " << sslBumpCertKey << " have found in cache");
+                if (Ssl::verifySslCertificate(dynCtx, certProperties)) {
+                    debugs(33, 5, HERE << "Cached SSL certificate for " << sslBumpCertKey << " is valid");
+                    getSslContextDone(dynCtx);
+                    return;
+                } else {
+                    debugs(33, 5, HERE << "Cached SSL certificate for " << sslBumpCertKey << " is out of date. Delete this certificate from cache");
+                    ssl_ctx_cache.del(sslBumpCertKey.termedBuf());
+                }
             } else {
-                debugs(33, 5, HERE << "Cached SSL certificate for " << sslBumpCertKey << " is out of date. Delete this certificate from cache");
-                ssl_ctx_cache.del(sslBumpCertKey.termedBuf());
+                debugs(33, 5, HERE << "SSL certificate for " << sslBumpCertKey << " haven't found in cache");
             }
-        } else {
-            debugs(33, 5, HERE << "SSL certificate for " << sslBumpCertKey << " haven't found in cache");
         }
 
 #if USE_SSL_CRTD
@@ -3852,8 +3875,15 @@ ConnStateData::getSslContextStart()
 #endif // USE_SSL_CRTD
 
         debugs(33, 5, HERE << "Generating SSL certificate for " << certProperties.commonName);
-        dynCtx = Ssl::generateSslContext(certProperties, *port);
-        getSslContextDone(dynCtx, true);
+        if (sslServerBump && sslServerBump->mode == Ssl::bumpPeekAndSplice) {
+            doPeekAndSpliceStep();
+            SSL *ssl = fd_table[clientConnection->fd].ssl;
+            if (!Ssl::configureSSL(ssl, certProperties, *port))
+                debugs(33, 5, HERE << "Failed to set certificates to ssl object for PeekAndSplice mode");
+        } else {
+            SSL_CTX *dynCtx = Ssl::generateSslContext(certProperties, *port);
+            getSslContextDone(dynCtx, true);
+        }
         return;
     }
     getSslContextDone(NULL);
@@ -3927,11 +3957,84 @@ ConnStateData::switchToHttps(HttpRequest *request, Ssl::BumpMode bumpServerMode)
         FwdState::fwdStart(clientConnection, sslServerBump->entry, sslServerBump->request);
         return;
     }
+    else if (bumpServerMode == Ssl::bumpPeekAndSplice) {
+        request->flags.sslPeek = true;
+        sslServerBump = new Ssl::ServerBump(request, NULL, Ssl::bumpPeekAndSplice);
+        startPeekAndSplice();
+        return;        
+    }
 
     // otherwise, use sslConnectHostOrIp
     getSslContextStart();
 }
 
+/** negotiate an SSL connection */
+static void
+clientPeekAndSpliceSSL(int fd, void *data)
+{
+    ConnStateData *conn = (ConnStateData *)data;
+    SSL *ssl = fd_table[fd].ssl;
+
+    debugs(83, 2, "Start peek and splice on" << fd);
+
+    if (!Squid_SSL_accept(conn, clientPeekAndSpliceSSL))
+        debugs(83, 2, "SSL_accept failed.");
+
+    BIO *b = SSL_get_rbio(ssl);
+    assert(b);
+    Ssl::ClientBio *bio = static_cast<Ssl::ClientBio *>(b->ptr);
+    if (bio->gotHello()) {
+        debugs(83, 2, "I got hello. Start forwarding the request!!! ");
+        Comm::SetSelect(fd, COMM_SELECT_READ, NULL, NULL, 0);
+        Comm::SetSelect(fd, COMM_SELECT_WRITE, NULL, NULL, 0);
+        conn->startPeekAndSpliceDone();
+        return;
+    }
+}
+
+void ConnStateData::startPeekAndSplice()
+{
+    // will call httpsPeeked() with certificate and connection, eventually
+    SSL_CTX *unConfiguredCTX = Ssl::createSSLContext(port->signingCert, port->signPkey, *port);
+    fd_table[clientConnection->fd].dynamicSslContext = unConfiguredCTX;
+
+    if (!httpsCreate(clientConnection, unConfiguredCTX))
+        return;
+
+    // commSetConnTimeout() was called for this request before we switched.
+
+    // Disable the client read handler until CachePeer selection is complete
+    Comm::SetSelect(clientConnection->fd, COMM_SELECT_READ, NULL, NULL, 0);
+    Comm::SetSelect(clientConnection->fd, COMM_SELECT_READ, clientPeekAndSpliceSSL, this, 0);
+    switchedToHttps_ = true;
+
+    SSL *ssl = fd_table[clientConnection->fd].ssl;
+    BIO *b = SSL_get_rbio(ssl);
+    Ssl::ClientBio *bio = static_cast<Ssl::ClientBio *>(b->ptr);
+    bio->hold(true);
+}
+
+void
+ConnStateData::startPeekAndSpliceDone()
+{
+    FwdState::fwdStart(clientConnection, sslServerBump->entry, sslServerBump->request);
+}
+
+void
+ConnStateData::doPeekAndSpliceStep()
+{
+    SSL *ssl = fd_table[clientConnection->fd].ssl;
+    BIO *b = SSL_get_rbio(ssl);
+    assert(b);
+    Ssl::ClientBio *bio = static_cast<Ssl::ClientBio *>(b->ptr);
+
+    debugs(33, 5, HERE << "PeekAndSplice mode, proceed with client negotiation. Currrent state:" << SSL_state_string_long(ssl));
+    bio->hold(false);
+
+    Comm::SetSelect(clientConnection->fd, COMM_SELECT_WRITE, clientNegotiateSSL, this, 0);
+    switchedToHttps_ = true;
+}
+
 void
 ConnStateData::httpsPeeked(Comm::ConnectionPointer serverConnection)
 {
index 208b3bf3619e2bd3278af0b440a96fe3e0900f83..c5a0099234cc1cdd0e989bd9e9ff2ce3eadf6fd7 100644 (file)
@@ -332,6 +332,14 @@ public:
     void quitAfterError(HttpRequest *request); // meant to be private
 
 #if USE_SSL
+    /// Initializes and starts a peek-and-splice negotiation with the SSL client
+    void startPeekAndSplice();
+    /// Called when the initialization of peek-and-splice negotiation finidhed
+    void startPeekAndSpliceDone();
+    /// Called when a peek-and-splice step finished. For example after
+    /// server-side SSL certificates received and client-side SSL certificates
+    /// generated
+    void doPeekAndSpliceStep();
     /// called by FwdState when it is done bumping the server
     void httpsPeeked(Comm::ConnectionPointer serverConnection);
 
index a5c08211162b12304f2e7c5dd2348fca6df9b156..4dfd63b6b8c2620e9101483d9e79bc064669c678 100644 (file)
@@ -155,7 +155,7 @@ public:
     /// returns raw sslBump mode value
     Ssl::BumpMode sslBumpNeed() const { return sslBumpNeed_; }
     /// returns true if and only if the request needs to be bumped
-    bool sslBumpNeeded() const { return sslBumpNeed_ == Ssl::bumpServerFirst || sslBumpNeed_ == Ssl::bumpClientFirst; }
+    bool sslBumpNeeded() const { return sslBumpNeed_ == Ssl::bumpServerFirst || sslBumpNeed_ == Ssl::bumpClientFirst || sslBumpNeed_ == Ssl::bumpPeekAndSplice; }
     /// set the sslBumpNeeded state
     void sslBumpNeed(Ssl::BumpMode mode);
     void sslBumpStart();
index 6a15ed82a5b659f1d936ad2be05242c4bb73398a..f774f0bf70c47fc5b2eebb7d41a31f670c35668e 100644 (file)
@@ -72,6 +72,7 @@
 #include "urn.h"
 #include "whois.h"
 #if USE_SSL
+#include "ssl/bio.h"
 #include "ssl/cert_validate_message.h"
 #include "ssl/Config.h"
 #include "ssl/helper.h"
@@ -651,6 +652,8 @@ FwdState::negotiateSSL(int fd)
     unsigned long ssl_lib_error = SSL_ERROR_NONE;
     SSL *ssl = fd_table[fd].ssl;
     int ret;
+    BIO *b = SSL_get_rbio(ssl);
+    Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(b->ptr);
 
     if ((ret = SSL_connect(ssl)) <= 0) {
         int ssl_error = SSL_get_error(ssl, ret);
@@ -667,6 +670,11 @@ FwdState::negotiateSSL(int fd)
             return;
 
         case SSL_ERROR_WANT_WRITE:
+            if (request->clientConnectionManager->sslBumpMode == Ssl::bumpPeekAndSplice && srvBio->holdWrite()) {
+                debugs(81, DBG_IMPORTANT, "hold write on SSL connection on FD " << fd);
+                checkForPeekAndSplice();
+                return;
+            }
             Comm::SetSelect(fd, COMM_SELECT_WRITE, fwdNegotiateSSLWrapper, this, 0);
             return;
 
@@ -796,6 +804,23 @@ FwdState::negotiateSSL(int fd)
     dispatch();
 }
 
+void
+FwdState::checkForPeekAndSplice()
+{
+    SSL *ssl = fd_table[serverConn->fd].ssl;
+    BIO *b = SSL_get_rbio(ssl);
+    Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(b->ptr);
+    debugs(83,5, "Will check for peek and splice on fd " << serverConn->fd);
+    const bool splice = false;
+    if (!splice) {
+        //Allow write, proceed with the connection
+        srvBio->holdWrite(false);
+        srvBio->recordInput(false);
+        Comm::SetSelect(serverConn->fd, COMM_SELECT_WRITE, fwdNegotiateSSLWrapper, this, 0);
+        debugs(83,5, "Retry the fwdNegotiateSSL on fd " << serverConn->fd);
+    }
+}
+
 void
 FwdState::sslCrtvdHandleReplyWrapper(void *data, Ssl::CertValidationResponse const &validationResponse)
 {
@@ -928,7 +953,7 @@ FwdState::initiateSSL()
 
     assert(sslContext);
 
-    SSL *ssl = Ssl::Create(sslContext, fd, "server https start");
+    SSL *ssl = Ssl::CreateClient(sslContext, fd, "server https start");
     if (!ssl) {
         ErrorState *anErr = new ErrorState(ERR_SOCKET_FAILURE, HTTP_INTERNAL_SERVER_ERROR, request);
         // TODO: create Ssl::ErrorDetail with OpenSSL-supplied error code
@@ -954,6 +979,28 @@ FwdState::initiateSSL()
         if (peer->sslSession)
             SSL_set_session(ssl, peer->sslSession);
 
+    } else if (request->clientConnectionManager->sslBumpMode == Ssl::bumpPeekAndSplice) {
+        SSL *clientSsl = fd_table[request->clientConnectionManager->clientConnection->fd].ssl;
+        BIO *b = SSL_get_rbio(clientSsl);
+        Ssl::ClientBio *clnBio = static_cast<Ssl::ClientBio *>(b->ptr);
+        const Ssl::Bio::sslFeatures &features = clnBio->getFeatures();
+        if (features.sslVersion != -1) {
+            SSL_set_ssl_method(ssl, Ssl::method(features.toSquidSSLVersion()));
+            if (!features.serverName.empty())
+                SSL_set_tlsext_host_name(ssl, features.serverName.c_str());
+            if (!features.clientRequestedCiphers.empty())
+                SSL_set_cipher_list(ssl, features.clientRequestedCiphers.c_str());
+#ifdef SSL_OP_NO_COMPRESSION /* XXX: OpenSSL 0.9.8k lacks SSL_OP_NO_COMPRESSION */
+            if (features.compressMethod == 0)
+                SSL_set_options(ssl, SSL_OP_NO_COMPRESSION);
+#endif
+            if (features.sslVersion >= 3) {
+                b = SSL_get_rbio(ssl);
+                Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(b->ptr);
+                srvBio->setClientRandom(features.client_random);
+                srvBio->recordInput(true);
+            }
+        }
     } else {
         // While we are peeking at the certificate, we may not know the server
         // name that the client will request (after interception or CONNECT)
index aad16c21ac8d2488cc545813e384dc6d589bfe39..aeca1aa2f6e7f300128ff5b125942c53d24651d5 100644 (file)
@@ -70,8 +70,11 @@ public:
     void connectStart();
     void connectDone(const Comm::ConnectionPointer & conn, comm_err_t status, int xerrno);
     void connectTimeout(int fd);
+#if USE_SSL
     void initiateSSL();
     void negotiateSSL(int fd);
+    void checkForPeekAndSplice();
+#endif
     bool checkRetry();
     bool checkRetriable();
     void dispatch();
index 10d718285828af5dc2c08f3ad2dc26e739bf282a..59eb761359ee86ba0799f9cbc0f69d9479f5c5a9 100644 (file)
 
 CBDATA_NAMESPACED_CLASS_INIT(Ssl, ServerBump);
 
-Ssl::ServerBump::ServerBump(HttpRequest *fakeRequest, StoreEntry *e):
+Ssl::ServerBump::ServerBump(HttpRequest *fakeRequest, StoreEntry *e, Ssl::BumpMode md):
         request(fakeRequest),
-        sslErrors(NULL)
+        sslErrors(NULL),
+        mode(md)
 {
     debugs(33, 4, HERE << "will peek at " << request->GetHost() << ':' << request->port);
     const char *uri = urlCanonical(request);
index f214e726aa1362719218a9e12e78aebf0d745d8b..b681505507ee1f5e3d5c623f62a3168c3d49ff37 100644 (file)
@@ -20,7 +20,7 @@ namespace Ssl
 class ServerBump
 {
 public:
-    explicit ServerBump(HttpRequest *fakeRequest, StoreEntry *e = NULL);
+    explicit ServerBump(HttpRequest *fakeRequest, StoreEntry *e = NULL, Ssl::BumpMode mode = Ssl::bumpServerFirst);
     ~ServerBump();
 
     /// faked, minimal request; required by server-side API
@@ -28,6 +28,7 @@ public:
     StoreEntry *entry; ///< for receiving Squid-generated error messages
     Ssl::X509_Pointer serverCert; ///< HTTPS server certificate
     Ssl::Errors *sslErrors; ///< SSL [certificate validation] errors
+    Ssl::BumpMode mode; ///< The SSL server bump mode
 
 private:
     store_client *sc; ///< dummy client to prevent entry trimming
index 1bee31cbb9d693f690f0e157a58f4edd92f1e9a3..d697a67a0c76e88a9ac7a1bea0a721c4e203228d 100644 (file)
@@ -4,17 +4,23 @@
  */
 
 #include "squid.h"
+#include "ssl/support.h"
 
 /* support.cc says this is needed */
 #if USE_SSL
 
 #include "comm.h"
+#include "ip/Address.h"
+#include "fde.h"
+#include "globals.h"
 #include "Mem.h"
 #include "ssl/bio.h"
 #if HAVE_OPENSSL_SSL_H
 #include <openssl/ssl.h>
 #endif
 
+#undef DO_SSLV23
+
 // TODO: fde.h should probably export these for wrappers like ours
 extern int default_read_method(int, char *, int);
 extern int default_write_method(int, const char *, int);
@@ -50,10 +56,10 @@ static BIO_METHOD SquidMethods = {
 };
 
 BIO *
-Ssl::Bio::Create(const int fd)
+Ssl::Bio::Create(const int fd, Ssl::Bio::Type type)
 {
     if (BIO *bio = BIO_new(&SquidMethods)) {
-        BIO_int_ctrl(bio, BIO_C_SET_FD, BIO_NOCLOSE, fd);
+        BIO_int_ctrl(bio, BIO_C_SET_FD, type, fd);
         return bio;
     }
     return NULL;
@@ -136,10 +142,231 @@ Ssl::Bio::stateChanged(const SSL *ssl, int where, int ret)
     // else if (where & SSL_CB_HANDSHAKE_DONE)
     //    debugs(83, 9, "SSL connection established");
 
-    debugs(83, 7, "FD " << fd_ << " now: " << where << ' ' <<
+    debugs(83, 7, "FD " << fd_ << " now: 0x" << std::hex << where << std::dec << ' ' <<
            SSL_state_string(ssl) << " (" << SSL_state_string_long(ssl) << ")");
 }
 
+bool
+Ssl::ClientBio::isClientHello(int state)
+{
+    return (state == SSL2_ST_GET_CLIENT_HELLO_A ||
+            state == SSL3_ST_SR_CLNT_HELLO_A ||
+            state == SSL23_ST_SR_CLNT_HELLO_A ||
+            state == SSL23_ST_SR_CLNT_HELLO_B ||
+            state == SSL3_ST_SR_CLNT_HELLO_B ||
+            state == SSL3_ST_SR_CLNT_HELLO_C
+        );
+}
+
+void 
+Ssl::ClientBio::stateChanged(const SSL *ssl, int where, int ret)
+{
+    Ssl::Bio::stateChanged(ssl, where, ret);
+}
+
+int
+Ssl::ClientBio::write(const char *buf, int size, BIO *table)
+{
+    if (holdWrite_) {
+        BIO_set_retry_write(table);
+        return 0;
+    }
+
+    return Ssl::Bio::write(buf, size, table);
+}
+
+const char *objToString(unsigned char const *bytes, int len)
+{
+    static std::string buf;
+    buf.clear();
+    for(int i = 0; i < len; i++ ) {
+        char tmp[3];
+        snprintf(tmp, sizeof(tmp), "%.2x", bytes[i]);
+        buf.append(tmp);
+    }
+    return buf.c_str();
+}
+
+int
+Ssl::ClientBio::read(char *buf, int size, BIO *table)
+{
+    if (headerState < 2) {
+
+        if (rbuf.isNull())
+            rbuf.init(1024, 4096);
+
+        size = rbuf.spaceSize() > size ? size : rbuf.spaceSize();
+
+        if (!size)
+            return 0;
+
+        int bytes = Ssl::Bio::read(buf, size, table);
+        if (!bytes)
+            return 0;
+        rbuf.append(buf, bytes);
+        debugs(83, 7, "rbuf size: " << rbuf.contentSize());
+    }
+
+    if (headerState == 0) {
+
+        const unsigned char *head = (const unsigned char *)rbuf.content();
+        const char *s = objToString(head, rbuf.contentSize());
+        debugs(83, 7, "SSL Header: " << s);
+        if (rbuf.contentSize() < 5) {
+            BIO_set_retry_read(table);
+            return 0;
+        }
+
+        if (head[0] == 0x16) {
+            debugs(83, 7, "SSL version 3 handshake message");
+            headerBytes = (head[3] << 8) + head[4];
+            debugs(83, 7, "SSL Header Size: " << headerBytes);
+#ifdef DO_SSLV23
+        } else if ((head[0] & 0x80) && head[2] == 0x01 && head[3] == 0x03) { 
+            debugs(83, 7, "SSL version 2 handshake message with v3 support");
+            headerBytes = head[1];
+#endif
+        }else {
+            debugs(83, 7, "Not an SSL acceptable handshake message (SSLv2 message?)");
+            return -1;
+        }
+
+        headerState = 1; //Next state
+    }
+
+    if (headerState == 1) {
+        const unsigned char *head = (const unsigned char *)rbuf.content();
+        const char *s = objToString(head, rbuf.contentSize());
+        debugs(83, 7, "SSL Header: " << s);
+
+        if (headerBytes > rbuf.contentSize()) {
+            BIO_set_retry_read(table);
+            return -1;
+        }
+        features.get((const unsigned char *)rbuf.content());
+        headerState = 2;
+    }
+
+    if (holdRead_) {
+        debugs(83, 7, "Hold flag is set, retry latter. (Hold " << size << "bytes)");
+        BIO_set_retry_read(table);
+        return -1;
+    }
+
+    if (headerState >=2) {
+        if (rbuf.hasContent()) {
+            int bytes = (size <= rbuf.contentSize() ? size : rbuf.contentSize());
+            memcpy(buf, rbuf.content(), bytes);
+            rbuf.consume(bytes);
+            return bytes;
+        } else
+            return Ssl::Bio::read(buf, size, table);
+    }
+
+    return -1;
+}
+
+void
+Ssl::ServerBio::stateChanged(const SSL *ssl, int where, int ret)
+{
+    Ssl::Bio::stateChanged(ssl, where, ret);
+}
+
+void
+Ssl::ServerBio::setClientRandom(const unsigned char *r)
+{
+    memcpy(clientRandom, r, SSL3_RANDOM_SIZE);
+    randomSet = true;
+};
+
+int
+Ssl::ServerBio::read(char *buf, int size, BIO *table)
+{
+    int bytes = Ssl::Bio::read(buf, size, table);
+
+    if (bytes > 0 && record_) {
+        if (rbuf.isNull())
+            rbuf.init(1024, 8196);
+        rbuf.append(buf, bytes);
+    }
+    return bytes;
+}
+
+int
+Ssl::ServerBio::write(const char *buf, int size, BIO *table)
+{
+
+    if (holdWrite_) {
+        debugs(83, 7,  "Hold write, for SSL connection on " << fd_);
+        BIO_set_retry_write(table);
+        return -1;
+    }
+
+    if (!helloBuild) {
+        if (
+            buf[1] >= 3  //it is an SSL Version3 message
+            && buf[0] == 0x16 // and it is a Handshake/Hello message
+            ) {
+            if (helloMsg.isNull())
+                helloMsg.init(1024, 4096);
+
+            //Hello message is the first message we write to server
+            assert(!helloMsg.hasContent());
+
+            SSL *ssl = fd_table[fd_].ssl;
+            if (randomSet && ssl && ssl->s3) {
+                assert(size > 11 + SSL3_RANDOM_SIZE);
+                helloMsg.append(buf, 11);
+                //The random number is stored in the 11 position of the 
+                // message we are going to sent
+                helloMsg.append((char *)clientRandom, SSL3_RANDOM_SIZE);
+                size_t len = size - 11 - SSL3_RANDOM_SIZE;
+                helloMsg.append(buf + 11 + SSL3_RANDOM_SIZE, len);
+
+                // We need to fix the random in SSL struct:
+                memcpy(ssl->s3->client_random, clientRandom, SSL3_RANDOM_SIZE);
+                // We also need to fix the raw message in SSL struct
+                // stored in SSL->init_buf. Looks that it is used to get
+                // digest of the previous sent SSL message, to compute keys
+                // for encryption/decryption:
+                memcpy(ssl->init_buf->data + 6, clientRandom, SSL3_RANDOM_SIZE);
+
+                debugs(83, 7,  "SSL HELLO message for FD " << fd_ << ": Random number is adjusted");
+            }
+        }
+        helloBuild = true;
+        helloMsgSize = helloMsg.contentSize();
+    }
+
+    if (helloMsg.hasContent()) {
+        debugs(83, 7,  "buffered write for FD " << fd_);
+        int ret = Ssl::Bio::write(helloMsg.content(), helloMsg.contentSize(), table);
+        helloMsg.consume(ret);
+        if (helloMsg.hasContent()) {
+            // We need to retry sendind data.
+            // Say to openSSL to retry sending hello message
+            BIO_set_retry_write(table);
+            return -1;
+        }
+
+        // Sending hello message complete. Do not send more data for now...
+        holdWrite_ = true; 
+        // The size should be less than the size of the hello message
+        assert(size >= helloMsgSize);
+        return helloMsgSize;
+    } else
+        return Ssl::Bio::write(buf, size, table);
+}
+
+void
+Ssl::ServerBio::flush(BIO *table)
+{
+    if (helloMsg.hasContent()) {
+        int ret = Ssl::Bio::write(helloMsg.content(), helloMsg.contentSize(), table);
+        helloMsg.consume(ret);
+    }
+}
+
 /// initializes BIO table after allocation
 static int
 squid_bio_create(BIO *bi)
@@ -196,7 +423,11 @@ squid_bio_ctrl(BIO *table, int cmd, long arg1, void *arg2)
     case BIO_C_SET_FD: {
         assert(arg2);
         const int fd = *static_cast<int*>(arg2);
-        Ssl::Bio *bio = new Ssl::Bio(fd);
+        Ssl::Bio *bio;
+        if (arg1 == Ssl::Bio::BIO_TO_SERVER)
+            bio = new Ssl::ServerBio(fd);
+        else
+            bio = new Ssl::ClientBio(fd);
         assert(!table->ptr);
         table->ptr = bio;
         table->init = 1;
@@ -222,7 +453,7 @@ squid_bio_ctrl(BIO *table, int cmd, long arg1, void *arg2)
         if (table->init) {
             Ssl::Bio *bio = static_cast<Ssl::Bio*>(table->ptr);
             assert(bio);
-            bio->flush();
+            bio->flush(table);
             return 1;
         }
         return 0;
@@ -255,4 +486,268 @@ squid_ssl_info(const SSL *ssl, int where, int ret)
     }
 }
 
+Ssl::Bio::sslFeatures::sslFeatures(): sslVersion(-1), compressMethod(-1)
+{
+    memset(client_random, 0, SSL3_RANDOM_SIZE);
+}
+
+int Ssl::Bio::sslFeatures::toSquidSSLVersion() const
+{
+    if(sslVersion == SSL2_VERSION)
+        return 2;
+    else if(sslVersion == SSL3_VERSION)
+        return 3;
+    else if(sslVersion == TLS1_VERSION)
+        return 4;
+#if OPENSSL_VERSION_NUMBER >= 0x10001000L
+    else if(sslVersion == TLS1_1_VERSION)
+        return 5;
+    else if(sslVersion == TLS1_2_VERSION)
+        return 6;
+#endif
+    else
+        return 1;
+}
+
+bool
+Ssl::Bio::sslFeatures::get(const SSL *ssl)
+{
+    sslVersion = SSL_version(ssl);
+    debugs(83, 7, "SSL version: " << SSL_get_version(ssl) << " (" << sslVersion << ")");
+
+    if(const char *server = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name))
+        serverName = server;
+    debugs(83, 7, "SNI server name: " << serverName);
+
+    if (ssl->session->compress_meth)
+            compressMethod = ssl->session->compress_meth;
+    else if(sslVersion >= 3) //if it is 3 or newer version then compression is disabled
+        compressMethod = 0;
+    debugs(83, 7, "SSL compression: " << compressMethod);
+
+    STACK_OF(SSL_CIPHER) * ciphers = NULL;
+    if (ssl->server)
+        ciphers = ssl->session->ciphers;
+    else
+        ciphers = ssl->cipher_list;
+    if (ciphers) {
+        for (int i = 0; i < sk_SSL_CIPHER_num(ciphers); ++i) {
+            SSL_CIPHER *c = sk_SSL_CIPHER_value(ciphers, i);
+            if (c != NULL) {
+                if(!clientRequestedCiphers.empty())
+                    clientRequestedCiphers.append(":");
+                clientRequestedCiphers.append(c->name);
+            }
+        }
+    }
+    debugs(83, 7, "Ciphers requested by client: " << clientRequestedCiphers);
+
+    if (sslVersion >=3 && ssl->s3 && ssl->s3->client_random[0]) {
+        memcpy(client_random, ssl->s3->client_random, SSL3_RANDOM_SIZE);
+    }
+
+#if 0 /* XXX: OpenSSL 0.9.8k lacks at least some of these tlsext_* fields */
+    //The following extracted for logging purpuses:
+    // TLSEXT_TYPE_ec_point_formats
+    unsigned char *p;
+    int len;
+    if (ssl->server) {
+        p = ssl->session->tlsext_ecpointformatlist;
+        len = ssl->session->tlsext_ecpointformatlist_length;
+    } else {
+        p = ssl->tlsext_ecpointformatlist;
+        len = ssl->tlsext_ecpointformatlist_length;
+    }
+    if (p) {
+        ecPointFormatList = objToString(p, len);
+        debugs(83, 7, "tlsExtension ecPointFormatList of length " << len << " :" << ecPointFormatList);
+    }
+
+    // TLSEXT_TYPE_elliptic_curves
+    if (ssl->server) {
+        p = ssl->session->tlsext_ellipticcurvelist;
+        len = ssl->session->tlsext_ellipticcurvelist_length;
+    } else {
+        p = ssl->tlsext_ellipticcurvelist;
+        len = ssl->tlsext_ellipticcurvelist_length;
+    }
+    if (p) {
+        ellipticCurves = objToString(p, len);
+        debugs(83, 7, "tlsExtension ellipticCurveList of length " <<  len <<" :" << ellipticCurves);
+    }
+    // TLSEXT_TYPE_opaque_prf_input
+    p = NULL;
+    if (ssl->server) {
+        if (ssl->s3 &&  ssl->s3->client_opaque_prf_input) {
+            p = (unsigned char *)ssl->s3->client_opaque_prf_input;
+            len = ssl->s3->client_opaque_prf_input_len;
+        }
+    } else {
+        p = (unsigned char *)ssl->tlsext_opaque_prf_input;
+        len = ssl->tlsext_opaque_prf_input_len;
+    }
+    if (p) {
+        debugs(83, 7, "tlsExtension client-opaque-prf-input of length " << len);
+        opaquePrf = objToString(p, len);
+    }
+#endif
+    return true;
+}
+
+bool
+Ssl::Bio::sslFeatures::get(const unsigned char *hello)
+{
+    // The SSL handshake message should starts with a 0x16 byte
+    if (hello[0] == 0x16) {
+        return parseV3Hello(hello);
+#ifdef DO_SSLV23
+    } else if ((hello[0] & 0x80) && hello[2] == 0x01 && hello[3] == 0x03) {
+        return parseV23Hello(hello);
+#endif
+    }
+    
+    debugs(83, 7, "Not a known SSL handshake message");
+    return false;
+}
+
+bool
+Ssl::Bio::sslFeatures::parseV3Hello(const unsigned char *hello)
+{
+    debugs(83, 7, "Get fake features from v3 hello message.");
+    // The SSL version exist in the 2nd and 3rd bytes
+    sslVersion = (hello[1] << 8) | hello[2];
+    debugs(83, 7, "Get fake features. Version :" << std::hex << std::setw(8) << std::setfill('0')<< sslVersion);
+
+    // The following hello message size exist in 4th and 5th bytes
+    int helloSize = (hello[3] << 8) | hello[4];
+    helloSize += 5; //Include the 5 header bytes.
+
+    //For SSLv3 or TLSv1.* protocols we can get some more informations
+    if (hello[1] == 0x3 && hello[5] == 0x1 /*HELLO A message*/) {
+        // Get the correct version of the sub-hello message
+        sslVersion = (hello[9] << 8) | hello[10];
+        //Get Client Random number. It starts on the position 11 of hello message
+        memcpy(client_random, hello + 11, SSL3_RANDOM_SIZE);
+        debugs(83, 7, "Client random: " <<  objToString(client_random, SSL3_RANDOM_SIZE));
+
+        // At the position 43 (11+SSL3_RANDOM_SIZE)
+        int sessIDLen = (int)hello[43];
+        debugs(83, 7, "Session ID Length: " <<  sessIDLen);
+
+        //Ciphers list. It is stored after the Session ID.
+        const unsigned char *ciphers = hello + 44 + sessIDLen;
+        int ciphersLen = (ciphers[0] << 8) | ciphers[1];
+        ciphers += 2;
+        if (ciphersLen) {
+            const SSL_METHOD *method = SSLv3_method();
+            int cs = method->put_cipher_by_char(NULL, NULL);
+            assert(cs > 0);
+            for (int i = 0; i < ciphersLen; i += cs) {
+                const SSL_CIPHER *c = method->get_cipher_by_char((ciphers + i));
+                if (c != NULL) {
+                    if(!clientRequestedCiphers.empty())
+                        clientRequestedCiphers.append(":");
+                    clientRequestedCiphers.append(c->name);
+                }
+            }
+        }
+        debugs(83, 7, "Ciphers requested by client: " << clientRequestedCiphers);
+
+        // Compression field: 1 bytes the number of compression methods and
+        // 1 byte for each compression method
+        const unsigned char *compression = ciphers + ciphersLen;
+        if (compression[0] > 1)
+            compressMethod = 1;
+        else
+            compressMethod = 0;
+        debugs(83, 7, "SSL compression methods number: " << (int)compression[0]);
+
+        const unsigned char *extensions = compression + 1 + (int)compression[0];
+        if (extensions <  hello + helloSize) { 
+            int extensionsLen = (extensions[0] << 8) | extensions[1];
+            const unsigned char *ext = extensions + 2;
+            while (ext < extensions+extensionsLen){
+                short extType = (ext[0] << 8) | ext[1];
+                ext += 2;
+                short extLen = (ext[0] << 8) | ext[1];
+                ext += 2;
+                debugs(83, 7, "SSL Exntension: " << std::hex << extType << " of size:" << extLen);
+                //The SNI extension has the type 0 (extType == 0)
+                // The two first bytes indicates the length of the SNI data (should be extLen-2)
+                // The next byte is the hostname type, it should be '0' for normal hostname (ext[2] == 0)
+                // The 3rd and 4th bytes are the length of the hostname
+                if (extType == 0 && ext[2] == 0) {
+                    int hostLen = (ext[3] << 8) | ext[4];
+                    serverName.assign((const char *)(ext+5), hostLen);
+                    debugs(83, 7, "Found server name: " << serverName);
+                }
+                ext += extLen;
+            }
+        }
+    }
+    return true;
+}
+
+bool
+Ssl::Bio::sslFeatures::parseV23Hello(const unsigned char *hello)
+{
+#ifdef DO_SSLV23
+    debugs(83, 7, "Get fake features from v23 hello message.");
+    sslVersion = (hello[3] << 8) | hello[4];
+    debugs(83, 7, "Get fake features. Version :" << std::hex << std::setw(8) << std::setfill('0')<< sslVersion);
+
+    // The following hello message size exist in 2nd byte
+    int helloSize = hello[1];
+    helloSize += 2; //Include the 2 header bytes.
+
+    //Ciphers list. It is stored after the Session ID.
+
+    int ciphersLen = (hello[5] << 8) | hello[6];
+    const unsigned char *ciphers = hello + 11;
+    if (ciphersLen) {
+        const SSL_METHOD *method = SSLv23_method();
+        int cs = method->put_cipher_by_char(NULL, NULL);
+        assert(cs > 0);
+        for (int i = 0; i < ciphersLen; i += cs) {
+            // The v2 hello messages cipher has 3 bytes.
+            // The v2 cipher has the first byte not null
+            // Because we are going to sent only v3 message we 
+            // are ignoring these ciphers
+            if (ciphers[i] != 0)
+                continue;
+            const SSL_CIPHER *c = method->get_cipher_by_char((ciphers + i + 1));
+            if (c != NULL) {
+                if(!clientRequestedCiphers.empty())
+                    clientRequestedCiphers.append(":");
+                clientRequestedCiphers.append(c->name);
+            }
+        }
+    }
+    debugs(83, 7, "Ciphers requested by client: " << clientRequestedCiphers);    
+
+    //Get Client Random number. It starts on the position 11 of hello message
+    memcpy(client_random, ciphers + ciphersLen, SSL3_RANDOM_SIZE);
+    debugs(83, 7, "Client random: " <<  objToString(client_random, SSL3_RANDOM_SIZE));
+
+    compressMethod = 0;
+    return true;
+#else
+    return false;
+#endif
+}
+
+std::ostream &
+Ssl::Bio::sslFeatures::print(std::ostream &os) const
+{
+    static std::string buf;
+    return os << "v" << sslVersion << 
+        " SNI:" << (serverName.empty() ? "-" : serverName) <<
+        " comp:" << compressMethod <<
+        " Ciphers:" << clientRequestedCiphers <<
+        " Random:" << objToString(client_random, SSL3_RANDOM_SIZE) <<
+        " ecPointFormats:" << ecPointFormatList <<
+        " ec:" << ellipticCurves <<
+        " opaquePrf:" << opaquePrf;
+}
+
 #endif /* USE_SSL */
index 2d0fa0468877495e0dba56303d85ae25556d0d28..690a6ab09924cf3bc392e5f7eefc32ebf2680745 100644 (file)
@@ -1,6 +1,8 @@
 #ifndef SQUID_SSL_BIO_H
 #define SQUID_SSL_BIO_H
 
+#include "MemBuf.h"
+#include <iosfwd>
 #if HAVE_OPENSSL_BIO_H
 #include <openssl/bio.h>
 #endif
@@ -13,29 +15,139 @@ namespace Ssl {
 /// BIO source and sink node, handling socket I/O and monitoring SSL state
 class Bio {
 public:
+    enum Type {
+        BIO_TO_CLIENT = 6000,
+        BIO_TO_SERVER
+    };
+
+    /// Class to store SSL connection features
+    class sslFeatures {
+    public:
+        sslFeatures();
+        bool get(const SSL *ssl); ///< Retrieves the features from SSL object
+        bool get(const unsigned char *hello); ///< Retrieves the features from raw SSL hello message
+        bool parseV3Hello(const unsigned char *hello);
+        bool parseV23Hello(const unsigned char *hello);
+        /// Prints to os stream a human readable form of sslFeatures object
+        std::ostream & print(std::ostream &os) const;
+        /// Converts to the internal squid SSL version form the sslVersion
+        int toSquidSSLVersion() const;
+    public:
+        int sslVersion; ///< The requested/used SSL version
+        int compressMethod; ///< The requested/used compressed  method
+        std::string serverName; ///< The SNI hostname, if any
+        std::string clientRequestedCiphers; ///< The client requested ciphers
+        std::string ecPointFormatList;///< tlsExtension ecPointFormatList
+        std::string ellipticCurves; ///< tlsExtension ellipticCurveList
+        std::string opaquePrf; ///< tlsExtension opaquePrf
+        /// The client random number
+        unsigned char client_random[SSL3_RANDOM_SIZE];
+    };
     explicit Bio(const int anFd);
     ~Bio();
 
-    int write(const char *buf, int size, BIO *table);
-    int read(char *buf, int size, BIO *table);
-    void flush() {} // we do not buffer (yet?)
+    /// Writes the given data to socket
+    virtual int write(const char *buf, int size, BIO *table);
+
+    /// Reads data from socket
+    virtual int read(char *buf, int size, BIO *table);
 
-    int fd() const { return fd_; }
+    /// Flushes any buffered data to socket.
+    /// The Ssl::Bio does not buffer any data, so this method has nothing to do
+    virtual void flush(BIO *table) {}
+
+    int fd() const { return fd_; } ///< The SSL socket descriptor
 
     /// Called by linked SSL connection whenever state changes, an alert
     /// appears, or an error occurs. See SSL_set_info_callback().
-    void stateChanged(const SSL *ssl, int where, int ret);
+    virtual void stateChanged(const SSL *ssl, int where, int ret);
 
     /// Creates a low-level BIO table, creates a high-level Ssl::Bio object
     /// for a given socket, and then links the two together via BIO_C_SET_FD.
-    static BIO *Create(const int fd);
+    static BIO *Create(const int fd, Type type);
     /// Tells ssl connection to use BIO and monitor state via stateChanged()
     static void Link(SSL *ssl, BIO *bio);
 
-private:
+protected:
     const int fd_; ///< the SSL socket we are reading and writing
 };
 
+/// BIO node to handle socket IO for squid client side
+class ClientBio: public Bio {
+public:
+    explicit ClientBio(const int anFd): Bio(anFd), holdRead_(false), holdWrite_(false), headerState(0), headerBytes(0) {}
+
+    /// The ClientBio version of the Ssl::Bio::stateChanged method
+    /// When the client hello message retrieved, fill the 
+    /// "features" member with the client provided informations.
+    virtual void stateChanged(const SSL *ssl, int where, int ret);
+    /// The ClientBio version of the Ssl::Bio::write method
+    virtual int write(const char *buf, int size, BIO *table);
+    /// The ClientBio version of the Ssl::Bio::read method
+    /// If the holdRead flag is true then it does not write any data
+    /// to socket and sets the "read retry" flag of the BIO to true
+    virtual int read(char *buf, int size, BIO *table);
+    /// Return true if the client hello message received and analized
+    bool gotHello() {return features.sslVersion != -1;}
+    /// Return the SSL features requested by SSL client
+    const Bio::sslFeatures &getFeatures() const {return features;}
+    /// Prevents or allow writting on socket.
+    void hold(bool h) {holdRead_ = holdWrite_ = h;}
+
+private:
+    /// True if the SSL state corresponds to a hello message
+    bool isClientHello(int state);
+    /// The futures retrieved from client SSL hello message
+    Bio::sslFeatures features;
+    bool holdRead_; ///< The read hold state of the bio.
+    bool holdWrite_;  ///< The write hold state of the bio.
+    MemBuf rbuf;  ///< Used to buffer input data.
+    int headerState;
+    int headerBytes;
+};
+
+/// BIO node to handle socket IO for squid server side
+class ServerBio: public Bio {
+public:
+    explicit ServerBio(const int anFd): Bio(anFd), randomSet(false), helloMsgSize(0), helloBuild(false), holdWrite_(false), record_(false) {}
+    /// 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
+    /// If a clientRandom number is set then rewrites the raw hello message
+    /// "client random" field with the provided random number.
+    /// It may buffer the output packets.
+    virtual int write(const char *buf, int size, BIO *table);
+    /// The ServerBio version of the Ssl::Bio::read method
+    /// If the record flag is set then append the data to the rbuf member
+    virtual int read(char *buf, int size, BIO *table);
+    /// The ServerBio version of the Ssl::Bio::flush method.
+    /// Flushes any buffered data
+    virtual void flush(BIO *table);
+    /// Sets the random number to use in client SSL HELLO message
+    void setClientRandom(const unsigned char *r);
+
+    bool holdWrite() const {return holdWrite_;}
+    void holdWrite(bool h) {holdWrite_ = h;}
+    void recordInput(bool r) {record_ = r;}
+    const MemBuf &rBufData() {return rbuf;}
+private:
+    /// A random number to use as "client random" in client hello message
+    unsigned char clientRandom[SSL3_RANDOM_SIZE];
+    bool randomSet; ///< True if the clientRandom member is set and can be used
+    MemBuf helloMsg; ///< Used to buffer output data.
+    int helloMsgSize;
+    bool helloBuild; ///< True if the client hello message sent to the server
+    bool holdWrite_;  ///< The write hold state of the bio.
+    bool record_;
+    MemBuf rbuf;  ///< Used to buffer input data.
+};
+
+inline
+std::ostream &operator <<(std::ostream &os, Ssl::Bio::sslFeatures const &f)
+{
+    return f.print(os);
+}
+
 } // namespace Ssl
 
 #endif /* SQUID_SSL_BIO_H */
index 8918bd8f27699f92ac82c10fd289d325da5331ea..a6be51a195d8d2d81f7dd23ebf21d6442fa01a6f 100644 (file)
@@ -59,6 +59,7 @@ const char *Ssl::BumpModeStr[] = {
     "none",
     "client-first",
     "server-first",
+    "peek-and-splice",
     NULL
 };
 
@@ -943,32 +944,95 @@ sslCreateServerContext(AnyP::PortCfg &port)
     return sslContext;
 }
 
-SSL_CTX *
-sslCreateClientContext(const char *certfile, const char *keyfile, int version, const char *cipher, const char *options, const char *flags, const char *CAfile, const char *CApath, const char *CRLfile)
+int Ssl::OpenSSLtoSquidSSLVersion(int sslVersion)
 {
-    int ssl_error;
+    if(sslVersion == SSL2_VERSION)
+        return 2;
+    else if(sslVersion == SSL3_VERSION)
+        return 3;
+    else if(sslVersion == TLS1_VERSION)
+        return 4;
+#if OPENSSL_VERSION_NUMBER >= 0x10001000L
+    else if(sslVersion == TLS1_1_VERSION)
+        return 5;
+    else if(sslVersion == TLS1_2_VERSION)
+        return 6;
+#endif
+    else
+        return 1;
+}
+
+
 #if OPENSSL_VERSION_NUMBER < 0x00909000L
-    SSL_METHOD *method;
+SSL_METHOD *
 #else
-    const SSL_METHOD *method;
+const SSL_METHOD *
 #endif
-    SSL_CTX *sslContext;
-    long fl = Ssl::parse_flags(flags);
+Ssl::method(int version)
+{
+    switch (version) {
 
-    ssl_initialize();
+    case 2:
+#ifndef OPENSSL_NO_SSL2
+        debugs(83, 5, "Using SSLv2.");
+        return SSLv2_client_method();
+#else
+        debugs(83, DBG_IMPORTANT, "SSLv2 is not available in this Proxy.");
+        return NULL;
+#endif
+        break;
 
-    if (!keyfile)
-        keyfile = certfile;
+    case 3:
+        debugs(83, 5, "Using SSLv3.");
+        return SSLv3_client_method();
+        break;
 
-    if (!certfile)
-        certfile = keyfile;
+    case 4:
+        debugs(83, 5, "Using TLSv1.");
+        return TLSv1_client_method();
+        break;
+
+    case 5:
+#if OPENSSL_VERSION_NUMBER >= 0x10001000L  // NP: not sure exactly which sub-version yet.
+        debugs(83, 5, "Using TLSv1.1.");
+        return TLSv1_1_client_method();
+#else
+        debugs(83, DBG_IMPORTANT, "TLSv1.1 is not available in this Proxy.");
+        return NULL;
+#endif
+        break;
+
+    case 6:
+#if OPENSSL_VERSION_NUMBER >= 0x10001000L // NP: not sure exactly which sub-version yet.
+        debugs(83, 5, "Using TLSv1.2");
+        return TLSv1_2_client_method();
+#else
+        debugs(83, DBG_IMPORTANT, "TLSv1.2 is not available in this Proxy.");
+        return NULL;
+#endif
+        break;
 
+    case 1:
+
+    default:
+        debugs(83, 5, "Using SSLv2/SSLv3.");
+        return SSLv23_client_method();
+        break;
+    }
+
+    //Not reached
+    return NULL;
+}
+
+const SSL_METHOD *
+Ssl::serverMethod(int version)
+{
     switch (version) {
 
     case 2:
 #ifndef OPENSSL_NO_SSL2
         debugs(83, 5, "Using SSLv2.");
-        method = SSLv2_client_method();
+        return SSLv2_server_method();
 #else
         debugs(83, DBG_IMPORTANT, "SSLv2 is not available in this Proxy.");
         return NULL;
@@ -977,18 +1041,18 @@ sslCreateClientContext(const char *certfile, const char *keyfile, int version, c
 
     case 3:
         debugs(83, 5, "Using SSLv3.");
-        method = SSLv3_client_method();
+        return SSLv3_server_method();
         break;
 
     case 4:
         debugs(83, 5, "Using TLSv1.");
-        method = TLSv1_client_method();
+        return TLSv1_server_method();
         break;
 
     case 5:
 #if OPENSSL_VERSION_NUMBER >= 0x10001000L  // NP: not sure exactly which sub-version yet.
         debugs(83, 5, "Using TLSv1.1.");
-        method = TLSv1_1_client_method();
+        return TLSv1_1_server_method();
 #else
         debugs(83, DBG_IMPORTANT, "TLSv1.1 is not available in this Proxy.");
         return NULL;
@@ -998,7 +1062,7 @@ sslCreateClientContext(const char *certfile, const char *keyfile, int version, c
     case 6:
 #if OPENSSL_VERSION_NUMBER >= 0x10001000L // NP: not sure exactly which sub-version yet.
         debugs(83, 5, "Using TLSv1.2");
-        method = TLSv1_2_client_method();
+        return TLSv1_2_server_method();
 #else
         debugs(83, DBG_IMPORTANT, "TLSv1.2 is not available in this Proxy.");
         return NULL;
@@ -1009,10 +1073,37 @@ sslCreateClientContext(const char *certfile, const char *keyfile, int version, c
 
     default:
         debugs(83, 5, "Using SSLv2/SSLv3.");
-        method = SSLv23_client_method();
+        return SSLv23_server_method();
         break;
     }
 
+    //Not reached
+    return NULL;
+}
+
+SSL_CTX *
+sslCreateClientContext(const char *certfile, const char *keyfile, int version, const char *cipher, const char *options, const char *flags, const char *CAfile, const char *CApath, const char *CRLfile)
+{
+    int ssl_error;
+#if OPENSSL_VERSION_NUMBER < 0x00909000L
+    SSL_METHOD *method;
+#else
+    const SSL_METHOD *method;
+#endif
+    SSL_CTX *sslContext;
+    long fl = Ssl::parse_flags(flags);
+
+    ssl_initialize();
+
+    if (!keyfile)
+        keyfile = certfile;
+
+    if (!certfile)
+        certfile = keyfile;
+
+    if (!(method = Ssl::method(version)))
+        return NULL;
+        
     sslContext = SSL_CTX_new(method);
 
     if (sslContext == NULL) {
@@ -1402,8 +1493,8 @@ Ssl::contextMethod(int version)
 
 /// \ingroup ServerProtocolSSLInternal
 /// Create SSL context and apply ssl certificate and private key to it.
-static SSL_CTX *
-createSSLContext(Ssl::X509_Pointer & x509, Ssl::EVP_PKEY_Pointer & pkey, AnyP::PortCfg &port)
+SSL_CTX *
+Ssl::createSSLContext(Ssl::X509_Pointer & x509, Ssl::EVP_PKEY_Pointer & pkey, AnyP::PortCfg &port)
 {
     Ssl::SSL_CTX_Pointer sslContext(SSL_CTX_new(port.contextMethod));
 
@@ -1450,6 +1541,49 @@ Ssl::generateSslContext(CertificateProperties const &properties, AnyP::PortCfg &
     return createSSLContext(cert, pkey, port);
 }
 
+bool
+Ssl::configureSSL(SSL *ssl, CertificateProperties const &properties, AnyP::PortCfg &port)
+{
+    Ssl::X509_Pointer cert;
+    Ssl::EVP_PKEY_Pointer pkey;
+    if (!generateSslCertificate(cert, pkey, properties))
+        return false;
+
+    if (!cert)
+        return false;
+
+    if (!pkey)
+        return false;
+
+    if (!SSL_use_certificate(ssl, cert.get()))
+        return false;
+
+    if (!SSL_use_PrivateKey(ssl, pkey.get()))
+        return false;
+
+    return true;
+}
+
+bool
+Ssl::configureSSLUsingPkeyAndCertFromMemory(SSL *ssl, const char *data, AnyP::PortCfg &port)
+{
+    Ssl::X509_Pointer cert;
+    Ssl::EVP_PKEY_Pointer pkey;
+    if (!readCertAndPrivateKeyFromMemory(cert, pkey, data))
+        return false;
+
+    if (!cert || !pkey)
+        return false;
+
+    if (!SSL_use_certificate(ssl, cert.get()))
+        return false;
+
+    if (!SSL_use_PrivateKey(ssl, pkey.get()))
+        return false;
+
+    return true;
+}
+
 bool Ssl::verifySslCertificate(SSL_CTX * sslContext, CertificateProperties const &properties)
 {
     // Temporary ssl for getting X509 certificate from SSL_CTX.
@@ -1582,13 +1716,13 @@ bool Ssl::generateUntrustedCert(X509_Pointer &untrustedCert, EVP_PKEY_Pointer &u
 }
 
 SSL *
-Ssl::Create(SSL_CTX *sslContext, const int fd, const char *squidCtx)
+SslCreate(SSL_CTX *sslContext, const int fd, Ssl::Bio::Type type, const char *squidCtx)
 {
     const char *errAction = NULL;
     int errCode = 0;
     if (SSL *ssl = SSL_new(sslContext)) {
         // without BIO, we would call SSL_set_fd(ssl, fd) instead
-        if (BIO *bio = Ssl::Bio::Create(fd)) {
+        if (BIO *bio = Ssl::Bio::Create(fd, type)) {
             Ssl::Bio::Link(ssl, bio); // cannot fail
 
             fd_table[fd].ssl = ssl;
@@ -1611,4 +1745,17 @@ Ssl::Create(SSL_CTX *sslContext, const int fd, const char *squidCtx)
     return NULL;
 }
 
+SSL *
+Ssl::CreateClient(SSL_CTX *sslContext, const int fd, const char *squidCtx)
+{
+    return SslCreate(sslContext, fd, Ssl::Bio::BIO_TO_SERVER, squidCtx);
+}
+
+SSL *
+Ssl::CreateServer(SSL_CTX *sslContext, const int fd, const char *squidCtx)
+{
+    return SslCreate(sslContext, fd, Ssl::Bio::BIO_TO_CLIENT, squidCtx);
+}
+
+
 #endif /* USE_SSL */
index c66a65b369f402326a578f2ae8f7c98fda0c91ce..61dc1fceecd360abcc13e5f31edb6846e72af408 100644 (file)
@@ -74,9 +74,13 @@ typedef int ssl_error_t;
 
 typedef CbDataList<Ssl::ssl_error_t> Errors;
 
-/// Creates SSL connection structure and initializes SSL I/O (Comm and BIO).
+/// Creates SSL Client connection structure and initializes SSL I/O (Comm and BIO).
 /// On errors, emits DBG_IMPORTANT with details and returns NULL.
-SSL *Create(SSL_CTX *sslContext, const int fd, const char *squidCtx);
+SSL *CreateClient(SSL_CTX *sslContext, const int fd, const char *squidCtx);
+
+/// Creates SSL Server connection structure and initializes SSL I/O (Comm and BIO).
+/// On errors, emits DBG_IMPORTANT with details and returns NULL.
+SSL *CreateServer(SSL_CTX *sslContext, const int fd, const char *squidCtx);
 
 } //namespace Ssl
 
@@ -128,7 +132,7 @@ GETX509ATTRIBUTE GetX509Fingerprint;
   \ingroup ServerProtocolSSLAPI
  * Supported ssl-bump modes
  */
-enum BumpMode {bumpNone = 0, bumpClientFirst, bumpServerFirst, bumpEnd};
+enum BumpMode {bumpNone = 0, bumpClientFirst, bumpServerFirst, bumpPeekAndSplice, bumpEnd};
 
 /**
  \ingroup  ServerProtocolSSLAPI
@@ -203,6 +207,27 @@ bool verifySslCertificate(SSL_CTX * sslContext,  CertificateProperties const &pr
  */
 SSL_CTX * generateSslContextUsingPkeyAndCertFromMemory(const char * data, AnyP::PortCfg &port);
 
+/**
+  \ingroup ServerProtocolSSLAPI
+  * Create an SSL context using the provided certificate and key
+ */
+SSL_CTX * createSSLContext(Ssl::X509_Pointer & x509, Ssl::EVP_PKEY_Pointer & pkey, AnyP::PortCfg &port);
+
+/**
+  \ingroup ServerProtocolSSLAPI
+  * Generates a certificate and a private key using provided properies and set it
+  * to SSL object.
+ */
+bool configureSSL(SSL *ssl, CertificateProperties const &properties, AnyP::PortCfg &port);
+
+/**
+  \ingroup ServerProtocolSSLAPI
+  * Read private key and certificate from memory and set it to SSL object
+  * using their.
+ */
+bool configureSSLUsingPkeyAndCertFromMemory(SSL *ssl, const char *data, AnyP::PortCfg &port);
+
+
 /**
   \ingroup ServerProtocolSSLAPI
   * Adds the certificates in certList to the certificate chain of the SSL context
@@ -255,6 +280,16 @@ int asn1timeToString(ASN1_TIME *tm, char *buf, int len);
    \return true if SNI set false otherwise
 */
 bool setClientSNI(SSL *ssl, const char *fqdn);
+
+int OpenSSLtoSquidSSLVersion(int sslVersion);
+
+#if OPENSSL_VERSION_NUMBER < 0x00909000L
+SSL_METHOD *method(int version);
+#else
+const SSL_METHOD *method(int version);
+#endif
+
+const SSL_METHOD *serverMethod(int version);
 } //namespace Ssl
 
 #if _SQUID_WINDOWS_