]> git.ipfire.org Git - thirdparty/squid.git/commitdiff
Optimization: Spend less CPU and RAM on adjustSSL(). Speed gain: ~5%.
authorAlex Rousskov <rousskov@measurement-factory.com>
Tue, 3 May 2016 17:18:39 +0000 (11:18 -0600)
committerAlex Rousskov <rousskov@measurement-factory.com>
Tue, 3 May 2016 17:18:39 +0000 (11:18 -0600)
Do not store extension types just to iterate over them in adjustSSL().
Check for extension support while parsing instead. Since the list of
OpenSSL-supported extensions is constant (does not depend on the
connection), we do not need to create and index extension storage once
for each TLS connection; we now do it once per worker lifetime instead.

Use std::unordered_set instead of std::list for ciphers. Most real-world
cipher lists probably contain dozens of 2-byte entries, making std::list
storage a poor choice. Unlike TLS extensions, supported ciphers depend
on the connection so we have to store all of them to check whether each
stored cipher is supported for the SSL connection object created later.
Having an O(1) lookup speeds up that last check a lot compared to the
old linear search across all stored ciphers.

Do fast adjustSSL() checks before the longer cipher loop check.

Acknowledge TLS_EMPTY_RENEGOTIATION_INFO_SCSV pseudo cipher support.

Added TLSEXT_TYPE_signature_algorithms(13) and
TLSEXT_TYPE_next_proto_neg(13172) to the list of TLS extensions
supported by OpenSSL and recognized by Squid. Recognizing these
extensions is necessary for adjustSSL() to work in more real-world
cases.

Also sorted TLSEXT_TYPE_* entries and replaced "#if 0" code with a way
to build Squid to recognize more extensions as OpenSSL's list grows.

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

index 97439a48d10b34f4440f9704f217e8bb7984be26..b429f3a79001c28bcbb1c78b45e5b70e114c5c27 100644 (file)
@@ -14,6 +14,8 @@
 #include "ssl/support.h"
 #endif
 
+#include <unordered_set>
+
 namespace Security {
 
 // TODO: Replace with Anyp::ProtocolVersion and use for TlsDetails::tls*Version.
@@ -105,12 +107,21 @@ static const uint64_t HelloRandomSize = 32;
 class Extension
 {
 public:
+    typedef uint16_t Type;
     explicit Extension(BinaryTokenizer &tk);
 
-    uint16_t type;
+    /// whether this extension is supported by Squid and, hence, may be bumped
+    /// after peeking or spliced after staring (subject to other restrictions)
+    bool supported() const;
+
+    Type type;
     SBuf data;
 };
 
+/// Extension types optimized for fast lookups.
+typedef std::unordered_set<Extension::Type> Extensions;
+static Extensions SupportedExtensions();
+
 } // namespace Security
 
 /// Convenience helper: We parse ProtocolVersion but store "int".
@@ -165,6 +176,13 @@ Security::Extension::Extension(BinaryTokenizer &tk)
     context.success();
 }
 
+bool
+Security::Extension::supported() const
+{
+    static const Extensions supportedExtensions = SupportedExtensions();
+    return supportedExtensions.find(type) != supportedExtensions.end();
+}
+
 Security::Sslv2Record::Sslv2Record(BinaryTokenizer &tk)
 {
     BinaryTokenizerContext context(tk, "Sslv2Record");
@@ -182,26 +200,9 @@ Security::TlsDetails::TlsDetails():
     doHeartBeats(true),
     tlsTicketsExtension(false),
     hasTlsTicket(false),
-    tlsStatusRequest(false)
-{
-}
-
-/// debugging helper to print various parsed records and messages
-class DebugFrame
-{
-public:
-    DebugFrame(const char *aName, uint64_t aType, uint64_t aSize):
-        name(aName), type(aType), size(aSize) {}
-
-    const char *name;
-    uint64_t type;
-    uint64_t size;
-};
-
-inline std::ostream &
-operator <<(std::ostream &os, const DebugFrame &frame)
+    tlsStatusRequest(false),
+    unsupportedExtensions(false)
 {
-    return os << frame.size << "-byte type-" << frame.type << ' ' << frame.name;
 }
 
 /* Security::HandshakeParser */
@@ -356,8 +357,8 @@ Security::HandshakeParser::parseHandshakeMessage()
             done = "ServerHelloDone";
             return;
     }
-    debugs(83, 5, "ignoring " <<
-           DebugFrame("handshake msg", message.msg_type, message.msg_body.length()));
+    debugs(83, 5, "ignoring " << message.msg_body.length() << "-byte type-" <<
+           message.msg_type << " handshake message");
 }
 
 void
@@ -404,7 +405,11 @@ Security::HandshakeParser::parseExtensions(const SBuf &raw)
     BinaryTokenizer tk(raw);
     while (!tk.atEnd()) {
         Extension extension(tk);
-        details->extensions.push_back(extension.type);
+
+        if (!details->unsupportedExtensions && !extension.supported()) {
+            debugs(83, 5, "first unsupported extension: " << extension.type);
+            details->unsupportedExtensions = true;
+        }
 
         switch(extension.type) {
         case 0: // The SNI extension; RFC 6066, Section 3
@@ -434,10 +439,11 @@ Security::HandshakeParser::parseExtensions(const SBuf &raw)
 void
 Security::HandshakeParser::parseCiphers(const SBuf &raw)
 {
+    details->ciphers.reserve(raw.length() / sizeof(uint16_t));
     BinaryTokenizer tk(raw);
     while (!tk.atEnd()) {
         const uint16_t cipher = tk.uint16("cipher");
-        details->ciphers.push_back(cipher);
+        details->ciphers.insert(cipher);
     }
 }
 
@@ -453,7 +459,7 @@ Security::HandshakeParser::parseV23Ciphers(const SBuf &raw)
         const uint8_t prefix = tk.uint8("prefix");
         const uint16_t cipher = tk.uint16("cipher");
         if (prefix == 0) // TODO: return immediately if prefix is positive?
-            details->ciphers.push_back(cipher);
+            details->ciphers.insert(cipher);
     }
 }
 
@@ -466,7 +472,7 @@ Security::HandshakeParser::parseServerHelloHandshakeMessage(const SBuf &raw)
     details->tlsSupportedVersion = ParseProtocolVersion(tk);
     tk.skip(HelloRandomSize, ".random");
     details->sessionId = tk.pstring8(".session_id");
-    details->ciphers.push_back(tk.uint16(".cipher_suite"));
+    details->ciphers.insert(tk.uint16(".cipher_suite"));
     details->compressMethod = tk.uint8(".compression_method") != 0; // not null
     if (!tk.atEnd()) // extensions present
         parseExtensions(tk.pstring16(".extensions"));
@@ -565,9 +571,108 @@ Security::HandshakeParser::parseServerCertificates(const SBuf &raw)
     }
 
 }
+
+/// A helper function to create a set of all supported TLS extensions
+static
+Security::Extensions
+Security::SupportedExtensions()
+{
+    // optimize lookup speed by reserving the number of values x3, approximately
+    Security::Extensions extensions(64);
+
+    // Keep this list ordered and up to date by running something like
+    // egrep '# *define TLSEXT_TYPE_' /usr/include/openssl/tls1.h
+    // TODO: Teach OpenSSL to return the list of extensions it supports.
+#if defined(TLSEXT_TYPE_server_name) // 0
+    extensions.insert(TLSEXT_TYPE_server_name);
+#endif
+#if defined(TLSEXT_TYPE_max_fragment_length) // 1
+    extensions.insert(TLSEXT_TYPE_max_fragment_length);
+#endif
+#if defined(TLSEXT_TYPE_client_certificate_url) // 2
+    extensions.insert(TLSEXT_TYPE_client_certificate_url);
+#endif
+#if defined(TLSEXT_TYPE_trusted_ca_keys) // 3
+    extensions.insert(TLSEXT_TYPE_trusted_ca_keys);
+#endif
+#if defined(TLSEXT_TYPE_truncated_hmac) // 4
+    extensions.insert(TLSEXT_TYPE_truncated_hmac);
+#endif
+#if defined(TLSEXT_TYPE_status_request) // 5
+    extensions.insert(TLSEXT_TYPE_status_request);
+#endif
+#if defined(TLSEXT_TYPE_user_mapping) // 6
+    extensions.insert(TLSEXT_TYPE_user_mapping);
+#endif
+#if defined(TLSEXT_TYPE_client_authz) // 7
+    extensions.insert(TLSEXT_TYPE_client_authz);
+#endif
+#if defined(TLSEXT_TYPE_server_authz) // 8
+    extensions.insert(TLSEXT_TYPE_server_authz);
+#endif
+#if defined(TLSEXT_TYPE_cert_type) // 9
+    extensions.insert(TLSEXT_TYPE_cert_type);
+#endif
+#if defined(TLSEXT_TYPE_elliptic_curves) // 10
+    extensions.insert(TLSEXT_TYPE_elliptic_curves);
+#endif
+#if defined(TLSEXT_TYPE_ec_point_formats) // 11
+    extensions.insert(TLSEXT_TYPE_ec_point_formats);
+#endif
+#if defined(TLSEXT_TYPE_srp) // 12
+    extensions.insert(TLSEXT_TYPE_srp);
+#endif
+#if defined(TLSEXT_TYPE_signature_algorithms) // 13
+    extensions.insert(TLSEXT_TYPE_signature_algorithms);
+#endif
+#if defined(TLSEXT_TYPE_use_srtp) // 14
+    extensions.insert(TLSEXT_TYPE_use_srtp);
+#endif
+#if defined(TLSEXT_TYPE_heartbeat) // 15
+    extensions.insert(TLSEXT_TYPE_heartbeat);
+#endif
+#if defined(TLSEXT_TYPE_session_ticket) // 35
+    extensions.insert(TLSEXT_TYPE_session_ticket);
+#endif
+#if defined(TLSEXT_TYPE_renegotiate) // 0xff01
+    extensions.insert(TLSEXT_TYPE_renegotiate);
+#endif
+#if defined(TLSEXT_TYPE_next_proto_neg) // 13172
+    extensions.insert(TLSEXT_TYPE_next_proto_neg);
+#endif
+
+    /*
+     * OpenSSL does not support these last extensions by default, but those
+     * building the OpenSSL libraries and/or Squid might define them.
+     */
+
+    // OpenSSL may be built to support draft-rescorla-tls-opaque-prf-input-00,
+    // with the extension type value configured at build time. OpenSSL, Squid,
+    // and TLS agents must all be built with the same extension type value.
+#if defined(TLSEXT_TYPE_opaque_prf_input)
+    extensions.insert(TLSEXT_TYPE_opaque_prf_input);
+#endif
+
+    // Define this to add extensions supported by your OpenSSL but unknown to
+    // your Squid version. Use {list-initialization} to add multiple extensions.
+#if defined(TLSEXT_TYPE_SUPPORTED_BY_MY_SQUID)
+    extensions.insert(TLSEXT_TYPE_SUPPORTED_BY_MY_SQUID);
+#endif
+
+    return extensions; // might be empty
+}
+
 #else
+
 void
 Security::HandshakeParser::parseServerCertificates(const SBuf &raw)
 {
 }
+
+static
+Security::Extensions
+Security::SupportedExtensions()
+{
+    return Extensions(); // no extensions are supported without OpenSSL
+}
 #endif
index 26bd40f0c72012c22e6bf787b03c7441e8595412..8726c08ce8547a6a285fdca75d1a71291171d255 100644 (file)
@@ -16,7 +16,7 @@
 #include "ssl/gadgets.h"
 #endif
 
-#include <list>
+#include <unordered_set>
 
 namespace Security
 {
@@ -38,12 +38,14 @@ public:
     bool tlsTicketsExtension; ///< whether TLS tickets extension is enabled
     bool hasTlsTicket; ///< whether a TLS ticket is included
     bool tlsStatusRequest; ///< whether the TLS status request extension is set
+    bool unsupportedExtensions; ///< whether any unsupported by Squid extensions are used
     SBuf tlsAppLayerProtoNeg; ///< The value of the TLS application layer protocol extension if it is enabled
     /// The client random number
     SBuf clientRandom;
     SBuf sessionId;
-    std::list<uint16_t> ciphers;
-    std::list<uint16_t> extensions;
+
+    typedef std::unordered_set<uint16_t> Ciphers;
+    Ciphers ciphers;
 };
 
 inline
index 34faf322d84b039e0c859087f10fa16ccc41f180..4142f3d96c826ec4627cd5fe7921a107809462d8 100644 (file)
@@ -338,24 +338,6 @@ adjustSSL(SSL *ssl, Security::TlsDetails::Pointer const &details, SBuf &helloMes
         return false;
     }
 
-    // Check ciphers list
-    for (auto cipherId: details->ciphers) {
-        bool found = false;
-        STACK_OF(SSL_CIPHER) *cipher_stack = SSL_get_ciphers(ssl);
-        for (int i = 0; i < sk_SSL_CIPHER_num(cipher_stack); i++) {
-            SSL_CIPHER *c = sk_SSL_CIPHER_value(cipher_stack, i);
-            const long cid = SSL_CIPHER_get_id(c);
-            if (cipherId == (0xFFFF & cid)) {
-                found = true;
-                break;
-            }
-        }
-        if (!found) {
-            debugs(83, 5, "Client Hello Data supports cipher '"<< cipherId <<"' but we do not support it!");
-            return false;
-        }
-    }
-
 #if !defined(SSL_TLSEXT_HB_ENABLED)
     if (details->doHeartBeats) {
         debugs(83, 5, "Client Hello Data supports HeartBeats but we do not support!");
@@ -363,56 +345,50 @@ adjustSSL(SSL *ssl, Security::TlsDetails::Pointer const &details, SBuf &helloMes
     }
 #endif
 
-    for (std::list<uint16_t>::iterator it = details->extensions.begin(); it != details->extensions.end(); ++it) {
-        static int supportedExtensions[] = {
-#if defined(TLSEXT_TYPE_server_name)
-            TLSEXT_TYPE_server_name,
-#endif
-#if defined(TLSEXT_TYPE_opaque_prf_input)
-            TLSEXT_TYPE_opaque_prf_input,
-#endif
-#if defined(TLSEXT_TYPE_heartbeat)
-            TLSEXT_TYPE_heartbeat,
-#endif
+    if (details->unsupportedExtensions) {
+        debugs(83, 5, "Client Hello contains extensions that we do not support!");
+        return false;
+    }
+
+    SSL3_BUFFER *wb=&(ssl->s3->wbuf);
+    if (wb->len < (size_t)helloMessage.length()) {
+        debugs(83, 5, "Client Hello exceeds OpenSSL buffer: " << helloMessage.length() << " >= " << wb->len);
+        return false;
+    }
+
+    /* Check whether all on-the-wire ciphers are supported by OpenSSL. */
+
+    const auto &wireCiphers = details->ciphers;
+    Security::TlsDetails::Ciphers::size_type ciphersToFind = wireCiphers.size();
+
+    // RFC 5746: "TLS_EMPTY_RENEGOTIATION_INFO_SCSV is not a true cipher suite".
+    // It is commonly seen on the wire, including in from-OpenSSL traffic, but
+    // SSL_get_ciphers() does not return this _pseudo_ cipher suite in my tests.
+    // If OpenSSL supports scsvCipher, we count it (at most once) further below.
 #if defined(TLSEXT_TYPE_renegotiate)
-            TLSEXT_TYPE_renegotiate,
-#endif
-#if defined(TLSEXT_TYPE_ec_point_formats)
-            TLSEXT_TYPE_ec_point_formats,
-#endif
-#if defined(TLSEXT_TYPE_elliptic_curves)
-            TLSEXT_TYPE_elliptic_curves,
-#endif
-#if defined(TLSEXT_TYPE_session_ticket)
-            TLSEXT_TYPE_session_ticket,
-#endif
-#if defined(TLSEXT_TYPE_status_request)
-            TLSEXT_TYPE_status_request,
-#endif
-#if defined(TLSEXT_TYPE_use_srtp)
-            TLSEXT_TYPE_use_srtp,
-#endif
-#if 0 //Allow 13172 Firefox supported extension for testing purposes
-            13172,
+    // the 0x00FFFF mask converts 3-byte OpenSSL cipher to our 2-byte cipher
+    const uint16_t scsvCipher = SSL3_CK_SCSV & 0x00FFFF;
+#else
+    const uint16_t scsvCipher = 0;
 #endif
-            -1
-        };
-        bool found = false;
-        for (int i = 0; supportedExtensions[i] != -1; i++) {
-            if (*it == supportedExtensions[i]) {
-                found = true;
-                break;
-            }
-        }
-        if (!found) {
-            debugs(83, 5, "Extension " << *it <<  " does not supported!");
-            return false;
-        }
+
+    STACK_OF(SSL_CIPHER) *cipher_stack = SSL_get_ciphers(ssl);
+    const int supportedCipherCount = sk_SSL_CIPHER_num(cipher_stack);
+    for (int idx = 0; idx < supportedCipherCount && ciphersToFind > 0; ++idx) {
+        const SSL_CIPHER *cipher = sk_SSL_CIPHER_value(cipher_stack, idx);
+        const auto id = SSL_CIPHER_get_id(cipher) & 0x00FFFF;
+        if (wireCiphers.find(id) != wireCiphers.end() && (!scsvCipher || id != scsvCipher))
+            --ciphersToFind;
     }
 
-    SSL3_BUFFER *wb=&(ssl->s3->wbuf);
-    if (wb->len < (size_t)helloMessage.length())
+    if (ciphersToFind > 0 && scsvCipher && wireCiphers.find(scsvCipher) != wireCiphers.end())
+        --ciphersToFind;
+
+    if (ciphersToFind > 0) {
+        // TODO: Add slowlyReportUnsupportedCiphers() to slowly find and report each of them
+        debugs(83, 5, "Client Hello Data has " << ciphersToFind << " ciphers that we do not support!");
         return false;
+    }
 
     debugs(83, 5, "OpenSSL SSL struct will be adjusted to mimic client hello data!");