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; } }