< Summary

Line coverage
16%
Covered lines: 63
Uncovered lines: 312
Coverable lines: 375
Total lines: 859
Line coverage: 16.8%
Branch coverage
13%
Covered branches: 23
Total branches: 166
Branch coverage: 13.8%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Net.Security/src/System/Net/Security/TlsFrameHelper.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Buffers.Binary;
 5using System.Globalization;
 6using System.Security.Authentication;
 7using System.Text;
 8
 9namespace System.Net.Security
 10{
 11    // SSL3/TLS protocol frames definitions.
 12    internal enum TlsContentType : byte
 13    {
 14        ChangeCipherSpec = 20,
 15        Alert = 21,
 16        Handshake = 22,
 17        AppData = 23
 18    }
 19
 20    internal enum TlsHandshakeType : byte
 21    {
 22        HelloRequest = 0,
 23        ClientHello = 1,
 24        ServerHello = 2,
 25        NewSessionTicket = 4,
 26        EndOfEarlyData = 5,
 27        EncryptedExtensions = 8,
 28        Certificate = 11,
 29        ServerKeyExchange = 12,
 30        CertificateRequest = 13,
 31        ServerHelloDone = 14,
 32        CertificateVerify = 15,
 33        ClientKeyExchange = 16,
 34        Finished = 20,
 35        KeyUpdate = 24,
 36        MessageHash = 254
 37    }
 38
 39    internal enum TlsAlertLevel : byte
 40    {
 41        Warning = 1,
 42        Fatal = 2,
 43    }
 44
 45    internal enum TlsAlertDescription : byte
 46    {
 47        CloseNotify = 0, // warning
 48        UnexpectedMessage = 10, // error
 49        BadRecordMac = 20, // error
 50        DecryptionFailed = 21, // reserved
 51        RecordOverflow = 22, // error
 52        DecompressionFail = 30, // error
 53        HandshakeFailure = 40, // error
 54        BadCertificate = 42, // warning or error
 55        UnsupportedCert = 43, // warning or error
 56        CertificateRevoked = 44, // warning or error
 57        CertificateExpired = 45, // warning or error
 58        CertificateUnknown = 46, // warning or error
 59        IllegalParameter = 47, // error
 60        UnknownCA = 48, // error
 61        AccessDenied = 49, // error
 62        DecodeError = 50, // error
 63        DecryptError = 51, // error
 64        ExportRestriction = 60, // reserved
 65        ProtocolVersion = 70, // error
 66        InsufficientSecurity = 71, // error
 67        InternalError = 80, // error
 68        UserCanceled = 90, // warning or error
 69        NoRenegotiation = 100, // warning
 70        UnsupportedExt = 110, // error
 71    }
 72
 73    internal enum ExtensionType : ushort
 74    {
 75        ServerName = 0,
 76        MaximumFragmentLength = 1,
 77        ClientCertificateUrl = 2,
 78        TrustedCaKeys = 3,
 79        TruncatedHmac = 4,
 80        CertificateStatusRequest = 5,
 81        ApplicationProtocols = 16,
 82        SupportedVersions = 43
 83    }
 84
 85    internal struct TlsFrameHeader
 86    {
 87        public TlsContentType Type;
 88        public SslProtocols Version;
 89        public int Length;
 90
 91        public override string ToString() => $"{Version}:{Type}[{Length}]";
 92    }
 93
 94    internal static class TlsFrameHelper
 95    {
 96        public const int HeaderSize = 5;
 97
 98        [Flags]
 99        public enum ProcessingOptions
 100        {
 101            ServerName = 0x1,
 102            ApplicationProtocol = 0x2,
 103            Versions = 0x4,
 104            RawApplicationProtocol = 0x8,
 105        }
 106
 107        [Flags]
 108        public enum ApplicationProtocolInfo
 109        {
 110            None = 0,
 111            Http11 = 1,
 112            Http2 = 2,
 113            Other = 128
 114        }
 115
 116        public struct TlsFrameInfo
 117        {
 118            public TlsFrameHeader Header;
 119            public TlsHandshakeType HandshakeType;
 120            public SslProtocols SupportedVersions;
 121            public string TargetName;
 122            public ApplicationProtocolInfo ApplicationProtocols;
 123            public TlsAlertDescription AlertDescription;
 124            public byte[]? RawApplicationProtocols;
 125
 126            public override string ToString()
 0127            {
 0128                if (Header.Type == TlsContentType.Handshake)
 0129                {
 0130                    if (HandshakeType == TlsHandshakeType.ClientHello)
 0131                    {
 0132                        return $"{Header.Version}:{HandshakeType}[{Header.Length}] TargetName='{TargetName}' SupportedVe
 133                    }
 0134                    else if (HandshakeType == TlsHandshakeType.ServerHello)
 0135                    {
 0136                        return $"{Header.Version}:{HandshakeType}[{Header.Length}] SupportedVersion='{SupportedVersions}
 137                    }
 138                    else
 0139                    {
 0140                        return $"{Header.Version}:{HandshakeType}[{Header.Length}] SupportedVersion='{SupportedVersions}
 141                    }
 142                }
 143                else
 0144                {
 0145                    return $"{Header.Version}:{Header.Type}[{Header.Length}]";
 146                }
 0147            }
 148        }
 149
 150        public delegate bool HelloExtensionCallback(ref TlsFrameInfo info, ExtensionType type, ReadOnlySpan<byte> extens
 151
 0152        private static readonly byte[] s_protocolMismatch13 = new byte[] { (byte)TlsContentType.Alert, 3, 4, 0, 2, 2, 70
 0153        private static readonly byte[] s_protocolMismatch12 = new byte[] { (byte)TlsContentType.Alert, 3, 3, 0, 2, 2, 70
 0154        private static readonly byte[] s_protocolMismatch11 = new byte[] { (byte)TlsContentType.Alert, 3, 2, 0, 2, 2, 70
 0155        private static readonly byte[] s_protocolMismatch10 = new byte[] { (byte)TlsContentType.Alert, 3, 1, 0, 2, 2, 70
 0156        private static readonly byte[] s_protocolMismatch30 = new byte[] { (byte)TlsContentType.Alert, 3, 0, 0, 2, 2, 40
 157
 158        private const int UInt24Size = 3;
 159        private const int RandomSize = 32;
 160        private const int MaxHostNameLength = 255;
 161        private const int ProtocolVersionMajorOffset = 0;
 162        private const int ProtocolVersionMinorOffset = 1;
 163        private const int ProtocolVersionSize = 2;
 164        private const int ProtocolVersionTlsMajorValue = 3;
 165
 166        // Per spec "AllowUnassigned flag MUST be set". See comment above DecodeString() for more details.
 0167        private static readonly IdnMapping s_idnMapping = new IdnMapping() { AllowUnassigned = true };
 0168        private static readonly Encoding s_encoding = Encoding.GetEncoding("utf-8", new EncoderExceptionFallback(), new 
 169
 170        public static bool TryGetFrameHeader(ReadOnlySpan<byte> frame, ref TlsFrameHeader header)
 37171        {
 37172            if (frame.Length < HeaderSize)
 0173            {
 0174                header.Length = -1;
 0175                return false;
 176            }
 177
 37178            header.Type = (TlsContentType)frame[0];
 179
 180            // SSLv3, TLS or later
 37181            if (frame[1] == 3)
 26182            {
 26183                header.Length = ((frame[3] << 8) | frame[4]) + HeaderSize;
 26184                header.Version = TlsMinorVersionToProtocol(frame[2]);
 26185            }
 11186            else if (frame[2] == (byte)TlsHandshakeType.ClientHello &&
 11187                     frame[3] == 3) // SSL3 or above
 3188            {
 189                int length;
 3190                if ((frame[0] & 0x80) != 0)
 1191                {
 192                    // Two bytes
 1193                    length = (((frame[0] & 0x7f) << 8) | frame[1]) + 2;
 1194                }
 195                else
 2196                {
 197                    // Three bytes
 2198                    length = (((frame[0] & 0x3f) << 8) | frame[1]) + 3;
 2199                }
 200
 201
 202                // max frame for SSLv2 is 32767.
 203                // However, we expect something reasonable for initial HELLO
 204                // We don't have enough logic to verify full validity,
 205                // the limits below are guesses.
 206#pragma warning disable CS0618 // Ssl2 and Ssl3 are obsolete
 3207                header.Version = SslProtocols.Ssl2;
 208#pragma warning restore CS0618
 3209                header.Length = length;
 3210                header.Type = TlsContentType.Handshake;
 3211            }
 212            else
 8213            {
 214                // unknown format
 8215                header.Length = -1;
 8216                return false;
 217            }
 218
 29219            return true;
 37220        }
 221
 222        // This function will try to parse TLS hello frame and fill details in provided info structure.
 223        // If frame was fully processed without any error, function returns true.
 224        // Otherwise, it returns false and info may have partial data.
 225        // It is OK to call it again if more data becomes available.
 226        // It is also possible to limit what information is processed.
 227        // If callback delegate is provided, it will be called on ALL extensions.
 228        public static bool TryGetFrameInfo(ReadOnlySpan<byte> frame, ref TlsFrameInfo info, ProcessingOptions options = 
 6229        {
 230            const int HandshakeTypeOffset = 5;
 6231            if (frame.Length < HeaderSize)
 0232            {
 0233                return false;
 234            }
 235
 6236            if (!TryGetFrameHeader(frame, ref info.Header))
 0237            {
 238                // Unknown or malformed frame format.
 0239                return false;
 240            }
 241
 6242            info.SupportedVersions = info.Header.Version;
 243
 6244            if (info.Header.Type == TlsContentType.Alert)
 6245            {
 6246                TlsAlertLevel level = default;
 6247                TlsAlertDescription description = default;
 6248                if (TryGetAlertInfo(frame, ref level, ref description))
 2249                {
 2250                    info.AlertDescription = description;
 2251                    return true;
 252                }
 253
 4254                return false;
 255            }
 256
 0257            if (info.Header.Type != TlsContentType.Handshake || frame.Length <= HandshakeTypeOffset)
 0258            {
 0259                return false;
 260            }
 261
 0262            info.HandshakeType = (TlsHandshakeType)frame[HandshakeTypeOffset];
 263#pragma warning disable CS0618 // Ssl2 and Ssl3 are obsolete
 0264            if (info.Header.Version == SslProtocols.Ssl2)
 0265            {
 266                // This is safe. We would not get here if the length is too small.
 0267                info.SupportedVersions |= TlsMinorVersionToProtocol(frame[4]);
 268                // We only recognize Unified ClientHello at the moment.
 269                // This is needed to trigger certificate selection callback in SslStream.
 0270                info.HandshakeType = TlsHandshakeType.ClientHello;
 271                // There is no more parsing for old protocols.
 0272                return true;
 273            }
 274#pragma warning restore CS0618
 275
 276            // Check if we have full frame.
 0277            bool isComplete = frame.Length >= info.Header.Length;
 278
 279#pragma warning disable SYSLIB0039 // TLS 1.0 and 1.1 are obsolete
 0280            if (((int)info.Header.Version >= (int)SslProtocols.Tls) &&
 0281#pragma warning restore SYSLIB0039
 0282                (info.HandshakeType == TlsHandshakeType.ClientHello || info.HandshakeType == TlsHandshakeType.ServerHell
 0283            {
 0284                if (!TryParseHelloFrame(frame.Slice(HeaderSize), ref info, options, callback))
 0285                {
 0286                    isComplete = false;
 0287                }
 0288            }
 289
 0290            return isComplete;
 6291        }
 292
 293        // This is similar to TryGetFrameInfo, but it will only process SNI.
 294        // It returns TargetName as string or NULL if SNI is missing or parsing error happened.
 295        public static string? GetServerName(ReadOnlySpan<byte> frame)
 296        {
 297            TlsFrameInfo info = default;
 298            if (!TryGetFrameInfo(frame, ref info, ProcessingOptions.ServerName))
 299            {
 300                return null;
 301            }
 302
 303            return info.TargetName;
 304        }
 305
 306        // This function will parse the TLS Alert message, and return the alert level and description.
 307        public static bool TryGetAlertInfo(ReadOnlySpan<byte> frame, ref TlsAlertLevel level, ref TlsAlertDescription de
 6308        {
 6309            if (frame.Length < 7 || frame[0] != (byte)TlsContentType.Alert)
 4310            {
 4311                return false;
 312            }
 313
 2314            level = (TlsAlertLevel)frame[5];
 2315            description = (TlsAlertDescription)frame[6];
 316
 2317            return true;
 6318        }
 319
 320        private static byte[] CreateProtocolVersionAlert(SslProtocols version) =>
 0321            version switch
 0322            {
 0323                SslProtocols.Tls13 => s_protocolMismatch13,
 0324                SslProtocols.Tls12 => s_protocolMismatch12,
 0325#pragma warning disable SYSLIB0039 // TLS 1.0 and 1.1 are obsolete
 0326                SslProtocols.Tls11 => s_protocolMismatch11,
 0327                SslProtocols.Tls => s_protocolMismatch10,
 0328#pragma warning restore SYSLIB0039
 0329#pragma warning disable 0618
 0330                SslProtocols.Ssl3 => s_protocolMismatch30,
 0331#pragma warning restore 0618
 0332                _ => Array.Empty<byte>(),
 0333            };
 334
 335        public static byte[] CreateAlertFrame(SslProtocols version, TlsAlertDescription reason)
 0336        {
 0337            if (reason == TlsAlertDescription.ProtocolVersion)
 0338            {
 0339                return CreateProtocolVersionAlert(version);
 340            }
 341#pragma warning disable SYSLIB0039 // TLS 1.0 and 1.1 are obsolete
 0342            else if ((int)version >= (int)SslProtocols.Tls)
 343#pragma warning restore SYSLIB0039
 0344            {
 345                // Create TLS1.2 alert
 0346                byte[] buffer = new byte[] { (byte)TlsContentType.Alert, 3, 3, 0, 2, 2, (byte)reason };
 0347                switch (version)
 348                {
 349                    case SslProtocols.Tls13:
 0350                        buffer[2] = 4;
 0351                        break;
 352#pragma warning disable SYSLIB0039 // TLS 1.0 and 1.1 are obsolete
 353                    case SslProtocols.Tls11:
 0354                        buffer[2] = 2;
 0355                        break;
 356                    case SslProtocols.Tls:
 0357                        buffer[2] = 1;
 0358                        break;
 359#pragma warning restore SYSLIB0039
 360                }
 361
 0362                return buffer;
 363            }
 364
 0365            return Array.Empty<byte>();
 0366        }
 367
 368        private static bool TryParseHelloFrame(ReadOnlySpan<byte> sslHandshake, ref TlsFrameInfo info, ProcessingOptions
 0369        {
 370            // https://tools.ietf.org/html/rfc6101#section-5.6
 371            // struct {
 372            //     HandshakeType msg_type;    /* handshake type */
 373            //     uint24 length;             /* bytes in message */
 374            //     select (HandshakeType) {
 375            //         ...
 376            //         case client_hello: ClientHello;
 377            //         case server_hello: ServerHello;
 378            //         ...
 379            //     } body;
 380            // } Handshake;
 381            const int HandshakeTypeOffset = 0;
 382            const int HelloLengthOffset = HandshakeTypeOffset + sizeof(TlsHandshakeType);
 383            const int HelloOffset = HelloLengthOffset + UInt24Size;
 384
 0385            if (sslHandshake.Length < HelloOffset ||
 0386                ((TlsHandshakeType)sslHandshake[HandshakeTypeOffset] != TlsHandshakeType.ClientHello &&
 0387                 (TlsHandshakeType)sslHandshake[HandshakeTypeOffset] != TlsHandshakeType.ServerHello))
 0388            {
 0389                return false;
 390            }
 391
 0392            int helloLength = ReadUInt24BigEndian(sslHandshake.Slice(HelloLengthOffset));
 0393            ReadOnlySpan<byte> helloData = sslHandshake.Slice(HelloOffset);
 394
 0395            if (helloLength < ProtocolVersionSize || helloData.Length < helloLength)
 0396            {
 0397                return false;
 398            }
 399
 400            // ProtocolVersion may be different from frame header.
 0401            if (helloData[ProtocolVersionMajorOffset] == ProtocolVersionTlsMajorValue)
 0402            {
 0403                info.SupportedVersions |= TlsMinorVersionToProtocol(helloData[ProtocolVersionMinorOffset]);
 0404            }
 405
 0406            return (TlsHandshakeType)sslHandshake[HandshakeTypeOffset] == TlsHandshakeType.ClientHello ?
 0407                        TryParseClientHello(helloData.Slice(0, helloLength), ref info, options, callback) :
 0408                        TryParseServerHello(helloData.Slice(0, helloLength), ref info, options, callback);
 0409        }
 410
 411        private static bool TryParseClientHello(ReadOnlySpan<byte> clientHello, ref TlsFrameInfo info, ProcessingOptions
 0412        {
 413            // Basic structure: https://tools.ietf.org/html/rfc6101#section-5.6.1.2
 414            // Extended structure: https://tools.ietf.org/html/rfc3546#section-2.1
 415            // struct {
 416            //     ProtocolVersion client_version; // 2x uint8
 417            //     Random random; // 32 bytes
 418            //     SessionID session_id; // opaque type
 419            //     CipherSuite cipher_suites<2..2^16-1>; // opaque type
 420            //     CompressionMethod compression_methods<1..2^8-1>; // opaque type
 421            //     Extension client_hello_extension_list<0..2^16-1>;
 422            // } ClientHello;
 423
 0424            ReadOnlySpan<byte> p = SkipBytes(clientHello, ProtocolVersionSize + RandomSize);
 425
 426            // Skip SessionID (max size 32 => size fits in 1 byte)
 0427            p = SkipOpaqueType1(p);
 428
 429            // Skip cipher suites (max size 2^16-1 => size fits in 2 bytes)
 0430            p = SkipOpaqueType2(p);
 431
 432            // Skip compression methods (max size 2^8-1 => size fits in 1 byte)
 0433            p = SkipOpaqueType1(p);
 434
 435            // no extensions
 0436            if (p.IsEmpty)
 0437            {
 0438                return true;
 439            }
 440
 0441            if (p.Length < sizeof(ushort))
 0442            {
 0443                return false;
 444            }
 445
 446            // client_hello_extension_list (max size 2^16-1 => size fits in 2 bytes)
 0447            int extensionListLength = BinaryPrimitives.ReadUInt16BigEndian(p);
 0448            p = SkipBytes(p, sizeof(ushort));
 0449            if (extensionListLength != p.Length)
 0450            {
 0451                return false;
 452            }
 453
 0454            return TryParseHelloExtensions(p, ref info, options, callback);
 0455        }
 456
 457        private static bool TryParseServerHello(ReadOnlySpan<byte> serverHello, ref TlsFrameInfo info, ProcessingOptions
 0458        {
 459            // Basic structure: https://tools.ietf.org/html/rfc6101#section-5.6.1.3
 460            // Extended structure: https://tools.ietf.org/html/rfc3546#section-2.2
 461            // struct {
 462            //   ProtocolVersion server_version;
 463            //   Random random;
 464            //   SessionID session_id;
 465            //   CipherSuite cipher_suite;
 466            //   CompressionMethod compression_method;
 467            //   Extension server_hello_extension_list<0..2^16-1>;
 468            // }
 469            // ServerHello;
 470            const int CipherSuiteLength = 2;
 471            const int CompressionMethodLength = 1;
 472
 0473            ReadOnlySpan<byte> p = SkipBytes(serverHello, ProtocolVersionSize + RandomSize);
 474            // Skip SessionID (max size 32 => size fits in 1 byte)
 0475            p = SkipOpaqueType1(p);
 0476            p = SkipBytes(p, CipherSuiteLength + CompressionMethodLength);
 477
 478            // is invalid structure or no extensions?
 0479            if (p.IsEmpty)
 0480            {
 0481                return false;
 482            }
 483
 0484            if (p.Length < sizeof(ushort))
 0485            {
 0486                return false;
 487            }
 488
 489            // client_hello_extension_list (max size 2^16-1 => size fits in 2 bytes)
 0490            int extensionListLength = BinaryPrimitives.ReadUInt16BigEndian(p);
 0491            p = SkipBytes(p, sizeof(ushort));
 0492            if (extensionListLength != p.Length)
 0493            {
 0494                return false;
 495            }
 496
 0497            return TryParseHelloExtensions(p, ref info, options, callback);
 0498        }
 499
 500        // This is common for ClientHello and ServerHello.
 501        private static bool TryParseHelloExtensions(ReadOnlySpan<byte> extensions, ref TlsFrameInfo info, ProcessingOpti
 0502        {
 503            const int ExtensionHeader = 4;
 0504            bool isComplete = true;
 505
 0506            while (extensions.Length >= ExtensionHeader)
 0507            {
 0508                ExtensionType extensionType = (ExtensionType)BinaryPrimitives.ReadUInt16BigEndian(extensions);
 0509                extensions = SkipBytes(extensions, sizeof(ushort));
 510
 0511                ushort extensionLength = BinaryPrimitives.ReadUInt16BigEndian(extensions);
 0512                extensions = SkipBytes(extensions, sizeof(ushort));
 0513                if (extensions.Length < extensionLength)
 0514                {
 0515                    isComplete = false;
 0516                    break;
 517                }
 518
 0519                ReadOnlySpan<byte> extensionData = extensions.Slice(0, extensionLength);
 520
 0521                if (extensionType == ExtensionType.ServerName && (options & ProcessingOptions.ServerName) != 0)
 0522                {
 0523                    if (!TryGetSniFromServerNameList(extensionData, out string? sni))
 0524                    {
 0525                        return false;
 526                    }
 527
 0528                    info.TargetName = sni!;
 0529                }
 0530                else if (extensionType == ExtensionType.SupportedVersions && (options & ProcessingOptions.Versions) != 0
 0531                {
 0532                    if (!TryGetSupportedVersionsFromExtension(extensionData, out SslProtocols versions))
 0533                    {
 0534                        return false;
 535                    }
 536
 0537                    info.SupportedVersions |= versions;
 0538                }
 0539                else if (extensionType == ExtensionType.ApplicationProtocols &&
 0540                          (options & (ProcessingOptions.ApplicationProtocol | ProcessingOptions.RawApplicationProtocol))
 0541                {
 0542                    if (!TryGetApplicationProtocolsFromExtension(extensionData, out ApplicationProtocolInfo alpn))
 0543                    {
 0544                        return false;
 545                    }
 546
 0547                    info.ApplicationProtocols |= alpn;
 548
 549                    // Process RAW options only if explicitly set since that will allocate....
 0550                    if (options.HasFlag(ProcessingOptions.RawApplicationProtocol))
 0551                    {
 552                        // Skip ALPN extension Length. We have that in span.
 0553                        info.RawApplicationProtocols = extensionData.Slice(sizeof(short)).ToArray();
 0554                    }
 0555                }
 556
 0557                callback?.Invoke(ref info, extensionType, extensionData);
 0558                extensions = extensions.Slice(extensionLength);
 0559            }
 560
 0561            return isComplete;
 0562        }
 563
 564        private static bool TryGetSniFromServerNameList(ReadOnlySpan<byte> serverNameListExtension, out string? sni)
 0565        {
 566            // https://tools.ietf.org/html/rfc3546#section-3.1
 567            // struct {
 568            //     ServerName server_name_list<1..2^16-1>
 569            // } ServerNameList;
 570            // ServerNameList is an opaque type (length of sufficient size for max data length is prepended)
 571            const int ServerNameListOffset = sizeof(ushort);
 0572            sni = null;
 573
 0574            if (serverNameListExtension.Length < ServerNameListOffset)
 0575            {
 0576                return false;
 577            }
 578
 0579            int serverNameListLength = BinaryPrimitives.ReadUInt16BigEndian(serverNameListExtension);
 0580            ReadOnlySpan<byte> serverNameList = serverNameListExtension.Slice(ServerNameListOffset);
 581
 0582            if (serverNameListLength != serverNameList.Length)
 0583            {
 0584                return false;
 585            }
 586
 0587            ReadOnlySpan<byte> serverName = serverNameList.Slice(0, serverNameListLength);
 588
 0589            sni = GetSniFromServerName(serverName, out bool invalid);
 0590            return !invalid;
 0591        }
 592
 593        private static string? GetSniFromServerName(ReadOnlySpan<byte> serverName, out bool invalid)
 0594        {
 595            // https://tools.ietf.org/html/rfc3546#section-3.1
 596            // struct {
 597            //     NameType name_type;
 598            //     select (name_type) {
 599            //         case host_name: HostName;
 600            //     } name;
 601            // } ServerName;
 602            // ServerName is an opaque type (length of sufficient size for max data length is prepended)
 603            const int NameTypeOffset = 0;
 604            const int HostNameStructOffset = NameTypeOffset + sizeof(NameType);
 0605            if (serverName.Length < HostNameStructOffset)
 0606            {
 0607                invalid = true;
 0608                return null;
 609            }
 610
 611            // Following can underflow but it is ok due to equality check below
 0612            NameType nameType = (NameType)serverName[NameTypeOffset];
 0613            ReadOnlySpan<byte> hostNameStruct = serverName.Slice(HostNameStructOffset);
 0614            if (nameType != NameType.HostName)
 0615            {
 0616                invalid = true;
 0617                return null;
 618            }
 619
 0620            return GetSniFromHostNameStruct(hostNameStruct, out invalid);
 0621        }
 622
 623        private static string? GetSniFromHostNameStruct(ReadOnlySpan<byte> hostNameStruct, out bool invalid)
 0624        {
 625            // https://tools.ietf.org/html/rfc3546#section-3.1
 626            // HostName is an opaque type (length of sufficient size for max data length is prepended)
 627            const int HostNameLengthOffset = 0;
 628            const int HostNameOffset = HostNameLengthOffset + sizeof(ushort);
 629
 0630            if (hostNameStruct.Length < HostNameOffset)
 0631            {
 0632                invalid = true;
 0633                return null;
 634            }
 635
 0636            int hostNameLength = BinaryPrimitives.ReadUInt16BigEndian(hostNameStruct);
 0637            ReadOnlySpan<byte> hostName = hostNameStruct.Slice(HostNameOffset);
 0638            if (hostNameLength != hostName.Length)
 0639            {
 0640                invalid = true;
 0641                return null;
 642            }
 643
 0644            invalid = false;
 0645            return hostNameLength <= MaxHostNameLength ? DecodeString(hostName) : null;
 0646        }
 647
 648        private static bool TryGetSupportedVersionsFromExtension(ReadOnlySpan<byte> extensionData, out SslProtocols prot
 0649        {
 650            // https://tools.ietf.org/html/rfc8446#section-4.2.1
 651            // struct {
 652            // select(Handshake.msg_type) {
 653            //  case client_hello:
 654            //    ProtocolVersion versions<2..254 >;
 655            //
 656            //  case server_hello: /* and HelloRetryRequest */
 657            //    ProtocolVersion selected_version;
 658            // };
 659            const int VersionListLengthOffset = 0;
 660            const int VersionListNameOffset = VersionListLengthOffset + sizeof(byte);
 661            const int VersionLength = 2;
 662
 0663            protocols = SslProtocols.None;
 664
 0665            if (extensionData.IsEmpty)
 0666            {
 0667                return false;
 668            }
 669
 0670            byte supportedVersionLength = extensionData[VersionListLengthOffset];
 0671            extensionData = extensionData.Slice(VersionListNameOffset);
 672
 0673            if (extensionData.Length != supportedVersionLength)
 0674            {
 0675                return false;
 676            }
 677
 678            // Get list of protocols we support. Ignore the rest.
 0679            while (extensionData.Length >= VersionLength)
 0680            {
 0681                if (extensionData[ProtocolVersionMajorOffset] == ProtocolVersionTlsMajorValue)
 0682                {
 0683                    protocols |= TlsMinorVersionToProtocol(extensionData[ProtocolVersionMinorOffset]);
 0684                }
 685
 0686                extensionData = extensionData.Slice(VersionLength);
 0687            }
 688
 0689            return true;
 0690        }
 691
 692        private static bool TryGetApplicationProtocolsFromExtension(ReadOnlySpan<byte> extensionData, out ApplicationPro
 0693        {
 694            // https://tools.ietf.org/html/rfc7301#section-3.1
 695            // opaque ProtocolName<1..2 ^ 8 - 1 >;
 696            //
 697            // struct {
 698            //   ProtocolName protocol_name_list<2..2^16-1>
 699            // }
 700            // ProtocolNameList;
 701            const int AlpnListLengthOffset = 0;
 702            const int AlpnListOffset = AlpnListLengthOffset + sizeof(short);
 703
 0704            alpn = ApplicationProtocolInfo.None;
 705
 0706            if (extensionData.Length < AlpnListOffset)
 0707            {
 0708                return false;
 709            }
 710
 0711            int AlpnListLength = BinaryPrimitives.ReadUInt16BigEndian(extensionData);
 0712            ReadOnlySpan<byte> alpnList = extensionData.Slice(AlpnListOffset);
 0713            if (AlpnListLength != alpnList.Length)
 0714            {
 0715                return false;
 716            }
 717
 0718            while (!alpnList.IsEmpty)
 0719            {
 0720                byte protocolLength = alpnList[0];
 0721                if (alpnList.Length < protocolLength + 1)
 0722                {
 0723                    return false;
 724                }
 725
 0726                ReadOnlySpan<byte> protocol = alpnList.Slice(1, protocolLength);
 0727                if (protocolLength == 2)
 0728                {
 0729                    if (protocol.SequenceEqual(SslApplicationProtocol.Http2.Protocol.Span))
 0730                    {
 0731                        alpn |= ApplicationProtocolInfo.Http2;
 0732                    }
 733                    else
 0734                    {
 0735                        alpn |= ApplicationProtocolInfo.Other;
 0736                    }
 0737                }
 0738                else if (protocolLength == SslApplicationProtocol.Http11.Protocol.Length &&
 0739                         protocol.SequenceEqual(SslApplicationProtocol.Http11.Protocol.Span))
 0740                {
 0741                    alpn |= ApplicationProtocolInfo.Http11;
 0742                }
 743                else
 0744                {
 0745                    alpn |= ApplicationProtocolInfo.Other;
 0746                }
 747
 0748                alpnList = alpnList.Slice(protocolLength + 1);
 0749            }
 750
 0751            return true;
 0752        }
 753
 754        private static SslProtocols TlsMinorVersionToProtocol(byte value)
 26755        {
 26756            return value switch
 26757            {
 0758                4 => SslProtocols.Tls13,
 3759                3 => SslProtocols.Tls12,
 26760#pragma warning disable SYSLIB0039 // TLS 1.0 and 1.1 are obsolete
 2761                2 => SslProtocols.Tls11,
 8762                1 => SslProtocols.Tls,
 26763#pragma warning restore SYSLIB0039
 26764#pragma warning disable 0618
 5765                0 => SslProtocols.Ssl3,
 26766#pragma warning restore 0618
 8767                _ => SslProtocols.None,
 26768            };
 26769        }
 770
 771        private static string? DecodeString(ReadOnlySpan<byte> bytes)
 0772        {
 773            // https://tools.ietf.org/html/rfc3546#section-3.1
 774            // Per spec:
 775            //   If the hostname labels contain only US-ASCII characters, then the
 776            //   client MUST ensure that labels are separated only by the byte 0x2E,
 777            //   representing the dot character U+002E (requirement 1 in section 3.1
 778            //   of [IDNA] notwithstanding). If the server needs to match the HostName
 779            //   against names that contain non-US-ASCII characters, it MUST perform
 780            //   the conversion operation described in section 4 of [IDNA], treating
 781            //   the HostName as a "query string" (i.e. the AllowUnassigned flag MUST
 782            //   be set). Note that IDNA allows labels to be separated by any of the
 783            //   Unicode characters U+002E, U+3002, U+FF0E, and U+FF61, therefore
 784            //   servers MUST accept any of these characters as a label separator.  If
 785            //   the server only needs to match the HostName against names containing
 786            //   exclusively ASCII characters, it MUST compare ASCII names case-
 787            //   insensitively.
 788
 789            string idnEncodedString;
 790            try
 0791            {
 0792                idnEncodedString = s_encoding.GetString(bytes);
 0793            }
 0794            catch (DecoderFallbackException)
 0795            {
 0796                return null;
 797            }
 798
 799            try
 0800            {
 0801                return s_idnMapping.GetUnicode(idnEncodedString);
 802            }
 0803            catch (ArgumentException)
 0804            {
 805                // client has not done IDN mapping
 0806                return idnEncodedString;
 807            }
 0808        }
 809
 810        private static int ReadUInt24BigEndian(ReadOnlySpan<byte> bytes)
 0811        {
 0812            return (bytes[0] << 16) | (bytes[1] << 8) | bytes[2];
 0813        }
 814
 815        private static ReadOnlySpan<byte> SkipBytes(ReadOnlySpan<byte> bytes, int numberOfBytesToSkip)
 0816        {
 0817            return (numberOfBytesToSkip < bytes.Length) ? bytes.Slice(numberOfBytesToSkip) : ReadOnlySpan<byte>.Empty;
 0818        }
 819
 820        // Opaque type is of structure:
 821        //   - length (minimum number of bytes to hold the max value)
 822        //   - data (length bytes)
 823        // We will only use opaque types which are of max size: 255 (length = 1) or 2^16-1 (length = 2).
 824        // We will call them SkipOpaqueType`length`
 825        private static ReadOnlySpan<byte> SkipOpaqueType1(ReadOnlySpan<byte> bytes)
 0826        {
 827            const int OpaqueTypeLengthSize = sizeof(byte);
 0828            if (bytes.Length < OpaqueTypeLengthSize)
 0829            {
 0830                return ReadOnlySpan<byte>.Empty;
 831            }
 832
 0833            byte length = bytes[0];
 0834            int totalBytes = OpaqueTypeLengthSize + length;
 835
 0836            return SkipBytes(bytes, totalBytes);
 0837        }
 838
 839        private static ReadOnlySpan<byte> SkipOpaqueType2(ReadOnlySpan<byte> bytes)
 0840        {
 841            const int OpaqueTypeLengthSize = sizeof(ushort);
 0842            if (bytes.Length < OpaqueTypeLengthSize)
 0843            {
 0844                return ReadOnlySpan<byte>.Empty;
 845            }
 846
 0847            ushort length = BinaryPrimitives.ReadUInt16BigEndian(bytes);
 0848            int totalBytes = OpaqueTypeLengthSize + length;
 849
 0850            return SkipBytes(bytes, totalBytes);
 0851        }
 852
 853        private enum NameType : byte
 854        {
 855            HostName = 0x00
 856        }
 857    }
 858}
 859

Methods/Properties

ToString()
.cctor()
TryGetFrameHeader(System.ReadOnlySpan`1<System.Byte>,System.Net.Security.TlsFrameHeader&)
TryGetFrameInfo(System.ReadOnlySpan`1<System.Byte>,System.Net.Security.TlsFrameHelper/TlsFrameInfo&,System.Net.Security.TlsFrameHelper/ProcessingOptions,System.Net.Security.TlsFrameHelper/HelloExtensionCallback)
TryGetAlertInfo(System.ReadOnlySpan`1<System.Byte>,System.Net.Security.TlsAlertLevel&,System.Net.Security.TlsAlertDescription&)
CreateProtocolVersionAlert(System.Security.Authentication.SslProtocols)
CreateAlertFrame(System.Security.Authentication.SslProtocols,System.Net.Security.TlsAlertDescription)
TryParseHelloFrame(System.ReadOnlySpan`1<System.Byte>,System.Net.Security.TlsFrameHelper/TlsFrameInfo&,System.Net.Security.TlsFrameHelper/ProcessingOptions,System.Net.Security.TlsFrameHelper/HelloExtensionCallback)
TryParseClientHello(System.ReadOnlySpan`1<System.Byte>,System.Net.Security.TlsFrameHelper/TlsFrameInfo&,System.Net.Security.TlsFrameHelper/ProcessingOptions,System.Net.Security.TlsFrameHelper/HelloExtensionCallback)
TryParseServerHello(System.ReadOnlySpan`1<System.Byte>,System.Net.Security.TlsFrameHelper/TlsFrameInfo&,System.Net.Security.TlsFrameHelper/ProcessingOptions,System.Net.Security.TlsFrameHelper/HelloExtensionCallback)
TryParseHelloExtensions(System.ReadOnlySpan`1<System.Byte>,System.Net.Security.TlsFrameHelper/TlsFrameInfo&,System.Net.Security.TlsFrameHelper/ProcessingOptions,System.Net.Security.TlsFrameHelper/HelloExtensionCallback)
TryGetSniFromServerNameList(System.ReadOnlySpan`1<System.Byte>,System.String&)
GetSniFromServerName(System.ReadOnlySpan`1<System.Byte>,System.Boolean&)
GetSniFromHostNameStruct(System.ReadOnlySpan`1<System.Byte>,System.Boolean&)
TryGetSupportedVersionsFromExtension(System.ReadOnlySpan`1<System.Byte>,System.Security.Authentication.SslProtocols&)
TryGetApplicationProtocolsFromExtension(System.ReadOnlySpan`1<System.Byte>,System.Net.Security.TlsFrameHelper/ApplicationProtocolInfo&)
TlsMinorVersionToProtocol(System.Byte)
DecodeString(System.ReadOnlySpan`1<System.Byte>)
ReadUInt24BigEndian(System.ReadOnlySpan`1<System.Byte>)
SkipBytes(System.ReadOnlySpan`1<System.Byte>,System.Int32)
SkipOpaqueType1(System.ReadOnlySpan`1<System.Byte>)
SkipOpaqueType2(System.ReadOnlySpan`1<System.Byte>)