#include "socks5/auth.h" #include "common/logging.h" namespace ovg::socks5 { namespace { constexpr const char *kMod = "auth"; } Authenticator::Authenticator(std::vector users, bool required) : users_(std::move(users)), required_(required) { // A fixed decoy, hashed exactly like a real credential, so the miss path // costs the same as the hit path. Built once because generating it per // attempt would itself be a timing signal. decoy_ = make_credential("\x00-nonexistent-\x00", "\x00-nonexistent-\x00"); if (required_ && users_.empty()) { LOG_WARN(kMod, "authentication is required but no credentials are loaded: " "every client will be rejected"); } } bool Authenticator::empty() const { std::lock_guard lk(mu_); return users_.empty(); } bool Authenticator::check(const std::string &user, const std::string &password) const { std::lock_guard lk(mu_); const Credential *found = nullptr; for (const auto &c : users_) { // Username comparison is not constant-time and does not need to be: the // name is not a secret, and the timing of a string compare over a // handful of entries is far below the noise of a network round trip. The // hash below is what must not vary. if (c.username == user) { found = &c; break; } } // Always exactly one verification, hit or miss. const bool ok = verify_credential(found != nullptr ? *found : decoy_, password); const bool result = ok && found != nullptr; if (result) { ++ok_; } else { ++bad_; } return result; } size_t Authenticator::replace(std::vector users) { std::lock_guard lk(mu_); users_ = std::move(users); LOG_INFO(kMod, "credential set replaced: {} user(s)", users_.size()); return users_.size(); } uint64_t Authenticator::successes() const { std::lock_guard lk(mu_); return ok_; } uint64_t Authenticator::failures() const { std::lock_guard lk(mu_); return bad_; } } // namespace ovg::socks5