feat: add OCSP peer verification and stapling support

Wire OcspPeerVerify into the client-cert validation callback in
TlsHelper so revocation is checked online when the flag is set.
Add TlsHelper.BuildCertificateContext to build an
SslStreamCertificateContext with offline:false, enabling the runtime
to fetch and staple OCSP responses during the TLS handshake.
NatsServer applies the context at startup when OcspConfig.Mode is not
Never. Ten unit tests cover the config defaults, mode ordinals, and
the null-return invariants of BuildCertificateContext.
This commit is contained in:
Joseph Doherty
2026-02-23 04:38:01 -05:00
parent d69308600a
commit 39a1383de2
3 changed files with 146 additions and 1 deletions

View File

@@ -33,6 +33,10 @@ public static class TlsHelper
if (opts.TlsVerify && opts.TlsCaCert != null)
{
var revocationMode = opts.OcspPeerVerify
? X509RevocationMode.Online
: X509RevocationMode.NoCheck;
var caCerts = LoadCaCertificates(opts.TlsCaCert);
authOpts.RemoteCertificateValidationCallback = (_, cert, chain, errors) =>
{
@@ -41,7 +45,19 @@ public static class TlsHelper
chain2.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
foreach (var ca in caCerts)
chain2.ChainPolicy.CustomTrustStore.Add(ca);
chain2.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
chain2.ChainPolicy.RevocationMode = revocationMode;
var cert2 = cert as X509Certificate2 ?? X509CertificateLoader.LoadCertificate(cert.GetRawCertData());
return chain2.Build(cert2);
};
}
else if (opts.OcspPeerVerify)
{
// No custom CA — still enable online revocation checking against the system store
authOpts.RemoteCertificateValidationCallback = (_, cert, chain, errors) =>
{
if (cert == null) return false;
using var chain2 = new X509Chain();
chain2.ChainPolicy.RevocationMode = X509RevocationMode.Online;
var cert2 = cert as X509Certificate2 ?? X509CertificateLoader.LoadCertificate(cert.GetRawCertData());
return chain2.Build(cert2);
};
@@ -50,6 +66,25 @@ public static class TlsHelper
return authOpts;
}
/// <summary>
/// Builds an <see cref="SslStreamCertificateContext"/> for OCSP stapling.
/// Returns null when TLS is not configured or OCSP mode is Never.
/// When <paramref name="offline"/> is false the runtime will contact the
/// certificate's OCSP responder to obtain a fresh stapled response.
/// </summary>
public static SslStreamCertificateContext? BuildCertificateContext(NatsOptions opts, bool offline = false)
{
if (!opts.HasTls) return null;
if (opts.OcspConfig is null || opts.OcspConfig.Mode == OcspMode.Never) return null;
var cert = LoadCertificate(opts.TlsCert!, opts.TlsKey);
var chain = new X509Certificate2Collection();
if (!string.IsNullOrEmpty(opts.TlsCaCert))
chain.ImportFromPemFile(opts.TlsCaCert);
return SslStreamCertificateContext.Create(cert, chain, offline: offline);
}
public static string GetCertificateHash(X509Certificate2 cert)
{
var spki = cert.PublicKey.ExportSubjectPublicKeyInfo();