// TLS certificate provider that supports atomic cert swapping for hot reload. // New connections get the current certificate; existing connections keep their original. // Reference: golang/nats-server/server/reload.go — tlsOption.Apply. using System.Net.Security; using System.Security.Cryptography.X509Certificates; namespace NATS.Server.Tls; /// /// Thread-safe provider for TLS certificates that supports atomic swapping /// during config reload. New connections retrieve the latest certificate via /// ; existing connections are unaffected. /// public sealed class TlsCertificateProvider : IDisposable { private volatile X509Certificate2? _currentCert; private volatile SslServerAuthenticationOptions? _currentSslOptions; private int _version; /// /// Creates a new provider and loads the initial certificate from the given paths. /// public TlsCertificateProvider(string certPath, string? keyPath) { _currentCert = TlsHelper.LoadCertificate(certPath, keyPath); } /// /// Creates a provider from a pre-loaded certificate (for testing). /// public TlsCertificateProvider(X509Certificate2 cert) { _currentCert = cert; } /// /// Returns the current certificate. This is called for each new TLS handshake /// so that new connections always get the latest certificate. /// public X509Certificate2? GetCurrentCertificate() => _currentCert; /// /// Atomically swaps the current certificate with a newly loaded one. /// Returns the old certificate (caller may dispose it after existing connections drain). /// public X509Certificate2? SwapCertificate(string certPath, string? keyPath) { var newCert = TlsHelper.LoadCertificate(certPath, keyPath); return SwapCertificate(newCert); } /// /// Atomically swaps the current certificate with the provided one. /// Returns the old certificate. /// public X509Certificate2? SwapCertificate(X509Certificate2 newCert) { var old = Interlocked.Exchange(ref _currentCert, newCert); Interlocked.Increment(ref _version); return old; } /// /// Returns the current SSL options, rebuilding them if the certificate has changed. /// public SslServerAuthenticationOptions? GetCurrentSslOptions() => _currentSslOptions; /// /// Atomically swaps the SSL server authentication options. /// Called after TLS config changes are detected during reload. /// public void SwapSslOptions(SslServerAuthenticationOptions newOptions) { Interlocked.Exchange(ref _currentSslOptions, newOptions); Interlocked.Increment(ref _version); } /// /// Monotonically increasing version number, incremented on each swap. /// Useful for tests to verify a reload occurred. /// public int Version => Volatile.Read(ref _version); public void Dispose() { _currentCert?.Dispose(); } }