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