From f316e6e86e5ac4c6dbfb694b4870630e0dde55e9 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Mon, 23 Feb 2026 04:23:14 -0500 Subject: [PATCH] feat: add OcspMode enum, OcspConfig class, and wire into NatsOptions Introduces NATS.Server.Tls.OcspMode (Auto/Always/Must/Never matching Go ocsp.go constants) and OcspConfig with Mode and OverrideUrls. Adds OcspConfig? and OcspPeerVerify to NatsOptions for stapling configuration and peer certificate revocation checking. Covered by 12 new unit tests. --- src/NATS.Server/NatsOptions.cs | 5 ++ src/NATS.Server/Tls/OcspConfig.cs | 20 +++++ tests/NATS.Server.Tests/OcspConfigTests.cs | 90 ++++++++++++++++++++++ 3 files changed, 115 insertions(+) create mode 100644 src/NATS.Server/Tls/OcspConfig.cs create mode 100644 tests/NATS.Server.Tests/OcspConfigTests.cs diff --git a/src/NATS.Server/NatsOptions.cs b/src/NATS.Server/NatsOptions.cs index 8a9b56d..efc50cd 100644 --- a/src/NATS.Server/NatsOptions.cs +++ b/src/NATS.Server/NatsOptions.cs @@ -1,5 +1,6 @@ using System.Security.Authentication; using NATS.Server.Auth; +using NATS.Server.Tls; namespace NATS.Server; @@ -85,5 +86,9 @@ public sealed class NatsOptions public HashSet? TlsPinnedCerts { get; set; } public SslProtocols TlsMinVersion { get; set; } = SslProtocols.Tls12; + // OCSP stapling and peer verification + public OcspConfig? OcspConfig { get; set; } + public bool OcspPeerVerify { get; set; } + public bool HasTls => TlsCert != null && TlsKey != null; } diff --git a/src/NATS.Server/Tls/OcspConfig.cs b/src/NATS.Server/Tls/OcspConfig.cs new file mode 100644 index 0000000..0634522 --- /dev/null +++ b/src/NATS.Server/Tls/OcspConfig.cs @@ -0,0 +1,20 @@ +namespace NATS.Server.Tls; + +// OcspMode mirrors the OCSPMode constants from the Go reference implementation (ocsp.go). +// Auto — staple only if the certificate contains the status_request TLS extension. +// Always — always attempt stapling; warn but continue if the OCSP response cannot be obtained. +// Must — stapling is mandatory; fail server startup if the OCSP response cannot be obtained. +// Never — never attempt stapling regardless of certificate extensions. +public enum OcspMode +{ + Auto = 0, + Always = 1, + Must = 2, + Never = 3, +} + +public sealed class OcspConfig +{ + public OcspMode Mode { get; init; } = OcspMode.Auto; + public string[] OverrideUrls { get; init; } = []; +} diff --git a/tests/NATS.Server.Tests/OcspConfigTests.cs b/tests/NATS.Server.Tests/OcspConfigTests.cs new file mode 100644 index 0000000..42122ac --- /dev/null +++ b/tests/NATS.Server.Tests/OcspConfigTests.cs @@ -0,0 +1,90 @@ +using NATS.Server.Tls; + +namespace NATS.Server.Tests; + +public class OcspConfigTests +{ + [Fact] + public void OcspMode_Auto_has_value_zero() + { + ((int)OcspMode.Auto).ShouldBe(0); + } + + [Fact] + public void OcspMode_Always_has_value_one() + { + ((int)OcspMode.Always).ShouldBe(1); + } + + [Fact] + public void OcspMode_Must_has_value_two() + { + ((int)OcspMode.Must).ShouldBe(2); + } + + [Fact] + public void OcspMode_Never_has_value_three() + { + ((int)OcspMode.Never).ShouldBe(3); + } + + [Fact] + public void OcspConfig_default_mode_is_Auto() + { + var config = new OcspConfig(); + config.Mode.ShouldBe(OcspMode.Auto); + } + + [Fact] + public void OcspConfig_OverrideUrls_defaults_to_empty_array() + { + var config = new OcspConfig(); + config.OverrideUrls.ShouldNotBeNull(); + config.OverrideUrls.ShouldBeEmpty(); + } + + [Fact] + public void OcspConfig_Mode_can_be_set_via_init() + { + var config = new OcspConfig { Mode = OcspMode.Must }; + config.Mode.ShouldBe(OcspMode.Must); + } + + [Fact] + public void OcspConfig_OverrideUrls_can_be_set_via_init() + { + var urls = new[] { "http://ocsp.example.com", "http://backup.example.com" }; + var config = new OcspConfig { OverrideUrls = urls }; + config.OverrideUrls.ShouldBe(urls); + } + + [Fact] + public void NatsOptions_OcspConfig_defaults_to_null() + { + var opts = new NatsOptions(); + opts.OcspConfig.ShouldBeNull(); + } + + [Fact] + public void NatsOptions_OcspPeerVerify_defaults_to_false() + { + var opts = new NatsOptions(); + opts.OcspPeerVerify.ShouldBeFalse(); + } + + [Fact] + public void NatsOptions_OcspConfig_can_be_assigned() + { + var config = new OcspConfig { Mode = OcspMode.Always }; + var opts = new NatsOptions { OcspConfig = config }; + opts.OcspConfig.ShouldNotBeNull(); + opts.OcspConfig!.Mode.ShouldBe(OcspMode.Always); + } + + [Fact] + public void NatsOptions_OcspPeerVerify_can_be_set_to_true() + { + var opts = new NatsOptions { OcspPeerVerify = true }; + opts.OcspPeerVerify.ShouldBeTrue(); + } +}