mirror of
https://github.com/hyprwm/hyprlock.git
synced 2026-05-19 19:28:08 +02:00
Merge 595b210e94 into b5a8374b01
This commit is contained in:
commit
bb8b14ae7f
8 changed files with 413 additions and 3 deletions
|
|
@ -88,6 +88,7 @@ pkg_check_modules(
|
|||
gbm
|
||||
hyprutils>=0.11.0
|
||||
sdbus-c++>=2.0.0
|
||||
libsodium
|
||||
hyprgraphics>=0.1.6)
|
||||
find_library(PAM_FOUND NAMES pam libpam)
|
||||
if(PAM_FOUND)
|
||||
|
|
@ -153,8 +154,12 @@ protocolnew("stable/viewporter" "viewporter" false)
|
|||
protocolnew("staging/cursor-shape" "cursor-shape-v1" false)
|
||||
protocolnew("stable/tablet" "tablet-v2" false)
|
||||
|
||||
# hyprlock-pwhash
|
||||
add_executable(hyprlock-pwhash "setpwhash/main.cpp")
|
||||
target_link_libraries(hyprlock-pwhash PRIVATE sodium hyprutils)
|
||||
|
||||
# Installation
|
||||
install(TARGETS hyprlock)
|
||||
install(TARGETS hyprlock hyprlock-pwhash)
|
||||
|
||||
install(FILES ${CMAKE_SOURCE_DIR}/pam/hyprlock
|
||||
DESTINATION ${CMAKE_INSTALL_FULL_SYSCONFDIR}/pam.d)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
cairo,
|
||||
libdrm,
|
||||
libGL,
|
||||
libsodium,
|
||||
libxkbcommon,
|
||||
libgbm,
|
||||
hyprgraphics,
|
||||
|
|
@ -39,6 +40,7 @@ stdenv.mkDerivation {
|
|||
cairo
|
||||
libdrm
|
||||
libGL
|
||||
libsodium
|
||||
libxkbcommon
|
||||
libgbm
|
||||
hyprgraphics
|
||||
|
|
|
|||
192
setpwhash/main.cpp
Normal file
192
setpwhash/main.cpp
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
#include "../src/helpers/Log.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
#include <hyprutils/path/Path.hpp>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <sodium.h>
|
||||
#include <string>
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
#include <print>
|
||||
|
||||
using std::filesystem::perms;
|
||||
|
||||
static void setStdinEcho(bool enable = true) {
|
||||
struct termios tty;
|
||||
tcgetattr(STDIN_FILENO, &tty);
|
||||
if (!enable)
|
||||
tty.c_lflag &= ~ECHO;
|
||||
else
|
||||
tty.c_lflag |= ECHO;
|
||||
RASSERT(tcsetattr(STDIN_FILENO, TCSANOW, &tty) == 0, "Failed to set terminal attributes");
|
||||
}
|
||||
|
||||
// returns the first none-whitespace char
|
||||
static int getChoice() {
|
||||
std::string input;
|
||||
std::getline(std::cin, input);
|
||||
const auto p = input.find_first_not_of(" \n");
|
||||
return (p == std::string::npos) ? 0 : input[p];
|
||||
}
|
||||
|
||||
constexpr auto CHOOSELIMITSPROMPT = R"#(
|
||||
Choose how hard it will be to brute force your password.
|
||||
This also defines how long it will take to check the password.
|
||||
1 - interactive (least security, pretty fast checking)
|
||||
2 - moderate (medium security, takes below a second on most machines)
|
||||
3 - sensitive (decent security, takes around 2-4 seconds on most machines)
|
||||
Type 1, 2 or 3, or Enter for default (2): )#";
|
||||
|
||||
static unsigned int getOpsLimit(int choice) {
|
||||
switch (choice) {
|
||||
case '1': return crypto_pwhash_OPSLIMIT_INTERACTIVE;
|
||||
case '2': return crypto_pwhash_OPSLIMIT_MODERATE;
|
||||
case '3': return crypto_pwhash_OPSLIMIT_SENSITIVE;
|
||||
default: return crypto_pwhash_OPSLIMIT_MODERATE;
|
||||
}
|
||||
std::unreachable();
|
||||
}
|
||||
|
||||
static unsigned int getMemLimit(int choice) {
|
||||
switch (choice) {
|
||||
case '1': return crypto_pwhash_MEMLIMIT_INTERACTIVE;
|
||||
case '2': return crypto_pwhash_MEMLIMIT_MODERATE;
|
||||
case '3': return crypto_pwhash_MEMLIMIT_SENSITIVE;
|
||||
default: return crypto_pwhash_MEMLIMIT_MODERATE;
|
||||
}
|
||||
std::unreachable();
|
||||
}
|
||||
|
||||
static void help() {
|
||||
std::println("Usage: hyprlock-setpwhash [options]\n\n"
|
||||
"Options:\n"
|
||||
" -c FILE, --config FILE - Specify config file to use\n"
|
||||
" -h, --help - Show this help message\n\n"
|
||||
"Interactive utility to set the password hash for hyprlock");
|
||||
}
|
||||
|
||||
static std::optional<std::string> parseArg(const std::vector<std::string>& args, const std::string& flag, std::size_t& i) {
|
||||
if (i + 1 < args.size()) {
|
||||
return args[++i];
|
||||
} else {
|
||||
std::println(stderr, "Error: Missing value for {} option.", flag);
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char** argv, char** envp) {
|
||||
std::string configPath;
|
||||
std::vector<std::string> args(argv, argv + argc);
|
||||
|
||||
RASSERT(sodium_init() >= 0, "Failed to initialize libsodium");
|
||||
|
||||
for (std::size_t i = 1; i < args.size(); ++i) {
|
||||
const std::string arg = argv[i];
|
||||
|
||||
if (arg == "--help" || arg == "-h") {
|
||||
help();
|
||||
return 0;
|
||||
} else if ((arg == "--config" || arg == "-c") && i + 1 < (std::size_t)argc) {
|
||||
if (auto value = parseArg(args, arg, i); value)
|
||||
configPath = *value;
|
||||
else
|
||||
return 1;
|
||||
|
||||
} else {
|
||||
std::cerr << "Unknown argument: " << arg << std::endl;
|
||||
help();
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
std::string DEST;
|
||||
const auto [SECRETSCONF, DOTDIR] = Hyprutils::Path::findConfig("hyprlock_sodium");
|
||||
|
||||
if (!configPath.empty())
|
||||
DEST = configPath;
|
||||
|
||||
else if (SECRETSCONF.has_value())
|
||||
DEST = SECRETSCONF.value();
|
||||
|
||||
else {
|
||||
RASSERT(DOTDIR.has_value(), "Failed to find config directory!");
|
||||
DEST = DOTDIR.value() + "/hypr/hyprlock_sodium.conf";
|
||||
}
|
||||
|
||||
if (std::filesystem::exists(DEST)) {
|
||||
// check permissions
|
||||
std::println("{} already exists.", DEST);
|
||||
std::print("Do you want to overwrite it? [y/N] ");
|
||||
const auto CHOICE = getChoice();
|
||||
|
||||
if (CHOICE != 'y' && CHOICE != 'Y') {
|
||||
std::println("Keeping existing secrets!");
|
||||
|
||||
const auto PERMS = std::filesystem::status(DEST).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) {
|
||||
std::println("Setting permissions of {} to -rw-------", DEST);
|
||||
|
||||
// set perms to -rw-------
|
||||
std::filesystem::permissions(DEST, perms::owner_read | perms::owner_write);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
std::println("Note: We are going to write a password hash to {}\n"
|
||||
" If you choose a weak password and this hash gets leaked,\n"
|
||||
" someone might be able to guess your password using a password list or brute force.\n"
|
||||
" So best to keep it safe and (or) choose a good password.",
|
||||
DEST);
|
||||
|
||||
std::print(CHOOSELIMITSPROMPT);
|
||||
const auto CHOICE = getChoice();
|
||||
|
||||
setStdinEcho(false);
|
||||
std::string pw = "";
|
||||
while (true) {
|
||||
std::print("New password: ");
|
||||
std::getline(std::cin, pw);
|
||||
|
||||
if (pw.empty()) {
|
||||
std::println("\rEmpty password");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pw.size() < 4) {
|
||||
std::println("\rPassword too short");
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string pw2 = "";
|
||||
std::print("\rRepeat password: ");
|
||||
std::getline(std::cin, pw2);
|
||||
|
||||
if (pw != pw2) {
|
||||
std::println("\rPasswords do not match");
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
setStdinEcho(true);
|
||||
|
||||
char hash[crypto_pwhash_STRBYTES];
|
||||
if (crypto_pwhash_str(hash, pw.c_str(), pw.size(), getOpsLimit(CHOICE), getMemLimit(CHOICE)) != 0) {
|
||||
std::println("[Sodium] Failed to hash password");
|
||||
return 1;
|
||||
}
|
||||
|
||||
{
|
||||
std::ofstream out(DEST);
|
||||
// set perms to -rw-------
|
||||
std::filesystem::permissions(DEST, perms::owner_read | perms::owner_write);
|
||||
|
||||
out << "hash = " << hash << std::endl;
|
||||
}
|
||||
|
||||
std::println("\nDone!");
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
#include "Auth.hpp"
|
||||
#include "Pam.hpp"
|
||||
#include "SodiumPWHash.hpp"
|
||||
#include "Fingerprint.hpp"
|
||||
#include "../config/ConfigManager.hpp"
|
||||
#include "../core/hyprlock.hpp"
|
||||
|
|
@ -10,8 +11,16 @@
|
|||
|
||||
CAuth::CAuth() {
|
||||
static const auto ENABLEPAM = g_pConfigManager->getValue<Hyprlang::INT>("auth:pam:enabled");
|
||||
static const auto ENABLESODIUM = g_pConfigManager->getValue<Hyprlang::INT>("auth:sodium:enabled");
|
||||
|
||||
RASSERT(!(*ENABLEPAM && *ENABLESODIUM), "Only one of PAM or Sodium authentication methods can be enabled!")
|
||||
|
||||
if (*ENABLEPAM)
|
||||
m_vImpls.emplace_back(makeShared<CPam>());
|
||||
|
||||
if (*ENABLESODIUM)
|
||||
m_vImpls.emplace_back(makeShared<CSodiumPWHash>());
|
||||
|
||||
static const auto ENABLEFINGERPRINT = g_pConfigManager->getValue<Hyprlang::INT>("auth:fingerprint:enabled");
|
||||
if (*ENABLEFINGERPRINT)
|
||||
m_vImpls.emplace_back(makeShared<CFingerprint>());
|
||||
|
|
|
|||
|
|
@ -7,8 +7,9 @@
|
|||
#include "../core/Timer.hpp"
|
||||
|
||||
enum eAuthImplementations {
|
||||
AUTH_IMPL_PAM = 0,
|
||||
AUTH_IMPL_FINGERPRINT = 1,
|
||||
AUTH_IMPL_PAM = 0,
|
||||
AUTH_IMPL_FINGERPRINT = 1,
|
||||
AUTH_IMPL_SODIUMPWHASH = 2,
|
||||
};
|
||||
|
||||
class IAuthImplementation {
|
||||
|
|
|
|||
148
src/auth/SodiumPWHash.cpp
Normal file
148
src/auth/SodiumPWHash.cpp
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
#include "SodiumPWHash.hpp"
|
||||
#include "Auth.hpp"
|
||||
#include "../config/ConfigManager.hpp"
|
||||
#include "../core/hyprlock.hpp"
|
||||
#include "../helpers/Log.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
#include <hyprlang.hpp>
|
||||
#include <hyprutils/path/Path.hpp>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <sodium.h>
|
||||
|
||||
static std::string getSecretsConfigPath() {
|
||||
std::filesystem::path secrets_file;
|
||||
static const auto PPWHASHSTEM = g_pConfigManager->getValue<Hyprlang::STRING>("auth:sodium:secret_file");
|
||||
const std::string PWHASHSTEM = *PPWHASHSTEM;
|
||||
std::filesystem::path dir = g_pConfigManager->configCurrentPath;
|
||||
dir = dir.parent_path();
|
||||
|
||||
RASSERT(!PWHASHSTEM.empty(), "[SodiumAuth] auth:sodium:secret_file must be set to a non-empty value");
|
||||
|
||||
if (PWHASHSTEM.contains('/')) {
|
||||
if (PWHASHSTEM.starts_with('/'))
|
||||
// An absolute path
|
||||
secrets_file = PWHASHSTEM;
|
||||
else
|
||||
// A relative path to main config file
|
||||
secrets_file = dir / PWHASHSTEM;
|
||||
} else {
|
||||
// A stem
|
||||
secrets_file = dir / PWHASHSTEM;
|
||||
secrets_file += ".conf";
|
||||
}
|
||||
|
||||
RASSERT(std::filesystem::exists(secrets_file), "[SodiumAuth] Failed to find {}. Use \"hyprlock-pwhash\" to generate it!", secrets_file.c_str());
|
||||
|
||||
// check permissions
|
||||
using std::filesystem::perms;
|
||||
const auto PERMS = std::filesystem::status(secrets_file).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] {} has insecure permissions", secrets_file.c_str());
|
||||
}
|
||||
return secrets_file;
|
||||
}
|
||||
|
||||
CSodiumPWHash::CSodiumPWHash() : m_config(getSecretsConfigPath().c_str(), {}) {
|
||||
m_config.addConfigValue("hash", Hyprlang::STRING{""});
|
||||
m_config.commence();
|
||||
auto result = m_config.parse();
|
||||
|
||||
if (result.error)
|
||||
Debug::log(ERR, "[SodiumAuth] Error in configuration:\n{}\nProceeding", result.getError());
|
||||
}
|
||||
|
||||
CSodiumPWHash::~CSodiumPWHash() {
|
||||
;
|
||||
}
|
||||
|
||||
void* const* CSodiumPWHash::getConfigValuePtr(const std::string& name) {
|
||||
return m_config.getConfigValuePtr(name.c_str())->getDataStaticPtr();
|
||||
}
|
||||
|
||||
void CSodiumPWHash::init() {
|
||||
RASSERT(sodium_init() >= 0, "[SodiumAuth] Failed to initialise libsodium");
|
||||
m_thread = std::thread([this]() {
|
||||
while (true) {
|
||||
m_sCheckerState.prompt = "Password: ";
|
||||
waitForInput();
|
||||
|
||||
// For grace or SIGUSR1 unlocks
|
||||
if (g_pHyprlock->isUnlocked())
|
||||
return;
|
||||
|
||||
const auto AUTHENTICATED = auth();
|
||||
|
||||
// For SIGUSR1 unlocks
|
||||
if (g_pHyprlock->isUnlocked())
|
||||
return;
|
||||
|
||||
if (!AUTHENTICATED)
|
||||
g_pAuth->enqueueFail(m_sCheckerState.failText, AUTH_IMPL_SODIUMPWHASH);
|
||||
else {
|
||||
g_pAuth->enqueueUnlock();
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void CSodiumPWHash::waitForInput() {
|
||||
std::unique_lock<std::mutex> lk(m_sCheckerState.inputMutex);
|
||||
m_bBlockInput = false;
|
||||
m_sCheckerState.state = SODIUMHASH_INPUT;
|
||||
m_sCheckerState.inputSubmittedCondition.wait(lk, [this]() { return (m_sCheckerState.state != SODIUMHASH_INPUT) || g_pHyprlock->m_bTerminate; });
|
||||
m_bBlockInput = true;
|
||||
}
|
||||
|
||||
bool CSodiumPWHash::auth() {
|
||||
static auto const PPWHASH = (Hyprlang::STRING*)getConfigValuePtr("hash");
|
||||
const std::string PWHASH = *PPWHASH;
|
||||
bool rv;
|
||||
|
||||
if (PWHASH.empty() || PWHASH.size() > crypto_pwhash_STRBYTES) {
|
||||
m_sCheckerState.failText = "Invalid hash. Check config";
|
||||
Debug::log(ERR, "[SodiumAuth] Invalid password hash set in configuration");
|
||||
rv = false;
|
||||
} else if (crypto_pwhash_str_verify(PWHASH.c_str(), m_sCheckerState.input.c_str(), m_sCheckerState.input.length()) == 0) {
|
||||
rv = true;
|
||||
} else {
|
||||
m_sCheckerState.failText = "Failed to authenticate";
|
||||
Debug::log(LOG, "[SodiumAuth] Failed to authenticate");
|
||||
rv = false;
|
||||
}
|
||||
m_sCheckerState.input.clear();
|
||||
m_sCheckerState.state = SODIUMHASH_IDLE;
|
||||
return rv;
|
||||
}
|
||||
|
||||
void CSodiumPWHash::handleInput(const std::string& input) {
|
||||
std::unique_lock<std::mutex> lk(m_sCheckerState.inputMutex);
|
||||
|
||||
if (m_sCheckerState.state != SODIUMHASH_INPUT)
|
||||
Debug::log(ERR, "SubmitInput called, but auth thread is not waiting for input!");
|
||||
|
||||
m_sCheckerState.input = input;
|
||||
m_sCheckerState.state = SODIUMHASH_AUTH;
|
||||
m_sCheckerState.inputSubmittedCondition.notify_all();
|
||||
}
|
||||
|
||||
bool CSodiumPWHash::checkWaiting() {
|
||||
return m_bBlockInput || (m_sCheckerState.state == SODIUMHASH_AUTH);
|
||||
}
|
||||
|
||||
std::optional<std::string> CSodiumPWHash::getLastFailText() {
|
||||
return m_sCheckerState.failText.empty() ? std::nullopt : std::optional(m_sCheckerState.failText);
|
||||
}
|
||||
|
||||
std::optional<std::string> CSodiumPWHash::getLastPrompt() {
|
||||
return m_sCheckerState.prompt.empty() ? std::nullopt : std::optional(m_sCheckerState.prompt);
|
||||
}
|
||||
|
||||
void CSodiumPWHash::terminate() {
|
||||
m_sCheckerState.inputSubmittedCondition.notify_all();
|
||||
if (m_thread.joinable())
|
||||
m_thread.join();
|
||||
}
|
||||
51
src/auth/SodiumPWHash.hpp
Normal file
51
src/auth/SodiumPWHash.hpp
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
#pragma once
|
||||
|
||||
#include "Auth.hpp"
|
||||
|
||||
#include <condition_variable>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <hyprlang.hpp>
|
||||
#include <thread>
|
||||
|
||||
class CSodiumPWHash : public IAuthImplementation {
|
||||
public:
|
||||
enum EState {
|
||||
SODIUMHASH_IDLE = 0,
|
||||
SODIUMHASH_INPUT = 1,
|
||||
SODIUMHASH_AUTH = 2,
|
||||
};
|
||||
struct SCheckerState {
|
||||
std::string input = "";
|
||||
std::string prompt = "";
|
||||
std::string failText = "";
|
||||
|
||||
std::mutex inputMutex;
|
||||
std::condition_variable inputSubmittedCondition;
|
||||
|
||||
EState state = SODIUMHASH_IDLE;
|
||||
};
|
||||
|
||||
CSodiumPWHash();
|
||||
|
||||
virtual ~CSodiumPWHash();
|
||||
virtual eAuthImplementations getImplType() {
|
||||
return AUTH_IMPL_SODIUMPWHASH;
|
||||
}
|
||||
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:
|
||||
bool m_bBlockInput;
|
||||
Hyprlang::CConfig m_config;
|
||||
std::thread m_thread;
|
||||
SCheckerState m_sCheckerState;
|
||||
|
||||
bool auth();
|
||||
void* const* getConfigValuePtr(const std::string& name);
|
||||
void waitForInput();
|
||||
};
|
||||
|
|
@ -254,6 +254,8 @@ void CConfigManager::init() {
|
|||
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:retry_delay", Hyprlang::INT{250});
|
||||
m_config.addConfigValue("auth:sodium:enabled", Hyprlang::INT{0});
|
||||
m_config.addConfigValue("auth:sodium:secret_file", Hyprlang::STRING{"hyprlock_sodium"});
|
||||
|
||||
m_config.addConfigValue("animations:enabled", Hyprlang::INT{1});
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue