.NET Tutorial
Middle6 мин чтенияВопрос 6 из 8

HTTPS, TLS и сертификаты в .NET: как работает и как настроить?

Что даёт TLS

HTTP

пароль открытым текстом

Клиент

Wi-Fi в кафе,
провайдер, прокси

Сервер

HTTPS

зашифровано,
подлинность проверена

Клиент

Посредник видит
только IP и домен (SNI)

Сервер

HTTP

пароль открытым текстом

Клиент

Wi-Fi в кафе,
провайдер, прокси

Сервер

HTTPS

зашифровано,
подлинность проверена

Клиент

Посредник видит
только IP и домен (SNI)

Сервер

Без TLS и с TLS

TLS 1.3 handshake

СерверКлиентобе стороны вычисляютобщий секрет (ECDHE) —дальше всё зашифрованоClientHello — версии,шифры, key_share (ECDHEпубличный ключ), SNIServerHello — выбранныйшифр, key_share сервераCertificate, CertificateVerify(подпись приватнымключом), Finishedпроверка цепочки,имени, срока,подписиFinishedHTTP-запрос (зашифрованAES-GCM)HTTP-ответ
СерверКлиентобе стороны вычисляютобщий секрет (ECDHE) —дальше всё зашифрованоClientHello — версии,шифры, key_share (ECDHEпубличный ключ), SNIServerHello — выбранныйшифр, key_share сервераCertificate, CertificateVerify(подпись приватнымключом), Finishedпроверка цепочки,имени, срока,подписиFinishedHTTP-запрос (зашифрованAES-GCM)HTTP-ответ
TLS 1.3: рукопожатие за один round-trip
  • Асимметричная криптография (ECDHE, подпись сертификата) — только для установки соединения: она медленная.
  • Симметричная (AES-GCM) — для данных: быстрая, аппаратно ускоренная.
  • Forward secrecy: ключи сессии эфемерные — даже если приватный ключ сервера украдут позже, записанный трафик не расшифровать. В TLS 1.3 обязательна.
  • TLS 1.2 требует 2 round-trip, TLS 1.3 — 1 (и 0-RTT для повторных соединений, с оговорками про replay).

Цепочка доверия

Корневой CA
(в хранилище ОС/браузера)

Промежуточный CA
подписан корневым

Сертификат сервера
CN/SAN: shop.example.uz
подписан промежуточным

Клиент проверяет:
подписи по цепочке, срок действия,
имя совпадает с SAN,
не отозван (OCSP/CRL),
назначение (EKU serverAuth)

Корневой CA
(в хранилище ОС/браузера)

Промежуточный CA
подписан корневым

Сертификат сервера
CN/SAN: shop.example.uz
подписан промежуточным

Клиент проверяет:
подписи по цепочке, срок действия,
имя совпадает с SAN,
не отозван (OCSP/CRL),
назначение (EKU serverAuth)

Проверка сертификата

Сервер должен отдавать полную цепочку (свой + промежуточные). Частая ошибка — не отдан промежуточный сертификат: браузеры иногда достраивают цепочку сами, а HttpClient в Linux-контейнере — нет, и запросы падают.

ASP.NET Core: базовая настройка

C#
var app = builder.Build();

if (!app.Environment.IsDevelopment())
    app.UseHsts();               // Strict-Transport-Security: браузер будет ходить только по HTTPS

app.UseHttpsRedirection();       // 307/308 с http на https
Параметры HSTS
builder.Services.AddHsts(o =>
{
    o.MaxAge = TimeSpan.FromDays(365);
    o.IncludeSubDomains = true;
    o.Preload = true;            // для включения в preload-список браузеров
});

Kestrel с сертификатом

appsettings.json
{
  "Kestrel": {
    "Endpoints": {
      "Https": {
        "Url": "https://0.0.0.0:8443",
        "Certificate": { "Path": "/certs/tls.crt", "KeyPath": "/certs/tls.key" }
      }
    }
  }
}
Программно: TLS 1.2/1.3 и перезагрузка сертификата
builder.WebHost.ConfigureKestrel(k =>
{
    k.ConfigureHttpsDefaults(h =>
    {
        h.SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls13;
        h.ServerCertificateSelector = (ctx, name) => certStore.GetCurrent(name);   // горячая замена при продлении
    });
});

Разработка: dotnet dev-certs https --trust создаёт и добавляет в доверенные локальный сертификат для localhost.

TLS-терминация на ingress

HTTPS

HTTP внутри кластера
(или mTLS через mesh)

Браузер

Ingress / балансировщик
сертификат от cert-manager
(Let's Encrypt)

ASP.NET Core
:8080

HTTPS

HTTP внутри кластера
(или mTLS через mesh)

Браузер

Ingress / балансировщик
сертификат от cert-manager
(Let's Encrypt)

ASP.NET Core
:8080

Типичная схема в Kubernetes

Приложение видит HTTP-запрос от прокси, поэтому без настройки ломаются Request.Scheme, редиректы, генерация ссылок и OIDC redirect_uri:

C#
builder.Services.Configure<ForwardedHeadersOptions>(o =>
{
    o.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
    o.KnownNetworks.Clear();                     // доверять только известным прокси
    o.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("10.0.0.0"), 8));
});

app.UseForwardedHeaders();                       // самым первым в конвейере

HttpClient: не отключайте проверку

Никогда в продакшене
var handler = new HttpClientHandler
{
    ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
};
// шифрование есть, а аутентификации сервера нет — MITM подменит сертификат незаметно

Правильные решения для «своего» CA (внутренние сервисы): добавить корневой сертификат компании в хранилище доверия образа (update-ca-certificates) или проверять конкретный CA/отпечаток (certificate pinning) в callback.

mTLS: взаимная аутентификация

Сервер требует клиентский сертификат
builder.WebHost.ConfigureKestrel(k => k.ConfigureHttpsDefaults(h =>
    h.ClientCertificateMode = ClientCertificateMode.RequireCertificate));

builder.Services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme)
    .AddCertificate(o =>
    {
        o.AllowedCertificateTypes = CertificateTypes.Chained;
        o.RevocationMode = X509RevocationMode.Online;
    });
Клиент предъявляет сертификат
var handler = new SocketsHttpHandler();
handler.SslOptions.ClientCertificates = [X509Certificate2.CreateFromPemFile("client.crt", "client.key")];

В Kubernetes mTLS между сервисами обычно берёт на себя service mesh (Istio, Linkerd): sidecar-прокси выпускают и ротируют сертификаты автоматически, приложение ничего не знает.

Вопросы на засыпку

Почему не шифровать всё асимметрично?

Асимметричные операции в сотни и тысячи раз медленнее симметричных и ограничены по размеру данных. Поэтому асимметрия используется только для обмена ключами и аутентификации, а поток данных шифруется симметричным ключом сессии.

Что видно наблюдателю при HTTPS?

IP-адреса, порт, объём и время трафика, а также доменное имя в SNI (пока не используется Encrypted Client Hello) и DNS-запросы (если не DoH/DoT). Путь URL, query string, заголовки и тело — зашифрованы.

Сертификат истёк ночью. Как не допустить?

Автоматизировать: ACME (Let's Encrypt) с cert-manager или встроенным продлением облака, сроки 90 дней и меньше (отрасль движется к 47 дням), мониторинг срока действия (алерт за 14–30 дней, blackbox exporter), горячая перезагрузка сертификата без рестарта.

Чем самоподписанный сертификат отличается от выданного CA?

Криптографически — ничем. Разница в доверии: самоподписанный никто не подписал, клиенту нечем проверить, что ключ принадлежит серверу. Для внутренних систем используют свой корпоративный CA, чей корень добавлен в доверенные на всех клиентах.

Итог

TLS защищает конфиденциальность, целостность и подлинность сервера: асимметрия для рукопожатия, симметрия для данных, сертификат с цепочкой доверия для идентификации. В ASP.NET Core — HTTPS-редирект и HSTS, TLS 1.2+, сертификаты от автоматизированного CA; при терминации на ingress — Forwarded Headers; никогда не отключайте проверку сертификатов в HttpClient, а для сервис-сервис рассматривайте mTLS или service mesh.