using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using NATS.Client.Core; namespace NATS.E2E.Tests.Infrastructure; public sealed class TlsServerFixture : IAsyncLifetime { private NatsServerProcess _server = null!; private string _tempDir = null!; public int Port => _server.Port; public string CaCertPath { get; private set; } = null!; public async Task InitializeAsync() { _tempDir = Path.Combine(Path.GetTempPath(), $"nats-e2e-tls-{Guid.NewGuid():N}"); Directory.CreateDirectory(_tempDir); var caCertPath = Path.Combine(_tempDir, "ca.pem"); var serverCertPath = Path.Combine(_tempDir, "server-cert.pem"); var serverKeyPath = Path.Combine(_tempDir, "server-key.pem"); GenerateCertificates(caCertPath, serverCertPath, serverKeyPath); CaCertPath = caCertPath; var config = $$""" tls { cert_file: "{{serverCertPath}}" key_file: "{{serverKeyPath}}" ca_file: "{{caCertPath}}" } """; _server = NatsServerProcess.WithConfig(config); await _server.StartAsync(); } public async Task DisposeAsync() { await _server.DisposeAsync(); if (_tempDir is not null && Directory.Exists(_tempDir)) Directory.Delete(_tempDir, recursive: true); } public NatsConnection CreateTlsClient() { var opts = new NatsOpts { Url = $"nats://127.0.0.1:{Port}", TlsOpts = new NatsTlsOpts { Mode = TlsMode.Require, InsecureSkipVerify = true, }, }; return new NatsConnection(opts); } public NatsConnection CreatePlainClient() { return new NatsConnection(new NatsOpts { Url = $"nats://127.0.0.1:{Port}" }); } private static void GenerateCertificates(string caCertPath, string serverCertPath, string serverKeyPath) { // Generate CA key and self-signed certificate using var caKey = RSA.Create(2048); var caReq = new CertificateRequest( "CN=E2E Test CA", caKey, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); caReq.CertificateExtensions.Add( new X509BasicConstraintsExtension(certificateAuthority: true, hasPathLengthConstraint: false, pathLengthConstraint: 0, critical: true)); var now = DateTimeOffset.UtcNow; using var caCert = caReq.CreateSelfSigned(now.AddMinutes(-5), now.AddDays(1)); // Generate server key and certificate signed by CA using var serverKey = RSA.Create(2048); var serverReq = new CertificateRequest( "CN=localhost", serverKey, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); var sanBuilder = new SubjectAlternativeNameBuilder(); sanBuilder.AddIpAddress(System.Net.IPAddress.Loopback); sanBuilder.AddDnsName("localhost"); serverReq.CertificateExtensions.Add(sanBuilder.Build()); serverReq.CertificateExtensions.Add( new X509BasicConstraintsExtension(certificateAuthority: false, hasPathLengthConstraint: false, pathLengthConstraint: 0, critical: false)); using var serverCert = serverReq.Create(caCert, now.AddMinutes(-5), now.AddDays(1), [1, 2, 3, 4]); // Export CA cert to PEM File.WriteAllText(caCertPath, caCert.ExportCertificatePem()); // Export server cert to PEM File.WriteAllText(serverCertPath, serverCert.ExportCertificatePem()); // Export server private key to PEM File.WriteAllText(serverKeyPath, serverKey.ExportRSAPrivateKeyPem()); } } [CollectionDefinition("E2E-TLS")] public class TlsCollection : ICollectionFixture;