共用方式為


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 屬性。 當憑證是由其他兩種方式之一取得時,SslStream 實作會在內部建立 SslStreamCertificateContext 執行個體。 建立 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 時需要連絡外部伺服器,在外部伺服器回應速度緩慢的情況下,應用程式便可能會受到拒絕服務的攻擊。 因此,伺服器應用程式應該使用 CertificateChainPolicy 來設定 X509Chain 建置行為。