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

懵懂的女人 提交于 2020-01-05 07:02:36

问题


Using framework 4.5.1 and the following requirement, am I doing this right?

  1. the URL in the certificate must match the given 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?

    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;
    }

回答1:


If you're trying to validate that an HTTPS certificate is valid, HttpWebRequest can do that for you.

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()).

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.
  2. Any SubjectAlternativeName DNS entry is allowed to have a wildcard (*) in it. But subject CN values are not (and the .NET APIs don't indicate which type of name you got back).
  3. Name normalization for IDNA, et cetera.

In fact, the only thing that HttpWebRequest doesn't automatically do for you (unless you set the global) is check revocation. 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. 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().

You should try your validator out to make sure it works:

  • Pick your favorite https site and make sure it passes.
  • All of these should fail:
    • https://expired.badssl.com/
    • https://wrong.host.badssl.com/
    • https://untrusted-root.badssl.com/
    • https://revoked.badssl.com/


来源:https://stackoverflow.com/questions/41063774/c-sharp-validating-an-x509certificate2-am-i-doing-this-right

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!