diff --git a/tests/Microsoft.Identity.Web.Test/CertificatesObserverTests.cs b/tests/Microsoft.Identity.Web.Test/CertificatesObserverTests.cs index 9d6f82844..bac961cc3 100644 --- a/tests/Microsoft.Identity.Web.Test/CertificatesObserverTests.cs +++ b/tests/Microsoft.Identity.Web.Test/CertificatesObserverTests.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Net; @@ -49,8 +50,12 @@ static void RemoveCertificate(X509Certificate2? certificate) var instance = "https://login.microsoftonline.com/"; var authority = instance + tenantId; - string certName = "CN=TestCert"; + string certName = $"CN=TestCert-{Guid.NewGuid():N}"; cert1 = CreateAndInstallCertificate(certName); + + // Verify certificate is properly installed in store with timeout + await VerifyCertificateInStoreAsync(cert1, TimeSpan.FromSeconds(5)); + var description = new CredentialDescription { SourceType = CredentialSource.StoreWithDistinguishedName, @@ -129,6 +134,9 @@ static void RemoveCertificate(X509Certificate2? certificate) // Change out the cert, so that if it reloads there will be a new one RemoveCertificate(cert1); cert2 = CreateAndInstallCertificate(certName); + + // Verify certificate is properly installed in store with timeout + await VerifyCertificateInStoreAsync(cert2, TimeSpan.FromSeconds(5)); // Rerun but it fails this time mockHttpFactory.ValidCertificates.Clear(); @@ -192,6 +200,35 @@ internal X509Certificate2 CreateAndInstallCertificate(string certName) return certWithPrivateKey; } + /// + /// Verifies that a certificate is properly installed in the certificate store. + /// + /// The certificate to verify. + /// Maximum time to wait for the certificate to appear in the store. + private static async Task VerifyCertificateInStoreAsync(X509Certificate2 certificate, TimeSpan timeout) + { + var stopwatch = Stopwatch.StartNew(); + var minWaitTime = TimeSpan.FromSeconds(2); // Minimum wait to ensure store operations complete + + do + { + using var store = new X509Store(StoreName.My, StoreLocation.CurrentUser); + store.Open(OpenFlags.ReadOnly); + + var foundCerts = store.Certificates.Find(X509FindType.FindByThumbprint, certificate.Thumbprint, false); + + if (foundCerts.Count > 0 && stopwatch.Elapsed >= minWaitTime) + { + return; // Certificate found and minimum wait time elapsed + } + + await Task.Delay(100); // Wait 100ms before checking again + } + while (stopwatch.Elapsed < timeout); + + throw new TimeoutException($"Certificate with thumbprint {certificate.Thumbprint} was not found in the certificate store within {timeout.TotalSeconds} seconds."); + } + private class TestCertificatesObserver : ICertificatesObserver { public Queue Events { get; } = new(); @@ -327,7 +364,7 @@ protected override Task SendAsync(HttpRequestMessage reques if (uri.StartsWith(kvp.Key, StringComparison.OrdinalIgnoreCase)) { if (this.description.Certificate == null || - !this.ValidCertificates.Contains(this.description.Certificate)) + !this.ValidCertificates.Any(cert => cert.Thumbprint.Equals(this.description.Certificate.Thumbprint, StringComparison.OrdinalIgnoreCase))) { var errorResponse = new {