using System.Diagnostics; using System.Collections; using System.Reflection; using System.Runtime; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; using System.Text.Json; using Opc.Ua; using Opc.Ua.Configuration; using Opc.Ua.Security.Certificates; [System.Runtime.Versioning.SupportedOSPlatform("macos")] internal static class ImportSeed { const string Uri = "urn:localhost:NativeRetention.Control"; static string Hash(Certificate cert) => Convert.ToHexString(SHA256.HashData(cert.RawData)).ToLowerInvariant(); static void Require(bool yes, string message) { if (!yes) throw new InvalidOperationException(message); } static ApplicationConfiguration Configuration(string pki, ITelemetryContext telemetry) => new(telemetry) { ApplicationName = "NativeRetention.Control", ApplicationUri = Uri, ApplicationType = ApplicationType.Client, SecurityConfiguration = new SecurityConfiguration { ApplicationCertificate = new CertificateIdentifier { StoreType = CertificateStoreType.Directory, StorePath = Path.Combine(pki, "own"), SubjectName = "CN=NativeRetention.Control" }, TrustedPeerCertificates = new CertificateTrustList { StoreType = CertificateStoreType.Directory, StorePath = Path.Combine(pki, "trusted") }, TrustedIssuerCertificates = new CertificateTrustList { StoreType = CertificateStoreType.Directory, StorePath = Path.Combine(pki, "issuers") }, RejectedCertificateStore = new CertificateTrustList { StoreType = CertificateStoreType.Directory, StorePath = Path.Combine(pki, "rejected") }, AutoAcceptUntrustedCertificates = false, }, ClientConfiguration = new ClientConfiguration { DefaultSessionTimeout = 15000 }, }; static async Task Open(string pki, ITelemetryContext telemetry, CancellationToken ct) { var app = new ApplicationInstance(telemetry) { ApplicationType = ApplicationType.Client, ApplicationName = "NativeRetention.Control", ApplicationConfiguration = Configuration(pki, telemetry) }; try { await app.ApplicationConfiguration!.ValidateAsync(ApplicationType.Client, ct); Require(await app.CheckApplicationInstanceCertificatesAsync(true, null, ct), "Normal application certificate check failed"); return app; } catch { await app.DisposeAsync(); throw; } } static bool ManagerDisposed(ICertificateManager manager) { Require(manager.GetType().FullName == "Opc.Ua.CertificateManager" && manager.GetType().Assembly.GetName().Version?.ToString() == "2.0.1.0", "Exact SDK diagnostic layout changed"); var field = manager.GetType().GetField("m_disposed", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.DeclaredOnly); Require(field?.FieldType == typeof(bool), "Exact SDK disposal bool missing"); return (bool)field!.GetValue(manager)!; } static void PrivateFile(string path, byte[] bytes) { using var stream = new FileStream(path, new FileStreamOptions { Mode = FileMode.CreateNew, Access = FileAccess.Write, UnixCreateMode = UnixFileMode.UserRead | UnixFileMode.UserWrite }); stream.Write(bytes); } public static async Task Main(string[] args) { using var deadline = new CancellationTokenSource(TimeSpan.FromSeconds(60)); ApplicationInstance? app = null; try { Require(OperatingSystem.IsMacOS() && Environment.Version.ToString() == "10.0.14", "Expected matched macOS/.NET10.0.14 seed"); var root = Path.GetFullPath(args.Single()); Require(Directory.Exists(root) && (File.GetUnixFileMode(root) & (UnixFileMode)63) == 0, "Expected private fresh seed directory"); app = await Open(Path.Combine(root, "pki"), DefaultTelemetry.Create(_ => { }), deadline.Token); var manager = app.ApplicationConfiguration!.CertificateManager; string leaf; using (var entry = manager.AcquireApplicationCertificateBySecurityPolicy(SecurityPolicies.Basic256Sha256)) { Require(entry is not null && entry.Certificate.HasPrivateKey, "Expected SDK-generated private certificate"); var certificate = entry!.Certificate; leaf = Hash(certificate); using var rsa = certificate.GetRSAPrivateKey(); using var publicKey = certificate.GetRSAPublicKey(); Require(rsa is not null && publicKey is not null, "Expected normal SDK RSA certificate"); var challenge = "native-import-seed-diagnostic"u8.ToArray(); var signature = rsa!.SignData(challenge, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); Require(publicKey!.VerifyData(challenge, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1), "Seed key mismatch"); var privateKey = rsa.ExportRSAPrivateKey(); try { PrivateFile(Path.Combine(root, "leaf.der"), certificate.RawData); PrivateFile(Path.Combine(root, "private.pkcs1"), privateKey); } finally { CryptographicOperations.ZeroMemory(privateKey); } } await app.DisposeAsync().AsTask().WaitAsync(deadline.Token); app = null; Require(ManagerDisposed(manager) && Certificate.InstancesLeaked == 0, "Seed SDK ownership remains"); Console.WriteLine(JsonSerializer.Serialize(new { kind = "seed", leafSha256 = leaf, runtime = Environment.Version.ToString(), sdkVersion = typeof(Certificate).Assembly.GetName().Version!.ToString(), managerDisposed = true, certificateCoresOutstanding = Certificate.InstancesLeaked, keyFormat = "PKCS1-private-RSA", privateFileMode = "0600", cryptographyRuntime = new { informationalVersion = typeof(X509Certificate2).Assembly.GetCustomAttribute()?.InformationalVersion, sha256 = Convert.ToHexString(SHA256.HashData(File.ReadAllBytes(typeof(X509Certificate2).Assembly.Location))).ToLowerInvariant() } })); return 0; } catch(Exception error) { Console.WriteLine(JsonSerializer.Serialize(new {kind="failed", errorType=error.GetType().FullName, message=error.Message}));return 1; } finally { if(app is not null) await app.DisposeAsync(); } } }