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); }
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); } }
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); } }
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); }
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; } }
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); } }
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); }
// .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)); } }
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; }
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); } } }
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); } }
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); }
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(); } }
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) { 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); }
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); } } }
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); }