< Summary

Line coverage
0%
Covered lines: 0
Uncovered lines: 1
Coverable lines: 1
Total lines: 859
Line coverage: 0%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
ToString()100%110%

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
 091        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()
 127            {
 128                if (Header.Type == TlsContentType.Handshake)
 129                {
 130                    if (HandshakeType == TlsHandshakeType.ClientHello)
 131                    {
 132                        return $"{Header.Version}:{HandshakeType}[{Header.Length}] TargetName='{TargetName}' SupportedVe
 133                    }
 134                    else if (HandshakeType == TlsHandshakeType.ServerHello)
 135                    {
 136                        return $"{Header.Version}:{HandshakeType}[{Header.Length}] SupportedVersion='{SupportedVersions}
 137                    }
 138                    else
 139                    {
 140                        return $"{Header.Version}:{HandshakeType}[{Header.Length}] SupportedVersion='{SupportedVersions}
 141                    }
 142                }
 143                else
 144                {
 145                    return $"{Header.Version}:{Header.Type}[{Header.Length}]";
 146                }
 147            }
 148        }
 149
 150        public delegate bool HelloExtensionCallback(ref TlsFrameInfo info, ExtensionType type, ReadOnlySpan<byte> extens
 151
 152        private static readonly byte[] s_protocolMismatch13 = new byte[] { (byte)TlsContentType.Alert, 3, 4, 0, 2, 2, 70
 153        private static readonly byte[] s_protocolMismatch12 = new byte[] { (byte)TlsContentType.Alert, 3, 3, 0, 2, 2, 70
 154        private static readonly byte[] s_protocolMismatch11 = new byte[] { (byte)TlsContentType.Alert, 3, 2, 0, 2, 2, 70
 155        private static readonly byte[] s_protocolMismatch10 = new byte[] { (byte)TlsContentType.Alert, 3, 1, 0, 2, 2, 70
 156        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.
 167        private static readonly IdnMapping s_idnMapping = new IdnMapping() { AllowUnassigned = true };
 168        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)
 171        {
 172            if (frame.Length < HeaderSize)
 173            {
 174                header.Length = -1;
 175                return false;
 176            }
 177
 178            header.Type = (TlsContentType)frame[0];
 179
 180            // SSLv3, TLS or later
 181            if (frame[1] == 3)
 182            {
 183                header.Length = ((frame[3] << 8) | frame[4]) + HeaderSize;
 184                header.Version = TlsMinorVersionToProtocol(frame[2]);
 185            }
 186            else if (frame[2] == (byte)TlsHandshakeType.ClientHello &&
 187                     frame[3] == 3) // SSL3 or above
 188            {
 189                int length;
 190                if ((frame[0] & 0x80) != 0)
 191                {
 192                    // Two bytes
 193                    length = (((frame[0] & 0x7f) << 8) | frame[1]) + 2;
 194                }
 195                else
 196                {
 197                    // Three bytes
 198                    length = (((frame[0] & 0x3f) << 8) | frame[1]) + 3;
 199                }
 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
 207                header.Version = SslProtocols.Ssl2;
 208#pragma warning restore CS0618
 209                header.Length = length;
 210                header.Type = TlsContentType.Handshake;
 211            }
 212            else
 213            {
 214                // unknown format
 215                header.Length = -1;
 216                return false;
 217            }
 218
 219            return true;
 220        }
 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 = 
 229        {
 230            const int HandshakeTypeOffset = 5;
 231            if (frame.Length < HeaderSize)
 232            {
 233                return false;
 234            }
 235
 236            if (!TryGetFrameHeader(frame, ref info.Header))
 237            {
 238                // Unknown or malformed frame format.
 239                return false;
 240            }
 241
 242            info.SupportedVersions = info.Header.Version;
 243
 244            if (info.Header.Type == TlsContentType.Alert)
 245            {
 246                TlsAlertLevel level = default;
 247                TlsAlertDescription description = default;
 248                if (TryGetAlertInfo(frame, ref level, ref description))
 249                {
 250                    info.AlertDescription = description;
 251                    return true;
 252                }
 253
 254                return false;
 255            }
 256
 257            if (info.Header.Type != TlsContentType.Handshake || frame.Length <= HandshakeTypeOffset)
 258            {
 259                return false;
 260            }
 261
 262            info.HandshakeType = (TlsHandshakeType)frame[HandshakeTypeOffset];
 263#pragma warning disable CS0618 // Ssl2 and Ssl3 are obsolete
 264            if (info.Header.Version == SslProtocols.Ssl2)
 265            {
 266                // This is safe. We would not get here if the length is too small.
 267                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.
 270                info.HandshakeType = TlsHandshakeType.ClientHello;
 271                // There is no more parsing for old protocols.
 272                return true;
 273            }
 274#pragma warning restore CS0618
 275
 276            // Check if we have full frame.
 277            bool isComplete = frame.Length >= info.Header.Length;
 278
 279#pragma warning disable SYSLIB0039 // TLS 1.0 and 1.1 are obsolete
 280            if (((int)info.Header.Version >= (int)SslProtocols.Tls) &&
 281#pragma warning restore SYSLIB0039
 282                (info.HandshakeType == TlsHandshakeType.ClientHello || info.HandshakeType == TlsHandshakeType.ServerHell
 283            {
 284                if (!TryParseHelloFrame(frame.Slice(HeaderSize), ref info, options, callback))
 285                {
 286                    isComplete = false;
 287                }
 288            }
 289
 290            return isComplete;
 291        }
 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
 308        {
 309            if (frame.Length < 7 || frame[0] != (byte)TlsContentType.Alert)
 310            {
 311                return false;
 312            }
 313
 314            level = (TlsAlertLevel)frame[5];
 315            description = (TlsAlertDescription)frame[6];
 316
 317            return true;
 318        }
 319
 320        private static byte[] CreateProtocolVersionAlert(SslProtocols version) =>
 321            version switch
 322            {
 323                SslProtocols.Tls13 => s_protocolMismatch13,
 324                SslProtocols.Tls12 => s_protocolMismatch12,
 325#pragma warning disable SYSLIB0039 // TLS 1.0 and 1.1 are obsolete
 326                SslProtocols.Tls11 => s_protocolMismatch11,
 327                SslProtocols.Tls => s_protocolMismatch10,
 328#pragma warning restore SYSLIB0039
 329#pragma warning disable 0618
 330                SslProtocols.Ssl3 => s_protocolMismatch30,
 331#pragma warning restore 0618
 332                _ => Array.Empty<byte>(),
 333            };
 334
 335        public static byte[] CreateAlertFrame(SslProtocols version, TlsAlertDescription reason)
 336        {
 337            if (reason == TlsAlertDescription.ProtocolVersion)
 338            {
 339                return CreateProtocolVersionAlert(version);
 340            }
 341#pragma warning disable SYSLIB0039 // TLS 1.0 and 1.1 are obsolete
 342            else if ((int)version >= (int)SslProtocols.Tls)
 343#pragma warning restore SYSLIB0039
 344            {
 345                // Create TLS1.2 alert
 346                byte[] buffer = new byte[] { (byte)TlsContentType.Alert, 3, 3, 0, 2, 2, (byte)reason };
 347                switch (version)
 348                {
 349                    case SslProtocols.Tls13:
 350                        buffer[2] = 4;
 351                        break;
 352#pragma warning disable SYSLIB0039 // TLS 1.0 and 1.1 are obsolete
 353                    case SslProtocols.Tls11:
 354                        buffer[2] = 2;
 355                        break;
 356                    case SslProtocols.Tls:
 357                        buffer[2] = 1;
 358                        break;
 359#pragma warning restore SYSLIB0039
 360                }
 361
 362                return buffer;
 363            }
 364
 365            return Array.Empty<byte>();
 366        }
 367
 368        private static bool TryParseHelloFrame(ReadOnlySpan<byte> sslHandshake, ref TlsFrameInfo info, ProcessingOptions
 369        {
 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
 385            if (sslHandshake.Length < HelloOffset ||
 386                ((TlsHandshakeType)sslHandshake[HandshakeTypeOffset] != TlsHandshakeType.ClientHello &&
 387                 (TlsHandshakeType)sslHandshake[HandshakeTypeOffset] != TlsHandshakeType.ServerHello))
 388            {
 389                return false;
 390            }
 391
 392            int helloLength = ReadUInt24BigEndian(sslHandshake.Slice(HelloLengthOffset));
 393            ReadOnlySpan<byte> helloData = sslHandshake.Slice(HelloOffset);
 394
 395            if (helloLength < ProtocolVersionSize || helloData.Length < helloLength)
 396            {
 397                return false;
 398            }
 399
 400            // ProtocolVersion may be different from frame header.
 401            if (helloData[ProtocolVersionMajorOffset] == ProtocolVersionTlsMajorValue)
 402            {
 403                info.SupportedVersions |= TlsMinorVersionToProtocol(helloData[ProtocolVersionMinorOffset]);
 404            }
 405
 406            return (TlsHandshakeType)sslHandshake[HandshakeTypeOffset] == TlsHandshakeType.ClientHello ?
 407                        TryParseClientHello(helloData.Slice(0, helloLength), ref info, options, callback) :
 408                        TryParseServerHello(helloData.Slice(0, helloLength), ref info, options, callback);
 409        }
 410
 411        private static bool TryParseClientHello(ReadOnlySpan<byte> clientHello, ref TlsFrameInfo info, ProcessingOptions
 412        {
 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
 424            ReadOnlySpan<byte> p = SkipBytes(clientHello, ProtocolVersionSize + RandomSize);
 425
 426            // Skip SessionID (max size 32 => size fits in 1 byte)
 427            p = SkipOpaqueType1(p);
 428
 429            // Skip cipher suites (max size 2^16-1 => size fits in 2 bytes)
 430            p = SkipOpaqueType2(p);
 431
 432            // Skip compression methods (max size 2^8-1 => size fits in 1 byte)
 433            p = SkipOpaqueType1(p);
 434
 435            // no extensions
 436            if (p.IsEmpty)
 437            {
 438                return true;
 439            }
 440
 441            if (p.Length < sizeof(ushort))
 442            {
 443                return false;
 444            }
 445
 446            // client_hello_extension_list (max size 2^16-1 => size fits in 2 bytes)
 447            int extensionListLength = BinaryPrimitives.ReadUInt16BigEndian(p);
 448            p = SkipBytes(p, sizeof(ushort));
 449            if (extensionListLength != p.Length)
 450            {
 451                return false;
 452            }
 453
 454            return TryParseHelloExtensions(p, ref info, options, callback);
 455        }
 456
 457        private static bool TryParseServerHello(ReadOnlySpan<byte> serverHello, ref TlsFrameInfo info, ProcessingOptions
 458        {
 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
 473            ReadOnlySpan<byte> p = SkipBytes(serverHello, ProtocolVersionSize + RandomSize);
 474            // Skip SessionID (max size 32 => size fits in 1 byte)
 475            p = SkipOpaqueType1(p);
 476            p = SkipBytes(p, CipherSuiteLength + CompressionMethodLength);
 477
 478            // is invalid structure or no extensions?
 479            if (p.IsEmpty)
 480            {
 481                return false;
 482            }
 483
 484            if (p.Length < sizeof(ushort))
 485            {
 486                return false;
 487            }
 488
 489            // client_hello_extension_list (max size 2^16-1 => size fits in 2 bytes)
 490            int extensionListLength = BinaryPrimitives.ReadUInt16BigEndian(p);
 491            p = SkipBytes(p, sizeof(ushort));
 492            if (extensionListLength != p.Length)
 493            {
 494                return false;
 495            }
 496
 497            return TryParseHelloExtensions(p, ref info, options, callback);
 498        }
 499
 500        // This is common for ClientHello and ServerHello.
 501        private static bool TryParseHelloExtensions(ReadOnlySpan<byte> extensions, ref TlsFrameInfo info, ProcessingOpti
 502        {
 503            const int ExtensionHeader = 4;
 504            bool isComplete = true;
 505
 506            while (extensions.Length >= ExtensionHeader)
 507            {
 508                ExtensionType extensionType = (ExtensionType)BinaryPrimitives.ReadUInt16BigEndian(extensions);
 509                extensions = SkipBytes(extensions, sizeof(ushort));
 510
 511                ushort extensionLength = BinaryPrimitives.ReadUInt16BigEndian(extensions);
 512                extensions = SkipBytes(extensions, sizeof(ushort));
 513                if (extensions.Length < extensionLength)
 514                {
 515                    isComplete = false;
 516                    break;
 517                }
 518
 519                ReadOnlySpan<byte> extensionData = extensions.Slice(0, extensionLength);
 520
 521                if (extensionType == ExtensionType.ServerName && (options & ProcessingOptions.ServerName) != 0)
 522                {
 523                    if (!TryGetSniFromServerNameList(extensionData, out string? sni))
 524                    {
 525                        return false;
 526                    }
 527
 528                    info.TargetName = sni!;
 529                }
 530                else if (extensionType == ExtensionType.SupportedVersions && (options & ProcessingOptions.Versions) != 0
 531                {
 532                    if (!TryGetSupportedVersionsFromExtension(extensionData, out SslProtocols versions))
 533                    {
 534                        return false;
 535                    }
 536
 537                    info.SupportedVersions |= versions;
 538                }
 539                else if (extensionType == ExtensionType.ApplicationProtocols &&
 540                          (options & (ProcessingOptions.ApplicationProtocol | ProcessingOptions.RawApplicationProtocol))
 541                {
 542                    if (!TryGetApplicationProtocolsFromExtension(extensionData, out ApplicationProtocolInfo alpn))
 543                    {
 544                        return false;
 545                    }
 546
 547                    info.ApplicationProtocols |= alpn;
 548
 549                    // Process RAW options only if explicitly set since that will allocate....
 550                    if (options.HasFlag(ProcessingOptions.RawApplicationProtocol))
 551                    {
 552                        // Skip ALPN extension Length. We have that in span.
 553                        info.RawApplicationProtocols = extensionData.Slice(sizeof(short)).ToArray();
 554                    }
 555                }
 556
 557                callback?.Invoke(ref info, extensionType, extensionData);
 558                extensions = extensions.Slice(extensionLength);
 559            }
 560
 561            return isComplete;
 562        }
 563
 564        private static bool TryGetSniFromServerNameList(ReadOnlySpan<byte> serverNameListExtension, out string? sni)
 565        {
 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);
 572            sni = null;
 573
 574            if (serverNameListExtension.Length < ServerNameListOffset)
 575            {
 576                return false;
 577            }
 578
 579            int serverNameListLength = BinaryPrimitives.ReadUInt16BigEndian(serverNameListExtension);
 580            ReadOnlySpan<byte> serverNameList = serverNameListExtension.Slice(ServerNameListOffset);
 581
 582            if (serverNameListLength != serverNameList.Length)
 583            {
 584                return false;
 585            }
 586
 587            ReadOnlySpan<byte> serverName = serverNameList.Slice(0, serverNameListLength);
 588
 589            sni = GetSniFromServerName(serverName, out bool invalid);
 590            return !invalid;
 591        }
 592
 593        private static string? GetSniFromServerName(ReadOnlySpan<byte> serverName, out bool invalid)
 594        {
 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);
 605            if (serverName.Length < HostNameStructOffset)
 606            {
 607                invalid = true;
 608                return null;
 609            }
 610
 611            // Following can underflow but it is ok due to equality check below
 612            NameType nameType = (NameType)serverName[NameTypeOffset];
 613            ReadOnlySpan<byte> hostNameStruct = serverName.Slice(HostNameStructOffset);
 614            if (nameType != NameType.HostName)
 615            {
 616                invalid = true;
 617                return null;
 618            }
 619
 620            return GetSniFromHostNameStruct(hostNameStruct, out invalid);
 621        }
 622
 623        private static string? GetSniFromHostNameStruct(ReadOnlySpan<byte> hostNameStruct, out bool invalid)
 624        {
 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
 630            if (hostNameStruct.Length < HostNameOffset)
 631            {
 632                invalid = true;
 633                return null;
 634            }
 635
 636            int hostNameLength = BinaryPrimitives.ReadUInt16BigEndian(hostNameStruct);
 637            ReadOnlySpan<byte> hostName = hostNameStruct.Slice(HostNameOffset);
 638            if (hostNameLength != hostName.Length)
 639            {
 640                invalid = true;
 641                return null;
 642            }
 643
 644            invalid = false;
 645            return hostNameLength <= MaxHostNameLength ? DecodeString(hostName) : null;
 646        }
 647
 648        private static bool TryGetSupportedVersionsFromExtension(ReadOnlySpan<byte> extensionData, out SslProtocols prot
 649        {
 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
 663            protocols = SslProtocols.None;
 664
 665            if (extensionData.IsEmpty)
 666            {
 667                return false;
 668            }
 669
 670            byte supportedVersionLength = extensionData[VersionListLengthOffset];
 671            extensionData = extensionData.Slice(VersionListNameOffset);
 672
 673            if (extensionData.Length != supportedVersionLength)
 674            {
 675                return false;
 676            }
 677
 678            // Get list of protocols we support. Ignore the rest.
 679            while (extensionData.Length >= VersionLength)
 680            {
 681                if (extensionData[ProtocolVersionMajorOffset] == ProtocolVersionTlsMajorValue)
 682                {
 683                    protocols |= TlsMinorVersionToProtocol(extensionData[ProtocolVersionMinorOffset]);
 684                }
 685
 686                extensionData = extensionData.Slice(VersionLength);
 687            }
 688
 689            return true;
 690        }
 691
 692        private static bool TryGetApplicationProtocolsFromExtension(ReadOnlySpan<byte> extensionData, out ApplicationPro
 693        {
 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
 704            alpn = ApplicationProtocolInfo.None;
 705
 706            if (extensionData.Length < AlpnListOffset)
 707            {
 708                return false;
 709            }
 710
 711            int AlpnListLength = BinaryPrimitives.ReadUInt16BigEndian(extensionData);
 712            ReadOnlySpan<byte> alpnList = extensionData.Slice(AlpnListOffset);
 713            if (AlpnListLength != alpnList.Length)
 714            {
 715                return false;
 716            }
 717
 718            while (!alpnList.IsEmpty)
 719            {
 720                byte protocolLength = alpnList[0];
 721                if (alpnList.Length < protocolLength + 1)
 722                {
 723                    return false;
 724                }
 725
 726                ReadOnlySpan<byte> protocol = alpnList.Slice(1, protocolLength);
 727                if (protocolLength == 2)
 728                {
 729                    if (protocol.SequenceEqual(SslApplicationProtocol.Http2.Protocol.Span))
 730                    {
 731                        alpn |= ApplicationProtocolInfo.Http2;
 732                    }
 733                    else
 734                    {
 735                        alpn |= ApplicationProtocolInfo.Other;
 736                    }
 737                }
 738                else if (protocolLength == SslApplicationProtocol.Http11.Protocol.Length &&
 739                         protocol.SequenceEqual(SslApplicationProtocol.Http11.Protocol.Span))
 740                {
 741                    alpn |= ApplicationProtocolInfo.Http11;
 742                }
 743                else
 744                {
 745                    alpn |= ApplicationProtocolInfo.Other;
 746                }
 747
 748                alpnList = alpnList.Slice(protocolLength + 1);
 749            }
 750
 751            return true;
 752        }
 753
 754        private static SslProtocols TlsMinorVersionToProtocol(byte value)
 755        {
 756            return value switch
 757            {
 758                4 => SslProtocols.Tls13,
 759                3 => SslProtocols.Tls12,
 760#pragma warning disable SYSLIB0039 // TLS 1.0 and 1.1 are obsolete
 761                2 => SslProtocols.Tls11,
 762                1 => SslProtocols.Tls,
 763#pragma warning restore SYSLIB0039
 764#pragma warning disable 0618
 765                0 => SslProtocols.Ssl3,
 766#pragma warning restore 0618
 767                _ => SslProtocols.None,
 768            };
 769        }
 770
 771        private static string? DecodeString(ReadOnlySpan<byte> bytes)
 772        {
 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
 791            {
 792                idnEncodedString = s_encoding.GetString(bytes);
 793            }
 794            catch (DecoderFallbackException)
 795            {
 796                return null;
 797            }
 798
 799            try
 800            {
 801                return s_idnMapping.GetUnicode(idnEncodedString);
 802            }
 803            catch (ArgumentException)
 804            {
 805                // client has not done IDN mapping
 806                return idnEncodedString;
 807            }
 808        }
 809
 810        private static int ReadUInt24BigEndian(ReadOnlySpan<byte> bytes)
 811        {
 812            return (bytes[0] << 16) | (bytes[1] << 8) | bytes[2];
 813        }
 814
 815        private static ReadOnlySpan<byte> SkipBytes(ReadOnlySpan<byte> bytes, int numberOfBytesToSkip)
 816        {
 817            return (numberOfBytesToSkip < bytes.Length) ? bytes.Slice(numberOfBytesToSkip) : ReadOnlySpan<byte>.Empty;
 818        }
 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)
 826        {
 827            const int OpaqueTypeLengthSize = sizeof(byte);
 828            if (bytes.Length < OpaqueTypeLengthSize)
 829            {
 830                return ReadOnlySpan<byte>.Empty;
 831            }
 832
 833            byte length = bytes[0];
 834            int totalBytes = OpaqueTypeLengthSize + length;
 835
 836            return SkipBytes(bytes, totalBytes);
 837        }
 838
 839        private static ReadOnlySpan<byte> SkipOpaqueType2(ReadOnlySpan<byte> bytes)
 840        {
 841            const int OpaqueTypeLengthSize = sizeof(ushort);
 842            if (bytes.Length < OpaqueTypeLengthSize)
 843            {
 844                return ReadOnlySpan<byte>.Empty;
 845            }
 846
 847            ushort length = BinaryPrimitives.ReadUInt16BigEndian(bytes);
 848            int totalBytes = OpaqueTypeLengthSize + length;
 849
 850            return SkipBytes(bytes, totalBytes);
 851        }
 852
 853        private enum NameType : byte
 854        {
 855            HostName = 0x00
 856        }
 857    }
 858}
 859

Methods/Properties

ToString()