Team,
I have build an API in .net core 3.1 with custom certificate handler for dynamically updating the certificate in sendasync request for mutual TLS authentication. The api app service deployment type is framework dependent.
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);
}
}
}
Every time I'm sending the request it returns the correct output but In addition, I'm receiving this exception i.e.
System.AggregateException: One or more errors occurred. (The SSL connection could not be established, see inner exception.) ---> System.Net.Http.HttpRequestException: The SSL connection could not be established, see inner exception. ---> System.ArgumentNullException: Value cannot be null. (Parameter 'value') at System.Collections.CollectionBase.OnValidate(Object value) at System.Security.Cryptography.X509Certificates.X509CertificateCollection.OnValidate(Object value) at System.Collections.CollectionBase.System.Collections.IList.Add(Object value) at System.Security.Cryptography.X509Certificates.X509CertificateCollection.AddRange(X509CertificateCollection value) at System.Net.Security.CertificateHelper.GetEligibleClientCertificate(X509CertificateCollection candidateCerts) at System.Net.Http.HttpClientHandler.b_70_0(Object sender, String targetHost, X509CertificateCollection localCertificates, X509Certificate remoteCertificate, String[] acceptableIssuers) at System.Net.Security.SslStream.UserCertSelectionCallbackWrapper(String targetHost, X509CertificateCollection localCertificates, X509Certificate remoteCertificate, String[] acceptableIssuers) at System.Net.Security.SecureChannel.AcquireClientCredentials(Byte[]& thumbPrint) at System.Net.Security.SecureChannel.GenerateToken(Byte[] input, Int32 offset, Int32 count, Byte[]& output) at System.Net.Security.SecureChannel.NextMessage(Byte[] incoming, Int32 offset, Int32 count) at System.Net.Security.SslStream.StartSendBlob(Byte[] incoming, Int32 count, AsyncProtocolRequest asyncRequest) at System.Net.Security.SslStream.ForceAuthentication(Boolean receiveFirst, Byte[] buffer, AsyncProtocolRequest asyncRequest) at System.Net.Security.SslStream.ProcessAuthentication(LazyAsyncResult lazyResult, CancellationToken cancellationToken) at System.Net.Security.SslStream.BeginAuthenticateAsClient(SslClientAuthenticationOptions sslClientAuthenticationOptions, CancellationToken cancellationToken, AsyncCallback asyncCallback, Object asyncState) at System.Net.Security.SslStream.<>c.b65_0(SslClientAuthenticationOptions arg1, CancellationToken arg2, AsyncCallback callback, Object state) at System.Threading.Tasks.TaskFactory`1.FromAsyncImpl[TArg1,TArg2](Func`5 beginMethod, Func`2 endFunction, Action`1 endAction, TArg1 arg1, TArg2 arg2, Object state, TaskCreationOptions creationOptions) at System.Threading.Tasks.TaskFactory.FromAsync[TArg1,TArg2](Func`5 beginMethod, Action`1 endMethod, TArg1 arg1, TArg2 arg2, Object state, TaskCreationOptions creationOptions) at System.Threading.Tasks.TaskFactory.FromAsync[TArg1,TArg2](Func`5 beginMethod, Action`1 endMethod, TArg1 arg1, TArg2 arg2, Object state) at System.Net.Security.SslStream.AuthenticateAsClientAsync(SslClientAuthenticationOptions sslClientAuthenticationOptions, CancellationToken cancellationToken) at System.Net.Http.ConnectHelper.EstablishSslConnectionAsyncCore(Stream stream, SslClientAuthenticationOptions sslOptions, CancellationToken cancellationToken) --- End of inner exception stack trace --- at System.Net.Http.ConnectHelper.EstablishSslConnectionAsyncCore(Stream stream, SslClientAuthenticationOptions sslOptions, CancellationToken cancellationToken) at System.Net.Http.HttpConnectionPool.ConnectAsync(HttpRequestMessage request, Boolean allowHttp2, CancellationToken cancellationToken) at System.Net.Http.HttpConnectionPool.CreateHttp11ConnectionAsync(HttpRequestMessage request, CancellationToken cancellationToken) at System.Net.Http.HttpConnectionPool.GetHttpConnectionAsync(HttpRequestMessage request, CancellationToken cancellationToken) at System.Net.Http.HttpConnectionPool.SendWithRetryAsync(HttpRequestMessage request, Boolean doRequestAuth, CancellationToken cancellationToken) at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) at System.Net.Http.DiagnosticsHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) at Middlewares.CertificateRegistryHttpHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) at Microsoft.Extensions.Http.Logging.LoggingHttpMessageHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) at Polly.CircuitBreaker.AsyncCircuitBreakerEngine.ImplementationAsync[TResult](Func`3 action, Context context, CancellationToken cancellationToken, Boolean continueOnCapturedContext, ExceptionPredicates shouldHandleExceptionPredicates, ResultPredicates`1 shouldHandleResultPredicates, ICircuitController`1 breakerController) at Polly.AsyncPolicy`1.ExecuteAsync(Func`3 action, Context context, CancellationToken cancellationToken, Boolean continueOnCapturedContext) at Polly.Wrap.AsyncPolicyWrapEngine.<>cDisplayClass0_0`1.<b0>d.MoveNext() --- End of stack trace from previous location where exception was thrown --- at Polly.Fallback.AsyncFallbackEngine.ImplementationAsync[TResult](Func`3 action, Context context, CancellationToken cancellationToken, ExceptionPredicates shouldHandleExceptionPredicates, ResultPredicates`1 shouldHandleResultPredicates, Func`3 onFallbackAsync, Func`4 fallbackAction, Boolean continueOnCapturedContext) at Polly.AsyncPolicy`1.ExecuteAsync(Func`3 action, Context context, CancellationToken cancellationToken, Boolean continueOnCapturedContext) at Polly.Wrap.AsyncPolicyWrapEngine.ImplementationAsync[TResult](Func`3 func, Context context, CancellationToken cancellationToken, Boolean continueOnCapturedContext, IAsyncPolicy`1 outerPolicy, IAsyncPolicy`1 innerPolicy) at Polly.AsyncPolicy`1.ExecuteAsync(Func`3 action, Context context, CancellationToken cancellationToken, Boolean continueOnCapturedContext) at Microsoft.Extensions.Http.PolicyHttpMessageHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) at Microsoft.Extensions.Http.Logging.LoggingScopeHttpMessageHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) at System.Net.Http.HttpClient.FinishSendAsyncBuffered(Task`1 sendTask, HttpRequestMessage request, CancellationTokenSource cts, Boolean disposeCts) at Services.HttpClient.Get(String url, String clientName) --- End of inner exception stack trace --- at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions) at System.Threading.Tasks.Task`1.GetResultCore(Boolean waitCompletionNotification) at Services.SerlializeService.GetData[T](String queryStringParam, String clientName) at Secure.Cyber.CyberResponse.RequestCyber_Credentials(String objectName, String safe, String user) at AutomationGateway.Controllers.CyberCredentialsController.Credentials(String safe, String objectname) at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.TaskOfIActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.gAwaited|12_0(ControllerActionInvoker invoker, ValueTask`1 actionResultValueTask) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.gAwaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync() --- End of stack trace from previous location where exception was thrown --- at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.gAwaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.gAwaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope) at Microsoft.AspNetCore.Routing.EndpointMiddleware.gAwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger) at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context) at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.g_Awaited|6_0(ExceptionHandlerMiddleware middleware, HttpContext context, Task task)
Generally SSL exception comes with System.Security.Authentication.AuthenticationException. In that case we can understand that https negotiation is not going through properly. But, I'm unable to figure out why it is coming with System.ArgumentNullException due to which unable to find out any resolution. I would be grateful if some can help on this. Thank you.
Regards,