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(); + } +}