Skip to content

Commit 91b25f7

Browse files
committed
auth: Support parallel fingerprint auth
I chose to use Fprint's dbus interface directly rather than going through pam (which uses Fprint's dbus interface) due to poor handling of system sleep somewhere between fprintd and pam. When preparing for sleep, fprintd puts the device to sleep, which causes VerifyStatus to emit with verify-unknown-error, which normally should be responded to by calling both Device.StopVerify and Device.Release (and this is what pam does). Unfortunately, if you try to release the device when the system is preparing for sleep, you'll get an error that the device is busy and then you can't can't claim or release the device for 30 seconds. pam also has a max timeout for pam_fprintd.so of 99 seconds, and so if we used pam, we'd have to deal with the timeouts and keep restarting the auth conversation. gdm/gnome-session lock seems to get around these issues by having a shutter on top of the lock screen that you have to interact with first that gives gnome-session a trigger to start fingerprint auth.
1 parent 7362ce3 commit 91b25f7

File tree

6 files changed

+326
-2
lines changed

6 files changed

+326
-2
lines changed

CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ pkg_check_modules(
5656
pangocairo
5757
libdrm
5858
gbm
59-
hyprutils>=0.2.0)
59+
hyprutils>=0.2.0
60+
sdbus-c++)
6061

6162
file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp")
6263
add_executable(hyprlock ${SRCFILES})

src/config/ConfigManager.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ void CConfigManager::init() {
5050
m_config.addConfigValue("general:immediate_render", Hyprlang::INT{0});
5151
m_config.addConfigValue("general:pam_module", Hyprlang::STRING{"hyprlock"});
5252
m_config.addConfigValue("general:fractional_scaling", Hyprlang::INT{2});
53+
m_config.addConfigValue("general:enable_fingerprint", Hyprlang::INT{0});
5354

5455
m_config.addSpecialCategory("background", Hyprlang::SSpecialCategoryOptions{.key = nullptr, .anonymousKeyBased = true});
5556
m_config.addSpecialConfigValue("background", "monitor", Hyprlang::STRING{""});

src/core/Fingerprint.cpp

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
#include "Fingerprint.hpp"
2+
#include "../helpers/Log.hpp"
3+
#include "src/config/ConfigManager.hpp"
4+
5+
#include <filesystem>
6+
#include <unistd.h>
7+
#include <pwd.h>
8+
#include <security/pam_appl.h>
9+
#if __has_include(<security/pam_misc.h>)
10+
#include <security/pam_misc.h>
11+
#endif
12+
13+
#include <cstring>
14+
#include <thread>
15+
16+
static const auto FPRINT = "net.reactivated.Fprint";
17+
static const auto DEVICE = "net.reactivated.Fprint.Device";
18+
static const auto MANAGER = "net.reactivated.Fprint.Manager";
19+
static const auto LOGIN_MANAGER = "org.freedesktop.login1.Manager";
20+
static const auto RETRY_MESSAGE = "Could not match fingerprint. Try again.";
21+
22+
CFingerprint::CFingerprint() {}
23+
24+
static void fingerprintCheckTimerCallback(std::shared_ptr<CTimer> self, void* data) {
25+
g_pHyprlock->unlock();
26+
}
27+
28+
void CFingerprint::start() {
29+
static auto* const PENABLEFINGERPRINT = (Hyprlang::INT* const*)g_pConfigManager->getValuePtr("general:enable_fingerprint");
30+
const bool ENABLEFINGERPRINT = **PENABLEFINGERPRINT;
31+
if (!ENABLEFINGERPRINT) {
32+
return;
33+
}
34+
std::thread([this]() {
35+
// For grace or SIGUSR1 unlocks
36+
if (g_pHyprlock->isUnlocked())
37+
return;
38+
39+
setupDBUS();
40+
41+
g_pHyprlock->addTimer(std::chrono::milliseconds(1), fingerprintCheckTimerCallback, nullptr);
42+
}).detach();
43+
}
44+
45+
bool CFingerprint::isAuthenticated() {
46+
return m_bAuthenticated;
47+
}
48+
49+
std::optional<std::string> CFingerprint::getLastFailText() {
50+
return m_sDBUSState.failText.empty() ? std::nullopt : std::optional(m_sDBUSState.failText);
51+
}
52+
53+
void CFingerprint::terminate() {
54+
m_sDBUSState.connection->leaveEventLoop();
55+
}
56+
57+
bool CFingerprint::setupDBUS() {
58+
Debug::log(LOG, "{}", m_sDBUSState.sleeping);
59+
m_sDBUSState.connection = sdbus::createSystemBusConnection();
60+
61+
registerSleepHandler();
62+
63+
bool preparingForSleep = m_sDBUSState.login->getProperty("PreparingForSleep").onInterface(LOGIN_MANAGER);
64+
if (preparingForSleep) {
65+
m_sDBUSState.sleeping = true;
66+
Debug::log(LOG, "Preparing for sleep");
67+
} else {
68+
m_sDBUSState.sleeping = false;
69+
}
70+
71+
if (preparingForSleep || !startVerify()) {
72+
m_sDBUSState.connection->enterEventLoop();
73+
}
74+
75+
if (m_sDBUSState.abort)
76+
return true;
77+
78+
releaseDevice();
79+
return true;
80+
}
81+
82+
void CFingerprint::registerSleepHandler() {
83+
m_sDBUSState.login = sdbus::createProxy(*m_sDBUSState.connection.get(), "org.freedesktop.login1", "/org/freedesktop/login1");
84+
85+
m_sDBUSState.login->uponSignal("PrepareForSleep").onInterface(LOGIN_MANAGER).call([this](bool start) {
86+
Debug::log(LOG, "prepare for sleep");
87+
if (start) {
88+
m_sDBUSState.sleeping = true;
89+
stopVerify();
90+
m_sDBUSState.inhibitLock.reset();
91+
} else {
92+
m_sDBUSState.sleeping = false;
93+
inhibitSleep();
94+
std::this_thread::sleep_for(std::chrono::seconds(1));
95+
startVerify();
96+
}
97+
});
98+
m_sDBUSState.login->finishRegistration();
99+
inhibitSleep();
100+
}
101+
102+
void CFingerprint::inhibitSleep() {
103+
m_sDBUSState.login->callMethod("Inhibit")
104+
.onInterface(LOGIN_MANAGER)
105+
.withArguments("sleep", "hyprlock", "Release fingerprint before sleep", "delay")
106+
.storeResultsTo(m_sDBUSState.inhibitLock);
107+
}
108+
109+
std::optional<sdbus::Error> CFingerprint::createDeviceProxy() {
110+
auto proxy = sdbus::createProxy(*m_sDBUSState.connection.get(), FPRINT, "/net/reactivated/Fprint/Manager");
111+
112+
sdbus::ObjectPath path;
113+
try {
114+
proxy->callMethod("GetDefaultDevice").onInterface(MANAGER).storeResultsTo(path);
115+
} catch (sdbus::Error& e) {
116+
Debug::log(WARN, "Couldn't connect to Fprint service ({})", e.what());
117+
return e;
118+
}
119+
Debug::log(LOG, "Using dbus path {}", path.c_str());
120+
m_sDBUSState.device = sdbus::createProxy(*m_sDBUSState.connection.get(), FPRINT, path);
121+
122+
m_sDBUSState.device->uponSignal("VerifyFingerSelected").onInterface(DEVICE).call([](const std::string& finger) { Debug::log(LOG, "Finger: {}", finger); });
123+
m_sDBUSState.device->uponSignal("VerifyStatus").onInterface(DEVICE).call([this](const std::string& result, const bool done) { handleVerifyStatus(result, done); });
124+
125+
m_sDBUSState.device->finishRegistration();
126+
return {};
127+
}
128+
129+
void CFingerprint::handleVerifyStatus(const std::string& result, bool done) {
130+
Debug::log(LOG, "Status: {} {}", done, result);
131+
auto matchResult = s_mapStringToTestType[result];
132+
if (m_sDBUSState.sleeping && matchResult != MatchResult::Disconnected) {
133+
return;
134+
}
135+
std::unique_lock<std::mutex> lk(m_sDBUSState.failTextMutex);
136+
switch (matchResult) {
137+
case MatchResult::NoMatch: //The verification did not match, Device.VerifyStop should now be called.
138+
stopVerify();
139+
if (m_sDBUSState.retries < 3) {
140+
startVerify();
141+
done = false;
142+
m_sDBUSState.retries++;
143+
m_sDBUSState.failText = RETRY_MESSAGE;
144+
if (clearRetryTextTimer) clearRetryTextTimer->cancel();
145+
clearRetryTextTimer = g_pHyprlock->addTimer(
146+
std::chrono::seconds(2),
147+
[this](std::shared_ptr<CTimer>, void* data) {
148+
std::unique_lock<std::mutex> lk(m_sDBUSState.failTextMutex);
149+
if (m_sDBUSState.failText == RETRY_MESSAGE) {
150+
m_sDBUSState.failText = "";
151+
g_pHyprlock->enqueueForceUpdateTimers();
152+
}
153+
},
154+
nullptr);
155+
} else {
156+
m_sDBUSState.failText = "Fingerprint auth disabled: too many failed attempts.";
157+
}
158+
break;
159+
case MatchResult::UnknownError:
160+
stopVerify();
161+
m_sDBUSState.failText = "Unknown fingerprint error.";
162+
break;
163+
case MatchResult::Match:
164+
stopVerify();
165+
m_bAuthenticated = true;
166+
m_sDBUSState.failText = "";
167+
Debug::log(LOG, "matched");
168+
break;
169+
case MatchResult::Retry: m_sDBUSState.failText = "Please retry fingerprint scan."; break;
170+
case MatchResult::SwipeTooShort: m_sDBUSState.failText = "Swipe too short - try again."; break;
171+
case MatchResult::FingerNotCentered: m_sDBUSState.failText = "Finger not centered - try again."; break;
172+
case MatchResult::RemoveAndRetry: m_sDBUSState.failText = "Remove your finger and try again."; break;
173+
case MatchResult::Disconnected: m_sDBUSState.abort = true; break;
174+
}
175+
g_pHyprlock->enqueueForceUpdateTimers();
176+
if (done || m_sDBUSState.abort) {
177+
Debug::log(LOG, "leaving dbus loop");
178+
m_sDBUSState.connection->leaveEventLoop();
179+
}
180+
}
181+
182+
std::optional<sdbus::Error> CFingerprint::claimDevice() {
183+
try {
184+
const auto currentUser = ""; // Empty string means use the caller's id.
185+
m_sDBUSState.device->callMethod("Claim").onInterface(DEVICE).withArguments(currentUser);
186+
} catch (sdbus::Error& e) {
187+
Debug::log(WARN, "could not claim device, {}", e.what());
188+
return e;
189+
}
190+
Debug::log(LOG, "Claimed device");
191+
return {};
192+
}
193+
194+
std::optional<sdbus::Error> CFingerprint::startVerify() {
195+
if (!m_sDBUSState.device) {
196+
auto result = createDeviceProxy();
197+
if (result.has_value())
198+
return result;
199+
200+
claimDevice();
201+
}
202+
try {
203+
auto finger = "any"; // Any finger.
204+
m_sDBUSState.device->callMethod("VerifyStart").onInterface(DEVICE).withArguments(finger);
205+
} catch (sdbus::Error& e) {
206+
Debug::log(WARN, "Could not start verifying, {}", e.what());
207+
return e;
208+
}
209+
Debug::log(LOG, "started verifying");
210+
return {};
211+
}
212+
213+
std::optional<sdbus::Error> CFingerprint::stopVerify() {
214+
if (!m_sDBUSState.device)
215+
return {};
216+
try {
217+
m_sDBUSState.device->callMethod("VerifyStop").onInterface(DEVICE);
218+
} catch (sdbus::Error& e) {
219+
Debug::log(WARN, "could not stop verifying, {}", e.what());
220+
return e;
221+
}
222+
Debug::log(LOG, "Stopped verification");
223+
return {};
224+
}
225+
226+
std::optional<sdbus::Error> CFingerprint::releaseDevice() {
227+
if (!m_sDBUSState.device)
228+
return {};
229+
try {
230+
m_sDBUSState.device->callMethod("Release").onInterface(DEVICE);
231+
} catch (sdbus::Error& e) {
232+
Debug::log(WARN, "could not release device, {}", e.what());
233+
return e;
234+
}
235+
Debug::log(LOG, "Released device");
236+
return {};
237+
}

src/core/Fingerprint.hpp

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
#pragma once
2+
3+
#include "hyprlock.hpp"
4+
5+
#include <memory>
6+
#include <optional>
7+
#include <string>
8+
#include <mutex>
9+
#include <condition_variable>
10+
#include <sdbus-c++/sdbus-c++.h>
11+
12+
class CFingerprint {
13+
public:
14+
CFingerprint();
15+
16+
void start();
17+
bool isAuthenticated();
18+
19+
std::optional<std::string> getLastFailText();
20+
21+
void terminate();
22+
bool setupDBUS();
23+
24+
private:
25+
enum class MatchResult {
26+
NoMatch,
27+
Match,
28+
Retry,
29+
SwipeTooShort,
30+
FingerNotCentered,
31+
RemoveAndRetry,
32+
Disconnected,
33+
UnknownError,
34+
};
35+
struct SDBUSState {
36+
std::string failText = "";
37+
38+
std::mutex failTextMutex;
39+
40+
std::unique_ptr<sdbus::IConnection> connection;
41+
std::unique_ptr<sdbus::IProxy> login;
42+
std::unique_ptr<sdbus::IProxy> device;
43+
sdbus::UnixFd inhibitLock;
44+
45+
bool abort = false;
46+
int retries = 0;
47+
bool sleeping = false;
48+
} m_sDBUSState;
49+
50+
bool m_bAuthenticated = false;
51+
std::shared_ptr<CTimer> clearRetryTextTimer;
52+
53+
std::map<std::string, MatchResult> s_mapStringToTestType = {{"verify-no-match", MatchResult::NoMatch},
54+
{"verify-match", MatchResult::Match},
55+
{"verify-retry-scan", MatchResult::Retry},
56+
{"verify-swipe-too-short", MatchResult::SwipeTooShort},
57+
{"verify-finger-not-centered", MatchResult::FingerNotCentered},
58+
{"verify-remove-and-retry", MatchResult::RemoveAndRetry},
59+
{"verify-disconnected", MatchResult::Disconnected},
60+
{"verify-unknown-error", MatchResult::UnknownError}};
61+
void handleVerifyStatus(const std::string& result, const bool done);
62+
63+
void registerSleepHandler();
64+
void inhibitSleep();
65+
66+
std::optional<sdbus::Error> createDeviceProxy();
67+
std::optional<sdbus::Error> claimDevice();
68+
std::optional<sdbus::Error> startVerify();
69+
std::optional<sdbus::Error> stopVerify();
70+
std::optional<sdbus::Error> releaseDevice();
71+
};
72+
73+
inline std::unique_ptr<CFingerprint> g_pFingerprint;

src/core/hyprlock.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include "../renderer/Renderer.hpp"
55
#include "Auth.hpp"
66
#include "Egl.hpp"
7+
#include "Fingerprint.hpp"
78
#include "linux-dmabuf-unstable-v1-protocol.h"
89
#include <sys/wait.h>
910
#include <sys/poll.h>
@@ -417,6 +418,9 @@ void CHyprlock::run() {
417418
g_pAuth = std::make_unique<CAuth>();
418419
g_pAuth->start();
419420

421+
g_pFingerprint = std::make_unique<CFingerprint>();
422+
g_pFingerprint->start();
423+
420424
registerSignalAction(SIGUSR1, handleUnlockSignal, SA_RESTART);
421425
registerSignalAction(SIGUSR2, handleForceUpdateSignal);
422426
registerSignalAction(SIGRTMIN, handlePollTerminate);
@@ -572,6 +576,7 @@ void CHyprlock::run() {
572576
pthread_kill(pollThr.native_handle(), SIGRTMIN);
573577

574578
g_pAuth->terminate();
579+
g_pFingerprint->terminate();
575580

576581
// wait for threads to exit cleanly to avoid a coredump
577582
pollThr.join();
@@ -783,7 +788,7 @@ static const ext_session_lock_v1_listener sessionLockListener = {
783788

784789
void CHyprlock::onPasswordCheckTimer() {
785790
// check result
786-
if (g_pAuth->isAuthenticated()) {
791+
if (g_pAuth->isAuthenticated() || g_pFingerprint->isAuthenticated()) {
787792
unlock();
788793
} else {
789794
m_sPasswordState.passBuffer = "";

src/renderer/widgets/IWidget.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include "../../helpers/Log.hpp"
33
#include "../../core/hyprlock.hpp"
44
#include "src/core/Auth.hpp"
5+
#include "src/core/Fingerprint.hpp"
56
#include <chrono>
67
#include <unistd.h>
78
#include <pwd.h>
@@ -160,6 +161,12 @@ IWidget::SFormatResult IWidget::formatString(std::string in) {
160161
result.allowForceUpdate = true;
161162
}
162163

164+
if (in.contains("$FPRINTMESSAGE")) {
165+
const auto FPRINTMESSAGE = g_pFingerprint->getLastFailText();
166+
replaceInString(in, "$FPRINTMESSAGE", FPRINTMESSAGE.has_value() ? FPRINTMESSAGE.value() : "");
167+
result.allowForceUpdate = true;
168+
}
169+
163170
if (in.starts_with("cmd[") && in.contains("]")) {
164171
// this is a command
165172
CVarList vars(in.substr(4, in.find_first_of(']') - 4), 0, ',', true);

0 commit comments

Comments
 (0)