簡體   English   中英

C# 如何驗證 Root-CA-Cert 證書 (x509) 鏈?

[英]C# How can I validate a Root-CA-Cert certificate (x509) chain?

假設我有三個證書(Base64 格式)

Root
 |
 --- CA
     |
     --- Cert (client/signing/whatever)

如何在 C# 中驗證證書和證書路徑/鏈? (所有這三個證書可能不在我的計算機證書商店中)

編輯:BouncyCastle 具有驗證功能。 但我盡量不使用任何第三方庫。

    byte[] b1 = Convert.FromBase64String(x509Str1);
    byte[] b2 = Convert.FromBase64String(x509Str2);
    X509Certificate cer1 = 
        new X509CertificateParser().ReadCertificate(b1);
    X509Certificate cer2 =
        new X509CertificateParser().ReadCertificate(b2);
    cer1.Verify(cer2.GetPublicKey());

如果 cer1 沒有被 cert2(CA 或 root)簽名,則會出現異常。 這正是我想要的。

X509Chain類旨在實現此目的,您甚至可以自定義它如何執行鏈構建過程。

static bool VerifyCertificate(byte[] primaryCertificate, IEnumerable<byte[]> additionalCertificates)
{
    var chain = new X509Chain();
    foreach (var cert in additionalCertificates.Select(x => new X509Certificate2(x)))
    {
        chain.ChainPolicy.ExtraStore.Add(cert);
    }

    // You can alter how the chain is built/validated.
    chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
    chain.ChainPolicy.VerificationFlags = X509VerificationFlags.IgnoreWrongUsage;

    // Do the validation.
    var primaryCert = new X509Certificate2(primaryCertificate);
    return chain.Build(primaryCert);
}

如果需要, X509Chain將包含有關Build() == false后驗證失敗的其他信息。

編輯:這只會確保您的CA有效。 如果要確保鏈條相同,可以手動檢查指紋。 您可以使用以下方法來確保認證鏈是正確的,它期望鏈的順序為: ..., INTERMEDIATE2, INTERMEDIATE1 (Signer of INTERMEDIATE2), CA (Signer of INTERMEDIATE1)

static bool VerifyCertificate(byte[] primaryCertificate, IEnumerable<byte[]> additionalCertificates)
{
    var chain = new X509Chain();
    foreach (var cert in additionalCertificates.Select(x => new X509Certificate2(x)))
    {
        chain.ChainPolicy.ExtraStore.Add(cert);
    }

    // You can alter how the chain is built/validated.
    chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
    chain.ChainPolicy.VerificationFlags = X509VerificationFlags.IgnoreWrongUsage;

    // Do the preliminary validation.
    var primaryCert = new X509Certificate2(primaryCertificate);
    if (!chain.Build(primaryCert))
        return false;

    // Make sure we have the same number of elements.
    if (chain.ChainElements.Count != chain.ChainPolicy.ExtraStore.Count + 1)
        return false;

    // Make sure all the thumbprints of the CAs match up.
    // The first one should be 'primaryCert', leading up to the root CA.
    for (var i = 1; i < chain.ChainElements.Count; i++)
    {
        if (chain.ChainElements[i].Certificate.Thumbprint != chain.ChainPolicy.ExtraStore[i - 1].Thumbprint)
            return false;
    }

    return true;
}

我無法測試這個,因為我沒有完整的CA鏈,所以最好調試並逐步完成代碼。

如果您在機器上的受信任 CA 存儲中沒有根證書,則X509Chain無法可靠地工作。

其他人會提倡使用充氣城堡。 我想避免為此任務引入另一個庫,所以我編寫了自己的庫。

RFC3280 第 4.1 節所示,證書是ASN1編碼結構,在其基礎級別僅由 3 個元素組成。

  1. “TBS”(待簽)證書
  2. 簽名算法
  3. 和簽名值
Certificate  ::=  SEQUENCE  {
     tbsCertificate TBSCertificate,
     signatureAlgorithm   AlgorithmIdentifier,
     signatureValue BIT STRING
}

C# 實際上有一個方便的工具來解析 ASN1,即System.Formats.Asn1.AsnDecoder

使用它,我們可以從證書中提取這 3 個元素來驗證鏈。

第一步是提取證書簽名,因為X509Certificate2類不公開此信息,而這是證書驗證所必需的。

提取簽名值部分的示例代碼:

public static byte[] Signature(
    this X509Certificate2 certificate,
    AsnEncodingRules encodingRules = AsnEncodingRules.BER)
{
    var signedData = certificate.RawDataMemory;
    AsnDecoder.ReadSequence(
        signedData.Span,
        encodingRules,
        out var offset,
        out var length,
        out _
    );

    var certificateSpan = signedData.Span[offset..(offset + length)];
    AsnDecoder.ReadSequence(
        certificateSpan,
        encodingRules,
        out var tbsOffset,
        out var tbsLength,
        out _
    );

    var offsetSpan = certificateSpan[(tbsOffset + tbsLength)..];
    AsnDecoder.ReadSequence(
        offsetSpan,
        encodingRules,
        out var algOffset,
        out var algLength,
        out _
    );

    return AsnDecoder.ReadBitString(
        offsetSpan[(algOffset + algLength)..],
        encodingRules,
        out _,
        out _
    );
}

下一步是提取 TBS 證書。 這是已簽名的原始數據。

提取 TBS 證書數據的示例代碼:

public static ReadOnlySpan<byte> TbsCertificate(
    this X509Certificate2 certificate,
    AsnEncodingRules encodingRules = AsnEncodingRules.BER)
{
    var signedData = certificate.RawDataMemory;
    AsnDecoder.ReadSequence(
        signedData.Span,
        encodingRules,
        out var offset,
        out var length,
        out _
    );

    var certificateSpan = signedData.Span[offset..(offset + length)];
    AsnDecoder.ReadSequence(
        certificateSpan,
        encodingRules,
        out var tbsOffset,
        out var tbsLength,
        out _
    );

    // include ASN1 4 byte header to get WHOLE TBS Cert
    return certificateSpan.Slice(tbsOffset - 4, tbsLength + 4);
}

您可能會注意到,在提取 TBS 證書時,我需要在數據中包含 ASN1 標頭,這是因為 TBS 證書的簽名包含此數據(這讓我惱火了一段時間)。

微軟有史以來第一次不妨礙我們的API設計,我們可以直接從X509Certificate2對象中獲取簽名算法 然后我們只需要決定我們將在多大程度上實施不同的哈希算法。

var signature = signed.Signature();
var tbs = signed.TbsCertificate();
var alg = signed.SignatureAlgorithm;

// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gpnap/a48b02b2-2a10-4eb0-bed4-1807a6d2f5ad
switch (alg)
{
    case { Value: var value } when value?.StartsWith("1.2.840.113549.1.1.") ?? false:
        return signedBy.GetRSAPublicKey()?.VerifyData(
            tbs,
            signature,
            value switch {
                "1.2.840.113549.1.1.11" => HashAlgorithmName.SHA256,
                "1.2.840.113549.1.1.12" => HashAlgorithmName.SHA384,
                "1.2.840.113549.1.1.13" => HashAlgorithmName.SHA512,
                _ => throw new UnsupportedSignatureAlgorithm(alg)
            },
            RSASignaturePadding.Pkcs1
        ) ?? false;
    case { Value: var value } when value?.StartsWith("1.2.840.10045.4.3.") ?? false:
        return signedBy.GetECDsaPublicKey()?.VerifyData(
            tbs,
            signature,
            value switch
            {
                "1.2.840.10045.4.3.2" => HashAlgorithmName.SHA256,
                "1.2.840.10045.4.3.3" => HashAlgorithmName.SHA384,
                "1.2.840.10045.4.3.4" => HashAlgorithmName.SHA512,
                _ => throw new UnsupportedSignatureAlgorithm(alg)
            },
            DSASignatureFormat.Rfc3279DerSequence
        ) ?? false;
    default: throw new UnsupportedSignatureAlgorithm(alg);
}

如上面的代碼所示, https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gpnap/a48b02b2-2a10-4eb0-bed4-1807a6d2f5ad是查看算法和 OID 映射的好資源.

您應該注意的另一件事是,有些文章聲稱對於橢圓曲線算法,Microsoft 需要R,S格式的密鑰而不是 DER 格式的密鑰。 我嘗試將密鑰轉換為這種格式,但最終沒有成功。 我發現有必要使用DSASignatureFormat.Rfc3279DerSequence參數。

除了鏈驗證之外,還可以進行額外的證書檢查,如“不在之前”和“不在之后”,或者 CRL 和 OCSP 檢查。

暫無
暫無

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

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