Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

auth: add libsodium based hash authentication #585

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ pkg_check_modules(
gbm
hyprutils>=0.2.6
sdbus-c++>=2.0.0
libsodium
hyprgraphics)

file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp")
Expand Down Expand Up @@ -115,8 +116,12 @@ protocol("protocols/wlr-screencopy-unstable-v1.xml"
protocol("unstable/linux-dmabuf/linux-dmabuf-unstable-v1.xml"
"linux-dmabuf-unstable-v1" false)

# hyprlock-setpwhash
add_executable(hyprlock-setpwhash "setpwhash/main.cpp")
target_link_libraries(hyprlock-setpwhash PRIVATE sodium hyprutils)

# Installation
install(TARGETS hyprlock)
install(TARGETS hyprlock hyprlock-setpwhash)

install(FILES ${CMAKE_SOURCE_DIR}/pam/hyprlock
DESTINATION ${CMAKE_INSTALL_FULL_SYSCONFDIR}/pam.d)
Expand Down
2 changes: 2 additions & 0 deletions nix/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
libdrm,
libGL,
libjpeg,
libsodium,
libwebp,
libxkbcommon,
mesa,
Expand Down Expand Up @@ -38,6 +39,7 @@ stdenv.mkDerivation {
buildInputs = [
cairo
file
libsodium
libdrm
libGL
libjpeg
Expand Down
167 changes: 167 additions & 0 deletions setpwhash/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
#include "../src/helpers/Log.hpp"

#include <filesystem>
#include <hyprutils/path/Path.hpp>
#include <iostream>
#include <fstream>
#include <istream>
#include <sodium.h>
#include <string>
#include <termios.h>
#include <unistd.h>
#include <print>

using std::filesystem::perms;

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
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): )#";

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();
}

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();
}

void help() {
std::println("Usage: hyprlock-setpwhash\n"
"Interactive utility to set the password hash for hyprlock");
}

int main(int argc, char** argv, char** envp) {
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 {
std::cerr << "Unknown argument: " << arg << std::endl;
help();
return 1;
}
}

const auto [SECRETSCONF, DOTDIR] = Hyprutils::Path::findConfig("hyprlock_pwhash");
if (SECRETSCONF.has_value()) {
// check permissions
std::println("{} already exists.", SECRETSCONF.value());
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(SECRETSCONF.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) {
std::println("Setting permissions of {} to -rw-------", SECRETSCONF.value());

// set perms to -rw-------
std::filesystem::permissions(SECRETSCONF.value(), perms::owner_read | perms::owner_write);
}
return 0;
}
}

RASSERT(DOTDIR.has_value(), "Failed to find config directory!");
const auto DEST = DOTDIR.value() + "/hypr/hyprlock_pwhash.conf";

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);
std::print("\r");

if (pw.empty()) {
std::println("Empty password");
continue;
}

if (pw.size() < 4) {
std::println("Less than 4 characters? Nope.");
continue;
}

std::string pw2 = "";
std::print("Repeat password: ");
std::getline(std::cin, pw2);
std::print("\r");

if (pw != pw2) {
std::println("Ups, passwords 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);
out << "pw_hash = " << hash << std::endl;
}

// set perms to -rw-------
std::filesystem::permissions(DEST, perms::owner_read | perms::owner_write);

std::println("Done!");
return 0;
}
5 changes: 5 additions & 0 deletions src/auth/Auth.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "Auth.hpp"
#include "Pam.hpp"
#include "Fingerprint.hpp"
#include "SodiumPWHash.hpp"
#include "../config/ConfigManager.hpp"
#include "../core/hyprlock.hpp"
#include "src/helpers/Log.hpp"
Expand All @@ -15,7 +16,11 @@ CAuth::CAuth() {
static auto* const PENABLEFINGERPRINT = (Hyprlang::INT* const*)g_pConfigManager->getValuePtr("auth:fingerprint:enabled");
if (**PENABLEFINGERPRINT)
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!");
}

Expand Down
1 change: 1 addition & 0 deletions src/auth/Auth.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
enum eAuthImplementations {
AUTH_IMPL_PAM = 0,
AUTH_IMPL_FINGERPRINT = 1,
AUTH_IMPL_SODIUM = 2,
};

class IAuthImplementation {
Expand Down
101 changes: 101 additions & 0 deletions src/auth/SodiumPWHash.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#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::checkerLoop() {
static auto* const PPWHASH = (Hyprlang::STRING*)getConfigValuePtr("pw_hash");
const auto PWHASH = std::string(*PPWHASH);

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) {
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;
}
}
41 changes: 41 additions & 0 deletions src/auth/SodiumPWHash.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#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();

Hyprlang::CConfig m_config;
};
1 change: 1 addition & 0 deletions src/config/ConfigManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ void CConfigManager::init() {
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: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.addSpecialConfigValue("background", "monitor", Hyprlang::STRING{""});
Expand Down
Loading