Dynamic certificate update is failing in HTTPClient Handler for mutual authentication?

Tanul 1,281 Reputation points
2021-07-29T18:10:28.77+00:00

Team,

I have made an api in .net core 3.1 and created a custom HTTP Client Handler to manage dynamic certificates per http call but, the calls with only first certificate remains successful. Any call for other certificates gives authentication error from server side because ClientCertificates.Clear() is not working.

Startup.cs

services.AddHttpClient(Configuration["ClientName"], c =>
             {
                 c.BaseAddress = new Uri(Configuration["URL"]);
             }).ConfigurePrimaryHttpMessageHandler<RegistryHttpHandler>()


 service.AddSingleton<IHttpClient, HttpClient>(); //My custom http client class 

 service.AddTransient<RegistryHttpHandler>();  // Here life time doesn't matter because Http Client is singleton

The code for send async is:

 using (var client = _clientFactory.CreateClient(clientName))
                {
                    using (var response = await client.SendAsync(request))
                    {
                        if (response != null)
                        {
                            data= await response.Content.ReadAsStringAsync();
                        }                        
                    }
                }

The code for certificate handler is:

public class CertificateRegistryHttpHandler : HttpClientHandler, IDisposable
    {
        private readonly IConfiguration _configuration;
        private readonly Dictionary<string, X509Certificate2> _certMap;
        private bool disposedValue;
        public CertificateRegistryHttpHandler(IConfiguration configuration) : base()
        {
            _configuration = configuration;
            _certMap = new Dictionary<string, X509Certificate2>() { { configuration["DefaultCertificateName"], new X509Certificate2(fileName: Path.Combine(configuration["CertificatePath"], configuration["DefaultCertificateName"]), password: _configuration[configuration["DefaultCertificateName"]]) } };
        }

        protected override async Task<HttpResponseMessage> SendAsync(
                HttpRequestMessage request,
                CancellationToken cancellationToken)
        {
            var appId = (string)Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(request.RequestUri.Query)["myID"];
            string certName = _configuration[appId];
            if (!string.IsNullOrEmpty(certName))
            {
                string certPass = _configuration[certName];
                if (!string.IsNullOrEmpty(certPass))
                {
                    if (!_certMap.ContainsKey(certName))
                    {
                        string certPath = Path.Combine(_configuration["CertificatePath"], certName);
                        if (File.Exists(certPath))
                        {
                            _certMap.Add(certName, new X509Certificate2(fileName: certPath, password: certPass));
                            AddCertificate(certName);
                        }
                        else
                        {
                            AddCertificate(_configuration["DefaultCertificateName"]);
                        }
                    }
                    else
                    {
                        AddCertificate(certName);
                    }

                }
                else
                {
                    AddCertificate(_configuration["DefaultCertificateName"]);
                }
            }
            else
            {
                AddCertificate(_configuration["DefaultCertificateName"]);
            }
            return await base.SendAsync(request, cancellationToken);
        }
        private void AddCertificate(string certName)
        {
            if (_certMap[certName]!=null && !ClientCertificates.Contains(_certMap[certName]))
            {
                ClientCertificates.Clear();
                ClientCertificates.Add(_certMap[certName]);
            }
        }
        protected override void Dispose(bool disposing)
        {
            if (!disposedValue)
            {
                if (disposing)
                {
                    _certMap.Values.All(x =>
                    {
                        x.Dispose();
                        return true;
                    });
                    _certMap.Clear();
                }
                base.Dispose();
                disposedValue = true;
            }
        }
        public new void Dispose()
        {
            Dispose(disposing: true);
            GC.SuppressFinalize(this);
        }
    }
}

In debug mode, I have cleared the pervious certificate and added the new one. Still, httpclient is sending the request with the old one and server returns authentication error.

But, on waiting for around 3 minutes in the debug the request with updated certificate also works. Then again update the certificate and wait for around 3 minutes. Please suggest how to updated the certificates in the ClientCertificates collection of httpclienthandler immediately as I'm facing error in production environment.

ASP.NET Core
ASP.NET Core
A set of technologies in the .NET Framework for building web applications and XML web services.
4,540 questions
.NET Runtime
.NET Runtime
.NET: Microsoft Technologies based on the .NET software framework.Runtime: An environment required to run apps that aren't compiled to machine language.
1,155 questions
{count} votes

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.