如何验证根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)签名,则会有例外。这正是我想要的
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个元素组成。
- "TBS"(待签署)证书
- 签名算法
- 和签名值
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检查可以在链验证之外进行。