I am using a custom server validation callback based on this example

private void InitServices(IHostBuilder builder, ...)
{
...
    builder
    .ConfigureServices((context, services) =>
    {
        services.AddHttpClient("myHttpClient", c =>
            {
                c.Timeout = TimeSpan.FromSeconds(httpClientTimeoutSeconds);
            })
            .ConfigurePrimaryHttpMessageHandler(() =>
            {
                 var handler = new HttpClientHandler();
                 var myPemCaCertificateString = File.ReadAllText(myPemCaFilePath);
                 var caRootBytes = Encoding.ASCII.GetBytes(myPemCaCertificateString);
                 myCaRootX509Certificate = new X509Certificate2(caRootBytes);
                 var myRootCertificates = new X509Certificate2Collection(myCaRootX509Certificate);

                 handler.ServerCertificateCustomValidationCallback = CreateCustomRootValidator(myRootCertificates);
         ...
            }

The validation method checks the chain.Build result and detects the SslPolicyErrors value and also the ChainElementStatus in case of failure. But the method can return only a bool value if the validation fails.

        private RemoteCertificateValidationCallback CreateCustomRootRemoteValidator(X509Certificate2Collection trustedRoots, X509Certificate2Collection intermediates = null)
        {
            if (trustedRoots == null)
                throw new ArgumentNullException(nameof(trustedRoots));
            if (trustedRoots.Count == 0)
                throw new ArgumentException("No trusted roots were provided", nameof(trustedRoots));

            X509Certificate2Collection roots = new X509Certificate2Collection(trustedRoots);
            X509Certificate2Collection intermeds = null;

            if (intermediates != null)
            {
                intermeds = new X509Certificate2Collection(intermediates);
            }

            intermediates = null;
            trustedRoots = null;

            return (sender, serverCert, chain, errors) =>
            {
                // Missing cert or the destination hostname wasn't valid for the cert.
                if ((errors & ~SslPolicyErrors.RemoteCertificateChainErrors) != 0)
                {
                    return false;
                }

                for (int i = 1; i < chain.ChainElements.Count; i++)
                {
                    chain.ChainPolicy.ExtraStore.Add(chain.ChainElements[i].Certificate);
                }

                if (intermeds != null)
                {
                    chain.ChainPolicy.ExtraStore.AddRange(intermeds);
                }

                chain.ChainPolicy.CustomTrustStore.Clear();
                chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
                chain.ChainPolicy.CustomTrustStore.AddRange(roots);
                var retChainBuild = chain.Build((X509Certificate2)serverCert);

                // Check if chain.Build returned false, that means ServerCertificateCustomValidation failed
                if (!retChainBuild)
                {
                    var nrChainElements = chain.ChainElements.Count;

                    if (nrChainElements > 0)
                    {
                        // the last element in the chain should be the root ca
                        X509ChainElement chainElement = chain.ChainElements[nrChainElements - 1];

                        foreach (X509ChainStatus status in chainElement.ChainElementStatus)
                        {
                            if (status.Status == X509ChainStatusFlags.PartialChain)
                            {
                            }
                            else if (status.Status == X509ChainStatusFlags.UntrustedRoot)
                            {
                            }
                        }
                    }
                }

                return retChainBuild;
            };
        }

If the custom implementation of the ServerCertificateCustomValidationCallback returns false, then the following message is available in the AuthenticationException that is thrown when using the HttpClient:

The remote certificate was rejected by the provided RemoteCertificateValidationCallback.

If the ServerCertificateCustomValidationCallback is not defined for the HttpClientHandler, the original Microsoft implementation returns the following message in the AuthenticationException:

The remote certificate is invalid because of errors in the certificate chain: UntrustedRoot

How can the information about the SslPolicyErrors type and also about the ChainElementStatus be passed to the AuthenticationException, in case of defining a ServerCertificateCustomValidationCallback for the HttpClientHandler?

0

There are 0 best solutions below