Jeremy Farmer
Jeremy Farmer

Reputation: 547

C# verify server certificate with .pem file

I am seeing an issue with sending an http request to an SSL enabled API. The error message i get back is -

AuthenticationException: The remote certificate is invalid according to the validation procedure.

based on this request

using (HttpResponseMessage res = client.GetAsync("https://example.com").Result)
            {
                using (HttpContent content = res.Content)
                {
                    string data = content.ReadAsStringAsync().Result;
                    if (data != null)
                    {
                        Console.WriteLine(data);
                    }
                    else
                    {
                        Console.WriteLine("Nothing returned");
                    }
                }
            }

I've been given a .pem file to verify that the certificate that is being sent back is signed by our CA and having some trouble figuring out how to do that in C#

In python I'm able to resolve the certificate errors by passing the .pem file to the verify parameter e.g.

r = requests.post(url="https://example.com", headers=headers, verify='mypem.pem') 

Is there something equivalent in Dotnet Core 3's HttpClient?

Thanks for any assistance!

Upvotes: 3

Views: 4576

Answers (1)

Kimberly
Kimberly

Reputation: 2712

If you can't set up the cert as trusted for whatever reason, then you can bypass the certificate validation and verify the server yourself. It's much less elegant in .NET unfortunately, and this may not work on all platforms. Refer to this answer on bypass invalid SSL certificate in .net core for more discussion on that.

using (var httpClientHandler = new HttpClientHandler())
{
    // Override server certificate validation.
    httpClientHandler.ServerCertificateCustomValidationCallback = VerifyServerCertificate;
    // ^ if this throws PlatformNotSupportedException (on iOS?), then you have to use
    //httpClientHandler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
    // ^ docs: https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclienthandler.dangerousacceptanyservercertificatevalidator?view=netcore-3.0

    using (var client = new HttpClient(httpClientHandler))
    {
        // Make your request...
    }
}

I think this implementation of the callback does what you need, "pinning" the CA. From this answer to Force HttpClient to trust single Certificate, with more comments from me. EDIT: That answer's status checking wasn't working, but per this answer linked by Jeremy Farmer, the following approach should:

    static bool VerifyServerCertificate(HttpRequestMessage sender, X509Certificate2 certificate,
    X509Chain chain, SslPolicyErrors sslPolicyErrors)
    {
        try
        {
            // Possibly required for iOS? :
            //if (chain.ChainElements.Count == 0) chain.Build(certificate);
            // https://forums.xamarin.com/discussion/180066/httpclienthandler-servercertificatecustomvalidationcallback-receives-empty-certchain
            // ^ Sorry that thread is such a mess!  But please check it.
            
            // Without having your PEM I am not sure if this approach to loading the cert works, but there are other ways.  From the doc:
            // "This constructor creates a new X509Certificate2 object using a certificate file name. It supports binary (DER) encoding or Base64 encoding."
            X509Certificate2 ca = new X509Certificate2("mypem.pem");

            X509Chain chain2 = new X509Chain();
            chain2.ChainPolicy.ExtraStore.Add(ca);

            // "tell the X509Chain class that I do trust this root certs and it should check just the certs in the chain and nothing else"
            chain2.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority;

            // This setup does not have revocation information
            chain2.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;

            // Build the chain and verify
            var isValid = chain2.Build(certificate);
            var chainRoot = chain2.ChainElements[chain2.ChainElements.Count - 1].Certificate;
            isValid = isValid && chainRoot.RawData.SequenceEqual(ca.RawData);

            Debug.Assert(isValid == true);

            return isValid;
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex);
        }

        return false;
    }

Sorry I can't test this at the moment, but hope it helps a bit.

Upvotes: 4

Related Questions