Example #1
0
        private static SafeSslHandle CreateSslContext(SafeFreeSslCredentials credential)
        {
            if (credential.CertificateContext == null)
            {
                return(Interop.AndroidCrypto.SSLStreamCreate());
            }

            SslStreamCertificateContext context = credential.CertificateContext;
            X509Certificate2            cert    = context.Certificate;

            Debug.Assert(context.Certificate.HasPrivateKey);

            PAL_KeyAlgorithm algorithm;

            byte[] keyBytes;
            using (AsymmetricAlgorithm key = GetPrivateKeyAlgorithm(cert, out algorithm))
            {
                keyBytes = key.ExportPkcs8PrivateKey();
            }
            IntPtr[] ptrs = new IntPtr[context.IntermediateCertificates.Length + 1];
            ptrs[0] = cert.Handle;
            for (int i = 0; i < context.IntermediateCertificates.Length; i++)
            {
                ptrs[i + 1] = context.IntermediateCertificates[i].Handle;
            }

            return(Interop.AndroidCrypto.SSLStreamCreateWithCertificates(keyBytes, algorithm, ptrs));
        }
Example #2
0
        private async Task DefaultContextHandshake(Stream client, Stream server)
        {
            if (_context == null)
            {
                _context = SslStreamCertificateContext.Create(_cert, null);
            }

            SslServerAuthenticationOptions serverOptions = new SslServerAuthenticationOptions
            {
                AllowRenegotiation             = false,
                EnabledSslProtocols            = SslProtocols.None,
                CertificateRevocationCheckMode = X509RevocationMode.NoCheck,
                ServerCertificateContext       = _context,
            };

            using (var sslClient = new SslStream(client, leaveInnerStreamOpen: true, delegate { return(true); }))
                using (var sslServer = new SslStream(server, leaveInnerStreamOpen: true, delegate { return(true); }))
                {
                    await Task.WhenAll(
                        sslClient.AuthenticateAsClientAsync("localhost", null, SslProtocols.None, checkCertificateRevocation: false),
                        sslServer.AuthenticateAsServerAsync(serverOptions, default));

                    // In Tls1.3 part of handshake happens with data exchange.
                    // To be consistent we do this extra step for all protocol versions
                    await sslClient.WriteAsync(_clientBuffer, default);

                    await sslServer.ReadAsync(_serverBuffer, default);

                    await sslServer.WriteAsync(_serverBuffer, default);

                    await sslClient.ReadAsync(_clientBuffer, default);
                }
        }
Example #3
0
        public HttpsConnectionMiddleware(ConnectionDelegate next, HttpsConnectionAdapterOptions options, ILoggerFactory loggerFactory)
        {
            if (options == null)
            {
                throw new ArgumentNullException(nameof(options));
            }

            if (options.ServerCertificate == null && options.ServerCertificateSelector == null)
            {
                throw new ArgumentException(CoreStrings.ServerCertificateRequired, nameof(options));
            }

            _next             = next;
            _handshakeTimeout = options.HandshakeTimeout;
            _logger           = loggerFactory.CreateLogger <HttpsConnectionMiddleware>();

            // Something similar to the following could allow us to remove more duplicate logic, but we need https://github.com/dotnet/runtime/issues/40402 to be fixed first.
            //var sniOptionsSelector = new SniOptionsSelector("", new Dictionary<string, SniConfig> { { "*", new SniConfig() } }, new NoopCertificateConfigLoader(), options, options.HttpProtocols, _logger);
            //_httpsOptionsCallback = SniOptionsSelector.OptionsCallback;
            //_httpsOptionsCallbackState = sniOptionsSelector;
            //_sslStreamFactory = s => new SslStream(s);

            _options = options;
            _options.HttpProtocols = ValidateAndNormalizeHttpProtocols(_options.HttpProtocols, _logger);

            // capture the certificate now so it can't be switched after validation
            _serverCertificate         = options.ServerCertificate;
            _serverCertificateSelector = options.ServerCertificateSelector;

            // If a selector is provided then ignore the cert, it may be a default cert.
            if (_serverCertificateSelector != null)
            {
                // SslStream doesn't allow both.
                _serverCertificate = null;
            }
            else
            {
                Debug.Assert(_serverCertificate != null);

                EnsureCertificateIsAllowedForServerAuth(_serverCertificate);

                var certificate = _serverCertificate;
                if (!certificate.HasPrivateKey)
                {
                    // SslStream historically has logic to deal with certificate missing private keys.
                    // By resolving the SslStreamCertificateContext eagerly, we circumvent this logic so
                    // try to resolve the certificate from the store if there's no private key in the cert.
                    certificate = LocateCertificateWithPrivateKey(certificate);
                }

                // This might be do blocking IO but it'll resolve the certificate chain up front before any connections are
                // made to the server
                _serverCertificateContext = SslStreamCertificateContext.Create(certificate, additionalCertificates: null);
            }

            var remoteCertificateValidationCallback = _options.ClientCertificateMode == ClientCertificateMode.NoCertificate ?
                                                      (RemoteCertificateValidationCallback?)null : RemoteCertificateValidationCallback;

            _sslStreamFactory = s => new SslStream(s, leaveInnerStreamOpen: false, userCertificateValidationCallback: remoteCertificateValidationCallback);
        }
Example #4
0
        public async Task SslStream_UntrustedCaWithCustomCallback_OK()
        {
            var clientOptions = new  SslClientAuthenticationOptions()
            {
                TargetHost = "localhost"
            };

            clientOptions.RemoteCertificateValidationCallback =
                (sender, certificate, chain, sslPolicyErrors) =>
            {
                chain.ChainPolicy.CustomTrustStore.Add(_serverChain[_serverChain.Count - 1]);
                chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;

                bool result = chain.Build((X509Certificate2)certificate);
                Assert.True(result);

                return(result);
            };

            var serverOptions = new SslServerAuthenticationOptions();

            serverOptions.ServerCertificateContext = SslStreamCertificateContext.Create(_serverCert, _serverChain);

            (Stream clientStream, Stream serverStream) = TestHelper.GetConnectedStreams();
            using (clientStream)
                using (serverStream)
                    using (SslStream client = new SslStream(clientStream))
                        using (SslStream server = new SslStream(serverStream))
                        {
                            Task t1 = client.AuthenticateAsClientAsync(clientOptions, CancellationToken.None);
                            Task t2 = server.AuthenticateAsServerAsync(serverOptions, CancellationToken.None);

                            await TestConfiguration.WhenAllOrAnyFailedWithTimeout(t1, t2);
                        }
        }
Example #5
0
        public async Task ConnectWithCertificateChain()
        {
            (X509Certificate2 certificate, X509Certificate2Collection chain) = System.Net.Security.Tests.TestHelper.GenerateCertificates("localhost", longChain: true);
            X509Certificate2 rootCA = chain[chain.Count - 1];

            var quicOptions = new QuicListenerOptions();

            quicOptions.ListenEndPoint = new IPEndPoint(IPAddress.Loopback, 0);
            quicOptions.ServerAuthenticationOptions = GetSslServerAuthenticationOptions();
            quicOptions.ServerAuthenticationOptions.ServerCertificateContext = SslStreamCertificateContext.Create(certificate, chain);
            quicOptions.ServerAuthenticationOptions.ServerCertificate        = null;

            using QuicListener listener = new QuicListener(QuicImplementationProviders.MsQuic, quicOptions);

            QuicClientConnectionOptions options = new QuicClientConnectionOptions()
            {
                RemoteEndPoint = listener.ListenEndPoint,
                ClientAuthenticationOptions = GetSslClientAuthenticationOptions(),
            };

            options.ClientAuthenticationOptions.RemoteCertificateValidationCallback = (sender, cert, chain, errors) =>
            {
                Assert.Equal(certificate.Subject, cert.Subject);
                Assert.Equal(certificate.Issuer, cert.Issuer);
                // We should get full chain without root CA.
                // With trusted root, we should be able to build chain.
                chain.ChainPolicy.CustomTrustStore.Add(rootCA);
                chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
                bool ret = chain.Build(certificate);
                if (!ret)
                {
                    _output.WriteLine("Chain build failed with {0} elements", chain.ChainElements);
                    foreach (X509ChainElement element in chain.ChainElements)
                    {
                        _output.WriteLine("Element subject {0} and issuer {1}", element.Certificate.Subject, element.Certificate.Issuer);
                        _output.WriteLine("Element status len {0}", element.ChainElementStatus.Length);
                        foreach (X509ChainStatus status in element.ChainElementStatus)
                        {
                            _output.WriteLine($"Status:  {status.Status}: {status.StatusInformation}");
                        }
                    }
                }

                return(ret);
            };

            using QuicConnection clientConnection = new QuicConnection(QuicImplementationProviders.MsQuic, options);
            Task <QuicConnection> serverTask = listener.AcceptConnectionAsync().AsTask();
            await TaskTimeoutExtensions.WhenAllOrAnyFailed(clientConnection.ConnectAsync().AsTask(), serverTask, PassingTestTimeoutMilliseconds);

            using QuicConnection serverConnection = serverTask.Result;
            Assert.Equal(certificate, clientConnection.RemoteCertificate);
            Assert.Null(serverConnection.RemoteCertificate);
        }
        private async Task DoHandshakeWithOptions(SslStream clientSslStream, SslStream serverSslStream, SslClientAuthenticationOptions clientOptions, SslServerAuthenticationOptions serverOptions)
        {
            using (X509Certificate2 certificate = Configuration.Certificates.GetServerCertificate())
            {
                clientOptions.RemoteCertificateValidationCallback = AllowAnyServerCertificate;
                clientOptions.TargetHost = certificate.GetNameInfo(X509NameType.SimpleName, false);
                serverOptions.ServerCertificateContext = SslStreamCertificateContext.Create(certificate, null);

                Task t1 = clientSslStream.AuthenticateAsClientAsync(TestAuthenticateAsync, clientOptions);
                Task t2 = serverSslStream.AuthenticateAsServerAsync(TestAuthenticateAsync, serverOptions);

                await TestConfiguration.WhenAllOrAnyFailedWithTimeout(t1, t2);
            }
        }
Example #7
0
        public async Task SslStream_UntrustedCaWithCustomCallback_Throws(bool customCallback)
        {
            string errorMessage;
            var    clientOptions = new  SslClientAuthenticationOptions()
            {
                TargetHost = "localhost"
            };

            if (customCallback)
            {
                clientOptions.RemoteCertificateValidationCallback =
                    (sender, certificate, chain, sslPolicyErrors) =>
                {
                    // Add only root CA to verify that peer did send intermediate CA cert.
                    chain.ChainPolicy.CustomTrustStore.Add(certificates.serverChain[certificates.serverChain.Count - 1]);
                    chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
                    // This should work and we should be able to trust the chain.
                    Assert.True(chain.Build((X509Certificate2)certificate));
                    // Reject it in custom callback to simulate for example pinning.
                    return(false);
                };

                errorMessage = "RemoteCertificateValidationCallback";
            }
            else
            {
                // On Windows we hand whole chain to OS so they can always see the root CA.
                errorMessage = PlatformDetection.IsWindows ? "UntrustedRoot" : "PartialChain";
            }

            var serverOptions = new SslServerAuthenticationOptions();

            serverOptions.ServerCertificateContext = SslStreamCertificateContext.Create(certificates.serverCert, certificates.serverChain);

            (Stream clientStream, Stream serverStream) = TestHelper.GetConnectedStreams();
            using (clientStream)
                using (serverStream)
                    using (SslStream client = new SslStream(clientStream))
                        using (SslStream server = new SslStream(serverStream))
                        {
                            Task t1 = client.AuthenticateAsClientAsync(clientOptions, CancellationToken.None);
                            Task t2 = server.AuthenticateAsServerAsync(serverOptions, CancellationToken.None);

                            var e = await Assert.ThrowsAsync <AuthenticationException>(() => t1);

                            Assert.Contains(errorMessage, e.Message);
                            // Server side should finish since we run custom callback after handshake is done.
                            await t2;
                        }
        }
Example #8
0
        public async Task ConnectWithCertificateChain()
        {
            (X509Certificate2 certificate, X509Certificate2Collection chain) = System.Net.Security.Tests.TestHelper.GenerateCertificates("localhost", longChain: true);
            X509Certificate2 rootCA = chain[chain.Count - 1];

            var listenerOptions = new QuicListenerOptions();

            listenerOptions.ListenEndPoint = new IPEndPoint(IPAddress.Loopback, 0);
            listenerOptions.ServerAuthenticationOptions = GetSslServerAuthenticationOptions();
            listenerOptions.ServerAuthenticationOptions.ServerCertificateContext = SslStreamCertificateContext.Create(certificate, chain);
            listenerOptions.ServerAuthenticationOptions.ServerCertificate        = null;

            QuicClientConnectionOptions clientOptions = CreateQuicClientOptions();

            clientOptions.ClientAuthenticationOptions.RemoteCertificateValidationCallback = (sender, cert, chain, errors) =>
            {
                Assert.Equal(certificate.Subject, cert.Subject);
                Assert.Equal(certificate.Issuer, cert.Issuer);
                // We should get full chain without root CA.
                // With trusted root, we should be able to build chain.
                chain.ChainPolicy.CustomTrustStore.Add(rootCA);
                chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
                bool ret = chain.Build(certificate);
                if (!ret)
                {
                    _output.WriteLine("Chain build failed with {0} elements", chain.ChainElements);
                    foreach (X509ChainElement element in chain.ChainElements)
                    {
                        _output.WriteLine("Element subject {0} and issuer {1}", element.Certificate.Subject, element.Certificate.Issuer);
                        _output.WriteLine("Element status len {0}", element.ChainElementStatus.Length);
                        foreach (X509ChainStatus status in element.ChainElementStatus)
                        {
                            _output.WriteLine($"Status:  {status.Status}: {status.StatusInformation}");
                        }
                    }
                }

                return(ret);
            };

            (QuicConnection clientConnection, QuicConnection serverConnection) = await CreateConnectedQuicConnection(clientOptions, listenerOptions);

            Assert.Equal(certificate, clientConnection.RemoteCertificate);
            Assert.Null(serverConnection.RemoteCertificate);
            serverConnection.Dispose();
            clientConnection.Dispose();
        }
        private async Task <string[]> ConnectAndGatherAcceptableIssuers(SslCertificateTrust trust)
        {
            (SslStream client, SslStream server) = TestHelper.GetConnectedSslStreams();
            using (client)
                using (server)
                    using (X509Certificate2 serverCertificate = Configuration.Certificates.GetServerCertificate())
                        using (X509Certificate2 clientCertificate = Configuration.Certificates.GetClientCertificate())
                        {
                            SslServerAuthenticationOptions serverOptions = new SslServerAuthenticationOptions
                            {
                                ServerCertificate                   = serverCertificate,
                                ClientCertificateRequired           = true,
                                RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true,
                                ServerCertificateContext            = SslStreamCertificateContext.Create(serverCertificate, null, false, trust)
                            };

                            string[] acceptableIssuers = Array.Empty <string>();
                            SslClientAuthenticationOptions clientOptions = new SslClientAuthenticationOptions
                            {
                                TargetHost = "localhost",
                                // Force Tls 1.2 to avoid issues with certain OpenSSL versions and Tls 1.3
                                // https://github.com/openssl/openssl/issues/7384
                                EnabledSslProtocols = SslProtocols.Tls12,
                                RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true,
                                LocalCertificateSelectionCallback   = (sender, targetHost, localCertificates, remoteCertificate, issuers) =>
                                {
                                    if (remoteCertificate == null)
                                    {
                                        // ignore the first call that is called before handshake
                                        return(null);
                                    }

                                    acceptableIssuers = issuers;
                                    return(clientCertificate);
                                },
                            };

                            await TestConfiguration.WhenAllOrAnyFailedWithTimeout(
                                client.AuthenticateAsClientAsync(clientOptions),
                                server.AuthenticateAsServerAsync(serverOptions));

                            return(acceptableIssuers);
                        }
        }
        // from: https://raw.githubusercontent.com/dotnet/aspnetcore/master/src/Servers/Kestrel/Core/src/Internal/SniOptionsSelector.cs
        // Copyright (c) .NET Foundation. All rights reserved.
        // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
        private static SslServerAuthenticationOptions CreateServerOptions(X509Certificate2 server)
        {
            if (!IsCertificateAllowedForServerAuth(server))
            {
                throw new InvalidOperationException($"Certificate {server.Subject} is not valid for server authentication");
            }

            SslProtocols sslProtocols;

            if (OperatingSystem.IsWindows())
            {
                sslProtocols = SslProtocols.Tls12;
            }
            else
            {
                sslProtocols = SslProtocols.Tls12 | SslProtocols.Tls13;
            }

            var options = new SslServerAuthenticationOptions()
            {
                ServerCertificate              = server,
                EnabledSslProtocols            = sslProtocols,
                CertificateRevocationCheckMode = X509RevocationMode.NoCheck,
                ServerCertificateContext       = SslStreamCertificateContext.Create(server, additionalCertificates: null),
                ApplicationProtocols           = new List <SslApplicationProtocol>()
                {
                    SslApplicationProtocol.Http2,
                    SslApplicationProtocol.Http11
                },
                AllowRenegotiation = false,
                EncryptionPolicy   = EncryptionPolicy.RequireEncryption,
                //CipherSuitesPolicy = new CipherSuitesPolicy(new[] {
                //	TlsCipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
                //	TlsCipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
                //	TlsCipherSuite.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
                //	TlsCipherSuite.TLS_AES_128_GCM_SHA256,
                //	TlsCipherSuite.TLS_AES_256_GCM_SHA384,
                //	TlsCipherSuite.TLS_CHACHA20_POLY1305_SHA256,
                //})
            };

            return(options);
        }
Example #11
0
        // .NET 標準の SslStreamCertificateContext.Create() では、不必要な証明書は削除されてしまう。
        // この関数は、不必要な証明書もチェーンに入れた状態で SslStreamCertificateContext を作成するのである。
        // ただし、Windows では正しく動作しない。
        public static SslStreamCertificateContext CreateSslCreateCertificateContextWithFullChain(X509Certificate2 target, X509Certificate2Collection?additionalCertificates = null, bool offline = false, bool errorWhenFailed = false)
        {
            // まずオブジェクトを普通通りに作成する
            SslStreamCertificateContext original = SslStreamCertificateContext.Create(target, additionalCertificates, offline);

            if (additionalCertificates == null || additionalCertificates.Count == 0)
            {
                // 証明書チェーンが 0 個の場合はこれでよい
                return(original);
            }

            List <X509Certificate2> chainList = new List <X509Certificate2>();

            foreach (var cert in additionalCertificates)
            {
                if (cert != null)
                {
                    chainList.Add(cert);
                }
            }

            X509Certificate2[] chainArray = chainList.ToArray();

            try
            {
                // このオブジェクトの internal の IntermediateCertificates フィールドを強制的に書き換える
                original._PrivateSet("IntermediateCertificates", chainArray);

                return(original);
            }
            catch
            {
                if (errorWhenFailed)
                {
                    throw;
                }

                // エラーが発生した場合 (.NET のライブラリのバージョンアップでフィールド名が変わった場合等) はオリジナルのものを作成して返す (failsafe)

                return(SslStreamCertificateContext.Create(target, additionalCertificates, offline));
            }
        }
Example #12
0
        public async Task ConnectWithCertificateChain()
        {
            (X509Certificate2 certificate, X509Certificate2Collection chain) = System.Net.Security.Tests.TestHelper.GenerateCertificates("localhost", longChain: true);
            X509Certificate2 rootCA = chain[chain.Count - 1];

            var quicOptions = new QuicListenerOptions();

            quicOptions.ListenEndPoint = new IPEndPoint(IPAddress.Loopback, 0);
            quicOptions.ServerAuthenticationOptions = GetSslServerAuthenticationOptions();
            quicOptions.ServerAuthenticationOptions.ServerCertificateContext = SslStreamCertificateContext.Create(certificate, chain);
            quicOptions.ServerAuthenticationOptions.ServerCertificate        = null;

            using QuicListener listener = new QuicListener(QuicImplementationProviders.MsQuic, quicOptions);

            QuicClientConnectionOptions options = new QuicClientConnectionOptions()
            {
                RemoteEndPoint = listener.ListenEndPoint,
                ClientAuthenticationOptions = GetSslClientAuthenticationOptions(),
            };

            options.ClientAuthenticationOptions.RemoteCertificateValidationCallback = (sender, cert, chain, errors) =>
            {
                Assert.Equal(certificate.Subject, cert.Subject);
                Assert.Equal(certificate.Issuer, cert.Issuer);
                // We should get full chain without root CA.
                // With trusted root, we should be able to build chain.
                chain.ChainPolicy.CustomTrustStore.Add(rootCA);
                chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
                Assert.True(chain.Build(certificate));

                return(true);
            };

            using QuicConnection clientConnection = new QuicConnection(QuicImplementationProviders.MsQuic, options);
            ValueTask clientTask = clientConnection.ConnectAsync();

            using QuicConnection serverConnection = await listener.AcceptConnectionAsync();

            await clientTask;
        }
Example #13
0
        public async Task SslStream_RandomSizeWrites_OK(int bufferSize, int readBufferSize, int writeBufferSize, bool useAsync)
        {
            byte[] dataToCopy   = RandomNumberGenerator.GetBytes(bufferSize);
            byte[] dataReceived = new byte[dataToCopy.Length + readBufferSize]; // make the buffer bigger to have chance to read more

            var clientOptions = new SslClientAuthenticationOptions()
            {
                TargetHost = "localhost"
            };

            clientOptions.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true;

            var serverOptions = new SslServerAuthenticationOptions();

            serverOptions.ServerCertificateContext = SslStreamCertificateContext.Create(Configuration.Certificates.GetServerCertificate(), null);

            (Stream clientStream, Stream serverStream) = TestHelper.GetConnectedTcpStreams();
            using (clientStream)
                using (serverStream)
                    using (SslStream client = new SslStream(new RandomReadWriteSizeStream(clientStream, readBufferSize)))
                        using (SslStream server = new SslStream(new RandomReadWriteSizeStream(serverStream)))
                        {
                            Task t1 = client.AuthenticateAsClientAsync(clientOptions, CancellationToken.None);
                            Task t2 = server.AuthenticateAsServerAsync(serverOptions, CancellationToken.None);

                            await TestConfiguration.WhenAllOrAnyFailedWithTimeout(t1, t2);

                            Task writer = Task.Run(() =>
                            {
                                Memory <byte> data = new Memory <byte>(dataToCopy);
                                while (data.Length > 0)
                                {
                                    int writeLength = Math.Min(data.Length, writeBufferSize);
                                    if (useAsync)
                                    {
                                        server.WriteAsync(data.Slice(0, writeLength)).GetAwaiter().GetResult();
                                    }
                                    else
                                    {
                                        server.Write(data.Span.Slice(0, writeLength));
                                    }

                                    data = data.Slice(Math.Min(writeBufferSize, data.Length));
                                }

                                server.ShutdownAsync().GetAwaiter().GetResult();
                            });

                            Task reader = Task.Run(() =>
                            {
                                Memory <byte> readBuffer = new Memory <byte>(dataReceived);
                                int totalLength          = 0;
                                int readLength;

                                while (true)
                                {
                                    if (useAsync)
                                    {
                                        readLength = client.ReadAsync(readBuffer.Slice(totalLength, readBufferSize)).GetAwaiter().GetResult();
                                    }
                                    else
                                    {
                                        readLength = client.Read(readBuffer.Span.Slice(totalLength, readBufferSize));
                                    }

                                    if (readLength == 0)
                                    {
                                        break;
                                    }

                                    totalLength += readLength;
                                    Assert.True(totalLength <= bufferSize);
                                }

                                Assert.Equal(bufferSize, totalLength);
                                AssertExtensions.SequenceEqual(dataToCopy.AsSpan(), dataReceived.AsSpan().Slice(0, totalLength));
                            });

                            await TestConfiguration.WhenAllOrAnyFailedWithTimeout(writer, reader);
                        }
        }
Example #14
0
        public async Task ClientOptions_ServerOptions_NotMutatedDuringAuthentication()
        {
            using (X509Certificate2 clientCert = Configuration.Certificates.GetClientCertificate())
                using (X509Certificate2 serverCert = Configuration.Certificates.GetServerCertificate())
                {
                    // Values used to populate client options
                    bool clientAllowRenegotiation = false;
                    List <SslApplicationProtocol> clientAppProtocols = new List <SslApplicationProtocol> {
                        SslApplicationProtocol.Http11
                    };
                    X509RevocationMode        clientRevocation   = X509RevocationMode.NoCheck;
                    X509CertificateCollection clientCertificates = new X509CertificateCollection()
                    {
                        clientCert
                    };
                    SslProtocols     clientSslProtocols = SslProtocols.Tls12;
                    EncryptionPolicy clientEncryption   = EncryptionPolicy.RequireEncryption;
                    LocalCertificateSelectionCallback   clientLocalCallback  = new LocalCertificateSelectionCallback(delegate { return(null); });
                    RemoteCertificateValidationCallback clientRemoteCallback = new RemoteCertificateValidationCallback(delegate { return(true); });
                    string clientHost = serverCert.GetNameInfo(X509NameType.SimpleName, false);

                    // Values used to populate server options
                    bool serverAllowRenegotiation = true;
                    List <SslApplicationProtocol> serverAppProtocols = new List <SslApplicationProtocol> {
                        SslApplicationProtocol.Http11, SslApplicationProtocol.Http2
                    };
                    X509RevocationMode serverRevocation = X509RevocationMode.NoCheck;
                    bool serverCertRequired             = false;
#pragma warning disable SYSLIB0039 // TLS 1.0 and 1.1 are obsolete
                    SslProtocols serverSslProtocols = SslProtocols.Tls11 | SslProtocols.Tls12;
#pragma warning restore SYSLIB0039
#pragma warning disable SYSLIB0040 // NoEncryption and AllowNoEncryption are obsolete
                    EncryptionPolicy serverEncryption = EncryptionPolicy.AllowNoEncryption;
#pragma warning restore SYSLIB0040
                    RemoteCertificateValidationCallback serverRemoteCallback = new RemoteCertificateValidationCallback(delegate { return(true); });
                    SslStreamCertificateContext         certificateContext   = SslStreamCertificateContext.Create(serverCert, null, false);

                    (Stream stream1, Stream stream2) = TestHelper.GetConnectedStreams();
                    using (var client = new SslStream(stream1))
                        using (var server = new SslStream(stream2))
                        {
                            // Create client options
                            var clientOptions = new SslClientAuthenticationOptions
                            {
                                AllowRenegotiation             = clientAllowRenegotiation,
                                ApplicationProtocols           = clientAppProtocols,
                                CertificateRevocationCheckMode = clientRevocation,
                                ClientCertificates             = clientCertificates,
                                EnabledSslProtocols            = clientSslProtocols,
                                EncryptionPolicy = clientEncryption,
                                LocalCertificateSelectionCallback   = clientLocalCallback,
                                RemoteCertificateValidationCallback = clientRemoteCallback,
                                TargetHost = clientHost
                            };

                            // Create server options
                            var serverOptions = new SslServerAuthenticationOptions
                            {
                                AllowRenegotiation             = serverAllowRenegotiation,
                                ApplicationProtocols           = serverAppProtocols,
                                CertificateRevocationCheckMode = serverRevocation,
                                ClientCertificateRequired      = serverCertRequired,
                                EnabledSslProtocols            = serverSslProtocols,
                                EncryptionPolicy = serverEncryption,
                                RemoteCertificateValidationCallback = serverRemoteCallback,
                                ServerCertificate        = serverCert,
                                ServerCertificateContext = certificateContext,
                            };

                            // Authenticate
                            Task clientTask = client.AuthenticateAsClientAsync(TestAuthenticateAsync, clientOptions);
                            Task serverTask = server.AuthenticateAsServerAsync(TestAuthenticateAsync, serverOptions);
                            await new[] { clientTask, serverTask }.WhenAllOrAnyFailed();

                            // Validate that client options are unchanged
                            Assert.Equal(clientAllowRenegotiation, clientOptions.AllowRenegotiation);
                            Assert.Same(clientAppProtocols, clientOptions.ApplicationProtocols);
                            Assert.Equal(1, clientOptions.ApplicationProtocols.Count);
                            Assert.Equal(clientRevocation, clientOptions.CertificateRevocationCheckMode);
                            Assert.Same(clientCertificates, clientOptions.ClientCertificates);
                            Assert.Contains(clientCert, clientOptions.ClientCertificates.Cast <X509Certificate2>());
                            Assert.Equal(clientSslProtocols, clientOptions.EnabledSslProtocols);
                            Assert.Equal(clientEncryption, clientOptions.EncryptionPolicy);
                            Assert.Same(clientLocalCallback, clientOptions.LocalCertificateSelectionCallback);
                            Assert.Same(clientRemoteCallback, clientOptions.RemoteCertificateValidationCallback);
                            Assert.Same(clientHost, clientOptions.TargetHost);

                            // Validate that server options are unchanged
                            Assert.Equal(serverAllowRenegotiation, serverOptions.AllowRenegotiation);
                            Assert.Same(serverAppProtocols, serverOptions.ApplicationProtocols);
                            Assert.Equal(2, serverOptions.ApplicationProtocols.Count);
                            Assert.Equal(clientRevocation, serverOptions.CertificateRevocationCheckMode);
                            Assert.Equal(serverCertRequired, serverOptions.ClientCertificateRequired);
                            Assert.Equal(serverSslProtocols, serverOptions.EnabledSslProtocols);
                            Assert.Equal(serverEncryption, serverOptions.EncryptionPolicy);
                            Assert.Same(serverRemoteCallback, serverOptions.RemoteCertificateValidationCallback);
                            Assert.Same(serverCert, serverOptions.ServerCertificate);
                            Assert.Same(certificateContext, serverOptions.ServerCertificateContext);
                        }
                }
        }
Example #15
0
        private static void SetCertificate(SafeSslHandle sslContext, SslStreamCertificateContext context)
        {
            Debug.Assert(sslContext != null, "sslContext != null");


            IntPtr[] ptrs = new IntPtr[context !.IntermediateCertificates !.Length + 1];
    public void CloneSslOptionsClonesAllProperties()
    {
        var propertyNames = typeof(SslServerAuthenticationOptions).GetProperties().Select(property => property.Name).ToList();

        CipherSuitesPolicy cipherSuitesPolicy = null;

        if (!OperatingSystem.IsWindows())
        {
            try
            {
                // The CipherSuitesPolicy ctor throws a PlatformNotSupportedException on Windows.
                cipherSuitesPolicy = new CipherSuitesPolicy(new[] { TlsCipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 });
            }
            catch (PlatformNotSupportedException)
            {
                // The CipherSuitesPolicy ctor throws a PlatformNotSupportedException on Ubuntu 16.04.
                // I don't know exactly which other distros/versions throw PNEs, but it isn't super relevant to this test,
                // so let's just swallow this exception.
            }
        }

        // Set options properties to non-default values to verify they're copied.
        var options = new SslServerAuthenticationOptions
        {
            // Defaults to true
            AllowRenegotiation = false,
            // Defaults to null
            ApplicationProtocols = new List <SslApplicationProtocol> {
                SslApplicationProtocol.Http2
            },
            // Defaults to X509RevocationMode.NoCheck
            CertificateRevocationCheckMode = X509RevocationMode.Offline,
            // Defaults to null
            CipherSuitesPolicy = cipherSuitesPolicy,
            // Defaults to false
            ClientCertificateRequired = true,
            // Defaults to SslProtocols.None
#pragma warning disable SYSLIB0039 // TLS 1.0 and 1.1 are obsolete
            EnabledSslProtocols = SslProtocols.Tls13 | SslProtocols.Tls11,
#pragma warning restore SYSLIB0039
#pragma warning disable SYSLIB0040 // EncryptionPolicy.NoEncryption is obsolete
            // Defaults to EncryptionPolicy.RequireEncryption
            EncryptionPolicy = EncryptionPolicy.NoEncryption,
#pragma warning restore SYSLIB0040
            // Defaults to null
            RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true,
            // Defaults to null
            ServerCertificate = new X509Certificate2(Array.Empty <byte>()),
            // Defaults to null
            ServerCertificateContext = SslStreamCertificateContext.Create(_x509Certificate2, additionalCertificates: null, offline: true),
            // Defaults to null
            ServerCertificateSelectionCallback = (sender, serverName) => null,
            // Defaults to null
            CertificateChainPolicy = new X509ChainPolicy(),
        };

        var clonedOptions = SniOptionsSelector.CloneSslOptions(options);

        Assert.NotSame(options, clonedOptions);

        Assert.Equal(options.AllowRenegotiation, clonedOptions.AllowRenegotiation);
        Assert.True(propertyNames.Remove(nameof(options.AllowRenegotiation)));

        // Ensure the List<SslApplicationProtocol> is also cloned since it could be modified by a user callback.
        Assert.NotSame(options.ApplicationProtocols, clonedOptions.ApplicationProtocols);
        Assert.Equal(Assert.Single(options.ApplicationProtocols), Assert.Single(clonedOptions.ApplicationProtocols));
        Assert.True(propertyNames.Remove(nameof(options.ApplicationProtocols)));

        Assert.Equal(options.CertificateRevocationCheckMode, clonedOptions.CertificateRevocationCheckMode);
        Assert.True(propertyNames.Remove(nameof(options.CertificateRevocationCheckMode)));

        Assert.Same(options.CipherSuitesPolicy, clonedOptions.CipherSuitesPolicy);
        Assert.True(propertyNames.Remove(nameof(options.CipherSuitesPolicy)));

        Assert.Equal(options.ClientCertificateRequired, clonedOptions.ClientCertificateRequired);
        Assert.True(propertyNames.Remove(nameof(options.ClientCertificateRequired)));

        Assert.Equal(options.EnabledSslProtocols, clonedOptions.EnabledSslProtocols);
        Assert.True(propertyNames.Remove(nameof(options.EnabledSslProtocols)));

        Assert.Equal(options.EncryptionPolicy, clonedOptions.EncryptionPolicy);
        Assert.True(propertyNames.Remove(nameof(options.EncryptionPolicy)));

        Assert.Same(options.RemoteCertificateValidationCallback, clonedOptions.RemoteCertificateValidationCallback);
        Assert.True(propertyNames.Remove(nameof(options.RemoteCertificateValidationCallback)));

        // Technically the ServerCertificate could be reset/reimported, but I'm hoping this is uncommon. Trying to clone the certificate and/or context seems risky.
        Assert.Same(options.ServerCertificate, clonedOptions.ServerCertificate);
        Assert.True(propertyNames.Remove(nameof(options.ServerCertificate)));

        Assert.Same(options.ServerCertificateContext, clonedOptions.ServerCertificateContext);
        Assert.True(propertyNames.Remove(nameof(options.ServerCertificateContext)));

        Assert.Same(options.ServerCertificateSelectionCallback, clonedOptions.ServerCertificateSelectionCallback);
        Assert.True(propertyNames.Remove(nameof(options.ServerCertificateSelectionCallback)));

        Assert.Same(options.CertificateChainPolicy, clonedOptions.CertificateChainPolicy);
        Assert.True(propertyNames.Remove(nameof(options.CertificateChainPolicy)));

        // Ensure we've checked every property. When new properties get added, we'll have to update this test along with the CloneSslOptions implementation.
        Assert.Empty(propertyNames);
    }
Example #17
0
        public async Task SslStream_ClientCertificate_SendsChain()
        {
            List <SslStream> streams = new List <SslStream>();

            TestHelper.CleanupCertificates();
            (X509Certificate2 clientCertificate, X509Certificate2Collection clientChain) = TestHelper.GenerateCertificates("SslStream_ClinetCertificate_SendsChain", serverCertificate: false);
            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.AddRange(clientChain);
                store.Close();
            }

            using (var chain = new X509Chain())
            {
                chain.ChainPolicy.VerificationFlags           = X509VerificationFlags.AllFlags;
                chain.ChainPolicy.RevocationMode              = X509RevocationMode.NoCheck;
                chain.ChainPolicy.DisableCertificateDownloads = false;
                bool chainStatus = chain.Build(clientCertificate);
                // Verify we can construct full chain
                if (chain.ChainElements.Count < clientChain.Count)
                {
                    throw new SkipTestException($"chain cannot be built {chain.ChainElements.Count}");
                }
            }

            var clientOptions = new  SslClientAuthenticationOptions()
            {
                TargetHost = "localhost",
            };

            clientOptions.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true;
            clientOptions.LocalCertificateSelectionCallback   = (sender, target, certificates, remoteCertificate, issuers) => clientCertificate;

            var serverOptions = new SslServerAuthenticationOptions()
            {
                ClientCertificateRequired = true
            };

            serverOptions.ServerCertificateContext            = SslStreamCertificateContext.Create(Configuration.Certificates.GetServerCertificate(), null);
            serverOptions.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
            {
                // Client should send chain without root CA. There is no good way how to know if the chain was built from certificates
                // from wire or from system store. However, SslStream adds certificates from wire to ExtraStore in RemoteCertificateValidationCallback.
                // So we verify the operation by checking the ExtraStore. On Windows, that includes leaf itself.
                _output.WriteLine("RemoteCertificateValidationCallback called with {0} and {1} extra certificates", sslPolicyErrors, chain.ChainPolicy.ExtraStore.Count);
                foreach (X509Certificate c in chain.ChainPolicy.ExtraStore)
                {
                    _output.WriteLine("received {0}", c.Subject);
                }

                Assert.True(chain.ChainPolicy.ExtraStore.Count >= clientChain.Count - 1, "client did not sent expected chain");
                return(true);
            };

            // run the test multiple times while holding established SSL so we could hit credential cache.
            for (int i = 0; i < 3; i++)
            {
                (Stream clientStream, Stream serverStream) = TestHelper.GetConnectedStreams();
                SslStream client = new SslStream(clientStream);
                SslStream server = new SslStream(serverStream);

                Task t1 = client.AuthenticateAsClientAsync(clientOptions, CancellationToken.None);
                Task t2 = server.AuthenticateAsServerAsync(serverOptions, CancellationToken.None);
                await Task.WhenAll(t1, t2).WaitAsync(TestConfiguration.PassingTestTimeout);

                // hold to the streams so they stay in credential cache
                streams.Add(client);
                streams.Add(server);
            }

            TestHelper.CleanupCertificates();
            clientCertificate.Dispose();
            foreach (X509Certificate c in clientChain)
            {
                c.Dispose();
            }

            foreach (SslStream s in  streams)
            {
                s.Dispose();
            }
        }
Example #18
0
        private async Task ConnectWithRevocation_WithCallback_Core(
            X509RevocationMode revocationMode,
            bool?offlineContext = false)
        {
            string offlinePart = offlineContext.HasValue ? offlineContext.GetValueOrDefault().ToString().ToLower() : "null";
            string serverName  = $"{revocationMode.ToString().ToLower()}.{offlinePart}.server.example";

            (Stream clientStream, Stream serverStream) = TestHelper.GetConnectedStreams();

            CertificateAuthority.BuildPrivatePki(
                PkiOptions.EndEntityRevocationViaOcsp | PkiOptions.CrlEverywhere,
                out RevocationResponder responder,
                out CertificateAuthority rootAuthority,
                out CertificateAuthority intermediateAuthority,
                out X509Certificate2 serverCert,
                subjectName: serverName,
                keySize: 2048,
                extensions: TestHelper.BuildTlsServerCertExtensions(serverName));

            SslClientAuthenticationOptions clientOpts = new SslClientAuthenticationOptions
            {
                TargetHost = serverName,
                RemoteCertificateValidationCallback = CertificateValidationCallback,
                CertificateRevocationCheckMode      = revocationMode,
            };

            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
            {
                X509Certificate2 temp = new X509Certificate2(serverCert.Export(X509ContentType.Pkcs12));
                serverCert.Dispose();
                serverCert = temp;
            }

            await using (clientStream)
                await using (serverStream)
                    using (responder)
                        using (rootAuthority)
                            using (intermediateAuthority)
                                using (serverCert)
                                    using (X509Certificate2 issuerCert = intermediateAuthority.CloneIssuerCert())
                                        await using (SslStream tlsClient = new SslStream(clientStream))
                                            await using (SslStream tlsServer = new SslStream(serverStream))
                                            {
                                                intermediateAuthority.Revoke(serverCert, serverCert.NotBefore);

                                                SslServerAuthenticationOptions serverOpts = new SslServerAuthenticationOptions();

                                                if (offlineContext.HasValue)
                                                {
                                                    serverOpts.ServerCertificateContext = SslStreamCertificateContext.Create(
                                                        serverCert,
                                                        new X509Certificate2Collection(issuerCert),
                                                        offlineContext.GetValueOrDefault());

                                                    if (revocationMode == X509RevocationMode.Offline)
                                                    {
                                                        if (offlineContext.GetValueOrDefault(false))
                                                        {
                                                            // Add a delay just to show we're not winning because of race conditions.
                                                            await Task.Delay(200);
                                                        }
                                                        else
                                                        {
                                                            if (!OperatingSystem.IsLinux())
                                                            {
                                                                throw new InvalidOperationException(
                                                                          "This test configuration uses reflection and is only defined for Linux.");
                                                            }

                                                            FieldInfo pendingDownloadTaskField = typeof(SslStreamCertificateContext).GetField(
                                                                "_pendingDownload",
                                                                BindingFlags.Instance | BindingFlags.NonPublic);

                                                            if (pendingDownloadTaskField is null)
                                                            {
                                                                throw new InvalidOperationException("Cannot find the pending download field.");
                                                            }

                                                            Task download = (Task)pendingDownloadTaskField.GetValue(serverOpts.ServerCertificateContext);

                                                            // If it's null, it should mean it has already finished. If not, it might not have.
                                                            if (download is not null)
                                                            {
                                                                await download;
                                                            }
                                                        }
                                                    }
                                                }
                                                else
                                                {
                                                    serverOpts.ServerCertificate = serverCert;
                                                }

                                                Task serverTask = tlsServer.AuthenticateAsServerAsync(serverOpts);
                                                Task clientTask = tlsClient.AuthenticateAsClientAsync(clientOpts);

                                                await TestConfiguration.WhenAllOrAnyFailedWithTimeout(clientTask, serverTask);
                                            }
Example #19
0
        public async Task SslStream_UntrustedCaWithCustomCallback_OK(bool usePartialChain)
        {
            var rnd   = new Random();
            int split = rnd.Next(0, certificates.serverChain.Count - 1);

            var clientOptions = new  SslClientAuthenticationOptions()
            {
                TargetHost = "localhost"
            };

            clientOptions.RemoteCertificateValidationCallback =
                (sender, certificate, chain, sslPolicyErrors) =>
            {
                // add our custom root CA
                chain.ChainPolicy.CustomTrustStore.Add(certificates.serverChain[certificates.serverChain.Count - 1]);
                chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
                // Add only one CA to verify that peer did send intermediate CA cert.
                // In case of partial chain, we need to make missing certs available.
                if (usePartialChain)
                {
                    for (int i = split; i < certificates.serverChain.Count - 1; i++)
                    {
                        chain.ChainPolicy.ExtraStore.Add(certificates.serverChain[i]);
                    }
                }

                bool result = chain.Build((X509Certificate2)certificate);
                Assert.True(result);

                return(result);
            };

            var serverOptions = new SslServerAuthenticationOptions();
            X509Certificate2Collection serverChain;

            if (usePartialChain)
            {
                // give first few certificates without root CA
                serverChain = new X509Certificate2Collection();
                for (int i = 0; i < split; i++)
                {
                    serverChain.Add(certificates.serverChain[i]);
                }
            }
            else
            {
                serverChain = certificates.serverChain;
            }

            serverOptions.ServerCertificateContext = SslStreamCertificateContext.Create(certificates.serverCert, certificates.serverChain);

            (Stream clientStream, Stream serverStream) = TestHelper.GetConnectedStreams();
            using (clientStream)
                using (serverStream)
                    using (SslStream client = new SslStream(clientStream))
                        using (SslStream server = new SslStream(serverStream))
                        {
                            Task t1 = client.AuthenticateAsClientAsync(clientOptions, CancellationToken.None);
                            Task t2 = server.AuthenticateAsServerAsync(serverOptions, CancellationToken.None);

                            await TestConfiguration.WhenAllOrAnyFailedWithTimeout(t1, t2);
                        }
        }
        private async Task ConnectWithRevocation_WithCallback_Core(
            X509RevocationMode revocationMode,
            bool?offlineContext = false)
        {
            string offlinePart = offlineContext.HasValue ? offlineContext.GetValueOrDefault().ToString().ToLower() : "null";
            string serverName  = $"{revocationMode.ToString().ToLower()}.{offlinePart}.server.example";

            (Stream clientStream, Stream serverStream) = TestHelper.GetConnectedStreams();

            CertificateAuthority.BuildPrivatePki(
                PkiOptions.EndEntityRevocationViaOcsp | PkiOptions.CrlEverywhere,
                out RevocationResponder responder,
                out CertificateAuthority rootAuthority,
                out CertificateAuthority intermediateAuthority,
                out X509Certificate2 serverCert,
                subjectName: serverName,
                keySize: 2048,
                extensions: TestHelper.BuildTlsServerCertExtensions(serverName));

            SslClientAuthenticationOptions clientOpts = new SslClientAuthenticationOptions
            {
                TargetHost = serverName,
                RemoteCertificateValidationCallback = CertificateValidationCallback,
                CertificateRevocationCheckMode      = revocationMode,
            };

            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
            {
                X509Certificate2 temp = new X509Certificate2(serverCert.Export(X509ContentType.Pkcs12));
                serverCert.Dispose();
                serverCert = temp;
            }

            await using (clientStream)
                await using (serverStream)
                    using (responder)
                        using (rootAuthority)
                            using (intermediateAuthority)
                                using (serverCert)
                                    using (X509Certificate2 issuerCert = intermediateAuthority.CloneIssuerCert())
                                        await using (SslStream tlsClient = new SslStream(clientStream))
                                            await using (SslStream tlsServer = new SslStream(serverStream))
                                            {
                                                intermediateAuthority.Revoke(serverCert, serverCert.NotBefore);

                                                SslServerAuthenticationOptions serverOpts = new SslServerAuthenticationOptions();

                                                if (offlineContext.HasValue)
                                                {
                                                    serverOpts.ServerCertificateContext = SslStreamCertificateContext.Create(
                                                        serverCert,
                                                        new X509Certificate2Collection(issuerCert),
                                                        offlineContext.GetValueOrDefault());

                                                    if (revocationMode == X509RevocationMode.Offline)
                                                    {
                                                        // Give the OCSP response a better chance to finish.
                                                        await Task.Delay(200);
                                                    }
                                                }
                                                else
                                                {
                                                    serverOpts.ServerCertificate = serverCert;
                                                }

                                                Task serverTask = tlsServer.AuthenticateAsServerAsync(serverOpts);
                                                Task clientTask = tlsClient.AuthenticateAsClientAsync(clientOpts);

                                                await TestConfiguration.WhenAllOrAnyFailedWithTimeout(clientTask, serverTask);
                                            }
Example #21
0
        public SniOptionsSelector(
            string endpointName,
            Dictionary <string, SniConfig> sniDictionary,
            ICertificateConfigLoader certifcateConfigLoader,
            HttpsConnectionAdapterOptions fallbackHttpsOptions,
            HttpProtocols fallbackHttpProtocols,
            ILogger <HttpsConnectionMiddleware> logger)
        {
            _endpointName = endpointName;

            _fallbackServerCertificateSelector = fallbackHttpsOptions.ServerCertificateSelector;
            _onAuthenticateCallback            = fallbackHttpsOptions.OnAuthenticate;

            foreach (var(name, sniConfig) in sniDictionary)
            {
                var sslOptions = new SslServerAuthenticationOptions
                {
                    ServerCertificate              = certifcateConfigLoader.LoadCertificate(sniConfig.Certificate, $"{endpointName}:Sni:{name}"),
                    EnabledSslProtocols            = sniConfig.SslProtocols ?? fallbackHttpsOptions.SslProtocols,
                    CertificateRevocationCheckMode = fallbackHttpsOptions.CheckCertificateRevocation ? X509RevocationMode.Online : X509RevocationMode.NoCheck,
                };

                if (sslOptions.ServerCertificate is null)
                {
                    if (fallbackHttpsOptions.ServerCertificate is null && _fallbackServerCertificateSelector is null)
                    {
                        throw new InvalidOperationException(CoreStrings.NoCertSpecifiedNoDevelopmentCertificateFound);
                    }

                    if (_fallbackServerCertificateSelector is null)
                    {
                        // Cache the fallback ServerCertificate since there's no fallback ServerCertificateSelector taking precedence.
                        sslOptions.ServerCertificate = fallbackHttpsOptions.ServerCertificate;
                    }
                }

                if (sslOptions.ServerCertificate != null)
                {
                    // This might be do blocking IO but it'll resolve the certificate chain up front before any connections are
                    // made to the server
                    sslOptions.ServerCertificateContext = SslStreamCertificateContext.Create((X509Certificate2)sslOptions.ServerCertificate, additionalCertificates: null);
                }

                if (!certifcateConfigLoader.IsTestMock && sslOptions.ServerCertificate is X509Certificate2 cert2)
                {
                    HttpsConnectionMiddleware.EnsureCertificateIsAllowedForServerAuth(cert2);
                }

                var clientCertificateMode = sniConfig.ClientCertificateMode ?? fallbackHttpsOptions.ClientCertificateMode;

                if (clientCertificateMode != ClientCertificateMode.NoCertificate)
                {
                    sslOptions.ClientCertificateRequired = clientCertificateMode == ClientCertificateMode.AllowCertificate ||
                                                           clientCertificateMode == ClientCertificateMode.RequireCertificate;
                    sslOptions.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
                                                                     HttpsConnectionMiddleware.RemoteCertificateValidationCallback(
                        clientCertificateMode, fallbackHttpsOptions.ClientCertificateValidation, certificate, chain, sslPolicyErrors);
                }

                var httpProtocols = sniConfig.Protocols ?? fallbackHttpProtocols;
                httpProtocols = HttpsConnectionMiddleware.ValidateAndNormalizeHttpProtocols(httpProtocols, logger);
                HttpsConnectionMiddleware.ConfigureAlpn(sslOptions, httpProtocols);

                var sniOptions = new SniOptions(sslOptions, httpProtocols, clientCertificateMode);

                if (name.Equals(WildcardHost, StringComparison.Ordinal))
                {
                    _wildcardOptions = sniOptions;
                }
                else if (name.StartsWith(WildcardPrefix, StringComparison.Ordinal))
                {
                    // Only slice off 1 character, the `*`. We want to match the leading `.` also.
                    _wildcardPrefixOptions.Add(name.Substring(1), sniOptions);
                }
                else
                {
                    _exactNameOptions.Add(name, sniOptions);
                }
            }
        }