Skip to content

Caching of SCHANNEL_CRED may lead to SslStream using expired certificate chain #43879

@egraff

Description

@egraff

Description

The static SslSessionsCache used by SecureChannel -- which in turn is used by SslStream -- to cache SCHANNEL_CRED handles is problematic, because the certificate chain for the end-entity certificate used by SslStream is only built when the SCHANNEL_CRED handle is first acquired, through a call to AcquireCredentialsHandle. If the root CA certificate or one of the intermediate CA certificates expire (this actually happened IRL quite recently), then all SslStream instances created for the rest of the entire lifetime of the process for the same end-entity certificate (for example a Kestrel-based ASP.Net Core web server) will continue to use the expired certificate chain.

Configuration

Seen on .NET on Windows. I have reproduced the problem on .NET Framework 4.8, .NET Core 2.1 and .NET Core 3.1.

Detailed reproduction

This problem was first observed in real life, when the Sectigo AddTrust External CA Root expired on May 30, 2020. A long-living web server serving the following certificate chain (end-entity certificate excluded) continued to serve the same, expired chain after the AddTrust CA certificate expired.

Subject Serial number
C=SE, O=AddTrust AB, OU=AddTrust External TTP Network, CN=AddTrust External CA Root
C=US, ST=New Jersey, L=Jersey City, O=The USERTRUST Network, CN=USERTrust RSA Certification Authority 13:ea:28:70:5b:f4:ec:ed:0c:36:63:09:80:61:43:36
C=GB, ST=Greater Manchester, L=Salford, O=Sectigo Limited, CN=Sectigo RSA Domain Validation Secure Server CA 7d:5b:51:26:b4:76:ba:11:db:74:16:0b:bc:53:0d:a7

The USERTrust intermediate certificate in this chain is a cross-signed CA, which also has a valid root CA certificate, so the expected certificate chain to be returned from the server after the AddTrust certificate expired would be this:

Subject Serial number
C=US, ST=New Jersey, L=Jersey City, O=The USERTRUST Network, CN=USERTrust RSA Certification Authority 01:fd:6d:30:fc:a3:ca:51:a8:1b:bc:64:0e:35:03:2d
C=GB, ST=Greater Manchester, L=Salford, O=Sectigo Limited, CN=Sectigo RSA Domain Validation Secure Server CA 7d:5b:51:26:b4:76:ba:11:db:74:16:0b:bc:53:0d:a7

I have reproduced this issue synthetically, with a stand-alone .NET executable. The application generates the following certificates:

    Old root CA (CN=TEST_CERT_Old_root_CA)
                   |
                   v
Old first intermediate (CN=TEST_CERT_New_root_CA)     New root CA (CN=TEST_CERT_New_root_CA)
               |                                            |
               v                                            v
           Second intermediate (CN=TEST_CERT_Second_Intermediate_CA)
                      |
                      v
      End entity (CN=some.server.invalid)

where Old first intermediate has the same subject name and private key as New root CA (i.e. the CA is cross-signed by Old root CA). Old root CA and Old first intermediate are set to expire in 2 minutes, but the notBefore date is set to be more recent than New root CA to trick CryptoAPI to prefer that certificate chain initially. After this is done, the root and intermediate CA certificates are installed in the system's certificate stores.

After the certificates are setup, the test first builds a certificate chain explicitly, using X509Chain.Build(). Then, the test runs a loop where it creates a new web server SslStream for each iteration, and logs the certificate chain that is received by a connecting web client (note that the root certificate is not included, as it is not part of the TLS Certificate message). There is a 30 seconds sleep between each loop iteration. Finally, after all the loop iterations have run, the test builds another certificate chain explicitly, again using X509Chain.Build().

Expand to see test code included below:
namespace SslStreamCertChainProblemExternalRepro
{
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.Linq;
    using System.Net;
    using System.Net.Security;
    using System.Net.Sockets;
    using System.Security.Authentication;
    using System.Security.Cryptography;
    using System.Security.Cryptography.X509Certificates;
    using System.Threading;
    using System.Threading.Tasks;

    using Org.BouncyCastle.Crypto.Tls;
    using Org.BouncyCastle.Security;

    using CertificateRequest = System.Security.Cryptography.X509Certificates.CertificateRequest;

    internal class TlsClient : DefaultTlsClient
    {
        private readonly Action<Certificate> onNotifyServerCert;

        /// <inheritdoc />
        public TlsClient(Action<Certificate> onNotifyServerCert)
        {
            this.onNotifyServerCert = onNotifyServerCert;
        }

        /// <inheritdoc />
        public override Org.BouncyCastle.Crypto.Tls.TlsAuthentication GetAuthentication()
        {
            return new TlsAuthentication(onNotifyServerCert);
        }

        private class TlsAuthentication : Org.BouncyCastle.Crypto.Tls.TlsAuthentication
        {
            private readonly Action<Certificate> onNotifyServerCert;

            public TlsAuthentication(Action<Certificate> onNotifyServerCert)
            {
                this.onNotifyServerCert = onNotifyServerCert;
            }

            /// <inheritdoc />
            public void NotifyServerCertificate(Certificate serverCertificate)
            {
                onNotifyServerCert(serverCertificate);
            }

            /// <inheritdoc />
            public TlsCredentials GetClientCredentials(
                Org.BouncyCastle.Crypto.Tls.CertificateRequest certificateRequest
            )
            {
                return null;
            }
        }
    }

    internal class Program
    {
        private static readonly RSAParameters OldRootCaRsaParams = new RSAParameters
        {
            Modulus = new byte[]
            {
                0xbd, 0x25, 0xba, 0x8a, 0xe4, 0xf8, 0x3b, 0xd3, 0xd7, 0x73, 0xe4, 0x27, 0x48, 0xb8, 0xf9, 0xf0, 0x83,
                0x27, 0x95, 0x16, 0xec, 0xa5, 0xdf, 0xfa, 0x5d, 0xa0, 0x10, 0xe1, 0x6a, 0x9b, 0xa3, 0x60, 0xeb, 0xef,
                0x75, 0xfd, 0xbd, 0x4d, 0x59, 0x0a, 0xdb, 0x04, 0x11, 0x6b, 0x0f, 0xd4, 0xc3, 0xb5, 0xb4, 0x47, 0x7d,
                0xf9, 0x7a, 0x73, 0xd3, 0xf7, 0x3d, 0x57, 0xae, 0x77, 0x17, 0x7a, 0x48, 0x03, 0x27, 0x74, 0x59, 0xd6,
                0xea, 0x98, 0xea, 0xb8, 0xd6, 0x84, 0x32, 0xab, 0x8f, 0x48, 0x3a, 0x84, 0x88, 0xbd, 0x0a, 0xd0, 0xb9,
                0xd9, 0xc6, 0x0b, 0xce, 0x1c, 0x9f, 0x13, 0x43, 0xe1, 0x18, 0xfc, 0xf0, 0xad, 0xf1, 0x36, 0xd9, 0x28,
                0x5f, 0xca, 0x11, 0xda, 0x1a, 0x8d, 0x83, 0x4e, 0x0e, 0x03, 0x6e, 0x27, 0xc9, 0x17, 0x0f, 0x28, 0xf1,
                0xaa, 0x9c, 0xd9, 0xc2, 0xa0, 0xe4, 0xe4, 0xa1, 0x6a, 0xca, 0xdb, 0xd7, 0x79, 0x3c, 0x27, 0x00, 0x81,
                0xd2, 0x45, 0xe3, 0x17, 0x48, 0x32, 0x6b, 0x31, 0x43, 0xd8, 0x16, 0xfc, 0xd9, 0x47, 0xba, 0xd9, 0xe2,
                0xd6, 0xc6, 0x98, 0xdc, 0xae, 0xe4, 0xf0, 0xb6, 0x4d, 0xed, 0x11, 0xec, 0x40, 0x6a, 0x19, 0x10, 0x01,
                0x44, 0xf4, 0x8a, 0x27, 0xd9, 0x06, 0x04, 0xec, 0xe1, 0xcb, 0x12, 0x87, 0x0b, 0xc3, 0x39, 0x6b, 0x0c,
                0x95, 0x14, 0x87, 0xdb, 0x9b, 0xc2, 0x98, 0xd7, 0xf4, 0x4a, 0x60, 0xca, 0xd6, 0xed, 0xa0, 0xc0, 0x46,
                0xfe, 0x00, 0x69, 0xbd, 0xb4, 0x70, 0x55, 0x94, 0x49, 0x98, 0x03, 0x43, 0x68, 0xf0, 0x96, 0xd2, 0x61,
                0xd6, 0x39, 0xa1, 0x48, 0x03, 0x7e, 0x78, 0xc3, 0xdb, 0xeb, 0x36, 0xd1, 0x83, 0x31, 0x16, 0x62, 0x89,
                0x38, 0xb3, 0x35, 0xbf, 0x9d, 0x7c, 0xc7, 0x92, 0x4f, 0xff, 0xcc, 0x45, 0x41, 0xad, 0x02, 0x23, 0x4c,
                0x39,
            },
            Exponent = new byte[] { 0x01, 0x00, 0x01 },
            P = new byte[]
            {
                0xc9, 0x4c, 0x6e, 0x6c, 0x3a, 0x6e, 0x79, 0x1b, 0x2e, 0x1b, 0x31, 0x51, 0x3a, 0xe3, 0x92, 0xb2, 0xe2,
                0x81, 0x7c, 0x6c, 0xa2, 0xbc, 0xd1, 0xd7, 0x83, 0xbc, 0x4c, 0xa1, 0xbe, 0xc7, 0xb4, 0x6b, 0x2b, 0x2e,
                0x2e, 0x07, 0x39, 0xcd, 0x64, 0x9d, 0xe6, 0x0d, 0xc6, 0xa0, 0x1d, 0xc9, 0x40, 0xfd, 0xa0, 0xa1, 0xa0,
                0x35, 0x10, 0x7c, 0x60, 0xc0, 0x2d, 0xdd, 0x1c, 0x14, 0x8f, 0x3c, 0x8e, 0x86, 0x8b, 0x6d, 0x1a, 0xf8,
                0x74, 0xe2, 0xd8, 0x94, 0x4d, 0xd5, 0xda, 0x44, 0xbd, 0xf6, 0x08, 0xeb, 0xc3, 0x35, 0x02, 0xc4, 0x40,
                0x95, 0x37, 0x10, 0x0b, 0xc4, 0xa1, 0x1d, 0xb3, 0x4a, 0xe3, 0x3f, 0x30, 0x8f, 0x8c, 0x60, 0x9a, 0xe9,
                0x41, 0xb7, 0xe6, 0x56, 0x28, 0xa9, 0x49, 0x00, 0xd8, 0x95, 0x49, 0xec, 0x9a, 0xe1, 0x88, 0x88, 0xea,
                0x6e, 0x64, 0x1b, 0xbf, 0xee, 0x5d, 0x1a, 0x3c, 0xd7,
            },
            Q = new byte[]
            {
                0xf0, 0x8b, 0xfc, 0x77, 0xd8, 0x73, 0x32, 0xd6, 0xba, 0x57, 0x97, 0xa2, 0xf9, 0xbc, 0xb5, 0x46, 0xee,
                0x8d, 0x54, 0xa1, 0xe0, 0x76, 0xa7, 0x4c, 0x22, 0x93, 0x57, 0x91, 0xa5, 0x2f, 0x14, 0xb8, 0x27, 0x78,
                0x71, 0xe7, 0xf4, 0xca, 0x49, 0xce, 0xc6, 0xa7, 0x56, 0x0f, 0x7b, 0xd1, 0xe9, 0xc7, 0x68, 0x97, 0x48,
                0x1e, 0x3e, 0xaa, 0x15, 0x9a, 0x6c, 0xc0, 0xcb, 0x73, 0xdb, 0x89, 0x27, 0xaf, 0xa2, 0x2e, 0x35, 0x5a,
                0x00, 0x4e, 0x4a, 0xd6, 0x60, 0x79, 0xb2, 0x37, 0xb7, 0x76, 0x0b, 0xeb, 0x69, 0x17, 0x87, 0x0f, 0x76,
                0xe4, 0xf4, 0xcb, 0xe4, 0xd7, 0xda, 0x90, 0xbc, 0x68, 0xe0, 0xcc, 0x71, 0x29, 0xee, 0x9e, 0x6d, 0xe7,
                0x8b, 0xdd, 0x25, 0x55, 0x1c, 0x3a, 0x69, 0x71, 0xfa, 0xd9, 0xec, 0xa5, 0xf3, 0x4b, 0x4e, 0x5c, 0x80,
                0xc0, 0xa8, 0x73, 0x7a, 0x52, 0x45, 0x34, 0x0d, 0x6f,
            },
            D = new byte[]
            {
                0x19, 0xc5, 0x27, 0x2a, 0x3f, 0x51, 0xf6, 0xcc, 0xc1, 0x01, 0x00, 0x27, 0xbe, 0x07, 0xbd, 0xbd, 0xe0,
                0x10, 0xa7, 0x86, 0x6d, 0x0d, 0x98, 0x7b, 0x83, 0x00, 0x08, 0xec, 0xbd, 0x5a, 0xa1, 0x22, 0xd6, 0x62,
                0x54, 0xc0, 0x12, 0x67, 0x94, 0x67, 0x1f, 0x39, 0xe9, 0xa9, 0x9d, 0x89, 0x8e, 0x74, 0x96, 0x30, 0x5b,
                0x60, 0x7f, 0x27, 0x82, 0xcc, 0x9f, 0xa5, 0xef, 0x96, 0x38, 0x33, 0xa7, 0xd6, 0x0e, 0x2d, 0x13, 0x04,
                0x6e, 0x08, 0x11, 0x8a, 0xc7, 0x67, 0x51, 0x84, 0x00, 0x4c, 0x85, 0xf4, 0x82, 0xb0, 0xc6, 0xe5, 0x73,
                0x3c, 0xd1, 0x77, 0xd0, 0x48, 0x24, 0x00, 0x3a, 0xf2, 0x53, 0x36, 0x3b, 0x10, 0x67, 0xd9, 0xe2, 0x32,
                0x57, 0x02, 0xaf, 0xf5, 0xf8, 0x42, 0x29, 0x8e, 0x9b, 0xe8, 0x29, 0xbc, 0xef, 0xef, 0xdd, 0x57, 0xe5,
                0xbd, 0x2a, 0x0b, 0x9d, 0x31, 0xd0, 0x9d, 0xb9, 0xc4, 0x17, 0xd6, 0x50, 0x97, 0xed, 0xfc, 0x8d, 0x2d,
                0xe2, 0x5c, 0xa9, 0xa5, 0xf7, 0xc7, 0xa4, 0x56, 0xac, 0xa3, 0xf1, 0x8c, 0x2c, 0x5c, 0x2c, 0x2b, 0xc9,
                0x09, 0x5c, 0x4d, 0x58, 0xea, 0x34, 0x97, 0x82, 0x29, 0x4c, 0x20, 0x1c, 0x98, 0xfd, 0xa0, 0xda, 0x12,
                0x89, 0x36, 0x03, 0x54, 0x17, 0x75, 0xb1, 0x15, 0x6f, 0x3a, 0xba, 0xfa, 0xd0, 0xc0, 0x65, 0x4a, 0x6b,
                0xee, 0xd2, 0xbe, 0x29, 0xdc, 0x32, 0x35, 0x6c, 0xa2, 0x46, 0xea, 0x6c, 0x34, 0x1a, 0x58, 0x54, 0xe7,
                0x16, 0x3e, 0xa7, 0x66, 0x95, 0x4b, 0xd3, 0x39, 0xd4, 0x6e, 0x8e, 0x67, 0x81, 0x84, 0x6c, 0x02, 0x34,
                0xbe, 0x2f, 0x29, 0x09, 0x73, 0xf2, 0x0f, 0x46, 0xc5, 0x97, 0xee, 0xef, 0x86, 0xa0, 0x9e, 0xb3, 0x51,
                0x09, 0xd0, 0xfe, 0xd6, 0x43, 0xcc, 0xcf, 0x6c, 0xae, 0xec, 0x26, 0x02, 0x85, 0xf6, 0x65, 0x04, 0x1f,
                0x75,
            },
            DP = new byte[]
            {
                0x27, 0xee, 0xd2, 0xa5, 0x03, 0xbe, 0x70, 0xa5, 0xf4, 0xb2, 0x8e, 0x49, 0x07, 0xfe, 0xa7, 0x56, 0x77,
                0x5a, 0xaa, 0x9d, 0x3e, 0x77, 0x68, 0xc5, 0x71, 0x2b, 0xc4, 0xa3, 0xfd, 0x9d, 0xce, 0x6b, 0xc8, 0x28,
                0x24, 0x17, 0x68, 0xdf, 0x17, 0x0b, 0xbf, 0xdc, 0x91, 0xbf, 0xa7, 0xc9, 0x09, 0xec, 0x86, 0x96, 0x29,
                0x9a, 0xfd, 0x47, 0x3f, 0x46, 0x40, 0x92, 0xf1, 0x84, 0x1e, 0x9a, 0x2b, 0xc8, 0x03, 0x16, 0xc4, 0xd7,
                0xef, 0x10, 0x25, 0x58, 0xde, 0x4c, 0x03, 0x3d, 0xc9, 0xd5, 0x6a, 0x15, 0xa3, 0x26, 0xfc, 0x02, 0x84,
                0xb1, 0x55, 0x38, 0x08, 0xad, 0x9c, 0x2c, 0xb1, 0x29, 0xb5, 0x05, 0x0f, 0x14, 0x1b, 0x0b, 0xfb, 0x0f,
                0x34, 0xeb, 0x4f, 0x75, 0x5e, 0x21, 0x9f, 0x0a, 0xdc, 0xec, 0x58, 0xba, 0xe8, 0x4a, 0xbf, 0x74, 0x31,
                0xd5, 0x6e, 0x64, 0x96, 0xe1, 0xa9, 0x74, 0x90, 0x07,
            },
            DQ = new byte[]
            {
                0x94, 0x29, 0xb5, 0x5d, 0x60, 0x4e, 0x3c, 0x9f, 0x34, 0x71, 0xe5, 0x6d, 0xb6, 0x23, 0xbd, 0x08, 0xae,
                0xc1, 0x10, 0xc3, 0x10, 0x8e, 0x25, 0x27, 0xe9, 0x18, 0x16, 0x55, 0x18, 0x88, 0xb2, 0x5b, 0x67, 0xbc,
                0x71, 0x74, 0xad, 0x3b, 0xb8, 0x50, 0x8b, 0xdd, 0xe0, 0x4d, 0x73, 0x37, 0xed, 0xa6, 0x2c, 0xcf, 0x19,
                0xd5, 0xbf, 0x45, 0x66, 0x3f, 0x13, 0x03, 0x81, 0x8d, 0xa0, 0x48, 0x8f, 0xd0, 0x47, 0xb5, 0xec, 0x98,
                0xbe, 0x1d, 0x7b, 0x4d, 0x26, 0x69, 0x6f, 0x13, 0x90, 0x86, 0x3e, 0x7b, 0x46, 0xe4, 0xfe, 0x04, 0xd1,
                0x77, 0xdf, 0x2c, 0x83, 0x8d, 0x4d, 0x0b, 0xdf, 0x71, 0x3e, 0x1d, 0xcf, 0x0a, 0x43, 0xf7, 0x03, 0xbd,
                0x1e, 0x52, 0x1d, 0xbf, 0x99, 0xbe, 0x84, 0xda, 0x76, 0xe7, 0xaa, 0x32, 0xe1, 0x73, 0x9e, 0x15, 0xae,
                0x1c, 0x7c, 0xe5, 0x7c, 0x0f, 0x96, 0x9e, 0xa4, 0x33,
            },
            InverseQ = new byte[]
            {
                0x60, 0x63, 0x29, 0x5e, 0x8a, 0x80, 0x9b, 0x52, 0xdc, 0x0a, 0x9e, 0x81, 0x99, 0x53, 0x40, 0x54, 0x74,
                0x6b, 0x11, 0xfe, 0xc8, 0x8f, 0x7b, 0x4b, 0x67, 0x9d, 0x2a, 0x83, 0x53, 0x32, 0x32, 0xec, 0xa4, 0xa7,
                0xaf, 0xeb, 0x4d, 0xac, 0xa4, 0x4b, 0xe0, 0xc3, 0x00, 0x01, 0x5d, 0x00, 0xef, 0x88, 0xea, 0x07, 0xa7,
                0xe5, 0xc1, 0x29, 0xde, 0xdf, 0x9b, 0x58, 0xed, 0xd0, 0x2a, 0xa3, 0xd9, 0x3a, 0x1c, 0xc5, 0xb4, 0x06,
                0xe8, 0xe8, 0x50, 0x03, 0x3d, 0x8b, 0xec, 0xa6, 0x1a, 0x8c, 0xe2, 0xab, 0x36, 0x54, 0x4c, 0xcd, 0x37,
                0x41, 0x8d, 0xd3, 0x6f, 0x3a, 0xc9, 0x97, 0xd3, 0x39, 0xdc, 0xf4, 0x19, 0x4a, 0xa7, 0xf1, 0x99, 0x1d,
                0x4e, 0x72, 0xb8, 0xd3, 0x3c, 0x3e, 0x8d, 0xf9, 0x3e, 0xfd, 0xc2, 0xb1, 0xc6, 0xb1, 0x75, 0x22, 0x72,
                0x04, 0x7e, 0x84, 0xc4, 0x1b, 0xf0, 0xa0, 0xaa, 0x75,
            },
        };

        private static readonly RSAParameters NewRootCaRsaParams = new RSAParameters
        {
            Modulus = new byte[]
            {
                0xa1, 0xed, 0x77, 0x2d, 0x3d, 0xd6, 0x6d, 0xd3, 0x5c, 0x7c, 0xe9, 0x96, 0x73, 0x51, 0x8b, 0x1f, 0xc7,
                0x9a, 0xca, 0xde, 0x28, 0x5b, 0xec, 0x05, 0xe1, 0x45, 0xac, 0xa0, 0x55, 0x78, 0x36, 0xb9, 0xd6, 0xb7,
                0x10, 0x67, 0xc2, 0x8d, 0xd6, 0xa1, 0x4a, 0x5f, 0x9e, 0xeb, 0xc1, 0x21, 0x7e, 0x5a, 0x6b, 0xd7, 0x4d,
                0x73, 0xbf, 0x72, 0x6f, 0x26, 0x32, 0x41, 0x44, 0x90, 0x4e, 0x00, 0x57, 0x2b, 0xd8, 0xf8, 0x44, 0x1a,
                0xc4, 0x58, 0x2e, 0x8c, 0x7b, 0x7c, 0x05, 0x5f, 0x2e, 0x53, 0xb4, 0xc7, 0x3f, 0x08, 0x8d, 0x38, 0xe8,
                0x12, 0x72, 0x29, 0xa0, 0x3c, 0x89, 0x4b, 0xd5, 0x2c, 0xe5, 0x52, 0xe0, 0xc8, 0x29, 0xf4, 0x53, 0x19,
                0x33, 0xb4, 0x5a, 0xb7, 0x0d, 0xdd, 0xd7, 0x51, 0x0d, 0xa8, 0x12, 0x6d, 0x5f, 0x63, 0x5d, 0x50, 0x68,
                0x45, 0x70, 0xd8, 0xe3, 0x63, 0x05, 0x05, 0x16, 0x33, 0x6a, 0xf3, 0x16, 0xd2, 0xfe, 0x10, 0x28, 0x11,
                0x5d, 0xee, 0xa2, 0x06, 0xa3, 0x09, 0x5f, 0xa9, 0x41, 0xa7, 0xae, 0xa6, 0x74, 0xa0, 0x52, 0xa2, 0x5a,
                0x49, 0xf9, 0x05, 0x74, 0x21, 0xe4, 0x9f, 0xd3, 0x5a, 0xea, 0xa2, 0xf8, 0x6f, 0x54, 0xf0, 0xc3, 0x8b,
                0x87, 0x91, 0x79, 0x4c, 0x99, 0x3f, 0x76, 0xe2, 0x2f, 0x34, 0x8c, 0xab, 0xef, 0x33, 0x4f, 0x7c, 0x57,
                0xef, 0x3d, 0xe2, 0x9f, 0x3d, 0xbc, 0x79, 0x7f, 0xd1, 0x9d, 0xca, 0x99, 0x3c, 0xda, 0x10, 0xab, 0x59,
                0xd0, 0x86, 0x49, 0x04, 0x09, 0x27, 0x21, 0xc6, 0xa9, 0x77, 0xc4, 0xef, 0x0c, 0xab, 0xf6, 0x45, 0x76,
                0x1e, 0xba, 0xaa, 0x7c, 0xee, 0x6c, 0xa3, 0xe4, 0x48, 0x2f, 0xa0, 0x31, 0x4f, 0x5b, 0xee, 0xa5, 0xa6,
                0xec, 0x00, 0xc4, 0x8e, 0xe0, 0x2e, 0xd9, 0x74, 0xa8, 0x4c, 0x60, 0xe1, 0x71, 0xca, 0x2c, 0x9d, 0xef,
                0xb1,
            },
            Exponent = new byte[] { 0x01, 0x00, 0x01 },
            P = new byte[]
            {
                0xcf, 0x75, 0xda, 0x43, 0x7f, 0x9c, 0x16, 0xfe, 0x17, 0xf0, 0xba, 0x7f, 0x59, 0x6a, 0xa1, 0xa7, 0xd0,
                0xed, 0xf8, 0xdf, 0x0d, 0x2e, 0xa8, 0xf9, 0x5d, 0xec, 0xd1, 0x2b, 0x66, 0x59, 0x70, 0xbb, 0x6e, 0x85,
                0xfe, 0x90, 0x0c, 0x9f, 0x56, 0xac, 0x38, 0x7b, 0x0e, 0x87, 0x25, 0xd4, 0xff, 0xe2, 0xb6, 0xc0, 0xfd,
                0xdf, 0xc9, 0x2b, 0x7c, 0x4b, 0x2f, 0xfc, 0x84, 0xc8, 0x87, 0x45, 0x09, 0x19, 0xf1, 0x32, 0x1c, 0x5d,
                0xf5, 0x9b, 0x6a, 0x75, 0x95, 0x6a, 0x4e, 0x6f, 0x6a, 0xfe, 0xe3, 0xad, 0x8f, 0x0e, 0x32, 0x9c, 0xd7,
                0x82, 0x5c, 0x01, 0x42, 0x0f, 0x7a, 0xee, 0x32, 0x44, 0x8a, 0x83, 0x5e, 0x30, 0x7f, 0xc2, 0xc2, 0x1d,
                0x3e, 0xed, 0x0d, 0x7a, 0x65, 0x10, 0x92, 0x32, 0x2e, 0x9b, 0x23, 0xeb, 0x71, 0xa5, 0x9e, 0xb4, 0xe1,
                0x0b, 0xb3, 0x09, 0xfb, 0x8d, 0x0a, 0xee, 0xf5, 0x13,
            },
            Q = new byte[]
            {
                0xc7, 0xd0, 0x5c, 0x3f, 0x07, 0xd8, 0x36, 0x9b, 0x3b, 0x1f, 0xeb, 0xef, 0x29, 0x96, 0x8d, 0xbf, 0x79,
                0x42, 0xea, 0x06, 0xc8, 0x0c, 0x18, 0x40, 0xca, 0xc8, 0x86, 0x6d, 0x65, 0xb4, 0x65, 0x5f, 0xe5, 0x44,
                0xaf, 0x93, 0xfd, 0x41, 0x56, 0xae, 0x7e, 0x08, 0x84, 0x5a, 0xab, 0x84, 0x23, 0x63, 0x84, 0x80, 0x52,
                0x32, 0x48, 0x4c, 0x17, 0x8a, 0x9a, 0xe8, 0xd6, 0xb6, 0x25, 0xb9, 0xe6, 0x11, 0x56, 0xa4, 0x12, 0x6b,
                0x46, 0x57, 0x10, 0xb2, 0xef, 0xe6, 0x75, 0x00, 0x5b, 0xe8, 0x7d, 0x50, 0x2e, 0xc4, 0xd0, 0xba, 0x8b,
                0x26, 0x32, 0xc1, 0x0c, 0x3a, 0xdc, 0xfe, 0xc7, 0xe0, 0x37, 0xc7, 0x68, 0xbf, 0x2a, 0x4a, 0x0a, 0x35,
                0x0b, 0x60, 0xe4, 0xfe, 0x7d, 0xc0, 0xe9, 0xbc, 0x69, 0x3b, 0x3a, 0x92, 0x41, 0x72, 0x33, 0xba, 0x04,
                0x62, 0x20, 0xcf, 0x23, 0x8c, 0x7f, 0xd2, 0x54, 0xab,
            },
            D = new byte[]
            {
                0x0e, 0xf7, 0x2c, 0x81, 0x90, 0xea, 0x85, 0x81, 0xf8, 0x55, 0x4f, 0x1d, 0x69, 0x91, 0x0a, 0xc3, 0xa8,
                0x26, 0x05, 0x43, 0xdd, 0x05, 0x09, 0xde, 0x4d, 0x40, 0xac, 0x40, 0xb1, 0x45, 0x88, 0x01, 0x4a, 0xf7,
                0xe6, 0x5b, 0x40, 0x9e, 0xed, 0x4c, 0x40, 0xdc, 0x6f, 0x8a, 0xa8, 0x71, 0xd9, 0x04, 0xb0, 0x5c, 0xd8,
                0xba, 0xae, 0xe1, 0xfb, 0xa5, 0xd3, 0x15, 0x37, 0x2a, 0x03, 0x1b, 0x82, 0xf2, 0xf9, 0x7d, 0x90, 0x85,
                0xcc, 0xc9, 0xbd, 0x9b, 0x9c, 0x3a, 0x0d, 0x21, 0x0b, 0xde, 0x0e, 0xa2, 0x2f, 0x90, 0x31, 0xc5, 0x25,
                0xa5, 0xb6, 0xc9, 0x64, 0x16, 0xd4, 0x35, 0x9f, 0x01, 0x24, 0x3c, 0xb1, 0xab, 0xae, 0x3c, 0xf7, 0x85,
                0x27, 0xc3, 0x2e, 0x73, 0x26, 0xa3, 0xef, 0x27, 0x7d, 0x94, 0xa0, 0x07, 0x5d, 0xbe, 0x88, 0x3c, 0x4b,
                0x36, 0xc7, 0xc4, 0x33, 0xd7, 0xcd, 0xf9, 0xf7, 0x60, 0x29, 0x89, 0x1b, 0xbe, 0x34, 0x08, 0x19, 0x7b,
                0xe8, 0xbd, 0x35, 0x68, 0x8c, 0xe5, 0x54, 0xd8, 0x73, 0xef, 0x95, 0xcb, 0x3a, 0xb0, 0x1f, 0x6d, 0x3a,
                0xcf, 0xda, 0xcf, 0x79, 0x05, 0x49, 0x86, 0x33, 0x67, 0xf8, 0x3f, 0x5e, 0xfa, 0xee, 0x40, 0x2e, 0xfa,
                0x22, 0x42, 0x2d, 0xca, 0x91, 0x87, 0xb4, 0x5c, 0x1c, 0xe3, 0x49, 0xf1, 0xbf, 0xd3, 0xf7, 0xd2, 0x20,
                0x88, 0xa2, 0xcb, 0x78, 0x55, 0x6c, 0x54, 0x3f, 0xc3, 0xff, 0x8f, 0x02, 0xd0, 0xac, 0x38, 0x84, 0x4d,
                0x9c, 0x10, 0x04, 0xaf, 0xb8, 0x3c, 0x91, 0x07, 0x85, 0xca, 0xd6, 0xdd, 0xf1, 0xc6, 0x81, 0xe4, 0xf9,
                0xa8, 0x53, 0xad, 0x3c, 0xdb, 0xcb, 0xce, 0x3d, 0x17, 0xab, 0xd7, 0x1b, 0x19, 0xa5, 0x5d, 0xed, 0xa2,
                0x2c, 0x9d, 0x32, 0x8b, 0xb9, 0xd8, 0x1b, 0xfd, 0x0b, 0x52, 0x98, 0x44, 0x41, 0xa8, 0x33, 0x7d, 0x7a,
                0x15,
            },
            DP = new byte[]
            {
                0x40, 0x95, 0x5d, 0xb7, 0x39, 0x98, 0xac, 0x07, 0xba, 0x08, 0x34, 0xe6, 0xc4, 0x3d, 0x4f, 0xc9, 0xe0,
                0x5e, 0xd4, 0xe5, 0x0b, 0x43, 0x85, 0x52, 0xec, 0x0c, 0x77, 0x29, 0x5d, 0x99, 0x66, 0x60, 0xd3, 0x0d,
                0x5f, 0xac, 0x14, 0x61, 0xde, 0x27, 0x8b, 0x05, 0xb4, 0x06, 0x1a, 0x9e, 0xee, 0x71, 0xf3, 0x96, 0xac,
                0xf1, 0xe7, 0xbc, 0x63, 0xdb, 0x3c, 0x3b, 0x0e, 0x53, 0x26, 0xd9, 0x34, 0xf6, 0x20, 0x82, 0x0b, 0x16,
                0xad, 0x09, 0x0e, 0xe1, 0x3f, 0xfc, 0x72, 0x22, 0x79, 0x5a, 0x94, 0x22, 0x49, 0xb0, 0x8f, 0xcd, 0x07,
                0xc2, 0x0b, 0x46, 0x50, 0xfc, 0x64, 0xb6, 0x96, 0x6b, 0x83, 0xfb, 0x55, 0x6d, 0x1f, 0xb8, 0xcf, 0x99,
                0x2f, 0x27, 0xb8, 0xd4, 0x6b, 0x75, 0xf7, 0x2d, 0x2c, 0x19, 0x1b, 0xdb, 0x85, 0xeb, 0x9b, 0x36, 0x90,
                0x00, 0x86, 0x62, 0x0b, 0x2f, 0x63, 0x41, 0xf4, 0x85,
            },
            DQ = new byte[]
            {
                0x48, 0xf4, 0x12, 0x3a, 0x11, 0x2b, 0x28, 0x04, 0xd5, 0x39, 0x7a, 0x72, 0xff, 0xc4, 0x30, 0x4c, 0xd0,
                0x81, 0x55, 0xe0, 0xd3, 0xf9, 0x56, 0x81, 0x91, 0x88, 0x55, 0x4c, 0x6b, 0xed, 0xc9, 0x75, 0x91, 0xc7,
                0xda, 0x33, 0x36, 0x2c, 0x3c, 0xf1, 0xc8, 0xa2, 0x63, 0x70, 0x97, 0x5e, 0xcf, 0x6c, 0xee, 0x5b, 0xcb,
                0xa7, 0x00, 0x2e, 0x88, 0x09, 0xdb, 0x35, 0x57, 0xef, 0x79, 0x26, 0xbe, 0x91, 0x66, 0x08, 0xd1, 0x90,
                0xc9, 0x45, 0xd0, 0x9b, 0x04, 0x6c, 0x28, 0x1b, 0xd1, 0x72, 0xb9, 0x0e, 0x25, 0x85, 0x46, 0x90, 0x55,
                0x6d, 0xc4, 0xea, 0x27, 0x04, 0xf6, 0xdb, 0x2f, 0x2e, 0xf6, 0x2e, 0xa0, 0x57, 0xeb, 0xa0, 0xcc, 0xc9,
                0x41, 0x36, 0x47, 0x65, 0x83, 0x94, 0x83, 0xc2, 0x7e, 0xc9, 0x59, 0x41, 0x3d, 0x13, 0xed, 0x3e, 0xe5,
                0x8a, 0xce, 0x5f, 0xd2, 0xa2, 0xe1, 0xa8, 0x71, 0xb1,
            },
            InverseQ = new byte[]
            {
                0x65, 0xdf, 0x97, 0xf3, 0x01, 0x6b, 0x10, 0x0a, 0xac, 0x80, 0x08, 0xac, 0xa5, 0x17, 0xd5, 0xeb, 0x2f,
                0x9a, 0xe4, 0xca, 0xa2, 0x19, 0x39, 0xd0, 0xe9, 0x15, 0xb0, 0x7e, 0xbd, 0xac, 0x7f, 0x04, 0x50, 0x34,
                0x01, 0xc2, 0x1d, 0xb3, 0x55, 0x3a, 0xc2, 0x76, 0x59, 0x74, 0x6b, 0xf0, 0x8e, 0x1a, 0x9c, 0x5d, 0xb6,
                0x89, 0x45, 0x08, 0xfb, 0x93, 0x34, 0x47, 0x00, 0x39, 0x92, 0xc2, 0xc3, 0xdb, 0x09, 0x23, 0x67, 0x7a,
                0xd2, 0x49, 0x6f, 0x09, 0xbc, 0xb7, 0x54, 0x2b, 0x35, 0xfe, 0xfa, 0x44, 0x48, 0x53, 0x1f, 0x78, 0x90,
                0xca, 0x4a, 0x1a, 0xa0, 0xe2, 0x60, 0xca, 0xfc, 0x38, 0xec, 0x23, 0xfb, 0xda, 0x4e, 0x6d, 0xac, 0xf8,
                0x57, 0x73, 0xc3, 0x58, 0xb9, 0x3f, 0xed, 0x5e, 0x13, 0xd4, 0xed, 0x97, 0x40, 0xd9, 0xfa, 0x88, 0xde,
                0x13, 0x48, 0xc0, 0x38, 0xd6, 0xad, 0x7c, 0x58, 0xb9,
            },
        };

        private static readonly RSAParameters SecondIntermediateRsaParams = new RSAParameters
        {
            Modulus = new byte[]
            {
                0xcf, 0xfc, 0xb1, 0x72, 0xcb, 0xeb, 0xc4, 0x67, 0xff, 0xc9, 0xc3, 0x48, 0xdc, 0x69, 0xd9, 0x78, 0xa6,
                0xc1, 0x66, 0x88, 0x7e, 0x69, 0xba, 0x6c, 0xd4, 0xf2, 0x9d, 0x4c, 0xf9, 0x1a, 0xdc, 0xc3, 0xd3, 0x45,
                0x89, 0xd1, 0xcf, 0x68, 0x40, 0x99, 0x9a, 0x32, 0xa9, 0xfd, 0x70, 0x8a, 0xa2, 0xeb, 0xeb, 0xda, 0x0d,
                0x1a, 0x8e, 0xb8, 0x2e, 0x5a, 0xe8, 0xa9, 0x84, 0x1f, 0xc2, 0x8b, 0x32, 0x92, 0x91, 0x3e, 0xec, 0x07,
                0x93, 0xb6, 0x43, 0x2d, 0xf6, 0xe6, 0x6a, 0x65, 0x10, 0xd9, 0x43, 0xbb, 0x64, 0xb7, 0xe5, 0xd6, 0xfb,
                0x60, 0xfc, 0x2f, 0x63, 0xe4, 0xcb, 0xcc, 0xe1, 0x75, 0x0f, 0x0f, 0x08, 0x6d, 0x02, 0x33, 0x15, 0xbe,
                0x49, 0x49, 0xb2, 0xbe, 0x72, 0x0c, 0x31, 0xf3, 0x8c, 0x3f, 0xc0, 0xa4, 0x2b, 0xbe, 0x35, 0xd7, 0x4b,
                0xa0, 0x3b, 0xca, 0xbf, 0x12, 0xa4, 0x7b, 0x98, 0xa9, 0x74, 0x51, 0xca, 0xb7, 0x58, 0xa8, 0x7d, 0x12,
                0x1c, 0x8d, 0xc7, 0x25, 0xa2, 0xe2, 0x1e, 0xf6, 0x47, 0x85, 0xf2, 0x42, 0xc7, 0x20, 0x1d, 0xcd, 0xf2,
                0xca, 0x3a, 0x2e, 0xe5, 0x18, 0xeb, 0xe5, 0xbb, 0xc6, 0x34, 0x76, 0x47, 0x33, 0x12, 0xe4, 0xd5, 0x05,
                0xcf, 0x63, 0x42, 0xb6, 0x54, 0x7d, 0x6f, 0x3b, 0x47, 0x8d, 0x82, 0xe8, 0x08, 0x66, 0x33, 0xc0, 0xc3,
                0x2c, 0x23, 0xe7, 0x75, 0x88, 0x33, 0x49, 0xfd, 0x4d, 0xea, 0x86, 0xf2, 0x47, 0xab, 0x5b, 0x83, 0x47,
                0x4e, 0x42, 0x8c, 0xfe, 0xf2, 0xff, 0x14, 0x2b, 0xb8, 0x33, 0x61, 0xf9, 0x13, 0x1b, 0x72, 0xc9, 0xac,
                0x91, 0x94, 0xe1, 0xc9, 0xa0, 0x3a, 0x65, 0x9d, 0x2d, 0x2c, 0xf6, 0x6f, 0x8f, 0xae, 0x96, 0x6a, 0x76,
                0xfb, 0xa0, 0x1d, 0x61, 0xef, 0x92, 0x4c, 0x8a, 0x1f, 0x7f, 0xb4, 0x75, 0x8c, 0xdf, 0x06, 0x0b, 0x20,
                0x01,
            },
            Exponent = new byte[] { 0x01, 0x00, 0x01 },
            P = new byte[]
            {
                0xdc, 0x0e, 0x30, 0x3b, 0x53, 0x44, 0xc9, 0xf1, 0x4f, 0x56, 0x33, 0x3e, 0x8c, 0xa6, 0x2f, 0xfa, 0x8c,
                0x6a, 0xa9, 0x9a, 0x84, 0x6e, 0x7d, 0xf3, 0x29, 0x3e, 0xaf, 0x59, 0x70, 0x44, 0x8c, 0x8c, 0x74, 0x78,
                0x3c, 0xb0, 0x45, 0x9c, 0x4d, 0x1c, 0xc0, 0x18, 0x82, 0x04, 0x97, 0xe6, 0x01, 0x1c, 0xa2, 0xf8, 0x06,
                0xbe, 0x14, 0x5f, 0xe9, 0xcf, 0x30, 0xa7, 0x94, 0xe5, 0x45, 0xf2, 0xc5, 0x82, 0x10, 0x61, 0xb7, 0x4d,
                0x9d, 0xb8, 0xc8, 0x42, 0x49, 0x32, 0x48, 0xc3, 0x74, 0x16, 0xf7, 0x4e, 0x7c, 0x38, 0x50, 0xf0, 0x30,
                0x95, 0x9d, 0x8d, 0x93, 0xc1, 0xee, 0xa1, 0x61, 0xa3, 0xb0, 0xc6, 0x80, 0x26, 0x3b, 0x71, 0x8d, 0x25,
                0x72, 0xba, 0xf5, 0xab, 0x70, 0xf0, 0x64, 0x9c, 0xf2, 0x72, 0x7b, 0x17, 0x03, 0xf2, 0xee, 0x97, 0xd8,
                0x09, 0x83, 0x21, 0x58, 0xd9, 0x24, 0xfb, 0x15, 0x17,
            },
            Q = new byte[]
            {
                0xf1, 0xf5, 0xdb, 0x3f, 0x93, 0xbf, 0x6d, 0xc2, 0x6e, 0xa5, 0x38, 0xb0, 0x16, 0x2c, 0xed, 0xdc, 0x64,
                0x06, 0xa0, 0x74, 0x06, 0x6a, 0xf6, 0xdc, 0xa5, 0x49, 0xd6, 0x6a, 0xdd, 0x83, 0x7f, 0x68, 0x48, 0xa9,
                0x60, 0xd2, 0xaf, 0x8e, 0x0c, 0xa3, 0xb0, 0x10, 0x31, 0x1f, 0x10, 0xdc, 0x6c, 0x02, 0xaa, 0x7e, 0x5c,
                0x62, 0x0d, 0x57, 0x7b, 0x73, 0x15, 0xa7, 0x9c, 0xda, 0xca, 0xcf, 0x65, 0xfb, 0xbe, 0x8c, 0xbf, 0x23,
                0x25, 0x79, 0x00, 0x4b, 0x2e, 0x19, 0xad, 0xed, 0x0c, 0x9b, 0x58, 0x89, 0xa5, 0x1e, 0xc0, 0x6b, 0x7a,
                0x0d, 0xdb, 0x13, 0x56, 0x57, 0x4a, 0xf3, 0xed, 0x79, 0x2f, 0xd0, 0x35, 0xbd, 0x69, 0xe7, 0xfb, 0xbb,
                0x66, 0x4e, 0x80, 0x15, 0xd4, 0xb8, 0x84, 0xa4, 0xd2, 0x2a, 0x39, 0xc5, 0x1c, 0x64, 0x25, 0x8f, 0x49,
                0x51, 0xc5, 0x26, 0xe1, 0x8b, 0xcf, 0xd9, 0x52, 0xa7,
            },
            D = new byte[]
            {
                0x71, 0x54, 0x21, 0x64, 0x89, 0xd4, 0xd7, 0xda, 0x4b, 0x38, 0x27, 0x1e, 0x86, 0x6b, 0x6e, 0xc2, 0x1d,
                0x2a, 0xf8, 0xb1, 0x9b, 0x20, 0x05, 0x22, 0xd4, 0x0d, 0xb6, 0xc4, 0x80, 0x5f, 0xb0, 0xb7, 0x4b, 0x61,
                0x72, 0xad, 0x4e, 0x34, 0x62, 0x2e, 0xd0, 0x24, 0x97, 0x17, 0xaf, 0xc0, 0xfa, 0xf1, 0x4e, 0x3c, 0x2c,
                0x33, 0xb2, 0x5c, 0x8d, 0x75, 0x9f, 0x9c, 0xee, 0x8f, 0x57, 0xb3, 0xfd, 0x44, 0x7b, 0xbc, 0x2b, 0x64,
                0x1d, 0x8a, 0x8c, 0xa4, 0x79, 0x0a, 0x28, 0x23, 0x15, 0xc1, 0x2c, 0xf4, 0xe8, 0xf6, 0x83, 0x58, 0x56,
                0xf5, 0x0b, 0xb4, 0xce, 0xdb, 0x96, 0x68, 0xf6, 0xd8, 0x8f, 0xf2, 0x03, 0x07, 0x52, 0xd1, 0x7f, 0x21,
                0x86, 0x51, 0x90, 0x01, 0xd1, 0xa6, 0xef, 0x78, 0xe3, 0x8c, 0xc1, 0x6d, 0x33, 0x20, 0x9b, 0xb9, 0xca,
                0x4d, 0x6f, 0x07, 0x14, 0x82, 0x15, 0xf9, 0xfa, 0xbe, 0x2f, 0x11, 0xe2, 0x08, 0x0f, 0x6c, 0x11, 0x18,
                0x50, 0xb3, 0xb2, 0xdc, 0x0f, 0x3e, 0x1d, 0xc0, 0x73, 0xf4, 0x9f, 0x8a, 0x86, 0xe5, 0x81, 0xa3, 0xa9,
                0x21, 0xdc, 0x2a, 0xd0, 0x74, 0x92, 0xf1, 0x7f, 0xef, 0x33, 0x09, 0x36, 0x43, 0xe6, 0x8d, 0xfc, 0x1f,
                0xb7, 0x0a, 0xb8, 0xda, 0x7b, 0xd1, 0x80, 0xd9, 0x06, 0x16, 0xcb, 0x84, 0x73, 0x00, 0x5a, 0x9b, 0x2d,
                0x40, 0x14, 0x56, 0x97, 0x37, 0x7e, 0x63, 0x98, 0xe2, 0xdc, 0x51, 0xbd, 0x40, 0xe6, 0xa9, 0x2a, 0x53,
                0xe9, 0x82, 0x94, 0xaf, 0x98, 0x41, 0xd1, 0xf6, 0x87, 0xcd, 0x48, 0xba, 0x6e, 0xed, 0xf5, 0x39, 0xa1,
                0x4a, 0xbc, 0x0a, 0xc4, 0xa0, 0x57, 0x1e, 0x93, 0xd8, 0xdb, 0x4a, 0x1e, 0xfb, 0xe7, 0x94, 0xb2, 0x78,
                0xca, 0x97, 0x22, 0xd6, 0xf6, 0x8a, 0x30, 0x39, 0x50, 0x7d, 0x8c, 0x2c, 0x4d, 0xea, 0x91, 0x82, 0x9d,
                0x79,
            },
            DP = new byte[]
            {
                0x04, 0x17, 0xd3, 0x77, 0xd7, 0x05, 0xa9, 0x87, 0xee, 0x84, 0xd8, 0xf1, 0x29, 0xe6, 0x91, 0x6f, 0xe4,
                0x9a, 0xbb, 0x4a, 0xdf, 0x79, 0xba, 0xa8, 0x02, 0x35, 0x63, 0x47, 0x93, 0x9a, 0x02, 0xcf, 0x3d, 0xff,
                0x5a, 0x89, 0xa0, 0xd1, 0xd6, 0x71, 0x59, 0x75, 0x5b, 0x3d, 0xc0, 0xa1, 0x8b, 0x95, 0xbf, 0xc6, 0x43,
                0x41, 0xd7, 0xd9, 0x2c, 0xdd, 0x28, 0xb7, 0x85, 0x42, 0x7a, 0x79, 0x98, 0x88, 0xe1, 0xcb, 0x1f, 0x80,
                0xb7, 0x15, 0x4f, 0xcc, 0x43, 0xd6, 0x46, 0x9a, 0x34, 0x17, 0x95, 0x44, 0x05, 0x75, 0x7a, 0xb3, 0xed,
                0x03, 0x6b, 0x69, 0xcd, 0xbc, 0xc6, 0xb6, 0x83, 0xbd, 0x6a, 0xb5, 0x56, 0x7e, 0xdf, 0x8e, 0x2e, 0x24,
                0xeb, 0x2c, 0x65, 0x13, 0x6a, 0x47, 0x5e, 0x67, 0xb8, 0xee, 0xbe, 0x31, 0x60, 0x66, 0x6d, 0x5f, 0xaf,
                0xaa, 0x05, 0xa1, 0x62, 0x28, 0xaf, 0xd2, 0x67, 0xc3,
            },
            DQ = new byte[]
            {
                0xd7, 0xa6, 0xc3, 0xe9, 0xaf, 0x5a, 0x9f, 0x8d, 0xfa, 0xa5, 0xc1, 0x97, 0x97, 0xbc, 0x25, 0xa8, 0x17,
                0x72, 0x9c, 0x91, 0xac, 0xa2, 0xab, 0x31, 0x4f, 0x14, 0x37, 0x49, 0xf9, 0xf2, 0x58, 0x9c, 0xee, 0x9e,
                0x20, 0x10, 0xc7, 0xad, 0x38, 0x70, 0xaf, 0xdc, 0x4e, 0x9b, 0x9c, 0xa0, 0x88, 0x97, 0xfb, 0xb3, 0x31,
                0xcd, 0xa6, 0x8c, 0x81, 0x17, 0xba, 0x86, 0x30, 0x54, 0x05, 0x0c, 0xc2, 0x9d, 0x94, 0x60, 0xc7, 0x7e,
                0xa0, 0xc8, 0x6e, 0xcd, 0x92, 0xe2, 0x0a, 0x55, 0x4b, 0xb6, 0x2d, 0x3c, 0x48, 0xf1, 0xd6, 0x98, 0x34,
                0x29, 0xa3, 0x47, 0x61, 0xdd, 0xdd, 0x8c, 0xf8, 0xeb, 0xd9, 0x04, 0x9e, 0xd1, 0x1a, 0x52, 0x4b, 0xd7,
                0xe3, 0xc0, 0x35, 0x82, 0xbf, 0x9c, 0x51, 0x84, 0xc1, 0x58, 0x27, 0x33, 0x1a, 0x31, 0xdc, 0x14, 0x10,
                0x4a, 0x14, 0x08, 0xaa, 0x5f, 0xbd, 0x39, 0xb9, 0xe3,
            },
            InverseQ = new byte[]
            {
                0xc3, 0xb6, 0xac, 0xaf, 0x03, 0x37, 0xce, 0x27, 0x48, 0xf7, 0x96, 0xa4, 0xac, 0xec, 0x2d, 0xd1, 0x22,
                0xe5, 0x05, 0xd2, 0x98, 0x5f, 0x35, 0xfc, 0x25, 0x63, 0xa6, 0x8e, 0x44, 0x63, 0x45, 0xef, 0xb1, 0x62,
                0xfb, 0x54, 0xcd, 0xa3, 0x0f, 0xa7, 0xaf, 0xb6, 0x63, 0x7a, 0xb2, 0x9d, 0xff, 0xe7, 0x84, 0x1e, 0x25,
                0xca, 0x6d, 0xa7, 0x41, 0x2a, 0xde, 0x25, 0xdb, 0xa8, 0xa2, 0x7a, 0x2d, 0x3d, 0xc9, 0x4d, 0x5e, 0x0b,
                0x78, 0x19, 0xfe, 0xc0, 0x20, 0x26, 0x72, 0xf4, 0xf5, 0xd2, 0x06, 0x68, 0x8a, 0xe9, 0xb9, 0xd6, 0xc9,
                0x6e, 0x9d, 0x7c, 0x8b, 0x56, 0xa7, 0x84, 0x65, 0xbc, 0xc3, 0xd7, 0xe6, 0xf6, 0x37, 0x1c, 0x68, 0x60,
                0xed, 0x4c, 0xba, 0x2d, 0x05, 0xeb, 0x00, 0xee, 0xe7, 0xf8, 0x52, 0x66, 0x98, 0xeb, 0x16, 0x16, 0x5f,
                0x6a, 0x8f, 0x61, 0x19, 0x2d, 0x96, 0x54, 0xff, 0xf6,
            },
        };

        private static readonly RSAParameters EndEntityRsaParams = new RSAParameters
        {
            Modulus = new byte[]
            {
                0xd8, 0xff, 0xb0, 0xad, 0xe1, 0x4f, 0xf0, 0x1f, 0x07, 0x28, 0x6a, 0x70, 0x2a, 0x8d, 0x92, 0x91, 0xd1,
                0x7f, 0x06, 0x1b, 0x51, 0xa3, 0x82, 0x48, 0x32, 0x67, 0xd5, 0x55, 0x2b, 0x1b, 0x53, 0x51, 0x24, 0x62,
                0xd7, 0xac, 0xb9, 0xf4, 0x27, 0x00, 0xf4, 0x23, 0xbf, 0xa9, 0xb3, 0xc7, 0x13, 0xfb, 0x46, 0x44, 0x4e,
                0xb4, 0x1a, 0x92, 0x4a, 0xfe, 0xc2, 0x39, 0x9b, 0xf3, 0x54, 0xee, 0x63, 0xd5, 0xa6, 0xcc, 0x85, 0xff,
                0xd8, 0x13, 0x0e, 0x77, 0xda, 0x7b, 0x11, 0x49, 0xb9, 0xa0, 0x9b, 0x73, 0x18, 0x2b, 0xae, 0x1d, 0xe1,
                0x8a, 0xfc, 0xfb, 0xd1, 0x6e, 0xa1, 0x8b, 0x7d, 0xb4, 0xde, 0xdc, 0xec, 0x58, 0xd8, 0x5a, 0x25, 0xfc,
                0xd6, 0x19, 0xfb, 0xc5, 0xa4, 0x88, 0x5d, 0x74, 0x7d, 0x5c, 0xeb, 0x64, 0xeb, 0x85, 0x1f, 0xb2, 0x89,
                0xc9, 0xb7, 0xeb, 0x68, 0xa6, 0x94, 0x18, 0xfd, 0x71, 0xce, 0x8d, 0x80, 0x9f, 0x52, 0x99, 0x4d, 0x9a,
                0x7b, 0x22, 0xbf, 0x9e, 0xb8, 0x18, 0x49, 0xda, 0xce, 0xff, 0x24, 0x32, 0x7b, 0x61, 0x0d, 0x4b, 0xbd,
                0x4e, 0xfe, 0xd3, 0x96, 0x49, 0xe1, 0xbf, 0xc2, 0x4f, 0x93, 0x7d, 0xcf, 0x35, 0xce, 0x80, 0xd2, 0x91,
                0x31, 0xa2, 0xd6, 0xd7, 0xe2, 0xc2, 0x9b, 0x90, 0xae, 0xfa, 0x4c, 0x11, 0x0f, 0x9c, 0xf7, 0x59, 0xea,
                0x81, 0x6b, 0xee, 0x4b, 0x43, 0x28, 0x88, 0x7e, 0x2c, 0xd8, 0xbd, 0x40, 0xc1, 0x2f, 0xf7, 0x78, 0x1d,
                0x63, 0x78, 0x15, 0x88, 0x38, 0x5b, 0x37, 0xfb, 0xdf, 0x12, 0x43, 0xe6, 0x0b, 0x26, 0xd5, 0xfd, 0x68,
                0xa6, 0x18, 0x05, 0x19, 0x26, 0x95, 0xed, 0x08, 0x37, 0xa8, 0xae, 0x6a, 0x83, 0x87, 0xc7, 0x68, 0xb6,
                0x16, 0x44, 0x8a, 0xec, 0x21, 0x04, 0x71, 0x95, 0x96, 0x38, 0x08, 0x77, 0x51, 0xdc, 0xb4, 0xf7, 0xf3,
                0xf9,
            },
            Exponent = new byte[] { 0x01, 0x00, 0x01 },
            P = new byte[]
            {
                0xf2, 0x53, 0xd8, 0xc9, 0xe5, 0xd1, 0xc2, 0x58, 0xce, 0x8e, 0xdb, 0xbf, 0xfc, 0x8d, 0x5e, 0x0a, 0xc5,
                0xfd, 0xdb, 0x4b, 0x27, 0x99, 0x23, 0x01, 0x4c, 0x22, 0x65, 0xdf, 0xfe, 0xc7, 0x29, 0x8a, 0xa7, 0xae,
                0xe4, 0xdc, 0xc4, 0xfa, 0x6d, 0xa5, 0x7d, 0x73, 0xc3, 0xee, 0x1b, 0xa0, 0x06, 0x30, 0x50, 0xdd, 0x71,
                0xff, 0xd6, 0x08, 0x17, 0xe5, 0x98, 0xfe, 0x1c, 0xb7, 0x0b, 0xef, 0x6f, 0xf5, 0xbe, 0xaa, 0xb8, 0x53,
                0x2c, 0x26, 0xe8, 0xe8, 0x39, 0xaf, 0xd2, 0x8c, 0xe0, 0x87, 0x08, 0x9c, 0xf3, 0x60, 0xd8, 0xb1, 0xd9,
                0x66, 0x93, 0xb9, 0x35, 0x11, 0xf5, 0xb6, 0xe2, 0x53, 0xb5, 0xa3, 0xdb, 0x1d, 0x75, 0x5c, 0xcb, 0x7c,
                0x3b, 0x91, 0x2f, 0x01, 0xf1, 0x46, 0x6b, 0xe7, 0xc0, 0x08, 0xa5, 0x89, 0x8a, 0xac, 0x5b, 0xa3, 0x78,
                0x12, 0x8e, 0x62, 0x0f, 0x3a, 0xf9, 0x88, 0x3d, 0x47,
            },
            Q = new byte[]
            {
                0xe5, 0x3d, 0xff, 0x6a, 0xcd, 0xf6, 0xb3, 0x2a, 0xd7, 0xee, 0x3a, 0x63, 0x30, 0x1c, 0x39, 0x51, 0x22,
                0x78, 0xed, 0x28, 0xb8, 0x11, 0x94, 0x78, 0xfa, 0x70, 0xd6, 0xdc, 0xa2, 0xa5, 0x5f, 0x90, 0x4b, 0x08,
                0x41, 0x3e, 0x73, 0x72, 0xe0, 0xde, 0x03, 0x3f, 0xb5, 0x07, 0xcc, 0x36, 0x46, 0x18, 0x6a, 0x4f, 0x8a,
                0x30, 0xdd, 0xf9, 0x28, 0xb0, 0x3a, 0xd7, 0x9b, 0x04, 0x14, 0x7b, 0xab, 0x2b, 0xa9, 0xb9, 0x61, 0xc8,
                0xcf, 0x59, 0x5c, 0x25, 0xdb, 0x05, 0x12, 0x97, 0x68, 0x7f, 0xad, 0x0c, 0xd2, 0x91, 0xfe, 0xa7, 0x10,
                0x1c, 0xa0, 0xa6, 0x22, 0xc3, 0xa8, 0x2c, 0x82, 0xef, 0xd7, 0xd7, 0x47, 0xa7, 0xd8, 0x74, 0x12, 0xb0,
                0xcd, 0x96, 0x6b, 0x9a, 0x50, 0xf0, 0xb1, 0xb6, 0xf1, 0x7c, 0x9c, 0xc5, 0x1a, 0x62, 0x24, 0x87, 0x99,
                0x85, 0xa3, 0xad, 0x39, 0x36, 0xc3, 0xc9, 0xe4, 0xbf,
            },
            D = new byte[]
            {
                0xa6, 0xa4, 0xcd, 0x70, 0xea, 0xfb, 0xf1, 0xa2, 0x52, 0x63, 0xe6, 0x41, 0x97, 0x5c, 0x3b, 0x78, 0x02,
                0x13, 0x73, 0x84, 0x1d, 0x50, 0xdd, 0x27, 0x46, 0x96, 0x58, 0xcd, 0x5c, 0x1a, 0x53, 0x04, 0x98, 0x55,
                0xd3, 0xdd, 0x50, 0xbc, 0xc0, 0x0b, 0x4a, 0x71, 0xfd, 0xa9, 0x7c, 0x67, 0x60, 0xdf, 0xf2, 0x19, 0x58,
                0xfb, 0x95, 0x00, 0x4d, 0xd9, 0x91, 0x1c, 0x9e, 0xb7, 0xe2, 0xbc, 0x64, 0x2c, 0xda, 0x38, 0x6c, 0x9b,
                0x8a, 0xbb, 0x2f, 0xbc, 0x39, 0x2b, 0x93, 0x9e, 0x33, 0x90, 0xb4, 0x70, 0x51, 0xda, 0x91, 0x8f, 0x5e,
                0xfa, 0xd6, 0xc7, 0x28, 0x11, 0xb6, 0xbb, 0xa1, 0xe0, 0xf9, 0xd9, 0x5d, 0x23, 0xe9, 0x9a, 0x69, 0x5b,
                0xde, 0xab, 0xfb, 0x9e, 0xcf, 0x78, 0xed, 0x94, 0x1d, 0x05, 0xf3, 0xbb, 0xff, 0xe6, 0xae, 0xed, 0xf4,
                0x44, 0xd6, 0x1a, 0x51, 0xb6, 0xc3, 0x3a, 0xe1, 0xbe, 0x4f, 0x44, 0xfa, 0x14, 0x4f, 0x3c, 0x81, 0x06,
                0x1f, 0x6d, 0xcd, 0x57, 0x14, 0x40, 0x01, 0x91, 0xd4, 0xc6, 0x58, 0xf6, 0x6b, 0x2c, 0x3e, 0x81, 0x6a,
                0x96, 0x4c, 0x3a, 0x46, 0xf7, 0x89, 0x30, 0xa0, 0x40, 0x25, 0x98, 0xb5, 0xc4, 0xea, 0x0d, 0x87, 0x06,
                0x27, 0xe1, 0x9e, 0x76, 0x70, 0xb1, 0xcd, 0xf1, 0xa2, 0x86, 0x90, 0x61, 0x6b, 0x92, 0xc6, 0xe2, 0xa9,
                0xff, 0x80, 0x54, 0x11, 0xed, 0x89, 0x5a, 0x29, 0x02, 0x8e, 0x74, 0x5b, 0xb3, 0x33, 0x37, 0x10, 0x19,
                0x7f, 0x06, 0x1c, 0x22, 0x7f, 0x67, 0xca, 0xf6, 0xba, 0x6f, 0x8f, 0xf3, 0xd8, 0xd7, 0x81, 0xa6, 0xf0,
                0x7c, 0x87, 0x79, 0xe5, 0x9c, 0xd5, 0xbc, 0xd3, 0x48, 0x6f, 0x34, 0x1f, 0x8b, 0x32, 0xef, 0xd9, 0xca,
                0xf0, 0x52, 0xb9, 0x98, 0x3f, 0x6f, 0x23, 0x63, 0xb2, 0xcf, 0xf0, 0xde, 0xda, 0x84, 0xac, 0x04, 0x4a,
                0xd5,
            },
            DP = new byte[]
            {
                0xbb, 0x39, 0xf3, 0x16, 0x52, 0xdd, 0x55, 0x06, 0x1e, 0x59, 0x9c, 0x09, 0x62, 0x8c, 0xaa, 0xeb, 0x31,
                0xfc, 0x28, 0x11, 0x91, 0xff, 0xac, 0x5f, 0x15, 0x3e, 0xc2, 0x6d, 0x65, 0x40, 0xe5, 0xa4, 0xbe, 0x57,
                0xcf, 0x75, 0x8f, 0x2f, 0x59, 0xc5, 0xf1, 0xfe, 0x9e, 0x93, 0xfa, 0x7e, 0x12, 0x2a, 0x04, 0x60, 0x83,
                0xf2, 0xc1, 0xa0, 0x31, 0x2e, 0x70, 0x9d, 0x6c, 0xfc, 0x34, 0x59, 0x83, 0xac, 0x5f, 0xeb, 0x31, 0x4c,
                0xf9, 0xa0, 0xfa, 0x74, 0x6a, 0x15, 0xa1, 0x5c, 0xbd, 0x21, 0x37, 0x93, 0x64, 0x2b, 0x20, 0x61, 0x90,
                0xf1, 0xc3, 0x12, 0xe6, 0xa1, 0x00, 0xb2, 0x93, 0x7d, 0x4f, 0xaa, 0xd0, 0xe1, 0x9a, 0xca, 0xde, 0x61,
                0x16, 0xf8, 0xde, 0x53, 0xe6, 0xe1, 0x9c, 0xff, 0x4a, 0x8c, 0xa3, 0xb1, 0x78, 0x16, 0x21, 0x1b, 0x54,
                0xeb, 0x29, 0x5d, 0x34, 0x1d, 0x41, 0xac, 0x74, 0x83,
            },
            DQ = new byte[]
            {
                0xa8, 0x89, 0x3a, 0x1d, 0x15, 0xab, 0x87, 0xf1, 0xb9, 0xaa, 0xc5, 0x76, 0x62, 0xca, 0x7d, 0x41, 0x2f,
                0x2c, 0xe4, 0x7f, 0x09, 0x44, 0xb3, 0x79, 0x75, 0xf6, 0x3b, 0xa1, 0x1e, 0x5a, 0xa2, 0xb5, 0x7c, 0xd4,
                0x66, 0xd3, 0x39, 0x21, 0x7e, 0x3c, 0xfa, 0xfa, 0x7d, 0x67, 0x6c, 0x35, 0x82, 0xb7, 0x34, 0x81, 0xa1,
                0xc1, 0x67, 0x90, 0x64, 0xdf, 0x9b, 0x83, 0x23, 0xce, 0x8e, 0x18, 0x95, 0xb1, 0x96, 0x28, 0x5a, 0xc1,
                0xbd, 0xdf, 0x9e, 0xa5, 0x9e, 0x2e, 0x4e, 0x8a, 0xce, 0x22, 0xff, 0xe0, 0xeb, 0x76, 0xb6, 0x57, 0xb0,
                0xba, 0xbb, 0x49, 0x29, 0x49, 0xdb, 0x7c, 0x4e, 0x0f, 0x73, 0x0a, 0x2c, 0xfe, 0x33, 0x5e, 0xb2, 0xd7,
                0x15, 0x6e, 0xbf, 0x51, 0x46, 0xac, 0x8e, 0x9b, 0x47, 0x53, 0x2c, 0x16, 0xa4, 0xdc, 0xfe, 0xaa, 0x4a,
                0xae, 0x3b, 0xb5, 0x80, 0xd8, 0xc8, 0x7c, 0xc8, 0x15,
            },
            InverseQ = new byte[]
            {
                0x1b, 0x05, 0x2a, 0x58, 0xa1, 0xe2, 0x3b, 0x15, 0x4b, 0xa8, 0x52, 0x6b, 0x1e, 0xf4, 0x56, 0x90, 0x03,
                0xef, 0xa7, 0x32, 0x64, 0x7c, 0xf1, 0xb8, 0xbe, 0x99, 0xfd, 0xc0, 0x6d, 0x9b, 0xc8, 0x0d, 0xe1, 0xe6,
                0xd7, 0x61, 0xf5, 0x66, 0x61, 0xd9, 0x6b, 0x54, 0xf4, 0x59, 0x6e, 0x11, 0xf0, 0x8b, 0xf9, 0x6a, 0x02,
                0x20, 0xbe, 0x49, 0xe2, 0x38, 0x71, 0xf7, 0xf2, 0x8d, 0x24, 0x3b, 0x8c, 0x6a, 0x2a, 0x03, 0x3f, 0xbb,
                0x38, 0x67, 0x18, 0xe5, 0x48, 0x5c, 0x48, 0x29, 0x91, 0x12, 0x45, 0xb2, 0xb1, 0xe9, 0xfc, 0x7d, 0xc2,
                0x0e, 0xb9, 0x7a, 0xe3, 0x61, 0xf7, 0x51, 0x42, 0xcd, 0x87, 0xe0, 0x96, 0x0e, 0xcc, 0x24, 0x89, 0xd8,
                0xba, 0xb9, 0x7f, 0x24, 0x10, 0xf9, 0x50, 0x23, 0x79, 0x9c, 0x75, 0x62, 0x79, 0x22, 0xe0, 0x9d, 0x19,
                0x49, 0x2f, 0x61, 0x0c, 0x91, 0x83, 0x3c, 0x55, 0x51,
            },
        };

        public static readonly X500DistinguishedName OldRootCaName =
            new X500DistinguishedName("CN=TEST_CERT_Old_root_CA");

        public static readonly X500DistinguishedName NewRootCaName =
            new X500DistinguishedName("CN=TEST_CERT_New_root_CA");

        public static readonly X500DistinguishedName SecondIntermediateCaName =
            new X500DistinguishedName("CN=TEST_CERT_Second_Intermediate_CA");

        public static readonly X500DistinguishedName EndEntityName =
            new X500DistinguishedName("CN=some.server.invalid");

        private static DateTimeOffset GetUtcNowWithSecondResolution()
        {
            DateTime now = DateTime.UtcNow;
            return new DateTime(
                now.Year,
                now.Month,
                now.Day,
                now.Hour,
                now.Minute,
                now.Second,
                DateTimeKind.Utc
            ).ToUniversalTime();
        }

        private static void PrintCertChain(X509ChainElementCollection chainElements)
        {
            foreach ((X509ChainElement chainElem, int idx) in chainElements.Cast<X509ChainElement>()
                .Select((x, i) => (x, i)))
            {
                string errorString = string.Join(
                    "\n\t",
                    string.Join("\n\t", chainElem.ChainElementStatus.Select(x => x.StatusInformation)),
                    chainElem.Information
                ).Trim();

                Console.WriteLine(
                    "{0}: {1}, {2} - {3}{4}",
                    idx,
                    chainElem.Certificate.Subject,
                    new DateTimeOffset(chainElem.Certificate.NotBefore.ToUniversalTime()),
                    new DateTimeOffset(chainElem.Certificate.NotAfter.ToUniversalTime()),
                    !string.IsNullOrEmpty(errorString)
                        ? string.Format(CultureInfo.InvariantCulture, "\n\t{0}", errorString)
                        : string.Empty
                );
            }
        }

        private static void PrintCerts(IEnumerable<X509Certificate2> certs)
        {
            foreach ((X509Certificate2 cert, int idx) in certs.Select((x, i) => (x, i)))
            {
                Console.WriteLine(
                    "{0}: {1}, {2} - {3}",
                    idx,
                    cert.Subject,
                    new DateTimeOffset(cert.NotBefore.ToUniversalTime()),
                    new DateTimeOffset(cert.NotAfter.ToUniversalTime())
                );
            }
        }

        private static CertificateRequest CreateCertificateRequest(
            RSA key,
            X500DistinguishedName subjectName,
            bool isCa
        )
        {
            var req = new CertificateRequest(
                subjectName,
                key,
                HashAlgorithmName.SHA256,
                RSASignaturePadding.Pkcs1
            );

            req.CertificateExtensions.Add(
                new X509SubjectKeyIdentifierExtension(
                    key: req.PublicKey,
                    critical: false
                )
            );

            X509KeyUsageFlags keyUsages = X509KeyUsageFlags.DigitalSignature;
            if (isCa)
            {
                keyUsages |= X509KeyUsageFlags.KeyCertSign;
            }

            req.CertificateExtensions.Add(
                new X509KeyUsageExtension(
                    keyUsages: keyUsages,
                    critical: true
                )
            );

            req.CertificateExtensions.Add(
                new X509BasicConstraintsExtension(
                    certificateAuthority: isCa,
                    hasPathLengthConstraint: false,
                    pathLengthConstraint: 0,
                    critical: true
                )
            );

            if (!isCa)
            {
                var extendedKeyUsages = new OidCollection
                {
                    new Oid("1.3.6.1.5.5.7.3.1", "id-kp-serverAuth"),
                    new Oid("1.3.6.1.5.5.7.3.2", "id-kp-clientAuth"),
                };

                req.CertificateExtensions.Add(
                    new X509EnhancedKeyUsageExtension(
                        enhancedKeyUsages: extendedKeyUsages,
                        critical: true
                    )
                );
            }

            return req;
        }

        private static X509Certificate2 CreateCert(
            RSA key,
            X500DistinguishedName subjectName,
            byte[] serialNumber,
            RSA issuerKey,
            X500DistinguishedName issuerName,
            DateTimeOffset notBefore,
            DateTimeOffset notAfter,
            bool isCa
        )
        {
            var signatureGenerator = X509SignatureGenerator.CreateForRSA(issuerKey, RSASignaturePadding.Pkcs1);

            CertificateRequest req = CreateCertificateRequest(key, subjectName, isCa);

            using (X509Certificate2 certWithoutKey = req.Create(
                issuerName: issuerName,
                generator: signatureGenerator,
                notBefore: notBefore,
                notAfter: notAfter,
                serialNumber: serialNumber
            ))
            {
                using (var certWithKey = certWithoutKey.CopyWithPrivateKey(key))
                {
                    // https://github.com/dotnet/runtime/issues/23749#issuecomment-388231655
                    return new X509Certificate2(
                        certWithKey.Export(X509ContentType.Pkcs12),
                        (string)null,
                        X509KeyStorageFlags.Exportable
                    );
                }
            }
        }

        private static void GenerateCerts(
            out X509Certificate2 oldRootCa,
            out X509Certificate2 newRootCa,
            out X509Certificate2 oldFirstIntermediate,
            out X509Certificate2 secondIntermediate,
            out X509Certificate2 endEntity
        )
        {
            DateTimeOffset now = GetUtcNowWithSecondResolution();

            RSA oldRootCaKey = RSA.Create(OldRootCaRsaParams);
            RSA newRootCaKey = RSA.Create(NewRootCaRsaParams);
            RSA secondIntermediateKey = RSA.Create(SecondIntermediateRsaParams);
            RSA endEntityKey = RSA.Create(EndEntityRsaParams);

            /*
             *         Old root CA
             *                 |
             *                 v
             *  Old first intermediate     New root CA
             *                 |             |
             *                 v             v
             *               Second intermediate
             *                        |
             *                        v
             *                   End entity  
             */

            oldRootCa = CreateCert(
                key: oldRootCaKey,
                subjectName: OldRootCaName,
                serialNumber: new byte[] { 0x01 },
                issuerKey: oldRootCaKey,
                issuerName: OldRootCaName,
                notBefore: now.AddYears(-10),
                notAfter: now.AddMinutes(2),
                isCa: true
            );

            newRootCa = CreateCert(
                key: newRootCaKey,
                subjectName: NewRootCaName,
                serialNumber: new byte[] { 0x02 },
                issuerKey: newRootCaKey,
                issuerName: NewRootCaName,
                notBefore: now.AddYears(-20),
                notAfter: now.AddYears(20),
                isCa: true
            );

            oldFirstIntermediate = CreateCert(
                key: newRootCaKey,
                subjectName: NewRootCaName,
                serialNumber: new byte[] { 0x03 },
                issuerKey: oldRootCaKey,
                issuerName: OldRootCaName,
                notBefore: now.AddYears(-10),
                notAfter: now.AddMinutes(2),
                isCa: true
            );

            secondIntermediate = CreateCert(
                key: secondIntermediateKey,
                subjectName: SecondIntermediateCaName,
                serialNumber: new byte[] { 0x04 },
                issuerKey: newRootCaKey,
                issuerName: NewRootCaName,
                notBefore: now.AddYears(-2),
                notAfter: now.AddYears(10),
                isCa: true
            );

            endEntity = CreateCert(
                key: endEntityKey,
                subjectName: EndEntityName,
                serialNumber: new byte[] { 0x05 },
                issuerKey: secondIntermediateKey,
                issuerName: SecondIntermediateCaName,
                notBefore: now.AddYears(-2),
                notAfter: now.AddYears(10),
                isCa: false
            );
        }

        private static void PurgeCertificateFromStore(X509Store store, string subjectName)
        {
            X509Certificate2Collection found = store.Certificates.Find(
                X509FindType.FindBySubjectDistinguishedName,
                subjectName,
                validOnly: false
            );
            if (found.Count > 0)
            {
                store.RemoveRange(found);
            }
        }

        private static void InstallCertificates(
            X509Certificate2 oldRootCa,
            X509Certificate2 newRootCa,
            X509Certificate2 oldFirstIntermediate,
            X509Certificate2 secondIntermediate
        )
        {
            const StoreLocation storeLocation = StoreLocation.LocalMachine;

            using (var intermediateStore = new X509Store(
                StoreName.CertificateAuthority,
                storeLocation
            ))
            using (var rootCaStore = new X509Store(StoreName.Root, storeLocation))
            {
                intermediateStore.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadWrite);
                rootCaStore.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadWrite);

                intermediateStore.Add(oldFirstIntermediate);
                intermediateStore.Add(secondIntermediate);

                rootCaStore.Add(oldRootCa);
                rootCaStore.Add(newRootCa);
            }
        }

        private static void PurgeCertificates(
            X509Certificate2 oldRootCa,
            X509Certificate2 newRootCa,
            X509Certificate2 oldFirstIntermediate,
            X509Certificate2 secondIntermediate
        )
        {
            const StoreLocation storeLocation = StoreLocation.LocalMachine;

            using (var intermediateStore = new X509Store(
                StoreName.CertificateAuthority,
                storeLocation
            ))
            using (var rootCaStore = new X509Store(StoreName.Root, storeLocation))
            {
                intermediateStore.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadWrite);
                rootCaStore.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadWrite);

                PurgeCertificateFromStore(intermediateStore, oldFirstIntermediate.Subject);
                PurgeCertificateFromStore(intermediateStore, secondIntermediate.Subject);
                PurgeCertificateFromStore(rootCaStore, oldRootCa.Subject);
                PurgeCertificateFromStore(rootCaStore, newRootCa.Subject);
            }
        }

        private static async Task TestSslStreamChainBuildingAsync(
            X509Certificate2 oldRootCa,
            X509Certificate2 newRootCa,
            X509Certificate2 oldFirstIntermediate,
            X509Certificate2 secondIntermediate,
            X509Certificate2 endEntity
        )
        {
            try
            {
                PurgeCertificates(oldRootCa, newRootCa, oldFirstIntermediate, secondIntermediate);
                InstallCertificates(oldRootCa, newRootCa, oldFirstIntermediate, secondIntermediate);

                Console.WriteLine("Testing chain locally...");
                Console.WriteLine();

                var localChain = new X509Chain(true)
                {
                    ChainPolicy = new X509ChainPolicy
                    {
                        RevocationMode = X509RevocationMode.NoCheck,
                        VerificationTime = DateTime.Now,
                    },
                };
                localChain.Build(endEntity);
                PrintCertChain(localChain.ChainElements);
                Console.WriteLine();

                Console.WriteLine("Starting test...");

                TcpListener serverListenSocket = new TcpListener(IPAddress.Loopback, 0);
                serverListenSocket.Start();

                for (int loopCnt = 0; loopCnt < 7; loopCnt++)
                {
                    Console.WriteLine();
                    Console.WriteLine("Chain #{0}, time is now: {1}", loopCnt, DateTimeOffset.UtcNow);

                    Task<TcpClient> serverSocketTask = serverListenSocket.AcceptTcpClientAsync();
                    using (var clientSocket = new TcpClient())
                    {
                        await clientSocket.ConnectAsync(
                            IPAddress.Loopback,
                            ((IPEndPoint)serverListenSocket.LocalEndpoint).Port
                        ).ConfigureAwait(false);

                        void OnNotifyServerCert(Certificate certificate)
                        {
                            X509Certificate2[] serverCertChain = certificate.GetCertificateList()
                                .Select(x => new X509Certificate2(x.GetDerEncoded()))
                                .ToArray();

                            PrintCerts(serverCertChain);
                            Console.WriteLine();
                        }

                        using (TcpClient serverSocket = await serverSocketTask.ConfigureAwait(false))
                        {
                            using (var serverSslStream = new SslStream(serverSocket.GetStream()))
                            {
                                Task serverHandshakeTask = serverSslStream.AuthenticateAsServerAsync(
                                    endEntity,
                                    false,
                                    SslProtocols.Tls12,
                                    false
                                );

                                var tlsClientProto = new TlsClientProtocol(
                                    clientSocket.GetStream(),
                                    new SecureRandom()
                                );

                                tlsClientProto.Connect(new TlsClient(OnNotifyServerCert));
                                await serverHandshakeTask.ConfigureAwait(false);
                            }
                        }
                    }

                    Console.WriteLine("-------");

                    Thread.Sleep(TimeSpan.FromSeconds(30));
                }

                Console.WriteLine();
                Console.WriteLine("Building new chain explicitly...");
                Console.WriteLine();

                localChain = new X509Chain(true)
                {
                    ChainPolicy = new X509ChainPolicy
                    {
                        RevocationMode = X509RevocationMode.NoCheck,
                        VerificationTime = DateTime.Now,
                    },
                };
                localChain.Build(endEntity);
                PrintCertChain(localChain.ChainElements);
                Console.WriteLine();
            }
            finally
            {
                PurgeCertificates(oldRootCa, newRootCa, oldFirstIntermediate, secondIntermediate);
            }
        }

        private static async Task Main()
        {
            GenerateCerts(
                out X509Certificate2 oldRootCa,
                out X509Certificate2 newRootCa,
                out X509Certificate2 oldFirstIntermediate,
                out X509Certificate2 secondIntermediate,
                out X509Certificate2 endEntity
            );

            await TestSslStreamChainBuildingAsync(
                oldRootCa,
                newRootCa,
                oldFirstIntermediate,
                secondIntermediate,
                endEntity
            ).ConfigureAwait(false);
        }
    }
}

Note that the test application must be run as Administrator on Windows, because it installs (and afterwards removes) certificates into the trusted root CA store of the machine. Also, it depends on the external Portable.BouncyCastle NuGet package (I've used version 1.8.8), because there are no native .NET abstractions that can be used to log the certificates that are sent in a TLS Certificate message (HttpClient uses SslStream which will build a certificate chain from the certificates in the TLS message).

The test code produces the following output:

Testing chain locally...

0: CN=some.server.invalid, 27.10.2018 08:21:00 +00:00 - 27.10.2030 08:21:00 +00:00
1: CN=TEST_CERT_Second_Intermediate_CA, 27.10.2018 08:21:00 +00:00 - 27.10.2030 08:21:00 +00:00
2: CN=TEST_CERT_New_root_CA, 27.10.2010 08:21:00 +00:00 - 27.10.2020 08:23:00 +00:00
3: CN=TEST_CERT_Old_root_CA, 27.10.2010 08:21:00 +00:00 - 27.10.2020 08:23:00 +00:00

Starting test...

Chain #0, time is now: 27.10.2020 08:21:01 +00:00
0: CN=some.server.invalid, 27.10.2018 08:21:00 +00:00 - 27.10.2030 08:21:00 +00:00
1: CN=TEST_CERT_Second_Intermediate_CA, 27.10.2018 08:21:00 +00:00 - 27.10.2030 08:21:00 +00:00
2: CN=TEST_CERT_New_root_CA, 27.10.2010 08:21:00 +00:00 - 27.10.2020 08:23:00 +00:00

-------

Chain #1, time is now: 27.10.2020 08:21:31 +00:00
0: CN=some.server.invalid, 27.10.2018 08:21:00 +00:00 - 27.10.2030 08:21:00 +00:00
1: CN=TEST_CERT_Second_Intermediate_CA, 27.10.2018 08:21:00 +00:00 - 27.10.2030 08:21:00 +00:00
2: CN=TEST_CERT_New_root_CA, 27.10.2010 08:21:00 +00:00 - 27.10.2020 08:23:00 +00:00

-------

Chain #2, time is now: 27.10.2020 08:22:01 +00:00
0: CN=some.server.invalid, 27.10.2018 08:21:00 +00:00 - 27.10.2030 08:21:00 +00:00
1: CN=TEST_CERT_Second_Intermediate_CA, 27.10.2018 08:21:00 +00:00 - 27.10.2030 08:21:00 +00:00
2: CN=TEST_CERT_New_root_CA, 27.10.2010 08:21:00 +00:00 - 27.10.2020 08:23:00 +00:00

-------

Chain #3, time is now: 27.10.2020 08:22:31 +00:00
0: CN=some.server.invalid, 27.10.2018 08:21:00 +00:00 - 27.10.2030 08:21:00 +00:00
1: CN=TEST_CERT_Second_Intermediate_CA, 27.10.2018 08:21:00 +00:00 - 27.10.2030 08:21:00 +00:00
2: CN=TEST_CERT_New_root_CA, 27.10.2010 08:21:00 +00:00 - 27.10.2020 08:23:00 +00:00

-------

Chain #4, time is now: 27.10.2020 08:23:01 +00:00
0: CN=some.server.invalid, 27.10.2018 08:21:00 +00:00 - 27.10.2030 08:21:00 +00:00
1: CN=TEST_CERT_Second_Intermediate_CA, 27.10.2018 08:21:00 +00:00 - 27.10.2030 08:21:00 +00:00
2: CN=TEST_CERT_New_root_CA, 27.10.2010 08:21:00 +00:00 - 27.10.2020 08:23:00 +00:00

-------

Chain #5, time is now: 27.10.2020 08:23:31 +00:00
0: CN=some.server.invalid, 27.10.2018 08:21:00 +00:00 - 27.10.2030 08:21:00 +00:00
1: CN=TEST_CERT_Second_Intermediate_CA, 27.10.2018 08:21:00 +00:00 - 27.10.2030 08:21:00 +00:00
2: CN=TEST_CERT_New_root_CA, 27.10.2010 08:21:00 +00:00 - 27.10.2020 08:23:00 +00:00

-------

Chain #6, time is now: 27.10.2020 08:24:01 +00:00
0: CN=some.server.invalid, 27.10.2018 08:21:00 +00:00 - 27.10.2030 08:21:00 +00:00
1: CN=TEST_CERT_Second_Intermediate_CA, 27.10.2018 08:21:00 +00:00 - 27.10.2030 08:21:00 +00:00
2: CN=TEST_CERT_New_root_CA, 27.10.2010 08:21:00 +00:00 - 27.10.2020 08:23:00 +00:00

-------

Building new chain explicitly...

0: CN=some.server.invalid, 27.10.2018 08:21:00 +00:00 - 27.10.2030 08:21:00 +00:00
1: CN=TEST_CERT_Second_Intermediate_CA, 27.10.2018 08:21:00 +00:00 - 27.10.2030 08:21:00 +00:00
2: CN=TEST_CERT_New_root_CA, 27.10.2000 08:21:00 +00:00 - 27.10.2040 08:21:00 +00:00

As can be seen, the old root certificate (CN=TEST_CERT_Old_root_CA) and its first intermediate (CN=TEST_CERT_New_root_CA) both expire @ 27.10.2020 08:23:00 +00:00, yet the web client receives certificate chains containing these certificates even after they expire (chain iterations 4 - 6). After the certificates expire, building a new chain using X509Chain.Build() returns the expected chain, though.

Conclusion

By using the Visual Studio debugger with just-my-code debugging turned off, I have been able to set breakpoints in SecureChannel.cs and SslSessionsCache.cs, and verify that new SslStream objects are indeed reusing the same SCHANNEL_CRED handle for the same end-entity certificate due to the static credential cache. From the documentation of AcquireCredentialsHandle, it seems the certificate chain associated with the handle is built when AcquireCredentialsHandle is called, which in this case only happens the first time the SCHANNEL_CRED handle is created. Therefore, I believe this to be the culprit of the problem.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions