From 9462f0b728fc8bb7bbebe970944aa62ddb29b390 Mon Sep 17 00:00:00 2001 From: Kevin Jones Date: Thu, 24 Sep 2020 11:13:23 -0400 Subject: [PATCH] Retry entire test for X509 TimeoutTests. 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. --- .../tests/RevocationTests/TimeoutTests.cs | 235 ++++++++---------- 1 file changed, 108 insertions(+), 127 deletions(-) diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/tests/RevocationTests/TimeoutTests.cs b/src/libraries/System.Security.Cryptography.X509Certificates/tests/RevocationTests/TimeoutTests.cs index b44e22d02980fc..bda193030e40a5 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/tests/RevocationTests/TimeoutTests.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/tests/RevocationTests/TimeoutTests.cs @@ -17,55 +17,51 @@ public static class TimeoutTests [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 @@ public static void RevocationCheckingMaximum(PkiOptions pkiOptions) [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 @@ private static X509ChainStatusFlags GetFlags(X509Chain chain, string thumbprint) chain.ChainElements.OfType(). Single(e => e.Certificate.Thumbprint == thumbprint). ChainElementStatus.Aggregate((X509ChainStatusFlags)0, (a, e) => a | e.Status); - - private static bool Retry(Func body, int times = 3) - { - for (int attempt = 0; attempt < times; attempt++) - { - if (body()) - { - return true; - } - } - - return false; - } } }