diff --git a/tests/Microsoft.Identity.Web.Test/CertificateRetryCounterTests.cs b/tests/Microsoft.Identity.Web.Test/CertificateRetryCounterTests.cs
index f2bb31139..9e06aa41e 100644
--- a/tests/Microsoft.Identity.Web.Test/CertificateRetryCounterTests.cs
+++ b/tests/Microsoft.Identity.Web.Test/CertificateRetryCounterTests.cs
@@ -143,7 +143,7 @@ await Task.Run(async () =>
/// Regression test: Simulates the Agent Identities scenario where nested token acquisitions
/// with bad config should fail quickly, not hang.
///
- [Fact(Timeout = 5000)] // 5 second timeout - if it takes longer, it's likely hanging
+ [Fact(Timeout = 30000)] // 30 second timeout - generous to accommodate slow CI, but still validates no infinite hang
public async Task GetAuthenticationResultForAppAsync_WithBadClientId_CompletesQuickly()
{
// Arrange
diff --git a/tests/Microsoft.Identity.Web.Test/CertificatesObserverTests.cs b/tests/Microsoft.Identity.Web.Test/CertificatesObserverTests.cs
index d7f084287..353dc5283 100644
--- a/tests/Microsoft.Identity.Web.Test/CertificatesObserverTests.cs
+++ b/tests/Microsoft.Identity.Web.Test/CertificatesObserverTests.cs
@@ -2,14 +2,10 @@
// Licensed under the MIT License.
using System;
-using System.Collections.Concurrent;
using System.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
-using System.Runtime.ConstrainedExecution;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
@@ -53,8 +49,6 @@ static void RemoveCertificate(X509Certificate2? certificate)
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,
@@ -134,9 +128,6 @@ static void RemoveCertificate(X509Certificate2? certificate)
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();
mockHttpFactory.ValidCertificates.Add(cert2);
@@ -184,7 +175,8 @@ internal X509Certificate2 CreateAndInstallCertificate(string certName)
var req = new CertificateRequest($"CN={certName}", rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
#endif
- var cert = req.CreateSelfSigned(DateTimeOffset.Now, DateTimeOffset.Now.AddDays(1));
+ // Backdate NotBefore to avoid time-boundary races with FindByTimeValid.
+ var cert = req.CreateSelfSigned(DateTimeOffset.Now.AddMinutes(-5), DateTimeOffset.Now.AddDays(1));
byte[] bytes = cert.Export(X509ContentType.Pfx, (string?)null);
#pragma warning disable SYSLIB0057 // Type or member is obsolete
@@ -196,32 +188,30 @@ internal X509Certificate2 CreateAndInstallCertificate(string certName)
x509Store.Add(certWithPrivateKey);
x509Store.Close();
+ // X509Store.Add is synchronous — verify the cert is immediately findable
+ // using the same DN-based lookup that production code uses.
+ VerifyCertificateInStore(certWithPrivateKey);
+
return certWithPrivateKey;
}
///
- /// Verifies that a certificate is properly installed in the certificate store.
+ /// Verifies that a certificate is installed in the store using the same
+ /// distinguished-name lookup that the production credential loader uses.
+ /// X509Store.Add is synchronous, so no polling is needed.
///
- /// 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)
+ private static void VerifyCertificateInStore(X509Certificate2 certificate)
{
- 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 found = store.Certificates
+ .Find(X509FindType.FindBySubjectDistinguishedName, certificate.SubjectName.Name, false);
+ if (found.Count == 0)
{
- 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
+ throw new InvalidOperationException(
+ $"Test setup failure: certificate '{certificate.SubjectName.Name}' (thumbprint {certificate.Thumbprint}) " +
+ $"was not found in CurrentUser/My immediately after Add. The store may be in an unexpected state.");
}
- 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