Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,13 @@ internal interface IPersistentCertificateCache
/// latest valid binding for the alias in place.
/// </summary>
void Delete(string alias, ILoggerAdapter logger);

/// <summary>
/// Deletes all entries for the alias (best-effort).
/// Implementations should remove all bindings for the alias.
/// </summary>
/// <param name="alias"></param>
/// <param name="logger"></param>
void DeleteAllForAlias(string alias, ILoggerAdapter logger);
Comment thread
gladjohn marked this conversation as resolved.
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,58 @@ public override async Task<ManagedIdentityResponse> AuthenticateAsync(
{
// Capture the attestation token provider delegate before calling base
_attestationTokenProvider = parameters.AttestationTokenProvider;
return await base.AuthenticateAsync(parameters, cancellationToken).ConfigureAwait(false);

try
{
return await base.AuthenticateAsync(parameters, cancellationToken).ConfigureAwait(false);
}
catch (MsalServiceException ex) when (ex.ErrorCode == MsalError.ManagedIdentityUnreachableNetwork && IsSchanelFailure(ex))
{
_requestContext.Logger.Verbose(() =>
"[ImdsV2] SCHANNEL mTLS failure detected. Removing bad persisted cert and retrying with fresh mint.");

// Remove the bad cert from both caches
string certCacheKey = GetMtlsCertCacheKey();
try
{
if (_mtlsCache is MtlsBindingCache mtlsCache)
{
mtlsCache.RemoveBadCert(certCacheKey, _requestContext.Logger);
}
}
catch (Exception removalEx)
{
_requestContext.Logger.Verbose(() => $"[ImdsV2] Error removing bad cert: {removalEx.Message}");
Comment thread
gladjohn marked this conversation as resolved.
}

// Retry - will mint fresh cert since we just deleted the bad one
return await base.AuthenticateAsync(parameters, cancellationToken).ConfigureAwait(false);
}
}

/// <summary>
/// Detects if the exception was caused by a SCHANNEL failure during mTLS authentication,
/// which can occur if the client certificate becomes invalid.
/// </summary>
/// <param name="ex"></param>
/// <returns></returns>
private static bool IsSchanelFailure(MsalServiceException ex)
{
for (Exception e = ex; e != null; e = e.InnerException)
{
if (e is System.Net.Sockets.SocketException se &&
(se.ErrorCode == 10054 || se.SocketErrorCode == System.Net.Sockets.SocketError.ConnectionReset))
{
return true;
}

if (e is System.Security.Authentication.AuthenticationException)
{
return true;
}
}

return false;
}

private async Task<CertificateRequestResponse> ExecuteCertificateRequestAsync(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,5 +143,31 @@ public async Task<MtlsBindingInfo> GetOrCreateAsync(
_gates.Release(cacheKey);
}
}

/// <summary>
/// Removes a certificate from both in-memory and persistent cache when SCHANNEL rejects it.
/// </summary>
public void RemoveBadCert(string cacheKey, ILoggerAdapter logger)
{
try
{
_memory.Remove(cacheKey, logger);
logger?.Verbose(() => $"[PersistentCert] Removed bad cert from memory cache for '{cacheKey}'");
}
catch (Exception ex)
Comment thread
gladjohn marked this conversation as resolved.
{
logger?.Verbose(() => $"[PersistentCert] Error removing from memory cache: {ex.Message}");
}

try
{
_persisted.DeleteAllForAlias(cacheKey, logger);
logger?.Verbose(() => $"[PersistentCert] Removed bad cert from persistent cache for '{cacheKey}'");
}
catch (Exception ex)
{
logger?.Verbose(() => $"[PersistentCert] Error removing from persistent cache: {ex.Message}");
Comment thread
trwalke marked this conversation as resolved.
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,10 @@ public void Delete(string alias, ILoggerAdapter logger)
{
// no-op
}

public void DeleteAllForAlias(string alias, ILoggerAdapter logger)
{
// no-op
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,63 @@ public void Delete(string alias, ILoggerAdapter logger)
logVerbose: s => logger.Verbose(() => s));
}

public void DeleteAllForAlias(string alias, ILoggerAdapter logger)
{
// Best-effort: short, non-configurable timeout.
InterprocessLock.TryWithAliasLock(
alias,
timeout: TimeSpan.FromMilliseconds(300),
action: () =>
{
try
{
using var store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadWrite);

X509Certificate2[] items;
try
{
items = new X509Certificate2[store.Certificates.Count];
store.Certificates.CopyTo(items, 0);
}
catch (Exception ex)
{
logger.Verbose(() => "[PersistentCert] Store snapshot via CopyTo failed; falling back to enumeration. Details: " + ex.Message);
items = store.Certificates.Cast<X509Certificate2>().ToArray();
}

int removed = 0;

foreach (var existing in items)
{
try
{
if (!MsiCertificateFriendlyNameEncoder.TryDecode(existing.FriendlyName, out var decodedAlias, out _))
continue;
if (!StringComparer.Ordinal.Equals(decodedAlias, alias))
continue;

// Delete ALL certs for this alias
store.Remove(existing);
removed++;
logger?.Verbose(() => $"[PersistentCert] Deleted certificate from store for alias '{alias}'");
}
finally
{
existing.Dispose();
}
}

logger?.Verbose(() => $"[PersistentCert] DeleteAllForAlias completed for alias '{alias}'. Removed={removed}.");
Comment thread
gladjohn marked this conversation as resolved.
Outdated
}
catch (Exception ex)
{
logger.Verbose(() => "[PersistentCert] DeleteAllForAlias failed: " + ex.Message);
}
},
logVerbose: s => logger.Verbose(() => s));
}

/// <summary>
/// Deletes only certificates that are actually expired (NotAfter &lt; nowUtc),
/// scoped to the given alias (cache key) via FriendlyName.
Expand Down
Loading