Рекомендации по протоколу TLS/SSL

TLS (транспортная безопасность) — это криптографический протокол, предназначенный для защиты связи между двумя компьютерами через Интернет. Протокол TLS предоставляется в .NET через SslStream класс.

В этой статье представлены рекомендации по настройке безопасного взаимодействия между клиентом и сервером и предполагается использование .NET. Рекомендации по использованию платформа .NET Framework см. в рекомендациях по протоколу TLS с помощью платформа .NET Framework.

Выбор версии TLS

Хотя можно указать версию протокола TLS для использования с помощью EnabledSslProtocols свойства, рекомендуется отложить параметры операционной системы с помощью None значения (это значение по умолчанию).

Отложение решения на ОС автоматически использует последнюю версию TLS и позволяет приложению выполнять изменения после обновления ОС. Операционная система также может предотвратить использование версий TLS, которые больше не считаются безопасными.

Выбор наборов шифров

SslStream позволяет пользователям указать, какие наборы шифров можно согласовывать с помощью подтверждения TLS через CipherSuitesPolicy класс. Как и в случае с версиями TLS, рекомендуется разрешить ОС решить, с какими из лучших наборов шифров можно вести переговоры, и поэтому рекомендуется избежать использования CipherSuitesPolicy.

Примечание.

CipherSuitesPolicy не поддерживается в Windows и пытается создать экземпляр, который приведет NotSupportedException к возникновению ошибки.

Указание сертификата сервера

При проверке подлинности в качестве сервера SslStream требуется X509Certificate2 экземпляр. Рекомендуется всегда использовать экземпляр, который также содержит закрытый X509Certificate2 ключ.

Существует несколько способов, которым можно передать SslStreamсертификат сервера:

Рекомендуемый подход — использовать SslServerAuthenticationOptions.ServerCertificateContext свойство. При получении сертификата одним из двух способов SslStreamCertificateContext экземпляр создается внутри SslStream реализации. SslStreamCertificateContext Создание предполагает создание X509Chain интенсивной операции ЦП. Более эффективно создать SslStreamCertificateContext один раз и повторно использовать его для нескольких SslStream экземпляров.

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

Настраиваемая X509Certificate проверка

Существуют определенные сценарии, в которых процедура проверки сертификатов по умолчанию не является достаточной, и требуется определенная пользовательская логика проверки. Части логики проверки можно настроить, SslClientAuthenticationOptions.CertificateChainPolicy указав или SslServerAuthenticationOptions.CertificateChainPolicy. Кроме того, полностью настраиваемую логику можно предоставить с помощью <свойства System.Net.Security.SslClientAuthenticationOptions.RemoteCertificateValidationCallback> . Дополнительные сведения см. в разделе "Доверие пользовательских сертификатов".

Доверие пользовательских сертификатов

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

Альтернативное решение заключается в указании пользовательских доверенных корневых сертификатов с помощью .X509ChainPolicy Чтобы указать настраиваемый список доверия, который будет использоваться вместо списка доверия системы во время проверки, рассмотрим следующий пример:

SslClientAuthenticationOptions clientOptions = new();

clientOptions.CertificateChainPolicy = new X509ChainPolicy()
{
    TrustMode = X509ChainTrustMode.CustomRootTrust,
    CustomTrustStore =
    {
        customIssuerCert
    }
};

Клиенты, настроенные с предыдущей политикой, будут принимать только доверенные customIssuerCertсертификаты.

Игнорировать определенные ошибки проверки

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

static bool CustomCertificateValidationCallback(
    object sender,
    X509Certificate? certificate,
    X509Chain? chain,
    SslPolicyErrors sslPolicyErrors)
{
    // Anything that would have been accepted by default is OK
    if (sslPolicyErrors == SslPolicyErrors.None)
    {
        return true;
    }
    
    // If there is something wrong other than a chain processing error, don't trust it.
    if (sslPolicyErrors != SslPolicyErrors.RemoteCertificateChainErrors)
    {
        return false;
    }
    
    Debug.Assert(chain is not null);

    // If the reason for RemoteCertificateChainError is that the chain built empty, don't trust it.
    if (chain.ChainStatus.Length == 0)
    {
        return false;
    }

    foreach (X509ChainStatus status in chain.ChainStatus)
    {
        // If an error other than `NotTimeValid` (or `NoError`) is present, don't trust it.
        if ((status.Status & ~X509ChainStatusFlags.NotTimeValid) != X509ChainStatusFlags.NoError)
        {
            return false;
        }
    }

    return true;
}

Привязка к сертификату

Другая ситуация, когда требуется проверка пользовательских сертификатов, заключается в том, что клиенты ожидают, что серверы будут использовать определенный сертификат или сертификат из небольшого набора известных сертификатов. Эта практика называется закреплением сертификатов. В следующем фрагменте кода показан обратный вызов проверки, который проверка, который сервер представляет сертификат с определенным открытым ключом.

static bool CustomCertificateValidationCallback(
    object sender,
    X509Certificate? certificate,
    X509Chain? chain,
    SslPolicyErrors sslPolicyErrors)
{
    // If there is something wrong other than a chain processing error, don't trust it.
    if ((sslPolicyErrors & ~SslPolicyErrors.RemoteCertificateChainErrors) != 0)
    {
        return false;
    }
    
    Debug.Assert(certificate is not null);

    const string ExpectedPublicKey =
        "3082010A0282010100C204ECF88CEE04C2B3D850D57058CC9318EB5C" +
        "A86849B022B5F9959EB12B2C763E6CC04B604C4CEAB2B4C00F80B6B0" +
        "F972C98602F95C415D132B7F71C44BBCE9942E5037A6671C618CF641" +
        "42C546D31687279F74EB0A9D11522621736C844C7955E4D16BE8063D" +
        "481552ADB328DBAAFF6EFF60954A776B39F124D131B6DD4DC0C4FC53" +
        "B96D42ADB57CFEAEF515D23348E72271C7C2147A6C28EA374ADFEA6C" +
        "B572B47E5AA216DC69B15744DB0A12ABDEC30F47745C4122E19AF91B" +
        "93E6AD2206292EB1BA491C0C279EA3FB8BF7407200AC9208D98C5784" +
        "538105CBE6FE6B5498402785C710BB7370EF6918410745557CF9643F" +
        "3D2CC3A97CEB931A4C86D1CA850203010001";

    return certificate.GetPublicKeyString().Equals(ExpectedPublicKey);
}

Рекомендации по проверке сертификата клиента

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

Необходимость связаться с внешними серверами при создании и проверке X509Chain приложения может привести к атакам типа "отказ в обслуживании", если внешние серверы медленно реагируют. Поэтому серверные приложения должны настроить поведение сборки X509Chain с помощью CertificateChainPolicy.