From 5693911e010ed6192d3803ac616a04a0bdfd8ee2 Mon Sep 17 00:00:00 2001 From: Albert S Date: Fri, 26 Mar 2021 22:48:26 +0100 Subject: [PATCH] Introduce Authenticator: Centralizes Authentication/password check logic --- authenticator.cpp | 108 ++++++++++++++++++++++++++++++++++++++++++++++ authenticator.h | 30 +++++++++++++ 2 files changed, 138 insertions(+) create mode 100644 authenticator.cpp create mode 100644 authenticator.h diff --git a/authenticator.cpp b/authenticator.cpp new file mode 100644 index 0000000..b742710 --- /dev/null +++ b/authenticator.cpp @@ -0,0 +1,108 @@ +#include +#include +#include "utils.h" +#include "authenticator.h" +#include "logger.h" +struct LoginFail +{ + std::mutex mutex; + std::atomic count; + time_t lastfail; +}; + +static std::map loginFails; + +Authenticator::Authenticator(UserDao &userDao) +{ + this->userDao = &userDao; +} + +// TODO: make failure counter configurable +bool Authenticator::isBanned(std::string ip) +{ + if(utils::hasKey(loginFails, ip)) + { + LoginFail &fl = loginFails[ip]; + std::lock_guard lock(fl.mutex); + return fl.count > 5 && (time(nullptr) - fl.lastfail) < 1200; + } + return false; +} + +void Authenticator::incFailureCount(std::string ip) +{ + LoginFail &fl = loginFails[ip]; + fl.count += 1; + fl.lastfail = time(nullptr); +} + +std::vector Authenticator::pbkdf5(std::string password, const std::vector &salt) +{ + unsigned char hash[32]; + const EVP_MD *sha256 = EVP_sha256(); + const unsigned char *rawsalt = reinterpret_cast(salt.data()); + int ret = PKCS5_PBKDF2_HMAC(password.c_str(), password.size(), rawsalt, salt.size(), 300000, sha256, sizeof(hash), hash); + if(ret != 1) + { + Logger::error() << "Authenticator: pbkdf5: Failed to create hash"; + return { }; + } + std::vector result; + + for(size_t i = 0; i < sizeof(hash); i++) + { + + result.push_back(static_cast(hash[i])); + } + + return result; +} + +std::variant Authenticator::authenticate(std::string username, std::string password) +{ + std::optional user = userDao->find(username); + if(user) + { + if(user->enabled) + { + auto hashresult = pbkdf5(password, user.value().salt); + if(hashresult.size() == 0) + { + return AuthenticationError::GeneralError; + } + // TODO: timing attack (even though practical relevancy questionable) + if(hashresult == user.value().password) + { + return user.value(); + } + return AuthenticationError::PasswordNotMatch; + } + return AuthenticationError::UserDisabled; + } + return AuthenticationError::UserNotFound; +} + +std::variant Authenticator::authenticate(std::string username, std::string password, + std::string ip) +{ + if(isBanned(ip)) + { + return AuthenticationError::BannedIP; + } + std::variant authresult = authenticate(username, password); + if(std::holds_alternative(authresult)) + { + AuthenticationError error = std::get(authresult); + if(error == AuthenticationError::PasswordNotMatch) + { + incFailureCount(ip); + } + return error; + } + return std::get(authresult); +} + +std::vector Authenticator::hash(std::string password, const std::vector &salt) +{ + return this->pbkdf5(password, salt); +} diff --git a/authenticator.h b/authenticator.h new file mode 100644 index 0000000..b1a450c --- /dev/null +++ b/authenticator.h @@ -0,0 +1,30 @@ +#ifndef AUTHENTICATOR_H +#define AUTHENTICATOR_H +#include +#include "database/userdao.h" + +enum AuthenticationError +{ + UserNotFound, + UserDisabled, + PasswordNotMatch, + BannedIP, + GeneralError +}; + +class Authenticator +{ + private: + UserDao *userDao; + bool isBanned(std::string ip); + void incFailureCount(std::string ip); + std::vector pbkdf5(std::string password, const std::vector &salt); + + public: + Authenticator(UserDao &userDao); + std::variant authenticate(std::string username, std::string password); + std::variant authenticate(std::string username, std::string password, std::string ip); + std::vector hash(std::string password, const std::vector &salt); +}; + +#endif // AUTHENTICATOR_H