using System.Net;
using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates;
using Opc.Ua;
using Opc.Ua.Configuration;
using Opc.Ua.Server;
namespace ZB.MOM.WW.ScadaBridge.DataConnectionLayer.Tests.Adapters;
///
/// An in-process OPC Foundation exposing None, Sign and
/// SignAndEncrypt (Basic256Sha256) endpoints with STRICT client trust
/// (AutoAcceptUntrustedCertificates = false) and its own temp PKI — the Gitea #121
/// counterpart for real mutual-trust tests of RealOpcUaClient.
///
internal sealed class SecureOpcUaTestServer : IAsyncDisposable
{
private readonly ApplicationInstance _app;
private readonly string _pkiRoot;
private SecureOpcUaTestServer(ApplicationInstance app, string pkiRoot, string url, X509Certificate2 certificate)
{
_app = app;
_pkiRoot = pkiRoot;
Url = url;
Certificate = certificate;
}
/// The endpoint URL (localhost, so the server certificate's domain matches).
public string Url { get; }
/// The server's application certificate (public part).
public X509Certificate2 Certificate { get; }
/// Starts a server that trusts exactly .
/// Client certificates the server's trusted peer store holds at start.
/// The running server.
public static async Task StartAsync(params X509Certificate2[] trustedClients)
{
var pkiRoot = Path.Combine(Path.GetTempPath(), "sb121-server", Guid.NewGuid().ToString("N"));
var port = FreePort();
var url = $"opc.tcp://localhost:{port}/sb121";
// Trust is written BEFORE start, so no test depends on trust-list cache refresh timing.
var trustedCerts = Path.Combine(pkiRoot, "trusted", "certs");
Directory.CreateDirectory(trustedCerts);
foreach (var c in trustedClients)
await File.WriteAllBytesAsync(Path.Combine(trustedCerts, $"{c.Thumbprint}.der"), c.RawData);
var config = new ApplicationConfiguration
{
ApplicationName = "SB121TestServer",
ApplicationUri = $"urn:localhost:SB121TestServer:{port}",
ApplicationType = ApplicationType.Server,
SecurityConfiguration = new SecurityConfiguration
{
ApplicationCertificate = new CertificateIdentifier
{
StoreType = CertificateStoreType.Directory,
StorePath = Path.Combine(pkiRoot, "own"),
SubjectName = "CN=SB121TestServer",
CertificateType = ObjectTypeIds.RsaSha256ApplicationCertificateType,
},
TrustedPeerCertificates = new CertificateTrustList { StorePath = Path.Combine(pkiRoot, "trusted") },
TrustedIssuerCertificates = new CertificateTrustList { StorePath = Path.Combine(pkiRoot, "issuers") },
RejectedCertificateStore = new CertificateTrustList { StorePath = Path.Combine(pkiRoot, "rejected") },
AutoAcceptUntrustedCertificates = false,
AddAppCertToTrustedStore = false,
},
TransportQuotas = new TransportQuotas { OperationTimeout = 15_000 },
ServerConfiguration = new ServerConfiguration
{
BaseAddresses = { url },
SecurityPolicies =
{
new ServerSecurityPolicy { SecurityMode = MessageSecurityMode.None, SecurityPolicyUri = SecurityPolicies.None },
new ServerSecurityPolicy { SecurityMode = MessageSecurityMode.Sign, SecurityPolicyUri = SecurityPolicies.Basic256Sha256 },
new ServerSecurityPolicy { SecurityMode = MessageSecurityMode.SignAndEncrypt, SecurityPolicyUri = SecurityPolicies.Basic256Sha256 },
},
UserTokenPolicies = { new UserTokenPolicy(UserTokenType.Anonymous) },
},
};
await config.ValidateAsync(ApplicationType.Server);
var telemetry = DefaultTelemetry.Create(_ => { });
var app = new ApplicationInstance(config, telemetry)
{
ApplicationName = config.ApplicationName,
ApplicationType = ApplicationType.Server,
};
await app.CheckApplicationInstanceCertificatesAsync(silent: true, lifeTimeInMonths: 12);
await app.StartAsync(new StandardServer());
var cert = X509CertificateLoader.LoadCertificate(
config.SecurityConfiguration.ApplicationCertificate.Certificate.RawData);
return new SecureOpcUaTestServer(app, pkiRoot, url, cert);
}
///
public async ValueTask DisposeAsync()
{
try { await _app.StopAsync(); } catch { /* best-effort */ }
Certificate.Dispose();
try { Directory.Delete(_pkiRoot, recursive: true); } catch { /* best-effort */ }
}
private static int FreePort()
{
var l = new TcpListener(IPAddress.Loopback, 0);
l.Start();
var port = ((IPEndPoint)l.LocalEndpoint).Port;
l.Stop();
return port;
}
}