]> git.ipfire.org Git - thirdparty/squid.git/commitdiff
First fast-sni implementation
authorChristos Tsantilas <chtsanti@users.sourceforge.net>
Thu, 31 Mar 2016 18:37:15 +0000 (21:37 +0300)
committerChristos Tsantilas <chtsanti@users.sourceforge.net>
Thu, 31 Mar 2016 18:37:15 +0000 (21:37 +0300)
Squid parses incomming client SSL hello mesage, before create any openSSL
related structures and objects. After acl check at bumping step2.
Actually creating openSSL objects for client side still can be delayed
untill the server side is finishes. The only reason to create openSSL
structures imediatelly after step2 is to use openSSL to check for unsupported
comunications features and settings and fallback to spliceOnError.

Regression:
  Squid does not parses client Hello message in the case of bump-server-first
  and bump-client-first.
  The supported and requested SSL versions (i %ssl::>received_supported_version
  and %ssl::>received_hello_version formating codes ) can not be logged for
  these modes.

The code still needs cleanup.

src/client_side.cc
src/client_side.h
src/security/Handshake.cc
src/security/NegotiationHistory.cc
src/security/NegotiationHistory.h
src/ssl/PeekingPeerConnector.cc
src/ssl/bio.cc
src/ssl/bio.h

index c0de5520ee2e49332eba1768356fc74bd6897406..b26f118af8db6857b8c7846c2744c11cae8ff510 100644 (file)
@@ -2186,6 +2186,22 @@ ConnStateData::clientParseRequests()
 void
 ConnStateData::afterClientRead()
 {
+#if USE_OPENSSL
+    if (atTlsPeek) {
+        assert(!inBuf.isEmpty());
+        if (!tlsParser.parseClientHello(inBuf)) {
+            if (!tlsParser.parseError) {
+                readSomeData();
+                return;
+            }
+        }
+        atTlsPeek = false;
+        Must(sslServerBump);
+        Must(sslServerBump->act.step1 == Ssl::bumpPeek || sslServerBump->act.step1 == Ssl::bumpStare);
+        startPeekAndSplice();
+        return;
+    }
+#endif
     /* Process next request */
     if (pipeline.empty())
         fd_note(clientConnection->fd, "Reading next request");
@@ -2420,6 +2436,7 @@ ConnStateData::ConnStateData(const MasterXaction::Pointer &xact) :
     switchedToHttps_(false),
     sslServerBump(NULL),
     signAlgorithm(Ssl::algSignTrusted),
+    atTlsPeek(false),
 #endif
     stoppedSending_(NULL),
     stoppedReceiving_(NULL)
@@ -2601,11 +2618,11 @@ Squid_SSL_accept(ConnStateData *conn, PF *callback)
         switch (ssl_error) {
 
         case SSL_ERROR_WANT_READ:
-            Comm::SetSelect(fd, COMM_SELECT_READ, callback, conn, 0);
+            Comm::SetSelect(fd, COMM_SELECT_READ, callback, (callback != NULL ? conn : NULL), 0);
             return 0;
 
         case SSL_ERROR_WANT_WRITE:
-            Comm::SetSelect(fd, COMM_SELECT_WRITE, callback, conn, 0);
+            Comm::SetSelect(fd, COMM_SELECT_WRITE, callback, (callback != NULL ? conn : NULL), 0);
             return 0;
 
         case SSL_ERROR_SYSCALL:
@@ -3092,6 +3109,11 @@ ConnStateData::getSslContextDone(Security::ContextPtr sslContext, bool isNew)
     Comm::SetSelect(clientConnection->fd, COMM_SELECT_READ, NULL, NULL, 0);
     Comm::SetSelect(clientConnection->fd, COMM_SELECT_READ, clientNegotiateSSL, this, 0);
     switchedToHttps_ = true;
+    auto ssl = fd_table[clientConnection->fd].ssl.get();
+    BIO *b = SSL_get_rbio(ssl);
+    Ssl::ClientBio *bio = static_cast<Ssl::ClientBio *>(b->ptr);
+    bio->setReadBufData(inBuf);
+    bio->parsedDetails(tlsParser.details);
 }
 
 void
@@ -3123,7 +3145,19 @@ ConnStateData::switchToHttps(HttpRequest *request, Ssl::BumpMode bumpServerMode)
     } else if (bumpServerMode == Ssl::bumpPeek || bumpServerMode == Ssl::bumpStare) {
         request->flags.sslPeek = true;
         sslServerBump = new Ssl::ServerBump(request, NULL, bumpServerMode);
-        startPeekAndSplice();
+
+        // commSetConnTimeout() was called for this request before we switched.
+        // Fix timeout to request_start_timeout
+        typedef CommCbMemFunT<ConnStateData, CommTimeoutCbParams> TimeoutDialer;
+        AsyncCall::Pointer timeoutCall =  JobCallback(33, 5,
+                                                      TimeoutDialer, this, ConnStateData::requestTimeout);
+        commSetConnTimeout(clientConnection, Config.Timeout.request_start_timeout, timeoutCall);
+        // Also reset receivedFirstByte_ flag to allow this timeout work in the case we have
+        // a bumbed "connect" request on non transparent port.
+        receivedFirstByte_ = false;
+        // Get more data to peek at Tls
+        atTlsPeek = true;
+        readSomeData();
         return;
     }
 
@@ -3148,77 +3182,29 @@ ConnStateData::spliceOnError(const err_type err)
     return false;
 }
 
-/** negotiate an SSL connection */
-static void
-clientPeekAndSpliceSSL(int fd, void *data)
-{
-    ConnStateData *conn = (ConnStateData *)data;
-    auto ssl = fd_table[fd].ssl.get();
-
-    debugs(83, 5, "Start peek and splice on FD " << fd);
 
-    int ret = 0;
-    if ((ret = Squid_SSL_accept(conn, clientPeekAndSpliceSSL)) < 0)
-        debugs(83, 2, "SSL_accept failed.");
-
-    BIO *b = SSL_get_rbio(ssl);
-    assert(b);
-    Ssl::ClientBio *bio = static_cast<Ssl::ClientBio *>(b->ptr);
-    if (ret < 0) {
-        const err_type err = bio->noSslClient() ? ERR_PROTOCOL_UNKNOWN : ERR_SECURE_ACCEPT_FAIL;
-        if (!conn->spliceOnError(err))
-            conn->clientConnection->close();
+void ConnStateData::startPeekAndSplice()
+{
+    if (tlsParser.parseError) {
+        if (!spliceOnError(ERR_PROTOCOL_UNKNOWN))
+            clientConnection->close();
         return;
     }
+    receivedFirstByte();
 
-    if (!bio->rBufData().isEmpty() > 0)
-        conn->receivedFirstByte();
-
-    if (bio->gotHello()) {
-        if (conn->serverBump()) {
-            Security::TlsDetails::Pointer const &details = bio->receivedHelloDetails();
-            if (!details->serverName.isEmpty()) {
-                conn->serverBump()->clientSni = details->serverName;
-                conn->resetSslCommonName(details->serverName.c_str());
-            }
+    if (serverBump()) {
+        Security::TlsDetails::Pointer const &details = tlsParser.details;
+        if (!details->serverName.isEmpty()) {
+            serverBump()->clientSni = details->serverName;
+            resetSslCommonName(details->serverName.c_str());
         }
-
-        debugs(83, 5, "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
-    auto 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.
-    // Fix timeout to request_start_timeout
-    typedef CommCbMemFunT<ConnStateData, CommTimeoutCbParams> TimeoutDialer;
-    AsyncCall::Pointer timeoutCall =  JobCallback(33, 5,
-                                      TimeoutDialer, this, ConnStateData::requestTimeout);
-    commSetConnTimeout(clientConnection, Config.Timeout.request_start_timeout, timeoutCall);
-    // Also reset receivedFirstByte_ flag to allow this timeout work in the case we have
-    // a bumbed "connect" request on non transparent port.
-    receivedFirstByte_ = false;
-
-    // Disable the client read handler until CachePeer selection is complete
+    // We should disable read/write handlers
     Comm::SetSelect(clientConnection->fd, COMM_SELECT_READ, NULL, NULL, 0);
-    Comm::SetSelect(clientConnection->fd, COMM_SELECT_READ, clientPeekAndSpliceSSL, this, 0);
-    switchedToHttps_ = true;
+    Comm::SetSelect(clientConnection->fd, COMM_SELECT_WRITE, NULL, NULL, 0);
 
-    auto ssl = fd_table[clientConnection->fd].ssl.get();
-    BIO *b = SSL_get_rbio(ssl);
-    Ssl::ClientBio *bio = static_cast<Ssl::ClientBio *>(b->ptr);
-    bio->hold(true);
+    startPeekAndSpliceDone();
 }
 
 void httpsSslBumpStep2AccessCheckDone(allow_t answer, void *data)
@@ -3252,32 +3238,38 @@ void
 ConnStateData::splice()
 {
     // normally we can splice here, because we just got client hello message
-    auto ssl = fd_table[clientConnection->fd].ssl.get();
 
-    //retrieve received TLS client information
-    clientConnection->tlsNegotiations()->fillWith(ssl);
+    if (auto ssl = fd_table[clientConnection->fd].ssl.get()) {
+        // We built
+        //retrieve received TLS client information
+        clientConnection->tlsNegotiations()->fillWith(ssl);
 
-    BIO *b = SSL_get_rbio(ssl);
-    Ssl::ClientBio *bio = static_cast<Ssl::ClientBio *>(b->ptr);
-    SBuf const &rbuf = bio->rBufData();
-    debugs(83,5, "Bio for  " << clientConnection << " read " << rbuf.length() << " helo bytes");
-    // Do splice:
-    fd_table[clientConnection->fd].read_method = &default_read_method;
-    fd_table[clientConnection->fd].write_method = &default_write_method;
+        // BIO *b = SSL_get_rbio(ssl);
+        // Ssl::ClientBio *bio = static_cast<Ssl::ClientBio *>(b->ptr);
+        // SBuf const &rbuf = bio->rBufData();
+        // // The following do not needed, inBuf and rbuf has the same content.
+        // inBuf.assign(rbuf);
+        // debugs(83,5, "Bio for  " << clientConnection << " read " << rbuf.length() << " helo bytes");
+
+        // Do splice:
+        fd_table[clientConnection->fd].read_method = &default_read_method;
+        fd_table[clientConnection->fd].write_method = &default_write_method;
+
+    } else {
+        clientConnection->tlsNegotiations()->fillWith(tlsParser.details);
+    }
 
     if (transparent()) {
         // set the current protocol to something sensible (was "HTTPS" for the bumping process)
         // we are sending a faked-up HTTP/1.1 message wrapper, so go with that.
         transferProtocol = Http::ProtocolVersion();
         // XXX: copy from MemBuf reallocates, not a regression since old code did too
-        fakeAConnectRequest("intercepted TLS spliced", rbuf);
+        fakeAConnectRequest("intercepted TLS spliced", inBuf);
     } else {
         // XXX: assuming that there was an HTTP/1.1 CONNECT to begin with...
 
         // reset the current protocol to HTTP/1.1 (was "HTTPS" for the bumping process)
         transferProtocol = Http::ProtocolVersion();
-        // inBuf still has the "CONNECT ..." request data, reset it to SSL hello message
-        inBuf.append(rbuf);
         Http::StreamPointer context = pipeline.front();
         ClientHttpRequest *http = context->http;
         tunnelStart(http);
@@ -3307,6 +3299,39 @@ ConnStateData::startPeekAndSpliceDone()
         return;
     }
 
+    // will call httpsPeeked() with certificate and connection, eventually
+    auto unConfiguredCTX = Ssl::createSSLContext(port->signingCert, port->signPkey, *port);
+    fd_table[clientConnection->fd].dynamicSslContext = unConfiguredCTX;
+
+    if (!httpsCreate(clientConnection, unConfiguredCTX))
+        return;
+
+    switchedToHttps_ = true;
+
+    auto ssl = fd_table[clientConnection->fd].ssl.get();
+    BIO *b = SSL_get_rbio(ssl);
+    Ssl::ClientBio *bio = static_cast<Ssl::ClientBio *>(b->ptr);
+    bio->setReadBufData(inBuf);
+    bio->parsedDetails(tlsParser.details);
+    bio->hold(true);
+
+    // Here squid should have all of the client hello message so the 
+    // Squid_SSL_accept should return 0;
+    // This block exist only to force openSSL parse client hello and detect
+    // ERR_SECURE_ACCEPT_FAIL error, which should be checked and splice if required.
+    int ret = 0;
+    if ((ret = Squid_SSL_accept(this, NULL)) < 0) {
+        debugs(83, 2, "SSL_accept failed.");
+        const err_type err = ERR_SECURE_ACCEPT_FAIL;
+        if (!spliceOnError(err))
+            clientConnection->close();
+        return;
+    }
+
+    // Do we need to reset inBuf here?
+    // inBuf.clear();
+
+    debugs(83, 5, "Peek and splice at step2 done. Start forwarding the request!!! ");
     FwdState::Start(clientConnection, sslServerBump->entry, sslServerBump->request.getRaw(), http ? http->al : NULL);
 }
 
index fc7a7a0898dd9cb5371186e86379e99127a6b89e..d34f6b9ceb4b88de9618a4da06983f20abe5782e 100644 (file)
@@ -24,6 +24,7 @@
 #include "auth/UserRequest.h"
 #endif
 #if USE_OPENSSL
+#include "security/Handshake.h"
 #include "ssl/support.h"
 #endif
 
@@ -254,7 +255,7 @@ public:
     bool serveDelayedError(Http::Stream *);
 
     Ssl::BumpMode sslBumpMode; ///< ssl_bump decision (Ssl::bumpEnd if n/a).
-
+    Security::HandshakeParser tlsParser;
 #else
     bool switchedToHttps() const { return false; }
 #endif
@@ -358,6 +359,7 @@ private:
     /// HTTPS server cert. fetching state for bump-ssl-server-first
     Ssl::ServerBump *sslServerBump;
     Ssl::CertSignAlgorithm signAlgorithm; ///< The signing algorithm to use
+    bool atTlsPeek;
 #endif
 
     /// the reason why we no longer write the response or nil
index 747d0bd9acca7e263620ed9c2698f187dbdf8286..afac430347aefe45a82b52e138c0c39891385d79 100644 (file)
@@ -398,15 +398,20 @@ Security::HandshakeParser::parseServerHelloHandshakeMessage(const SBuf &raw)
 {
     BinaryTokenizer tkHsk(raw);
     Must(details);
-    
-    details->tlsSupportedVersion = tkHsk.uint16("tlsSupportedVersion");
-    tkHsk.commit();
-    details->clientRandom = tkHsk.area(SQUID_TLS_RANDOM_SIZE, "Client Random");
-    tkHsk.commit();
-    P8String session(tkHsk, "Session ID");
-    details->sessionId = session.body;
-    P16String extensions(tkHsk, "Extensions List");
-    parseExtensions(extensions.body);
+#if 0  // Always retrieve details, enough fast operation
+    if (::Config.onoff.logTlsServerHelloDetails) {
+#endif
+        details->tlsSupportedVersion = tkHsk.uint16("tlsSupportedVersion");
+        tkHsk.commit();
+        details->clientRandom = tkHsk.area(SQUID_TLS_RANDOM_SIZE, "Client Random");
+        tkHsk.commit();
+        P8String session(tkHsk, "Session ID");
+        details->sessionId = session.body;
+        P16String extensions(tkHsk, "Extensions List");
+        parseExtensions(extensions.body);
+#if 0
+    }
+#endif
 }
 
 void
index d9906104704c09eb9cdff9db911e174936094916..65e5a8e16401351798cdd6ba914f1833085f13b9 100644 (file)
@@ -63,16 +63,8 @@ Security::NegotiationHistory::fillWith(Security::SessionPtr ssl)
 
     BIO *b = SSL_get_rbio(ssl);
     Ssl::Bio *bio = static_cast<Ssl::Bio *>(b->ptr);
-
-    if (::Config.onoff.logTlsServerHelloDetails) {
-        //PRobably move this if inside HandhakeParser
-        // if (Ssl::ServerBio *srvBio = dynamic_cast<Ssl::ServerBio *>(bio))
-        //    srvBio->extractHelloFeatures();
-    }
-
-    const Security::TlsDetails::Pointer &details = bio->receivedHelloDetails();
-    helloVersion_ = details->tlsVersion;
-    supportedVersion_ = details->tlsSupportedVersion;
+    if (const Security::TlsDetails::Pointer &details = bio->receivedHelloDetails())
+        fillWith(details);
 
     debugs(83, 5, "SSL connection info on FD " << bio->fd() <<
            " SSL version " << version_ <<
@@ -80,6 +72,13 @@ Security::NegotiationHistory::fillWith(Security::SessionPtr ssl)
 #endif
 }
 
+void
+Security::NegotiationHistory::fillWith(Security::TlsDetails::Pointer const &details)
+{
+    helloVersion_ = details->tlsVersion;
+    supportedVersion_ = details->tlsSupportedVersion;
+}
+
 const char *
 Security::NegotiationHistory::cipherName() const
 {
index 18f4d313f0df011e26a372169b97d5755d5cd67f..44edaf5a1aa30906a6be3c97a44f8042453a24e1 100644 (file)
@@ -10,6 +10,7 @@
 #define SQUID_SRC_SECURITY_NEGOTIATIONHISTORY_H
 
 #include "security/Session.h"
+#include "security/Handshake.h"
 
 namespace Security {
 
@@ -18,6 +19,7 @@ class NegotiationHistory
 public:
     NegotiationHistory();
     void fillWith(Security::SessionPtr); ///< Extract negotiation information from TLS object
+    void fillWith(Security::TlsDetails::Pointer const &details); ///< Extract negotiation information from parser TlsDetails  object
     const char *cipherName() const; ///< The name of negotiated cipher
     /// String representation of TLS negotiated version
     const char *negotiatedVersion() const {return printTlsVersion(version_);}
index 89ec1a1dcced56aae51d516d6992b675d88456be..9b2fe56591dd5e3cecaad34ae48f5e3a7869392d 100644 (file)
@@ -146,19 +146,13 @@ Ssl::PeekingPeerConnector::initializeSsl()
         // or terminate client and server connections
         assert(clientConn != NULL);
         SBuf *hostName = NULL;
-        Ssl::ClientBio *cltBio = NULL;
 
         //Enable Status_request tls extension, required to bump some clients
         SSL_set_tlsext_status_type(ssl, TLSEXT_STATUSTYPE_ocsp);
 
-        // In server-first bumping mode, clientSsl is NULL.
-        if (auto clientSsl = fd_table[clientConn->fd].ssl.get()) {
-            BIO *b = SSL_get_rbio(clientSsl);
-            cltBio = static_cast<Ssl::ClientBio *>(b->ptr);
-            const Security::TlsDetails::Pointer &details = cltBio->receivedHelloDetails();
-            if (details != NULL && !details->serverName.isEmpty())
-                hostName = new SBuf(details->serverName);
-        }
+        const Security::TlsDetails::Pointer details = csd->tlsParser.details;
+        if (details != NULL && !details->serverName.isEmpty())
+            hostName = new SBuf(details->serverName);
 
         if (!hostName) {
             // While we are peeking at the certificate, we may not know the server
@@ -174,8 +168,12 @@ Ssl::PeekingPeerConnector::initializeSsl()
 
         Must(!csd->serverBump() || csd->serverBump()->step <= Ssl::bumpStep2);
         if (csd->sslBumpMode == Ssl::bumpPeek || csd->sslBumpMode == Ssl::bumpStare) {
-            assert(cltBio);
-            const Security::TlsDetails::Pointer &details = cltBio->receivedHelloDetails();
+            auto clientSsl = fd_table[clientConn->fd].ssl.get();
+            Must(clientSsl);
+            BIO *bc = SSL_get_rbio(clientSsl);
+            Ssl::ClientBio *cltBio = static_cast<Ssl::ClientBio *>(bc->ptr);
+            Must(cltBio);
+            //const Security::TlsDetails::Pointer &details = csd->tlsParser.details;
             if (details->tlsVersion != -1) {
                 applyTlsDetailsToSSL(ssl, details, csd->sslBumpMode);
                 // Should we allow it for all protocols?
index ef242d0cfa4307c94927613132d3eeaa7b141e16..506f0626415ab7847c38e84b8bad576de16e5085 100644 (file)
@@ -195,32 +195,19 @@ Ssl::ClientBio::write(const char *buf, int size, BIO *table)
 int
 Ssl::ClientBio::read(char *buf, int size, BIO *table)
 {
-    if (!parser_.parseDone) {
-        int bytes = readAndBuffer(table, "TLS client Hello");
-        if (bytes <= 0)
-            return bytes;
-        if (!parser_.parseClientHello(rbuf)) {
-            if (!parser_.parseError) 
-                BIO_set_retry_read(table);
-            return -1;
-        }
-    }
-
     if (holdRead_) {
         debugs(83, 7, "Hold flag is set, retry latter. (Hold " << size << "bytes)");
         BIO_set_retry_read(table);
         return -1;
     }
 
-    if (parser_.parseDone) {
-        if (!rbuf.isEmpty()) {
-            int bytes = (size <= (int)rbuf.length() ? size : rbuf.length());
-            memcpy(buf, rbuf.rawContent(), bytes);
-            rbuf.consume(bytes);
-            return bytes;
-        } else
-            return Ssl::Bio::read(buf, size, table);
-    }
+    if (!rbuf.isEmpty()) {
+        int bytes = (size <= (int)rbuf.length() ? size : rbuf.length());
+        memcpy(buf, rbuf.rawContent(), bytes);
+        rbuf.consume(bytes);
+        return bytes;
+    } else
+        return Ssl::Bio::read(buf, size, table);
 
     return -1;
 }
index 8182f0e950888351a12def51ea7b39171ad38eb0..b94cd7938663cf0162d63ecdc0328b0f1a00979f 100644 (file)
@@ -52,6 +52,9 @@ public:
     /// appears, or an error occurs. See SSL_set_info_callback().
     virtual void stateChanged(const SSL *ssl, int where, int ret);
 
+    /// Return the TLS Details advertised by TLS server.
+    virtual const Security::TlsDetails::Pointer &receivedHelloDetails() const = 0;
+
     /// 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, Type type);
@@ -61,16 +64,10 @@ public:
     /// Reads data from socket and record them to a buffer
     int readAndBuffer(BIO *table, const char *description);
 
-    /// Return the TLS Details requested/advirised by TLS client or server.
-    const Security::TlsDetails::Pointer &receivedHelloDetails() const {return parser_.details;}
-
     const SBuf &rBufData() {return rbuf;}
 protected:
     const int fd_; ///< the SSL socket we are reading and writing
     SBuf rbuf;  ///< Used to buffer input data.
-    /// The features retrieved from client or Server TLS hello message
-    //Security::TlsDetails::Pointer receivedHelloDetails_;
-    Security::HandshakeParser parser_; ///< The SSL messages parser.
 };
 
 /// BIO node to handle socket IO for squid client side
@@ -92,18 +89,21 @@ public:
     /// 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() const { return (parser_.parseDone && !parser_.parseError); }
+    //bool gotHello() const { return (parser_.parseDone && !parser_.parseError); }
     /// Prevents or allow writting on socket.
     void hold(bool h) {holdRead_ = holdWrite_ = h;}
-    /// True if client does not looks like an SSL client
-    bool noSslClient() {return parser_.parseError;}
+    void setReadBufData(SBuf &data) {rbuf = data;}
+    const Security::TlsDetails::Pointer &receivedHelloDetails() const {return details;}
+    void parsedDetails(Security::TlsDetails::Pointer &someDetails) {details = someDetails;}
 private:
     /// True if the SSL state corresponds to a hello message
     bool isClientHello(int state);
     bool holdRead_; ///< The read hold state of the bio.
     bool holdWrite_;  ///< The write hold state of the bio.
     int helloSize; ///< The SSL hello message sent by client size
-    //bool wrongProtocol; ///< true if client SSL hello parsing failed
+
+    /// SSL client features extracted from ClientHello message or SSL object
+    Security::TlsDetails::Pointer details;
 };
 
 /// BIO node to handle socket IO for squid server side
@@ -165,6 +165,8 @@ public:
     void mode(Ssl::BumpMode m) {bumpMode_ = m;}
     Ssl::BumpMode bumpMode() {return bumpMode_;} ///< return the bumping mode
 
+    /// Return the TLS Details advertised by TLS server.
+    const Security::TlsDetails::Pointer &receivedHelloDetails() const {return parser_.details;}
     /// Return true if the Server hello message received
     bool gotHello() const { return (parser_.parseDone && !parser_.parseError); }
 
@@ -190,6 +192,7 @@ private:
 
     ///< The size of data stored in rbuf which passed to the openSSL
     size_t rbufConsumePos;
+    Security::HandshakeParser parser_; ///< The SSL messages parser.
 };
 
 } // namespace Ssl