diff --git a/src/libraries/System.Net.Mail/ref/System.Net.Mail.cs b/src/libraries/System.Net.Mail/ref/System.Net.Mail.cs index 66a663d05940de..f531d503da81b3 100644 --- a/src/libraries/System.Net.Mail/ref/System.Net.Mail.cs +++ b/src/libraries/System.Net.Mail/ref/System.Net.Mail.cs @@ -181,6 +181,7 @@ public SmtpClient(string? host, int port) { } public string? PickupDirectoryLocation { get { throw null; } set { } } public int Port { get { throw null; } set { } } public System.Net.ServicePoint ServicePoint { get { throw null; } } + public System.Net.Security.SslClientAuthenticationOptions SslOptions { get { throw null; } set { } } public string? TargetName { get { throw null; } set { } } public int Timeout { get { throw null; } set { } } public bool UseDefaultCredentials { get { throw null; } set { } } diff --git a/src/libraries/System.Net.Mail/ref/System.Net.Mail.csproj b/src/libraries/System.Net.Mail/ref/System.Net.Mail.csproj index 26e17b8b347788..2d68e1d7bf609c 100644 --- a/src/libraries/System.Net.Mail/ref/System.Net.Mail.csproj +++ b/src/libraries/System.Net.Mail/ref/System.Net.Mail.csproj @@ -12,6 +12,7 @@ + diff --git a/src/libraries/System.Net.Mail/src/System.Net.Mail.csproj b/src/libraries/System.Net.Mail/src/System.Net.Mail.csproj index 46fc41705f60cb..7e835fc2971978 100644 --- a/src/libraries/System.Net.Mail/src/System.Net.Mail.csproj +++ b/src/libraries/System.Net.Mail/src/System.Net.Mail.csproj @@ -106,6 +106,8 @@ + diff --git a/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpClient.Wasm.cs b/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpClient.Wasm.cs index b8b9c08b4cd65f..d6851d215ed39c 100644 --- a/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpClient.Wasm.cs +++ b/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpClient.Wasm.cs @@ -4,6 +4,7 @@ using System; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; +using System.Net.Security; using System.Runtime.Versioning; using System.Security.Cryptography.X509Certificates; using System.Threading; @@ -116,6 +117,13 @@ public bool EnableSsl set => throw new PlatformNotSupportedException(); } + /// Gets or sets the options used to establish a TLS connection. + public SslClientAuthenticationOptions SslOptions + { + get => throw new PlatformNotSupportedException(); + set => throw new PlatformNotSupportedException(); + } + /// /// Certificates used by the client for establishing an SSL connection with the server. /// diff --git a/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpClient.cs b/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpClient.cs index 372fd2b06ce932..4818f4b5e8eb1a 100644 --- a/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpClient.cs +++ b/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpClient.cs @@ -7,6 +7,7 @@ using System.Globalization; using System.IO; using System.Net.NetworkInformation; +using System.Net.Security; using System.Runtime.ExceptionServices; using System.Runtime.Versioning; using System.Security; @@ -341,9 +342,41 @@ public bool EnableSsl } } - /// - /// Certificates used by the client for establishing an SSL connection with the server. - /// + /// Gets or sets the options used to establish a TLS connection. + /// The TLS client authentication options. The default is a new instance. + /// + /// These options are used only when is . + /// When is , + /// the current is used without modifying the options. + /// Changing this object in place, including its nested objects such as + /// , does not invalidate an existing connection. + /// To ensure changes are used for the next send, assign this property again, even to the same instance. + /// Assignment invalidates the cached connection so that the next send establishes a new connection. + /// Do not modify the options or their nested objects while a send is in progress. + /// + /// The value is . + /// A send operation is in progress. + public SslClientAuthenticationOptions SslOptions + { + get => _transport.SslOptions; + set + { + ArgumentNullException.ThrowIfNull(value); + + if (_inCall) + { + throw new InvalidOperationException(SR.SmtpInvalidOperationDuringSend); + } + + _transport.SslOptions = value; + } + } + + /// Gets the certificates used by the client to establish a TLS connection with the server. + /// + /// Returns from , + /// initializing an empty collection if necessary. Modifying the collection does not invalidate an existing connection. + /// public X509CertificateCollection ClientCertificates { get diff --git a/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpConnection.cs b/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpConnection.cs index 5744d9faceea55..f2bee927815dd8 100644 --- a/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpConnection.cs +++ b/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpConnection.cs @@ -9,9 +9,7 @@ using System.Net.Security; using System.Net.Sockets; using System.Runtime.ExceptionServices; -using System.Security.Authentication; using System.Security.Authentication.ExtendedProtection; -using System.Security.Cryptography.X509Certificates; using System.Security.Principal; using System.Threading; using System.Threading.Tasks; @@ -33,8 +31,6 @@ internal sealed partial class SmtpConnection private readonly ICredentialsByHost? _credentials; private string[]? _extensions; - private bool _enableSsl; - private X509CertificateCollection? _clientCertificates; internal SmtpConnection(SmtpTransport parent, SmtpClient client, ICredentialsByHost? credentials, ISmtpAuthenticationModule[] authenticationModules) { @@ -54,29 +50,7 @@ internal SmtpConnection(SmtpTransport parent, SmtpClient client, ICredentialsByH internal SmtpReplyReaderFactory? Reader => _responseReader; - internal bool EnableSsl - { - get - { - return _enableSsl; - } - set - { - _enableSsl = value; - } - } - - internal X509CertificateCollection? ClientCertificates - { - get - { - return _clientCertificates; - } - set - { - _clientCertificates = value; - } - } + internal SslClientAuthenticationOptions? SslOptions { get; set; } internal void InitializeConnection(string host, int port) { @@ -144,7 +118,7 @@ internal async Task GetConnectionAsync(string host, int port, Cancel } // Handle SSL/TLS - if (_enableSsl) + if (SslOptions is SslClientAuthenticationOptions sslOptions) { if (!_serverSupportsStartTls) { @@ -157,30 +131,19 @@ internal async Task GetConnectionAsync(string host, int port, Cancel await StartTlsCommand.SendAsync(this, cancellationToken).ConfigureAwait(false); -#pragma warning disable SYSLIB0014 // ServicePointManager is obsolete - SslStream sslStream = new SslStream(_stream!, false, ServicePointManager.ServerCertificateValidationCallback); + SslStream sslStream = new SslStream(_stream!); + _stream = sslStream; if (isAsync) { // If we are using async, we need to use the async version of AuthenticateAsClientAsync - await sslStream.AuthenticateAsClientAsync( - new SslClientAuthenticationOptions - { - TargetHost = host, - ClientCertificates = _clientCertificates, - EnabledSslProtocols = (SslProtocols)ServicePointManager.SecurityProtocol, // enums use same values - CertificateRevocationCheckMode = ServicePointManager.CheckCertificateRevocationList ? - X509RevocationMode.Online : X509RevocationMode.NoCheck, - }, - cancellationToken).ConfigureAwait(false); + await sslStream.AuthenticateAsClientAsync(sslOptions, cancellationToken).ConfigureAwait(false); } else { // Synchronous version - sslStream.AuthenticateAsClient(host, _clientCertificates, (SslProtocols)ServicePointManager.SecurityProtocol, ServicePointManager.CheckCertificateRevocationList); + sslStream.AuthenticateAsClient(sslOptions); } -#pragma warning restore SYSLIB0014 // ServicePointManager is obsolete - _stream = sslStream; _responseReader = new SmtpReplyReaderFactory(_stream); // According to RFC 3207: The client SHOULD send an EHLO command diff --git a/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpTransport.cs b/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpTransport.cs index 19d01f8af9f838..d01c7a18e2c229 100644 --- a/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpTransport.cs +++ b/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpTransport.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.IO; using System.Net.Mime; +using System.Net.Security; using System.Runtime.ExceptionServices; using System.Security.Cryptography.X509Certificates; using System.Threading; @@ -79,7 +80,17 @@ internal bool EnableSsl } } - internal X509CertificateCollection ClientCertificates => field ??= new X509CertificateCollection(); + internal SslClientAuthenticationOptions SslOptions + { + get => field ??= new SslClientAuthenticationOptions(); + set + { + field = value; + InvalidateCachedConnection(); + } + } + + internal X509CertificateCollection ClientCertificates => SslOptions.ClientCertificates ??= new X509CertificateCollection(); internal bool ServerSupportsEai { @@ -122,8 +133,9 @@ internal Task GetConnectionAsync(string host, int port, Cancellation if (EnableSsl) { - _connection.EnableSsl = true; - _connection.ClientCertificates = ClientCertificates; + SslClientAuthenticationOptions sslOptions = SslOptions.ShallowClone(); + sslOptions.TargetHost ??= host; + _connection.SslOptions = sslOptions; } return _connection.GetConnectionAsync(host, port, cancellationToken); diff --git a/src/libraries/System.Net.Mail/tests/Functional/LoopbackSmtpServer.cs b/src/libraries/System.Net.Mail/tests/Functional/LoopbackSmtpServer.cs index 39b73ff3eea558..2a138b2161ebeb 100644 --- a/src/libraries/System.Net.Mail/tests/Functional/LoopbackSmtpServer.cs +++ b/src/libraries/System.Net.Mail/tests/Functional/LoopbackSmtpServer.cs @@ -55,6 +55,8 @@ public class LoopbackSmtpServer : IDisposable public ParsedMailMessage Message { get; private set; } public bool IsEncrypted { get; private set; } public string TlsHostName { get; private set; } + public System.Security.Authentication.SslProtocols TlsProtocol { get; private set; } + public SslApplicationProtocol ApplicationProtocol { get; private set; } public int ConnectionCount { get; private set; } public int MessagesReceived { get; private set; } @@ -263,6 +265,8 @@ await SendMessageAsync( await sslStream.AuthenticateAsServerAsync(SslOptions); IsEncrypted = true; TlsHostName = sslStream.TargetHostName; + TlsProtocol = sslStream.SslProtocol; + ApplicationProtocol = sslStream.NegotiatedApplicationProtocol; stream = sslStream; break; diff --git a/src/libraries/System.Net.Mail/tests/Functional/SmtpClientTest.cs b/src/libraries/System.Net.Mail/tests/Functional/SmtpClientTest.cs index 5cd51cd41c8920..7a934cef72a9e2 100644 --- a/src/libraries/System.Net.Mail/tests/Functional/SmtpClientTest.cs +++ b/src/libraries/System.Net.Mail/tests/Functional/SmtpClientTest.cs @@ -14,8 +14,11 @@ using System.Globalization; using System.IO; using System.Net.NetworkInformation; +using System.Net.Security; using System.Net.Sockets; using System.Reflection; +using System.Security.Authentication; +using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.RemoteExecutor; @@ -80,6 +83,113 @@ public void EnableSslTest(bool value) Assert.Equal(value, Smtp.EnableSsl); } + [Fact] + public void SslOptions_DefaultsAndIdentity() + { + SslClientAuthenticationOptions options = Smtp.SslOptions; + Assert.Same(options, Smtp.SslOptions); + Assert.Null(options.TargetHost); + Assert.Null(options.ClientCertificates); + Assert.Null(options.RemoteCertificateValidationCallback); + Assert.Equal(SslProtocols.None, options.EnabledSslProtocols); + Assert.Equal(X509RevocationMode.NoCheck, options.CertificateRevocationCheckMode); + + using var other = new SmtpClient(); + Assert.NotSame(options, other.SslOptions); + + var replacement = new SslClientAuthenticationOptions(); + Smtp.SslOptions = replacement; + Assert.Same(replacement, Smtp.SslOptions); + AssertExtensions.Throws("value", () => Smtp.SslOptions = null!); + Assert.Same(replacement, Smtp.SslOptions); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void ClientCertificates_ForwardsToSslOptions(bool getCertificatesFirst) + { + if (getCertificatesFirst) + { + Assert.Empty(Smtp.ClientCertificates); + } + + var certificates = new X509CertificateCollection(); + var options = new SslClientAuthenticationOptions { ClientCertificates = certificates }; + Smtp.SslOptions = options; + Assert.Same(certificates, Smtp.ClientCertificates); + + var replacement = new X509CertificateCollection(); + options.ClientCertificates = replacement; + Assert.Same(replacement, Smtp.ClientCertificates); + + options.ClientCertificates = null; + X509CertificateCollection initialized = Smtp.ClientCertificates; + Assert.Empty(initialized); + Assert.Same(initialized, options.ClientCertificates); + Assert.Same(initialized, Smtp.ClientCertificates); + + Smtp.SslOptions = new SslClientAuthenticationOptions(); + Assert.NotSame(initialized, Smtp.ClientCertificates); + Assert.Same(Smtp.ClientCertificates, Smtp.SslOptions.ClientCertificates); + } + + [ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + [InlineData(false)] + [InlineData(true)] + public async Task SslOptions_ServicePointManagerIgnored(bool customValidation) + { + await RemoteExecutor.Invoke(async useCustomValidation => + { + int globalCallbackCalls = 0; +#pragma warning disable SYSLIB0014 // Verify that SMTP no longer uses these global settings. + ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13; + ServicePointManager.CheckCertificateRevocationList = true; + ServicePointManager.ServerCertificateValidationCallback = (sender, certificate, chain, errors) => + { + globalCallbackCalls++; + return !bool.Parse(useCustomValidation); + }; +#pragma warning restore SYSLIB0014 + + using var certificates = new CertificateSetup(); + using var server = new LoopbackSmtpServer(); + server.SslOptions = new SslServerAuthenticationOptions + { + ServerCertificateContext = certificates.CreateSslStreamCertificateContext(), + EnabledSslProtocols = SslProtocols.Tls12, + }; + using SmtpClient client = server.CreateClient(); + client.EnableSsl = true; + Assert.Equal(SslProtocols.None, client.SslOptions.EnabledSslProtocols); + Assert.Equal(X509RevocationMode.NoCheck, client.SslOptions.CertificateRevocationCheckMode); + Assert.Null(client.SslOptions.RemoteCertificateValidationCallback); + + if (bool.Parse(useCustomValidation)) + { + int clientCallbackCalls = 0; + client.SslOptions.RemoteCertificateValidationCallback = (sender, certificate, chain, errors) => + { + clientCallbackCalls++; + Assert.Equal(X509RevocationMode.NoCheck, chain.ChainPolicy.RevocationMode); + return true; + }; + + await client.SendMailAsync("from@example.com", "to@example.com", "subject", "body"); + Assert.Equal(1, clientCallbackCalls); + Assert.True(server.IsEncrypted); + } + else + { + SmtpException exception = await Assert.ThrowsAsync(() => + client.SendMailAsync("from@example.com", "to@example.com", "subject", "body")); + Assert.IsType(exception.InnerException); + } + + Assert.Equal(0, globalCallbackCalls); + }, customValidation.ToString()).DisposeAsync(); + } + [Theory] [InlineData("127.0.0.1")] [InlineData("smtp.ximian.com")] diff --git a/src/libraries/System.Net.Mail/tests/Functional/SmtpClientTlsTest.cs b/src/libraries/System.Net.Mail/tests/Functional/SmtpClientTlsTest.cs index 262a01e06f7919..39cf7f23e3a929 100644 --- a/src/libraries/System.Net.Mail/tests/Functional/SmtpClientTlsTest.cs +++ b/src/libraries/System.Net.Mail/tests/Functional/SmtpClientTlsTest.cs @@ -52,9 +52,7 @@ public SmtpClientTlsTest(ITestOutputHelper output, CertificateSetup certificateS ClientCertificateRequired = false, }; -#pragma warning disable SYSLIB0014 // ServicePointManager is obsolete - ServicePointManager.ServerCertificateValidationCallback = ServerCertValidationCallback; -#pragma warning restore SYSLIB0014 // ServicePointManager is obsolete + Smtp.SslOptions.RemoteCertificateValidationCallback = ServerCertValidationCallback; } [ActiveIssue("https://github.com/dotnet/runtime/issues/120959", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot), nameof(PlatformDetection.IsAndroid))] @@ -157,8 +155,10 @@ public async Task AuthenticationException_Propagates() } [ActiveIssue("https://github.com/dotnet/runtime/issues/120959", typeof(PlatformDetection), nameof(PlatformDetection.IsNativeAot), nameof(PlatformDetection.IsAndroid))] - [Fact] - public async Task ClientCertificateRequired_Sent() + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task ClientCertificateRequired_Sent(bool useSslOptions) { Server.SslOptions.ClientCertificateRequired = true; X509Certificate2 clientCert = _certificateSetup.ServerCert; // use the server cert as a client cert for testing @@ -176,7 +176,14 @@ public async Task ClientCertificateRequired_Sent() Smtp.Credentials = new NetworkCredential("foo", "bar"); Smtp.EnableSsl = true; - Smtp.ClientCertificates.Add(clientCert); + if (useSslOptions) + { + Smtp.SslOptions.ClientCertificates = new X509CertificateCollection { clientCert }; + } + else + { + Smtp.ClientCertificates.Add(clientCert); + } MailMessage msg = new MailMessage("foo@example.com", "bar@example.com", "hello", "howdydoo"); @@ -206,6 +213,163 @@ public async Task EnableSsl_ChangedAfterConnect_EstablishesNewEncryptedConnectio Assert.True(Server.IsEncrypted, "Second connection should be encrypted after enabling SSL."); } + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData("smtp.example.com")] + public async Task SslOptions_TargetHost(string? targetHost) + { + Smtp.EnableSsl = true; + Smtp.SslOptions.TargetHost = targetHost; + _serverCertValidationCallback = (cert, chain, errors) => true; + using var message = new MailMessage("from@example.com", "to@example.com", "subject", "body"); + + await SendMail(message); + + Assert.Equal(targetHost ?? Smtp.Host, Server.TlsHostName); + Assert.Equal(targetHost, Smtp.SslOptions.TargetHost); + } + + [Fact] + public async Task SslOptions_DefaultTargetHostFollowsHostChange() + { + Server.ReceiveMultipleConnections = true; + Smtp.EnableSsl = true; + string? validatedHost = null; + Smtp.SslOptions.RemoteCertificateValidationCallback = (sender, cert, chain, errors) => + { + validatedHost = Assert.IsType(sender).TargetHostName; + return true; + }; + using var message = new MailMessage("from@example.com", "to@example.com", "subject", "body"); + + await SendMail(message); + Assert.Equal("localhost", validatedHost); + + Smtp.Host = "127.0.0.1"; + await SendMail(message); + Assert.Equal("127.0.0.1", validatedHost); + Assert.Equal(2, Server.ConnectionCount); + Assert.Null(Smtp.SslOptions.TargetHost); + } + + [Fact] + public async Task SslOptions_ProtocolSelection() + { + Smtp.EnableSsl = true; + Smtp.SslOptions.EnabledSslProtocols = SslProtocols.Tls12; + _serverCertValidationCallback = (cert, chain, errors) => true; + using var message = new MailMessage("from@example.com", "to@example.com", "subject", "body"); + + await SendMail(message); + + Assert.Equal(SslProtocols.Tls12, Server.TlsProtocol); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.SupportsAlpn))] + public async Task SslOptions_ApplicationProtocols() + { + var protocol = new SslApplicationProtocol("smtp-test"); + Server.SslOptions.ApplicationProtocols = new() { protocol }; + Smtp.EnableSsl = true; + Smtp.SslOptions.ApplicationProtocols = new() { protocol }; + _serverCertValidationCallback = (cert, chain, errors) => true; + using var message = new MailMessage("from@example.com", "to@example.com", "subject", "body"); + + await SendMail(message); + + Assert.Equal(protocol, Server.ApplicationProtocol); + } + + [Fact] + public async Task SslOptions_DisabledSslDoesNotUseOptions() + { + bool callbackCalled = false; + Smtp.SslOptions.RemoteCertificateValidationCallback = (sender, cert, chain, errors) => + { + callbackCalled = true; + return false; + }; + using var message = new MailMessage("from@example.com", "to@example.com", "subject", "body"); + + await SendMail(message); + + Assert.False(Server.IsEncrypted); + Assert.False(callbackCalled); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task SslOptions_AssignmentInvalidatesConnection(bool sameInstance) + { + Server.ReceiveMultipleConnections = true; + Smtp.EnableSsl = true; + X509CertificateCollection certificates = Smtp.ClientCertificates; + _serverCertValidationCallback = (cert, chain, errors) => true; + using var message = new MailMessage("from@example.com", "to@example.com", "subject", "body"); + await SendMail(message); + await SendMail(message); + Assert.Equal(1, Server.ConnectionCount); + + SslClientAuthenticationOptions options = sameInstance ? Smtp.SslOptions : new SslClientAuthenticationOptions + { + RemoteCertificateValidationCallback = ServerCertValidationCallback, + }; + options.TargetHost = "smtp.example.com"; + if (sameInstance) + { + certificates.Add(_certificateSetup.ServerCert); + Assert.Same(certificates, options.ClientCertificates); + await SendMail(message); + Assert.Equal(1, Server.ConnectionCount); + Assert.Equal("localhost", Server.TlsHostName); + } + + Smtp.SslOptions = options; + await SendMail(message); + Assert.Equal(2, Server.ConnectionCount); + Assert.Equal("smtp.example.com", Server.TlsHostName); + } + + [Fact] + public async Task SslOptions_InPlaceChangeAppliesAfterReconnect() + { + Server.ReceiveMultipleConnections = true; + Smtp.EnableSsl = true; + _serverCertValidationCallback = (cert, chain, errors) => true; + using var message = new MailMessage("from@example.com", "to@example.com", "subject", "body"); + await SendMail(message); + + Smtp.SslOptions.TargetHost = "smtp.example.com"; + Smtp.TargetName = "SMTPSVC/another-name"; + await SendMail(message); + + Assert.Equal(2, Server.ConnectionCount); + Assert.Equal("smtp.example.com", Server.TlsHostName); + } + + [Fact] + public async Task SslOptions_AssignmentDuringSendThrows() + { + Smtp.EnableSsl = true; + SslClientAuthenticationOptions original = Smtp.SslOptions; + bool callbackCalled = false; + _serverCertValidationCallback = (cert, chain, errors) => + { + callbackCalled = true; + Assert.Throws(() => Smtp.SslOptions = new SslClientAuthenticationOptions()); + Assert.Throws(() => Smtp.SslOptions = original); + Assert.Same(original, Smtp.SslOptions); + return true; + }; + using var message = new MailMessage("from@example.com", "to@example.com", "subject", "body"); + + await SendMail(message); + + Assert.True(callbackCalled); + } + private bool ServerCertValidationCallback(object sender, X509Certificate? certificate, X509Chain? chain, SslPolicyErrors sslPolicyErrors) { if (_serverCertValidationCallback != null) @@ -218,21 +382,16 @@ private bool ServerCertValidationCallback(object sender, X509Certificate? certif } } - // since the tests change global state (ServicePointManager.ServerCertificateValidationCallback), we need to run them in isolation - - [Collection(nameof(DisableParallelization))] public class SmtpClientTlsTest_Send : SmtpClientTlsTest, IClassFixture { public SmtpClientTlsTest_Send(ITestOutputHelper output, CertificateSetup certificateSetup) : base(output, certificateSetup) { } } - [Collection(nameof(DisableParallelization))] public class SmtpClientTlsTest_SendAsync : SmtpClientTlsTest, IClassFixture { public SmtpClientTlsTest_SendAsync(ITestOutputHelper output, CertificateSetup certificateSetup) : base(output, certificateSetup) { } } - [Collection(nameof(DisableParallelization))] public class SmtpClientTlsTest_SendMailAsync : SmtpClientTlsTest, IClassFixture { public SmtpClientTlsTest_SendMailAsync(ITestOutputHelper output, CertificateSetup certificateSetup) : base(output, certificateSetup) { }