]> git.ipfire.org Git - thirdparty/squid.git/commitdiff
Support for resuming sessions
authorChristos Tsantilas <chtsanti@users.sourceforge.net>
Sat, 11 Apr 2015 09:36:48 +0000 (12:36 +0300)
committerChristos Tsantilas <chtsanti@users.sourceforge.net>
Sat, 11 Apr 2015 09:36:48 +0000 (12:36 +0300)
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.

src/ssl/PeerConnector.cc
src/ssl/PeerConnector.h
src/ssl/bio.cc
src/ssl/bio.h
src/ssl/support.cc

index e690e366d178ecc4d321521b169eff9805330e1e..102cfc171f3e05edc54d27edc01982ca5ea13042 100644 (file)
@@ -46,6 +46,7 @@ Ssl::PeerConnector::PeerConnector(
     negotiationTimeout(timeout),
     startTime(squid_curtime),
     splice(false),
+    resumingSession(false),
     serverCertificateHandled(false)
 {
     // if this throws, the caller's cb dialer is not our CbDialer
@@ -147,6 +148,9 @@ Ssl::PeerConnector::initializeSsl()
         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);
@@ -173,7 +177,7 @@ Ssl::PeerConnector::initializeSsl()
             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);
@@ -292,6 +296,11 @@ 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()) {
@@ -569,6 +578,19 @@ Ssl::PeerConnector::handleNegotiateError(const int ret)
     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.
index d20dbf7565547695ee40d5df56b27584ac260e6c..75b76855ebd0e1a860191ff44fba1e6dbf851388 100644 (file)
@@ -165,6 +165,7 @@ private:
     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
 };
 
index ddaf4237fa119038b27e0b7315ffa64e5f7e8066..98195848f3cd04c50984a12ed9e260787c789d52 100644 (file)
@@ -209,28 +209,12 @@ Ssl::ClientBio::read(char *buf, int size, BIO *table)
     }
 
     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) {
             wrongProtocol = true;
             return -1;
         }
@@ -247,7 +231,7 @@ Ssl::ClientBio::read(char *buf, int size, BIO *table)
             BIO_set_retry_read(table);
             return -1;
         }
-        features.get((const unsigned char *)rbuf.content());
+        features.get(rbuf);
         helloState = atHelloReceived;
     }
 
@@ -279,17 +263,7 @@ Ssl::ServerBio::stateChanged(const SSL *ssl, int where, int 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
@@ -459,7 +433,7 @@ Ssl::ServerBio::write(const char *buf, int size, BIO *table)
             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;
@@ -520,6 +494,24 @@ Ssl::ServerBio::flush(BIO *table)
     }
 }
 
+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)
@@ -639,7 +631,7 @@ squid_ssl_info(const SSL *ssl, int where, int 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);
 }
@@ -748,38 +740,226 @@ Ssl::Bio::sslFeatures::get(const SSL *ssl)
         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);
+    // 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;
+    }
 
-    // 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);
+    // 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*/) {
@@ -790,18 +970,35 @@ Ssl::Bio::sslFeatures::parseV3Hello(const unsigned char *hello)
         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())
@@ -822,27 +1019,56 @@ Ssl::Bio::sslFeatures::parseV3Hello(const unsigned char *hello)
             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);
 
@@ -854,23 +1080,19 @@ Ssl::Bio::sslFeatures::parseV3Hello(const unsigned char *hello)
 }
 
 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);
@@ -904,7 +1126,7 @@ Ssl::Bio::sslFeatures::parseV23Hello(const unsigned char *hello)
 }
 
 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
@@ -926,12 +1148,28 @@ Ssl::Bio::sslFeatures::applyToSSL(SSL *ssl) const
         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 <<
index f053fb2525c710a5881b6410af4a54fcf853b3be..66a8eb5b6fe3936642c2fb04ba2f2e62f8fbfab8 100644 (file)
@@ -37,18 +37,33 @@ public:
     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
@@ -56,10 +71,19 @@ public:
         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();
@@ -113,7 +137,7 @@ public:
     /// to socket and sets the "read retry" flag of the BIO to true
     virtual int read(char *buf, int size, BIO *table);
     /// Return true if the client hello message received and analized
-    bool gotHello() {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.
@@ -150,7 +174,7 @@ private:
 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
@@ -167,6 +191,7 @@ public:
     /// 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
@@ -181,9 +206,8 @@ public:
     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
index f6c744f915bf1a77c6cadfc5d5ff59a395d1129a..58279d88c85addb69cdb8a71432ed2c80937de1d 100644 (file)
@@ -1106,6 +1106,17 @@ Ssl::serverMethod(int version)
     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)
 {
@@ -1203,6 +1214,9 @@ sslCreateClientContext(const char *certfile, const char *keyfile, int version, c
         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;
 }