System.Net.Security.ClientHelloInfo

Represents information about a client's initial TLS/SSL handshake message (ClientHello).

Table of Contents

Properties

ProtocolType

Gets the protocol type of the client's connection.

Public ProtocolType ProtocolType { get; }

TargetHost

Gets the target host name for the client's connection.

Public string TargetHost { get; }

SslProtocol

Gets the SSL/TLS protocol version requested by the client.

Public SslProtocol SslProtocol { get; }

CipherSuites

Gets the list of cipher suites supported by the client.

Public IEnumerable<string> CipherSuites { get; }

Extensions

Gets the list of TLS extensions sent by the client.

Public IEnumerable<string> Extensions { get; }

Remarks

The ClientHelloInfo class is used to inspect the details of an incoming TLS/SSL handshake from a client. This information can be valuable for security analysis, custom TLS server implementations, or for enforcing specific client requirements.

This class is typically accessed within event handlers for security-related operations, such as when customizing the behavior of an TcpClient or HttpClient.

Note: The availability and content of properties like Extensions may vary depending on the TLS version and client implementation.

Examples

C# Example: Inspecting ClientHelloInfo

This example demonstrates how to capture and inspect ClientHelloInfo in a custom TLS server scenario.


using System;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;

public class CustomTlsServer
{
    public static async Task StartServer(int port)
    {
        TcpListener listener = new TcpListener(IPAddress.Any, port);
        listener.Start();
        Console.WriteLine($"Server started on port {port}. Waiting for connections...");

        while (true)
        {
            TcpClient client = await listener.AcceptTcpClientAsync();
            _ = Task.Run(() => HandleClient(client));
        }
    }

    private static void HandleClient(TcpClient tcpClient)
    {
        try
        {
            // Obtain the certificate for the server
            X509Certificate serverCertificate = GetServerCertificate();

            // Use SslStream to wrap the network stream and enable SSL/TLS
            SslStream sslStream = new SslStream(
                tcpClient.GetStream(),
                false,
                new RemoteCertificateValidationCallback(ValidateServerCertificate),
                new LocalCertificateSelectionCallback(SelectServerCertificate)
            );

            // Begin the SSL/TLS handshake.
            // This is where ClientHelloInfo becomes available if we hook into it.
            // For simplicity, we'll just initiate the handshake here.
            // A more advanced scenario might involve intercepting the handshake process.
            sslStream.AuthenticateAsServer(serverCertificate,
                                         clientCertificateRequired: false,
                                         enabledSslProtocols: System.Security.Authentication.SslProtocols.Tls12 | System.Security.Authentication.SslProtocols.Tls13,
                                         checkCertificateRevocation: true);

            // At this point, the handshake is complete.
            // To directly access ClientHelloInfo during authentication,
            // you'd typically need a lower-level interception mechanism or a
            // custom SslStream implementation, which is more complex.
            // However, some properties might be inferable or accessible post-authentication
            // through different means depending on the framework version.

            Console.WriteLine($"Client authenticated. SSL/TLS Protocol: {sslStream.SslProtocol}");
            Console.WriteLine($"Cipher Suite: {sslStream.CipherSuite}");

            // Example: Reading data from the client
            byte[] buffer = new byte[1024];
            int bytesRead = sslStream.Read(buffer, 0, buffer.Length);
            string request = System.Text.Encoding.ASCII.GetString(buffer, 0, bytesRead);
            Console.WriteLine($"Received: {request}");

            // Send a response back to the client
            byte[] responseBytes = System.Text.Encoding.ASCII.GetBytes("Hello from server!");
            sslStream.Write(responseBytes, 0, responseBytes.Length);

            sslStream.Close();
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error handling client: {ex.Message}");
        }
        finally
        {
            tcpClient.Close();
        }
    }

    // Placeholder for getting the server certificate
    private static X509Certificate GetServerCertificate()
    {
        // In a real application, you would load your server certificate here.
        // For demonstration, we'll assume a self-signed certificate exists.
        // Replace with your certificate loading logic.
        try
        {
            // Example: Load from a PFX file
            // return new X509Certificate2("server.pfx", "password");

            // Example: Find a certificate in the local machine store
            var store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
            store.Open(OpenFlags.ReadOnly);
            var certificates = store.Certificates.Find(X509FindType.FindBySubjectName, "localhost", false);
            if (certificates.Count > 0)
            {
                return certificates[0];
            }
            else
            {
                throw new Exception("Server certificate not found. Please create a self-signed certificate for 'localhost'.");
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error loading certificate: {ex.Message}");
            throw;
        }
    }

    // Callback to validate the client certificate (optional)
    private static bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
    {
        if (sslPolicyErrors == SslPolicyErrors.None)
            return true;

        Console.WriteLine("Certificate error: " + sslPolicyErrors);
        return false; // Reject the connection if there are errors
    }

    // Callback to select the server certificate (if multiple are available)
    private static X509Certificate SelectServerCertificate(object sender, string targetHost, X509CertificateCollection localCertificates, X509Certificate remoteCertificate, string[] acceptableIssuers)
    {
        // If no certificates were passed in, return null.
        if (localCertificates == null || localCertificates.Count == 0)
            return null;

        // Use the first certificate in the collection.
        // In a real-world scenario, you might have logic to select the best certificate.
        return localCertificates[0];
    }

    // Example of how to start the server
    // public static void Main(string[] args)
    // {
    //     // Make sure you have a certificate for 'localhost' in your LocalMachine\My store
    //     // Or replace GetServerCertificate with your own logic to load a certificate.
    //     StartServer(8080).GetAwaiter().GetResult();
    // }
}
                

VB.NET Example: Inspecting ClientHelloInfo

This example demonstrates how to capture and inspect ClientHelloInfo in a custom TLS server scenario.


Imports System
Imports System.Net
Imports System.Net.Security
Imports System.Net.Sockets
Imports System.Security.Cryptography.X509Certificates
Imports System.Threading.Tasks

Public Class CustomTlsServerVB
    Public Shared Async Function StartServer(port As Integer) As Task
        Dim listener As New TcpListener(IPAddress.Any, port)
        listener.Start()
        Console.WriteLine($"Server started on port {port}. Waiting for connections...")

        While True
            Dim client As TcpClient =Await listener.AcceptTcpClientAsync()
            _ = Task.Run(Sub() HandleClient(client))
        End While
    End Function

    Private Shared Sub HandleClient(tcpClient As TcpClient)
        Try
            ' Obtain the certificate for the server
            Dim serverCertificate As X509Certificate = GetServerCertificate()

            ' Use SslStream to wrap the network stream and enable SSL/TLS
            Dim sslStream As New SslStream(
                tcpClient.GetStream(),
                False,
                New RemoteCertificateValidationCallback(AddressOf ValidateServerCertificate),
                New LocalCertificateSelectionCallback(AddressOf SelectServerCertificate)
            )

            ' Begin the SSL/TLS handshake.
            ' This is where ClientHelloInfo becomes available if we hook into it.
            ' For simplicity, we'll just initiate the handshake here.
            ' A more advanced scenario might involve intercepting the handshake process.
            sslStream.AuthenticateAsServer(serverCertificate,
                                         clientCertificateRequired:=False,
                                         enabledSslProtocols:=System.Security.Authentication.SslProtocols.Tls12 Or System.Security.Authentication.SslProtocols.Tls13,
                                         checkCertificateRevocation:=True)

            ' At this point, the handshake is complete.
            ' To directly access ClientHelloInfo during authentication,
            ' you'd typically need a lower-level interception mechanism or a
            ' custom SslStream implementation, which is more complex.
            ' However, some properties might be inferable or accessible post-authentication
            ' through different means depending on the framework version.

            Console.WriteLine($"Client authenticated. SSL/TLS Protocol: {sslStream.SslProtocol}")
            Console.WriteLine($"Cipher Suite: {sslStream.CipherSuite}")

            ' Example: Reading data from the client
            Dim buffer(1023) As Byte
            Dim bytesRead As Integer = sslStream.Read(buffer, 0, buffer.Length)
            Dim request As String = System.Text.Encoding.ASCII.GetString(buffer, 0, bytesRead)
            Console.WriteLine($"Received: {request}")

            ' Send a response back to the client
            Dim responseBytes As Byte() = System.Text.Encoding.ASCII.GetBytes("Hello from server!")
            sslStream.Write(responseBytes, 0, responseBytes.Length)

            sslStream.Close()
        Catch ex As Exception
            Console.WriteLine($"Error handling client: {ex.Message}")
        Finally
            tcpClient.Close()
        End Try
    End Sub

    ' Placeholder for getting the server certificate
    Private Shared Function GetServerCertificate() As X509Certificate
        ' In a real application, you would load your server certificate here.
        ' For demonstration, we'll assume a self-signed certificate exists.
        ' Replace with your certificate loading logic.
        Try
            ' Example: Load from a PFX file
            ' Return New X509Certificate2("server.pfx", "password")

            ' Example: Find a certificate in the local machine store
            Dim store As New X509Store(StoreName.My, StoreLocation.LocalMachine)
            store.Open(OpenFlags.ReadOnly)
            Dim certificates = store.Certificates.Find(X509FindType.FindBySubjectName, "localhost", False)
            If certificates.Count > 0 Then
                Return certificates(0)
            Else
                Throw New Exception("Server certificate not found. Please create a self-signed certificate for 'localhost'.")
            End If
        Catch ex As Exception
            Console.WriteLine($"Error loading certificate: {ex.Message}")
            Throw
        End Try
    End Function

    ' Callback to validate the client certificate (optional)
    Private Shared Function ValidateServerCertificate(sender As Object, certificate As X509Certificate, chain As X509Chain, sslPolicyErrors As SslPolicyErrors) As Boolean
        If sslPolicyErrors = SslPolicyErrors.None Then
            Return True
        End If

        Console.WriteLine("Certificate error: " + sslPolicyErrors.ToString())
        Return False ' Reject the connection if there are errors
    End Function

    ' Callback to select the server certificate (if multiple are available)
    Private Shared Function SelectServerCertificate(sender As Object, targetHost As String, localCertificates As X509CertificateCollection, remoteCertificate As X509Certificate, acceptableIssuers As String()) As X509Certificate
        ' If no certificates were passed in, return null.
        If localCertificates Is Nothing OrElse localCertificates.Count = 0 Then
            Return Nothing
        End If

        ' Use the first certificate in the collection.
        ' In a real-world scenario, you might have logic to select the best certificate.
        Return localCertificates(0)
    End Function

    ' Example of how to start the server
    ' Public Shared Sub Main(args As String())
    '     ' Make sure you have a certificate for 'localhost' in your LocalMachine\My store
    '     ' Or replace GetServerCertificate with your own logic to load a certificate.
    '     StartServer(8080).GetAwaiter().GetResult()
    ' End Sub
End Class
                

Requirements

.NET Framework

  • Supported in: 4.5, 4.5.1, 4.5.2, 4.6, 4.6.1, 4.6.2, 4.7, 4.7.1, 4.7.2, 4.8
  • Platforms: Windows

.NET Core / .NET 5+

While there might not be a direct 1:1 mapping of the ClientHelloInfo class as seen in .NET Framework, the underlying TLS/SSL functionality and access to handshake information are available through System.Net.Security.SslStream and related classes in .NET Core and later. Developers can achieve similar inspection capabilities using lower-level APIs or by leveraging libraries designed for advanced network programming.

  • Supported in: .NET Core 1.0, 1.1, 2.0, 2.1, 2.2, 3.0, 3.1, .NET 5, .NET 6, .NET 7, .NET 8
  • Platforms: Windows, macOS, Linux