using System.Net.Security; using System.Security.Authentication; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; namespace NATS.Server.Tls; public static class TlsHelper { public static X509Certificate2 LoadCertificate(string certPath, string? keyPath) { if (keyPath != null) return X509Certificate2.CreateFromPemFile(certPath, keyPath); return X509CertificateLoader.LoadCertificateFromFile(certPath); } public static X509Certificate2Collection LoadCaCertificates(string caPath) { var collection = new X509Certificate2Collection(); collection.ImportFromPemFile(caPath); return collection; } public static SslServerAuthenticationOptions BuildServerAuthOptions(NatsOptions opts) { var cert = LoadCertificate(opts.TlsCert!, opts.TlsKey); var authOpts = new SslServerAuthenticationOptions { ServerCertificate = cert, EnabledSslProtocols = opts.TlsMinVersion, ClientCertificateRequired = opts.TlsVerify, }; if (opts.TlsVerify && opts.TlsCaCert != null) { var revocationMode = opts.OcspPeerVerify ? X509RevocationMode.Online : X509RevocationMode.NoCheck; var caCerts = LoadCaCertificates(opts.TlsCaCert); authOpts.RemoteCertificateValidationCallback = (_, cert, chain, errors) => { if (cert == null) return false; using var chain2 = new X509Chain(); chain2.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust; foreach (var ca in caCerts) chain2.ChainPolicy.CustomTrustStore.Add(ca); 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); }; } return authOpts; } /// /// Builds an for OCSP stapling. /// Returns null when TLS is not configured or OCSP mode is Never. /// When is false the runtime will contact the /// certificate's OCSP responder to obtain a fresh stapled response. /// 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(); var hash = SHA256.HashData(spki); return Convert.ToHexStringLower(hash); } public static bool MatchesPinnedCert(X509Certificate2 cert, HashSet pinned) { var hash = GetCertificateHash(cert); return pinned.Contains(hash); } }