
Login con impronta digitale sul desktop Linux usando un sensore Grow R503 + Arduino + un daemon Rust sostitutivo di fprintd
Un lettore di impronte digitali USB per desktop Linux, assemblato da componenti singoli. Costo totale dei componenti
inferiore a 15 $. Sostituto diretto del progetto upstream fprintd — funzionano PAM, KDE Settings,
GNOME Settings, fprintd-verify, sudo con il dito e sblocco dello schermo
con il dito.
A partire da fw=1.0 / r503d 1.0.0 il collegamento Arduino↔host è autenticato:
ogni comando e ogni risposta trasporta un MAC SipHash-2-4 basato su un
segreto accoppiato tramite TOFU e memorizzato in EEPROM. Gli attacchi di replay e di scambio a caldo
contro il collegamento seriale USB sono bloccati. Vedi SPEC.md §13 per la progettazione completa,
incluso ciò che il modello di minaccia non copre.

vorrei tanto avere una stampante 3D…``` ┌──────────┐ UART ┌─────────────┐ USB-CDC ┌──────────────────┐ │ Grow │ 57600 8N1│ Arduino │ /dev/r503 │ r503d daemon │ │ R503 │◀─────────▶│ (firmware) │◀──────────▶│ net.reactivated │ │ sensor │ 3.3V TTL │ │ framed, │ .Fprint on D-Bus│ └──────────┘ └─────────────┘ MAC'd └──────────────────┘ │ ▼ PAM, KDE, GNOME, fprintd-verify, …
## Perché
I lettori di impronte digitali USB hardware per Linux sono scarsi, costosi, e
quelli che esistono (Validity, Synaptics, ecc.) sono reverse-engineered tramite
driver libfprint instabili che si rompono con gli aggiornamenti del firmware del fornitore. Il
protocollo del Grow R503 è **pubblico**, il lato Arduino è codice tuo,
e il livello di compatibilità libfprint è solo D-Bus.
Ti ritrovi anche con un lettore di impronte digitali di cui puoi leggere il sorgente, dall'inizio
alla fine.
## Distinta dei materiali
| Parte | Note | Costo approssimativo |
|------|-------|------|
| Sensore capacitivo di impronte digitali Grow R503 | Quello rotondo con l'anello RGB | ~$10 |
| Arduino Uno R3 / Nano / Mega / qualsiasi scheda ATmega328 | Qualsiasi cosa che esegua SoftwareSerial | $5–$25 |
| 4–6 fili jumper | Dupont / breadboard | trascurabile |
Questo è tutto. **Niente level shifter, niente partitore di tensione** — vedi [`SPEC.md` §3.1](https://github.com/matpb/linux-fingerprint-r503/blob/main/SPEC.md)
per il motivo (la linea RX dell'R503 è tollerante ai 5V nella pratica; il datasheet mente).
## Cablaggio```
R503 Arduino (Uno R3 / Nano / etc.)
---- ------------------------------
Red (VCC) 3V3
White (3.3VT) 3V3 (touch-IC supply; shares rail with red)
Black (GND) GND
Yellow (TXD) D2 ── SoftwareSerial RX
Brown (RXD) D3 ── SoftwareSerial TX (direct — no divider!)
Blue (WAKEUP) D4 (optional; not used by firmware yet)
Se la tua R503 viene fornita con il connettore JST-SH, taglia un pigtail a 6 pin JST-SH-to-Dupont per separare i fili. Il marrone a volte è verde a seconda del venditore — verifica in base al filo che entra nel pin RXD del connettore JST, non al colore.
Testato su Fedora 44 KDE; dovrebbe funzionare su qualsiasi distro basata su systemd con
fprintd, pam_fprintd e una toolchain Rust recente.
Pacchetti di sistema:
| Distro | Compilazione | Runtime |
|---|---|---|
| Fedora / RHEL | rust cargo arduino-cli tpm2-tss-devel | fprintd pam fprintd-pam tpm2-tss |
| Debian / Ubuntu | rustc cargo arduino-cli libtss2-dev | fprintd libpam-fprintd libtss2-esys-3.0.2-0 |
I pacchetti tss-esapi sono necessari solo se prevedi di usare --pair --seal-tpm
(SPEC §13.12). Altrimenti il demone compila e funziona senza TPM — tss-esapi
è una dipendenza di compilazione obbligatoria ma di runtime facoltativa (il percorso del codice viene seguito solo quando
/var/lib/r503d/key.tpm esiste).
Rust 1.95+, arduino-cli nel tuo $PATH.
Hai un TPM2?```bash ls /dev/tpmrm0 && tpm2_pcrread sha256:7 | head -3
Se entrambi riescono, il tuo host può utilizzare il percorso della chiave sigillata. Se `/dev/tpmrm0` è
assente (hardware più vecchio, TPM disabilitato nel BIOS o una VM senza TPM virtuale),
attieniti al flusso predefinito con chiave in chiaro.
## Compilazione e installazione
### 1. Flash del firmware
Apri `firmware/r503fp/r503fp.ino` nell'IDE Arduino e caricalo. Oppure con
`arduino-cli`:```bash
# Uno R3:
arduino-cli compile --fqbn arduino:avr:uno firmware/r503fp/
arduino-cli upload --fqbn arduino:avr:uno --port /dev/ttyACM0 firmware/r503fp/
# Nano (modern Optiboot, including most Elegoo / WAVGAT clones):
arduino-cli compile --fqbn arduino:avr:nano:cpu=atmega328 firmware/r503fp/
arduino-cli upload --fqbn arduino:avr:nano:cpu=atmega328 --port /dev/ttyUSB0 firmware/r503fp/
# Nano with legacy 57600-baud bootloader (older clones):
# replace `cpu=atmega328` with `cpu=atmega328old`
The firmware uses Adafruit_Fingerprint. The IDE will offer to install it
on first compile.
If arduino-cli upload fails with not in sync: resp=0x7e, your bootloader
is the other variant — swap atmega328 ↔ atmega328old and retry. Both
work; the difference is just bootloader baud rate.
Requires Rust 1.95+.
Traduzione in italiano:
Il firmware utilizza Adafruit_Fingerprint. L'IDE offrirà di installarlo
alla prima compilazione.
Se arduino-cli upload fallisce con not in sync: resp=0x7e, il tuo bootloader
è l'altra variante — scambia atmega328 ↔ atmega328old e riprova. Entrambi
funzionano; la differenza è solo il baud rate del bootloader.
Richiede Rust 1.95+.```bash cd pcside/daemon cargo build --release
### 3. Installazione```bash
sudo bash pcside/daemon/dist/install.sh
Questo script:
target/release/r503d in /usr/local/bin/r503d/var/lib/r503d/ (permessi 0700 root:root) per la chiave, lo stato e il
registro degli slot utente/dev/r503 e fissa il
nodo del dispositivo a root:root 0600 (solo il daemon, in esecuzione come root, ne ha bisogno;
questo chiude il percorso predefinito 0660 root:dialout così nessun altro utente locale può
aprire la porta — audit di sicurezza 2026-05-28 / H1). Conseguenza: dopo
l'installazione, qualsiasi comando manuale arduino-cli/serial-monitor su /dev/r503
richiede sudo./etc/systemd/system/r503d.service)net.reactivated.Fprint/usr/share/polkit-1/actions/net.reactivated.fprint.device.r503d.policy)
usata dal gate dell'identità del chiamanteÈ idempotente — rieseguilo dopo ogni cargo build --release per
ridistribuire il nuovo binario.
Un Nano appena flashato non è associato — il daemon gli parlerebbe, ma il firmware rifiuterebbe ogni comando strutturato in frame. Scegli uno dei due flussi sotto; entrambi terminano con un Nano associato e un daemon funzionante. Il flusso sigillato con TPM è consigliato se il tuo host ha un TPM2 (vedi Prerequisiti per la verifica rapida).
Il file opt-in (/etc/r503d/allow-pair) usato in entrambi i flussi serve a
sconfiggere un attaccante che si precipita alla tua scrivania con il proprio Nano — l'associazione
senza root è impossibile. r503d --pair elimina il marcatore prima
di inviare la chiave al Nano: se l'host va in crash tra il commit lato Nano
e la persistenza lato host, la porta è già chiusa, quindi il tentativo di
associazione successivo richiede che un amministratore esegua di nuovo touch sul marcatore. Un'uscita anticipata prima dell'invio
(niente marcatore, o "già associato") lascia il marcatore intatto per un nuovo tentativo.
Usa questo se non hai un dispositivo TPM2, o se non ti serve la resistenza ad attacchi offline su disco.```bash sudo systemctl stop r503d sudo mkdir -p /etc/r503d sudo touch /etc/r503d/allow-pair # opt-in (see SPEC §13.5) sudo r503d --pair # 128-bit key → /var/lib/r503d/key sudo systemctl start r503d
* Genera una tabella Markdown con i campi dello strumento, supporta più righe e ha una logica per decidere colonne e righe.
+ $ drupwn users:list http://example.com --markdown
+ $ drupwn nodes:list http://example.com --markdown
+ $ drupwn modules:list http://example.com --markdown
* Genera un formato JSON con i campi dello strumento, questa opzione sopprime automaticamente la verbosità.
+ $ drupwn config:dump http://example.com --json
+ $ drupwn taxonomy:list http://example.com --json
* Genera un formato XML con i campi dello strumento, questa opzione sopprime automaticamente la verbosità.
+ $ drupwn config:dump http://example.com --xml
+ $ drupwn users:list http://example.com --xml
* Salva l'intero output su un file, utile per grandi output che possono essere ulteriormente elaborati.
+ $ drupwn config:dump http://example.com > output.txt
+ $ drupwn modules:list http://example.com > output.txt```bash
sudo r503d --status
# port: /dev/r503
# firmware: fw=1.1 fmt=2
# firmware paired: true
# firmware counter: 42
# host key.tpm: (absent)
# host key: /var/lib/r503d/key
# host key.bak: /var/lib/r503d/key.bak
# tpm device: (absent)
# allow-pair: (absent)
Stesso flusso, più --seal-tpm. La chiave generata viene sigillata su PCR7 (policy Secure Boot + chiavi) e scritta in /var/lib/r503d/key.tpm invece del file key in chiaro. Gli attaccanti con accesso offline al disco (dd di una partizione non montata, scambio dell'SSD in un host ostile) ottengono solo testo cifrato.```bash
sudo systemctl stop r503d
sudo mkdir -p /etc/r503d
sudo touch /etc/r503d/allow-pair
sudo r503d --pair --seal-tpm # seals new key to current PCR7
sudo systemctl start r503d
Il contenuto del chunk 19 non è stato fornito nell'input. Nessuna traduzione possibile.```bash
sudo r503d --status
# port: /dev/r503
# firmware: fw=1.1 fmt=2
# firmware paired: true
# firmware counter: 12
# host key.tpm: /var/lib/r503d/key.tpm
# host key: (missing)
# host key.bak: (missing)
# tpm device: /dev/tpmrm0
# allow-pair: (absent)
Gli aggiornamenti del kernel, gli aggiornamenti initrd, gli aggiornamenti del firmware UEFI tramite fwupd e gli aggiornamenti di grub2
non modificano PCR7 e non richiedono una nuova sigillatura. PCR7 cambia solo
in caso di modifiche alle policy di Secure Boot, iscrizioni MOK o spostamento del disco
su un host diverso — a quel punto il daemon rifiuta di avviarsi con
TPM_RC_POLICY_FAIL e dist/reseal-tpm.sh recupera in ~90 secondi.
Vedi Recupero: PCR7 modificata.
fprintd-enroll mat
fprintd-verify mat
sudo whoami
Sia KDE Settings (Plasma 6) che i dialoghi delle impronte digitali dell'account utente di GNOME Control Center guidano `r503d` esattamente come guidano l'upstream `fprintd`.
### Riassociazione / rotazione delle chiavi
Se vuoi una chiave nuova (chiave compromessa, sostituzione hardware pianificata, paranoia):```bash
sudo systemctl stop r503d
sudo r503d --unpair # framed; wipes Nano EEPROM + host key
sudo touch /etc/r503d/allow-pair
sudo r503d --pair # plaintext-key rotation
# - or -
sudo r503d --pair --seal-tpm # TPM-sealed rotation
sudo systemctl start r503d
Abbina il tuo percorso di associazione originale. Se hai usato originariamente --seal-tpm,
ruota con --seal-tpm — altrimenti la rotazione ti declassa silenziosamente
a una chiave in chiaro su disco.
Se hai usato --pair --seal-tpm e in seguito hai cambiato qualcosa che PCR7
misura (Secure Boot disattivato/attivato, nuovo MOK registrato, disco spostato
su un'altra macchina), il daemon rifiuterà di avviarsi con un messaggio di journal
relativo a TPM_RC_POLICY_FAIL. Il recupero richiede un solo comando:```bash
sudo bash pcside/daemon/dist/reseal-tpm.sh
Lo script ferma `r503d`, riprogramma `firmware/r503fp_wipe/` per cancellare la EEPROM del Nano, riprogramma il firmware principale, crea `/etc/r503d/allow-pair`, esegue `r503d --reseal-tpm` per generare una nuova chiave sigillata al PCR7 *attuale* e riavvia il demone. Tempo reale: ~90 secondi. Le impronte registrate vengono preservate: i template risiedono nella flash del sensore R503, non nel Nano.
Lo script richiede `arduino-cli` disponibile. Se è installato in `$HOME/.local/bin` dell'utente, viene rilevato automaticamente tramite `$SUDO_USER`; altrimenti imposta `ARDUINO_CLI=/full/path/to/arduino-cli` prima di eseguirlo.
### Recupero: `state.json` mancante (desincronizzazione del contatore)
Se la chiave host è intatta ma `/var/lib/r503d/state.json` è sparito o è stato ripristinato da un backup vecchio (o cancellato accidentalmente), il contatore del demone resta indietro rispetto al `last_seen` del Nano e ogni comando incorniciato viene respinto con `ERR replay`. `r503d --status` lo segnala; la soluzione è un solo comando:```bash
sudo systemctl stop r503d
sudo r503d --resync # reads Nano last_seen, sets host counter to last_seen+1
sudo systemctl start r503d
No re-pair, no reflash: la chiave non si sposta mai. La query status su cui si basa --resync non è autenticata, ma può solo spostare il contatore dell'host in avanti per allinearlo a ciò che il Nano ha già impegnato, quindi non può mai rendere un vecchio frame riproducibile (nel peggiore dei casi un MITM mendace forza un altro ERR replay, cosa che potrebbe già fare corrompendo i frame). Vedi SPEC.md §13.11.
L'--unpair autenticato ha bisogno della chiave per autorizzare. Se tutte le copie su disco sono sparite (crash del disco, rm accidentale, sia key che key.bak eliminate, o blob key.tpm perso), hai bisogno della via di fuga reflash-to-wipe — la stessa procedura che dist/reseal-tpm.sh automatizza per il caso di PCR7 cambiato sopra:```bash
sudo systemctl stop r503d
sudo arduino-cli upload --fqbn arduino:avr:nano:cpu=atmega328 --port /dev/r503 firmware/r503fp_wipe/
sudo arduino-cli upload --fqbn arduino:avr:nano:cpu=atmega328 --port /dev/r503 firmware/r503fp/ sudo touch /etc/r503d/allow-pair sudo r503d --pair sudo systemctl start r503d
Se `sudo arduino-cli` segnala un errore di comando non trovato (arduino-cli si trova nella tua
`~/.local/bin`, non nel `PATH` di root), eseguilo come
`sudo env "PATH=$PATH" arduino-cli …` oppure fornisci il percorso assoluto.
Non è una backdoor che un attaccante può usare: la ri-associazione richiede i privilegi di root sul
host (il file opt-in e la CLI `--pair` richiedono entrambi root), quindi un
Nano reflashato non può essere portato in uno stato di fiducia senza che tu sia già
root.
### Disinstallazione```bash
sudo bash pcside/daemon/dist/uninstall.sh
Ripristina tutto, rimuove il mascheramento di fprintd, lascia /var/lib/r503d/ (chiave, stato, utenti) al suo posto nel caso si voglia reinstallare in seguito. Elimina manualmente quella directory se vuoi un vero stato pulito.
L'Arduino esegue un piccolo firmware a protocollo ASCII (firmware/r503fp/) che parla il protocollo binario nativo R30x ("Sync Word") dell'R503 sul lato UART e scambia comandi di testo orientati a riga con l'host tramite USB-CDC: ping, info, enroll N, verify, delete N, clear, led off. Protocollo v1 completo in SPEC.md §5.
Dalla versione fw=1.0 (Milestone E del lavoro sul canale autenticato v2), ogni comando e risposta è incapsulato in un frame C <counter> <body> M <mac> / R <counter> <seq> <body> M <mac>, autenticato con MAC SipHash-2-4 su una chiave a 128 bit abbinata tramite TOFU. La Nano mantiene un contatore monotono con livellamento dell'usura in EEPROM; il demone mantiene un contatore corrispondente in /var/lib/r503d/state.json. I tentativi di replay (lato firmware incoming <= last_seen) vengono rifiutati come ERR replay; i frame manomessi ricevono ERR mac_invalid. Specifica completa, modello di minaccia e limitazioni note in SPEC.md §13.
Il demone Rust (r503d) parla D-Bus su net.reactivated.Fprint — bit per bit la stessa interfaccia esposta dall'upstream fprintd — quindi ogni client fprintd funziona senza modifiche. Un sidecar JSON in /var/lib/r503d/users.json mappa (utente, dito) agli indici di slot nella flash interna dell'R503.
Layout:``` firmware/r503fp/ Arduino firmware (v2 framed ASCII protocol) firmware/r503fp_wipe/ Emergency one-shot EEPROM wipe (lost-key recovery) firmware/* Diagnostic / development sketches (ping, loopback, ...) pcside/daemon/ Rust daemon (the fprintd replacement) pcside/daemon/src/{crypto,framing,keystore,state,pairing}.rs v2 wire protocol implementation pcside/daemon/src/auth.rs caller-identity gating for D-Bus methods pcside/daemon/dist/ udev rule, systemd unit, polkit + bus policy, install scripts docs/ Decision logs + troubleshooting SPEC.md Full architecture + protocol spec (§13 = v2 auth)
## Modello di sicurezza — riepilogo rapido
L'autenticazione a livello di wire mira a una minaccia specifica —
**"evil maid con cinque minuti e una Nano di scorta"** più un processo
locale ostile su `/dev/r503` — non a stati-nazione o attaccanti hardware
con laboratori. Distribuzione desktop per singolo utente con una lista
documentata di elementi fuori scope. Il modello di minaccia completo è in
[`SPEC.md` §13.1](https://github.com/matpb/linux-fingerprint-r503/blob/main/SPEC.md); le prove di implementazione e revisione sono
in
[`docs/REVIEW-2026-05-28.md`](https://github.com/matpb/linux-fingerprint-r503/blob/main/docs/REVIEW-2026-05-28.md). Un audit
separato di escalation dei privilegi avversariale (2026-05-28) e la
relativa passata di validazione/rimediazione per ogni affermazione sono
in
[`docs/SECURITY-AUDIT-2026-05-28.html`](https://github.com/matpb/linux-fingerprint-r503/blob/main/docs/SECURITY-AUDIT-2026-05-28.html)
e
[`docs/SECURITY-AUDIT-2026-05-28-VALIDATION.html`](https://github.com/matpb/linux-fingerprint-r503/blob/main/docs/SECURITY-AUDIT-2026-05-28-VALIDATION.html).
**Difeso:**
- Hot-swap della Nano con un'unità ostile (nessuna chiave → tutti i frame falliscono il MAC).
- Processo locale che inietta finte risposte di match su `/dev/r503`. Due livelli:
il nodo del dispositivo è `root:root 0600` (regola udev) e il demone lo tiene
con `TIOCEXCL`, quindi un processo non-root non può aprirlo — e anche se
potesse, non ha la chiave, quindi il frame fallisce la verifica MAC.
- Replay di frame `OK match=...` registrati in una sessione futura.
- Manomissione bit-flip di qualsiasi campo del frame (confronto MAC a tempo costante).
- Brick per esaurimento contatore: un peer (o un MITM one-shot durante `--resync`)
che porta il contatore monotono a `u64::MAX` e blocca permanentemente il
canale viene bloccato da un tetto del contatore riservato, applicato su entrambe
le estremità (`fw=1.1+`; audit SPEC §13.4 / 2026-05-28 DoS-2).
- Denial-of-service locale del sensore da parte dell'utente: un singolo gate dello
slot di acquisizione limita il lavoro di enroll/verify in corso e i percorsi di
eliminazione sono vincolati da un'azione, quindi un flood di `Start`/`Stop` (o
di eliminazioni concorrenti) non può bloccare l'autenticazione.
- Impianto / cancellazione / enumerazione di impronte cross-user da parte di un
utente locale non-root (es. `mallory` che chiama `Claim "root"` e poi registra
il proprio dito) — l'identità del chiamante viene controllata su ogni metodo
D-Bus che accetta `username`, e la policy del bus di sistema nega i chiamanti
non-`wheel` a livello di broker.
- **Attacchi offline al disco contro la chiave host** *se abbinati a `--seal-tpm`*:
la chiave su disco è sigillata TPM2 rispetto a PCR7, quindi `dd` di una
partizione smontata o lo spostamento di un SSD in un host ostile produce solo
ciphertext. Viene dissigillata solo sulla stessa macchina con la stessa policy
Secure Boot. Vedi [SPEC §13.12](https://github.com/matpb/linux-fingerprint-r503/blob/main/SPEC.md).
**Non difeso:**
- Compromissione della root dell'host (la chiave è in `/var/lib/r503d/key`, `0600 root:root`).
La root su un host in esecuzione può dissigillare anche la variante sigillata TPM —
la sigillatura attenua gli attacchi *offline*, non quelli online.
- Attacco fisico alla Nano (lettura EEPROM ~30 sec con ISP; decap del chip; ecc.).
- Attacco di reflash del firmware (il bootloader Arduino non ha firma — ma la
ri-associazione richiede la root sull'host, quindi una Nano riflashata non può
essere resa affidabile senza compromettere comunque l'host).
- Compromissione lato R503 (il protocollo R30x non ha alcuna autenticazione;
fuori dal nostro scope).
- **Postura crittografica.** MAC SipHash-2-4, chiave condivisa a 128 bit, output
MAC a 64 bit, input MAC separati per dominio. Due implementazioni indipendenti
(C++ scritto a mano sull'AVR con autotest KAT all'avvio; Rust scritto a mano
sull'host, convalidato in modo incrociato bit per bit contro la crate
`siphasher` di terze parti su 1024 vettori casuali in CI). Il confronto MAC
sull'host usa `subtle::ConstantTimeEq`. I parser wire vengono sottoposti a
property-fuzzing a ogni esecuzione CI (~135 000 input). `cargo audit` pulito.
La chiave SipHash è avvolta in `zeroize::Zeroizing<...>` così da essere
cancellata al drop (come i buffer di input MAC per-frame). Un target libFuzzer
di `cargo fuzz` è incluso in `pcside/daemon/fuzz/` per esecuzioni con corpus
lungo su nightly. Nessun audit umano di terze parti a pagamento — sarebbe
comunque prezioso, PR benvenute.
Modello di minaccia completo con motivazioni: [`SPEC.md` §13.1](https://github.com/matpb/linux-fingerprint-r503/blob/main/SPEC.md).
## Limitazioni
- **Il multi-utente funziona, ma solo per i membri di `wheel`.** L'identità del
chiamante viene verificata su ogni metodo D-Bus che accetta un `username`
(`Claim`, `EnrollStart`, `VerifyStart`, `ListEnrolledFingers`,
`DeleteEnrolledFingers`); le richieste verso se stessi e `uid 0` (PAM) riescono
silenziosamente, mentre una richiesta cross-user da un chiamante non-root viene
negata con `net.reactivated.Fprint.Error.PermissionDenied`. La policy del bus
di sistema limita ulteriormente quali account possono persino avviare una
conversazione: solo `root` e i membri di `wheel` raggiungono il demone, tutti
gli altri ricevono `org.freedesktop.DBus.Error.AccessDenied` a livello di
broker. Serve un enroll cross-user? Diventa root:
`sudo fprintd-enroll target-user`. Serve allentare il gate cross-user per un
chiosco / laboratorio multi-utente? Inserisci una regola JS in
`/etc/polkit-1/rules.d/` che ha come target
[`net.reactivated.fprint.device.setusername`](https://gitlab.freedesktop.org/libfprint/fprintd/-/blob/master/src/net.reactivated.fprint.device.policy.in)
— il nome dell'azione rispecchia alla lettera l'upstream fprintd.
- **Un solo lettore.** Il demone espone un singolo oggetto Device su D-Bus.
Le configurazioni multi-lettore richiedono un'estensione del Manager.
- **Nessun emit di `PropertiesChanged`** per le proprietà hint
`finger-present` / `finger-needed`. Ogni client fprintd comune (PAM, KDE
Settings, GNOME) si basa sui segnali `EnrollStatus` / `VerifyStatus` (che
vengono emessi), non su quegli hint interrogati — ma un client rigoroso che
fa `Get + PropertiesChanged` vedrà valori obsoleti.
- **Una sola Nano = punto unico di guasto.** Se la Nano muore, il login con
impronta sparisce finché non riflashate una di scorta e la ri-associate.
Tenete attivo un metodo di autenticazione con password come backup.
- **La perdita di State.json si recupera con un comando.** Se `state.json` viene
perso mentre il firmware ha ancora un `last_seen` alto, il demone incontra
`ERR replay` al primo invio. Eseguite `sudo r503d --resync` per leggere il
contatore della Nano e riallineare l'host — nessuna ri-associazione necessaria.
Vedi [`SPEC.md` §13.11](https://github.com/matpb/linux-fingerprint-r503/blob/main/SPEC.md).
## Risoluzione dei problemi```bash
# Daemon logs:
sudo journalctl -u r503d.service -f
# Confirm the sensor enumerates correctly:
ls -l /dev/r503
busctl --system call net.reactivated.Fprint /net/reactivated/Fprint/Device/0 \
net.reactivated.Fprint.Device ListEnrolledFingers s ""
# Confirm fprintd is masked and r503d owns the bus name:
systemctl is-enabled fprintd # should print "masked"
busctl --system list | grep -i fprint
Se il daemon non si avvia o il sensore non risponde mai, la soluzione più comune
è il cablaggio — vedi SPEC.md §3, in particolare la
nota "no voltage divider" nel §3.1. C'è un runbook più dettagliato
in docs/TROUBLESHOOTING.md.
MIT — vedi LICENSE.
fprintd — per aver progettato un'interfaccia D-Bus pulita che
questo daemon potesse implementare senza mai leggere il codice sorgente
di libfprint./etc/dbus-1/system.d/net.reactivated.Fprint.conf) — solo root e i membri di wheel
possono parlare col daemon; tutti gli altri ricevono
AccessDenied dal broker, prima che il daemon veda la chiamatafprintd.servicer503d.service