Hi Amos,

   I make a new patch for squid-3.5. Use this one it should be OK.
It include changes from r14013.



On 04/13/2015 03:49 PM, Amos Jeffries wrote:
On 11/04/2015 10:01 p.m., Tsantilas Christos wrote:
Patch applied as r14012.
I am attaching the t13 patch for squid-3.5 too.


I've backported the server_name ACL patch before this one and your 3.5
patch does not seem to apply well on top of it.

However the regular backport method "bzr merge -r15011..14013 trunk"
seems to have only one minor collision.

I'm unsure its doing the right things though. Comparing your patch to
the changeset bzr merge produces I see these two odd chunks (in relation
to the server_name ACL changes):

--- christos.patch  2015-04-13 04:54:19.678645218 -0700
+++ bzr_merge.patch       2015-04-13 04:33:23.514618772 -0700
@@ -599,19 +598,15 @@
  +    return helloMsgSize;
  +}
  +
- bool
--Ssl::Bio::sslFeatures::get(const unsigned char *hello)
++bool
  +Ssl::Bio::sslFeatures::checkForCcsOrNst(const unsigned char *msg,
size_t size)
- {
--    // The SSL handshake message should starts with a 0x16 byte
--    if (hello[0] == 0x16) {
--        return parseV3Hello(hello);
++{
  +    while (size > 5) {
  +        const int msgType = msg[0];
  +        const int msgSslVersion = (msg[1] << 8) | msg[2];
  +        debugs(83, 7, "SSL Message Version :" << std::hex <<
std::setw(8) << std::setfill('0') << msgSslVersion);
  +        // Check for Change Cipher Spec message
-+        // RFC5246 section 6.2.1
++        // RFC5246 section 6.2.1
  +        if (msgType == 0x14) {// Change Cipher Spec message found
  +            debugs(83, 7, "SSL  Change Cipher Spec message found");
  +            return true;
@@ -636,9 +631,13 @@
  +    return false;
  +}
  +
-+bool
+ bool
+-Ssl::Bio::sslFeatures::get(const unsigned char *hello)
  +Ssl::Bio::sslFeatures::get(const MemBuf &buf, bool record)
-+{
+ {
+-    // The SSL handshake message should starts with a 0x16 byte
+-    if (hello[0] == 0x16) {
+-        return parseV3Hello(hello);
  +    int msgSize;
  +    if ((msgSize = parseMsgHead(buf)) <= 0) {
  +        debugs(83, 7, "Not a known SSL handshake message");


If you can try the bzr merge and confirm the diff/changeset is correct I
will do that normal cherrypicking backport.

Amos

_______________________________________________
squid-dev mailing list
squid-dev@lists.squid-cache.org
http://lists.squid-cache.org/listinfo/squid-dev


Splicing resuming sessions

This patch adds code in squid to control SslBump behavior when dealing with
"resuming SSL/TLS sessions". Without these changes, SslBump usually terminates
all resuming sessions with an error because such sessions do not include
server certificates, preventing Squid from successfully validating the server
identity.

After these changes, Squid splices resuming sessions. Splicing is the right
because Squid most likely has spliced the original connections that the client
and server are trying to resume now.
Without SslBump, session resumption would just work, and SslBump behaviour
should approach that ideal.

Future projects may add ACL checks for allowing resuming sessions and may
add more complex algorithms, including maintaining an SMP-shared
cache of sessions that may be resumed in the future and evaluating
client/server attempts to resume a session using that cache.

This patch also makes SSL client Hello message parsing more robust and
adds an SSL server Hello message parser.

Also add support for NPN (next protocol negotiation) and ALPN (Application-Layer Protocol Negotiation) tls extensions, required to correctly bump web clients
support these extensions

Technical details
-----------------

In Peek mode, the old Squid code would forward the client Hello message to the
server. If the server tries to resume the previous (spliced) SSL session with
the client, then Squid SSL code gets an ssl/PeerConnector.cc "ccs received
early" error (or similar) because the Squid SSL object expects a server
certificate and does not know anything about the session being resumed.

With this patch, Squid detects session resumption attempts and splices

Session resumption detection
----------------------------

There are two mechanism in SSL/TLS for resuming sessions. The traditional
shared session IDs and the TLS ticket extensions:

* If Squid detects a shared ID in both client and server Hello messages, then
Squid decides whether the session is being resumed by comparing those client
and server shared IDs. If (and only if) the IDs are the same, then Squid
assumes that it is dealing with a resuming session (using session IDs).

* If Squid detects a TLS ticket in the client Hello message and TLS ticket
support in the server Hello message as well as a Change Cipher Spec or a New
TLS Ticket message (following the server Hello message), then (and only then)
Squid assumes that it is dealing with a resuming session (using TLS tickets).

The TLS tickets check is not performed if Squid detects a shared session ID
in both client and server Hello messages.

NPN and ALPN tls extensions
---------------------------

Even if squid has some SSL hello messages parsing code, we are relying to 
openSSL for full parsing. The openSSL used in peek and splice mode to  parse
server hello message, check for errors and verify server certificates. 
If the openSSL, while parses the server hello message, find an extension enabled
in the server hello message, which is not enabled in its side, fails with an
error ("...parse tlsext...").

OpenSSL supports NPN tls extension and from 1.0.2 release supports also the
ALPN tls extensions. In peek mode we are forwading the client SSL hello message
as is, and if this message include support for NPN or ALPN tls extension is
possible that the SSL server support them and include related extensions
in its response. The openSSL will fail if support for these extensions is 
not enabled in its side.

This patch handles the NPN (TLSEXT_TYPE_next_proto_neg) as follows: 
Try to select the http/1.1 protocol from the server protocols list. If the
http/1.1 is not supported then the SSL bumping will fail. This is valid
because only http protocol we are supporting in squid.
Splicing is not affected. 

Also add support for the ALPN TLS extension. This extension is a replacement
for the NPN extension. The client sends a list of supported protocols. In the
case of stare mode squid now sends only http as supported protocol. In the
case of server-first or client-first bumbing modes, squid does enable this
extension.

The NPN supported by chromium browser the ALPN supported by firefox. 
Support for ALPN is added to openSSL 1.0.2 release.
These extensions are used to support SPDY and similar protocols.

This is a Measurement Factory project.

=== modified file 'src/ssl/PeerConnector.cc'
--- src/ssl/PeerConnector.cc	2015-04-13 05:59:05 +0000
+++ src/ssl/PeerConnector.cc	2015-04-14 09:58:32 +0000
@@ -29,40 +29,41 @@
 #include "ssl/PeerConnector.h"
 #include "ssl/ServerBump.h"
 #include "ssl/support.h"
 
 CBDATA_NAMESPACED_CLASS_INIT(Ssl, PeerConnector);
 
 Ssl::PeerConnector::PeerConnector(
     HttpRequestPointer &aRequest,
     const Comm::ConnectionPointer &aServerConn,
     const Comm::ConnectionPointer &aClientConn,
     AsyncCall::Pointer &aCallback,
     const time_t timeout):
     AsyncJob("Ssl::PeerConnector"),
     request(aRequest),
     serverConn(aServerConn),
     clientConn(aClientConn),
     callback(aCallback),
     negotiationTimeout(timeout),
     startTime(squid_curtime),
     splice(false),
+    resumingSession(false),
     serverCertificateHandled(false)
 {
     // if this throws, the caller's cb dialer is not our CbDialer
     Must(dynamic_cast<CbDialer*>(callback->getDialer()));
 }
 
 Ssl::PeerConnector::~PeerConnector()
 {
     debugs(83, 5, "Peer connector " << this << " gone");
 }
 
 bool Ssl::PeerConnector::doneAll() const
 {
     return (!callback || callback->canceled()) && AsyncJob::doneAll();
 }
 
 /// Preps connection and SSL state. Calls negotiate().
 void
 Ssl::PeerConnector::start()
 {
@@ -135,67 +136,70 @@
 
 #if NOT_YET
 
         else if (peer->name)
             SSL_set_ex_data(ssl, ssl_ex_index_server, peer->name);
 
 #endif
 
         else
             SSL_set_ex_data(ssl, ssl_ex_index_server, peer->host);
 
         if (peer->sslSession)
             SSL_set_session(ssl, peer->sslSession);
     } else 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);
         const char *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();
             if (!features.serverName.isEmpty())
                 hostName = features.serverName.c_str();
         }
 
         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 = request->GetHost();
         }
 
         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();
             if (features.sslVersion != -1) {
-                features.applyToSSL(ssl);
+                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, ::Config.ssl_client.parsedOptions);
 
             // Use SNI TLS extension only when we connect directly
             // to the origin server and we know the server host name.
             const char *sniServer = hostName ? hostName :
                                     (!request->GetHostIsNumeric() ? request->GetHost() : NULL);
             if (sniServer)
                 Ssl::setClientSNI(ssl, sniServer);
@@ -280,40 +284,45 @@
 
         serverCertificateHandled = true;
 
         csd->resetSslCommonName(Ssl::CommonHostName(serverCert.get()));
         debugs(83, 5, "HTTPS server CN: " << csd->sslCommonName() <<
                " bumped: " << *serverConnection());
 
         // remember the server certificate for later use
         if (Ssl::ServerBump *serverBump = csd->serverBump()) {
             serverBump->serverCert.reset(serverCert.release());
         }
     }
 }
 
 bool
 Ssl::PeerConnector::sslFinalized()
 {
     const int fd = serverConnection()->fd;
     SSL *ssl = fd_table[fd].ssl;
 
+    // In the case the session is resuming, the certificates does not exist and
+    // we did not do any cert validation
+    if (resumingSession)
+        return true;
+
     handleServerCertificate();
 
     if (ConnStateData *csd = request->clientConnectionManager.valid()) {
         if (Ssl::ServerBump *serverBump = csd->serverBump()) {
             // remember validation errors, if any
             if (Ssl::CertErrors *errs = static_cast<Ssl::CertErrors *>(SSL_get_ex_data(ssl, ssl_ex_index_ssl_errors)))
                 serverBump->sslErrors = cbdataReference(errs);
         }
     }
 
     if (Ssl::TheConfig.ssl_crt_validator) {
         Ssl::CertValidationRequest validationRequest;
         // WARNING: Currently we do not use any locking for any of the
         // members of the Ssl::CertValidationRequest class. In this code the
         // Ssl::CertValidationRequest object used only to pass data to
         // Ssl::CertValidationHelper::submit method.
         validationRequest.ssl = ssl;
         validationRequest.domainName = request->GetHost();
         if (Ssl::CertErrors *errs = static_cast<Ssl::CertErrors *>(SSL_get_ex_data(ssl, ssl_ex_index_ssl_errors)))
             // validationRequest disappears on return so no need to cbdataReference
@@ -557,40 +566,53 @@
     switch (ssl_error) {
 
     case SSL_ERROR_WANT_READ:
         setReadTimeout();
         Comm::SetSelect(fd, COMM_SELECT_READ, &NegotiateSsl, this, 0);
         return;
 
     case SSL_ERROR_WANT_WRITE:
         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;
         }
         Comm::SetSelect(fd, COMM_SELECT_WRITE, &NegotiateSsl, this, 0);
         return;
 
     case SSL_ERROR_SSL:
     case SSL_ERROR_SYSCALL:
         ssl_lib_error = ERR_get_error();
 
+        // In Peek mode, the ClientHello message sent to the server. If the
+        // server resuming a previous (spliced) SSL session with the client,
+        // then probably we are here because local SSL object does not know
+        // anything about the session being resumed.
+        //
+        if (srvBio->bumpMode() == Ssl::bumpPeek && (resumingSession = srvBio->resumingSession())) {
+            // we currently splice all resumed sessions unconditionally
+            if (const bool spliceResumed = true) {
+                checkForPeekAndSpliceDone(Ssl::bumpSplice);
+                return;
+            } // else fall through to find a matching ssl_bump action (with limited info)
+        }
+
         // If we are in peek-and-splice mode and still we did not write to
         // server yet, try to see if we should splice.
         // In this case the connection can be saved.
         // If the checklist decision is do not splice a new error will
         // occure in the next SSL_connect call, and we will fail again.
         // Abort on certificate validation errors to avoid splicing and
         // thus hiding them.
         // Abort if no certificate found probably because of malformed or
         // unsupported server Hello message (TODO: make configurable).
 #if 1
         if (!SSL_get_ex_data(ssl, ssl_ex_index_ssl_error_detail) &&
                 SSL_get_peer_certificate(ssl) &&
                 (srvBio->bumpMode() == Ssl::bumpPeek  || srvBio->bumpMode() == Ssl::bumpStare) && srvBio->holdWrite()) {
             debugs(81, 3, "Error ("  << ERR_error_string(ssl_lib_error, NULL) <<  ") but, hold write on SSL connection on FD " << fd);
             checkForPeekAndSplice();
             return;
         }
 #endif
 
         // store/report errno when ssl_error is SSL_ERROR_SYSCALL, ssl_lib_error is 0, and ret is -1

=== modified file 'src/ssl/PeerConnector.h'
--- src/ssl/PeerConnector.h	2015-04-13 05:59:05 +0000
+++ src/ssl/PeerConnector.h	2015-04-14 09:31:37 +0000
@@ -158,31 +158,32 @@
     /// if the server certificate was received from the server.
     void handleServerCertificate();
 
     /// Callback function called when squid receive message from cert validator helper
     static void sslCrtvdHandleReplyWrapper(void *data, Ssl::CertValidationResponse const &);
 
     /// A wrapper function for negotiateSsl for use with Comm::SetSelect
     static void NegotiateSsl(int fd, void *data);
 
     /// A wrapper function for checkForPeekAndSpliceDone for use with acl
     static void cbCheckForPeekAndSpliceDone(allow_t answer, void *data);
 
     HttpRequestPointer request; ///< peer connection trigger or cause
     Comm::ConnectionPointer serverConn; ///< TCP connection to the peer
     Comm::ConnectionPointer clientConn; ///< TCP connection to the client
     AsyncCall::Pointer callback; ///< we call this with the results
     AsyncCall::Pointer closeHandler; ///< we call this when the connection closed
     time_t negotiationTimeout; ///< the ssl connection timeout to use
     time_t startTime; ///< when the peer connector negotiation started
     bool splice; ///< Whether we are going to splice or not
+    bool resumingSession; ///< whether it is an SSL resuming session connection
     bool serverCertificateHandled; ///< whether handleServerCertificate() succeeded
 
     CBDATA_CLASS2(PeerConnector);
 };
 
 std::ostream &operator <<(std::ostream &os, const Ssl::PeerConnectorAnswer &a);
 
 } // namespace Ssl
 
 #endif /* SQUID_PEER_CONNECTOR_H */
 

=== modified file 'src/ssl/bio.cc'
--- src/ssl/bio.cc	2015-03-03 14:57:33 +0000
+++ src/ssl/bio.cc	2015-04-14 09:55:58 +0000
@@ -196,120 +196,94 @@
 Ssl::ClientBio::read(char *buf, int size, BIO *table)
 {
     if (helloState < atHelloReceived) {
 
         if (rbuf.isNull())
             rbuf.init(1024, 16384);
 
         size = rbuf.spaceSize() > size ? size : rbuf.spaceSize();
 
         if (!size)
             return 0;
 
         int bytes = Ssl::Bio::read(buf, size, table);
         if (bytes <= 0)
             return bytes;
         rbuf.append(buf, bytes);
         debugs(83, 7, "rbuf size: " << rbuf.contentSize());
     }
 
     if (helloState == atHelloNone) {
-
-        const unsigned char *head = (const unsigned char *)rbuf.content();
-        const char *s = objToString(head, rbuf.contentSize());
-        debugs(83, 7, "SSL Header: " << s);
-        if (rbuf.contentSize() < 5) {
+        helloSize = features.parseMsgHead(rbuf);
+        if (helloSize == 0) {
+            // Not enough bytes to get hello message size
             BIO_set_retry_read(table);
-            return 0;
-        }
-
-        if (head[0] == 0x16) {
-            debugs(83, 7, "SSL version 3 handshake message");
-            helloSize = (head[3] << 8) + head[4];
-            debugs(83, 7, "SSL Header Size: " << helloSize);
-            helloSize +=5;
-#if defined(DO_SSLV23)
-        } else if ((head[0] & 0x80) && head[2] == 0x01 && head[3] == 0x03) {
-            debugs(83, 7, "SSL version 2 handshake message with v3 support");
-            helloSize = head[1];
-            helloSize +=5;
-#endif
-        } else {
-            debugs(83, 7, "Not an SSL acceptable handshake message (SSLv2 message?)");
+            return -1;
+        } else if (helloSize < 0) {
             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((const unsigned char *)rbuf.content());
+        features.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;
 }
 
 void
 Ssl::ServerBio::stateChanged(const SSL *ssl, int where, int ret)
 {
     Ssl::Bio::stateChanged(ssl, where, ret);
 }
 
 void
 Ssl::ServerBio::setClientFeatures(const Ssl::Bio::sslFeatures &features)
 {
-    clientFeatures.sslVersion = features.sslVersion;
-    clientFeatures.compressMethod = features.compressMethod;
-    clientFeatures.serverName = features.serverName;
-    clientFeatures.clientRequestedCiphers = features.clientRequestedCiphers;
-    clientFeatures.unknownCiphers = features.unknownCiphers;
-    memcpy(clientFeatures.client_random, features.client_random, SSL3_RANDOM_SIZE);
-    clientFeatures.helloMessage.clear();
-    clientFeatures.helloMessage.append(features.helloMessage.rawContent(), features.helloMessage.length());
-    clientFeatures.doHeartBeats = features.doHeartBeats;
-    clientFeatures.extensions = features.extensions;
-    featuresSet = true;
+    clientFeatures = features;
 };
 
 int
 Ssl::ServerBio::read(char *buf, int size, BIO *table)
 {
     int bytes = Ssl::Bio::read(buf, size, table);
 
     if (bytes > 0 && record_) {
         if (rbuf.isNull())
             rbuf.init(1024, 16384);
         rbuf.append(buf, bytes);
         debugs(83, 5, "Record is enabled store " << bytes << " bytes");
     }
     debugs(83, 5, "Read " << bytes << " from " << size << " bytes");
     return bytes;
 }
 
 // This function makes the required checks to examine if the client hello
 // message is compatible with the features provided by OpenSSL toolkit.
 // If the features are compatible and can be supported it tries to rewrite SSL
@@ -445,41 +419,41 @@
 int
 Ssl::ServerBio::write(const char *buf, int size, BIO *table)
 {
 
     if (holdWrite_) {
         debugs(83, 7,  "Hold write, for SSL connection on " << fd_ << "will not write bytes of size " << size);
         BIO_set_retry_write(table);
         return -1;
     }
 
     if (!helloBuild && (bumpMode_ == Ssl::bumpPeek || bumpMode_ == Ssl::bumpStare)) {
         if (
             buf[1] >= 3  //it is an SSL Version3 message
             && buf[0] == 0x16 // and it is a Handshake/Hello message
         ) {
 
             //Hello message is the first message we write to server
             assert(helloMsg.isEmpty());
 
             SSL *ssl = fd_table[fd_].ssl;
-            if (featuresSet && ssl) {
+            if (clientFeatures.initialized_ && ssl) {
                 if (bumpMode_ == Ssl::bumpPeek) {
                     if (adjustSSL(ssl, clientFeatures))
                         allowBump = true;
                     allowSplice = true;
                     helloMsg.append(clientFeatures.helloMessage);
                     debugs(83, 7,  "SSL HELLO message for FD " << fd_ << ": Random number is adjusted for peek mode");
                 } else { /*Ssl::bumpStare*/
                     allowBump = true;
                     if (adjustSSL(ssl, clientFeatures)) {
                         allowSplice = true;
                         helloMsg.append(clientFeatures.helloMessage);
                         debugs(83, 7,  "SSL HELLO message for FD " << fd_ << ": Random number is adjusted for stare mode");
                     }
                 }
             }
         }
         // If we do not build any hello message, copy the current
         if (helloMsg.isEmpty())
             helloMsg.append(buf, size);
 
@@ -506,40 +480,58 @@
         }
 
         // 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);
     }
 }
 
+bool
+Ssl::ServerBio::resumingSession()
+{
+    if (!serverFeatures.initialized_)
+        serverFeatures.get(rbuf, false);
+
+    if (!clientFeatures.sessionId.isEmpty() && !serverFeatures.sessionId.isEmpty())
+        return clientFeatures.sessionId == serverFeatures.sessionId;
+
+    // is this a session resuming attempt using TLS tickets?
+    if (clientFeatures.hasTlsTicket &&
+            serverFeatures.tlsTicketsExtension &&
+            serverFeatures.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)
 {
     delete static_cast<Ssl::Bio*>(table->ptr);
     table->ptr = NULL;
     return 1;
 }
 
@@ -625,41 +617,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), unknownCiphers(false), doHeartBeats(true)
+Ssl::Bio::sslFeatures::sslFeatures(): 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;
@@ -734,216 +726,461 @@
     if (p) {
         ellipticCurves = objToString(p, len);
         debugs(83, 7, "tlsExtension ellipticCurveList of length " <<  len <<" :" << ellipticCurves);
     }
     // TLSEXT_TYPE_opaque_prf_input
     p = NULL;
     if (ssl->server) {
         if (ssl->s3 &&  ssl->s3->client_opaque_prf_input) {
             p = (unsigned char *)ssl->s3->client_opaque_prf_input;
             len = ssl->s3->client_opaque_prf_input_len;
         }
     } else {
         p = (unsigned char *)ssl->tlsext_opaque_prf_input;
         len = ssl->tlsext_opaque_prf_input_len;
     }
     if (p) {
         debugs(83, 7, "tlsExtension client-opaque-prf-input of length " << len);
         opaquePrf = objToString(p, len);
     }
 #endif
+    initialized_ = true;
     return true;
 }
 
+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];
+        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;
+#if defined(DO_SSLV23)
+    } else if ((head[0] & 0x80) && head[2] == 0x01 && head[3] == 0x03) {
+        debugs(83, 7, "SSL version 2 handshake message with v3 support");
+        sslVersion = (hello[3] << 8) | hello[4];
+        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;
+#endif
+    } 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];
+        const int msgSslVersion = (msg[1] << 8) | msg[2];
+        debugs(83, 7, "SSL Message Version :" << std::hex << std::setw(8) << std::setfill('0') << msgSslVersion);
+        // Check for Change Cipher Spec message
+        // RFC5246 section 6.2.1
+        if (msgType == 0x14) {// Change Cipher Spec message found
+            debugs(83, 7, "SSL  Change Cipher Spec message found");
+            return true;
+        }
+        // Check for New Session Ticket message
+        // RFC5077 section 3.3
+        if (msgType == 0x04) {// New Session Ticket message found
+            debugs(83, 7, "TLS  New Session Ticket message found");
+            return true;
+        }
+        // The hello message size exist in 4th and 5th bytes
+        size_t msgLength = (msg[3] << 8) + msg[4];
+        debugs(83, 7, "SSL Message Size: " << msgLength);
+        msgLength += 5;
+
+        if (msgLength <= size) {
+            msg += msgLength;
+            size -= msgLength;
+        } else
+            size = 0;
+    }
+    return false;
+}
+
 bool
-Ssl::Bio::sslFeatures::get(const unsigned char *hello)
+Ssl::Bio::sslFeatures::get(const MemBuf &buf, bool record)
 {
-    // The SSL handshake message should starts with a 0x16 byte
-    if (hello[0] == 0x16) {
-        return parseV3Hello(hello);
+    int msgSize;
+    if ((msgSize = parseMsgHead(buf)) <= 0) {
+        debugs(83, 7, "Not a known SSL handshake message");
+        return false;
+    }
+
+    if (msgSize > buf.contentSize()) {
+        debugs(83, 2, "Partial SSL handshake message, can not parse!");
+        return false;
+    }
+
+    if (record) {
+        helloMessage.clear();
+        helloMessage.append(buf.content(), buf.contentSize());
+    }
+
+    const unsigned char *msg = (const unsigned char *)buf.content();
 #if defined(DO_SSLV23)
-    } else if ((hello[0] & 0x80) && hello[2] == 0x01 && hello[3] == 0x03) {
-        return parseV23Hello(hello);
+    if (msg[0] & 0x80)
+        return parseV23Hello(msg, (size_t)msgSize);
+    else
 #endif
+    {
+        // Hello messages require 5 bytes header + 1 byte Msg type + 3 bytes for Msg size
+        if (buf.contentSize() < 9)
+            return false;
+
+        // Check for the Handshake/Message type
+        // The type 2 is a ServerHello, the type 1 is a ClientHello
+        // RFC5246 section 7.4
+        if (msg[5] == 0x2) { // ServerHello message
+            if (parseV3ServerHello(msg, (size_t)msgSize)) {
+                hasCcsOrNst = checkForCcsOrNst(msg + msgSize,  buf.contentSize() - msgSize);
+                return true;
+            }
+        } else if (msg[5] == 0x1) // ClientHello message,
+            return parseV3Hello(msg, (size_t)msgSize);
     }
 
-    debugs(83, 7, "Not a known SSL handshake message");
     return false;
 }
 
 bool
-Ssl::Bio::sslFeatures::parseV3Hello(const unsigned char *hello)
+Ssl::Bio::sslFeatures::parseV3ServerHello(const unsigned char *hello, size_t size)
 {
-    debugs(83, 7, "Get fake features from v3 hello message.");
-    // The SSL version exist in the 2nd and 3rd bytes
-    sslVersion = (hello[1] << 8) | hello[2];
-    debugs(83, 7, "Get fake features. Version :" << std::hex << std::setw(8) << std::setfill('0')<< sslVersion);
-
-    // The following hello message size exist in 4th and 5th bytes
-    int helloSize = (hello[3] << 8) | hello[4];
-    helloSize += 5; //Include the 5 header bytes.
-    helloMessage.clear();
-    helloMessage.append((const char *)hello, helloSize);
+    // Parse a ServerHello Handshake message
+    // RFC5246 section 7.4, 7.4.1.3
+    // The ServerHello starts at hello+5
+    const size_t helloSize = (hello[6] << 16) | (hello[7] << 8) | hello[8];
+    debugs(83, 7, "ServerHello message size: " << helloSize);
+    // helloSize should be msgSize + hello Header (4 bytes)
+    if (helloSize + 4 > size) {
+        debugs(83, 2, "ServerHello parse error");
+        return false;
+    }
+
+    // helloSize should be at least 38 bytes long:
+    // (SSL Version + Random + SessionId Length + Cipher Suite + Compression Method)
+    if (helloSize < 38) {
+        debugs(83, 2, "Too short ServerHello message");
+        return false;
+    }
+
+    debugs(83, 7, "Get fake features from v3 ServerHello message.");
+    // Get the correct version of the sub-hello message
+    sslVersion = (hello[9] << 8) | hello[10];
+    // At the position 43 (MsgHeader(5 bytes) + HelloHeader (6bytes) + SSL3_RANDOM_SIZE (32bytes))
+    const size_t sessIdLen = (size_t)hello[43];
+    debugs(83, 7, "Session ID Length: " <<  sessIdLen);
+
+    // The size should be enough to hold at least the following
+    // 5 MsgHelloHeader + 4 (hello header)
+    // + 2 (SSL Version) + 32 (random) + 1 (sessionId length)
+    // + sessIdLength + 2 (cipher suite) + 1 (compression method)
+    // = 47 + sessIdLength
+    if (47 + sessIdLen > size) {
+        debugs(83, 2, "ciphers length parse error");
+        return false;
+    }
+
+    // The sessionID stored at 44 position, after sessionID length field
+    sessionId.assign((const char *)(hello + 44), sessIdLen);
+
+    // Check if there are extensions in hello message
+    // RFC5246 section 7.4.1.4
+    if (size > 47 + sessIdLen + 2) {
+        // 47 + sessIdLen
+        const unsigned char *pToExtensions = hello + 47 + sessIdLen;
+        const size_t extensionsLen = (pToExtensions[0] << 8) | pToExtensions[1];
+        // Check if the hello size can hold extensions
+        if (47 + 2 + sessIdLen + extensionsLen > size ) {
+            debugs(83, 2, "Extensions length parse error");
+            return false;
+        }
+
+        pToExtensions += 2;
+        const unsigned char *ext = pToExtensions;
+        while (ext + 4 <= pToExtensions + extensionsLen) {
+            const short extType = (ext[0] << 8) | ext[1];
+            ext += 2;
+            const short extLen = (ext[0] << 8) | ext[1];
+            ext += 2;
+            debugs(83, 7, "TLS Extension: " << std::hex << extType << " of size:" << extLen);
+            // SessionTicket TLS Extension, RFC5077 section 3.2
+            if (extType == 0x23) {
+                tlsTicketsExtension = true;
+            }
+            ext += extLen;
+        }
+    }
+    return true;
+}
+
+bool
+Ssl::Bio::sslFeatures::parseV3Hello(const unsigned char *hello, size_t size)
+{
+    // Parse a ClientHello Handshake message
+    // RFC5246 section 7.4, 7.4.1.2
+    // The ClientHello starts at hello+5
+
+    debugs(83, 7, "Get fake features from v3 ClientHello message.");
+    const size_t helloSize = (hello[6] << 16) | (hello[7] << 8) | hello[8];
+    debugs(83, 7, "ClientHello message size: " << helloSize);
+    // helloSize should be size + hello Header (4 bytes)
+    if (helloSize + 4 > size) {
+        debugs(83, 2, "ClientHello parse error");
+        return false;
+    }
+
+    // helloSize should be at least 38 bytes long:
+    // (SSL Version(2) + Random(32) + SessionId Length(1) + Cipher Suite Length(2) + Compression Method Length(1))
+    if (helloSize < 38) {
+        debugs(83, 2, "Too short ClientHello message");
+        return false;
+    }
 
     //For SSLv3 or TLSv1.* protocols we can get some more informations
     if (hello[1] == 0x3 && hello[5] == 0x1 /*HELLO A message*/) {
         // Get the correct version of the sub-hello message
         sslVersion = (hello[9] << 8) | hello[10];
         //Get Client Random number. It starts on the position 11 of hello message
         memcpy(client_random, hello + 11, SSL3_RANDOM_SIZE);
         debugs(83, 7, "Client random: " <<  objToString(client_random, SSL3_RANDOM_SIZE));
 
         // At the position 43 (11+SSL3_RANDOM_SIZE)
-        int sessIDLen = (int)hello[43];
+        const size_t sessIDLen = (size_t)hello[43];
         debugs(83, 7, "Session ID Length: " <<  sessIDLen);
 
+        // The size should be enough to hold at least the following
+        // 5 MsgHelloHeader + 4 (hello header)
+        // + 2 (SSL Version) + 32 (random) + 1 (sessionId length)
+        // + sessIdLength + 2 (cipher suite length) + 1 (compression method length)
+        // = 47 + sessIdLength
+        if (47 + sessIDLen > size)
+            return false;
+
+        // The sessionID stored art 44 position, after sessionID length field
+        sessionId.assign((const char *)(hello + 44), sessIDLen);
+
         //Ciphers list. It is stored after the Session ID.
+        // It is a variable-length vector(RFC5246 section 4.3)
         const unsigned char *ciphers = hello + 44 + sessIDLen;
-        int ciphersLen = (ciphers[0] << 8) | ciphers[1];
+        const size_t ciphersLen = (ciphers[0] << 8) | ciphers[1];
+        if (47 + sessIDLen + ciphersLen > size) {
+            debugs(83, 2, "ciphers length parse error");
+            return false;
+        }
+
         ciphers += 2;
         if (ciphersLen) {
             const SSL_METHOD *method = SSLv3_method();
-            int cs = method->put_cipher_by_char(NULL, NULL);
+            const int cs = method->put_cipher_by_char(NULL, NULL);
             assert(cs > 0);
-            for (int i = 0; i < ciphersLen; i += cs) {
+            for (size_t i = 0; i < ciphersLen; i += cs) {
                 const SSL_CIPHER *c = method->get_cipher_by_char((ciphers + i));
                 if (c != NULL) {
                     if (!clientRequestedCiphers.empty())
                         clientRequestedCiphers.append(":");
                     clientRequestedCiphers.append(c->name);
                 } else
                     unknownCiphers = true;
             }
         }
         debugs(83, 7, "Ciphers requested by client: " << clientRequestedCiphers);
 
         // Compression field: 1 bytes the number of compression methods and
         // 1 byte for each compression method
         const unsigned char *compression = ciphers + ciphersLen;
         if (compression[0] > 1)
             compressMethod = 1;
         else
             compressMethod = 0;
         debugs(83, 7, "SSL compression methods number: " << (int)compression[0]);
 
+        // Parse Extensions, RFC5246 section 7.4.1.4
         const unsigned char *pToExtensions = compression + 1 + (int)compression[0];
-        if (pToExtensions <  hello + helloSize) {
-            int extensionsLen = (pToExtensions[0] << 8) | pToExtensions[1];
-            const unsigned char *ext = pToExtensions + 2;
-            while (ext < pToExtensions + extensionsLen) {
-                short extType = (ext[0] << 8) | ext[1];
+        if ((size_t)((pToExtensions - hello) + 2) < size) {
+            const size_t extensionsLen = (pToExtensions[0] << 8) | pToExtensions[1];
+            if ((pToExtensions - hello) + 2 + extensionsLen > size) {
+                debugs(83, 2, "Extensions length parse error");
+                return false;
+            }
+
+            pToExtensions += 2;
+            const unsigned char *ext = pToExtensions;
+            while (ext + 4 <= pToExtensions + extensionsLen) {
+                const short extType = (ext[0] << 8) | ext[1];
                 ext += 2;
-                short extLen = (ext[0] << 8) | ext[1];
+                const short extLen = (ext[0] << 8) | ext[1];
                 ext += 2;
-                debugs(83, 7, "SSL Exntension: " << std::hex << extType << " of size:" << extLen);
+                debugs(83, 7, "TLS Extension: " << std::hex << extType << " of size:" << extLen);
+
+                if (ext + extLen > pToExtensions + extensionsLen) {
+                    debugs(83, 2, "Extension " << std::hex << extType << " length parser error");
+                    return false;
+                }
+
                 //The SNI extension has the type 0 (extType == 0)
+                // RFC6066 sections 3, 10.2
                 // The two first bytes indicates the length of the SNI data (should be extLen-2)
                 // The next byte is the hostname type, it should be '0' for normal hostname (ext[2] == 0)
                 // The 3rd and 4th bytes are the length of the hostname
                 if (extType == 0 && ext[2] == 0) {
-                    int hostLen = (ext[3] << 8) | ext[4];
+                    const int hostLen = (ext[3] << 8) | ext[4];
                     serverName.assign((const char *)(ext+5), hostLen);
                     debugs(83, 7, "Found server name: " << serverName);
                 } else if (extType == 15 && ext[0] != 0) {
-                    // The heartBeats are the type 15
+                    // The heartBeats are the type 15, RFC6520
                     doHeartBeats = true;
+                } else if (extType == 0x23) {
+                    //SessionTicket TLS Extension RFC5077
+                    tlsTicketsExtension = true;
+                    if (extLen != 0)
+                        hasTlsTicket = true;
+                } else if (extType == 0x05) {
+                    // RFC6066 sections 8, 10.2
+                    tlsStatusRequest = true;
+                } else if (extType == 0x3374) {
+                    // detected TLS next protocol negotiate extension
+                } else if (extType == 0x10) {
+                    // Application-Layer Protocol Negotiation Extension, RFC7301
+                    const int listLen = (ext[0] << 8) | ext[1];
+                    if (listLen < extLen)
+                        tlsAppLayerProtoNeg.assign((const char *)(ext+5), listLen);
                 } else
                     extensions.push_back(extType);
 
                 ext += extLen;
             }
         }
     }
     return true;
 }
 
 bool
-Ssl::Bio::sslFeatures::parseV23Hello(const unsigned char *hello)
+Ssl::Bio::sslFeatures::parseV23Hello(const unsigned char *hello, size_t size)
 {
 #if defined(DO_SSLV23)
-    debugs(83, 7, "Get fake features from v23 hello message.");
-    sslVersion = (hello[3] << 8) | hello[4];
-    debugs(83, 7, "Get fake features. Version :" << std::hex << std::setw(8) << std::setfill('0')<< sslVersion);
-
-    // The following hello message size exist in 2nd byte
-    int helloSize = hello[1];
-    helloSize += 2; //Include the 2 header bytes.
-    helloMessage.clear();
-    helloMessage.append((char *)hello, helloSize);
-
+    debugs(83, 7, "Get fake features from v23 ClientHello message.");
+    if (size < 7)
+        return false;
     //Ciphers list. It is stored after the Session ID.
-
-    int ciphersLen = (hello[5] << 8) | hello[6];
+    const int ciphersLen = (hello[5] << 8) | hello[6];
     const unsigned char *ciphers = hello + 11;
+
+    if (size < ciphersLen + 11 + SSL3_RANDOM_SIZE)
+        return false;
+
     if (ciphersLen) {
         const SSL_METHOD *method = SSLv23_method();
         int cs = method->put_cipher_by_char(NULL, NULL);
         assert(cs > 0);
         for (int i = 0; i < ciphersLen; i += cs) {
             // The v2 hello messages cipher has 3 bytes.
             // The v2 cipher has the first byte not null
             // Because we are going to sent only v3 message we
             // are ignoring these ciphers
             if (ciphers[i] != 0)
                 continue;
             const SSL_CIPHER *c = method->get_cipher_by_char((ciphers + i + 1));
             if (c != NULL) {
                 if (!clientRequestedCiphers.empty())
                     clientRequestedCiphers.append(":");
                 clientRequestedCiphers.append(c->name);
             }
         }
     }
     debugs(83, 7, "Ciphers requested by client: " << clientRequestedCiphers);
 
     //Get Client Random number. It starts on the position 11 of hello message
     memcpy(client_random, ciphers + ciphersLen, SSL3_RANDOM_SIZE);
     debugs(83, 7, "Client random: " <<  objToString(client_random, SSL3_RANDOM_SIZE));
 
     compressMethod = 0;
     return true;
 #else
     return false;
 #endif
 }
 
 void
-Ssl::Bio::sslFeatures::applyToSSL(SSL *ssl) const
+Ssl::Bio::sslFeatures::applyToSSL(SSL *ssl, Ssl::BumpMode bumpMode) const
 {
     // To increase the possibility for bumping after peek mode selection or
     // splicing after stare mode selection it is good to set the
     // SSL protocol version.
     // The SSL_set_ssl_method is not the correct method because it will strict
     // SSL version which can be used to the SSL version used for client hello message.
     // For example will prevent comunnicating with a tls1.0 server if the
     // client sent and tlsv1.2 Hello message.
     //SSL_set_ssl_method(ssl, Ssl::method(features.toSquidSSLVersion()));
 #if defined(TLSEXT_NAMETYPE_host_name)
     if (!serverName.isEmpty()) {
         SSL_set_tlsext_host_name(ssl, serverName.c_str());
     }
 #endif
     if (!clientRequestedCiphers.empty())
         SSL_set_cipher_list(ssl, clientRequestedCiphers.c_str());
 #if defined(SSL_OP_NO_COMPRESSION) /* XXX: OpenSSL 0.9.8k lacks SSL_OP_NO_COMPRESSION */
     if (compressMethod == 0)
         SSL_set_options(ssl, SSL_OP_NO_COMPRESSION);
 #endif
 
+#if defined(TLSEXT_STATUSTYPE_ocsp)
+    if (tlsStatusRequest)
+        SSL_set_tlsext_status_type(ssl, TLSEXT_STATUSTYPE_ocsp);
+#endif
+
+#if defined(TLSEXT_TYPE_application_layer_protocol_negotiation)
+    if (!tlsAppLayerProtoNeg.isEmpty()) {
+        if (bumpMode == Ssl::bumpPeek)
+            SSL_set_alpn_protos(ssl, (const unsigned char*)tlsAppLayerProtoNeg.rawContent(), tlsAppLayerProtoNeg.length());
+        else {
+            static const unsigned char supported_protos[] = {8, 'h','t','t', 'p', '/', '1', '.', '1'};
+            SSL_set_alpn_protos(ssl, supported_protos, sizeof(supported_protos));
+        }
+    }
+#endif
 }
 
 std::ostream &
 Ssl::Bio::sslFeatures::print(std::ostream &os) const
 {
     static std::string buf;
+    // TODO: Also print missing features like the HeartBeats and AppLayerProtoNeg
     return os << "v" << sslVersion <<
            " SNI:" << (serverName.isEmpty() ? SBuf("-") : serverName) <<
            " comp:" << compressMethod <<
            " Ciphers:" << clientRequestedCiphers <<
            " Random:" << objToString(client_random, SSL3_RANDOM_SIZE) <<
            " ecPointFormats:" << ecPointFormatList <<
            " ec:" << ellipticCurves <<
            " opaquePrf:" << opaquePrf;
 }
 
 #endif /* USE_SSL */
 

=== modified file 'src/ssl/bio.h'
--- src/ssl/bio.h	2015-04-09 02:15:19 +0000
+++ src/ssl/bio.h	2015-04-14 09:56:48 +0000
@@ -20,63 +20,87 @@
 #include <string>
 
 namespace Ssl
 {
 
 /// BIO source and sink node, handling socket I/O and monitoring SSL state
 class Bio
 {
 public:
     enum Type {
         BIO_TO_CLIENT = 6000,
         BIO_TO_SERVER
     };
 
     /// Class to store SSL connection features
     class sslFeatures
     {
     public:
         sslFeatures();
         bool get(const SSL *ssl); ///< Retrieves the features from SSL object
-        bool get(const unsigned char *hello); ///< Retrieves the features from raw SSL hello message
-        bool parseV3Hello(const unsigned char *hello);
-        bool parseV23Hello(const unsigned char *hello);
+        /// Retrieves features from raw SSL Hello message.
+        /// \param record  whether to store Message to the helloMessage member
+        bool get(const MemBuf &, bool record = true);
+        /// Parses a v3 ClientHello message
+        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) const;
+        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 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;
         std::list<int> extensions;
         SBuf helloMessage;
+        bool initialized_;
     };
     explicit Bio(const int anFd);
     virtual ~Bio();
 
     /// Writes the given data to socket
     virtual int write(const char *buf, int size, BIO *table);
 
     /// Reads data from socket
     virtual int read(char *buf, int size, BIO *table);
 
     /// Flushes any buffered data to socket.
     /// The Ssl::Bio does not buffer any data, so this method has nothing to do
     virtual void flush(BIO *table) {}
 
     int fd() const { return fd_; } ///< The SSL socket descriptor
 
     /// Called by linked SSL connection whenever state changes, an alert
     /// appears, or an error occurs. See SSL_set_info_callback().
     virtual void stateChanged(const SSL *ssl, int where, int ret);
 
@@ -96,109 +120,109 @@
 /// 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) {}
 
     /// The ClientBio version of the Ssl::Bio::stateChanged method
     /// When the client hello message retrieved, fill the
     /// "features" member with the client provided informations.
     virtual void stateChanged(const SSL *ssl, int where, int ret);
     /// The ClientBio version of the Ssl::Bio::write method
     virtual int write(const char *buf, int size, BIO *table);
     /// The ClientBio version of the Ssl::Bio::read method
     /// If the holdRead flag is true then it does not write any data
     /// to socket and sets the "read retry" flag of the BIO to true
     virtual int read(char *buf, int size, BIO *table);
     /// Return true if the client hello message received and analized
-    bool gotHello() {return features.sslVersion != -1;}
+    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;}
 
 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
 };
 
 /// 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.
 ///  This is may allow bumping in peek mode and splicing in stare mode after
 ///  the server hello message received.
 class ServerBio: public Bio
 {
 public:
-    explicit ServerBio(const int anFd): Bio(anFd), featuresSet(false), helloMsgSize(0), helloBuild(false), allowSplice(false), allowBump(false), holdWrite_(false), record_(false), bumpMode_(bumpNone) {}
+    explicit ServerBio(const int anFd): Bio(anFd), helloMsgSize(0), helloBuild(false), allowSplice(false), allowBump(false), holdWrite_(false), 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);
 
+    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:
-    /// A random number to use as "client random" in client hello message
-    sslFeatures clientFeatures;
-    bool featuresSet; ///< True if the clientFeatures member is set and can be used
+    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-01-24 04:53:39 +0000
+++ src/ssl/support.cc	2015-04-10 10:10:50 +0000
@@ -1114,40 +1114,51 @@
         debugs(83, 5, "Using TLSv1.2");
         return TLSv1_2_server_method();
 #else
         debugs(83, DBG_IMPORTANT, "TLSv1.2 is not available in this Proxy.");
         return NULL;
 #endif
         break;
 
     case 1:
 
     default:
         debugs(83, 5, "Using SSLv2/SSLv3.");
         return SSLv23_server_method();
         break;
     }
 
     //Not reached
     return NULL;
 }
 
+#if defined(TLSEXT_TYPE_next_proto_neg)
+//Dummy next_proto_neg callback
+static int
+ssl_next_proto_cb(SSL *s, unsigned char **out, unsigned char *outlen, const unsigned char *in, unsigned int inlen, void *arg)
+{
+    static const unsigned char supported_protos[] = {8, 'h','t','t', 'p', '/', '1', '.', '1'};
+    (void)SSL_select_next_proto(out, outlen, in, inlen, supported_protos, sizeof(supported_protos));
+    return SSL_TLSEXT_ERR_OK;
+}
+#endif
+
 SSL_CTX *
 sslCreateClientContext(const char *certfile, const char *keyfile, int version, const char *cipher, const char *options, const char *flags, const char *CAfile, const char *CApath, const char *CRLfile)
 {
     int ssl_error;
     Ssl::ContextMethod method;
     SSL_CTX * sslContext;
     long fl = Ssl::parse_flags(flags);
 
     ssl_initialize();
 
     if (!keyfile)
         keyfile = certfile;
 
     if (!certfile)
         certfile = keyfile;
 
     if (!(method = Ssl::method(version)))
         return NULL;
 
     sslContext = SSL_CTX_new(method);
@@ -1217,40 +1228,43 @@
 
     if (CRLfile) {
         ssl_load_crl(sslContext, CRLfile);
         fl |= SSL_FLAG_VERIFY_CRL;
     }
 
 #if X509_V_FLAG_CRL_CHECK
     if (fl & SSL_FLAG_VERIFY_CRL_ALL)
         X509_STORE_set_flags(SSL_CTX_get_cert_store(sslContext), X509_V_FLAG_CRL_CHECK|X509_V_FLAG_CRL_CHECK_ALL);
     else if (fl & SSL_FLAG_VERIFY_CRL)
         X509_STORE_set_flags(SSL_CTX_get_cert_store(sslContext), X509_V_FLAG_CRL_CHECK);
 
 #endif
 
     if (!(fl & SSL_FLAG_NO_DEFAULT_CA) &&
             !SSL_CTX_set_default_verify_paths(sslContext)) {
         ssl_error = ERR_get_error();
         debugs(83, DBG_IMPORTANT, "WARNING: Ignoring error setting default CA certificate location: " << ERR_error_string(ssl_error, NULL));
     }
 
+#if defined(TLSEXT_TYPE_next_proto_neg)
+    SSL_CTX_set_next_proto_select_cb(sslContext, &ssl_next_proto_cb, NULL);
+#endif
     return sslContext;
 }
 
 /// \ingroup ServerProtocolSSLInternal
 int
 ssl_read_method(int fd, char *buf, int len)
 {
     SSL *ssl = fd_table[fd].ssl;
     int i;
 
 #if DONT_DO_THIS
 
     if (!SSL_is_init_finished(ssl)) {
         errno = ENOTCONN;
         return -1;
     }
 
 #endif
 
     i = SSL_read(ssl, buf, len);

_______________________________________________
squid-dev mailing list
squid-dev@lists.squid-cache.org
http://lists.squid-cache.org/listinfo/squid-dev

Reply via email to