diff --git a/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/MxGatewayClientTlsHandlerTests.cs b/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/MxGatewayClientTlsHandlerTests.cs new file mode 100644 index 0000000..3ba09f5 --- /dev/null +++ b/clients/dotnet/ZB.MOM.WW.MxGateway.Client.Tests/MxGatewayClientTlsHandlerTests.cs @@ -0,0 +1,42 @@ +using System.Net.Http; +using ZB.MOM.WW.MxGateway.Client; + +namespace ZB.MOM.WW.MxGateway.Client.Tests; + +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. + /// + [Fact] + public void Handler_SkipsVerification_WhenTlsAndNoCaPinned() + { + MxGatewayClientOptions options = new() + { + Endpoint = new Uri("https://localhost:5120"), + ApiKey = "k", + UseTls = true, + }; + using SocketsHttpHandler handler = MxGatewayClient.CreateHttpHandlerForTests(options); + Assert.NotNull(handler.SslOptions.RemoteCertificateValidationCallback); + } + + /// + /// Verifies that when RequireCertificateValidation is true, the 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 = MxGatewayClient.CreateHttpHandlerForTests(options); + Assert.Null(handler.SslOptions.RemoteCertificateValidationCallback); + } +} diff --git a/clients/dotnet/ZB.MOM.WW.MxGateway.Client/MxGatewayClient.cs b/clients/dotnet/ZB.MOM.WW.MxGateway.Client/MxGatewayClient.cs index 2ae95ff..20c35a4 100644 --- a/clients/dotnet/ZB.MOM.WW.MxGateway.Client/MxGatewayClient.cs +++ b/clients/dotnet/ZB.MOM.WW.MxGateway.Client/MxGatewayClient.cs @@ -315,7 +315,10 @@ public sealed class MxGatewayClient : 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() { @@ -350,6 +353,10 @@ public sealed class MxGatewayClient : 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/MxGatewayClientOptions.cs b/clients/dotnet/ZB.MOM.WW.MxGateway.Client/MxGatewayClientOptions.cs index f66b56e..df93c3b 100644 --- a/clients/dotnet/ZB.MOM.WW.MxGateway.Client/MxGatewayClientOptions.cs +++ b/clients/dotnet/ZB.MOM.WW.MxGateway.Client/MxGatewayClientOptions.cs @@ -27,6 +27,14 @@ public sealed class MxGatewayClientOptions /// public string? CaCertificatePath { get; init; } + /// + /// When true, TLS connections without a pinned + /// use the OS trust store. When false (default), the gateway certificate is + /// accepted without verification — appropriate for this internal tool's + /// auto-generated self-signed certificate. Pinning a CA always verifies. + /// + public bool RequireCertificateValidation { get; init; } + /// /// Gets the server name override for SNI during TLS handshake. /// diff --git a/clients/dotnet/ZB.MOM.WW.MxGateway.Client/ZB.MOM.WW.MxGateway.Client.csproj b/clients/dotnet/ZB.MOM.WW.MxGateway.Client/ZB.MOM.WW.MxGateway.Client.csproj index 590435d..bf34ef7 100644 --- a/clients/dotnet/ZB.MOM.WW.MxGateway.Client/ZB.MOM.WW.MxGateway.Client.csproj +++ b/clients/dotnet/ZB.MOM.WW.MxGateway.Client/ZB.MOM.WW.MxGateway.Client.csproj @@ -27,4 +27,10 @@ + + + <_Parameter1>ZB.MOM.WW.MxGateway.Client.Tests + + +