]> git.ipfire.org Git - thirdparty/squid.git/commitdiff
SslBump: Support parsing GREASEd (and future) TLS handshakes (#663)
authorChristos Tsantilas <christos@chtsanti.net>
Fri, 12 Jun 2020 23:32:16 +0000 (23:32 +0000)
committerSquid Anubis <squid-anubis@squid-cache.org>
Sat, 13 Jun 2020 06:21:57 +0000 (06:21 +0000)
A peeking or staring Squid aborted TLS connections containing a GREASE
version in the supported_versions handshake extension (e.g., 0x3a3a).
Here is a sample cache.log error (debug_options ALL,1 83,2):

```
parseTlsHandshake: ... check failed: vMajor == 3
    exception location: Handshake.cc(119) ParseProtocolVersion
```

The same problem would apply to some other "unsupported" (and currently
non-existent) TLS versions (e.g., 0x0400). The growing popularity of
GREASE values exposed the bug (just like RFC 8710 was designed to do).

Squid now ignores all unsupported-by-Squid TLS versions in handshake
extensions. Squid still requires a supported TLS version for
framing-related handshake fields -- no changes there.

It is difficult to define "supported" in this context precisely because
a peeking Squid only observes the TLS handshake. Our handshake parser
may report the following versions: SSL v2.0, SSL v3.0, and TLS v1.x
(with x >= 0). This logic allows us to safely parse the handshake (no
framing errors) and continue to make version-based decisions like
disabling OpenSSL TLS v1.3 support when the agents are negotiating TLS
v1.3+ (master cd29a42). Also, access logs benefit from this "support"
going beyond TLS v1.2 even though Squid cannot bump most TLS v1.3+
handshakes.

Squid was and still is compliant with the "MUST NOT treat GREASE values
differently from any unknown value" requirement of RFC 8710.

Also added source code comments to mark other places where unsupported
(for some definition of "support") TLS values, including GREASE values
may appear.

Folks studying debugging logs (where GREASE values may appear) will
probably either recognize their easy-to-remember 0x?A?A pattern or
not really know/care about RFC 8710 and its special values.

This is a Measurement Factory project.

src/security/Handshake.cc

index 3db9cfec73d13fe82236c4295be6bb13c026099d..f045b82c3291cffcf69ac78353f53911a3b699ac 100644 (file)
@@ -9,6 +9,7 @@
 /* DEBUG: section 83    SSL-Bump Server/Peer negotiation */
 
 #include "squid.h"
+#include "sbuf/Stream.h"
 #include "security/Handshake.h"
 #if USE_OPENSSL
 #include "ssl/support.h"
@@ -104,25 +105,52 @@ public:
 typedef std::unordered_set<Extension::Type> Extensions;
 static Extensions SupportedExtensions();
 
-} // namespace Security
-
 /// parse TLS ProtocolVersion (uint16) and convert it to AnyP::ProtocolVersion
+/// \retval PROTO_NONE for unsupported values (in relaxed mode)
 static AnyP::ProtocolVersion
-ParseProtocolVersion(Parser::BinaryTokenizer &tk, const char *contextLabel = ".version")
+ParseProtocolVersionBase(Parser::BinaryTokenizer &tk, const char *contextLabel, const bool beStrict)
 {
     Parser::BinaryTokenizerContext context(tk, contextLabel);
     uint8_t vMajor = tk.uint8(".major");
     uint8_t vMinor = tk.uint8(".minor");
+
     if (vMajor == 0 && vMinor == 2)
         return AnyP::ProtocolVersion(AnyP::PROTO_SSL, 2, 0);
 
-    Must(vMajor == 3);
-    if (vMinor == 0)
-        return AnyP::ProtocolVersion(AnyP::PROTO_SSL, 3, 0);
+    if (vMajor == 3) {
+        if (vMinor == 0)
+            return AnyP::ProtocolVersion(AnyP::PROTO_SSL, 3, 0);
+        return AnyP::ProtocolVersion(AnyP::PROTO_TLS, 1, (vMinor - 1));
+    }
+
+    /* handle unsupported versions */
+
+    const uint16_t vRaw = (vMajor << 8) | vMinor;
+    debugs(83, 7, "unsupported: " << asHex(vRaw));
+    if (beStrict)
+        throw TextException(ToSBuf("unsupported TLS version: ", asHex(vRaw)), Here());
+    // else hide unsupported version details from the caller behind PROTO_NONE
+    return AnyP::ProtocolVersion();
+}
+
+/// parse a framing-related TLS ProtocolVersion
+/// \returns a supported SSL or TLS Anyp::ProtocolVersion, never PROTO_NONE
+static AnyP::ProtocolVersion
+ParseProtocolVersion(Parser::BinaryTokenizer &tk)
+{
+    return ParseProtocolVersionBase(tk, ".version", true);
+}
 
-    return AnyP::ProtocolVersion(AnyP::PROTO_TLS, 1, (vMinor - 1));
+/// parse a framing-unrelated TLS ProtocolVersion
+/// \retval PROTO_NONE for unsupported values
+static AnyP::ProtocolVersion
+ParseOptionalProtocolVersion(Parser::BinaryTokenizer &tk, const char *contextLabel)
+{
+    return ParseProtocolVersionBase(tk, contextLabel, false);
 }
 
+} // namespace Security
+
 Security::TLSPlaintext::TLSPlaintext(Parser::BinaryTokenizer &tk)
 {
     Parser::BinaryTokenizerContext context(tk, "TLSPlaintext");
@@ -431,6 +459,8 @@ Security::HandshakeParser::parseExtensions(const SBuf &raw)
             break;
         case 16: { // Application-Layer Protocol Negotiation Extension, RFC 7301
             Parser::BinaryTokenizer tkAPN(extension.data);
+            // Store the entire protocol list, including unsupported-by-Squid
+            // values (if any). We have to use all when peeking at the server.
             details->tlsAppLayerProtoNeg = tkAPN.pstring16("APN");
             break;
         }
@@ -441,8 +471,9 @@ Security::HandshakeParser::parseExtensions(const SBuf &raw)
         case 43: // supported_versions extension; RFC 8446
             parseSupportedVersionsExtension(extension.data);
             break;
-        case 13172: // Next Protocol Negotiation Extension (expired draft?)
         default:
+            // other extensions, including those that Squid does not support, do
+            // not require special handling here, but see unsupportedExtensions
             break;
         }
     }
@@ -455,7 +486,7 @@ Security::HandshakeParser::parseCiphers(const SBuf &raw)
     Parser::BinaryTokenizer tk(raw);
     while (!tk.atEnd()) {
         const uint16_t cipher = tk.uint16("cipher");
-        details->ciphers.insert(cipher);
+        details->ciphers.insert(cipher); // including Squid-unsupported ones
     }
 }
 
@@ -473,7 +504,7 @@ Security::HandshakeParser::parseV23Ciphers(const SBuf &raw)
         const uint8_t prefix = tk.uint8("prefix");
         const uint16_t cipher = tk.uint16("cipher");
         if (prefix == 0)
-            details->ciphers.insert(cipher);
+            details->ciphers.insert(cipher); // including Squid-unsupported ones
     }
 }
 
@@ -486,6 +517,7 @@ Security::HandshakeParser::parseServerHelloHandshakeMessage(const SBuf &raw)
     details->tlsSupportedVersion = ParseProtocolVersion(tk);
     tk.skip(HelloRandomSize, ".random");
     details->sessionId = tk.pstring8(".session_id");
+    // cipherSuite may be unsupported by a peeking Squid
     details->ciphers.insert(tk.uint16(".cipher_suite"));
     details->compressionSupported = tk.uint8(".compression_method") != 0; // not null
     if (!tk.atEnd()) // extensions present
@@ -554,12 +586,15 @@ Security::HandshakeParser::parseSupportedVersionsExtension(const SBuf &extension
         Parser::BinaryTokenizer tkList(extensionData);
         Parser::BinaryTokenizer tkVersions(tkList.pstring8("SupportedVersions"));
         while (!tkVersions.atEnd()) {
-            const auto version = ParseProtocolVersion(tkVersions, "supported_version");
+            const auto version = ParseOptionalProtocolVersion(tkVersions, "supported_version");
+            // ignore values unsupported by Squid,represented by a falsy version
+            if (!version)
+                continue;
             if (!supportedVersionMax || TlsVersionEarlierThan(supportedVersionMax, version))
                 supportedVersionMax = version;
         }
 
-        // ignore empty supported_versions
+        // ignore empty and ignored-values-only supported_versions
         if (!supportedVersionMax)
             return;
 
@@ -569,7 +604,11 @@ Security::HandshakeParser::parseSupportedVersionsExtension(const SBuf &extension
     } else {
         assert(messageSource == fromServer);
         Parser::BinaryTokenizer tkVersion(extensionData);
-        const auto version = ParseProtocolVersion(tkVersion, "selected_version");
+        const auto version = ParseOptionalProtocolVersion(tkVersion, "selected_version");
+        // Ignore values unsupported by Squid. There should not be any until we
+        // start seeing TLS v2+, but they do not affect TLS framing anyway.
+        if (!version)
+            return;
         // RFC 8446 Section 4.2.1:
         // A server which negotiates a version of TLS prior to TLS 1.3 [...]
         // MUST NOT send the "supported_versions" extension.