This patch adds the following formatting codes:

  %ssl::>negotiated_version
  The SSL version of the client-to-Squid connection.

  %ssl::<negotiated_version
  The SSL version of the Squid-to-server connection.

  %ssl::>received_hello_version
  The SSL version of the Hello message received from SSL client

  %ssl::<received_hello_version
  The SSL version of the Hello message received from SSL server.

  %ssl::>received_supported_version
  The maximum SSL version supported by the the SSL client.

  %ssl::<received_supported_version
  The maximum SSL version supported by the the SSL server.

  %ssl::>cipher
  The negotiated cipher of the client-to-Squid connection.

  %ssl::<cipher
  The negotiated cipher of the Squid-to-server connection.

These are useful for statistics collection, security reviews, and reviews prior to adjusting the list of the allowed SSL protocols and ciphers.

This is a Measurement Factory project
Log SSL Cryptography Parameters

This patch adds the following formatting codes:
  %ssl::>negotiated_version  The SSL version of the client-to-Squid connection.
  %ssl::<negotiated_version  The SSL version of the Squid-to-server connection.
  %ssl::>received_hello_version The SSL version of the Hello message received
                                from SSL client
  %ssl::<received_hello_version The SSL version of the Hello message received
                                from SSL server.
  %ssl::>received_supported_version The maximum SSL version supported by the
                                    the SSL client.
  %ssl::<received_supported_version The maximum SSL version supported by the
                                    the SSL server.
  %ssl::>cipher   The negotiated cipher of the client-to-Squid connection.
  %ssl::<cipher   The negotiated cipher of the Squid-to-server connection.

These are useful for statistics collection, security reviews, and reviews
prior to adjusting the list of the allowed SSL protocols and ciphers.

This is a Measurement Factory project

=== modified file 'src/SquidConfig.h'
--- src/SquidConfig.h	2015-12-02 19:45:15 +0000
+++ src/SquidConfig.h	2015-12-10 17:08:38 +0000
@@ -318,40 +318,43 @@
         int emailErrData;
         int httpd_suppress_version_string;
         int global_internal_static;
         int collapsed_forwarding;
 
 #if FOLLOW_X_FORWARDED_FOR
         int acl_uses_indirect_client;
         int delay_pool_uses_indirect_client;
         int log_uses_indirect_client;
 #if LINUX_NETFILTER
         int tproxy_uses_indirect_client;
 #endif
 #endif /* FOLLOW_X_FORWARDED_FOR */
 
         int WIN32_IpAddrChangeMonitor;
         int memory_cache_first;
         int memory_cache_disk;
         int hostStrictVerify;
         int client_dst_passthru;
         int dns_mdns;
+#if USE_OPENSSL
+        int logTlsServerHelloDetails;
+#endif
     } onoff;
 
     int pipeline_max_prefetch;
 
     int forward_max_tries;
     int connect_retries;
 
     class ACL *aclList;
 
     struct {
         acl_access *http;
         acl_access *adapted_http;
         acl_access *icp;
         acl_access *miss;
         acl_access *NeverDirect;
         acl_access *AlwaysDirect;
         acl_access *ASlists;
         acl_access *noCache;
         acl_access *sendHit;
         acl_access *storeMiss;

=== modified file 'src/cf.data.pre'
--- src/cf.data.pre	2015-12-02 19:45:15 +0000
+++ src/cf.data.pre	2015-12-10 17:12:48 +0000
@@ -4182,40 +4182,64 @@
 				SSL certificate or a dash ('-') if Squid has
 				received an invalid/malformed certificate or
 				no certificate at all. Consider encoding the
 				logged value because Subject often has spaces.
 
 		ssl::>cert_issuer
 				The Issuer field of the received client
 				SSL certificate or a dash ('-') if Squid has
 				received an invalid/malformed certificate or
 				no certificate at all. Consider encoding the
 				logged value because Issuer often has spaces.
 
 		ssl::<cert_errors
 				The list of certificate validation errors
 				detected by Squid (including OpenSSL and
 				certificate validation helper components). The
 				errors are listed in the discovery order. By
 				default, the error codes are separated by ':'.
 				Accepts an optional separator argument.
 
+		%ssl::>negotiated_version The negotiated SSL version of the
+				client-to-Squid connection.
+
+		%ssl::<negotiated_version The negotiated SSL version of the
+				Squid-to-server connection.
+
+		%ssl::>received_hello_version The SSL version of the Hello
+				message received from SSL client.
+
+		%ssl::<received_hello_version The SSL version of the Hello
+				message received from SSL server.
+
+		%ssl::>received_supported_version The maximum SSL version
+				supported by the SSL client.
+
+		%ssl::<received_supported_version The maximum SSL version
+				supported by the SSL server.
+
+		%ssl::>negotiated_cipher The negotiated cipher of the
+				client-to-Squid connection.
+
+		%ssl::<negotiated_cipher The negotiated cipher of the
+				Squid-to-server connection.
+
 	If ICAP is enabled, the following code becomes available (as
 	well as ICAP log codes documented with the icap_log option):
 
 		icap::tt        Total ICAP processing time for the HTTP
 				transaction. The timer ticks when ICAP
 				ACLs are checked and when ICAP
 				transaction is in progress.
 
 	If adaptation is enabled the following codes become available:
 
 		adapt::<last_h	The header of the last ICAP response or
 				meta-information from the last eCAP
 				transaction related to the HTTP transaction.
 				Like <h, accepts an optional header name
 				argument.
 
 		adapt::sum_trs Summed adaptation transaction response
 				times recorded as a comma-separated list in
 				the order of transaction start time. Each time
 				value is recorded as an integer number,

=== modified file 'src/client_side.cc'
--- src/client_side.cc	2015-12-04 13:35:13 +0000
+++ src/client_side.cc	2015-12-10 17:20:59 +0000
@@ -3391,42 +3391,42 @@
             * Because there are two possible usable cast, if you get an error here, try the other
             * commented line. */
 
             PEM_ASN1_write((int(*)())i2d_SSL_SESSION, PEM_STRING_SSL_SESSION, debug_log, (char *)SSL_get_session(ssl), NULL,NULL,0,NULL,NULL);
             /* PEM_ASN1_write((int(*)(...))i2d_SSL_SESSION, PEM_STRING_SSL_SESSION, debug_log, (char *)SSL_get_session(ssl), NULL,NULL,0,NULL,NULL); */
 
 #else
 
             debugs(83, 4, "With " OPENSSL_VERSION_TEXT ", session details are available only defining ALLOW_ALWAYS_SSL_SESSION_DETAIL=1 in the source." );
 
 #endif
             /* Note: This does not automatically fflush the log file.. */
         }
 
         debugs(83, 2, "clientNegotiateSSL: New session " <<
                SSL_get_session(ssl) << " on FD " << fd << " (" <<
                fd_table[fd].ipaddr << ":" << (int)fd_table[fd].remote_port <<
                ")");
     }
 
-    debugs(83, 3, "clientNegotiateSSL: FD " << fd << " negotiated cipher " <<
-           SSL_get_cipher(ssl));
+    // Connection established. Retrieve SSL connection parameters for logging.
+    conn->clientConnection->tlsNegotiations()->fillWith(ssl);
 
     client_cert = SSL_get_peer_certificate(ssl);
 
     if (client_cert != NULL) {
         debugs(83, 3, "clientNegotiateSSL: FD " << fd <<
                " client certificate: subject: " <<
                X509_NAME_oneline(X509_get_subject_name(client_cert), 0, 0));
 
         debugs(83, 3, "clientNegotiateSSL: FD " << fd <<
                " client certificate: issuer: " <<
                X509_NAME_oneline(X509_get_issuer_name(client_cert), 0, 0));
 
         X509_free(client_cert);
     } else {
         debugs(83, 5, "clientNegotiateSSL: FD " << fd <<
                " has no certificate.");
     }
 
 #if defined(TLSEXT_NAMETYPE_host_name)
     if (!conn->serverBump()) {
@@ -3874,41 +3874,41 @@
 
     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();
         return;
     }
 
     if (bio->rBufData().contentSize() > 0)
         conn->receivedFirstByte();
 
     if (bio->gotHello()) {
         if (conn->serverBump()) {
-            Ssl::Bio::sslFeatures const &features = bio->getFeatures();
+            Ssl::Bio::sslFeatures const &features = bio->receivedHelloFeatures();
             if (!features.serverName.isEmpty()) {
                 conn->serverBump()->clientSni = features.serverName;
                 conn->resetSslCommonName(features.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;
 
@@ -3951,40 +3951,44 @@
         bumpAction = (Ssl::BumpMode)answer.kind;
     } else
         bumpAction = Ssl::bumpSplice;
 
     connState->serverBump()->act.step2 = bumpAction;
     connState->sslBumpMode = bumpAction;
 
     if (bumpAction == Ssl::bumpTerminate) {
         connState->clientConnection->close();
     } else if (bumpAction != Ssl::bumpSplice) {
         connState->startPeekAndSpliceDone();
     } else
         connState->splice();
 }
 
 void
 ConnStateData::splice()
 {
     //Normally we can splice here, because we just got client hello message
     auto ssl = fd_table[clientConnection->fd].ssl;
+
+    //retrieve received SSL client information
+    clientConnection->tlsNegotiations()->fillWith(ssl);
+
     BIO *b = SSL_get_rbio(ssl);
     Ssl::ClientBio *bio = static_cast<Ssl::ClientBio *>(b->ptr);
     MemBuf const &rbuf = bio->rBufData();
     debugs(83,5, "Bio for  " << clientConnection << " read " << rbuf.contentSize() << " helo bytes");
     // Do splice:
     fd_table[clientConnection->fd].read_method = &default_read_method;
     fd_table[clientConnection->fd].write_method = &default_write_method;
 
     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
         SBuf temp;
         temp.append(rbuf.content(), rbuf.contentSize());
         fakeAConnectRequest("intercepted TLS spliced", temp);
     } 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)

=== modified file 'src/comm/Connection.cc'
--- src/comm/Connection.cc	2015-08-24 14:20:07 +0000
+++ src/comm/Connection.cc	2015-12-10 17:24:01 +0000
@@ -1,67 +1,74 @@
 /*
  * Copyright (C) 1996-2015 The Squid Software Foundation and contributors
  *
  * Squid software is distributed under GPLv2+ license and includes
  * contributions from numerous individuals and organizations.
  * Please see the COPYING and CONTRIBUTORS files for details.
  */
 
 #include "squid.h"
 #include "CachePeer.h"
 #include "cbdata.h"
 #include "comm.h"
 #include "comm/Connection.h"
 #include "fde.h"
 #include "neighbors.h"
 #include "SquidConfig.h"
 #include "SquidTime.h"
+#if USE_OPENSSL
+#include "ssl/support.h"
+#endif
 
 class CachePeer;
 bool
 Comm::IsConnOpen(const Comm::ConnectionPointer &conn)
 {
     return conn != NULL && conn->isOpen();
 }
 
 Comm::Connection::Connection() :
     peerType(HIER_NONE),
     fd(-1),
     tos(0),
     nfmark(0),
     flags(COMM_NONBLOCKING),
     peer_(nullptr),
     startTime_(squid_curtime)
+#if USE_OPENSSL
+    , tlsHistory(nullptr)
+#endif
 {
     *rfc931 = 0; // quick init the head. the rest does not matter.
 }
 
 static int64_t lost_conn = 0;
 Comm::Connection::~Connection()
 {
     if (fd >= 0) {
         debugs(5, 4, "BUG #3329: Orphan Comm::Connection: " << *this);
         debugs(5, 4, "NOTE: " << ++lost_conn << " Orphans since last started.");
         close();
     }
 
     cbdataReferenceDone(peer_);
+    delete tlsHistory;
 }
 
 Comm::ConnectionPointer
 Comm::Connection::copyDetails() const
 {
     ConnectionPointer c = new Comm::Connection;
 
     c->setAddrs(local, remote);
     c->peerType = peerType;
     c->tos = tos;
     c->nfmark = nfmark;
     c->flags = flags;
     c->startTime_ = startTime_;
 
     // ensure FD is not open in the new copy.
     c->fd = -1;
 
     // ensure we have a cbdata reference to peer_ not a straight ptr copy.
     c->peer_ = cbdataReference(getPeer());
 
@@ -102,20 +109,30 @@
     /* set to self. nothing to do. */
     if (getPeer() == p)
         return;
 
     cbdataReferenceDone(peer_);
     if (p) {
         peer_ = cbdataReference(p);
     }
 }
 
 time_t
 Comm::Connection::timeLeft(const time_t idleTimeout) const
 {
     if (!Config.Timeout.pconnLifetime)
         return idleTimeout;
 
     const time_t lifeTimeLeft = lifeTime() < Config.Timeout.pconnLifetime ? Config.Timeout.pconnLifetime - lifeTime() : 1;
     return min(lifeTimeLeft, idleTimeout);
 }
 
+#if USE_OPENSSL
+Ssl::NegotiationHistory *
+Comm::Connection::tlsNegotiations()
+{
+    if (!tlsHistory)
+        tlsHistory = new Ssl::NegotiationHistory;
+    return tlsHistory;
+}
+
+#endif

=== modified file 'src/comm/Connection.h'
--- src/comm/Connection.h	2015-08-31 06:17:22 +0000
+++ src/comm/Connection.h	2015-11-10 17:00:25 +0000
@@ -11,40 +11,47 @@
 #ifndef _SQUIDCONNECTIONDETAIL_H_
 #define _SQUIDCONNECTIONDETAIL_H_
 
 #include "comm/forward.h"
 #include "defines.h"
 #if USE_SQUID_EUI
 #include "eui/Eui48.h"
 #include "eui/Eui64.h"
 #endif
 #include "hier_code.h"
 #include "ip/Address.h"
 #include "ip/forward.h"
 #include "mem/forward.h"
 #include "SquidTime.h"
 
 #include <iosfwd>
 #include <ostream>
 
 class CachePeer;
 
+#if USE_OPENSSL
+namespace Ssl
+{
+class NegotiationHistory;
+};
+#endif
+
 namespace Comm
 {
 
 /* TODO: make these a struct of boolean flags members in the connection instead of a bitmap.
  * we can't do that until all non-comm code uses Commm::Connection objects to create FD
  * currently there is code still using comm_open() and comm_openex() synchronously!!
  */
 #define COMM_UNSET              0x00
 #define COMM_NONBLOCKING        0x01  // default flag.
 #define COMM_NOCLOEXEC          0x02
 #define COMM_REUSEADDR          0x04  // shared FD may be both accept()ing and read()ing
 #define COMM_DOBIND             0x08  // requires a bind()
 #define COMM_TRANSPARENT        0x10  // arrived via TPROXY
 #define COMM_INTERCEPTION       0x20  // arrived via NAT
 
 /**
  * Store data about the physical and logical attributes of a connection.
  *
  * Some link state can be infered from the data, however this is not an
  * object for state data. But a semantic equivalent for FD with easily
@@ -90,40 +97,46 @@
      * The caller is responsible for all CBDATA operations regarding the
      * used of the pointer returned.
      */
     CachePeer * getPeer() const;
 
     /** alter the stored CachePeer pointer.
      * Perform appropriate CBDATA operations for locking the CachePeer pointer
      */
     void setPeer(CachePeer * p);
 
     /** The time the connection started */
     time_t startTime() const {return startTime_;}
 
     /** The connection lifetime */
     time_t lifeTime() const {return squid_curtime - startTime_;}
 
     /** The time left for this connection*/
     time_t timeLeft(const time_t idleTimeout) const;
 
     void noteStart() {startTime_ = squid_curtime;}
+
+#if USE_OPENSSL
+    Ssl::NegotiationHistory *tlsNegotiations();
+    const Ssl::NegotiationHistory *hasTlsNegotiations() const {return tlsHistory;}
+#endif
+
 private:
     /** These objects may not be exactly duplicated. Use copyDetails() instead. */
     Connection(const Connection &c);
 
     /** These objects may not be exactly duplicated. Use copyDetails() instead. */
     Connection & operator =(const Connection &c);
 
 public:
     /** Address/Port for the Squid end of a TCP link. */
     Ip::Address local;
 
     /** Address for the Remote end of a TCP link. */
     Ip::Address remote;
 
     /** Hierarchy code for this connection link */
     hier_code peerType;
 
     /** Socket used by this connection. Negative if not open. */
     int fd;
 
@@ -132,40 +145,45 @@
 
     /** Netfilter MARK values currently sent on this connection */
     nfmark_t nfmark;
 
     /** COMM flags set on this connection */
     int flags;
 
     char rfc931[USER_IDENT_SZ];
 
 #if USE_SQUID_EUI
     Eui::Eui48 remoteEui48;
     Eui::Eui64 remoteEui64;
 #endif
 
 private:
     /** cache_peer data object (if any) */
     CachePeer *peer_;
 
     /** The time the connection object was created */
     time_t startTime_;
+
+#if USE_OPENSSL
+    /** SSL conenction details*/
+    Ssl::NegotiationHistory *tlsHistory;
+#endif
 };
 
 }; // namespace Comm
 
 // NP: Order and namespace here is very important.
 //     * The second define inlines the first.
 //     * Stream inheritance overloading is searched in the global scope first.
 
 inline std::ostream &
 operator << (std::ostream &os, const Comm::Connection &conn)
 {
     os << "local=" << conn.local << " remote=" << conn.remote;
     if (conn.fd >= 0)
         os << " FD " << conn.fd;
     if (conn.flags != COMM_UNSET)
         os << " flags=" << conn.flags;
 #if USE_IDENT
     if (*conn.rfc931)
         os << " IDENT::" << conn.rfc931;
 #endif

=== modified file 'src/format/ByteCode.h'
--- src/format/ByteCode.h	2015-10-09 09:23:22 +0000
+++ src/format/ByteCode.h	2015-11-10 16:30:58 +0000
@@ -201,40 +201,48 @@
 
     LFT_ICAP_REP_HEADER,
     LFT_ICAP_REP_HEADER_ELEM,
     LFT_ICAP_REP_ALL_HEADERS,
 
     LFT_ICAP_TR_RESPONSE_TIME,
     LFT_ICAP_IO_TIME,
     LFT_ICAP_OUTCOME,
     LFT_ICAP_STATUS_CODE,
 #endif
     LFT_CREDENTIALS,
 
 #if USE_OPENSSL
     LFT_SSL_BUMP_MODE,
     LFT_SSL_USER_CERT_SUBJECT,
     LFT_SSL_USER_CERT_ISSUER,
     LFT_SSL_CLIENT_SNI,
     LFT_SSL_SERVER_CERT_SUBJECT,
     LFT_SSL_SERVER_CERT_ISSUER,
     LFT_SSL_SERVER_CERT_ERRORS,
+    LFT_SSL_CLIENT_NEGOTIATED_VERSION,
+    LFT_SSL_SERVER_NEGOTIATED_VERSION,
+    LFT_SSL_CLIENT_NEGOTIATED_CIPHER,
+    LFT_SSL_SERVER_NEGOTIATED_CIPHER,
+    LFT_SSL_CLIENT_RECEIVED_HELLO_VERSION,
+    LFT_SSL_SERVER_RECEIVED_HELLO_VERSION,
+    LFT_SSL_CLIENT_SUPPORTED_VERSION,
+    LFT_SSL_SERVER_SUPPORTED_VERSION,
 #endif
 
     LFT_NOTE,
     LFT_PERCENT,            /* special string cases for escaped chars */
 
     // TODO assign better bytecode names and Token strings for these
 #if USE_OPENSSL
     LFT_EXT_ACL_USER_CERT_RAW,
     LFT_EXT_ACL_USER_CERTCHAIN_RAW,
     LFT_EXT_ACL_USER_CERT,
     LFT_EXT_ACL_USER_CA_CERT,
 #endif
     LFT_EXT_ACL_CLIENT_EUI48,
     LFT_EXT_ACL_CLIENT_EUI64,
     LFT_EXT_ACL_NAME,
     LFT_EXT_ACL_DATA
 
 } ByteCode_t;
 
 /// Quoting style for a format output.

=== modified file 'src/format/Format.cc'
--- src/format/Format.cc	2015-10-11 18:12:00 +0000
+++ src/format/Format.cc	2015-12-10 17:25:16 +0000
@@ -1242,40 +1242,80 @@
                 if (Ssl::ServerBump * srvBump = al->request->clientConnectionManager->serverBump()) {
                     const char *separator = fmt->data.string ? fmt->data.string : ":";
                     for (Ssl::CertErrors *sslError = srvBump->sslErrors; sslError != NULL;  sslError = sslError->next) {
                         if (sb.size())
                             sb.append(separator);
                         if (const char *errorName = Ssl::GetErrorName(sslError->element.code))
                             sb.append(errorName);
                         else
                             sb.append(sslErrorName(sslError->element.code, tmp, sizeof(tmp)));
                     }
                     if (sb.size())
                         out = sb.termedBuf();
                 }
             }
             break;
 
         case LFT_SSL_SERVER_CERT_ISSUER:
         case LFT_SSL_SERVER_CERT_SUBJECT:
             // Not implemented
             break;
+
+        case LFT_SSL_CLIENT_NEGOTIATED_VERSION:
+            if (al->tcpClient != nullptr && al->tcpClient->hasTlsNegotiations())
+                out = al->tcpClient->hasTlsNegotiations()->negotiatedVersion();
+            break;
+
+        case LFT_SSL_SERVER_NEGOTIATED_VERSION:
+            if (al->hier.tcpServer != nullptr && al->hier.tcpServer->hasTlsNegotiations())
+                out = al->hier.tcpServer->hasTlsNegotiations()->negotiatedVersion();
+            break;
+
+        case LFT_SSL_CLIENT_RECEIVED_HELLO_VERSION:
+            if (al->tcpClient != nullptr && al->tcpClient->hasTlsNegotiations())
+                out = al->tcpClient->hasTlsNegotiations()->helloVersion();
+            break;
+
+        case LFT_SSL_SERVER_RECEIVED_HELLO_VERSION:
+            if (al->hier.tcpServer != nullptr && al->hier.tcpServer->hasTlsNegotiations())
+                out = al->hier.tcpServer->hasTlsNegotiations()->helloVersion();
+            break;
+
+        case LFT_SSL_CLIENT_SUPPORTED_VERSION:
+            if (al->tcpClient != nullptr && al->tcpClient->hasTlsNegotiations())
+                out = al->tcpClient->hasTlsNegotiations()->supportedVersion();
+            break;
+
+        case LFT_SSL_SERVER_SUPPORTED_VERSION:
+            if (al->hier.tcpServer != nullptr && al->hier.tcpServer->hasTlsNegotiations())
+                out = al->hier.tcpServer->hasTlsNegotiations()->supportedVersion();
+            break;
+
+        case LFT_SSL_CLIENT_NEGOTIATED_CIPHER:
+            if (al->tcpClient != nullptr && al->tcpClient->hasTlsNegotiations())
+                out = al->tcpClient->hasTlsNegotiations()->cipherName();
+            break;
+
+        case LFT_SSL_SERVER_NEGOTIATED_CIPHER:
+            if (al->hier.tcpServer != nullptr && al->hier.tcpServer->hasTlsNegotiations())
+                out = al->hier.tcpServer->hasTlsNegotiations()->cipherName();
+            break;
 #endif
 
         case LFT_REQUEST_URLGROUP_OLD_2X:
             assert(LFT_REQUEST_URLGROUP_OLD_2X == 0); // should never happen.
 
         case LFT_NOTE:
             tmp[0] = fmt->data.header.separator;
             tmp[1] = '\0';
             if (fmt->data.header.header && *fmt->data.header.header) {
                 const char *separator = tmp;
 #if USE_ADAPTATION
                 Adaptation::History::Pointer ah = al->request ? al->request->adaptHistory() : Adaptation::History::Pointer();
                 if (ah != NULL && ah->metaHeaders != NULL) {
                     if (const char *meta = ah->metaHeaders->find(fmt->data.header.header, separator))
                         sb.append(meta);
                 }
 #endif
                 if (al->notes != NULL) {
                     if (const char *note = al->notes->find(fmt->data.header.header, separator)) {
                         if (sb.size())

=== modified file 'src/format/Token.cc'
--- src/format/Token.cc	2015-10-09 09:23:22 +0000
+++ src/format/Token.cc	2015-11-10 17:05:04 +0000
@@ -202,40 +202,48 @@
 
     TokenTableEntry("tr",  LFT_ICAP_TR_RESPONSE_TIME),
     TokenTableEntry("tio", LFT_ICAP_IO_TIME),
     TokenTableEntry("to",  LFT_ICAP_OUTCOME),
     TokenTableEntry("Hs",  LFT_ICAP_STATUS_CODE),
 
     TokenTableEntry(NULL, LFT_NONE)           /* this must be last */
 };
 #endif
 
 #if USE_OPENSSL
 // SSL (ssl::) tokens
 static TokenTableEntry TokenTableSsl[] = {
     TokenTableEntry("bump_mode", LFT_SSL_BUMP_MODE),
     TokenTableEntry(">cert_subject", LFT_SSL_USER_CERT_SUBJECT),
     TokenTableEntry(">cert_issuer", LFT_SSL_USER_CERT_ISSUER),
     TokenTableEntry(">sni", LFT_SSL_CLIENT_SNI),
     /*TokenTableEntry("<cert_subject", LFT_SSL_SERVER_CERT_SUBJECT), */
     /*TokenTableEntry("<cert_issuer", LFT_SSL_SERVER_CERT_ISSUER), */
     TokenTableEntry("<cert_errors", LFT_SSL_SERVER_CERT_ERRORS),
+    TokenTableEntry(">negotiated_version", LFT_SSL_CLIENT_NEGOTIATED_VERSION),
+    TokenTableEntry("<negotiated_version", LFT_SSL_SERVER_NEGOTIATED_VERSION),
+    TokenTableEntry(">negotiated_cipher", LFT_SSL_CLIENT_NEGOTIATED_CIPHER),
+    TokenTableEntry("<negotiated_cipher", LFT_SSL_SERVER_NEGOTIATED_CIPHER),
+    TokenTableEntry(">received_hello_version", LFT_SSL_CLIENT_RECEIVED_HELLO_VERSION),
+    TokenTableEntry("<received_hello_version", LFT_SSL_SERVER_RECEIVED_HELLO_VERSION),
+    TokenTableEntry(">received_supported_version", LFT_SSL_CLIENT_SUPPORTED_VERSION),
+    TokenTableEntry("<received_supported_version", LFT_SSL_SERVER_SUPPORTED_VERSION),
     TokenTableEntry(NULL, LFT_NONE)
 };
 #endif
 } // namespace Format
 
 /// Register all components custom format tokens
 void
 Format::Token::Init()
 {
     // TODO standard log tokens
 
 #if USE_ADAPTATION
     TheConfig.registerTokens(String("adapt"),::Format::TokenTableAdapt);
 #endif
 #if ICAP_CLIENT
     TheConfig.registerTokens(String("icap"),::Format::TokenTableIcap);
 #endif
 #if USE_OPENSSL
     TheConfig.registerTokens(String("ssl"),::Format::TokenTableSsl);
 #endif
@@ -582,40 +590,48 @@
         debugs(46, DBG_PARSE_NOTE(DBG_IMPORTANT), "WARNING: The \"oa\" formatting code is deprecated. Use the \"<la\" instead.");
         type = LFT_SERVER_LOCAL_IP;
         break;
 
     case LFT_REQUEST_URLPATH_OLD_31:
         debugs(46, DBG_PARSE_NOTE(DBG_IMPORTANT), "WARNING: The \"rp\" formatting code is deprecated. Use the \">rp\" instead.");
         type = LFT_CLIENT_REQ_URLPATH;
         break;
 
     case LFT_REQUEST_VERSION_OLD_2X:
         debugs(46, DBG_PARSE_NOTE(DBG_IMPORTANT), "WARNING: The \">v\" formatting code is deprecated. Use the \">rv\" instead.");
         type = LFT_REQUEST_VERSION;
         break;
 
 #if !USE_SQUID_EUI
     case LFT_CLIENT_EUI:
         debugs(46, DBG_CRITICAL, "WARNING: The \">eui\" formatting code requires EUI features which are disabled in this Squid.");
         break;
 #endif
 
+#if USE_OPENSSL
+    case LFT_SSL_SERVER_NEGOTIATED_VERSION:
+    case LFT_SSL_SERVER_RECEIVED_HELLO_VERSION:
+    case LFT_SSL_SERVER_SUPPORTED_VERSION:
+        Config.onoff.logTlsServerHelloDetails = 1;
+        break;
+#endif
+
     case LFT_REQUEST_URLGROUP_OLD_2X:
         debugs(46, DBG_PARSE_NOTE(DBG_IMPORTANT), "WARNING: The \"rG\" formatting code is deprecated. Use \"note{urlgroup}\" instead.");
         type = LFT_NOTE;
         data.header.header = xstrdup("urlgroup");
         break;
 
     default:
         break;
     }
 
     return (cur - def);
 }
 
 Format::Token::Token() : type(LFT_NONE),
     label(NULL),
     widthMin(-1),
     widthMax(-1),
     quote(LOG_QUOTE_NONE),
     left(false),
     space(false),

=== modified file 'src/ssl/PeerConnector.cc'
--- src/ssl/PeerConnector.cc	2015-12-07 09:53:57 +0000
+++ src/ssl/PeerConnector.cc	2015-12-10 17:20:50 +0000
@@ -663,61 +663,61 @@
 {
     SSL *ssl = Ssl::PeerConnector::initializeSsl();
     if (!ssl)
         return NULL;
 
     if (ConnStateData *csd = request->clientConnectionManager.valid()) {
 
         // client connection is required in the case we need to splice
         // 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 (SSL *clientSsl = fd_table[clientConn->fd].ssl) {
             BIO *b = SSL_get_rbio(clientSsl);
             cltBio = static_cast<Ssl::ClientBio *>(b->ptr);
-            const Ssl::Bio::sslFeatures &features = cltBio->getFeatures();
+            const Ssl::Bio::sslFeatures &features = cltBio->receivedHelloFeatures();
             if (!features.serverName.isEmpty())
                 hostName = new SBuf(features.serverName);
         }
 
         if (!hostName) {
             // While we are peeking at the certificate, we may not know the server
             // name that the client will request (after interception or CONNECT)
             // unless it was the CONNECT request with a user-typed address.
             const bool isConnectRequest = !csd->port->flags.isIntercepted();
             if (!request->flags.sslPeek || isConnectRequest)
                 hostName = new SBuf(request->url.host());
         }
 
         if (hostName)
             SSL_set_ex_data(ssl, ssl_ex_index_server, (void*)hostName);
 
         Must(!csd->serverBump() || csd->serverBump()->step <= Ssl::bumpStep2);
         if (csd->sslBumpMode == Ssl::bumpPeek || csd->sslBumpMode == Ssl::bumpStare) {
             assert(cltBio);
-            const Ssl::Bio::sslFeatures &features = cltBio->getFeatures();
+            const Ssl::Bio::sslFeatures &features = cltBio->receivedHelloFeatures();
             if (features.sslVersion != -1) {
                 features.applyToSSL(ssl, csd->sslBumpMode);
                 // Should we allow it for all protocols?
                 if (features.sslVersion >= 3) {
                     BIO *b = SSL_get_rbio(ssl);
                     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);
                 }
             }
         } else {
             // Set client SSL options
             SSL_set_options(ssl, ::Security::ProxyOutgoingConfig.parsedOptions);
 
             // Use SNI TLS extension only when we connect directly
             // to the origin server and we know the server host name.
             const char *sniServer = NULL;
             const bool redirected = request->flags.redirected && ::Config.onoff.redir_rewrites_host;
@@ -768,44 +768,50 @@
                 handleServerCertificate();
             }
         }
 
         if (error) {
             // For intercepted connections, set the host name to the server
             // certificate CN. Otherwise, we just hope that CONNECT is using
             // a user-entered address (a host name or a user-entered IP).
             const bool isConnectRequest = !request->clientConnectionManager->port->flags.isIntercepted();
             if (request->flags.sslPeek && !isConnectRequest) {
                 if (X509 *srvX509 = serverBump->serverCert.get()) {
                     if (const char *name = Ssl::CommonHostName(srvX509)) {
                         request->url.host(name);
                         debugs(83, 3, "reset request host: " << name);
                     }
                 }
             }
         }
     }
 
+    // retrieve SSL server information if any
+    serverConnection()->tlsNegotiations()->fillWith(ssl);
     if (!error) {
         serverCertificateVerified();
-        if (splice)
+        if (splice) {
+            //retrieved received SSL client informations
+            SSL *clientSsl = fd_table[clientConn->fd].ssl;
+            clientConn->tlsNegotiations()->fillWith(clientSsl);
             switchToTunnel(request.getRaw(), clientConn, serverConn);
+        }
     }
 }
 
 void
 Ssl::PeekingPeerConnector::noteWantWrite()
 {
     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->bumpMode() == Ssl::bumpPeek || srvBio->bumpMode() == Ssl::bumpStare) && srvBio->holdWrite()) {
         debugs(81, DBG_IMPORTANT, "hold write on SSL connection on FD " << fd);
         checkForPeekAndSplice();
         return;
     }
 
     Ssl::PeerConnector::noteWantWrite();
 }
 

=== modified file 'src/ssl/bio.cc'
--- src/ssl/bio.cc	2015-10-11 05:30:33 +0000
+++ src/ssl/bio.cc	2015-11-10 16:25:04 +0000
@@ -208,63 +208,63 @@
     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 (helloState < atHelloReceived) {
         int bytes = readAndBuffer(buf, size, table, "TLS client Hello");
         if (bytes <= 0)
             return bytes;
     }
 
     if (helloState == atHelloNone) {
-        helloSize = features.parseMsgHead(rbuf);
+        helloSize = receivedHelloFeatures_.parseMsgHead(rbuf);
         if (helloSize == 0) {
             // Not enough bytes to get hello message size
             BIO_set_retry_read(table);
             return -1;
         } else if (helloSize < 0) {
             wrongProtocol = true;
             return -1;
         }
 
         helloState = atHelloStarted; //Next state
     }
 
     if (helloState == atHelloStarted) {
         const unsigned char *head = (const unsigned char *)rbuf.content();
         const char *s = objToString(head, rbuf.contentSize());
         debugs(83, 7, "SSL Header: " << s);
 
         if (helloSize > rbuf.contentSize()) {
             BIO_set_retry_read(table);
             return -1;
         }
-        features.get(rbuf);
+        receivedHelloFeatures_.get(rbuf);
         helloState = atHelloReceived;
     }
 
     if (holdRead_) {
         debugs(83, 7, "Hold flag is set, retry latter. (Hold " << size << "bytes)");
         BIO_set_retry_read(table);
         return -1;
     }
 
     if (helloState == atHelloReceived) {
         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;
@@ -485,53 +485,59 @@
         }
 
         // Sending hello message complete. Do not send more data for now...
         holdWrite_ = true;
 
         // spoof openSSL that we write what it ask us to write
         return size;
     } else
         return Ssl::Bio::write(buf, size, table);
 }
 
 void
 Ssl::ServerBio::flush(BIO *table)
 {
     if (!helloMsg.isEmpty()) {
         int ret = Ssl::Bio::write(helloMsg.rawContent(), helloMsg.length(), table);
         helloMsg.consume(ret);
     }
 }
 
+void 
+Ssl::ServerBio::extractHelloFeatures()
+{
+    if (!receivedHelloFeatures_.initialized_)
+        receivedHelloFeatures_.get(rbuf, false);
+}
+
 bool
 Ssl::ServerBio::resumingSession()
 {
-    if (!serverFeatures.initialized_)
-        serverFeatures.get(rbuf, false);
+    extractHelloFeatures();
 
-    if (!clientFeatures.sessionId.isEmpty() && !serverFeatures.sessionId.isEmpty())
-        return clientFeatures.sessionId == serverFeatures.sessionId;
+    if (!clientFeatures.sessionId.isEmpty() && !receivedHelloFeatures_.sessionId.isEmpty())
+        return clientFeatures.sessionId == receivedHelloFeatures_.sessionId;
 
     // is this a session resuming attempt using TLS tickets?
     if (clientFeatures.hasTlsTicket &&
-            serverFeatures.tlsTicketsExtension &&
-            serverFeatures.hasCcsOrNst)
+            receivedHelloFeatures_.tlsTicketsExtension &&
+            receivedHelloFeatures_.hasCcsOrNst)
         return true;
 
     return false;
 }
 
 /// initializes BIO table after allocation
 static int
 squid_bio_create(BIO *bi)
 {
     bi->init = 0; // set when we store Bio object and socket fd (BIO_C_SET_FD)
     bi->num = 0;
     bi->ptr = NULL;
     bi->flags = 0;
     return 1;
 }
 
 /// cleans BIO table before deallocation
 static int
 squid_bio_destroy(BIO *table)
 {
@@ -622,41 +628,41 @@
         case BIO_CTRL_WPENDING:
     */
     default:
         return 0;
 
     }
 
     return 0; /* NOTREACHED */
 }
 
 /// wrapper for Bio::stateChanged()
 static void
 squid_ssl_info(const SSL *ssl, int where, int ret)
 {
     if (BIO *table = SSL_get_rbio(ssl)) {
         if (Ssl::Bio *bio = static_cast<Ssl::Bio*>(table->ptr))
             bio->stateChanged(ssl, where, 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(): sslHelloVersion(-1), sslVersion(-1), compressMethod(-1), helloMsgSize(0), unknownCiphers(false), doHeartBeats(true), tlsTicketsExtension(false), hasTlsTicket(false), tlsStatusRequest(false), hasCcsOrNst(false), initialized_(false)
 {
     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;
@@ -753,49 +759,49 @@
 }
 
 int
 Ssl::Bio::sslFeatures::parseMsgHead(const MemBuf &buf)
 {
     const unsigned char *head = (const unsigned char *)buf.content();
     const char *s = objToString(head, buf.contentSize());
     debugs(83, 7, "SSL Header: " << s);
     if (buf.contentSize() < 5)
         return 0;
 
     if (helloMsgSize > 0)
         return helloMsgSize;
 
     // Check for SSLPlaintext/TLSPlaintext record
     // RFC6101 section 5.2.1
     // RFC5246 section 6.2.1
     if (head[0] == 0x16) {
         debugs(83, 7, "SSL version 3 handshake message");
         // The SSL version exist in the 2nd and 3rd bytes
-        sslVersion = (head[1] << 8) | head[2];
+        sslHelloVersion = (head[1] << 8) | head[2];
         debugs(83, 7, "SSL Version :" << std::hex << std::setw(8) << std::setfill('0') << sslVersion);
         // The hello message size exist in 4th and 5th bytes
         helloMsgSize = (head[3] << 8) + head[4];
         debugs(83, 7, "SSL Header Size: " << helloMsgSize);
         helloMsgSize +=5;
     } else if ((head[0] & 0x80) && head[2] == 0x01 && head[3] == 0x03) {
         debugs(83, 7, "SSL version 2 handshake message with v3 support");
-        sslVersion = (head[3] << 8) | head[4];
+        sslHelloVersion = 0x0002;
         debugs(83, 7, "SSL Version :" << std::hex << std::setw(8) << std::setfill('0') << sslVersion);
         // The hello message size exist in 2nd byte
         helloMsgSize = head[1];
         helloMsgSize +=2;
     } else {
         debugs(83, 7, "Not an SSL acceptable handshake message (SSLv2 message?)");
         return (helloMsgSize = -1);
     }
 
     // Set object as initialized. Even if we did not full parsing yet
     // The basic features, like the SSL version is set
     initialized_ = true;
     return helloMsgSize;
 }
 
 bool
 Ssl::Bio::sslFeatures::checkForCcsOrNst(const unsigned char *msg, size_t size)
 {
     while (size > 5) {
         const int msgType = msg[0];
@@ -1082,40 +1088,44 @@
             } else if (extType == 0x10) {
                 // Application-Layer Protocol Negotiation Extension, RFC7301
                 const size_t listLen = (ext[0] << 8) | ext[1];
                 if (listLen < extLen)
                     tlsAppLayerProtoNeg.assign(reinterpret_cast<const char *>(ext+5), listLen);
             } else
                 extensions.push_back(extType);
 
             ext += extLen;
         }
     }
     return true;
 }
 
 bool
 Ssl::Bio::sslFeatures::parseV23Hello(const unsigned char *hello, size_t size)
 {
     debugs(83, 7, "Get fake features from v23 ClientHello message.");
     if (size < 7)
         return false;
+
+    // Get the SSL version supported by client
+    sslVersion = (hello[3] << 8) | hello[4];
+
     //Ciphers list. It is stored after the Session ID.
     const unsigned int ciphersLen = (hello[5] << 8) | hello[6];
     const unsigned char *ciphers = hello + 11;
 
     if (size < ciphersLen + 11)
         return false;
 
     if (ciphersLen) {
 #if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
         const SSL_METHOD *method = TLS_method();
 #else
         const SSL_METHOD *method = SSLv23_method();
 #endif
         for (unsigned int i = 0; i < ciphersLen; i += 3) {
             // 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;

=== modified file 'src/ssl/bio.h'
--- src/ssl/bio.h	2015-05-20 11:00:11 +0000
+++ src/ssl/bio.h	2015-12-10 17:22:45 +0000
@@ -44,40 +44,41 @@
         bool parseV3Hello(const unsigned char *hello, size_t helloSize);
         /// Parses a v23 ClientHello message
         bool parseV23Hello(const unsigned char *hello, size_t helloSize);
         /// Parses a v3 ServerHello message.
         bool parseV3ServerHello(const unsigned char *hello, size_t helloSize);
         /// 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;
         /// Configure the SSL object with the SSL features of the sslFeatures object
         void applyToSSL(SSL *ssl, Ssl::BumpMode bumpMode) const;
         /// Parses an SSL Message header. It returns the ssl Message size.
         /// \retval >0 if the hello size is retrieved
         /// \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 sslHelloVersion; ///< The SSL hello message version
         int sslVersion; ///< The requested/used SSL version
         int compressMethod; ///< The requested/used compressed  method
         int helloMsgSize; ///< the hello message size
         mutable SBuf serverName; ///< The SNI hostname, if any
         std::string clientRequestedCiphers; ///< The client requested ciphers
         bool unknownCiphers; ///< True if one or more ciphers are unknown
         std::string ecPointFormatList;///< tlsExtension ecPointFormatList
         std::string ellipticCurves; ///< tlsExtension ellipticCurveList
         std::string opaquePrf; ///< tlsExtension opaquePrf
         bool doHeartBeats;
         bool tlsTicketsExtension; ///< whether TLS tickets extension is enabled
         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;
@@ -99,79 +100,80 @@
     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().
     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, Type type);
     /// Tells ssl connection to use BIO and monitor state via stateChanged()
     static void Link(SSL *ssl, BIO *bio);
 
     /// Prepare the rbuf buffer to accept hello data
     void prepReadBuf();
 
     /// Reads data from socket and record them to a buffer
     int readAndBuffer(char *buf, int size, BIO *table, const char *description);
 
+    /// Return the SSL features requested by SSL client
+    const Bio::sslFeatures &receivedHelloFeatures() const {return receivedHelloFeatures_;}
+
     const MemBuf &rBufData() {return rbuf;}
 protected:
     const int fd_; ///< the SSL socket we are reading and writing
     MemBuf rbuf;  ///< Used to buffer input data.
+    /// The futures retrieved from client or Server SSL hello message
+    Bio::sslFeatures receivedHelloFeatures_;
 };
 
 /// BIO node to handle socket IO for squid client side
 /// If bumping is enabled  this Bio detects and analyses client hello message
 /// to retrieve the SSL features supported by the client
 class ClientBio: public Bio
 {
 public:
     /// The ssl hello message read states
     typedef enum {atHelloNone = 0, atHelloStarted, atHelloReceived} HelloReadState;
     explicit ClientBio(const int anFd): Bio(anFd), holdRead_(false), holdWrite_(false), helloState(atHelloNone), helloSize(0), wrongProtocol(false) {}
 
     /// 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 (helloState == atHelloReceived); }
-    /// 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;}
     /// True if client does not looks like an SSL client
     bool noSslClient() {return wrongProtocol;}
 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.
     HelloReadState helloState; ///< The SSL hello read state
     int helloSize; ///< The SSL hello message sent by client size
     bool wrongProtocol; ///< true if client SSL hello parsing failed
 };
 
 /// BIO node to handle socket IO for squid server side
 /// If bumping is enabled, analyses the SSL hello message sent by squid OpenSSL
 /// subsystem (step3 bumping step) against bumping mode:
 ///   * Peek mode:  Send client hello message instead of the openSSL generated
 ///                 hello message and normaly denies bumping and allow only
 ///                 splice or terminate the SSL connection
 ///   * Stare mode: Sends the openSSL generated hello message and normaly
 ///                 denies splicing and allow bump or terminate the SSL
 ///                 connection
 ///  If SQUID_USE_OPENSSL_HELLO_OVERWRITE_HACK is enabled also checks if the
 ///  openSSL library features are compatible with the features reported in
 ///  web client SSL hello message and if it is, overwrites the openSSL SSL
 ///  object members to replace hello message with web client hello message.
@@ -180,57 +182,60 @@
 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) {}
     /// 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 setClientFeatures(const sslFeatures &features);
 
+    /// Parses server Hello message if it is recorded and extracts
+    /// server-supported features.
+    void extractHelloFeatures();
+
     bool resumingSession();
     /// 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;}
     /// Whether we can splice or not the SSL stream
     bool canSplice() {return allowSplice;}
     /// Whether we can bump or not the SSL stream
     bool canBump() {return allowBump;}
     /// The bumping mode
     void mode(Ssl::BumpMode m) {bumpMode_ = m;}
     Ssl::BumpMode bumpMode() {return bumpMode_;} ///< return the bumping mode
 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
     Ssl::BumpMode bumpMode_;
 };
 
 inline
 std::ostream &operator <<(std::ostream &os, Ssl::Bio::sslFeatures const &f)
 {
     return f.print(os);
 }
 
 } // namespace Ssl
 
 #endif /* SQUID_SSL_BIO_H */
 

=== modified file 'src/ssl/support.cc'
--- src/ssl/support.cc	2015-12-08 01:48:40 +0000
+++ src/ssl/support.cc	2015-12-10 17:29:53 +0000
@@ -1364,40 +1364,95 @@
 Ssl::CertError &
 Ssl::CertError::operator = (const CertError &old)
 {
     code = old.code;
     cert.resetAndLock(old.cert.get());
     return *this;
 }
 
 bool
 Ssl::CertError::operator == (const CertError &ce) const
 {
     return code == ce.code && cert.get() == ce.cert.get();
 }
 
 bool
 Ssl::CertError::operator != (const CertError &ce) const
 {
     return code != ce.code || cert.get() != ce.cert.get();
 }
 
+const char *
+Ssl::NegotiationHistory::printTlsVersion(int v) const
+{
+    switch(v) {
+    case TLS1_2_VERSION:
+        return "TLSv1.2";
+    case TLS1_1_VERSION:
+        return "TLSv1.1";
+    case TLS1_VERSION:
+        return "TLSv1";
+    case SSL3_VERSION:
+        return "SSLv3";
+    case SSL2_VERSION:
+        return "SSLv2";
+    default:
+        return nullptr;
+    }
+}
+
+void
+Ssl::NegotiationHistory::fillWith(SSL *ssl)
+{
+    if ((cipher = SSL_get_current_cipher(ssl)) != NULL) {
+        // Set the negotiated version only if the cipher negotiated
+        // else probably the negotiation is not completed and version
+        // is not the final negotiated version
+        version_ = ssl->version;
+    }
+
+    BIO *b = SSL_get_rbio(ssl);
+    Ssl::Bio *bio = static_cast<Ssl::Bio *>(b->ptr);
+
+    if (::Config.onoff.logTlsServerHelloDetails) {
+        if (Ssl::ServerBio *srvBio = dynamic_cast<Ssl::ServerBio *>(bio))
+            srvBio->extractHelloFeatures();
+    }
+
+    const Bio::sslFeatures &features = bio->receivedHelloFeatures();
+    helloVersion_ = features.sslHelloVersion;
+    supportedVersion_ = features.sslVersion;
+
+    debugs(83, 5, "SSL connection info on FD " << bio->fd() <<
+           " SSL version " << version_ <<
+           " negotiated cipher " << cipherName());
+}
+
+const char *
+Ssl::NegotiationHistory::cipherName() const
+{
+    if (!cipher)
+        return nullptr;
+
+    return SSL_CIPHER_get_name(cipher);
+}
+
 static int
 store_session_cb(SSL *ssl, SSL_SESSION *session)
 {
     if (!SslSessionCache)
         return 0;
 
     debugs(83, 5, "Request to store SSL Session ");
 
     SSL_SESSION_set_timeout(session, Config.SSL.session_ttl);
 
     unsigned char *id = session->session_id;
     unsigned int idlen = session->session_id_length;
     unsigned char key[MEMMAP_SLOT_KEY_SIZE];
     // Session ids are of size 32bytes. They should always fit to a
     // MemMap::Slot::key
     assert(idlen <= MEMMAP_SLOT_KEY_SIZE);
     memset(key, 0, sizeof(key));
     memcpy(key, id, idlen);
     int pos;
     Ipc::MemMap::Slot *slotW = SslSessionCache->openForWriting((const cache_key*)key, pos);

=== modified file 'src/ssl/support.h'
--- src/ssl/support.h	2015-12-08 01:48:40 +0000
+++ src/ssl/support.h	2015-12-10 17:08:38 +0000
@@ -73,40 +73,62 @@
 /// On errors, emits DBG_IMPORTANT with details and returns NULL.
 SSL *CreateServer(Security::ContextPtr sslContext, const int fd, const char *squidCtx);
 
 /// An SSL certificate-related error.
 /// Pairs an error code with the certificate experiencing the error.
 class CertError
 {
 public:
     ssl_error_t code; ///< certificate error code
     Security::CertPointer cert; ///< certificate with the above error code
     CertError(ssl_error_t anErr, X509 *aCert);
     CertError(CertError const &err);
     CertError & operator = (const CertError &old);
     bool operator == (const CertError &ce) const;
     bool operator != (const CertError &ce) const;
 };
 
 /// Holds a list of certificate SSL errors
 typedef CbDataList<Ssl::CertError> CertErrors;
 
+class NegotiationHistory
+{
+public:
+    NegotiationHistory(): helloVersion_(-1), supportedVersion_(-1), version_(-1), cipher(NULL) {}
+    void fillWith(SSL *); ///< Extract negotiation information from TLS object
+    const char *cipherName() const; ///< The name of negotiated cipher
+    /// String representation of TLS negotiated version
+    const char *negotiatedVersion() const {return printTlsVersion(version_);}
+    /// String representation of the received TLS hello message version.
+    const char *helloVersion() const {return printTlsVersion(helloVersion_);}
+    /// String representation of the maximum supported TLS version
+    /// by remote peer
+    const char *supportedVersion() const {return printTlsVersion(supportedVersion_);}
+private:
+    /// String representation of the TLS version 'v'
+    const char *printTlsVersion(int v) const;
+    int helloVersion_; ///< The SSL version of the hello message
+    int supportedVersion_; ///< The maximum supported SSL version
+    int version_; ///< The negotiated SSL version
+    const SSL_CIPHER *cipher; ///< The negotiated cipher
+};
+
 } //namespace Ssl
 
 /// \ingroup ServerProtocolSSLAPI
 Security::ContextPtr sslCreateServerContext(AnyP::PortCfg &port);
 
 /// \ingroup ServerProtocolSSLAPI
 Security::ContextPtr sslCreateClientContext(Security::PeerOptions &, long options, long flags);
 
 /// \ingroup ServerProtocolSSLAPI
 int ssl_read_method(int, char *, int);
 
 /// \ingroup ServerProtocolSSLAPI
 int ssl_write_method(int, const char *, int);
 
 /// \ingroup ServerProtocolSSLAPI
 void ssl_shutdown_method(SSL *ssl);
 
 /// \ingroup ServerProtocolSSLAPI
 const char *sslGetUserEmail(SSL *ssl);
 

_______________________________________________
squid-dev mailing list
[email protected]
http://lists.squid-cache.org/listinfo/squid-dev

Reply via email to