簡體   English   中英

C#/ .NET - 如何在我的應用程序中僅允許HTTPS的“自定義”Root-CA?

[英]C# / .NET - How to allow a “custom” Root-CA for HTTPS in my application (only)?

好的,這是我需要做的:

我的應用程序是用C#(.NET Framework 4.5)編寫的,需要通過HTTPS與我們的服務器通信。 我們的服務器使用由我們自己的Root-CA頒發的TLS / SSL證書。 Root CA雖然完全受我的應用程序信任, 但未安裝在系統的“受信任的根”證書存儲中。 因此,如果沒有進一步的工作,C#拒絕聯系服務器,因為無法驗證服務器的證書 - 正如預期的那樣。 注意:我們無法使用已安裝在系統中的Root-CA。

我該怎么做才能讓我的應用程序(安全地)聯系我們的服務器? 我知道C#提供了將Root-CA證書作為“受信任的根”安裝到系統證書存儲中的類。 不是我們想要做的! 這是因為(a)它向用戶顯示警告(並且過於技術性)警告,並且因為(b)它也會影響其他應用程序 - 這是我們不想要或不需要的。

所以我需要的是告訴C#/ .NET使用“自定義”(即特定於應用程序 )的證書集 - 而不是系統范圍的證書存儲 - 來驗證服務器證書的鏈。 整個證書鏈仍然需要正確驗證(包括撤銷列表!)。 只有我們的Root-CA需要被接受為我的應用程序的“受信任”根。

最好的方法是什么?

任何幫助將非常感激。 提前致謝!


BTW:我發現我可以使用ServicePointManager.ServerCertificateValidationCallback來安裝我自己的證書驗證功能。 確實有效。 但是這種方法並不好,因為現在我需要在我自己的代碼中手動完成整個證書驗證。 不過,我不想重新實現整個證書驗證過程(例如,下載和檢查CRL的等),在.NET框架中,這已經實現(和測試)。 這就像重新發明輪子一樣,永遠不能像已經存在的.NET實現那樣徹底地進行測試。

RemoteCertificateValidationCallback委托是解決方案的正確方法。 但是,我會在委托中使用不同的行為,而不是Olivier建議的行為。 這就是為什么:執行了太多不相關的檢查而相關的檢查沒有。

所以,詳細看一下這個問題:

首先,我們將考慮您的服務使用從商業CA購買的合法證書的情況(現在可能不是這種情況,但可能在將來)。 這意味着如果sslPolicyErrors參數顯示None標志,則立即返回True ,證書有效且沒有明顯的理由拒絕它。 僅當以下語句不嚴格時,才需要執行此步驟:

只有我們的Root-CA需要被接受為我的應用程序的“受信任”根。

否則,忽略第一步。

我們假設,該服務仍使用來自私有和不受信任的CA的證書。 在這種情況下,我們必須處理與證書鏈無關的錯誤,並且僅針對SSL會話。 因此,當調用RemoteCertificateValidationCallback委托時,我們將確保在sslPolicyErrors參數中不顯示RemoteCertificateNameMismatchRemoteCertificateNotAvailable標志。 如果其中任何一個出現,我們將拒絕連接而無需額外檢查。

我們假設這些標志都沒有出現。 此時,我們正確處理了特定於SSL的錯誤,並且只有證書鏈可能存在問題。

如果我們到目前為止,我們可以聲稱sslPolicyErrors參數包含RemoteCertificateChainErrors標志。 這可能意味着一切,我們必須進行額外的檢查。 您的根CA證書是常量。 這意味着我們可以檢查chain參數中的根證書,並將其與我們的常量(例如,根CA證書的指紋)進行比較。 如果比較失敗,我們會立即拒絕該證書,因為它不是您的證書,並且沒有明顯的理由信任由未知CA頒發的證書,並且可能存在其他鏈問題。

如果比較成功,那么我們就必須小心謹慎地處理。 我們必須執行另一個證書鏈引擎實例,並指示它收集任何鏈問題,只有UntrustedRoot錯誤。 這意味着如果SSL證書有其他問題(例如RevocationOffline,有效性,策略錯誤),我們將了解這一點,並將拒絕此證書。

下面的代碼是上面許多單詞的程序化實現:

using System;
using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;

namespace MyNamespace {
    class MyClass {
        Boolean ServerCertificateValidationCallback(Object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) {
            String rootCAThumbprint = ""; // write your code to get your CA's thumbprint

            // remove this line if commercial CAs are not allowed to issue certificate for your service.
            if ((sslPolicyErrors & (SslPolicyErrors.None)) > 0) { return true; }

            if (
                (sslPolicyErrors & (SslPolicyErrors.RemoteCertificateNameMismatch)) > 0 ||
                (sslPolicyErrors & (SslPolicyErrors.RemoteCertificateNotAvailable)) > 0
            ) { return false; }
            // get last chain element that should contain root CA certificate
            // but this may not be the case in partial chains
            X509Certificate2 projectedRootCert = chain.ChainElements[chain.ChainElements.Count - 1].Certificate;
            if (projectedRootCert.Thumbprint != rootCAThumbprint) {
                return false;
            }
            // execute certificate chaining engine and ignore only "UntrustedRoot" error
            X509Chain customChain = new X509Chain {
                ChainPolicy = {
                    VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority
                }
            };
            Boolean retValue = customChain.Build(chain.ChainElements[0].Certificate);
            // RELEASE unmanaged resources behind X509Chain class.
            customChain.Reset();
            return retValue;
        }
    }
}

此方法(名為委托)可以附加到ServicePointManager.ServerCertificateValidationCallback 代碼可能會被壓縮(例如,在一個IF語句中組合多個IF),我使用詳細版本來反映文本邏輯。

您可以使用該回調而不必重新發明輪子。

如果仔細看一下, 回調有多個參數

public delegate bool RemoteCertificateValidationCallback(
    object sender,
    X509Certificate certificate,
    X509Chain chain,
    SslPolicyErrors sslPolicyErrors
)

參數是通過.Net實現檢查的結果。 您可以使用以下代碼檢查唯一的問題是否是缺少的根CA:

// Check if the only error of the chain is the missing root CA,
// otherwise reject the given certificate.
if (chain.ChainStatus.Any(statusFlags => statusFlags.Status != X509ChainStatusFlags.UntrustedRoot))
    return false;

它將遍歷整個鏈並檢查所有狀態。 如果有任何其他東西,那么不信任的根(注意:枚舉有一個標志屬性)我們失敗了。 但如果唯一不好的是不受信任的根,你可以接受它。

但是你現在遇到的另一個問題是,你知道這個證書有一個不受信任的根,但你不知道這是否真的是你的證書(或任何其他證書)。

要確保這一點,您必須閱讀與應用程序一起存儲的服務器證書的公共部分,並將其與給定鏈進行比較:

// Read CA certificate from file.
var now = DateTime.UtcNow;
var certificateAuthority = new X509Certificate(_ServerCertificateLocation);
var caEffectiveDate = DateTime.Parse(certificateAuthority.GetEffectiveDateString());
var caExpirationDate = DateTime.Parse(certificateAuthority.GetExpirationDateString());

// Check if CA certificate is valid.
if (now <= caEffectiveDate
    || now > caExpirationDate)
    return false;

// Check if CA certificate is available in the chain.
return chain.ChainElements.Cast<X509ChainElement>()
                          .Select(element => element.Certificate)
                          .Where(chainCertificate => chainCertificate.Subject == certificateAuthority.Subject)
                          .Where(chainCertificate => chainCertificate.GetRawCertData().SequenceEqual(certificateAuthority.GetRawCertData()))
                          .Any();

如果服務器提供由已安裝的根CA(可能不是您的CA)簽名的證書,那么在函數開頭添加快速退出可能是明智的:

if (sslPolicyErrors == SslPolicyErrors.None
    && chain.ChainStatus.All(statusFlags => statusFlags.Status == X509ChainStatusFlags.NoError))
    return true;

此外,如果需要(根據您的需要),您可以允許或禁止使用證書,其中服務器名稱和證書名稱不匹配。 發生這種情況,例如,如果證書是為localhost制作的,並且您從另一台計算機訪問服務器。 或者在Intranet中,您只使用http://myserver而不是http://myserver.domain.com ,但證書是使用完全限定名稱(反之亦然):

if (sslPolicyErrors == SslPolicyErrors.RemoteCertificateNameMismatch)
    return false;

就是這樣。 您仍然依賴默認實現,只需另外檢查您的部件。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM