using System.Net.Security; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using Shouldly; using ZB.MOM.NatsNet.Server; using ZB.MOM.NatsNet.Server.Auth.CertificateIdentityProvider; namespace ZB.MOM.NatsNet.Server.Tests.Auth; public sealed class OcspPeerValidationTests : IDisposable { private readonly List _certs = []; [Fact] public void ParseOCSPPeer_ValidMap_ReturnsConfig() { Dictionary map = new() { ["verify"] = true, ["allowed_clockskew"] = 30.0, ["ca_timeout"] = 5.0, ["cache_ttl_when_next_update_unset"] = 120.0, ["warn_only"] = true, ["unknown_is_good"] = true, ["allow_when_ca_unreachable"] = true, }; var (config, err) = OcspHandler.ParseOCSPPeer(map); err.ShouldBeNull(); config.ShouldNotBeNull(); config.Verify.ShouldBeTrue(); config.ClockSkew.ShouldBe(30.0); config.Timeout.ShouldBe(5.0); config.TTLUnsetNextUpdate.ShouldBe(120.0); config.WarnOnly.ShouldBeTrue(); config.UnknownIsGood.ShouldBeTrue(); config.AllowWhenCAUnreachable.ShouldBeTrue(); } [Fact] public void PeerFromVerifiedChains_EmptyChains_ReturnsNull() { OcspHandler.PeerFromVerifiedChains([]).ShouldBeNull(); } [Fact] public void PeerFromVerifiedChains_NonEmptyChains_ReturnsFirstLeaf() { var cert = CreateSelfSignedCertificate("CN=peer-first"); var result = OcspHandler.PeerFromVerifiedChains([[cert]]); result.ShouldNotBeNull(); result!.Subject.ShouldBe(cert.Subject); } [Fact] public void PlugTLSOCSPPeer_ClientWithoutVerify_ReturnsError() { var server = NewServer(); var cert = CreateSelfSignedCertificate("CN=client-no-verify"); var config = new OcspTlsConfig { Kind = "client", TlsConfig = new SslServerAuthenticationOptions { ServerCertificate = cert }, TlsOptions = new TlsConfigOpts { Verify = false, OcspPeerConfig = new OcspPeerConfig { Verify = true }, }, Apply = _ => { }, }; var (_, plugged, err) = server.PlugTLSOCSPPeer(config); plugged.ShouldBeFalse(); err.ShouldNotBeNull(); err!.Message.ShouldContain("mTLS"); } [Fact] public void PlugTLSOCSPPeer_ClientWithVerify_ReturnsPlugged() { var server = NewServer(); var cert = CreateSelfSignedCertificate("CN=client-verify"); var config = new OcspTlsConfig { Kind = "client", TlsConfig = new SslServerAuthenticationOptions { ServerCertificate = cert }, TlsOptions = new TlsConfigOpts { Verify = true, OcspPeerConfig = new OcspPeerConfig { Verify = true }, }, Apply = _ => { }, }; var (tlsConfig, plugged, err) = server.PlugTLSOCSPPeer(config); err.ShouldBeNull(); plugged.ShouldBeTrue(); tlsConfig.ShouldNotBeNull(); tlsConfig!.RemoteCertificateValidationCallback.ShouldNotBeNull(); } [Fact] public void PlugTLSOCSPPeer_LeafSpoke_ReturnsPlugged() { var server = NewServer(); var cert = CreateSelfSignedCertificate("CN=leaf-spoke"); var config = new OcspTlsConfig { Kind = "leaf", IsLeafSpoke = true, TlsConfig = new SslServerAuthenticationOptions { ServerCertificate = cert }, TlsOptions = new TlsConfigOpts { Verify = true, OcspPeerConfig = new OcspPeerConfig { Verify = true }, }, Apply = _ => { }, }; var (_, plugged, err) = server.PlugTLSOCSPPeer(config); err.ShouldBeNull(); plugged.ShouldBeTrue(); } [Fact] public void TlsServerOCSPValid_SelfSignedChain_ReturnsTrue() { var server = NewServer(); var cert = CreateSelfSignedCertificate("CN=selfsigned"); var opts = new OcspPeerConfig { Verify = true }; var valid = server.TlsServerOCSPValid([[cert]], opts); valid.ShouldBeTrue(); } [Fact] public void TlsClientOCSPValid_EmptyChains_ReturnsFalse() { var server = NewServer(); var opts = new OcspPeerConfig { Verify = true }; var valid = server.TlsClientOCSPValid([], opts); valid.ShouldBeFalse(); } [Fact] public void CertOCSPGood_EmptyChainLink_ReturnsFalse() { var server = NewServer(); var (reason, good) = server.CertOCSPGood(new ChainLink(), new OcspPeerConfig()); good.ShouldBeFalse(); reason.ShouldContain("Empty chainlink"); } public void Dispose() { foreach (var cert in _certs) { cert.Dispose(); } } private NatsServer NewServer() { var (server, err) = NatsServer.NewServer(new ServerOptions()); err.ShouldBeNull(); return server!; } private X509Certificate2 CreateSelfSignedCertificate(string subject) { var request = new CertificateRequest( subject, RSA.Create(2048), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); request.CertificateExtensions.Add(new X509BasicConstraintsExtension(false, false, 0, true)); request.CertificateExtensions.Add(new X509SubjectKeyIdentifierExtension(request.PublicKey, false)); var cert = request.CreateSelfSigned(DateTimeOffset.UtcNow.AddDays(-1), DateTimeOffset.UtcNow.AddDays(30)); _certs.Add(cert); return cert; } }