diff --git a/tests/IceRpc.Conformance.Tests/Transports/MultiplexedTransportSslAuthenticationConformanceTests.cs b/tests/IceRpc.Conformance.Tests/Transports/MultiplexedTransportSslAuthenticationConformanceTests.cs deleted file mode 100644 index f970718177..0000000000 --- a/tests/IceRpc.Conformance.Tests/Transports/MultiplexedTransportSslAuthenticationConformanceTests.cs +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright (c) ZeroC, Inc. - -using IceRpc.Tests.Common; -using IceRpc.Transports; -using Microsoft.Extensions.DependencyInjection; -using NUnit.Framework; -using System.Net.Security; -using System.Security.Authentication; -using System.Security.Cryptography.X509Certificates; - -namespace IceRpc.Conformance.Tests; - -/// Conformance tests to ensure the correct use of the SSL authentication options by the multiplexed transport -/// implementation. It also checks some basic expected behavior from the SSL implementation. -public abstract class MultiplexedTransportSslAuthenticationConformanceTests -{ - [Test] - public async Task Ssl_client_connection_connect_fails_when_server_provides_untrusted_certificate() - { - // Arrange - await using ServiceProvider provider = CreateServiceCollection() - .AddSingleton( - new SslServerAuthenticationOptions - { - ServerCertificate = new X509Certificate2("server-untrusted.p12"), - }) - .AddSingleton( - new SslClientAuthenticationOptions - { - RemoteCertificateValidationCallback = (sender, certificate, chain, errors) => false - }) - .BuildServiceProvider(validateScopes: true); - - var sut = provider.GetRequiredService(); - - var listener = provider.GetRequiredService>(); - - // Start the TLS handshake. - Task clientConnectTask = sut.Client.ConnectAsync(default); - Task? serverConnectTask = null; - IMultiplexedConnection? serverConnection = null; - try - { - // We accept two behaviors here: - // - the listener can internally kill the connection if it's not valid (e.g.: Quic behavior) - // - the listener can return the connection but ConnectAsync fails(e.g.: Slic behavior) - (serverConnection, _) = await listener.AcceptAsync(default); - serverConnectTask = serverConnection.ConnectAsync(default); - } - catch (AuthenticationException) - { - // Expected with Quic - serverConnectTask = null; - } - - // Act/Assert - Assert.That(async () => await clientConnectTask, Throws.TypeOf()); - - // We accept two behaviors here: - // - serverConnectTask is null, the listener internally rejects the connection (e.g.: Quic behavior) - // - the server connect operation fails with an IceRpcException (e.g: Slic behavior). - if (serverConnection is not null) - { - // The client will typically close the transport connection after receiving AuthenticationException - await sut.Client.DisposeAsync(); - var exception = Assert.ThrowsAsync(async () => await serverConnectTask!); - Assert.That( - exception?.IceRpcError, - Is.EqualTo(IceRpcError.ConnectionAborted).Or.EqualTo(IceRpcError.IceRpcError), - $"The test failed with an unexpected IceRpcError {exception}"); - await serverConnection.DisposeAsync(); - } - } - - [System.Diagnostics.CodeAnalysis.SuppressMessage( - "Security", - "CA5359:Do Not Disable Certificate Validation", - Justification = "The client doesn't need to validate the server certificate for this test")] - [Test] - public async Task Ssl_server_connection_connect_fails_when_client_provides_untrusted_certificate() - { - // Arrange - await using ServiceProvider provider = CreateServiceCollection() - .AddSingleton( - new SslServerAuthenticationOptions - { - ClientCertificateRequired = true, - RemoteCertificateValidationCallback = (sender, certificate, chain, errors) => false, - ServerCertificate = new X509Certificate2("server.p12"), - }) - .AddSingleton( - new SslClientAuthenticationOptions - { - ClientCertificates = new X509CertificateCollection() - { - new X509Certificate2("client-untrusted.p12") - }, - RemoteCertificateValidationCallback = (sender, certificate, chain, errors) => true - }) - .BuildServiceProvider(validateScopes: true); - - var sut = provider.GetRequiredService(); - var listener = provider.GetRequiredService>(); - - // Start the TLS handshake by calling connect on the client and server connections and wait for the - // connection establishment. - var clientConnectTask = sut.Client.ConnectAsync(default); - - // Act/Assert - IMultiplexedConnection? serverConnection = null; - Assert.That( - async () => - { - // We accept two behaviors here: - // - the listener can internally kill the client connection if it's not valid (e.g.: Quic behavior) - // - the listener can return the connection but ConnectAsync fails (e.g.: Slic behavior) - using var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(250)); - (serverConnection, _) = await listener.AcceptAsync(cts.Token); - await serverConnection.ConnectAsync(default); - }, - Throws.TypeOf().Or.InstanceOf()); - - Assert.That( - async () => - { - // We accept two behaviors here: - // - the client connection fails with AuthenticationException when try to create a stream - // (e.g.: Quic behavior) - // - the client connect operation fails with either TransportException (e.g: Slic behavior). - if (serverConnection is not null) - { - await serverConnection.DisposeAsync(); - } - await clientConnectTask; - var stream = await sut.Client.CreateStreamAsync(bidirectional: false, CancellationToken.None); - await stream.Output.WriteAsync(new ReadOnlyMemory(new byte[] { 0xFF }), CancellationToken.None); - }, - Throws.TypeOf().Or.TypeOf()); - } - - /// Creates the service collection used for the duplex transport conformance tests. - protected abstract IServiceCollection CreateServiceCollection(); -} diff --git a/tests/IceRpc.Quic.Tests/Transports/QuicTransportSslAuthenticationConformanceTests.cs b/tests/IceRpc.Quic.Tests/Transports/QuicTransportSslAuthenticationConformanceTests.cs deleted file mode 100644 index 8e32ece86a..0000000000 --- a/tests/IceRpc.Quic.Tests/Transports/QuicTransportSslAuthenticationConformanceTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) ZeroC, Inc. - -using IceRpc.Conformance.Tests; -using IceRpc.Tests.Common; -using Microsoft.Extensions.DependencyInjection; -using NUnit.Framework; -using System.Net.Quic; - -namespace IceRpc.Tests.Transports; - -public class QuicTransportSslAuthenticationConformanceTests : MultiplexedTransportSslAuthenticationConformanceTests -{ - [OneTimeSetUp] - public void FixtureSetUp() - { - if (!QuicConnection.IsSupported) - { - Assert.Ignore("Quic is not supported on this platform"); - } - } - - protected override IServiceCollection CreateServiceCollection() => - new ServiceCollection() - .AddMultiplexedTransportTest(new Uri("icerpc://127.0.0.1:0/")) - .AddQuicTransport(); -} diff --git a/tests/IceRpc.Quic.Tests/Transports/QuicTransportSslAuthenticationTests.cs b/tests/IceRpc.Quic.Tests/Transports/QuicTransportSslAuthenticationTests.cs new file mode 100644 index 0000000000..0d06f60b05 --- /dev/null +++ b/tests/IceRpc.Quic.Tests/Transports/QuicTransportSslAuthenticationTests.cs @@ -0,0 +1,108 @@ +// Copyright (c) ZeroC, Inc. + +using IceRpc.Tests.Common; +using IceRpc.Transports; +using Microsoft.Extensions.DependencyInjection; +using NUnit.Framework; +using System.Net.Quic; +using System.Net.Security; +using System.Security.Authentication; +using System.Security.Cryptography.X509Certificates; + +namespace IceRpc.Tests.Transports; + +/// Test Ssl authentication with Quic transport. +[Parallelizable(ParallelScope.All)] +public class QuicTransportSslAuthenticationTests +{ + [OneTimeSetUp] + public void FixtureSetUp() + { + if (!QuicConnection.IsSupported) + { + Assert.Ignore("Quic is not supported on this platform"); + } + } + + [Test] + public async Task Quic_client_connection_connect_fails_when_server_provides_untrusted_certificate() + { + // Arrange + await using ServiceProvider provider = CreateServiceCollection() + .AddSingleton( + new SslServerAuthenticationOptions + { + ServerCertificate = new X509Certificate2("server-untrusted.p12"), + }) + .AddSingleton( + new SslClientAuthenticationOptions + { + RemoteCertificateValidationCallback = (sender, certificate, chain, errors) => false + }) + .BuildServiceProvider(validateScopes: true); + + var sut = provider.GetRequiredService(); + + var listener = provider.GetRequiredService>(); + + // Act/Assert + + // The connect attempt starts the TLS handshake. + Assert.That( + async () => await sut.Client.ConnectAsync(default), + Throws.TypeOf()); + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Security", + "CA5359:Do Not Disable Certificate Validation", + Justification = "The client doesn't need to validate the server certificate for this test")] + [Test] + public async Task Quic_server_connection_connect_fails_when_client_provides_untrusted_certificate() + { + // Arrange + await using ServiceProvider provider = CreateServiceCollection() + .AddSingleton( + new SslServerAuthenticationOptions + { + ClientCertificateRequired = true, + RemoteCertificateValidationCallback = (sender, certificate, chain, errors) => false, + ServerCertificate = new X509Certificate2("server.p12"), + }) + .AddSingleton( + new SslClientAuthenticationOptions + { + ClientCertificates = new X509CertificateCollection() + { + new X509Certificate2("client-untrusted.p12") + }, + RemoteCertificateValidationCallback = (sender, certificate, chain, errors) => true + }) + .BuildServiceProvider(validateScopes: true); + + var sut = provider.GetRequiredService(); + var listener = provider.GetRequiredService>(); + + // The connect attempt starts the TLS handshake. + var clientConnectTask = sut.Client.ConnectAsync(default); + + // Act/Assert + Assert.That( + async () => await listener.AcceptAsync(default), + Throws.InstanceOf()); + + try + { + await clientConnectTask; + } + catch + { + // Avoid UTE + } + } + + private static IServiceCollection CreateServiceCollection() => + new ServiceCollection() + .AddMultiplexedTransportTest(new Uri("icerpc://127.0.0.1:0/")) + .AddQuicTransport(); +} diff --git a/tests/IceRpc.Tests/Transports/Slic/SlicTransportSslAuthenticationConformanceTests.cs b/tests/IceRpc.Tests/Transports/Slic/SlicTransportSslAuthenticationConformanceTests.cs deleted file mode 100644 index bc8d7c2367..0000000000 --- a/tests/IceRpc.Tests/Transports/Slic/SlicTransportSslAuthenticationConformanceTests.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) ZeroC, Inc. - -using IceRpc.Conformance.Tests; -using IceRpc.Tests.Common; -using IceRpc.Tests.Transports.Tcp; -using Microsoft.Extensions.DependencyInjection; -using NUnit.Framework; - -namespace IceRpc.Tests.Transports.Slic; - -/// Conformance tests for the Ssl duplex transport. -[Parallelizable(ParallelScope.All)] -public class SlicTransportSslAuthenticationConformanceTests : MultiplexedTransportSslAuthenticationConformanceTests -{ - protected override IServiceCollection CreateServiceCollection() => - new ServiceCollection() - .AddMultiplexedTransportTest(new Uri("icerpc://127.0.0.1:0/")) - .AddSlicTransport() - .AddTcpTransport(); -} diff --git a/tests/IceRpc.Tests/Transports/Slic/SlicTransportSslAuthenticationTests.cs b/tests/IceRpc.Tests/Transports/Slic/SlicTransportSslAuthenticationTests.cs new file mode 100644 index 0000000000..93b0e16dc5 --- /dev/null +++ b/tests/IceRpc.Tests/Transports/Slic/SlicTransportSslAuthenticationTests.cs @@ -0,0 +1,117 @@ +// Copyright (c) ZeroC, Inc. + +using IceRpc.Tests.Common; +using IceRpc.Tests.Transports.Tcp; +using IceRpc.Transports; +using Microsoft.Extensions.DependencyInjection; +using NUnit.Framework; +using System.Net.Security; +using System.Security.Authentication; +using System.Security.Cryptography.X509Certificates; + +namespace IceRpc.Tests.Transports.Slic; + +/// Test Ssl authentication with Slic transport. +[Parallelizable(ParallelScope.All)] +public class SlicTransportSslAuthenticationTests +{ + [Test] + public async Task Slic_over_ssl_client_connection_connect_fails_when_server_provides_untrusted_certificate() + { + // Arrange + await using ServiceProvider provider = CreateServiceCollection() + .AddSingleton( + new SslServerAuthenticationOptions + { + ServerCertificate = new X509Certificate2("server-untrusted.p12"), + }) + .AddSingleton( + new SslClientAuthenticationOptions + { + RemoteCertificateValidationCallback = (sender, certificate, chain, errors) => false + }) + .BuildServiceProvider(validateScopes: true); + + var sut = provider.GetRequiredService(); + + var listener = provider.GetRequiredService>(); + + // The connect attempt starts the TLS handshake. + Task clientConnectTask = sut.Client.ConnectAsync(default); + await using IMultiplexedConnection serverConnection = + (await listener.AcceptAsync(default)).Connection; + var serverConnectTask = serverConnection.ConnectAsync(default); + + // Act/Assert + Assert.That(async () => await clientConnectTask, Throws.TypeOf()); + + // Dispose the client connection here to ensure the server connect task fails promptly. + await sut.Client.DisposeAsync(); + try + { + await serverConnectTask; + } + catch + { + // Avoid UTE + } + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Security", + "CA5359:Do Not Disable Certificate Validation", + Justification = "The client doesn't need to validate the server certificate for this test")] + [Test] + public async Task Slic_over_ssl_server_connection_connect_fails_when_client_provides_untrusted_certificate() + { + // Arrange + await using ServiceProvider provider = CreateServiceCollection() + .AddSingleton( + new SslServerAuthenticationOptions + { + ClientCertificateRequired = true, + RemoteCertificateValidationCallback = (sender, certificate, chain, errors) => false, + ServerCertificate = new X509Certificate2("server.p12"), + }) + .AddSingleton( + new SslClientAuthenticationOptions + { + ClientCertificates = new X509CertificateCollection() + { + new X509Certificate2("client-untrusted.p12") + }, + RemoteCertificateValidationCallback = (sender, certificate, chain, errors) => true + }) + .BuildServiceProvider(validateScopes: true); + + var sut = provider.GetRequiredService(); + var listener = provider.GetRequiredService>(); + + // The connect attempt starts the TLS handshake. + var clientConnectTask = sut.Client.ConnectAsync(default); + + // Act/Assert + await using IMultiplexedConnection serverConnection = + (await listener.AcceptAsync(default)).Connection; + Assert.That( + async () => await serverConnection.ConnectAsync(default), + Throws.TypeOf()); + + // Dispose the server connection here to ensure the client connect task fails promptly. + await serverConnection.DisposeAsync(); + try + { + await clientConnectTask; + } + catch + { + // Avoid UTE + } + } + + private static IServiceCollection CreateServiceCollection() => + new ServiceCollection() + .AddMultiplexedTransportTest(new Uri("icerpc://127.0.0.1:0/")) + .AddSlicTransport() + .AddTcpTransport(); +}