简体   繁体   English

c# 验证 X509Certificate2:我这样做对吗?

[英]c# Validating an X509Certificate2: am I doing this right?

Using framework 4.5.1 and the following requirement, am I doing this right?使用框架 4.5.1 和以下要求,我这样做对吗?

  1. the URL in the certificate must match the given URL证书中的 URL 必须与给定的 URL 匹配
  2. the certificate must be valid and trusted证书必须有效且受信任
  3. the certificate must not be expired证书不得过期

The following passes, but is this sufficient?以下通过,但这是否足够?

In particular does the call to chain.Build(cert) satisfy #2 above?特别是对chain.Build(cert) 的调用是否满足上面的#2

    protected bool ValidateDigitalSignature(Uri uri)
    {
        bool isValid = false;
        X509Certificate2 cert = null;
        HttpWebRequest request = WebRequest.Create(uri) as HttpWebRequest;
        using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
        {
            response.Close();
        }

        isValid = (request.ServicePoint.Certificate != null);
        if(isValid)
            cert = new X509Certificate2(request.ServicePoint.Certificate);
        if (isValid)
        {
            X509Chain chain = new X509Chain();
            chain.ChainPolicy.RevocationMode = X509RevocationMode.Online;
            chain.ChainPolicy.RevocationFlag = X509RevocationFlag.EntireChain;
            chain.Build(cert);
            isValid = (chain.ChainStatus.Length == 0);
        }
        if (isValid)
        {
            var dnsName = cert.GetNameInfo(X509NameType.DnsName, false);

            isValid = (Uri.CheckHostName(dnsName) == UriHostNameType.Dns
                && uri.Host.Equals(dnsName, StringComparison.InvariantCultureIgnoreCase));
        }
        if (isValid)
        {
            //The certificate must not be expired
            DateTimeOffset today = DateTimeOffset.Now;
            isValid = (today >= cert.NotBefore && today <= cert.NotAfter);
        }
        return isValid;
    }

If you're trying to validate that an HTTPS certificate is valid, HttpWebRequest can do that for you.如果您尝试验证 HTTPS 证书是否有效,HttpWebRequest 可以为您执行此操作。

To make HttpWebRequest check the revocation status you need to set the global ServicePointManager.CheckCertificateRevocationList = true before calling GetResponse() (I think it's GetResponse, as opposed to the call to Create()).要使 HttpWebRequest 检查吊销状态,您需要在调用GetResponse()之前设置全局ServicePointManager.CheckCertificateRevocationList = true (我认为它是 GetResponse,而不是调用 Create())。

That will then check:然后将检查:

  • The certificate chains to a trusted root证书链到受信任的根
  • The certificate is not expired (and other such things)证书未过期(和其他类似的东西)
  • The request hostname matches what it should请求主机名匹配它应该的

Which is all three points that you asked about.这是你问的所有三点。 The hardest one is getting the hostname matching correct, because最难的是让主机名匹配正确,因为

  1. There can be several SubjectAlternativeName DNS entries, and there's not a good way to ask about them in .NET.可能有多个 SubjectAlternativeName DNS 条目,在 .NET 中没有很好的方法来询问它们。
  2. Any SubjectAlternativeName DNS entry is allowed to have a wildcard (*) in it.任何 SubjectAlternativeName DNS 条目都允许包含通配符 (*)。 But subject CN values are not (and the .NET APIs don't indicate which type of name you got back).但主题 CN 值不是(并且 .NET API 不会指示您返回的名称类型)。
  3. Name normalization for IDNA, et cetera. IDNA 等的名称标准化。

In fact, the only thing that HttpWebRequest doesn't automatically do for you (unless you set the global) is check revocation.事实上,HttpWebRequest 唯一不会自动为您做的事情(除非您设置全局)是检查撤销。 And you can do that via你可以通过

HttpWebRequest request = WebRequest.Create(uri) as HttpWebRequest;
request.ServerCertificateValidationCallback = ValidationCallback;

private static bool ValidationCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
    // Since you want to be more strict than the default, reject it if anything went wrong.
    if (sslPolicyErrors != SslPolicyErrors.None)
    {
        return false;
    }

    // If the chain didn't suppress any type of error, and revocation
    // was checked, then it's okay.
    if (chain.ChainPolicy.VerificationFlags == X509VerificationFlags.None &&
        chain.ChainPolicy.RevocationMode == X509RevocationMode.Online)
    {
        return true;
    }

    X509Chain newChain = new X509Chain();
    // change any other ChainPolicy options you want.
    X509ChainElementCollection chainElements = chain.ChainElements;

    // Skip the leaf cert and stop short of the root cert.
    for (int i = 1; i < chainElements.Count - 1; i++)
    {
        newChain.ChainPolicy.ExtraStore.Add(chainElements[i].Certificate);
    }

    // Use chainElements[0].Certificate since it's the right cert already
    // in X509Certificate2 form, preventing a cast or the sometimes-dangerous
    // X509Certificate2(X509Certificate) constructor.
    // If the chain build successfully it matches all our policy requests,
    // if it fails, it either failed to build (which is unlikely, since we already had one)
    // or it failed policy (like it's revoked).        
    return newChain.Build(chainElements[0].Certificate);
}

And, of note, as I put in the sample code here, you only need to check the return value of chain.Build(), because that will be false if any cert is expired or whatnot.而且,值得注意的是,正如我在此处输入的示例代码,您只需要检查chain.Build() 的返回值,因为如果任何证书过期或诸如此类,那将是错误的。 You also may want to check the root cert (or an intermediate, or whatever) out of the built chain for being an expected value (certificate pinning).您可能还想检查构建链中的根证书(或中间证书或其他)是否为预期值(证书固定)。

If the ServerCertificateValidationCallback returns false an exception is thrown on GetResponse().如果 ServerCertificateValidationCallback 返回 false,则会在 GetResponse() 上引发异常。

You should try your validator out to make sure it works:您应该尝试验证器以确保其正常工作:

With .NET 5 and higher, follow the example posted in https://github.com/dotnet/runtime/issues/39835 :对于 .NET 5 及更高版本,请遵循https://github.com/dotnet/runtime/issues/39835中发布的示例:

RemoteCertificateValidationCallback callback = (object sender, X509Certificate? certificate, X509Chain? chain, SslPolicyErrors sslPolicyErrors) =>
{
    if (sslPolicyErrors == SslPolicyErrors.None)
    {
        return true;
    }
    else if (sslPolicyErrors != SslPolicyErrors.RemoteCertificateChainErrors || chain is null || certificate is null)
    {
        // Name mismatch or no cert
        return false;
    }
    // name matches and there was a certificate

    // Only works with .NET 5 and higher, taken from
    // https://github.com/dotnet/runtime/issues/39835
    chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
    chain.ChainPolicy.CustomTrustStore.Add(/* Load your root certificate here */);
    chain.ChainPolicy.ExtraStore.Add(X509Certificate2.CreateFromPemFile(/* Load any intermediate certificates here */);
    return chain.Build(new X509Certificate2(certificate));
};

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM