Как в .NET5 задать TLS13 программно

У провайдера есть сервер с какой-то старой версией Windows (возможно, что даже не Windows Server 2012, а Windows Server 2008) c IIS10 - и у меня был запущен там сайт с ботом телеграм. Изначально он не заработал, т.к. у telegram обязательно требуется https и не ниже чем TLS1.2 - но когда я попросил доставить нужные kb'шки - доставили и недели три работало нормально, а потом снова отвалилось и снова по-причине неработающего TLS.

И вот тут я взялся отлаживать прямо на низком уровне - ведь бот телеграм использует тот же самый HttpClient.

И я взял для образца сайт, который отдаёт список установленных шифров и сделал минимально воспроизводимый пример:

protected static async Task<string> TestTls()
{
    //ServicePointManager.Expect100Continue = true;
    //ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
    var uri = "https://www.howsmyssl.com/a/check";
    var client = new HttpClient();
    var result = await client.GetStringAsync(uri);
    return result;
}

И этот код не заработал, выдав ошибку Authentication failed because the remote party sent a TLS alert: 'HandshakeFailure'.

Trace:

at System.Net.Http.ConnectHelper.EstablishSslConnectionAsyncCore(Boolean async, Stream stream, SslClientAuthenticationOptions sslOptions, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.ConnectAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.CreateHttp11ConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.GetHttpConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.SendWithRetryAsync(HttpRequestMessage request, Boolean async, Boolean doRequestAuth, CancellationToken cancellationToken)
at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
at System.Net.Http.DiagnosticsHandler.SendAsyncCore(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
at System.Net.Http.HttpClient.SendAsyncCore(HttpRequestMessage request, HttpCompletionOption completionOption, Boolean async, Boolean emitTelemetryStartStop, CancellationToken cancellationToken)
at System.Net.Http.HttpClient.GetStringAsyncCore(HttpRequestMessage request, CancellationToken cancellationToken)
at WebApp1.Controllers.TestController.TestTls() in C:\proj\test\src\WebApp1\Controllers\TestController.cs:line 83
at WebApp1.Controllers.TestController.Update(Update update, CancellationToken cancellationToken) in C:\proj\test\src\WebApp1\Controllers\TestController.cs:line 28 
Exception: The SSL connection could not be established, see inner exception.
ExceptionInner: Authentication failed because the remote party sent a TLS alert: 'HandshakeFailure'.
ExceptionInnerInner: The message received was unexpected or badly formatted.

При этом техподдержка мастерхоста говорит, что хоть и сервер старый - но они всё настроили и у них всё работает. Приводят скрипт powershell в котором они тыкали реальный адрес https (и я видел в своём чате телеграм сообщение HelloWorld, поэтому верю)

$InitialSecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12 Invoke-WebRequest "https://api.telegram.org/bot<MySecretKeyHere>/sendMessage?chat_id=<RealChatIdHere>&text=HelloWorld" -UseBasicParsing

Т.е. задача сводится к тому, чтобы из кода C# включить Tls12 или Tls13

И вот как это сделать - вопрос. В документации майкрософт написано "если у вас фреймворк выше 4.7 - то пусть ОС сама делает выбор протоколов", в более ранних версиях рекомендуется ставить

ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;

При этом у меня не работает никак, хоть у меня .NET 5 (CORE, не Framework). Если попробовать проанализировать при помощи https://www.ssllabs.com контроллер - то видно, что сервер поддерживает TLS 1.3, 1.2, 1.1, 1.0 и не поддерживает SSL 3 и 2 - и общая оценка в тесте весьма высокая (grade "B").

И как это тогда делается правильно?


Ответы (1 шт):

Автор решения: aepot

Нужно дополнительное исследование, давайте порефлексируем немного, написал тестовый HTTP запрос.

private static readonly HttpClient client = new(); // { DefaultRequestVersion = HttpVersion.Version20 };

static async Task Main(string[] args)
{
    using var response = await client.GetAsync("https://google.com/", HttpCompletionOption.ResponseHeadersRead);
    Console.WriteLine($"HTTP/{response.Version}");
    var contentStream = GetPrivateField(response.Content, "_stream");
    if (response.Version == HttpVersion.Version20)
    {
        contentStream = GetPrivateField(contentStream, "_http2Stream");
    }
    var connection = GetPrivateField(contentStream, "_connection");
    SslStream sslStream = (SslStream)GetPrivateField(connection, "_stream");
    Console.WriteLine(sslStream.SslProtocol);
    Console.WriteLine(sslStream.NegotiatedCipherSuite);
}

private static object GetPrivateField(object obj, string field) 
    => obj.GetType().GetField(field, BindingFlags.Instance | BindingFlags.NonPublic).GetValue(obj);

.NET 6, Windows 11, проверяю обе версии HTTP, получаю:

HTTP/1.1
Tls13
TLS_AES_256_GCM_SHA384
HTTP/2.0
Tls13
TLS_AES_256_GCM_SHA384

Рекомендую протестировать, это поможет разобраться. Если вы получите TLS 1.3 на гугле, а к себе не сможете подключиться, или получите 1.2, значит проблема на стороне сервера. Если на гугле получите 1.2, значит проблема на стороне клиента.


При всём при этом, ваш метод TestTls() выдает то же самое исключение. С причинами я не разобрался, надо снифать трафик, сравнивать что шлет браузер и что шлет .NET, а у меня сейчас нет времени для глубоких раскопок.


Как задать настройки TLS версии для HttpClient в .NET Core 3+ явно:

private static readonly HttpClient client = new(new SocketsHttpHandler
{
    SslOptions = new() { EnabledSslProtocols = SslProtocols.Tls13 | SslProtocols.Tls12 }
});
→ Ссылка