using System.Net.Security; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text.Json; using Shouldly; using ZB.MOM.NatsNet.Server; namespace ZB.MOM.NatsNet.Server.Tests; public sealed class NatsServerOcspTests : IDisposable { private readonly List _tempDirs = []; private readonly List _certs = []; [Fact] public void SetupOCSPStapleStoreDir_WithStoreDir_CreatesDirectory() { var dir = MakeTempDir(); var server = NewServer(new ServerOptions { StoreDir = dir, }); var err = server.SetupOCSPStapleStoreDir(); err.ShouldBeNull(); Directory.Exists(Path.Combine(dir, "ocsp")).ShouldBeTrue(); } [Fact] public void ConfigureOCSP_WithTlsConfig_ReturnsClientEntry() { var cert = CreateSelfSignedCertificate("CN=configure-ocsp"); var server = NewServer(new ServerOptions { TlsConfig = new SslServerAuthenticationOptions { ServerCertificate = cert }, }); var configs = server.ConfigureOCSP(); configs.Count.ShouldBe(1); configs[0].Kind.ShouldBe("client"); } [Fact] public void NewOCSPMonitor_OcspNever_ReturnsNoMonitor() { var cert = CreateSelfSignedCertificate("CN=monitor-never"); var server = NewServer(new ServerOptions { StoreDir = MakeTempDir(), OcspConfig = new OcspConfig { Mode = ZB.MOM.NatsNet.Server.OcspMode.Never }, }); var config = new OcspTlsConfig { Kind = "client", TlsConfig = new SslServerAuthenticationOptions { ServerCertificate = cert }, TlsOptions = null, Apply = _ => { }, }; var (_, monitor, err) = server.NewOCSPMonitor(config); err.ShouldBeNull(); monitor.ShouldBeNull(); } [Fact] public void EnableOCSP_WithAlwaysMode_AddsMonitor() { var cert = CreateSelfSignedCertificate("CN=enable-ocsp"); var storeDir = MakeTempDir(); WriteLocalOcspStatus(storeDir, cert); var server = NewServer(new ServerOptions { StoreDir = storeDir, OcspConfig = new OcspConfig { Mode = ZB.MOM.NatsNet.Server.OcspMode.Always, OverrideUrls = ["https://ocsp.example.test"], }, TlsConfig = new SslServerAuthenticationOptions { ServerCertificate = cert }, }); var err = server.EnableOCSP(); err.ShouldBeNull(); server.GetOcspMonitors().Length.ShouldBe(1); } [Fact] public void StartOCSPMonitoring_NoMonitors_DoesNotThrow() { var server = NewServer(new ServerOptions()); Should.NotThrow(() => server.StartOCSPMonitoring()); } [Fact] public void ReloadOCSP_WithConfiguredTls_ReplacesMonitors() { var cert = CreateSelfSignedCertificate("CN=reload-ocsp"); var storeDir = MakeTempDir(); WriteLocalOcspStatus(storeDir, cert); var server = NewServer(new ServerOptions { StoreDir = storeDir, OcspConfig = new OcspConfig { Mode = ZB.MOM.NatsNet.Server.OcspMode.Always, OverrideUrls = ["https://ocsp.example.test"], }, TlsConfig = new SslServerAuthenticationOptions { ServerCertificate = cert }, }); server.EnableOCSP().ShouldBeNull(); server.GetOcspMonitors().Length.ShouldBe(1); var err = server.ReloadOCSP(); err.ShouldBeNull(); server.GetOcspMonitors().Length.ShouldBe(1); } [Fact] public void HasOCSPStatusRequest_CertificateWithoutExtension_ReturnsFalse() { var cert = CreateSelfSignedCertificate("CN=no-status-request"); OcspHandler.HasOCSPStatusRequest(cert).ShouldBeFalse(); } public void Dispose() { foreach (var cert in _certs) { cert.Dispose(); } foreach (var dir in _tempDirs) { try { Directory.Delete(dir, recursive: true); } catch { } } } private NatsServer NewServer(ServerOptions options) { var (server, err) = NatsServer.NewServer(options); err.ShouldBeNull(); return server!; } private X509Certificate2 CreateSelfSignedCertificate(string subject) { var req = new CertificateRequest( subject, RSA.Create(2048), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); req.CertificateExtensions.Add(new X509BasicConstraintsExtension(true, false, 0, true)); req.CertificateExtensions.Add(new X509SubjectKeyIdentifierExtension(req.PublicKey, false)); var cert = req.CreateSelfSigned(DateTimeOffset.UtcNow.AddDays(-1), DateTimeOffset.UtcNow.AddDays(90)); _certs.Add(cert); return cert; } private string MakeTempDir() { var path = Path.Combine(Path.GetTempPath(), "nats-ocsp-" + Path.GetRandomFileName()); Directory.CreateDirectory(path); _tempDirs.Add(path); return path; } private static void WriteLocalOcspStatus(string storeDir, X509Certificate2 cert) { var key = Convert.ToHexString(SHA256.HashData(cert.RawData)).ToLowerInvariant(); var ocspDir = Path.Combine(storeDir, "ocsp"); Directory.CreateDirectory(ocspDir); var payload = JsonSerializer.SerializeToUtf8Bytes(new { Status = 0, ThisUpdate = DateTime.UtcNow.AddMinutes(-5), NextUpdate = DateTime.UtcNow.AddHours(6), }); File.WriteAllBytes(Path.Combine(ocspDir, key), payload); } }