# fingerprint-switch A Rust tool for selecting a fingerprint reader through **fprintd**, plus optional lid-based routing for normal login, lock-screen and sudo authentication that already uses fprintd. Uses the installed libfprint drivers and Polkit permissions. Built for this laptop's two readers: | Role | USB ID | libfprint driver | fprintd name | | --- | --- | --- | --- | | Internal | `27c6:609c` | `goodixmoc` | Goodix MOC Fingerprint Sensor | | External | `3274:8012` | `mafpmoc` | MAFP MOC Fingerprint Sensor | The USB sensor is already supported by libfprint **1.94.100**, installed on this Aurora 44 machine. A new USB driver is unnecessary. These are match-on-chip readers; this tool does not export fingerprint images or implement matching. ## Build and use ```sh cargo build --release --locked ./target/release/fingerprint-switch list ./target/release/fingerprint-switch status ./target/release/fingerprint-switch probe --sensor external ``` Run these as your normal desktop user. `probe` opens and releases the reader without enrolling. Commands that access fprintd need the host's system D-Bus; run them outside a restricted sandbox. Polkit may request authentication. Enroll one finger on the external sensor, then verify it: ```sh ./target/release/fingerprint-switch enroll --sensor external \ --finger right-index-finger --allow-template-reset ./target/release/fingerprint-switch verify --sensor external ``` **The template-reset flag acknowledges a real driver limitation.** In libfprint 1.94.100, the `mafpmoc` enrollment path unconditionally sends command `0x0d`, the same command used to clear all device storage. Thus enrollment can erase all previously stored templates on the external sensor, including other users' or Windows templates. It does not erase the separate Goodix reader. This was confirmed from source, not by erasing this device. Use one enrolled finger on the external reader with this driver; enrolling a second can invalidate the first. The tool requires the flag for MAFP/Microarray readers even when fprintd lists no prints, because device storage may contain templates from another installation. Follow the prompts and lift your finger between samples. Enrollment defaults to 120 seconds, verification to 30; use `--timeout SECONDS` (1–600) to change this. Ctrl-C cancels and releases the reader. Only an explicit successful completion produces exit code 0; no-match, cancellation and timeout produce nonzero exits. Other examples: ```sh ./target/release/fingerprint-switch verify --sensor internal ./target/release/fingerprint-switch verify --sensor auto ./target/release/fingerprint-switch verify --sensor 'MAFP MOC Fingerprint Sensor' ``` `auto` reads the lid state at the start of the command: closed prefers external, open prefers internal, and an absent preferred reader falls back to the other. Explicit selection never silently falls back. Duplicate device names require a D-Bus path from `list`; paths may change when fprintd restarts. Fingerprints must be enrolled separately on each reader. `--user USER` selects another account subject to fprintd's normal permissions; omitting it uses the calling user. ## Automatic routing for login and sudo CLI selection affects only that invocation. Stock `pam_fprintd` chooses the reader with the most enrolled fingers; it does not use the CLI's selected reader. The optional system service checks the lid and USB presence every second and writes a root-owned environment file under `/run/fingerprint-switch`. A fprintd service drop-in reads `FP_DRIVERS_ALLOWLIST` from this file. It allows the internal driver with the lid open and the external driver with it closed, falling back to the connected reader if the preferred one is missing. With neither connected it allows both drivers for hotplug discovery. If lid state is unavailable, it prefers the internal reader and logs the condition. Only the selected driver is exposed through fprintd in automatic mode. Both devices remain physically connected. Changes queue a restart of an active fprintd; they do not start an idle fprintd. **Switching can cancel an enrollment or authentication already in progress.** The next attempt uses the new reader. Keep the lid and USB connection stable while enrolling. After enrolling and verifying both readers, install the optional integration: ```sh sudo ./scripts/install.sh fingerprint-switch status journalctl -u fingerprint-switch -n 30 --no-pager ``` The installer copies the built binary to `/usr/local/bin`, installs its own systemd service and fprintd drop-in, and enables the watcher. It does not replace libfprint, edit PAM, change suspend behavior or delete enrollment data. Existing fingerprint-enabled authentication flows use the selected reader. Applications that access USB directly are outside this routing mechanism. The allowlist operates per driver, so it is intended for this Goodix-plus-Microarray pair. The installer restarts an already-running fprintd once to apply the new drop-in. The runtime environment is preserved across watcher stops/restarts, and is removed during uninstall or reboot. If the watcher is interrupted between writing a selection and restarting fprintd, force application with `sudo systemctl try-restart fprintd.service`. Manual overrides, stored in `/etc/fingerprint-switch/mode`: ```sh sudo fingerprint-switch mode external # only Microarray, regardless of lid sudo fingerprint-switch mode internal # only Goodix, regardless of lid sudo fingerprint-switch mode both # expose both for enrollment/CLI selection sudo fingerprint-switch mode auto # return to lid-based routing ``` The installed watcher applies changes within a few seconds. In `both` mode PAM returns to its usual enrollment-count selection; it does not listen on both readers simultaneously. If the service is not installed/running, `mode` only saves a preference. Automatic routing does not check whether the current user has enrolled a finger on the selected reader; enroll first. It also cannot make an inaccessible built-in reader usable when the lid is closed and USB unplugged; use the normal password fallback in that situation. Rollback: ```sh sudo ./scripts/uninstall.sh ``` This removes the watcher and its drop-in, restarts an active fprintd to restore stock discovery, and preserves all enrollments. No password/PAM settings change. ## Validation and sources ```sh cargo test --locked cargo clippy --all-targets --locked -- -D warnings cargo fmt --check bash -n scripts/install.sh scripts/uninstall.sh ``` Tests use `dbus-daemon` to run isolated mock buses and cover reader-selection policy and D-Bus operation handling without storing biometrics. Run them outside a sandbox that blocks local sockets. Full enrollment/verification needs a person touching the sensor; lid-based system routing must also be tested after opting into installation. See `VALIDATION.md` for the checks actually performed on this machine. - [Official supported-device list](https://fprint.freedesktop.org/supported-devices.html) - [libfprint v1.94.100 mafpmoc source](https://gitlab.freedesktop.org/libfprint/libfprint/-/blob/v1.94.100/libfprint/drivers/mafpmoc/mafpmoc.c) - [Commit introducing unconditional enrollment clear](https://gitlab.freedesktop.org/libfprint/libfprint/-/commit/67649a0efde02211b89a8c8153d37d797bd2ca6f) - [fprintd Device D-Bus API](https://fprint.freedesktop.org/fprintd-dev/Device.html) - [PAM device-selection implementation](https://gitlab.freedesktop.org/libfprint/fprintd/-/blob/v1.94.5/pam/pam_fprintd.c) - [UPower lid properties](https://upower.freedesktop.org/docs/UPower/) `research/` contains ignored, unmodified upstream checkouts used during the investigation; they are not dependencies of this program.