using NATS.NKeys; namespace NATS.Server.Auth; /// /// Authenticates clients using NKey (Ed25519) public-key signature verification. /// The server sends a random nonce in the INFO message. The client signs the nonce /// with their private key and sends the public key + base64-encoded signature in CONNECT. /// The server verifies the signature against the registered NKey users. /// /// /// Reference: golang/nats-server/server/auth.go — checkNKeyAuth /// public sealed class NKeyAuthenticator(IEnumerable nkeyUsers) : IAuthenticator { private readonly Dictionary _nkeys = nkeyUsers.ToDictionary( u => u.Nkey, u => u, StringComparer.Ordinal); public AuthResult? Authenticate(ClientAuthContext context) { var clientNkey = context.Opts.Nkey; if (string.IsNullOrEmpty(clientNkey)) return null; if (!_nkeys.TryGetValue(clientNkey, out var nkeyUser)) return null; var clientSig = context.Opts.Sig; if (string.IsNullOrEmpty(clientSig)) return null; try { // Decode base64 signature (handle both standard and URL-safe base64) byte[] sigBytes; try { sigBytes = Convert.FromBase64String(clientSig); } catch (FormatException) { // Try URL-safe base64 by converting to standard base64 var padded = clientSig.Replace('-', '+').Replace('_', '/'); padded = padded.PadRight(padded.Length + (4 - padded.Length % 4) % 4, '='); sigBytes = Convert.FromBase64String(padded); } var kp = KeyPair.FromPublicKey(clientNkey); if (!kp.Verify(context.Nonce, sigBytes)) return null; } catch { return null; } return new AuthResult { Identity = clientNkey, AccountName = nkeyUser.Account, Permissions = nkeyUser.Permissions, }; } }