Retry entire test for X.509 TimeoutTests instead of just X509Chain.Build
authorKevin Jones <kevin@vcsjones.com>
Fri, 25 Sep 2020 16:51:38 +0000 (12:51 -0400)
committerGitHub <noreply@github.com>
Fri, 25 Sep 2020 16:51:38 +0000 (09:51 -0700)
The TimeoutTests had functionality to retry the X509Chain.Build call if
it failed to improve the reliability of the tests. However, retrying
only the Build portion broke some assumptions about how long a chain
build would take.

If Build is invoked for a second time per the retry, however the
previous call to Build primed some of the AIA or CRL/OCSP caches, this
broke the assumption of how long a delayed fetch would take.

Instead, retry the entire test with a new chain every time so that
nothing is in the cache and the timing assumptions remain valid.

src/libraries/System.Security.Cryptography.X509Certificates/tests/RevocationTests/TimeoutTests.cs

index b44e22d..bda1930 100644 (file)
@@ -17,55 +17,51 @@ namespace System.Security.Cryptography.X509Certificates.Tests.RevocationTests
         [PlatformSpecific(TestPlatforms.Windows | TestPlatforms.Linux)]
         public static void RevocationCheckingDelayed(PkiOptions pkiOptions)
         {
-            CertificateAuthority.BuildPrivatePki(
-                pkiOptions,
-                out RevocationResponder responder,
-                out CertificateAuthority rootAuthority,
-                out CertificateAuthority intermediateAuthority,
-                out X509Certificate2 endEntityCert,
-                nameof(RevocationCheckingDelayed));
-
-            using (responder)
-            using (rootAuthority)
-            using (intermediateAuthority)
-            using (endEntityCert)
-            using (ChainHolder holder = new ChainHolder())
-            using (X509Certificate2 rootCert = rootAuthority.CloneIssuerCert())
-            using (X509Certificate2 intermediateCert = intermediateAuthority.CloneIssuerCert())
-            {
-                TimeSpan delay = TimeSpan.FromSeconds(8);
-
-                X509Chain chain = holder.Chain;
-                responder.ResponseDelay = delay;
-                responder.DelayedActions = RevocationResponder.DelayedActionsFlag.All;
-
-                // This needs to be greater than delay, but less than 2x delay to ensure
-                // that the time is a timeout for individual fetches, not a running total.
-                chain.ChainPolicy.UrlRetrievalTimeout = TimeSpan.FromSeconds(15);
-                chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
-                chain.ChainPolicy.CustomTrustStore.Add(rootCert);
-                chain.ChainPolicy.ExtraStore.Add(intermediateCert);
-                chain.ChainPolicy.RevocationMode = X509RevocationMode.Online;
-                chain.ChainPolicy.RevocationFlag = X509RevocationFlag.ExcludeRoot;
-
-                chain.ChainPolicy.DisableCertificateDownloads = true;
-
-                Stopwatch watch = Stopwatch.StartNew();
-                Assert.True(
-                    Retry(() =>
-                    {
-                        watch.Restart();
-                        return chain.Build(endEntityCert);
-                    }),
-                    $"chain.Build; Chain status: {chain.AllStatusFlags()}");
-                watch.Stop();
-
-                // There should be two network fetches, OCSP/CRL to intermediate to get leaf status,
-                // OCSP/CRL to root to get intermediate statuses. It should take at least 2x the delay
-                // plus other non-network time, so we can at least ensure it took as long as
-                // the delay for each fetch.
-                Assert.True(watch.Elapsed >= delay * 2, $"watch.Elapsed: {watch.Elapsed}");
-            }
+            RetryHelper.Execute(() => {
+                CertificateAuthority.BuildPrivatePki(
+                    pkiOptions,
+                    out RevocationResponder responder,
+                    out CertificateAuthority rootAuthority,
+                    out CertificateAuthority intermediateAuthority,
+                    out X509Certificate2 endEntityCert,
+                    nameof(RevocationCheckingDelayed));
+
+                using (responder)
+                using (rootAuthority)
+                using (intermediateAuthority)
+                using (endEntityCert)
+                using (ChainHolder holder = new ChainHolder())
+                using (X509Certificate2 rootCert = rootAuthority.CloneIssuerCert())
+                using (X509Certificate2 intermediateCert = intermediateAuthority.CloneIssuerCert())
+                {
+                    TimeSpan delay = TimeSpan.FromSeconds(8);
+
+                    X509Chain chain = holder.Chain;
+                    responder.ResponseDelay = delay;
+                    responder.DelayedActions = RevocationResponder.DelayedActionsFlag.All;
+
+                    // This needs to be greater than delay, but less than 2x delay to ensure
+                    // that the time is a timeout for individual fetches, not a running total.
+                    chain.ChainPolicy.UrlRetrievalTimeout = TimeSpan.FromSeconds(15);
+                    chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
+                    chain.ChainPolicy.CustomTrustStore.Add(rootCert);
+                    chain.ChainPolicy.ExtraStore.Add(intermediateCert);
+                    chain.ChainPolicy.RevocationMode = X509RevocationMode.Online;
+                    chain.ChainPolicy.RevocationFlag = X509RevocationFlag.ExcludeRoot;
+
+                    chain.ChainPolicy.DisableCertificateDownloads = true;
+
+                    Stopwatch watch = Stopwatch.StartNew();
+                    Assert.True(chain.Build(endEntityCert),  $"chain.Build; Chain status: {chain.AllStatusFlags()}");
+                    watch.Stop();
+
+                    // There should be two network fetches, OCSP/CRL to intermediate to get leaf status,
+                    // OCSP/CRL to root to get intermediate statuses. It should take at least 2x the delay
+                    // plus other non-network time, so we can at least ensure it took as long as
+                    // the delay for each fetch.
+                    Assert.True(watch.Elapsed >= delay * 2, $"watch.Elapsed: {watch.Elapsed}");
+                }
+            });
         }
 
         [Theory]
@@ -182,85 +178,83 @@ namespace System.Security.Cryptography.X509Certificates.Tests.RevocationTests
         [PlatformSpecific(TestPlatforms.Windows | TestPlatforms.Linux)]
         public static void RevocationCheckingNegativeTimeout(PkiOptions pkiOptions)
         {
-            CertificateAuthority.BuildPrivatePki(
-                pkiOptions,
-                out RevocationResponder responder,
-                out CertificateAuthority rootAuthority,
-                out CertificateAuthority intermediateAuthority,
-                out X509Certificate2 endEntityCert,
-                nameof(RevocationCheckingNegativeTimeout));
-
-            using (responder)
-            using (rootAuthority)
-            using (intermediateAuthority)
-            using (endEntityCert)
-            using (ChainHolder holder = new ChainHolder())
-            using (X509Certificate2 rootCert = rootAuthority.CloneIssuerCert())
-            using (X509Certificate2 intermediateCert = intermediateAuthority.CloneIssuerCert())
-            {
-                // Delay is more than the 15 second default.
-                TimeSpan delay = TimeSpan.FromSeconds(25);
+            RetryHelper.Execute(() => {
+                CertificateAuthority.BuildPrivatePki(
+                    pkiOptions,
+                    out RevocationResponder responder,
+                    out CertificateAuthority rootAuthority,
+                    out CertificateAuthority intermediateAuthority,
+                    out X509Certificate2 endEntityCert,
+                    nameof(RevocationCheckingNegativeTimeout));
+
+                using (responder)
+                using (rootAuthority)
+                using (intermediateAuthority)
+                using (endEntityCert)
+                using (ChainHolder holder = new ChainHolder())
+                using (X509Certificate2 rootCert = rootAuthority.CloneIssuerCert())
+                using (X509Certificate2 intermediateCert = intermediateAuthority.CloneIssuerCert())
+                {
+                    // Delay is more than the 15 second default.
+                    TimeSpan delay = TimeSpan.FromSeconds(25);
 
-                X509Chain chain = holder.Chain;
-                responder.ResponseDelay = delay;
-                responder.DelayedActions = RevocationResponder.DelayedActionsFlag.All;
+                    X509Chain chain = holder.Chain;
+                    responder.ResponseDelay = delay;
+                    responder.DelayedActions = RevocationResponder.DelayedActionsFlag.All;
 
-                chain.ChainPolicy.UrlRetrievalTimeout = TimeSpan.FromMinutes(-1);
-                chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
-                chain.ChainPolicy.CustomTrustStore.Add(rootCert);
-                chain.ChainPolicy.ExtraStore.Add(intermediateCert);
-                chain.ChainPolicy.RevocationMode = X509RevocationMode.Online;
-                chain.ChainPolicy.RevocationFlag = X509RevocationFlag.EndCertificateOnly;
+                    chain.ChainPolicy.UrlRetrievalTimeout = TimeSpan.FromMinutes(-1);
+                    chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
+                    chain.ChainPolicy.CustomTrustStore.Add(rootCert);
+                    chain.ChainPolicy.ExtraStore.Add(intermediateCert);
+                    chain.ChainPolicy.RevocationMode = X509RevocationMode.Online;
+                    chain.ChainPolicy.RevocationFlag = X509RevocationFlag.EndCertificateOnly;
 
-                chain.ChainPolicy.DisableCertificateDownloads = true;
+                    chain.ChainPolicy.DisableCertificateDownloads = true;
 
-                Assert.True(Retry(() => chain.Build(endEntityCert)), $"chain.Build; Chain status: {chain.AllStatusFlags()}");
-            }
+                    Assert.True(chain.Build(endEntityCert), $"chain.Build; Chain status: {chain.AllStatusFlags()}");
+                }
+            });
         }
 
         [Fact]
         [PlatformSpecific(TestPlatforms.Linux)]
         public static void AiaFetchDelayed()
         {
-            CertificateAuthority.BuildPrivatePki(
-                PkiOptions.OcspEverywhere,
-                out RevocationResponder responder,
-                out CertificateAuthority rootAuthority,
-                out CertificateAuthority intermediateAuthority,
-                out X509Certificate2 endEntityCert,
-                nameof(AiaFetchDelayed));
+            RetryHelper.Execute(() => {
+                CertificateAuthority.BuildPrivatePki(
+                    PkiOptions.OcspEverywhere,
+                    out RevocationResponder responder,
+                    out CertificateAuthority rootAuthority,
+                    out CertificateAuthority intermediateAuthority,
+                    out X509Certificate2 endEntityCert,
+                    nameof(AiaFetchDelayed));
+
+                using (responder)
+                using (rootAuthority)
+                using (intermediateAuthority)
+                using (endEntityCert)
+                using (ChainHolder holder = new ChainHolder())
+                using (X509Certificate2 rootCert = rootAuthority.CloneIssuerCert())
+                using (X509Certificate2 intermediateCert = intermediateAuthority.CloneIssuerCert())
+                {
+                    TimeSpan delay = TimeSpan.FromSeconds(1);
 
-            using (responder)
-            using (rootAuthority)
-            using (intermediateAuthority)
-            using (endEntityCert)
-            using (ChainHolder holder = new ChainHolder())
-            using (X509Certificate2 rootCert = rootAuthority.CloneIssuerCert())
-            using (X509Certificate2 intermediateCert = intermediateAuthority.CloneIssuerCert())
-            {
-                TimeSpan delay = TimeSpan.FromSeconds(1);
+                    X509Chain chain = holder.Chain;
+                    responder.ResponseDelay = delay;
+                    responder.DelayedActions = RevocationResponder.DelayedActionsFlag.All;
 
-                X509Chain chain = holder.Chain;
-                responder.ResponseDelay = delay;
-                responder.DelayedActions = RevocationResponder.DelayedActionsFlag.All;
+                    chain.ChainPolicy.UrlRetrievalTimeout = TimeSpan.FromSeconds(15);
+                    chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
+                    chain.ChainPolicy.CustomTrustStore.Add(rootCert);
+                    chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
 
-                chain.ChainPolicy.UrlRetrievalTimeout = TimeSpan.FromSeconds(15);
-                chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
-                chain.ChainPolicy.CustomTrustStore.Add(rootCert);
-                chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
+                    Stopwatch watch = Stopwatch.StartNew();
+                    Assert.True(chain.Build(endEntityCert), GetFlags(chain, endEntityCert.Thumbprint).ToString());
+                    watch.Stop();
 
-                Stopwatch watch = Stopwatch.StartNew();
-                Assert.True(
-                    Retry(() =>
-                    {
-                        watch.Restart();
-                        return chain.Build(endEntityCert);
-                    }),
-                    GetFlags(chain, endEntityCert.Thumbprint).ToString());
-                watch.Stop();
-
-                Assert.True(watch.Elapsed >= delay, $"watch.Elapsed: {watch.Elapsed}");
-            }
+                    Assert.True(watch.Elapsed >= delay, $"watch.Elapsed: {watch.Elapsed}");
+                }
+            });
         }
 
         [Fact]
@@ -308,18 +302,5 @@ namespace System.Security.Cryptography.X509Certificates.Tests.RevocationTests
             chain.ChainElements.OfType<X509ChainElement>().
                 Single(e => e.Certificate.Thumbprint == thumbprint).
                 ChainElementStatus.Aggregate((X509ChainStatusFlags)0, (a, e) => a | e.Status);
-
-        private static bool Retry(Func<bool> body, int times = 3)
-        {
-            for (int attempt = 0; attempt < times; attempt++)
-            {
-                if (body())
-                {
-                    return true;
-                }
-            }
-
-            return false;
-        }
     }
 }