This is an automated email from the ASF dual-hosted git repository.

ptupitsyn pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git


The following commit(s) were added to refs/heads/main by this push:
     new 4136fcfcd9 IGNITE-19045 .NET: Refactor SslStreamFactory to use 
SslClientAuthenticationOptions (#1817)
4136fcfcd9 is described below

commit 4136fcfcd96be236b4c50719800339a841934d76
Author: Pavel Tupitsyn <[email protected]>
AuthorDate: Tue Mar 21 09:53:16 2023 +0300

    IGNITE-19045 .NET: Refactor SslStreamFactory to use 
SslClientAuthenticationOptions (#1817)
    
    * Replace a custom and potentially incomplete (e.g. ciphers could not be 
set before) set of options in `SslStreamFactory` with 
`SslClientAuthenticationOptions`. This way our API is simpler, and full set of 
options is available to the users.
    * Make `ISslStreamFactory` asynchronous.
    
    
[SslClientAuthenticationOptions](https://learn.microsoft.com/en-us/dotnet/api/system.net.security.sslclientauthenticationoptions?view=net-7.0)
 is a property bag that can be passed to `SslStream.AuthenticateAsClientAsync`. 
This API is not available in "classic" .NET or .NET standard, so we could not 
use it in Ignite 2.x.
---
 .../dotnet/Apache.Ignite.Tests/SslTests.cs         | 65 +++++++++++++++----
 .../dotnet/Apache.Ignite/ISslStreamFactory.cs      |  3 +-
 .../dotnet/Apache.Ignite/Internal/ClientSocket.cs  |  2 +-
 .../dotnet/Apache.Ignite/SslStreamFactory.cs       | 72 +++-------------------
 4 files changed, 63 insertions(+), 79 deletions(-)

diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/SslTests.cs 
b/modules/platforms/dotnet/Apache.Ignite.Tests/SslTests.cs
index 335733d086..2a17f930a3 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/SslTests.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/SslTests.cs
@@ -23,6 +23,7 @@ using System.IO;
 using System.Linq;
 using System.Net.Security;
 using System.Security.Authentication;
+using System.Security.Cryptography.X509Certificates;
 using System.Threading.Tasks;
 using Log;
 using NUnit.Framework;
@@ -30,6 +31,7 @@ using NUnit.Framework;
 /// <summary>
 /// SSL tests.
 /// </summary>
+[SuppressMessage("Security", "CA5359:Do Not Disable Certificate Validation", 
Justification = "Tests.")]
 public class SslTests : IgniteTestsBase
 {
     private const string CertificatePassword = "123456";
@@ -51,7 +53,13 @@ public class SslTests : IgniteTestsBase
         var cfg = new IgniteClientConfiguration
         {
             Endpoints = { SslEndpoint },
-            SslStreamFactory = new SslStreamFactory { 
SkipServerCertificateValidation = true }
+            SslStreamFactory = new SslStreamFactory
+            {
+                SslClientAuthenticationOptions = new 
SslClientAuthenticationOptions
+                {
+                    RemoteCertificateValidationCallback = (_, _, _, _) => true
+                }
+            }
         };
 
         using var client = await IgniteClient.StartAsync(cfg);
@@ -76,9 +84,11 @@ public class SslTests : IgniteTestsBase
             Endpoints = { SslEndpointWithClientAuth },
             SslStreamFactory = new SslStreamFactory
             {
-                SkipServerCertificateValidation = true,
-                CertificatePath = CertificatePath,
-                CertificatePassword = CertificatePassword
+                SslClientAuthenticationOptions = new()
+                {
+                    RemoteCertificateValidationCallback = (_, _, _, _) => true,
+                    ClientCertificates = new X509Certificate2Collection(new 
X509Certificate2(CertificatePath, CertificatePassword))
+                }
             },
             Logger = new ConsoleLogger { MinLevel = LogLevel.Trace }
         };
@@ -101,10 +111,7 @@ public class SslTests : IgniteTestsBase
     public void TestSslOnClientWithoutSslOnServerThrows()
     {
         var cfg = GetConfig();
-        cfg.SslStreamFactory = new SslStreamFactory
-        {
-            SslProtocols = SslProtocols.Tls13 | SslProtocols.Tls12
-        };
+        cfg.SslStreamFactory = new SslStreamFactory();
 
         var ex = Assert.ThrowsAsync<AggregateException>(async () => await 
IgniteClient.StartAsync(cfg));
         
Assert.IsInstanceOf<IgniteClientConnectionException>(ex?.InnerException);
@@ -130,8 +137,11 @@ public class SslTests : IgniteTestsBase
             Endpoints = { SslEndpointWithClientAuth },
             SslStreamFactory = new SslStreamFactory
             {
-                SkipServerCertificateValidation = true,
-                CheckCertificateRevocation = true
+                SslClientAuthenticationOptions = new()
+                {
+                    RemoteCertificateValidationCallback = (_, _, _, _) => true,
+                    CertificateRevocationCheckMode = X509RevocationMode.NoCheck
+                }
             }
         };
 
@@ -166,14 +176,43 @@ public class SslTests : IgniteTestsBase
         Assert.IsNull(client.GetConnections().Single().SslInfo);
     }
 
+    [Test]
+    public async Task TestCustomCipherSuite()
+    {
+        var cfg = new IgniteClientConfiguration
+        {
+            Endpoints = { SslEndpoint },
+            SslStreamFactory = new SslStreamFactory
+            {
+                SslClientAuthenticationOptions = new 
SslClientAuthenticationOptions
+                {
+                    RemoteCertificateValidationCallback = (_, _, _, _) => true,
+                    CipherSuitesPolicy = new CipherSuitesPolicy(new[]
+                    {
+                        TlsCipherSuite.TLS_AES_128_GCM_SHA256
+                    })
+                }
+            }
+        };
+
+        using var client = await IgniteClient.StartAsync(cfg);
+
+        var connection = client.GetConnections().Single();
+        var sslInfo = connection.SslInfo;
+
+        Assert.IsNotNull(sslInfo);
+        Assert.IsFalse(sslInfo!.IsMutuallyAuthenticated);
+        Assert.AreEqual(TlsCipherSuite.TLS_AES_128_GCM_SHA256.ToString(), 
sslInfo.NegotiatedCipherSuiteName);
+    }
+
     private class NullSslStreamFactory : ISslStreamFactory
     {
-        public SslStream? Create(Stream stream, string targetHost) => null;
+        public Task<SslStream?> CreateAsync(Stream stream, string targetHost) 
=> Task.FromResult<SslStream?>(null);
     }
 
     private class CustomSslStreamFactory : ISslStreamFactory
     {
-        public SslStream Create(Stream stream, string targetHost)
+        public async Task<SslStream?> CreateAsync(Stream stream, string 
targetHost)
         {
             var sslStream = new SslStream(
                 innerStream: stream,
@@ -181,7 +220,7 @@ public class SslTests : IgniteTestsBase
                 userCertificateValidationCallback: (_, certificate, _, _) => 
certificate!.Issuer.Contains("ignite"),
                 userCertificateSelectionCallback: null);
 
-            sslStream.AuthenticateAsClient(targetHost, null, 
SslProtocols.None, false);
+            await sslStream.AuthenticateAsClientAsync(targetHost, null, 
SslProtocols.None, false);
 
             return sslStream;
         }
diff --git a/modules/platforms/dotnet/Apache.Ignite/ISslStreamFactory.cs 
b/modules/platforms/dotnet/Apache.Ignite/ISslStreamFactory.cs
index 6cd692b9ac..9bc51c3ceb 100644
--- a/modules/platforms/dotnet/Apache.Ignite/ISslStreamFactory.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/ISslStreamFactory.cs
@@ -19,6 +19,7 @@ namespace Apache.Ignite;
 
 using System.IO;
 using System.Net.Security;
+using System.Threading.Tasks;
 
 /// <summary>
 /// SSL Stream Factory defines how SSL connection is established.
@@ -35,5 +36,5 @@ public interface ISslStreamFactory
     /// <returns>
     /// SSL stream, or null if SSL is not enabled.
     /// </returns>
-    SslStream? Create(Stream stream, string targetHost);
+    Task<SslStream?> CreateAsync(Stream stream, string targetHost);
 }
diff --git a/modules/platforms/dotnet/Apache.Ignite/Internal/ClientSocket.cs 
b/modules/platforms/dotnet/Apache.Ignite/Internal/ClientSocket.cs
index e525862fe3..f9630ee4e0 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/ClientSocket.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/ClientSocket.cs
@@ -181,7 +181,7 @@ namespace Apache.Ignite.Internal
                 Stream stream = new NetworkStream(socket, ownsSocket: true);
 
                 if (configuration.SslStreamFactory is { } sslStreamFactory &&
-                    sslStreamFactory.Create(stream, endPoint.Host) is { } 
sslStream)
+                    await sslStreamFactory.CreateAsync(stream, 
endPoint.Host).ConfigureAwait(false) is { } sslStream)
                 {
                     stream = sslStream;
 
diff --git a/modules/platforms/dotnet/Apache.Ignite/SslStreamFactory.cs 
b/modules/platforms/dotnet/Apache.Ignite/SslStreamFactory.cs
index 02f552260d..cbdc43a4ee 100644
--- a/modules/platforms/dotnet/Apache.Ignite/SslStreamFactory.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/SslStreamFactory.cs
@@ -17,11 +17,9 @@
 
 namespace Apache.Ignite;
 
-using System.ComponentModel;
 using System.IO;
 using System.Net.Security;
-using System.Security.Authentication;
-using System.Security.Cryptography.X509Certificates;
+using System.Threading.Tasks;
 using Internal.Common;
 
 /// <summary>
@@ -30,76 +28,22 @@ using Internal.Common;
 public sealed class SslStreamFactory : ISslStreamFactory
 {
     /// <summary>
-    /// Default SSL protocols.
+    /// Gets or sets client authentication options.
     /// </summary>
-    public const SslProtocols DefaultSslProtocols = SslProtocols.None;
-
-    /// <summary>
-    /// Gets or sets the certificate file path (see <see 
cref="X509Certificate2"/>).
-    /// </summary>
-    public string? CertificatePath { get; set; }
-
-    /// <summary>
-    /// Gets or sets the certificate file password (see <see 
cref="X509Certificate2"/>).
-    /// </summary>
-    public string? CertificatePassword { get; set; }
-
-    /// <summary>
-    /// Gets or sets a value indicating whether to ignore invalid remote 
(server) certificates.
-    /// This may be useful for testing with self-signed certificates.
-    /// </summary>
-    public bool SkipServerCertificateValidation { get; set; }
-
-    /// <summary>
-    /// Gets or sets a value indicating whether the certificate revocation 
list is checked during authentication.
-    /// </summary>
-    public bool CheckCertificateRevocation { get; set; }
-
-    /// <summary>
-    /// Gets or sets the SSL protocols.
-    /// </summary>
-    [DefaultValue(DefaultSslProtocols)]
-    public SslProtocols SslProtocols { get; set; } = DefaultSslProtocols;
+    public SslClientAuthenticationOptions? SslClientAuthenticationOptions { 
get; set; }
 
     /// <inheritdoc />
-    public SslStream Create(Stream stream, string targetHost)
+    public async Task<SslStream?> CreateAsync(Stream stream, string targetHost)
     {
         IgniteArgumentCheck.NotNull(stream, "stream");
 
-        var sslStream = new SslStream(stream, false, 
ValidateServerCertificate, null);
+        var sslStream = new SslStream(stream, false, null, null);
 
-        var cert = string.IsNullOrEmpty(CertificatePath)
-            ? null
-            : new X509Certificate2(CertificatePath, CertificatePassword);
+        var options = SslClientAuthenticationOptions ?? new 
SslClientAuthenticationOptions();
+        options.TargetHost ??= targetHost;
 
-        var certs = cert == null
-            ? null
-            : new X509CertificateCollection(new X509Certificate[] { cert });
-
-        sslStream.AuthenticateAsClient(targetHost, certs, SslProtocols, 
CheckCertificateRevocation);
+        await 
sslStream.AuthenticateAsClientAsync(options).ConfigureAwait(false);
 
         return sslStream;
     }
-
-    /// <summary>
-    /// Validates the server certificate.
-    /// </summary>
-    private bool ValidateServerCertificate(
-        object sender,
-        X509Certificate? certificate,
-        X509Chain? chain,
-        SslPolicyErrors sslPolicyErrors)
-    {
-        if (SkipServerCertificateValidation)
-        {
-            return true;
-        }
-
-        if (sslPolicyErrors == SslPolicyErrors.None)
-        {
-            return true;
-        }
-
-        return false;
-    }
 }

Reply via email to