How to validate a SSL certificate with C#

12,127

Solution 1

This is partially just a repeat of c# Validating an X509Certificate2: am I doing this right?, the short form answer from there is that the checks you get for free (with no custom handler, or that are already done before your handler):

  • The certificate chain is valid (otherwise SslPolicyErrors.RemoteCertificateChainErrors is set)
    • The certificate chains up to a trusted root authority
    • The certificate is not expired (or from the future)
    • The certificate indicates that it is intended to be used as a TLS server certificate
    • If revocation was enabled on the request (it's off by default), no certs in the chain are revoked.
  • The certificate applies to the hostname you were connecting to (otherwise SslPolicyErrors.RemoteCertificateNameMismatch is set)

For example, is the root certificate also validated here and where it comes from?

The certificate verification here uses the same root trust list as Internet Explorer, Chrome, Edge, Outlook, et cetera use, which is centrally managed in Windows. (Firefox uses a different root trust list, which is managed only by Firefox.)

In your opinion ...

Opinion questions are off-topic on StackOverflow.

it still necessary (or useful) to add additionally my own checks to these checks, which the .NET Framework hasn't made yet?

That depends.

If you are contacting one of your own servers, and you know something to expect about the certificate chain, you could add an extra check. For example, you could know that your server uses Let's Encrypt, and you embed in your application both "Let’s Encrypt Authority X3" certificates from https://letsencrypt.org/certificates/ then you could pin the intermediate... you just need to be sure to react when they move to a new intermediate.

private bool ValidateRemoteCertificate(
    object sender,
    X509Certificate cert,
    X509Chain chain,
    SslPolicyErrors policyErrors)
{
    if (policyErrors != SslPolicyErrors.None)
    {
        return false;
    }

    if (chain.ChainElements.Count != 3)
    {
        return false;
    }

    byte[] foundCert = chain.ChainElements[1].Certificate.RawData;

    return s_letsEncryptX3Direct.SequenceEqual(foundCert) ||
        s_letsEncryptX3Cross.SequenceEqual(foundCert);
}

If you're not going to perform additional checks (or do custom logging, et cetera) then it's better to not even register the callback, because the implementation is a bit nicer with memory and CPU when there's no callback registered.

Solution 2

Just check chain status. There is already descriptive message:

    [Test]
    public async Task CheckWhatError()
    {
        var handler = new HttpClientHandler();
        handler.ServerCertificateCustomValidationCallback = ValidateRemoteCertificate;
        var client = new HttpClient(handler);
        try
        {
            using (var msg = new HttpRequestMessage(HttpMethod.Get, "https://expired.badssl.com/"))
            {
                using (var response = await client.SendAsync(msg))
                {
                    // do stuff with the response
                }
            }
        }
        catch (Exception ex)
        {
            throw;
        }
    }

    private bool ValidateRemoteCertificate(object sender, X509Certificate cert, X509Chain chain, SslPolicyErrors policyErrors)
    {
        if(policyErrors == SslPolicyErrors.RemoteCertificateChainErrors)
        {
            foreach(var cs in chain.ChainStatus)
            {
                Console.WriteLine(cs.StatusInformation);//validation errors here
            }
        }
        return policyErrors == SslPolicyErrors.None;
    }
Share:
12,127
Admin
Author by

Admin

Updated on July 05, 2022

Comments

  • Admin
    Admin almost 2 years

    maybe one of you can help me answer my question about validating certificates in the .NET framework. Unfortunately I found only superficial answers to my questions during my research on the Internet, but no exact explanation.

    Meanwhile I know that I can check in my software with the following code whether a certificate is valid.

    ServicePointManager.ServerCertificateValidationCallback += ValidateRemoteCertificate;
    
    private bool ValidateRemoteCertificate(object sender, X509Certificate cert, X509Chain chain, SslPolicyErrors policyErrors)
    {
       return policyErrors == SslPolicyErrors.None;
    }
    

    But I would now like to know what Microsoft is checking here exactly? What checks were performed by the framework to make the result SslPolicyErrors.None?

    For example, is the root certificate also validated here and where it comes from? If so, how?

    In your opinion is it still necessary (or useful) to add additionally my own checks to these checks, which the .NET Framework hasn't made yet?