Skip to content

use of certificates after adding to X509Store is non-deterministic #48017

@wfurt

Description

@wfurt

I did ran into this while working on #47729 and #46837.
related to #47680 and #47580 where users reported issues with certificate chain in SslStream.

Essentially I have private chain of certificates, I add CAs to to StoreName.CertificateAuthority and subsequent X509Chain.Build() may or may not see them. At this point I'm not sure if the issue is with the store or chain. When I add Sleep(5000) between store.Add() and chain.Build() I usually get expected result. That makes it very difficult to write reliable code. For the test I was working on I added retry loop but that is not great solution either IMHO.

This is essential for SslStream's ability to send correct chain from server or when client certificate authentication is used.

I have test in #48014 to demonstrate the behavior.

      System.Security.Cryptography.X509Certificates.Tests.X509StoreTests.CertificatesUsableAfterAdd [STARTING]
      System.Security.Cryptography.X509Certificates.Tests.X509StoreTests.CertificatesUsableAfterAdd [FAIL]
        Assert.Equal() Failure
        Expected: 3
        Actual:   1
        Stack Trace:
          /home/furt/github/wfurt-runtime/src/libraries/System.Security.Cryptography.X509Certificates/tests/X509StoreTests.cs(617,0): at System.Security.Cryptography.X509Certificates.Tests.X509StoreTests.CertificatesUsableAfterAdd()
        Output:
          Test result after sleep is True and 3
      System.Security.Cryptography.X509Certificates.Tests.X509StoreTests.CertificatesUsableAfterAdd [FINISHED] Time: 5.23547s

here is relevant fragment from #48014

          
            using (X509Store store = new X509Store(StoreName.CertificateAuthority, StoreLocation.CurrentUser))
            {
                // add chain certificate so we can construct chain since there is no way how to pass intermediates directly.
                store.Open(OpenFlags.ReadWrite);
                store.Add(intermediate.CloneIssuerCert());
                store.Add(root.CloneIssuerCert());
                store.Close();
            }

           using(....) 
           {
                X509Chain chain = new X509Chain();
                chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllFlags;
                chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
                chain.ChainPolicy.DisableCertificateDownloads = true;

                bool result = chain.Build(endEntity);
                int count = result ? chain.ChainElements.Count : -1;

                if (!result || count != 3)
                {
                    Thread.Sleep(5000);
                    bool result2 = chain.Build(endEntity);
                    _output.WriteLine("Test result after sleep is {0} and {1}", result2, chain.ChainElements?.Count);
                }
                CleanupCertificates(MethodBase.GetCurrentMethod().Name);

                Assert.True(result, "chain was not built");
                Assert.Equal(3, count);

one more note, that on macOS this seems to always fail. I don't know at this point if this is just timing e.g. needs more time or if the StoreName.CertificateAuthority is ignored or if it is something else. It would be great if this works consistent on all platforms.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions