# fingerprintd Fingerprint daemon for the Fairphone 6 (`milos`, SM7635) on mainline Linux. ## Why a daemon The sensor is a FocalTech FT9391 on a TrustZone-owned SPI bus. `spi@a88000` is `disabled` in both the mainline and the stock Android device tree, and the pads are XPU-protected — touching them from the normal world is an instant SError reboot. Every pixel the sensor produces stays inside the TEE: capture, preprocessing, the classifier, enrolment and matching all run in the `focal64` trustlet, which reports a matched finger id and nothing else. A libfprint-style driver cannot exist on this device. So the normal world's job is narrower than usual, and none of it is per-request work: * **Power the sensor.** Rail on gpio29, reset on gpio74, interrupt on gpio75 — the same division of labour the downstream driver uses. One sensor reset buys exactly one trustlet init, so whatever powers the sensor must also hold the session open. * **Be QTEE's filesystem.** QTEE cannot reach storage. When the trustlet saves or loads a template it calls back into the normal world through the `gpfile` (0x7000) and RPMB (0x2000) listeners, and expects them served. QTEE does the crypto and the anti-rollback; this side moves opaque bytes and performs the authenticated RPMB transactions against the UFS device. * **Speak a biometrics API.** The daemon owns `net.reactivated.Fprint`, so `pam_fprintd`, the Plasma fingerprint KCM and `fprintd-enroll(1)` work against it unmodified. A listener registration is held for as long as the process lives and QTEE's listener table is global to the boot, so this has to be one long-lived process rather than a tool spawned per request. ## Layout ``` interfaces/ Fingerprintd{,-Sfs}.cppm the core: pure C++ modules implementations/main.cpp the daemon shell tests/ one suite per core module ``` `fingerprintd-core` is a static library with no GLib, no libqcomtee and no system headers. Everything in it is a wire format or a state machine that was recovered by reverse-engineering, so all of it is pinned by tests that run on a dev box with no phone, no TEE and no sensor. The daemon shell holds everything that touches hardware. ## Build ```sh crafter-build # bin/fingerprintd--/fingerprintd crafter-build test # the unit suites ``` Cross-compiling for the phone: ```sh packaging/make-sysroot.sh # once; no root, no qemu, no device crafter-build -- --target=aarch64-alpine-linux-musl \ --sysroot=~/.cache/fingerprintd/sysroot-aarch64-alpine \ --march=armv8-a --mtune=generic crafter-build test --target=aarch64-alpine-linux-musl --sysroot=... \ --march=armv8-a --mtune=generic # runs the suites under qemu-aarch64 ``` The result links dynamically against the phone's own musl and libc++ (`libc++`, `libc++abi`, `libunwind`, `libgcc_s`, all already present on pmOS). The research harness this replaces had to be built `-static`, but only because it was built with the host's glibc toolchain — that constraint does not apply to a real Alpine sysroot. Verified on the device: all five suites pass cross-built and run **on the phone itself**, not only under emulation. ## Status **The core is complete; the daemon does not run yet.** Everything was ported out of the research harness that first made the sensor work (`utilities/fpta.c` in the fp6 repo), one module at a time, each landing with its tests before the next started. | module | what it holds | |---|---| | `:Sfs` | the gpfile frame — the read/write offset split, the `O_TRUNC` guard, root mapping, path-traversal rejection | | `:Rpmb` | request/reply framing, the bytes-transferred out-parameter, JEDEC result codes, chunking, the one-time-programmable key guard | | `:Ta` | command surface, the 740-byte event context, capture flags, SAVE_DATA masks, enrol/auth payloads, the error table, the verdict rule | | `:Engine` | baseline calibration, touch edges, enrolment progress, and the accounting | | `:Store` | the finger name map | Every constant that was recovered by reverse-engineering carries where it came from, and the tests are written to fail if it is undone rather than to restate it. Several replay real captures: two SFS containers off the phone, and three recorded authentication runs. Next is the I/O shell — the TEE session, the sensor rail, the RPMB device and the bus — which is the first part that cannot be validated without hardware. The working reference enrols a finger, keeps it across a reboot, and matches it with zero false accepts; the port exists to turn that into a service rather than to rediscover it. ## Runtime dependencies, not carried here The `focal64` trustlet is proprietary and is **not** in this repo. It is extracted from the device's own stock Android partition on first boot by the `fp6-vendor-blobs` mechanism, the same way the audio firmware is.