]> git.ipfire.org Git - thirdparty/squid.git/commitdiff
Fast SNI peek
authorChristos Tsantilas <chtsanti@users.sourceforge.net>
Wed, 18 May 2016 17:22:44 +0000 (20:22 +0300)
committerChristos Tsantilas <chtsanti@users.sourceforge.net>
Wed, 18 May 2016 17:22:44 +0000 (20:22 +0300)
Currently, bumping peek mode at step2 and splice at step2, after the SNI is
received is slow.
The most of the performance overhead comes from openSSL. However Squid does not
need openSSL to peek at SNI. It needs only to get client TLS Hello message
analyze it to retrieve SNI and then splice at step2.

This patch:
  - Postpone creation of the OpenSSL connection (i.e. SSL) object for the
    accepted TCP connection until after we peek at SNI (after step2).
  - Implements the Parser::BinaryTokenizer parser for extracting byte-oriented
    fields from raw input
  - Reimplement a new SSL/TLS handshake messages parser using the
    BinaryTokenizer, and remove old buggy parsing code from ssl/bio.cc
  - Adjust ConnStateData, Ssl::Bio, Ssl::PeerConnector classes to use the
    new parsers and parsing results.

This is a Measurement Factory project

1  2 
src/security/Handshake.cc
src/security/Handshake.h
src/ssl/bio.h

index 0000000000000000000000000000000000000000,cd8f418d7f81ffce03cffd2d88514db9eb86d2e4..f5744f1eb9bcda6f776fc434733efca080d831e5
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,673 +1,638 @@@
 -        parseServerCertificates(message.msg_body);
+ /*
+  * Copyright (C) 1996-2016 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.
+  */
+ /* DEBUG: section 83    SSL-Bump Server/Peer negotiation */
+ #include "squid.h"
+ #include "security/Handshake.h"
+ #if USE_OPENSSL
+ #include "ssl/support.h"
+ #endif
+ #include <unordered_set>
+ namespace Security {
+ /*
+  * The types below represent various SSL and TLS protocol elements. Most names
+  * are based on RFC 5264 and RFC 6066 terminology. Objects of these explicit
+  * types are stored or passed around. Other protocol elements are simply parsed
+  * in-place, without declaring a corresponding explicit class.
+  */
+ /// TLS Record Layer's content types from RFC 5246 Section 6.2.1
+ enum ContentType {
+     ctChangeCipherSpec = 20,
+     ctAlert = 21,
+     ctHandshake = 22,
+     ctApplicationData = 23
+ };
+ /// TLS Record Layer's frame from RFC 5246 Section 6.2.1.
+ class TLSPlaintext
+ {
+ public:
+     explicit TLSPlaintext(Parser::BinaryTokenizer &tk);
+     uint8_t type; ///< see ContentType
+     AnyP::ProtocolVersion version; ///< Record Layer, not necessarily the negotiated TLS version;
+     SBuf fragment; ///< possibly partial content
+ };
+ /// draft-hickman-netscape-ssl-00. Section 4.1. SSL Record Header Format
+ class Sslv2Record
+ {
+ public:
+     explicit Sslv2Record(Parser::BinaryTokenizer &tk);
+     SBuf fragment;
+ };
+ /// TLS Handshake protocol's handshake types from RFC 5246 Section 7.4
+ enum HandshakeType {
+     hskClientHello = 1,
+     hskServerHello = 2,
+     hskCertificate = 11,
+     hskServerHelloDone = 14
+ };
+ /// TLS Handshake Protocol frame from RFC 5246 Section 7.4.
+ class Handshake
+ {
+ public:
+     explicit Handshake(Parser::BinaryTokenizer &tk);
+     uint8_t msg_type; ///< see HandshakeType
+     SBuf msg_body; ///< Handshake Protocol message
+ };
+ /// TLS Alert protocol frame from RFC 5246 Section 7.2.
+ class Alert
+ {
+ public:
+     explicit Alert(Parser::BinaryTokenizer &tk);
+     bool fatal() const { return level == 2; }
+     uint8_t level; ///< warning or fatal
+     uint8_t description; ///< close_notify, unexpected_message, etc.
+ };
+ /// The size of the TLS Random structure from RFC 5246 Section 7.4.1.2.
+ static const uint64_t HelloRandomSize = 32;
+ /// TLS Hello Extension from RFC 5246 Section 7.4.1.4.
+ class Extension
+ {
+ public:
+     typedef uint16_t Type;
+     explicit Extension(Parser::BinaryTokenizer &tk);
+     /// whether this extension is supported by Squid and, hence, may be bumped
+     /// after peeking or spliced after staring (subject to other restrictions)
+     bool supported() const;
+     Type type;
+     SBuf data;
+ };
+ /// Extension types optimized for fast lookups.
+ typedef std::unordered_set<Extension::Type> Extensions;
+ static Extensions SupportedExtensions();
+ } // namespace Security
+ /// Convenience helper: We parse ProtocolVersion but store "int".
+ static AnyP::ProtocolVersion
+ ParseProtocolVersion(Parser::BinaryTokenizer &tk)
+ {
+     Parser::BinaryTokenizerContext context(tk, ".version");
+     uint8_t vMajor = tk.uint8(".major");
+     uint8_t vMinor = tk.uint8(".minor");
+     if (vMajor == 0 && vMinor == 2)
+         return AnyP::ProtocolVersion(AnyP::PROTO_SSL, 2, 0);
+     Must(vMajor == 3);
+     if (vMinor == 0)
+         return AnyP::ProtocolVersion(AnyP::PROTO_SSL, 3, 0);
+     return AnyP::ProtocolVersion(AnyP::PROTO_TLS, 1, (vMinor - 1));
+ }
+ Security::TLSPlaintext::TLSPlaintext(Parser::BinaryTokenizer &tk)
+ {
+     Parser::BinaryTokenizerContext context(tk, "TLSPlaintext");
+     type = tk.uint8(".type");
+     Must(type >= ctChangeCipherSpec && type <= ctApplicationData);
+     version = ParseProtocolVersion(tk);
+     // TODO: Must(version.major == 3);
+     fragment = tk.pstring16(".fragment");
+     context.success();
+ }
+ Security::Handshake::Handshake(Parser::BinaryTokenizer &tk)
+ {
+     Parser::BinaryTokenizerContext context(tk, "Handshake");
+     msg_type = tk.uint8(".msg_type");
+     msg_body = tk.pstring24(".msg_body");
+     context.success();
+ }
+ Security::Alert::Alert(Parser::BinaryTokenizer &tk)
+ {
+     Parser::BinaryTokenizerContext context(tk, "Alert");
+     level = tk.uint8(".level");
+     description = tk.uint8(".description");
+     context.success();
+ }
+ Security::Extension::Extension(Parser::BinaryTokenizer &tk)
+ {
+     Parser::BinaryTokenizerContext context(tk, "Extension");
+     type = tk.uint16(".type");
+     data = tk.pstring16(".data");
+     context.success();
+ }
+ bool
+ Security::Extension::supported() const
+ {
+     static const Extensions supportedExtensions = SupportedExtensions();
+     return supportedExtensions.find(type) != supportedExtensions.end();
+ }
+ Security::Sslv2Record::Sslv2Record(Parser::BinaryTokenizer &tk)
+ {
+     Parser::BinaryTokenizerContext context(tk, "Sslv2Record");
+     const uint16_t head = tk.uint16(".head");
+     const uint16_t length = head & 0x7FFF;
+     Must((head & 0x8000) && length); // SSLv2 message [without padding]
+     fragment = tk.area(length, ".fragment");
+     context.success();
+ }
+ Security::TlsDetails::TlsDetails():
+     compressionSupported(false),
+     doHeartBeats(false),
+     tlsTicketsExtension(false),
+     hasTlsTicket(false),
+     tlsStatusRequest(false),
+     unsupportedExtensions(false)
+ {
+ }
+ /* Security::HandshakeParser */
+ Security::HandshakeParser::HandshakeParser():
+     details(new TlsDetails),
+     state(atHelloNone),
+     resumingSession(false),
+     currentContentType(0),
+     done(nullptr),
+     expectingModernRecords(false)
+ {
+ }
+ void
+ Security::HandshakeParser::parseVersion2Record()
+ {
+     const Sslv2Record record(tkRecords);
+     tkRecords.commit();
+     details->tlsVersion = AnyP::ProtocolVersion(AnyP::PROTO_SSL, 2, 0);
+     parseVersion2HandshakeMessage(record.fragment);
+     state = atHelloReceived;
+     done = "SSLv2";
+ }
+ /// RFC 5246. Appendix E.2. Compatibility with SSL 2.0
+ /// And draft-hickman-netscape-ssl-00. Section 4.1. SSL Record Header Format
+ bool
+ Security::HandshakeParser::isSslv2Record(const SBuf &raw) const
+ {
+     Parser::BinaryTokenizer tk(raw, true);
+     const uint16_t head = tk.uint16("?v2Hello.msg_head");
+     const uint8_t type = tk.uint8("?v2Hello.msg_type");
+     const uint16_t length = head & 0x7FFF;
+     return (head & 0x8000) && length && type == 1;
+ }
+ void
+ Security::HandshakeParser::parseRecord()
+ {
+     if (expectingModernRecords)
+         parseModernRecord();
+     else
+         parseVersion2Record();
+ }
+ /// parses a single TLS Record Layer frame
+ void
+ Security::HandshakeParser::parseModernRecord()
+ {
+     const TLSPlaintext record(tkRecords);
+     tkRecords.commit();
+     details->tlsVersion = record.version;
+     // RFC 5246: length MUST NOT exceed 2^14
+     Must(record.fragment.length() <= (1 << 14));
+     // RFC 5246: MUST NOT send zero-length [non-application] fragments
+     Must(record.fragment.length() || record.type == ContentType::ctApplicationData);
+     if (currentContentType != record.type) {
+         Must(tkMessages.atEnd()); // no currentContentType leftovers
+         fragments = record.fragment;
+         tkMessages.reset(fragments, true); // true because more fragments may come
+         currentContentType = record.type;
+     } else {
+         fragments.append(record.fragment);
+         tkMessages.reinput(fragments, true); // true because more fragments may come
+         tkMessages.rollback();
+     }
+     parseMessages();
+ }
+ /// parses one or more "higher-level protocol" frames of currentContentType
+ void
+ Security::HandshakeParser::parseMessages()
+ {
+     for (; !tkMessages.atEnd(); tkMessages.commit()) {
+         switch (currentContentType) {
+         case ContentType::ctChangeCipherSpec:
+             parseChangeCipherCpecMessage();
+             continue;
+         case ContentType::ctAlert:
+             parseAlertMessage();
+             continue;
+         case ContentType::ctHandshake:
+             parseHandshakeMessage();
+             continue;
+         case ContentType::ctApplicationData:
+             parseApplicationDataMessage();
+             continue;
+         }
+         skipMessage("unknown ContentType msg [fragment]");
+     }
+ }
+ void
+ Security::HandshakeParser::parseChangeCipherCpecMessage()
+ {
+     Must(currentContentType == ContentType::ctChangeCipherSpec);
+     // We are currently ignoring Change Cipher Spec Protocol messages.
+     skipMessage("ChangeCipherCpec msg [fragment]");
+     // Everything after the ChangeCipherCpec message may be encrypted.
+     // Continuing parsing is pointless. Stop here.
+     resumingSession = true;
+     done = "ChangeCipherCpec";
+ }
+ void
+ Security::HandshakeParser::parseAlertMessage()
+ {
+     Must(currentContentType == ContentType::ctAlert);
+     const Alert alert(tkMessages);
+     debugs(83, (alert.fatal() ? 2:3),
+            "level " << static_cast<int>(alert.level) <<
+            " description " << static_cast<int>(alert.description));
+     if (alert.fatal())
+         done = "fatal Alert";
+     // else ignore the warning (at least for now)
+ }
+ void
+ Security::HandshakeParser::parseHandshakeMessage()
+ {
+     Must(currentContentType == ContentType::ctHandshake);
+     const Handshake message(tkMessages);
+     switch (message.msg_type) {
+     case HandshakeType::hskClientHello:
+         Must(state < atHelloReceived);
+         Security::HandshakeParser::parseClientHelloHandshakeMessage(message.msg_body);
+         state = atHelloReceived;
+         done = "ClientHello";
+         return;
+     case HandshakeType::hskServerHello:
+         Must(state < atHelloReceived);
+         parseServerHelloHandshakeMessage(message.msg_body);
+         state = atHelloReceived;
+         return;
+     case HandshakeType::hskCertificate:
+         Must(state < atCertificatesReceived);
 -X509 *
 -Security::HandshakeParser::ParseCertificate(const SBuf &raw)
 -{
 -    typedef const unsigned char *x509Data;
 -    const x509Data x509Start = reinterpret_cast<x509Data>(raw.rawContent());
 -    x509Data x509Pos = x509Start;
 -    X509 *x509 = d2i_X509(nullptr, &x509Pos, raw.length());
 -    Must(x509); // successfully parsed
 -    Must(x509Pos == x509Start + raw.length()); // no leftovers
 -    return x509;
 -}
 -
 -void
 -Security::HandshakeParser::parseServerCertificates(const SBuf &raw)
 -{
 -    Parser::BinaryTokenizer tkList(raw);
 -    const SBuf clist = tkList.pstring24("CertificateList");
 -    Must(tkList.atEnd()); // no leftovers after all certificates
 -
 -    Parser::BinaryTokenizer tkItems(clist);
 -    while (!tkItems.atEnd()) {
 -        X509 *cert = ParseCertificate(tkItems.pstring24("Certificate"));
 -        if (!serverCertificates.get())
 -            serverCertificates.reset(sk_X509_new_null());
 -        sk_X509_push(serverCertificates.get(), cert);
 -        debugs(83, 7, "parsed " << sk_X509_num(serverCertificates.get()) << " certificates so far");
 -    }
 -
 -}
+         state = atCertificatesReceived;
+         return;
+     case HandshakeType::hskServerHelloDone:
+         Must(state < atHelloDoneReceived);
+         // zero-length
+         state = atHelloDoneReceived;
+         done = "ServerHelloDone";
+         return;
+     }
+     debugs(83, 5, "ignoring " << message.msg_body.length() << "-byte type-" <<
+            message.msg_type << " handshake message");
+ }
+ void
+ Security::HandshakeParser::parseApplicationDataMessage()
+ {
+     Must(currentContentType == ContentType::ctApplicationData);
+     skipMessage("app data [fragment]");
+ }
+ void
+ Security::HandshakeParser::parseVersion2HandshakeMessage(const SBuf &raw)
+ {
+     Parser::BinaryTokenizer tk(raw);
+     Parser::BinaryTokenizerContext hello(tk, "V2ClientHello");
+     Must(tk.uint8(".type") == hskClientHello); // Only client hello supported.
+     details->tlsSupportedVersion = ParseProtocolVersion(tk);
+     const uint16_t ciphersLen = tk.uint16(".cipher_specs.length");
+     const uint16_t sessionIdLen = tk.uint16(".session_id.length");
+     const uint16_t challengeLen = tk.uint16(".challenge.length");
+     parseV23Ciphers(tk.area(ciphersLen, ".cipher_specs.body"));
+     details->sessionId = tk.area(sessionIdLen, ".session_id.body");
+     tk.skip(challengeLen, ".challenge.body");
+     hello.success();
+ }
+ void
+ Security::HandshakeParser::parseClientHelloHandshakeMessage(const SBuf &raw)
+ {
+     Parser::BinaryTokenizer tk(raw);
+     Parser::BinaryTokenizerContext hello(tk, "ClientHello");
+     details->tlsSupportedVersion = ParseProtocolVersion(tk);
+     details->clientRandom = tk.area(HelloRandomSize, ".random");
+     details->sessionId = tk.pstring8(".session_id");
+     parseCiphers(tk.pstring16(".cipher_suites"));
+     details->compressionSupported = parseCompressionMethods(tk.pstring8(".compression_methods"));
+     if (!tk.atEnd()) // extension-free message ends here
+         parseExtensions(tk.pstring16(".extensions"));
+     hello.success();
+ }
+ bool
+ Security::HandshakeParser::parseCompressionMethods(const SBuf &raw)
+ {
+     if (raw.length() == 0)
+         return false;
+     Parser::BinaryTokenizer tk(raw);
+     while (!tk.atEnd()) {
+         // Probably here we should check for DEFLATE(1) compression method
+         // which is the only supported by openSSL subsystem.
+         if (tk.uint8("compression_method") != 0)
+             return true;
+     }
+     return false;
+ }
+ void
+ Security::HandshakeParser::parseExtensions(const SBuf &raw)
+ {
+     Parser::BinaryTokenizer tk(raw);
+     while (!tk.atEnd()) {
+         Extension extension(tk);
+         if (!details->unsupportedExtensions && !extension.supported()) {
+             debugs(83, 5, "first unsupported extension: " << extension.type);
+             details->unsupportedExtensions = true;
+         }
+         switch(extension.type) {
+         case 0: // The SNI extension; RFC 6066, Section 3
+             details->serverName = parseSniExtension(extension.data);
+             break;
+         case 5: // Certificate Status Request; RFC 6066, Section 8
+             details->tlsStatusRequest = true;
+             break;
+         case 15: // The heartBeats, RFC 6520
+             details->doHeartBeats = true;
+             break;
+         case 16: { // Application-Layer Protocol Negotiation Extension, RFC 7301
+             Parser::BinaryTokenizer tkAPN(extension.data);
+             details->tlsAppLayerProtoNeg = tkAPN.pstring16("APN");
+             break;
+         }
+         case 35: // SessionTicket TLS Extension; RFC 5077
+             details->tlsTicketsExtension = true;
+             details->hasTlsTicket = !extension.data.isEmpty();
+         case 13172: // Next Protocol Negotiation Extension (expired draft?)
+         default:
+             break;
+         }
+     }
+ }
+ void
+ Security::HandshakeParser::parseCiphers(const SBuf &raw)
+ {
+     details->ciphers.reserve(raw.length() / sizeof(uint16_t));
+     Parser::BinaryTokenizer tk(raw);
+     while (!tk.atEnd()) {
+         const uint16_t cipher = tk.uint16("cipher");
+         details->ciphers.insert(cipher);
+     }
+ }
+ void
+ Security::HandshakeParser::parseV23Ciphers(const SBuf &raw)
+ {
+     Parser::BinaryTokenizer tk(raw);
+     while (!tk.atEnd()) {
+         // RFC 6101 Appendix E, RFC 5246 Appendix E2
+         // Unlike TLS, ciphers in SSLv23 Hellos are 3 bytes long and come in
+         // two versions: v2 and v3. The two versions may co-exist in a single
+         // SSLv23 Hello. Only v3 ciphers have a first byte value of zero.
+         // The ciphers are needed for our peeking/staring code that
+         // does not support SSLv2, so we ignore v2 ciphers.
+         const uint8_t prefix = tk.uint8("prefix");
+         const uint16_t cipher = tk.uint16("cipher");
+         if (prefix == 0)
+             details->ciphers.insert(cipher);
+     }
+ }
+ /// RFC 5246 Section 7.4.1.3. Server Hello
+ void
+ Security::HandshakeParser::parseServerHelloHandshakeMessage(const SBuf &raw)
+ {
+     Parser::BinaryTokenizer tk(raw);
+     Parser::BinaryTokenizerContext hello(tk, "ServerHello");
+     details->tlsSupportedVersion = ParseProtocolVersion(tk);
+     tk.skip(HelloRandomSize, ".random");
+     details->sessionId = tk.pstring8(".session_id");
+     details->ciphers.insert(tk.uint16(".cipher_suite"));
+     details->compressionSupported = tk.uint8(".compression_method") != 0; // not null
+     if (!tk.atEnd()) // extensions present
+         parseExtensions(tk.pstring16(".extensions"));
+     hello.success();
+ }
+ // RFC 6066 Section 3: ServerNameList (may be sent by both clients and servers)
+ SBuf
+ Security::HandshakeParser::parseSniExtension(const SBuf &extensionData) const
+ {
+     // Servers SHOULD send an empty SNI extension, not an empty ServerNameList!
+     if (extensionData.isEmpty())
+         return SBuf();
+     // SNI MUST NOT contain more than one name of the same name_type but
+     // we ignore violations and simply return the first host name found.
+     Parser::BinaryTokenizer tkList(extensionData);
+     Parser::BinaryTokenizer tkNames(tkList.pstring16("ServerNameList"));
+     while (!tkNames.atEnd()) {
+         Parser::BinaryTokenizerContext serverName(tkNames, "ServerName");
+         const uint8_t nameType = tkNames.uint8(".name_type");
+         const SBuf name = tkNames.pstring16(".name");
+         serverName.success();
+         if (nameType == 0) {
+             debugs(83, 3, "host_name=" << name);
+             return name; // it may be empty
+         }
+         // else we just parsed a new/unsupported NameType which,
+         // according to RFC 6066, MUST begin with a 16-bit length field
+     }
+     return SBuf(); // SNI extension lacks host_name
+ }
+ void
+ Security::HandshakeParser::skipMessage(const char *description)
+ {
+     // tkMessages/fragments can only contain messages of the same ContentType.
+     // To skip a message, we can and should skip everything we have [left]. If
+     // we have partial messages, debugging will mislead about their boundaries.
+     tkMessages.skip(tkMessages.leftovers().length(), description);
+ }
+ bool
+ Security::HandshakeParser::parseHello(const SBuf &data)
+ {
+     try {
+         if (!expectingModernRecords.configured())
+             expectingModernRecords.configure(!isSslv2Record(data));
+         // data contains everything read so far, but we may read more later
+         tkRecords.reinput(data, true);
+         tkRecords.rollback();
+         while (!done)
+             parseRecord();
+         debugs(83, 7, "success; got: " << done);
+         // we are done; tkRecords may have leftovers we are not interested in
+         return true;
+     }
+     catch (const Parser::BinaryTokenizer::InsufficientInput &) {
+         debugs(83, 5, "need more data");
+         return false;
+     }
+     return false; // unreached
+ }
+ #if USE_OPENSSL
 -void
 -Security::HandshakeParser::parseServerCertificates(const SBuf &raw)
 -{
 -}
 -
+ /// A helper function to create a set of all supported TLS extensions
+ static
+ Security::Extensions
+ Security::SupportedExtensions()
+ {
+     // optimize lookup speed by reserving the number of values x3, approximately
+     Security::Extensions extensions(64);
+     // Keep this list ordered and up to date by running something like
+     // egrep '# *define TLSEXT_TYPE_' /usr/include/openssl/tls1.h
+     // TODO: Teach OpenSSL to return the list of extensions it supports.
+ #if defined(TLSEXT_TYPE_server_name) // 0
+     extensions.insert(TLSEXT_TYPE_server_name);
+ #endif
+ #if defined(TLSEXT_TYPE_max_fragment_length) // 1
+     extensions.insert(TLSEXT_TYPE_max_fragment_length);
+ #endif
+ #if defined(TLSEXT_TYPE_client_certificate_url) // 2
+     extensions.insert(TLSEXT_TYPE_client_certificate_url);
+ #endif
+ #if defined(TLSEXT_TYPE_trusted_ca_keys) // 3
+     extensions.insert(TLSEXT_TYPE_trusted_ca_keys);
+ #endif
+ #if defined(TLSEXT_TYPE_truncated_hmac) // 4
+     extensions.insert(TLSEXT_TYPE_truncated_hmac);
+ #endif
+ #if defined(TLSEXT_TYPE_status_request) // 5
+     extensions.insert(TLSEXT_TYPE_status_request);
+ #endif
+ #if defined(TLSEXT_TYPE_user_mapping) // 6
+     extensions.insert(TLSEXT_TYPE_user_mapping);
+ #endif
+ #if defined(TLSEXT_TYPE_client_authz) // 7
+     extensions.insert(TLSEXT_TYPE_client_authz);
+ #endif
+ #if defined(TLSEXT_TYPE_server_authz) // 8
+     extensions.insert(TLSEXT_TYPE_server_authz);
+ #endif
+ #if defined(TLSEXT_TYPE_cert_type) // 9
+     extensions.insert(TLSEXT_TYPE_cert_type);
+ #endif
+ #if defined(TLSEXT_TYPE_elliptic_curves) // 10
+     extensions.insert(TLSEXT_TYPE_elliptic_curves);
+ #endif
+ #if defined(TLSEXT_TYPE_ec_point_formats) // 11
+     extensions.insert(TLSEXT_TYPE_ec_point_formats);
+ #endif
+ #if defined(TLSEXT_TYPE_srp) // 12
+     extensions.insert(TLSEXT_TYPE_srp);
+ #endif
+ #if defined(TLSEXT_TYPE_signature_algorithms) // 13
+     extensions.insert(TLSEXT_TYPE_signature_algorithms);
+ #endif
+ #if defined(TLSEXT_TYPE_use_srtp) // 14
+     extensions.insert(TLSEXT_TYPE_use_srtp);
+ #endif
+ #if defined(TLSEXT_TYPE_heartbeat) // 15
+     extensions.insert(TLSEXT_TYPE_heartbeat);
+ #endif
+ #if defined(TLSEXT_TYPE_session_ticket) // 35
+     extensions.insert(TLSEXT_TYPE_session_ticket);
+ #endif
+ #if defined(TLSEXT_TYPE_renegotiate) // 0xff01
+     extensions.insert(TLSEXT_TYPE_renegotiate);
+ #endif
+ #if defined(TLSEXT_TYPE_next_proto_neg) // 13172
+     extensions.insert(TLSEXT_TYPE_next_proto_neg);
+ #endif
+     /*
+      * OpenSSL does not support these last extensions by default, but those
+      * building the OpenSSL libraries and/or Squid might define them.
+      */
+     // OpenSSL may be built to support draft-rescorla-tls-opaque-prf-input-00,
+     // with the extension type value configured at build time. OpenSSL, Squid,
+     // and TLS agents must all be built with the same extension type value.
+ #if defined(TLSEXT_TYPE_opaque_prf_input)
+     extensions.insert(TLSEXT_TYPE_opaque_prf_input);
+ #endif
+     // Define this to add extensions supported by your OpenSSL but unknown to
+     // your Squid version. Use {list-initialization} to add multiple extensions.
+ #if defined(TLSEXT_TYPE_SUPPORTED_BY_MY_SQUID)
+     extensions.insert(TLSEXT_TYPE_SUPPORTED_BY_MY_SQUID);
+ #endif
+     return extensions; // might be empty
+ }
+ #else
+ static
+ Security::Extensions
+ Security::SupportedExtensions()
+ {
+     return Extensions(); // no extensions are supported without OpenSSL
+ }
+ #endif
index 0000000000000000000000000000000000000000,d3ff94a1463c326135344270718579c07253f5b9..c5c49a413fe55639b8e1bb8a0765a2f29011ef9d
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,132 +1,120 @@@
 -#if USE_OPENSSL
 -#include "ssl/gadgets.h"
 -#endif
+ /*
+  * Copyright (C) 1996-2016 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.
+  */
+ #ifndef SQUID_SECURITY_HANDSHAKE_H
+ #define SQUID_SECURITY_HANDSHAKE_H
+ #include "anyp/ProtocolVersion.h"
+ #include "base/YesNoNone.h"
+ #include "parser/BinaryTokenizer.h"
 -#if USE_OPENSSL
 -    Ssl::X509_STACK_Pointer serverCertificates; ///< parsed certificates chain
 -#endif
 -
+ #include <unordered_set>
+ namespace Security
+ {
+ class TlsDetails: public RefCountable
+ {
+ public:
+     typedef RefCount<TlsDetails> Pointer;
+     TlsDetails();
+     /// Prints to os stream a human readable form of TlsDetails object
+     std::ostream & print(std::ostream &os) const;
+     AnyP::ProtocolVersion tlsVersion; ///< The TLS hello message version
+     AnyP::ProtocolVersion tlsSupportedVersion; ///< The requested/used TLS version
+     bool compressionSupported; ///< The requested/used compressed  method
+     SBuf serverName; ///< The SNI hostname, if any
+     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
+     bool unsupportedExtensions; ///< whether any unsupported by Squid extensions are used
+     SBuf tlsAppLayerProtoNeg; ///< The value of the TLS application layer protocol extension if it is enabled
+     /// The client random number
+     SBuf clientRandom;
+     SBuf sessionId;
+     typedef std::unordered_set<uint16_t> Ciphers;
+     Ciphers ciphers;
+ };
+ inline
+ std::ostream &operator <<(std::ostream &os, Security::TlsDetails const &details)
+ {
+     return details.print(os);
+ }
+ /// Incremental TLS/SSL Handshake parser.
+ class HandshakeParser
+ {
+ public:
+     /// The parsing states
+     typedef enum {atHelloNone = 0, atHelloStarted, atHelloReceived, atCertificatesReceived, atHelloDoneReceived, atNstReceived, atCcsReceived, atFinishReceived} ParserState;
+     HandshakeParser();
+     /// Parses the initial sequence of raw bytes sent by the TLS/SSL agent.
+     /// Returns true upon successful completion (e.g., got HelloDone).
+     /// Returns false if more data is needed.
+     /// Throws on errors.
+     bool parseHello(const SBuf &data);
+     TlsDetails::Pointer details; ///< TLS handshake meta info or nil.
 -    void parseServerCertificates(const SBuf &raw);
 -#if USE_OPENSSL
 -    static X509 *ParseCertificate(const SBuf &raw);
 -#endif
 -
+     ParserState state; ///< current parsing state.
+     bool resumingSession; ///< True if this is a resuming session
+ private:
+     bool isSslv2Record(const SBuf &raw) const;
+     void parseRecord();
+     void parseModernRecord();
+     void parseVersion2Record();
+     void parseMessages();
+     void parseChangeCipherCpecMessage();
+     void parseAlertMessage();
+     void parseHandshakeMessage();
+     void parseApplicationDataMessage();
+     void skipMessage(const char *msgType);
+     bool parseRecordVersion2Try();
+     void parseVersion2HandshakeMessage(const SBuf &raw);
+     void parseClientHelloHandshakeMessage(const SBuf &raw);
+     void parseServerHelloHandshakeMessage(const SBuf &raw);
+     bool parseCompressionMethods(const SBuf &raw);
+     void parseExtensions(const SBuf &raw);
+     SBuf parseSniExtension(const SBuf &extensionData) const;
+     void parseCiphers(const SBuf &raw);
+     void parseV23Ciphers(const SBuf &raw);
+     unsigned int currentContentType; ///< The current TLS/SSL record content type
+     const char *done; ///< not nil if we got what we were looking for
+     /// concatenated TLSPlaintext.fragments of TLSPlaintext.type
+     SBuf fragments;
+     /// TLS record layer (parsing uninterpreted data)
+     Parser::BinaryTokenizer tkRecords;
+      /// TLS message layer (parsing fragments)
+     Parser::BinaryTokenizer tkMessages;
+      /// Whether to use TLS parser or a V2 compatible parser
+     YesNoNone expectingModernRecords;
+ };
+ }
+ #endif // SQUID_SECURITY_HANDSHAKE_H
diff --cc src/ssl/bio.h
index c8f537a15972e8095abfe9e7eef77220f2847b60,a06592660d26150124ffa6258d00fa1630ed485a..32b4e1ad4e01ea5ffb181fa98a0c3e51e094e033
@@@ -217,8 -147,22 +147,20 @@@ public
      /// The bumping mode
      void mode(Ssl::BumpMode m) {bumpMode_ = m;}
      Ssl::BumpMode bumpMode() {return bumpMode_;} ///< return the bumping mode
 -    const Ssl::X509_STACK_Pointer &serverCertificatesIfAny() { return parser_.serverCertificates; } /* XXX: may be nil */
 -
+     /// \return the TLS Details advertised by TLS server.
+     const Security::TlsDetails::Pointer &receivedHelloDetails() const {return parser_.details;}
  private:
-     sslFeatures clientFeatures; ///< SSL client features extracted from ClientHello message or SSL object
+     int readAndGive(char *buf, const int size, BIO *table);
+     int readAndParse(char *buf, const int size, BIO *table);
+     int readAndBuffer(BIO *table);
+     int giveBuffered(char *buf, const int size);
+     /// SSL client features extracted from ClientHello message or SSL object
+     Security::TlsDetails::Pointer clientTlsDetails;
+     /// TLS client hello message, used to adapt our tls Hello message to the server
+     SBuf clientHelloMessage;
      SBuf helloMsg; ///< Used to buffer output data.
      mb_size_t  helloMsgSize;
      bool helloBuild; ///< True if the client hello message sent to the server