3

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!

Jeremy Farmer
  • 425
  • 4
  • 14
  • does this help? https://stackoverflow.com/questions/44953894/how-can-you-verify-validity-of-an-https-ssl-certificate-in-net – sagar1025 Aug 11 '20 at 01:40

1 Answers1

4

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.

Kimberly
  • 2,657
  • 2
  • 16
  • 24
  • This was helpful, unfortunately I couldn't get your above example to work, the ChainStatus' length would always end up being 0 and return false, this did lead me to this article - https://stackoverflow.com/a/50807130/12082289 using that example I was able to get the verification working. Thanks for your help! and let me know if you have any idea why the Chain Status may have been coming back with a Length of 0 I'd be interested to get your example to work. – Jeremy Farmer Aug 11 '20 at 13:10
  • @JeremyFarmer Hmmm. It's old (& copied) code. Looking at the [src of the ChainStatus stuff](https://github.com/dotnet/runtime/blob/master/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Windows/ChainPal.GetChainStatusInformation.cs), it's not clear that the X509ChainStatusFlags.NoError is actually used in the success case; it looks like they took the simpler option of "no statuses ==> no errors". This part of the internals varies by platform and it's a bit on the edge of my understanding... If Build(certificate) returns true then I think you're good. – Kimberly Aug 11 '20 at 15:32
  • I did read elsewhere that "Build returns TRUE if ChainStatus.Length is 0 (no errors), or if ChainStatus.Length > 0 but we decided to ignore all those errors with some flags in ChainPolicy.VerificationFlags." I'll update the code to match the other answer, please let me know if the edit matched what actually works, for posterity. – Kimberly Aug 11 '20 at 15:33
  • 1
    Yup you nailed it, the edited is exactly what I ended up with, I really appreciate your help with this! – Jeremy Farmer Aug 11 '20 at 15:59