diff --git a/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/MxGatewayClientTlsHandlerTests.cs b/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/MxGatewayClientTlsHandlerTests.cs
index 3ba09f5..ac6fde9 100644
--- a/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/MxGatewayClientTlsHandlerTests.cs
+++ b/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/MxGatewayClientTlsHandlerTests.cs
@@ -1,4 +1,5 @@
using System.Net.Http;
+using System.Net.Security;
using ZB.MOM.WW.MxGateway.Client;
namespace ZB.MOM.WW.MxGateway.Client.Tests;
@@ -8,6 +9,7 @@ public sealed class MxGatewayClientTlsHandlerTests
///
/// Verifies that when TLS is used with no pinned CA and RequireCertificateValidation is false (default),
/// the handler installs an accept-all callback so the gateway's self-signed cert is trusted.
+ /// The callback must return true regardless of chain errors.
///
[Fact]
public void Handler_SkipsVerification_WhenTlsAndNoCaPinned()
@@ -20,6 +22,7 @@ public sealed class MxGatewayClientTlsHandlerTests
};
using SocketsHttpHandler handler = MxGatewayClient.CreateHttpHandlerForTests(options);
Assert.NotNull(handler.SslOptions.RemoteCertificateValidationCallback);
+ Assert.True(handler.SslOptions.RemoteCertificateValidationCallback!(null!, null!, null, SslPolicyErrors.RemoteCertificateChainErrors));
}
///
@@ -40,3 +43,43 @@ public sealed class MxGatewayClientTlsHandlerTests
Assert.Null(handler.SslOptions.RemoteCertificateValidationCallback);
}
}
+
+public sealed class GalaxyRepositoryClientTlsHandlerTests
+{
+ ///
+ /// Verifies that when TLS is used with no pinned CA and RequireCertificateValidation is false (default),
+ /// the Galaxy client handler installs an accept-all callback so the gateway's self-signed cert is trusted.
+ /// The callback must return true regardless of chain errors.
+ ///
+ [Fact]
+ public void Handler_SkipsVerification_WhenTlsAndNoCaPinned()
+ {
+ MxGatewayClientOptions options = new()
+ {
+ Endpoint = new Uri("https://localhost:5120"),
+ ApiKey = "k",
+ UseTls = true,
+ };
+ using SocketsHttpHandler handler = GalaxyRepositoryClient.CreateHttpHandlerForTests(options);
+ Assert.NotNull(handler.SslOptions.RemoteCertificateValidationCallback);
+ Assert.True(handler.SslOptions.RemoteCertificateValidationCallback!(null!, null!, null, SslPolicyErrors.RemoteCertificateChainErrors));
+ }
+
+ ///
+ /// Verifies that when RequireCertificateValidation is true, the Galaxy client callback is left null
+ /// so the OS trust store performs validation.
+ ///
+ [Fact]
+ public void Handler_KeepsDefaultVerification_WhenRequireCertificateValidation()
+ {
+ MxGatewayClientOptions options = new()
+ {
+ Endpoint = new Uri("https://localhost:5120"),
+ ApiKey = "k",
+ UseTls = true,
+ RequireCertificateValidation = true,
+ };
+ using SocketsHttpHandler handler = GalaxyRepositoryClient.CreateHttpHandlerForTests(options);
+ Assert.Null(handler.SslOptions.RemoteCertificateValidationCallback);
+ }
+}
diff --git a/clients/dotnet/ZB.MOM.WW.MxGateway.Client/GalaxyRepositoryClient.cs b/clients/dotnet/ZB.MOM.WW.MxGateway.Client/GalaxyRepositoryClient.cs
index ac5029b..7d05638 100644
--- a/clients/dotnet/ZB.MOM.WW.MxGateway.Client/GalaxyRepositoryClient.cs
+++ b/clients/dotnet/ZB.MOM.WW.MxGateway.Client/GalaxyRepositoryClient.cs
@@ -490,7 +490,10 @@ public sealed class GalaxyRepositoryClient : IAsyncDisposable
.ConfigureAwait(false);
}
- private static HttpMessageHandler CreateHttpHandler(MxGatewayClientOptions options)
+ private static HttpMessageHandler CreateHttpHandler(MxGatewayClientOptions options) =>
+ CreateHttpHandlerForTests(options);
+
+ internal static SocketsHttpHandler CreateHttpHandlerForTests(MxGatewayClientOptions options)
{
SocketsHttpHandler handler = new()
{
@@ -510,6 +513,11 @@ public sealed class GalaxyRepositoryClient : IAsyncDisposable
X509Certificate2 trustedRoot = X509CertificateLoader.LoadCertificateFromFile(options.CaCertificatePath);
handler.SslOptions.RemoteCertificateValidationCallback = (_, certificate, chain, errors) =>
{
+ if ((errors & System.Net.Security.SslPolicyErrors.RemoteCertificateNameMismatch) != 0)
+ {
+ return false;
+ }
+
if (certificate is null)
{
return false;
@@ -525,6 +533,10 @@ public sealed class GalaxyRepositoryClient : IAsyncDisposable
return customChain.Build(certificateToValidate);
};
}
+ else if (!options.RequireCertificateValidation)
+ {
+ handler.SslOptions.RemoteCertificateValidationCallback = (_, _, _, _) => true;
+ }
}
return handler;
diff --git a/clients/dotnet/ZB.MOM.WW.MxGateway.Client/MxGatewayClient.cs b/clients/dotnet/ZB.MOM.WW.MxGateway.Client/MxGatewayClient.cs
index 20c35a4..6d9ab58 100644
--- a/clients/dotnet/ZB.MOM.WW.MxGateway.Client/MxGatewayClient.cs
+++ b/clients/dotnet/ZB.MOM.WW.MxGateway.Client/MxGatewayClient.cs
@@ -338,6 +338,11 @@ public sealed class MxGatewayClient : IAsyncDisposable
X509Certificate2 trustedRoot = X509CertificateLoader.LoadCertificateFromFile(options.CaCertificatePath);
handler.SslOptions.RemoteCertificateValidationCallback = (_, certificate, chain, errors) =>
{
+ if ((errors & System.Net.Security.SslPolicyErrors.RemoteCertificateNameMismatch) != 0)
+ {
+ return false;
+ }
+
if (certificate is null)
{
return false;