- 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
62 lines
2.1 KiB
C#
62 lines
2.1 KiB
C#
using System.Security.Cryptography;
|
|
using System.Text;
|
|
|
|
namespace NATS.Server.Auth;
|
|
|
|
/// <summary>
|
|
/// Authenticates a single username/password pair configured on the server.
|
|
/// Supports plain-text and bcrypt-hashed passwords.
|
|
/// Uses constant-time comparison for both username and password to prevent timing attacks.
|
|
/// Reference: golang/nats-server/server/auth.go checkClientAuth for single user.
|
|
/// </summary>
|
|
public sealed class SimpleUserPasswordAuthenticator : IAuthenticator
|
|
{
|
|
private readonly byte[] _expectedUsername;
|
|
private readonly string _serverPassword;
|
|
|
|
public SimpleUserPasswordAuthenticator(string username, string password)
|
|
{
|
|
_expectedUsername = Encoding.UTF8.GetBytes(username);
|
|
_serverPassword = password;
|
|
}
|
|
|
|
public AuthResult? Authenticate(ClientAuthContext context)
|
|
{
|
|
var clientUsername = context.Opts.Username;
|
|
if (string.IsNullOrEmpty(clientUsername))
|
|
return null;
|
|
|
|
var clientUsernameBytes = Encoding.UTF8.GetBytes(clientUsername);
|
|
if (!CryptographicOperations.FixedTimeEquals(clientUsernameBytes, _expectedUsername))
|
|
return null;
|
|
|
|
var clientPassword = context.Opts.Password ?? string.Empty;
|
|
|
|
if (!ComparePasswords(_serverPassword, clientPassword))
|
|
return null;
|
|
|
|
return new AuthResult { Identity = clientUsername };
|
|
}
|
|
|
|
private static bool ComparePasswords(string serverPassword, string clientPassword)
|
|
{
|
|
// Bcrypt hashes start with "$2" (e.g., $2a$, $2b$, $2y$)
|
|
if (serverPassword.StartsWith("$2"))
|
|
{
|
|
try
|
|
{
|
|
return BCrypt.Net.BCrypt.Verify(clientPassword, serverPassword);
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Plain-text: constant-time comparison to prevent timing attacks
|
|
var serverBytes = Encoding.UTF8.GetBytes(serverPassword);
|
|
var clientBytes = Encoding.UTF8.GetBytes(clientPassword);
|
|
return CryptographicOperations.FixedTimeEquals(serverBytes, clientBytes);
|
|
}
|
|
}
|