Add HistorianClientOptions.AllowUntrustedServerCertificate
When true, the SDK's WCF channel factories accept the server's X.509 certificate without chain validation. Intended for connecting to development / on-prem Historians whose /HistCert endpoint presents an installer-generated self-signed cert that isn't in the local trust store. Particularly relevant on Linux: .NET WCF on Linux does its own X509Chain validation that doesn't honor the system CA bundle, so even after `update-ca-certificates` succeeds the cert binding still rejects the server. With this option set, custom certificate validator accepts any cert and revocation checking is disabled. Default false. Centralized in HistorianWcfClientCredentialsHelper.Configure and applied at every ChannelFactory<T> instantiation in the WCF layer (no-op when the option is false). 171/171 Windows tests still pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -27,4 +27,15 @@ public sealed class HistorianClientOptions
|
||||
public HistorianTransport Transport { get; init; } = HistorianTransport.LocalPipe;
|
||||
|
||||
public string TargetSpn { get; init; } = @"NT SERVICE\aahClientAccessPoint";
|
||||
|
||||
/// <summary>
|
||||
/// When true, the WCF channel factories used by the SDK accept the server's
|
||||
/// X.509 certificate without chain validation. Useful when connecting to a
|
||||
/// development / on-prem Historian whose <c>/HistCert</c> endpoint presents an
|
||||
/// installer-generated self-signed cert that isn't in the local trust store
|
||||
/// (notably .NET WCF on Linux ignores the system CA bundle for its own
|
||||
/// X509Chain checks). Default false; do not enable in production where the
|
||||
/// server's identity matters.
|
||||
/// </summary>
|
||||
public bool AllowUntrustedServerCertificate { get; init; }
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ internal static class HistorianWcfAuthChainHelper
|
||||
Action<IHistoryServiceContract2, OpenConnectionContext>? additionalSetup = null)
|
||||
{
|
||||
ChannelFactory<IHistoryServiceContract2> historyFactory = new(historyBinding, historyEndpoint);
|
||||
HistorianWcfClientCredentialsHelper.Configure(historyFactory, options);
|
||||
historyFactory.Endpoint.EndpointBehaviors.Add(new HistorianWcfHistAddressingBehavior());
|
||||
if (HistorianWcfMessageCaptureBehavior.IsEnabled)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
using System.IdentityModel.Selectors;
|
||||
using System.IdentityModel.Tokens;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.ServiceModel;
|
||||
using System.ServiceModel.Security;
|
||||
|
||||
namespace AVEVA.Historian.Client.Wcf;
|
||||
|
||||
/// <remarks>
|
||||
/// Centralizes per-channel-factory credentials configuration that's not bound to a
|
||||
/// single binding type. Today this covers <c>ServerCertificateValidation</c> for the
|
||||
/// cert-transport binding when callers opt into <see cref="HistorianClientOptions.AllowUntrustedServerCertificate"/>.
|
||||
/// Apply at every ChannelFactory<T> instantiation point in the WCF layer.
|
||||
/// </remarks>
|
||||
internal static class HistorianWcfClientCredentialsHelper
|
||||
{
|
||||
public static void Configure<TChannel>(ChannelFactory<TChannel> factory, HistorianClientOptions options)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(factory);
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
|
||||
if (options.AllowUntrustedServerCertificate)
|
||||
{
|
||||
factory.Credentials.ServiceCertificate.SslCertificateAuthentication = new X509ServiceCertificateAuthentication
|
||||
{
|
||||
CertificateValidationMode = X509CertificateValidationMode.Custom,
|
||||
CustomCertificateValidator = AcceptAnyCertificateValidator.Instance,
|
||||
RevocationMode = X509RevocationMode.NoCheck,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class AcceptAnyCertificateValidator : X509CertificateValidator
|
||||
{
|
||||
public static readonly AcceptAnyCertificateValidator Instance = new();
|
||||
public override void Validate(X509Certificate2 certificate) { }
|
||||
}
|
||||
}
|
||||
@@ -111,6 +111,7 @@ internal sealed class HistorianWcfEventOrchestrator
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
ChannelFactory<IRetrievalServiceContract4> factory = new(binding, retrievalEndpoint);
|
||||
HistorianWcfClientCredentialsHelper.Configure(factory, _options);
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
@@ -179,6 +179,7 @@ internal sealed class HistorianWcfReadOrchestrator
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
ChannelFactory<IRetrievalServiceContract2> retrievalFactory = new(binding, retrievalEndpoint);
|
||||
HistorianWcfClientCredentialsHelper.Configure(retrievalFactory, _options);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -280,6 +281,7 @@ internal sealed class HistorianWcfReadOrchestrator
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
ChannelFactory<IRetrievalServiceContract2> retrievalFactory = new(binding, retrievalEndpoint);
|
||||
HistorianWcfClientCredentialsHelper.Configure(retrievalFactory, _options);
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user