public static void VerifyCrlCache()
        {
            string crlDirectory = PersistedFiles.GetUserFeatureDirectory("cryptography", "crls");
            string crlFile      = Path.Combine(crlDirectory, MicrosoftDotComRootCrlFilename);

            Directory.CreateDirectory(crlDirectory);
            File.Delete(crlFile);

            using (var microsoftDotComIssuer = new X509Certificate2(TestData.MicrosoftDotComIssuerBytes))
                using (var microsoftDotComRoot = new X509Certificate2(TestData.MicrosoftDotComRootBytes))
                    using (var unrelated = new X509Certificate2(TestData.DssCer))
                        using (var chainHolder = new ChainHolder())
                        {
                            X509Chain chain = chainHolder.Chain;

                            chain.ChainPolicy.ExtraStore.Add(unrelated);
                            chain.ChainPolicy.ExtraStore.Add(microsoftDotComRoot);

                            // The very start of the CRL period.
                            chain.ChainPolicy.VerificationTime   = new DateTime(2015, 6, 17, 0, 0, 0, DateTimeKind.Utc);
                            chain.ChainPolicy.RevocationMode     = X509RevocationMode.NoCheck;
                            chain.ChainPolicy.RevocationFlag     = X509RevocationFlag.EndCertificateOnly;
                            chain.ChainPolicy.VerificationFlags |= X509VerificationFlags.AllowUnknownCertificateAuthority;

                            bool valid = chain.Build(microsoftDotComIssuer);
                            Assert.True(valid, "Precondition: Chain builds with no revocation checks");

                            int initialErrorCount = chain.ChainStatus.Length;
                            Assert.InRange(initialErrorCount, 0, 1);

                            X509ChainStatusFlags initialFlags = chain.AllStatusFlags();

                            if (initialFlags != X509ChainStatusFlags.NoError)
                            {
                                Assert.Equal(X509ChainStatusFlags.UntrustedRoot, initialFlags);
                            }

                            chainHolder.DisposeChainElements();

                            chain.ChainPolicy.RevocationMode = X509RevocationMode.Offline;

                            valid = chain.Build(microsoftDotComIssuer);
                            Assert.False(valid, "Chain should not build validly");

                            const X509ChainStatusFlags UnknownOffline =
                                X509ChainStatusFlags.RevocationStatusUnknown | X509ChainStatusFlags.OfflineRevocation;

                            Assert.Equal(initialFlags | UnknownOffline, chain.AllStatusFlags());

                            File.WriteAllText(crlFile, MicrosoftDotComRootCrlPem, Encoding.ASCII);

                            chainHolder.DisposeChainElements();

                            valid = chain.Build(microsoftDotComIssuer);
                            Assert.True(valid, "Chain should build validly now");
                            Assert.Equal(initialErrorCount, chain.ChainStatus.Length);
                        }
        }
예제 #2
0
        public static void CustomTrustModeWithNoCustomTrustCerts()
        {
            TestDataGenerator.MakeTestChain3(
                out X509Certificate2 endEntityCert,
                out X509Certificate2 intermediateCert,
                out X509Certificate2 rootCert);

            using (endEntityCert)
                using (intermediateCert)
                    using (rootCert)
                        using (ChainHolder chainHolder = new ChainHolder())
                        {
                            X509Chain chain = chainHolder.Chain;
                            chain.ChainPolicy.RevocationMode   = X509RevocationMode.NoCheck;
                            chain.ChainPolicy.VerificationTime = endEntityCert.NotBefore.AddSeconds(1);
                            chain.ChainPolicy.TrustMode        = X509ChainTrustMode.CustomRootTrust;

                            if (PlatformDetection.IsAndroid)
                            {
                                // Android does not support an empty custom root trust
                                Assert.Throws <PlatformNotSupportedException>(() => chain.Build(endEntityCert));
                            }
                            else
                            {
                                Assert.False(chain.Build(endEntityCert));
                                Assert.Equal(1, chain.ChainElements.Count);
                                Assert.Equal(X509ChainStatusFlags.PartialChain, chain.AllStatusFlags());
                            }
                        }
        }
예제 #3
0
        public static void CustomRootTrustDoesNotTrustIntermediates(
            bool saveAllInCustomTrustStore,
            X509ChainStatusFlags chainFlags)
        {
            TestDataGenerator.MakeTestChain3(
                out X509Certificate2 endEntityCert,
                out X509Certificate2 intermediateCert,
                out X509Certificate2 rootCert);

            using (endEntityCert)
                using (intermediateCert)
                    using (rootCert)
                        using (ChainHolder chainHolder = new ChainHolder())
                        {
                            X509Chain chain = chainHolder.Chain;
                            chain.ChainPolicy.RevocationMode   = X509RevocationMode.NoCheck;
                            chain.ChainPolicy.VerificationTime = endEntityCert.NotBefore.AddSeconds(1);
                            chain.ChainPolicy.TrustMode        = X509ChainTrustMode.CustomRootTrust;
                            chain.ChainPolicy.CustomTrustStore.Add(intermediateCert);

                            if (saveAllInCustomTrustStore)
                            {
                                chain.ChainPolicy.CustomTrustStore.Add(rootCert);
                            }
                            else
                            {
                                chain.ChainPolicy.ExtraStore.Add(rootCert);
                            }

                            Assert.Equal(saveAllInCustomTrustStore, chain.Build(endEntityCert));
                            Assert.Equal(3, chain.ChainElements.Count);
                            Assert.Equal(chainFlags, chain.AllStatusFlags());
                        }
        }
예제 #4
0
        public static void EmptyAiaResponseIsIgnored()
        {
            CertificateAuthority.BuildPrivatePki(
                PkiOptions.AllRevocation,
                out RevocationResponder responder,
                out CertificateAuthority root,
                out CertificateAuthority intermediate,
                out X509Certificate2 endEntity,
                pkiOptionsInSubject: false);

            using (responder)
                using (root)
                    using (intermediate)
                        using (endEntity)
                            using (ChainHolder holder = new ChainHolder())
                                using (X509Certificate2 rootCert = root.CloneIssuerCert())
                                    using (X509Certificate2 intermediateCert = intermediate.CloneIssuerCert())
                                    {
                                        responder.RespondEmpty = true;

                                        X509Chain chain = holder.Chain;
                                        chain.ChainPolicy.TrustMode           = X509ChainTrustMode.CustomRootTrust;
                                        chain.ChainPolicy.VerificationTime    = endEntity.NotBefore.AddMinutes(1);
                                        chain.ChainPolicy.UrlRetrievalTimeout = DynamicRevocationTests.s_urlRetrievalLimit;
                                        chain.ChainPolicy.RevocationMode      = X509RevocationMode.NoCheck;

                                        Assert.False(chain.Build(endEntity));
                                        X509ChainStatusFlags chainFlags = chain.AllStatusFlags();
                                        Assert.True(chainFlags.HasFlag(X509ChainStatusFlags.PartialChain), $"expected partial chain flags, got {chainFlags}");
                                    }
        }
예제 #5
0
        public static void MismatchKeyIdentifiers()
        {
            X509Extension[] intermediateExtensions = new [] {
                new X509BasicConstraintsExtension(
                    certificateAuthority: true,
                    hasPathLengthConstraint: false,
                    pathLengthConstraint: 0,
                    critical: true),
                new X509Extension(
                    "2.5.29.14",
                    "0414C7AC28EFB300F46F9406ED155628A123633E556F".HexToByteArray(),
                    critical: false)
            };

            X509Extension[] endEntityExtensions = new [] {
                new X509BasicConstraintsExtension(
                    certificateAuthority: false,
                    hasPathLengthConstraint: false,
                    pathLengthConstraint: 0,
                    critical: true),
                new X509Extension(
                    "2.5.29.35",
                    "30168014A84A6A63047DDDBAE6D139B7A64565EFF3A8ECA1".HexToByteArray(),
                    critical: false)
            };

            TestDataGenerator.MakeTestChain3(
                out X509Certificate2 endEntityCert,
                out X509Certificate2 intermediateCert,
                out X509Certificate2 rootCert,
                intermediateExtensions: intermediateExtensions,
                endEntityExtensions: endEntityExtensions);

            using (endEntityCert)
                using (intermediateCert)
                    using (rootCert)
                        using (ChainHolder chainHolder = new ChainHolder())
                        {
                            X509Chain chain = chainHolder.Chain;
                            chain.ChainPolicy.RevocationMode   = X509RevocationMode.NoCheck;
                            chain.ChainPolicy.VerificationTime = endEntityCert.NotBefore.AddSeconds(1);
                            chain.ChainPolicy.TrustMode        = X509ChainTrustMode.CustomRootTrust;
                            chain.ChainPolicy.CustomTrustStore.Add(rootCert);
                            chain.ChainPolicy.ExtraStore.Add(intermediateCert);

                            if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
                            {
                                Assert.False(chain.Build(endEntityCert), "chain.Build");
                                Assert.Equal(X509ChainStatusFlags.PartialChain, chain.AllStatusFlags());
                            }
                            else
                            {
                                Assert.True(chain.Build(endEntityCert), "chain.Build");
                                Assert.Equal(3, chain.ChainElements.Count);
                            }
                        }
        }
예제 #6
0
        public static void BuildInvalidSignatureTwice()
        {
            byte[] bytes = (byte[])TestData.MsCertificate.Clone();
            bytes[bytes.Length - 1] ^= 0xFF;

            using (X509Certificate2 cert = new X509Certificate2(bytes))
                using (ChainHolder chainHolder = new ChainHolder())
                {
                    X509Chain chain = chainHolder.Chain;
                    chain.ChainPolicy.VerificationTime  = cert.NotBefore.AddHours(2);
                    chain.ChainPolicy.VerificationFlags =
                        X509VerificationFlags.AllowUnknownCertificateAuthority;

                    chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;

                    int iter = 0;

                    void CheckChain()
                    {
                        iter++;
                        bool valid = chain.Build(cert);
                        X509ChainStatusFlags allFlags = chain.AllStatusFlags();

                        if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
                        {
                            // OSX considers this to be valid because it doesn't report NotSignatureValid,
                            // just PartialChain ("I couldn't find an issuer that made the signature work"),
                            // and PartialChain + AllowUnknownCertificateAuthority == pass.
                            Assert.True(valid, $"Chain is valid on execution {iter}");

                            Assert.Equal(1, chain.ChainElements.Count);

                            Assert.Equal(
                                X509ChainStatusFlags.PartialChain,
                                allFlags);
                        }
                        else
                        {
                            Assert.False(valid, $"Chain is valid on execution {iter}");

                            Assert.Equal(3, chain.ChainElements.Count);

                            // Clear UntrustedRoot, if it happened.
                            allFlags &= ~X509ChainStatusFlags.UntrustedRoot;

                            Assert.Equal(
                                X509ChainStatusFlags.NotSignatureValid,
                                allFlags);
                        }

                        chainHolder.DisposeChainElements();
                    }

                    CheckChain();
                    CheckChain();
                }
        }
예제 #7
0
        public static void InvalidSelfSignedSignature()
        {
            X509ChainStatusFlags expectedFlags;

            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
            {
                expectedFlags = X509ChainStatusFlags.NotSignatureValid;
            }
            else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
            {
                // For OSX alone expectedFlags here means OR instead of AND.
                // Because the error code changed in 10.13.4 from UntrustedRoot to PartialChain
                // and we handle that later in this test.
                expectedFlags =
                    X509ChainStatusFlags.UntrustedRoot |
                    X509ChainStatusFlags.PartialChain;
            }
            else
            {
                expectedFlags =
                    X509ChainStatusFlags.NotSignatureValid |
                    X509ChainStatusFlags.UntrustedRoot;
            }

            byte[] certBytes = (byte[])TestData.MicrosoftDotComRootBytes.Clone();
            // The signature goes up to the very last byte, so flip some bits in it.
            certBytes[certBytes.Length - 1] ^= 0xFF;

            using (var cert = new X509Certificate2(certBytes))
                using (ChainHolder holder = new ChainHolder())
                {
                    X509Chain       chain  = holder.Chain;
                    X509ChainPolicy policy = chain.ChainPolicy;
                    policy.VerificationTime = cert.NotBefore.AddDays(3);
                    policy.RevocationMode   = X509RevocationMode.NoCheck;

                    chain.Build(cert);

                    X509ChainStatusFlags allFlags = chain.AllStatusFlags();

                    if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
                    {
                        // If we're on 10.13.3 or older we get UntrustedRoot.
                        // If we're on 10.13.4 or newer we get PartialChain.
                        //
                        // So make the expectedValue be whichever of those two is set.
                        expectedFlags = (expectedFlags & allFlags);
                        // One of them has to be set.
                        Assert.NotEqual(X509ChainStatusFlags.NoError, expectedFlags);
                        // Continue executing now to ensure that no other unexpected flags were set.
                    }

                    Assert.Equal(expectedFlags, allFlags);
                }
        }
예제 #8
0
        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}");
                                    }
        }
예제 #9
0
        public static void CustomRootTrustDoesNotTrustIntermediates(
            bool saveAllInCustomTrustStore,
            X509ChainStatusFlags chainFlags)
        {
            string testName = $"{nameof(CustomRootTrustDoesNotTrustIntermediates)} {saveAllInCustomTrustStore} {chainFlags}";

            TestDataGenerator.MakeTestChain3(
                out X509Certificate2 endEntityCert,
                out X509Certificate2 intermediateCert,
                out X509Certificate2 rootCert,
                testName: testName);

            using (endEntityCert)
                using (intermediateCert)
                    using (rootCert)
                        using (ChainHolder chainHolder = new ChainHolder())
                        {
                            X509Chain chain = chainHolder.Chain;
                            chain.ChainPolicy.RevocationMode   = X509RevocationMode.NoCheck;
                            chain.ChainPolicy.VerificationTime = endEntityCert.NotBefore.AddSeconds(1);
                            chain.ChainPolicy.TrustMode        = X509ChainTrustMode.CustomRootTrust;
                            chain.ChainPolicy.CustomTrustStore.Add(intermediateCert);

                            if (saveAllInCustomTrustStore)
                            {
                                chain.ChainPolicy.CustomTrustStore.Add(rootCert);
                            }
                            else
                            {
                                chain.ChainPolicy.ExtraStore.Add(rootCert);
                            }

                            if (PlatformDetection.IsAndroid && !saveAllInCustomTrustStore)
                            {
                                // Android does not support an empty custom root trust
                                // Only self-issued certs are treated as trusted anchors, so building the chain
                                // should through PNSE even though the intermediate cert is added to the store
                                Assert.Throws <PlatformNotSupportedException>(() => chain.Build(endEntityCert));
                            }
                            else
                            {
                                Assert.Equal(saveAllInCustomTrustStore, chain.Build(endEntityCert));
                                Assert.Equal(3, chain.ChainElements.Count);
                                Assert.Equal(chainFlags, chain.AllStatusFlags());
                            }
                        }
        }
예제 #10
0
        public static void TestLeafCertificateWithUnknownCriticalExtension()
        {
            using (RSA key = RSA.Create())
            {
                CertificateRequest certReq = new CertificateRequest(
                    new X500DistinguishedName("CN=Cert"),
                    key,
                    HashAlgorithmName.SHA256,
                    RSASignaturePadding.Pkcs1);

                const string PrecertificatePoisonExtensionOid = "1.3.6.1.4.1.11129.2.4.3";
                certReq.CertificateExtensions.Add(new X509Extension(
                                                      new AsnEncodedData(
                                                          new Oid(PrecertificatePoisonExtensionOid),
                                                          new byte[] { 5, 0 }),
                                                      critical: true));

                DateTimeOffset notBefore = DateTimeOffset.UtcNow.AddDays(-1);
                DateTimeOffset notAfter  = notBefore.AddDays(30);

                using (X509Certificate2 cert = certReq.CreateSelfSigned(notBefore, notAfter))
                    using (ChainHolder holder = new ChainHolder())
                    {
                        X509Chain chain = holder.Chain;
                        chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
                        Assert.False(chain.Build(cert));

                        if (PlatformDetection.IsAndroid)
                        {
                            // Android always unsupported critical extensions as part of building a path,
                            // so errors comes back as PartialChain with no elements
                            Assert.Equal(X509ChainStatusFlags.PartialChain, chain.AllStatusFlags());
                            Assert.Equal(0, chain.ChainElements.Count);
                        }
                        else
                        {
                            X509ChainElement           certElement  = chain.ChainElements.OfType <X509ChainElement>().Single();
                            const X509ChainStatusFlags ExpectedFlag = X509ChainStatusFlags.HasNotSupportedCriticalExtension;
                            X509ChainStatusFlags       actualFlags  = certElement.AllStatusFlags();
                            Assert.True((actualFlags & ExpectedFlag) == ExpectedFlag, $"Has expected flag {ExpectedFlag} but was {actualFlags}");
                        }
                    }
            }
        }
예제 #11
0
        public static void TestInvalidAia()
        {
            using (RSA key = RSA.Create())
            {
                CertificateRequest rootReq = new CertificateRequest(
                    "CN=Root",
                    key,
                    HashAlgorithmName.SHA256,
                    RSASignaturePadding.Pkcs1);

                rootReq.CertificateExtensions.Add(
                    new X509BasicConstraintsExtension(true, false, 0, true));

                CertificateRequest certReq = new CertificateRequest(
                    "CN=test",
                    key,
                    HashAlgorithmName.SHA256,
                    RSASignaturePadding.Pkcs1);

                certReq.CertificateExtensions.Add(
                    new X509BasicConstraintsExtension(false, false, 0, false));

                certReq.CertificateExtensions.Add(
                    new X509Extension(
                        "1.3.6.1.5.5.7.1.1",
                        new byte[] { 5 },
                        critical: false));

                DateTimeOffset notBefore = DateTimeOffset.UtcNow.AddDays(-1);
                DateTimeOffset notAfter  = notBefore.AddDays(30);

                using (X509Certificate2 root = rootReq.CreateSelfSigned(notBefore, notAfter))
                    using (X509Certificate2 ee = certReq.Create(root, notBefore, notAfter, root.GetSerialNumber()))
                    {
                        X509Chain chain = new X509Chain();
                        chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
                        Assert.False(chain.Build(ee));
                        Assert.Equal(1, chain.ChainElements.Count);
                        Assert.Equal(X509ChainStatusFlags.PartialChain, chain.AllStatusFlags());
                    }
            }
        }
예제 #12
0
        public static void CustomTrustModeWithNoCustomTrustCerts()
        {
            TestDataGenerator.MakeTestChain3(
                out X509Certificate2 endEntityCert,
                out X509Certificate2 intermediateCert,
                out X509Certificate2 rootCert);

            using (endEntityCert)
                using (intermediateCert)
                    using (rootCert)
                        using (ChainHolder chainHolder = new ChainHolder())
                        {
                            X509Chain chain = chainHolder.Chain;
                            chain.ChainPolicy.RevocationMode   = X509RevocationMode.NoCheck;
                            chain.ChainPolicy.VerificationTime = endEntityCert.NotBefore.AddSeconds(1);
                            chain.ChainPolicy.TrustMode        = X509ChainTrustMode.CustomRootTrust;

                            Assert.False(chain.Build(endEntityCert));
                            Assert.Equal(1, chain.ChainElements.Count);
                            Assert.Equal(X509ChainStatusFlags.PartialChain, chain.AllStatusFlags());
                        }
        }
예제 #13
0
        public static void BuildInvalidSignatureTwice(
            X509ChainStatusFlags endEntityErrors,
            X509ChainStatusFlags intermediateErrors,
            X509ChainStatusFlags rootErrors)
        {
            TestDataGenerator.MakeTestChain3(
                out X509Certificate2 endEntityCert,
                out X509Certificate2 intermediateCert,
                out X509Certificate2 rootCert);

            X509Certificate2 TamperIfNeeded(X509Certificate2 input, X509ChainStatusFlags flags)
            {
                if ((flags & X509ChainStatusFlags.NotSignatureValid) != 0)
                {
                    X509Certificate2 tampered = TamperSignature(input);
                    input.Dispose();
                    return(tampered);
                }

                return(input);
            }

            DateTime RewindIfNeeded(DateTime input, X509Certificate2 cert, X509ChainStatusFlags flags)
            {
                if ((flags & X509ChainStatusFlags.NotTimeValid) != 0)
                {
                    return(cert.NotBefore.AddMinutes(-1));
                }

                return(input);
            }

            int expectedCount = 3;

            DateTime verificationTime = endEntityCert.NotBefore.AddMinutes(1);

            verificationTime = RewindIfNeeded(verificationTime, endEntityCert, endEntityErrors);
            verificationTime = RewindIfNeeded(verificationTime, intermediateCert, intermediateErrors);
            verificationTime = RewindIfNeeded(verificationTime, rootCert, rootErrors);

            // Replace the certs for the scenario.
            endEntityCert    = TamperIfNeeded(endEntityCert, endEntityErrors);
            intermediateCert = TamperIfNeeded(intermediateCert, intermediateErrors);
            rootCert         = TamperIfNeeded(rootCert, rootErrors);

            if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
            {
                // For the lower levels, turn NotSignatureValid into PartialChain,
                // and clear all errors at higher levels.

                if ((endEntityErrors & X509ChainStatusFlags.NotSignatureValid) != 0)
                {
                    expectedCount      = 1;
                    endEntityErrors   &= ~X509ChainStatusFlags.NotSignatureValid;
                    endEntityErrors   |= X509ChainStatusFlags.PartialChain;
                    intermediateErrors = X509ChainStatusFlags.NoError;
                    rootErrors         = X509ChainStatusFlags.NoError;
                }
                else if ((intermediateErrors & X509ChainStatusFlags.NotSignatureValid) != 0)
                {
                    expectedCount       = 2;
                    intermediateErrors &= ~X509ChainStatusFlags.NotSignatureValid;
                    intermediateErrors |= X509ChainStatusFlags.PartialChain;
                    rootErrors          = X509ChainStatusFlags.NoError;
                }
                else if ((rootErrors & X509ChainStatusFlags.NotSignatureValid) != 0)
                {
                    rootErrors &= ~X509ChainStatusFlags.NotSignatureValid;

                    // On 10.13+ it becomes PartialChain, and UntrustedRoot goes away.
                    if (PlatformDetection.IsOSX)
                    {
                        rootErrors &= ~X509ChainStatusFlags.UntrustedRoot;
                        rootErrors |= X509ChainStatusFlags.PartialChain;
                    }
                }
            }
            else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
            {
                // Windows only reports NotTimeValid on the start-of-chain (end-entity in this case)
                // If it were possible in this suite to get only a higher-level cert as NotTimeValid
                // without the lower one, that would have resulted in NotTimeNested.
                intermediateErrors &= ~X509ChainStatusFlags.NotTimeValid;
                rootErrors         &= ~X509ChainStatusFlags.NotTimeValid;
            }

            X509ChainStatusFlags expectedAllErrors = endEntityErrors | intermediateErrors | rootErrors;

            // If PartialChain or UntrustedRoot are the only remaining errors, the chain will succeed.
            const X509ChainStatusFlags SuccessCodes =
                X509ChainStatusFlags.UntrustedRoot | X509ChainStatusFlags.PartialChain;

            bool expectSuccess = (expectedAllErrors & ~SuccessCodes) == 0;

            using (endEntityCert)
                using (intermediateCert)
                    using (rootCert)
                        using (ChainHolder chainHolder = new ChainHolder())
                        {
                            X509Chain chain = chainHolder.Chain;
                            chain.ChainPolicy.VerificationTime = verificationTime;
                            chain.ChainPolicy.RevocationMode   = X509RevocationMode.NoCheck;
                            chain.ChainPolicy.ExtraStore.Add(intermediateCert);
                            chain.ChainPolicy.ExtraStore.Add(rootCert);

                            chain.ChainPolicy.VerificationFlags |=
                                X509VerificationFlags.AllowUnknownCertificateAuthority;

                            int i = 0;

                            void CheckChain()
                            {
                                i++;

                                bool valid = chain.Build(endEntityCert);

                                if (expectSuccess)
                                {
                                    Assert.True(valid, $"Chain build on iteration {i}");
                                }
                                else
                                {
                                    Assert.False(valid, $"Chain build on iteration {i}");
                                }

                                Assert.Equal(expectedCount, chain.ChainElements.Count);
                                Assert.Equal(expectedAllErrors, chain.AllStatusFlags());

                                Assert.Equal(endEntityErrors, chain.ChainElements[0].AllStatusFlags());

                                if (expectedCount > 2)
                                {
                                    Assert.Equal(rootErrors, chain.ChainElements[2].AllStatusFlags());
                                }

                                if (expectedCount > 1)
                                {
                                    Assert.Equal(intermediateErrors, chain.ChainElements[1].AllStatusFlags());
                                }

                                chainHolder.DisposeChainElements();
                            }

                            CheckChain();
                            CheckChain();
                        }
        }
예제 #14
0
        public static void RevocationCheckingNegativeTimeout(PkiOptions pkiOptions)
        {
            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 = 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.DisableCertificateDownloads = true;

                    Assert.True(chain.Build(endEntityCert), $"chain.Build; Chain status: {chain.AllStatusFlags()}");
                }
            });
        }
예제 #15
0
        public static void BasicConstraints_ViolatesCaFalse()
        {
            X509Extension[] intermediateExtensions = new [] {
                new X509BasicConstraintsExtension(
                    certificateAuthority: false,
                    hasPathLengthConstraint: false,
                    pathLengthConstraint: 0,
                    critical: true)
            };

            TestDataGenerator.MakeTestChain3(
                out X509Certificate2 endEntityCert,
                out X509Certificate2 intermediateCert,
                out X509Certificate2 rootCert,
                intermediateExtensions: intermediateExtensions);

            using (endEntityCert)
                using (intermediateCert)
                    using (rootCert)
                        using (ChainHolder chainHolder = new ChainHolder())
                        {
                            X509Chain chain = chainHolder.Chain;
                            chain.ChainPolicy.RevocationMode   = X509RevocationMode.NoCheck;
                            chain.ChainPolicy.VerificationTime = endEntityCert.NotBefore.AddSeconds(1);
                            chain.ChainPolicy.TrustMode        = X509ChainTrustMode.CustomRootTrust;
                            chain.ChainPolicy.CustomTrustStore.Add(rootCert);
                            chain.ChainPolicy.ExtraStore.Add(intermediateCert);

                            Assert.False(chain.Build(endEntityCert));
                            Assert.Equal(X509ChainStatusFlags.InvalidBasicConstraints, chain.AllStatusFlags());
                        }
        }
예제 #16
0
        public static void RevocationCheckingTimeoutFallbackToOther(DelayedActionsFlag delayFlags)
        {
            RetryHelper.Execute(() => {
                CertificateAuthority.BuildPrivatePki(
                    PkiOptions.AllRevocation,
                    out RevocationResponder responder,
                    out CertificateAuthority rootAuthority,
                    out CertificateAuthority intermediateAuthority,
                    out X509Certificate2 endEntityCert,
                    nameof(RevocationCheckingTimeoutFallbackToOther));

                using (responder)
                using (rootAuthority)
                using (intermediateAuthority)
                using (endEntityCert)
                using (ChainHolder holder = new ChainHolder())
                using (X509Certificate2 rootCert = rootAuthority.CloneIssuerCert())
                using (X509Certificate2 intermediateCert = intermediateAuthority.CloneIssuerCert())
                {
                    X509Chain chain = holder.Chain;
                    responder.ResponseDelay = TimeSpan.FromSeconds(8);
                    responder.DelayedActions = delayFlags;

                    chain.ChainPolicy.UrlRetrievalTimeout = TimeSpan.FromSeconds(4);
                    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;

                    Assert.True(chain.Build(endEntityCert), $"chain.Build; Chain status: {chain.AllStatusFlags()}");
                }
            });
        }
예제 #17
0
        public static void ChainErrorsAtMultipleLayers()
        {
            // These certificates were generated for this test using CertificateRequest
            // but the netstandard(2.0) version of this test library doesn't have
            // CertificateRequest available.
            //
            // These certificates have been hard-coded to enable the scenario on
            // netstandard.
            byte[] endEntityBytes    = Encoding.ASCII.GetBytes(@"
-----BEGIN CERTIFICATE-----
MIIC6DCCAdCgAwIBAgIQAKjmD7+TWUwQN2ucajn9kTANBgkqhkiG9w0BAQsFADAXMRUwEwYDVQQD
EwxJbnRlcm1lZGlhdGUwHhcNMTkwMzAzMjM1NzA3WhcNMTkwNjAzMjM1NzA3WjAVMRMwEQYDVQQD
EwpFbmQtRW50aXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxTybBkpMdQ8IeL1C
jG755+ifQfqjNt4+Xhm3pbMi+nCRD68tym1xviUka1hQmx+I1mptswW0Laq1owur0r2KanKoIP2F
i2h6orOOdslMFPMWqCuNTU4C7cUxokaWah0R7FihwW+aBeWgxG948Cvt+ByQeR1ns9yo7wa8f8kT
IwzOUu0v1Yj5oW5bOn/cmIBE1C5CD5RivPMGUXX8mZ/myNh16dLQqJW5yQt/uvfr7lkNWC0qq+v7
Ely4+X27acwMTdtk4chcr5/bTS5FXV7HVqwhajOmm6WrzagPZBELWKRk2EaJkha/MLrBqNfHExs4
sx2ks+TTclrOrRzG+AUBuQIDAQABozIwMDAMBgNVHRMBAf8EAjAAMAsGA1UdDwQEAwIF4DATBgNV
HSUEDDAKBggrBgEFBQcDAjANBgkqhkiG9w0BAQsFAAOCAQEAbrEbiw4gpgWi3SJ+sGrfcWCAldpx
0735hkkYz94OsJjIwWfgQ03pYZwjcnIE4Ln0PU2E52D2ldsJlAE376hpNxdO0X4RLpZVZPEjKGTF
v2Rf+d0cpqha5J//mqcTTm7F58JRKyfEQn0pqfxx4VyXeLfEsqYbT3kY7ufK0km3Jst0DGw2AGue
MPmiZicaNlXPVO9vyW4s6J23+kol6X8K2rnVht9jagfnOQ990Ux2xXGyDGM4I0pvW1Zo4vid/eli
psHHsU9xg0o7L2WXD5qYhD2JCQIVWNRmRZCf1luWlKqUaqWWONMJ44hk8Md+ohxpyCRmbtLRZPzd
wlkQzPsc9A==
-----END CERTIFICATE-----");
            byte[] intermediateBytes = Encoding.ASCII.GetBytes(@"
-----BEGIN CERTIFICATE-----
MIIC1DCCAbygAwIBAgIPRoY1rB2tMVJeYB4GILkNMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNVBAMT
CVRlc3QgUm9vdDAeFw0xOTAyMTgyMzU3MDdaFw0yMDAyMTgyMzU3MDdaMBcxFTATBgNVBAMTDElu
dGVybWVkaWF0ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALxYzEN6nYvQ0TOg/jOF
wdBGRUYhTiJpYGFBh9826X5vKlbCS1UAcjFRXmKtJ4WZ8v3peCBPxvVe/1KR38+MWNVtO4B1GBvr
qR2T9k1ewgn0lO3i6krnIAhJQ+F94xGcsRAfZjXBh7lOmTE9ZlDhDJWkehBIs5TteiBOfbGDml2S
v7x81cmm2o/sDoP1oVGhezOkFtI2/NdZYKxRthnjDywN3W4KFataJFATVv/yq+QjcLEWrXFRpzDE
rpVdYmj66kaAnu9D4sHhFqOk1SX3JvcB361stVuUPp2ri75MaaXakweH6X/Yb4nPNV6m1ENwMoDy
HqrZrHSK8SpzfhY9aB0CAwEAAaMgMB4wDwYDVR0TAQH/BAUwAwEB/zALBgNVHQ8EBAMCAYYwDQYJ
KoZIhvcNAQELBQADggEBAC4oJ2SH+Ov4QIMXo7mwGSrwONkdMuKyyM9shZiGEH+zIO9SVuPuvtQG
cePR2bijSz2DtjySi+ST8y3Ql7A3isfbXYPDFmnkzKP6hGvLkctc8eO8U1x7ny+QW1max0gm3UA8
CY0IMP8pCHUZH9OX/K0N9L+GItqlBK8G4grJ4o43da2x9L0hIrdauPadaGcJalf8k1ymhJ4VDj7t
ueuTl2qTtbBh015GuEld61EBXSBLIUqwOAeFYrNJbC4J2mXgnLTWC380cBf5KWeSdjLYgk2sZ1V4
FKKQecZIhxdlDGzMAbbmEV+2EqS+As2C7+y4dkpG4nnbQe/4AFr8vekHdrI=
-----END CERTIFICATE-----");
            byte[] rootBytes         = Encoding.ASCII.GetBytes(@"
-----BEGIN CERTIFICATE-----
MIICyjCCAbKgAwIBAgIIKKt3K3rRbvQwDQYJKoZIhvcNAQELBQAwFDESMBAGA1UEAxMJVGVzdCBS
b290MB4XDTE5MDIwNDIzNTcwN1oXDTIxMDIwNDIzNTcwN1owFDESMBAGA1UEAxMJVGVzdCBSb290
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAiM7tv4YvqmWYGF1vbeM2cQWV1NVBxKU4
ZK5XEJHZirzE2HCiA0+hI/UD7xnfBrzGQRLsHnp9vfhBi/0wenSIKTckxcGGpuM+JzNoVF97uFSd
bKvfIwQZzbdRGyTF1eoQWCylsZsnZOXg8c/yoFhG2TJB38l09RYn+HkMkapQERFKSXPZ7taNVJNb
Sedp3l9jO0aVmh9rmJ7taBXBfWDmSWqhkxjkEcbiRxB7z5K8YxZBlHQCLqf43JiCbKIMBHdzTg+N
lEBkBGp6T2hoJ4/A1uwvhesjmyqagZrC2NnzOWOxUQ/WujIUfS62ii/yDkP4Jo3745lJ9XXoPbIw
AwvWYQIDAQABoyAwHjAPBgNVHRMBAf8EBTADAQH/MAsGA1UdDwQEAwIBhjANBgkqhkiG9w0BAQsF
AAOCAQEAA/pfswrUzcLP5UfmHgQDc1slJjh0btnkN+4dxCCTLcnteJCTumYw+/82qL+O4t1KlzlS
2Eqgyx0u48YmwDp/5jWAvT8RX8pvV3Prd7T8/dp/ucES7R9r3zF2Rmw5Me9iq1yaLAypGyBGqV1J
HAwJjH/eKZ5iuOMhFljs2R5Gh5rRsQjNVUCRsolCds4d1f+76fi2SGaKqkAA4gzg1c71SPTAaUPR
ythjxnoCBDVFmwV5opXZj9qIZoUdH92gCVFgMWkxWCYWzyH78uIUzV1oo+KNwK1SCTnfVHcfWRIL
tHP28fj0LUop/QFojSZPsaPAW6JvoQ0t4hd6WoyX6z7FsA==
-----END CERTIFICATE-----");

            using (X509Certificate2 endEntityCert = new X509Certificate2(endEntityBytes))
                using (X509Certificate2 intermediateCert = new X509Certificate2(intermediateBytes))
                    using (X509Certificate2 rootCert = new X509Certificate2(rootBytes))
                        using (ChainHolder chainHolder = new ChainHolder())
                        {
                            X509Chain chain = chainHolder.Chain;
                            chain.ChainPolicy.RevocationMode     = X509RevocationMode.NoCheck;
                            chain.ChainPolicy.VerificationFlags |= X509VerificationFlags.AllowUnknownCertificateAuthority;
                            chain.ChainPolicy.ExtraStore.Add(intermediateCert);
                            chain.ChainPolicy.ExtraStore.Add(rootCert);
                            chain.ChainPolicy.VerificationTime = endEntityCert.NotAfter.AddDays(1);

                            Assert.Equal(false, chain.Build(endEntityCert));

                            Assert.Equal(3, chain.ChainElements.Count);
                            Assert.Equal(X509ChainStatusFlags.NotTimeValid, chain.ChainElements[0].AllStatusFlags());
                            Assert.Equal(X509ChainStatusFlags.NoError, chain.ChainElements[1].AllStatusFlags());
                            Assert.Equal(X509ChainStatusFlags.UntrustedRoot, chain.ChainElements[2].AllStatusFlags());

                            Assert.Equal(
                                X509ChainStatusFlags.NotTimeValid | X509ChainStatusFlags.UntrustedRoot,
                                chain.AllStatusFlags());
                        }
        }
        public static void DisableAiaOptionWorks()
        {
            CertificateAuthority.BuildPrivatePki(
                PkiOptions.AllRevocation,
                out RevocationResponder responder,
                out CertificateAuthority root,
                out CertificateAuthority intermediate,
                out X509Certificate2 endEntity,
                pkiOptionsInSubject: false);

            using (responder)
                using (root)
                    using (intermediate)
                        using (endEntity)
                            using (ChainHolder holder = new ChainHolder())
                                using (X509Certificate2 rootCert = root.CloneIssuerCert())
                                    using (X509Certificate2 intermediateCert = intermediate.CloneIssuerCert())
                                        using (var cuCaStore = new X509Store(StoreName.CertificateAuthority, StoreLocation.CurrentUser))
                                        {
                                            cuCaStore.Open(OpenFlags.ReadWrite);

                                            X509Chain chain = holder.Chain;
                                            chain.ChainPolicy.DisableCertificateDownloads = true;
                                            chain.ChainPolicy.CustomTrustStore.Add(rootCert);
                                            chain.ChainPolicy.TrustMode           = X509ChainTrustMode.CustomRootTrust;
                                            chain.ChainPolicy.VerificationTime    = endEntity.NotBefore.AddMinutes(1);
                                            chain.ChainPolicy.UrlRetrievalTimeout = DynamicRevocationTests.s_urlRetrievalLimit;

                                            Assert.False(chain.Build(endEntity), "Chain build with no intermediate, AIA disabled");

                                            // If a previous run of this test leaves contamination in the CU\CA store on Windows
                                            // the Windows chain engine will match the bad issuer and report NotSignatureValid instead
                                            // of PartialChain.
                                            X509ChainStatusFlags chainFlags = chain.AllStatusFlags();

                                            if (chainFlags.HasFlag(X509ChainStatusFlags.NotSignatureValid))
                                            {
                                                Assert.Equal(3, chain.ChainElements.Count);

                                                foreach (X509Certificate2 storeCert in cuCaStore.Certificates)
                                                {
                                                    if (storeCert.Subject.Equals(intermediateCert.Subject))
                                                    {
                                                        cuCaStore.Remove(storeCert);
                                                    }

                                                    storeCert.Dispose();
                                                }

                                                holder.DisposeChainElements();

                                                // Try again, with no caching side effect.
                                                Assert.False(chain.Build(endEntity), "Chain build 2 with no intermediate, AIA disabled");
                                            }

                                            Assert.Equal(1, chain.ChainElements.Count);
                                            Assert.Contains(X509ChainStatusFlags.PartialChain, chain.ChainStatus.Select(s => s.Status));
                                            holder.DisposeChainElements();

                                            // macOS doesn't like our revocation responder, so disable revocation checks there.
                                            if (PlatformDetection.IsOSX)
                                            {
                                                chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
                                            }

                                            chain.ChainPolicy.ExtraStore.Add(intermediateCert);
                                            Assert.True(chain.Build(endEntity), "Chain build with intermediate, AIA disabled");
                                            Assert.Equal(3, chain.ChainElements.Count);
                                            Assert.Equal(X509ChainStatusFlags.NoError, chain.AllStatusFlags());
                                            holder.DisposeChainElements();

                                            chain.ChainPolicy.DisableCertificateDownloads = false;
                                            chain.ChainPolicy.ExtraStore.Clear();
                                            Assert.True(chain.Build(endEntity), "Chain build with no intermediate, AIA enabled");
                                            Assert.Equal(3, chain.ChainElements.Count);
                                            Assert.Equal(X509ChainStatusFlags.NoError, chain.AllStatusFlags());

                                            cuCaStore.Remove(intermediateCert);
                                        }
        }
예제 #19
0
        public static void VerifyWithRevocation()
        {
            using (var cert = new X509Certificate2(Path.Combine("TestData", "MS.cer")))
                using (var onlineChainHolder = new ChainHolder())
                    using (var offlineChainHolder = new ChainHolder())
                    {
                        X509Chain onlineChain  = onlineChainHolder.Chain;
                        X509Chain offlineChain = offlineChainHolder.Chain;

                        onlineChain.ChainPolicy.VerificationFlags =
                            X509VerificationFlags.AllowUnknownCertificateAuthority;

                        onlineChain.ChainPolicy.VerificationTime = cert.NotBefore.AddHours(2);
                        onlineChain.ChainPolicy.RevocationMode   = X509RevocationMode.Online;
                        onlineChain.ChainPolicy.RevocationFlag   = X509RevocationFlag.EntireChain;

                        // Attempt the online test a couple of times, in case there was just a CRL
                        // download failure.
                        const int RetryLimit = 3;
                        bool      valid      = false;

                        for (int i = 0; i < RetryLimit; i++)
                        {
                            valid = onlineChain.Build(cert);

                            if (valid)
                            {
                                break;
                            }

                            for (int j = 0; j < onlineChain.ChainElements.Count; j++)
                            {
                                X509ChainStatusFlags chainFlags = onlineChain.AllStatusFlags();

                                const X509ChainStatusFlags WontCheck =
                                    X509ChainStatusFlags.RevocationStatusUnknown | X509ChainStatusFlags.UntrustedRoot;

                                if (chainFlags == WontCheck)
                                {
                                    Console.WriteLine($"{nameof(VerifyWithRevocation)}: online chain failed with {{{chainFlags}}}, skipping");
                                    return;
                                }

                                X509ChainElement chainElement = onlineChain.ChainElements[j];

                                // Since `NoError` gets mapped as the empty array, just look for non-empty arrays
                                if (chainElement.ChainElementStatus.Length > 0)
                                {
                                    X509ChainStatusFlags allFlags = chainElement.AllStatusFlags();

                                    Console.WriteLine(
                                        $"{nameof(VerifyWithRevocation)}: online attempt {i} - errors at depth {j}: {allFlags}");
                                }

                                chainElement.Certificate.Dispose();
                            }

                            Thread.Sleep(1000); // For network flakiness
                        }

                        if (TestEnvironmentConfiguration.RunManualTests)
                        {
                            Assert.True(valid, $"Online Chain Built Validly within {RetryLimit} tries");
                        }
                        else if (!valid)
                        {
                            Console.WriteLine($"SKIP [{nameof(VerifyWithRevocation)}]: Chain failed to build within {RetryLimit} tries.");
                            return;
                        }

                        // Since the network was enabled, we should get the whole chain.
                        Assert.Equal(3, onlineChain.ChainElements.Count);

                        Assert.Equal(0, onlineChain.ChainElements[0].ChainElementStatus.Length);
                        Assert.Equal(0, onlineChain.ChainElements[1].ChainElementStatus.Length);

                        // The root CA is not expected to be installed on everyone's machines,
                        // so allow for it to report UntrustedRoot, but nothing else..
                        X509ChainStatus[] rootElementStatus = onlineChain.ChainElements[2].ChainElementStatus;

                        if (rootElementStatus.Length != 0)
                        {
                            Assert.Equal(1, rootElementStatus.Length);
                            Assert.Equal(X509ChainStatusFlags.UntrustedRoot, rootElementStatus[0].Status);
                        }

                        // Now that everything is cached, try again in Offline mode.
                        offlineChain.ChainPolicy.VerificationFlags = onlineChain.ChainPolicy.VerificationFlags;
                        offlineChain.ChainPolicy.VerificationTime  = onlineChain.ChainPolicy.VerificationTime;
                        offlineChain.ChainPolicy.RevocationMode    = X509RevocationMode.Offline;
                        offlineChain.ChainPolicy.RevocationFlag    = onlineChain.ChainPolicy.RevocationFlag;

                        valid = offlineChain.Build(cert);
                        Assert.True(valid, "Offline Chain Built Validly");

                        // Everything should look just like the online chain:
                        Assert.Equal(onlineChain.ChainElements.Count, offlineChain.ChainElements.Count);

                        for (int i = 0; i < offlineChain.ChainElements.Count; i++)
                        {
                            X509ChainElement onlineElement  = onlineChain.ChainElements[i];
                            X509ChainElement offlineElement = offlineChain.ChainElements[i];

                            Assert.Equal(onlineElement.ChainElementStatus, offlineElement.ChainElementStatus);
                            Assert.Equal(onlineElement.Certificate, offlineElement.Certificate);
                        }
                    }
        }