Настройка SNI в HTTP-запросах

Когда клиент и сервер согласовывают подключение HTTPS, сначала необходимо установить подключение TLS. В рамках рукопожатия TLS клиент отправляет доменное имя сервера, к которому он подключается, в одном из расширений TLS. При размещении нескольких (виртуальных) серверов на одном компьютере эта функция протокола TLS позволяет клиентам различать, к каким из этих серверов они подключаются и настраивают параметры TLS, например сертификат сервера, соответственно.

При выполнении HTTP-запроса HttpClient реализация автоматически выбирает значение расширения имени сервера (SNI) на основе URL-адреса, к которому подключается клиент. Для сценариев, требующих более ручного управления расширением, можно использовать один из следующих подходов.

Заголовок хоста

Заголовок HTTP узла выполняет аналогичную функцию в качестве расширения SNI в TLS. Он позволяет целевому серверу различать запросы для нескольких имен узлов в одном IP-адресе. HttpClient автоматически заполняет заголовок узла с помощью URI запроса. Однако можно также задать его значение вручную, а HttpClient также использовать новое значение в расширении SNI. Вы можете использовать либо HttpRequestMessage.Headers.Host, либо HttpClient.DefaultRequestHeaders.Host, чтобы достичь этого эффекта.

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);

Замечание

Этот метод не позволяет избежать отправки SNI в целом при подключении к URL-адресу с именем узла. Если для заголовка задана пустая строка, HttpClient вместо этого используется имя узла из URL-адреса.

Замечание

Настройка заголовка 'Host' влияет на проверку сертификата сервера. По умолчанию клиент ожидает, что сертификат сервера соответствует имени узла в заголовке узла.

Проверка подлинности SslStream вручную через ConnectCallback

Более сложный, но и более мощный вариант — использовать SocketsHttpHandler.ConnectCallback. Начиная с .NET 7, можно вернуть прошедший SslStream проверку подлинности и таким образом настроить, как устанавливается TLS-подключение. В обратном вызове произвольные SslClientAuthenticationOptions параметры можно использовать для аутентификации на стороне клиента.

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);