-
Notifications
You must be signed in to change notification settings - Fork 395
Description
Library version used
4.61.0
.NET version
.NET 8.0.23, Windows 10.0.20348
Scenario
ManagedIdentityClient - managed identity
Is this a new or an existing app?
No response
Issue description and reproduction steps
Issue details
In Managed Identity V2 scenarios, certificates are stored in the Windows certificate store and private keys are protected by KeyGuard.
If KeyGuard becomes inaccessible (due to machine restart, corruption, VBS failure), the certificate may remain while its private key reference is broken. This results in mTLS handshake failures caused by the inability to access the private key, producing errors like:
System.Net.Http.HttpRequestException: An error occurred while sending the request.
---> System.Net.Sockets.SocketException (10054): An existing connection was forcibly closed by the remote host.
Current logic only checks cert.HasPrivateKey and does not validate key accessibility before using a cached certificate. As a result, both persistent and in-memory caches may retain unusable certificates, leading to repeated failures and service disruptions with no automatic remediation.
Relevant code snippets
try {
using var rsa = cert.GetRSAPrivateKey();
byte[] data = Encoding.UTF8.GetBytes("test");
rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
} catch {
// On failure, delete cert from cert store and clear in-memory cache, fetch new cert
}Expected behavior
Before using cached certificates, MSAL should proactively validate the accessibility of the private key by attempting a signing operation. If validation fails, the certificate should be deleted from both persistent and in-memory caches, and a fresh certificate provisioned. Fallback to non-KeyGuard keys or retrying with exponential backoff should be avoided.
Identity provider
Microsoft Entra ID (Work and School accounts and Personal Microsoft accounts)
Regression
No response
Solution and workarounds
Add key accessibility checks at cache retrieval points in MtlsCertificateCache and WindowsPersistentCertificateCache. On validation failure, delete from all caches and trigger fresh certificate provisioning in managed identity scenarios.
Error Logs
this is the error
AL] True MSAL 4.61.0.0 MSAL.NetCore .NET 8.0.23 Microsoft Windows 10.0.20348 [2026-02-14 17:10:46Z - ffcf10a2-11ad-44f5-a544-b528876bb456] Starting [HttpManager] ExecuteAsync
[MSAL] True MSAL 4.61.0.0 MSAL.NetCore .NET 8.0.23 Microsoft Windows 10.0.20348 [2026-02-14 17:10:46Z - ffcf10a2-11ad-44f5-a544-b528876bb456] [HttpManager] Sending request. Method: POST. URI: https://centraluseuap.mtlsauth.microsoft.com/72f988bf-86f1-41af-91ab-2d7cd011db47/oauth2/v2.0/token. Binding Certificate: True. Endpoint: https://centraluseuap.mtlsauth.microsoft.com/72f988bf-86f1-41af-91ab-2d7cd011db47/oauth2/v2.0/token
[MSAL] True MSAL 4.61.0.0 MSAL.NetCore .NET 8.0.23 Microsoft Windows 10.0.20348 [2026-02-14 17:10:46Z - ffcf10a2-11ad-44f5-a544-b528876bb456] Finished [HttpManager] ExecuteAsync in 235 ms
[MSAL] True MSAL 4.61.0.0 MSAL.NetCore .NET 8.0.23 Microsoft Windows 10.0.20348 [2026-02-14 17:10:46Z - ffcf10a2-11ad-44f5-a544-b528876bb456] [ManagedIdentityRequest] Released managed identity request semaphore.
[MSAL] True MSAL 4.61.0.0 MSAL.NetCore .NET 8.0.23 Microsoft Windows 10.0.20348 [2026-02-14 17:10:47Z - ffcf10a2-11ad-44f5-a544-b528876bb456] MSAL.NetCore.4.61.0.0.MsalServiceException:
ErrorCode: managed_identity_unreachable_network
Microsoft.Identity.Client.MsalServiceException: An error occurred while sending the request.
---> System.Net.Http.HttpRequestException: An error occurred while sending the request.
---> System.IO.IOException: Unable to write data to the transport connection: An existing connection was forcibly closed by the remote host..
---> System.Net.Sockets.SocketException (10054): An existing connection was forcibly closed by the remote host.
at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.CreateException(SocketError error, Boolean forAsyncThrow)
at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.SendAsyncForNetworkStream(Socket socket, CancellationToken cancellationToken)
at System.Net.Sockets.Socket.SendAsyncForNetworkStream(ReadOnlyMemory1 buffer, SocketFlags socketFlags, CancellationToken cancellationToken) at System.Net.Sockets.NetworkStream.WriteAsync(ReadOnlyMemory1 buffer, CancellationToken cancellationToken)
at System.Net.Security.SslStream.WriteSingleChunk[TIOAdapter](ReadOnlyMemory1 buffer, CancellationToken cancellationToken) at System.Net.Security.SslStream.WriteAsyncInternal[TIOAdapter](ReadOnlyMemory1 buffer, CancellationToken cancellationToken)
at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](TStateMachine& stateMachine)
at System.Net.Security.SslStream.WriteAsyncInternal[TIOAdapter](ReadOnlyMemory1 buffer, CancellationToken cancellationToken) at System.Net.Http.HttpConnection.WriteToStreamAsync(ReadOnlyMemory1 source, Boolean async)
at System.Net.Http.HttpConnection.FlushAsync(Boolean async)
at System.Net.Http.HttpConnection.SendRequestContentAsync(HttpRequestMessage request, HttpContentWriteStream stream, Boolean async, CancellationToken cancellationToken)
at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](TStateMachine& stateMachine)
at System.Net.Http.HttpConnection.SendRequestContentAsync(HttpRequestMessage request, HttpContentWriteStream stream, Boolean async, CancellationToken cancellationToken)
at System.Net.Http.HttpConnection.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](TStateMachine& stateMachine)
at System.Net.Http.HttpConnection.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.SendWithVersionDetectionAndRetryAsync(HttpRequestMessage request, Boolean async, Boolean doRequestAuth, CancellationToken cancellationToken)
at System.Runtime.CompilerServices.AsyncTaskMethodBuilder1.AsyncStateMachineBox1.ExecutionContextCallback(Object s)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Runtime.CompilerServices.AsyncTaskMethodBuilder1.AsyncStateMachineBox1.MoveNext(Thread threadPoolThread)
at System.Runtime.CompilerServices.AsyncTaskMethodBuilder1.AsyncStateMachineBox1.MoveNext()
at System.Threading.Tasks.AwaitTaskContinuation.RunOrScheduleAction(IAsyncStateMachineBox box, Boolean allowInlining)
at System.Threading.Tasks.Task.RunContinuations(Object continuationObject)
at System.Threading.Tasks.Task1.TrySetResult(TResult result) at System.Runtime.CompilerServices.AsyncTaskMethodBuilder1.SetExistingTaskResult(Task1 task, TResult result) at System.Threading.Tasks.TaskCompletionSourceWithCancellation1.WaitWithCancellationAsync(CancellationToken cancellationToken)
at System.Runtime.CompilerServices.AsyncTaskMethodBuilder1.AsyncStateMachineBox1.ExecutionContextCallback(Object s)
at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Runtime.CompilerServices.AsyncTaskMethodBuilder1.AsyncStateMachineBox1.MoveNext(Thread threadPoolThread)
at System.Runtime.CompilerServices.AsyncTaskMethodBuilder1.AsyncStateMachineBox1.ExecuteFromThreadPool(Thread threadPoolThread)
at System.Threading.ThreadPoolWorkQueue.Dispatch()
at System.Threading.PortableThreadPool.WorkerThread.WorkerThreadStart()
--- End of stack trace from previous location ---
--- End of inner exception stack trace ---
at System.Net.Security.SslStream.g__CompleteWriteAsync|157_1[TIOAdapter](ValueTask writeTask, Byte[] bufferToReturn)
at System.Net.Security.SslStream.WriteAsyncInternal[TIOAdapter](ReadOnlyMemory1 buffer, CancellationToken cancellationToken) at System.Net.Http.HttpConnection.SendRequestContentAsync(HttpRequestMessage request, HttpContentWriteStream stream, Boolean async, CancellationToken cancellationToken) at System.Net.Http.HttpConnection.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken) --- End of inner exception stack trace --- at System.Net.Http.HttpConnection.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken) at System.Net.Http.HttpConnectionPool.SendWithVersionDetectionAndRetryAsync(HttpRequestMessage request, Boolean async, Boolean doRequestAuth, CancellationToken cancellationToken) at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken) at System.Net.Http.HttpClient.<SendAsync>g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken) at Microsoft.Identity.Client.Http.HttpManager.ExecuteAsync(Uri endpoint, IDictionary2 headers, HttpContent body, HttpMethod method, X509Certificate2 bindingCertificate, Func5 validateServerCert, ILoggerAdapter logger, CancellationToken cancellationToken) in C:\fix-mtls-handshake-failures-new\src\client\Microsoft.Identity.Client\Http\HttpManager.cs:line 257 at Microsoft.Identity.Client.Http.HttpManager.SendRequestAsync(Uri endpoint, IDictionary2 headers, HttpContent body, HttpMethod method, ILoggerAdapter logger, Boolean doNotThrow, X509Certificate2 bindingCertificate, Func`5 validateServerCert, CancellationToken cancellationToken, IRetryPolicy retryPolicy, Int32 retryCount) in C:\fix-mtls-handshake-failures-new\src\client\Microsoft.Identity.Client\Http\HttpManager.cs:line 85
Metadata
Metadata
Type
Projects
Status