如何验证根ca - cert证书(x509)链

本文关键字:cert 证书 x509 何验证 验证 ca | 更新日期: 2023-09-27 18:07:45

假设我有三个证书(Base64格式)

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

如何在c#中验证证书和证书路径/链?(这三个证书可能都不在我的计算机证书存储中)

Edit: 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)签名,则会有例外。这正是我想要的

如何验证根ca - cert证书(x509)链

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之后包含有关验证失败的附加信息。

Edit:这只会确保您的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映射的一个很好的资源。

你应该知道的另一件事是,有一些文章声称,对于椭圆曲线算法,微软希望使用R,S格式的密钥而不是DER格式的密钥。我试图将密钥转换为这种格式,但最终没有成功。我发现有必要使用DSASignatureFormat.Rfc3279DerSequence参数。

额外的证书检查,如&;not before&;和"not after",或者CRL和OCSP检查可以在链验证之外进行。