共用方式為


TLS/SSL 最佳做法

TLS (傳輸層安全性) 是一種密碼編譯通訊協定,旨在透過因特網保護兩部計算機之間的通訊。 TLS 通訊協定會透過 SslStream 類別在 .NET 中公開。

本文提供設定客戶端與伺服器之間安全通訊的最佳做法,並假設使用 .NET。 如需 .NET Framework 的最佳做法,請參閱 使用 .NET Framework 的傳輸層安全性 (TLS) 最佳做法

選取 TLS 版本

雖然您可以透過 EnabledSslProtocols 屬性指定要使用的 TLS 協議版本,但建議使用 None 值來遵循作業系統的設定(這是預設值)。

將決策交由 OS 自行決定會自動使用最新的 TLS 版本,並在 OS 升級後讓應用程式自動適應變更。 作系統也可能防止使用不再被視為安全的 TLS 版本。

選取加密套件

SslStream 可讓使用者透過 CipherSuitesPolicy 類別指定哪些加密套件可以在 TLS 交握時進行協商。 如同 TLS 版本,建議讓 OS 決定要與 交涉的最佳加密套件,因此建議避免使用 CipherSuitesPolicy

備註

CipherSuitesPolicy 在 Windows 上不受支援,而且嘗試具現化它會導致 NotSupportedException 擲回。

指定伺服器證書

作為伺服器進行驗證時,SslStream 需要一個 X509Certificate2 實例。 建議始終使用包含私鑰的 X509Certificate2 實例。

有多種方式可將伺服器憑證傳遞至 SslStream

建議的方法是使用 SslServerAuthenticationOptions.ServerCertificateContext 屬性。 當憑證由另外兩種方式之一取得時, SslStreamCertificateContext 實作會在 SslStream 內部建立實例。 建立SslStreamCertificateContext 牽涉到建置X509Chain,這是一個需要大量 CPU 運算的過程。 建立一個 SslStreamCertificateContext 並重複使用於多個 SslStream 實例會更有效率。

重複使用SslStreamCertificateContext實例還可以啟用其他功能,例如在Linux伺服器上的TLS會話恢復

自訂 X509Certificate 驗證

在某些情況下,預設憑證驗證程序不夠充分,而且需要一些自定義驗證邏輯。 您可以藉由指定 SslClientAuthenticationOptions.CertificateChainPolicySslServerAuthenticationOptions.CertificateChainPolicy來自定義驗證邏輯的一部分。 或者,您可以透過 <System.Net.Security.SslClientAuthenticationOptions.RemoteCertificateValidationCallback> 屬性來提供完全自定義邏輯。 如需詳細資訊,請參閱 自定義憑證信任

自定義憑證信任

當遇到計算機信任的任何證書頒發機構單位未核發的憑證時(包括自我簽署憑證),預設憑證驗證程式將會失敗。 解決此問題的其中一個可能方法是將必要的簽發者憑證新增至計算機的信任存放區。 不過,這可能會影響系統上的其他應用程式,而且不一定可行。

替代解決方案是透過 X509ChainPolicy 指定自定義信任的根憑證。 若要指定將在驗證期間使用而不是系統信任清單的自訂信任清單,請考慮下列範例:

SslClientAuthenticationOptions clientOptions = new();

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

使用上述原則設定的用戶端只會接受 所信任的 customIssuerCert憑證。

忽略特定的驗證錯誤

請考慮沒有持續性時鐘的IoT裝置。 開機後,裝置的時鐘會設定在過去的許多年前,因此,所有憑證都會被視為「尚未生效」。 考慮下列程式碼,該程式碼展示了一種驗證回呼函式的實作,其忽略了效期違規。

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建置行為。