Files
natsdotnet/src/NATS.Server/Auth/NKeyAuthenticator.cs
Joseph Doherty 6ebe791c6d feat: add authenticators, Account, and ClientPermissions (Tasks 3-7, 9)
- Account: per-account SubList and client tracking
- IAuthenticator interface, AuthResult, ClientAuthContext
- TokenAuthenticator: constant-time token comparison
- UserPasswordAuthenticator: multi-user with bcrypt/plain support
- SimpleUserPasswordAuthenticator: single user/pass config
- NKeyAuthenticator: Ed25519 nonce signature verification
- ClientPermissions: SubList-based publish/subscribe authorization
2026-02-22 22:41:45 -05:00

67 lines
2.1 KiB
C#

using NATS.NKeys;
namespace NATS.Server.Auth;
/// <summary>
/// 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.
/// </summary>
/// <remarks>
/// Reference: golang/nats-server/server/auth.go — checkNKeyAuth
/// </remarks>
public sealed class NKeyAuthenticator(IEnumerable<NKeyUser> nkeyUsers) : IAuthenticator
{
private readonly Dictionary<string, NKeyUser> _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,
};
}
}