using System.Security.Cryptography.X509Certificates; namespace NATS.Server.Auth; /// /// Authenticates clients by mapping TLS certificate subject DN to configured users. /// Corresponds to Go server/auth.go checkClientTLSCertSubject. /// public sealed class TlsMapAuthenticator : IAuthenticator { private readonly Dictionary _usersByDn; private readonly Dictionary _usersByCn; public TlsMapAuthenticator(IReadOnlyList users) { _usersByDn = new Dictionary(StringComparer.OrdinalIgnoreCase); _usersByCn = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (var user in users) { _usersByDn[user.Username] = user; _usersByCn[user.Username] = user; } } public AuthResult? Authenticate(ClientAuthContext context) { var cert = context.ClientCertificate; if (cert == null) return null; var dn = cert.SubjectName; var dnString = dn.Name; // RFC 2253 format // Try exact DN match first if (_usersByDn.TryGetValue(dnString, out var user)) return BuildResult(user); // Try CN extraction var cn = ExtractCn(dn); if (cn != null && _usersByCn.TryGetValue(cn, out user)) return BuildResult(user); return null; } private static string? ExtractCn(X500DistinguishedName dn) { var dnString = dn.Name; foreach (var rdn in dnString.Split(',', StringSplitOptions.TrimEntries)) { if (rdn.StartsWith("CN=", StringComparison.OrdinalIgnoreCase)) return rdn[3..]; } return null; } private static AuthResult BuildResult(User user) { return new AuthResult { Identity = user.Username, AccountName = user.Account, Permissions = user.Permissions, Expiry = user.ConnectionDeadline, }; } }