auth: add sodium pwhash authentication

This commit is contained in:
Maximilian Seidler 2024-12-16 15:59:39 +01:00
parent d12b4a7fba
commit 3f20cc2679
7 changed files with 177 additions and 0 deletions

View file

@ -58,6 +58,7 @@ pkg_check_modules(
gbm gbm
hyprutils>=0.2.6 hyprutils>=0.2.6
sdbus-c++>=2.0.0 sdbus-c++>=2.0.0
libsodium
hyprgraphics) hyprgraphics)
file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp") file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp")

View file

@ -8,6 +8,7 @@
libdrm, libdrm,
libGL, libGL,
libjpeg, libjpeg,
libsodium,
libwebp, libwebp,
libxkbcommon, libxkbcommon,
mesa, mesa,
@ -38,6 +39,7 @@ stdenv.mkDerivation {
buildInputs = [ buildInputs = [
cairo cairo
file file
libsodium
libdrm libdrm
libGL libGL
libjpeg libjpeg

View file

@ -1,6 +1,7 @@
#include "Auth.hpp" #include "Auth.hpp"
#include "Pam.hpp" #include "Pam.hpp"
#include "Fingerprint.hpp" #include "Fingerprint.hpp"
#include "SodiumPWHash.hpp"
#include "../config/ConfigManager.hpp" #include "../config/ConfigManager.hpp"
#include "../core/hyprlock.hpp" #include "../core/hyprlock.hpp"
#include "src/helpers/Log.hpp" #include "src/helpers/Log.hpp"
@ -15,7 +16,11 @@ CAuth::CAuth() {
static auto* const PENABLEFINGERPRINT = (Hyprlang::INT* const*)g_pConfigManager->getValuePtr("auth:fingerprint:enabled"); static auto* const PENABLEFINGERPRINT = (Hyprlang::INT* const*)g_pConfigManager->getValuePtr("auth:fingerprint:enabled");
if (**PENABLEFINGERPRINT) if (**PENABLEFINGERPRINT)
m_vImpls.push_back(std::make_shared<CFingerprint>()); m_vImpls.push_back(std::make_shared<CFingerprint>());
static auto* const PENABLESODIUM = (Hyprlang::INT* const*)g_pConfigManager->getValuePtr("auth:sodium:enabled");
if (**PENABLESODIUM)
m_vImpls.push_back(std::make_shared<CSodiumPWHash>());
RASSERT(!(**PENABLEPAM && **PENABLESODIUM), "Pam and sodium hash authentication are mutually exclusive! Please enable one or the other.");
RASSERT(!m_vImpls.empty(), "At least one authentication method must be enabled!"); RASSERT(!m_vImpls.empty(), "At least one authentication method must be enabled!");
} }

View file

@ -7,6 +7,7 @@
enum eAuthImplementations { enum eAuthImplementations {
AUTH_IMPL_PAM = 0, AUTH_IMPL_PAM = 0,
AUTH_IMPL_FINGERPRINT = 1, AUTH_IMPL_FINGERPRINT = 1,
AUTH_IMPL_SODIUM = 2,
}; };
class IAuthImplementation { class IAuthImplementation {

124
src/auth/SodiumPWHash.cpp Normal file
View file

@ -0,0 +1,124 @@
#include "SodiumPWHash.hpp"
#include "../helpers/Log.hpp"
#include "../core/hyprlock.hpp"
#include <filesystem>
#include <hyprutils/path/Path.hpp>
#include <sodium.h>
static std::string getSecretsConfigPath() {
static const auto [PWHASHPATH, DOTDIR] = Hyprutils::Path::findConfig("hyprlock_pwhash");
(void)DOTDIR;
RASSERT(PWHASHPATH.has_value(), "[SodiumAuth] Failed to find hyprlock_pwhash.conf. Please use \"hyprlock-setpwhash\" to generate it!");
// check permissions
using std::filesystem::perms;
const auto PERMS = std::filesystem::status(PWHASHPATH.value()).permissions();
if ((PERMS & perms::group_read) != perms::none || (PERMS & perms::group_write) != perms::none || (PERMS & perms::others_read) != perms::none ||
(PERMS & perms::others_write) != perms::none) {
RASSERT(false, "[SodiumAuth] hyprlock_pwhash.conf has insecure permissions");
}
return PWHASHPATH.value();
}
void* const* CSodiumPWHash::getConfigValuePtr(const std::string& name) {
return m_config.getConfigValuePtr(name.c_str())->getDataStaticPtr();
}
CSodiumPWHash::CSodiumPWHash() : m_config(getSecretsConfigPath().c_str(), {}) {
m_config.addConfigValue("pw_hash", Hyprlang::STRING{""});
m_config.commence();
auto result = m_config.parse();
if (result.error)
Debug::log(ERR, "Config has errors:\n{}\nProceeding ignoring faulty entries", result.getError());
m_checkerThread = std::thread([this]() { checkerLoop(); });
}
CSodiumPWHash::~CSodiumPWHash() {
;
}
void CSodiumPWHash::init() {
RASSERT(sodium_init() >= 0, "Failed to initialize libsodium");
}
void CSodiumPWHash::handleInput(const std::string& input) {
std::lock_guard<std::mutex> lk(m_sCheckerState.requestMutex);
m_sCheckerState.input = input;
m_sCheckerState.requested = true;
m_sCheckerState.requestCV.notify_all();
}
bool CSodiumPWHash::checkWaiting() {
return m_sCheckerState.requested;
}
std::optional<std::string> CSodiumPWHash::getLastFailText() {
return m_sCheckerState.failText.empty() ? std::nullopt : std::optional<std::string>(m_sCheckerState.failText);
}
std::optional<std::string> CSodiumPWHash::getLastPrompt() {
return "Password: ";
}
void CSodiumPWHash::terminate() {
m_sCheckerState.requestCV.notify_all();
if (m_checkerThread.joinable())
m_checkerThread.join();
}
void CSodiumPWHash::rehash(std::string& input) {
const auto CONFIGPATH = getSecretsConfigPath();
char hash[crypto_pwhash_STRBYTES];
if (crypto_pwhash_str(hash, input.c_str(), input.size(), crypto_pwhash_OPSLIMIT_MODERATE, crypto_pwhash_MEMLIMIT_MODERATE) != 0) {
Debug::log(ERR, "[Sodium] Failed to hash password");
return;
}
std::ofstream out(CONFIGPATH);
out << "hyprlock {\n pw_hash = " << hash << "\n}\n";
out.close();
// set perms to -rw-------
using std::filesystem::perms;
std::filesystem::permissions(CONFIGPATH, perms::owner_read | perms::owner_write);
}
void CSodiumPWHash::checkerLoop() {
static auto* const PPWHASH = (Hyprlang::STRING*)getConfigValuePtr("pw_hash");
const auto PWHASH = std::string(*PPWHASH);
const bool NEEDSREHASH = crypto_pwhash_str_needs_rehash(PWHASH.c_str(), crypto_pwhash_OPSLIMIT_MODERATE, crypto_pwhash_MEMLIMIT_MODERATE) != 0;
if (NEEDSREHASH)
Debug::log(WARN, "[Sodium] Password hash needs rehashing");
while (true) {
std::unique_lock<std::mutex> lk(m_sCheckerState.requestMutex);
m_sCheckerState.requestCV.wait(lk, [this]() { return m_sCheckerState.requested || g_pHyprlock->m_bTerminate; });
if (g_pHyprlock->isUnlocked())
return;
if (PWHASH.empty() || PWHASH.size() > crypto_pwhash_STRBYTES) {
m_sCheckerState.failText = "Invalid password hash";
Debug::log(ERR, "[SodiumAuth] Invalid password hash set in secrets.conf");
g_pAuth->enqueueFail();
} else if (crypto_pwhash_str_verify(PWHASH.c_str(), m_sCheckerState.input.c_str(), m_sCheckerState.input.length()) == 0) {
if (NEEDSREHASH)
rehash(m_sCheckerState.input);
g_pAuth->enqueueUnlock();
} else {
g_pAuth->enqueueFail();
m_sCheckerState.failText = "Failed to authenticate";
Debug::log(LOG, "[SodiumAuth] Failed to authenticate");
}
m_sCheckerState.input.clear();
m_sCheckerState.requested = false;
}
}

43
src/auth/SodiumPWHash.hpp Normal file
View file

@ -0,0 +1,43 @@
#pragma once
#include "Auth.hpp"
#include <condition_variable>
#include <optional>
#include <string>
#include <hyprlang.hpp>
#include <thread>
class CSodiumPWHash : public IAuthImplementation {
public:
CSodiumPWHash();
virtual ~CSodiumPWHash();
virtual eAuthImplementations getImplType() {
return AUTH_IMPL_SODIUM;
}
virtual void init();
virtual void handleInput(const std::string& input);
virtual bool checkWaiting();
virtual std::optional<std::string> getLastFailText();
virtual std::optional<std::string> getLastPrompt();
virtual void terminate();
private:
void* const* getConfigValuePtr(const std::string& name);
struct {
std::condition_variable requestCV;
std::string input;
bool requested = false;
std::mutex requestMutex;
std::string failText;
} m_sCheckerState;
std::thread m_checkerThread;
void checkerLoop();
void rehash(std::string& input);
Hyprlang::CConfig m_config;
};

View file

@ -178,6 +178,7 @@ void CConfigManager::init() {
m_config.addConfigValue("auth:fingerprint:enabled", Hyprlang::INT{0}); m_config.addConfigValue("auth:fingerprint:enabled", Hyprlang::INT{0});
m_config.addConfigValue("auth:fingerprint:ready_message", Hyprlang::STRING{"(Scan fingerprint to unlock)"}); m_config.addConfigValue("auth:fingerprint:ready_message", Hyprlang::STRING{"(Scan fingerprint to unlock)"});
m_config.addConfigValue("auth:fingerprint:present_message", Hyprlang::STRING{"Scanning fingerprint"}); m_config.addConfigValue("auth:fingerprint:present_message", Hyprlang::STRING{"Scanning fingerprint"});
m_config.addConfigValue("auth:sodium:enabled", Hyprlang::INT{1});
m_config.addSpecialCategory("background", Hyprlang::SSpecialCategoryOptions{.key = nullptr, .anonymousKeyBased = true}); m_config.addSpecialCategory("background", Hyprlang::SSpecialCategoryOptions{.key = nullptr, .anonymousKeyBased = true});
m_config.addSpecialConfigValue("background", "monitor", Hyprlang::STRING{""}); m_config.addSpecialConfigValue("background", "monitor", Hyprlang::STRING{""});