Personalizar SNI em solicitações HTTP

Quando um cliente e um servidor negociam uma conexão HTTPS, uma conexão TLS precisa ser estabelecida primeiro. Como parte do handshake TLS, o cliente envia o nome de domínio do servidor ao qual está se conectando em uma das extensões TLS. Quando vários servidores (virtuais) são hospedados na mesma máquina, esse recurso do protocolo TLS permite que os clientes distingam a quais desses servidores estão se conectando e configurem as configurações de TLS, como o certificado do servidor, de acordo.

Quando uma solicitação HTTP usando HttpClient é feita, a implementação seleciona automaticamente um valor para a extensão de indicação do nome do servidor (SNI) com base na URL à qual o cliente está se conectando. Para cenários que exigem mais controle manual da extensão, você pode usar uma das seguintes abordagens.

Cabeçalho do host

O cabeçalho HTTP do host executa uma função semelhante à extensão SNI no TLS. Ele permite que o servidor de destino distinga entre solicitações para vários nomes de host em um único endereço IP. HttpClient preenche automaticamente o cabeçalho do Host usando o URI da solicitação. No entanto, você também pode definir seu valor manualmente e HttpClient também usará o novo valor na extensão SNI. Você pode usar qualquer um HttpRequestMessage.Headers.Host ou HttpClient.DefaultRequestHeaders.Host para conseguir esse efeito.

using HttpClient client = new();

client.DefaultRequestHeaders.Host = "www.microsoft.com";

using var response = await client.GetAsync("https://127.0.0.1:5001/");

System.Console.WriteLine(response);

Observação

Esse método não permite que você evite o envio de SNI completamente ao se conectar a uma URL com um nome de host. Se o cabeçalho estiver definido como string vazia, HttpClient usa o hostname da URL.

Observação

A personalização do cabeçalho Host afeta a validação do certificado do servidor. Por padrão, o cliente esperará que o certificado do servidor corresponda ao nome do host no cabeçalho do host.

Autenticação manual do SslStream através do ConnectCallback

Uma opção mais complicada, mas também mais poderosa, é usar o SocketsHttpHandler.ConnectCallback. Desde o .NET 7, é possível retornar um autenticado SslStream e, assim, personalizar como a conexão TLS é estabelecida. Dentro do callback, opções arbitrárias SslClientAuthenticationOptions podem ser usadas para executar a autenticação do lado do cliente.

var handler = new SocketsHttpHandler
{
    ConnectCallback = async (context, cancellationToken) =>
    {
        var socket = new Socket(SocketType.Stream, ProtocolType.Tcp) { NoDelay = true };
        try
        {
            await socket.ConnectAsync(context.DnsEndPoint, cancellationToken);

            var sslStream = new SslStream(new NetworkStream(socket, ownsSocket: true));

            // When using HTTP/2, you must also keep in mind to set options like ApplicationProtocols
            await sslStream.AuthenticateAsClientAsync(new SslClientAuthenticationOptions
            {
                TargetHost = context.DnsEndPoint.Host,

            }, cancellationToken);

            return sslStream;
        }
        catch
        {
            socket.Dispose();
            throw;
        }
    }
};

using HttpClient client = new(handler);

using var response = await client.GetAsync("https://www.microsoft.com");

System.Console.WriteLine(response);