[release/6.0] add support for parsing Unified TLS hello (#68425)
authorTomas Weinfurt <tweinfurt@yahoo.com>
Wed, 4 May 2022 17:44:36 +0000 (10:44 -0700)
committerGitHub <noreply@github.com>
Wed, 4 May 2022 17:44:36 +0000 (10:44 -0700)
* add support for parsing Unified TLS hello

* feedback from review

* update comment

src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Implementation.cs
src/libraries/System.Net.Security/src/System/Net/Security/TlsFrameHelper.cs

index add3a7ddfec47ffabf9575b996317ea84e0f74f9..47342bea906af0dd3b72d8ad6b1636aef17821bb 100644 (file)
@@ -43,6 +43,8 @@ namespace System.Net.Security
         private const int FrameOverhead = 64;
         private const int ReadBufferSize = 4096 * 4 + FrameOverhead;         // We read in 16K chunks + headers.
         private const int InitialHandshakeBufferSize = 4096 + FrameOverhead; // try to fit at least 4K ServerCertificate
+        private const int HandshakeTypeOffsetSsl2 = 2;                       // Offset of HelloType in Sslv2 and Unified frames
+        private const int HandshakeTypeOffsetTls = 5;                        // Offset of HelloType in Sslv3 and TLS frames
         private ArrayBuffer _handshakeBuffer;
         private bool _receivedEOF;
 
@@ -475,6 +477,7 @@ namespace System.Net.Security
 #pragma warning disable 0618
                 _lastFrame.Header.Version = SslProtocols.Ssl2;
 #pragma warning restore 0618
+                _lastFrame.Header.Type = TlsContentType.Handshake;  // Implied. We only call this during handshake and SSL2 does not have framing layer.
                 _lastFrame.Header.Length = GetFrameSize(_handshakeBuffer.ActiveReadOnlySpan) - TlsFrameHelper.HeaderSize;
             }
             else
@@ -506,9 +509,11 @@ namespace System.Net.Security
                     }
                     break;
                 case TlsContentType.Handshake:
-                    if (!_isRenego && _handshakeBuffer.ActiveReadOnlySpan[TlsFrameHelper.HeaderSize] == (byte)TlsHandshakeType.ClientHello &&
-                        (_sslAuthenticationOptions!.ServerCertSelectionDelegate != null ||
-                        _sslAuthenticationOptions!.ServerOptionDelegate != null))
+                    byte handshakeType = _handshakeBuffer.ActiveReadOnlySpan[_framing == Framing.SinceSSL3 ? HandshakeTypeOffsetTls : HandshakeTypeOffsetSsl2];
+
+                    if (!_isRenego &&
+                        (_sslAuthenticationOptions!.ServerCertSelectionDelegate != null || _sslAuthenticationOptions!.ServerOptionDelegate != null) &&
+                        (handshakeType == (byte)TlsHandshakeType.ClientHello))
                     {
                         TlsFrameHelper.ProcessingOptions options = NetEventSource.Log.IsEnabled() ?
                                                                     TlsFrameHelper.ProcessingOptions.All :
index 8f5b5ed2a07c53ef7dbd92d133efe7eb5e6cc146..0057b757d632a876343b48c67759c1c3c02ca1d6 100644 (file)
@@ -173,33 +173,41 @@ namespace System.Net.Security
 
         public static bool TryGetFrameHeader(ReadOnlySpan<byte> frame, ref TlsFrameHeader header)
         {
-            bool result = frame.Length > 4;
-
-            if (frame.Length >= 1)
+            header.Type = (TlsContentType)(frame.Length >= 1 ? frame[0] : 0);
+            if (frame.Length < TlsFrameHelper.HeaderSize)
             {
-                header.Type = (TlsContentType)frame[0];
-
-                if (frame.Length >= 3)
-                {
-                    // SSLv3, TLS or later
-                    if (frame[1] == 3)
-                    {
-                        if (frame.Length > 4)
-                        {
-                            header.Length = ((frame[3] << 8) | frame[4]);
-                        }
+                header.Length = -1;
+                header.Version = SslProtocols.None;
+                return false;
+            }
 
-                        header.Version = TlsMinorVersionToProtocol(frame[2]);
-                    }
-                    else
-                    {
-                        header.Length = -1;
-                        header.Version = SslProtocols.None;
-                    }
-                }
+            // SSLv3, TLS or later
+            if (frame[1] == ProtocolVersionTlsMajorValue)
+            {
+                header.Length = ((frame[3] << 8) | frame[4]);
+                header.Version = TlsMinorVersionToProtocol(frame[2]);
+            }
+            // Sslv2 or Unified
+            else if (frame[2] == (byte)TlsHandshakeType.ClientHello &&
+                     frame[3] == ProtocolVersionTlsMajorValue) // SSL3 or above
+            {
+                header.Length = (frame[0] & 0x80) != 0 ?
+                                    (((frame[0] & 0x7f) << 8) | frame[1]) + 2 : // two bytes
+                                    (((frame[0] & 0x3f) << 8) | frame[1]) + 3;  // three bytes
+#pragma warning disable CS0618 // Ssl2 and Ssl3 are obsolete
+                header.Version = SslProtocols.Ssl2;
+#pragma warning restore CS0618
+                header.Type = TlsContentType.Handshake;
+            }
+            else
+            {
+                // neither looks like TLS nor Ssl2 Hello
+                header.Length = -1;
+                header.Version = SslProtocols.None;
+                return false;
             }
 
-            return result;
+            return true;
         }
 
         // Returns frame size e.g. header + content
@@ -252,6 +260,18 @@ namespace System.Net.Security
             }
 
             info.HandshakeType = (TlsHandshakeType)frame[HandshakeTypeOffset];
+#pragma warning disable CS0618 // Ssl2 and Ssl3 are obsolete
+            if (info.Header.Version == SslProtocols.Ssl2)
+            {
+                // This is safe. We would not get here if the length is too small.
+                info.SupportedVersions |= TlsMinorVersionToProtocol(frame[4]);
+                // We only recognize Unified ClientHello at the moment.
+                // This is needed to trigger certificate selection callback in SslStream.
+                info.HandshakeType = TlsHandshakeType.ClientHello;
+                // There is no more parsing for old protocols.
+                return true;
+            }
+#pragma warning restore CS0618
 
             // Check if we have full frame.
             bool isComplete = frame.Length >= HeaderSize + info.Header.Length;
@@ -404,10 +424,10 @@ namespace System.Net.Security
             // Skip compression methods (max size 2^8-1 => size fits in 1 byte)
             p = SkipOpaqueType1(p);
 
-            // is invalid structure or no extensions?
+            // no extensions
             if (p.IsEmpty)
             {
-                return false;
+                return true;
             }
 
             // client_hello_extension_list (max size 2^16-1 => size fits in 2 bytes)