98 lines
5.1 KiB
C#
98 lines
5.1 KiB
C#
using Opc.Ua;
|
|
using Opc.Ua.Configuration;
|
|
using System.Security.Cryptography;
|
|
using System.Security.Cryptography.X509Certificates;
|
|
using Shouldly;
|
|
using Xunit;
|
|
using ZB.MOM.WW.OtOpcUa.Client.Shared.Adapters;
|
|
using ZB.MOM.WW.OtOpcUa.Client.Shared.Models;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Client.Shared.Tests.Adapters;
|
|
|
|
/// <summary>Uses the real configuration factory so fake sessions cannot conceal SDK initialization failures.</summary>
|
|
public sealed class DefaultApplicationConfigurationFactoryTests
|
|
{
|
|
[Theory]
|
|
[InlineData(false)]
|
|
[InlineData(true)]
|
|
public async Task New_certificates_use_the_default_lifetime_and_existing_private_keys_are_reused(bool seedLongerCertificate)
|
|
{
|
|
var ct = TestContext.Current.CancellationToken;
|
|
var root = Path.Combine(Path.GetTempPath(), $"otopcua-certificate-lifetime-{Guid.NewGuid():N}");
|
|
var settings = new ConnectionSettings
|
|
{
|
|
EndpointUrl = "opc.tcp://localhost:4840", CertificateStorePath = root,
|
|
SecurityMode = SecurityMode.None, AutoAcceptCertificates = false,
|
|
};
|
|
var factory = new DefaultApplicationConfigurationFactory();
|
|
ApplicationConfiguration? current = null;
|
|
try
|
|
{
|
|
if (seedLongerCertificate)
|
|
{
|
|
var seed = await factory.CreateAsync(settings, ct);
|
|
await using var application = new ApplicationInstance(DefaultTelemetry.Create(_ => { })) { ApplicationConfiguration = seed };
|
|
(await application.CheckApplicationInstanceCertificatesAsync(true, lifeTimeInMonths: 60, ct)).ShouldBeTrue();
|
|
}
|
|
settings.SecurityMode = SecurityMode.SignAndEncrypt;
|
|
current = await factory.CreateAsync(settings, ct);
|
|
var certPath = Directory.GetFiles(Path.Combine(root, "own", "certs"), "*.der").ShouldHaveSingleItem();
|
|
var privatePath = Directory.GetFiles(Path.Combine(root, "own", "private"), "*.pfx").ShouldHaveSingleItem();
|
|
using var certificate = X509CertificateLoader.LoadCertificateFromFile(certPath);
|
|
using var privateCertificate = X509CertificateLoader.LoadPkcs12FromFile(privatePath, null, OperatingSystem.IsMacOS() ? X509KeyStorageFlags.DefaultKeySet : X509KeyStorageFlags.EphemeralKeySet);
|
|
privateCertificate.HasPrivateKey.ShouldBeTrue();
|
|
certificate.Thumbprint.ShouldBe(privateCertificate.Thumbprint);
|
|
using var rsa = certificate.GetRSAPublicKey();
|
|
rsa.ShouldNotBeNull().KeySize.ShouldBeGreaterThanOrEqualTo(2048);
|
|
if (seedLongerCertificate)
|
|
certificate.NotAfter.ToUniversalTime().ShouldBeGreaterThan(DateTime.UtcNow.AddYears(4));
|
|
else
|
|
{
|
|
certificate.NotAfter.ToUniversalTime().ShouldBeGreaterThan(DateTime.UtcNow.AddMonths(CertificateFactory.DefaultLifeTime - 1));
|
|
certificate.NotAfter.ToUniversalTime().ShouldBeLessThan(DateTime.UtcNow.AddMonths(CertificateFactory.DefaultLifeTime).AddDays(2));
|
|
}
|
|
var originalKeyHash = SHA256.HashData(await File.ReadAllBytesAsync(privatePath, ct));
|
|
(current.CertificateManager as IDisposable)?.Dispose(); current = null;
|
|
current = await factory.CreateAsync(settings, ct);
|
|
using var reused = X509CertificateLoader.LoadCertificateFromFile(certPath);
|
|
reused.Thumbprint.ShouldBe(certificate.Thumbprint);
|
|
SHA256.HashData(await File.ReadAllBytesAsync(privatePath, ct)).ShouldBe(originalKeyHash,
|
|
"reinitializing with a shorter creation default must not rotate an existing certificate or private key");
|
|
Directory.GetFiles(Path.Combine(root, "own", "certs"), "*.der").Length.ShouldBe(1);
|
|
}
|
|
finally
|
|
{
|
|
(current?.CertificateManager as IDisposable)?.Dispose();
|
|
if (Directory.Exists(root)) Directory.Delete(root, true);
|
|
}
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(SecurityMode.None)]
|
|
[InlineData(SecurityMode.SignAndEncrypt)]
|
|
public async Task Real_configuration_initializes_its_certificate_manager(SecurityMode mode)
|
|
{
|
|
var root = Path.Combine(Path.GetTempPath(), $"otopcua-real-config-{Guid.NewGuid():N}");
|
|
ApplicationConfiguration? config = null;
|
|
try
|
|
{
|
|
config = await new DefaultApplicationConfigurationFactory().CreateAsync(new ConnectionSettings
|
|
{
|
|
EndpointUrl = "opc.tcp://localhost:4840",
|
|
CertificateStorePath = root,
|
|
SecurityMode = mode,
|
|
AutoAcceptCertificates = false,
|
|
}, TestContext.Current.CancellationToken);
|
|
config.CertificateManager.ShouldNotBeNull();
|
|
config.SecurityConfiguration.AutoAcceptUntrustedCertificates.ShouldBeFalse();
|
|
if (mode != SecurityMode.None)
|
|
Directory.EnumerateFiles(Path.Combine(root, "own"), "*", SearchOption.AllDirectories).ShouldNotBeEmpty();
|
|
}
|
|
finally
|
|
{
|
|
(config?.CertificateManager as IDisposable)?.Dispose();
|
|
if (Directory.Exists(root)) Directory.Delete(root, recursive: true);
|
|
}
|
|
}
|
|
}
|