Files
natsnet/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/NatsServerOcspTests.cs
2026-02-28 12:28:16 -05:00

190 lines
5.7 KiB
C#

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<string> _tempDirs = [];
private readonly List<X509Certificate2> _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);
}
}