using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using Microsoft.Extensions.Logging.Abstractions; using Opc.Ua; using Opc.Ua.Identity; using Shouldly; using Xunit; namespace ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests; public sealed class Sdk2CertificateLifecycleTests { [Fact] public async Task Restart_reuses_the_application_certificate_and_its_private_key() { var pki = Path.Combine(Path.GetTempPath(), $"otua-sdk2-cert-{Guid.NewGuid():N}"); try { string? thumbprint = null; for (var restart = 0; restart < 2; restart++) { var started = await OpcUaHostStart.StartAsync(port => new OpcUaApplicationHost(new OpcUaApplicationHostOptions { ApplicationName = "SDK2.CertReuse", ApplicationUri = "urn:SDK2.CertReuse", PublicHostname = "localhost", OpcUaPort = port, PkiStoreRoot = pki, AllowAnonymous = false, }, NullLogger.Instance), () => new OtOpcUaSdkServer(), TestContext.Current.CancellationToken); await using var host = started.Host; var keyFile = Directory.GetFiles(Path.Combine(pki, "own"), "*.pfx", SearchOption.AllDirectories).ShouldHaveSingleItem(); using var certificate = X509CertificateLoader.LoadPkcs12FromFile(keyFile, null); certificate.HasPrivateKey.ShouldBeTrue(); if (thumbprint is null) thumbprint = certificate.Thumbprint; else certificate.Thumbprint.ShouldBe(thumbprint); using var key = certificate.GetRSAPrivateKey(); using var publicKey = certificate.GetRSAPublicKey(); var signature = key!.SignData([1, 2, 3], HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); publicKey!.VerifyData(new byte[] { 1, 2, 3 }, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1).ShouldBeTrue(); var registry = started.Server.CurrentInstance.IdentityRegistry; var context = new AuthenticationContext(new AnonymousIdentityTokenHandler(), new UserTokenPolicy(UserTokenType.Anonymous), new EndpointDescription(), started.Server.MessageContext); (await registry.AuthenticateAsync(context, TestContext.Current.CancellationToken)).Outcome.ShouldBe(AuthenticationOutcome.Rejected); var userContext = context with { TokenHandler = new UserNameIdentityTokenHandler("apikey", "invalid"u8), UserTokenPolicy = new UserTokenPolicy(UserTokenType.UserName), }; (await registry.AuthenticateAsync(userContext, TestContext.Current.CancellationToken)).Outcome.ShouldBe(AuthenticationOutcome.Rejected); } } finally { if (Directory.Exists(pki)) Directory.Delete(pki, true); } } [Fact] public async Task A_canceled_start_disposes_its_application_owner() { var pki = Path.Combine(Path.GetTempPath(), $"otua-sdk2-canceled-{Guid.NewGuid():N}"); await using var host = new OpcUaApplicationHost(new OpcUaApplicationHostOptions { PkiStoreRoot = pki }, NullLogger.Instance); using var server = new OtOpcUaSdkServer(); using var cancellation = new CancellationTokenSource(); cancellation.Cancel(); await Should.ThrowAsync(() => host.StartAsync(server, cancellation.Token)); host.ApplicationInstance.ShouldBeNull(); if (Directory.Exists(pki)) Directory.Delete(pki, true); } }