一个由零件组装的USB指纹读取器,适用于Linux桌面。总零件成本不到15美元。可直接替代上游的fprintd——PAM、KDE设置、GNOME设置、fprintd-verify、使用手指的sudo、使用手指的屏幕解锁均可正常工作。
截至fw=1.0 / r503d 1.0.0,Arduino↔主机线路已认证:每个命令和响应都携带一个SipHash-2-4 MAC,其密钥基于EEPROM中TOFU配对的秘密。针对USB串行链路的重放和热插拔攻击被阻止。完整设计见SPEC.md §13,包括威胁模型不涵盖的内容。

真希望我有一台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, …
## 为什么
Linux 下的硬件 USB 指纹读取器稀缺、昂贵,并且现有的(如 Validity、Synaptics 等)都是通过不稳定的 libfprint 驱动逆向工程而来,这些驱动会在供应商固件更新时失效。Grow R503 的协议是 **公开** 的,Arduino 端是你自己的代码,而 libfprint 兼容层只是 D-Bus。
你最终还会得到一个从头到尾都能读到源代码的指纹读取器。
## 物料清单
| 部件 | 备注 | 大致成本 |
|------|-------|------|
| Grow R503 电容式指纹传感器 | 带有 RGB 环的圆形款 | ~$10 |
| Arduino Uno R3 / Nano / Mega / 任何 ATmega328 开发板 | 任何能运行 SoftwareSerial 的板子 | $5–$25 |
| 4–6 根杜邦线 | 杜邦 / 面包板 | 微不足道 |
就这样。**无需电平转换器,无需分压器** — 原因见 [`SPEC.md` §3.1](https://github.com/matpb/linux-fingerprint-r503/blob/main/SPEC.md)(R503 的 RX 线在实践中可承受 5V;数据手册有误)。
## 接线```
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)
如果您的 R503 配备了 JST-SH 连接器,请剪断一根 6 针 JST-SH 转杜邦线来分出线缆。棕色线有时会是绿色(取决于卖家)——请根据连接到 JST 接头的 RXD 针脚的那根线来验证,而不是根据颜色。
已在 Fedora 44 KDE 上测试;应适用于任何基于 systemd 的发行版,并包含 fprintd、pam_fprintd 以及较新的 Rust 工具链。
系统包:
| 发行版 | 构建依赖 | 运行时依赖 |
|---|---|---|
| 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 |
仅当您计划使用 --pair --seal-tpm(规范 §13.12)时才需要 tss-esapi 包。否则守护进程无需 TPM 即可构建和运行——tss-esapi 是硬性构建依赖但属于软性运行时依赖(代码路径仅在 /var/lib/r503d/key.tpm 存在时才会进入)。
需要 Rust 1.95+ 以及 $PATH 中的 arduino-cli。
您是否有 TPM2?```bash ls /dev/tpmrm0 && tpm2_pcrread sha256:7 | head -3
如果两者都成功,你的主机可以使用密封密钥路径。如果 `/dev/tpmrm0` 缺失(旧硬件、BIOS中禁用了TPM,或没有虚拟TPM的虚拟机),则坚持使用默认的明文密钥流程。
## 构建与安装
### 1. 烧录固件
在Arduino IDE中打开 `firmware/r503fp/r503fp.ino` 并上传。或者使用 `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`
固件使用了 Adafruit_Fingerprint。IDE 会在首次编译时提供安装。
如果 arduino-cli upload 失败并显示 not in sync: resp=0x7e,则你的 bootloader 是另一个变体——交换 atmega328 ↔ atmega328old 并重试。两者都有效;区别仅在于 bootloader 波特率。
需要 Rust 1.95+。```bash cd pcside/daemon cargo build --release
### 3. 安装```bash
sudo bash pcside/daemon/dist/install.sh
该脚本:
target/release/r503d 安装到 /usr/local/bin/r503d/var/lib/r503d/(权限 0700 root:root)用于存放密钥、状态和用户槽注册表/dev/r503,并将设备节点锁定为 root:root 0600(只有以 root 身份运行的守护进程需要它;这会关闭默认的 0660 root:dialout 路径,使其他本地用户无法打开端口 —— 安全审计 2026-05-28 / H1)。后果: 安装后,任何针对 /dev/r503 的手动 arduino-cli/串口监视器命令都需要 sudo。/etc/systemd/system/r503d.service)net.reactivated.Fprint/usr/share/polkit-1/actions/net.reactivated.fprint.device.r503d.policy),供调用者身份门使用/etc/dbus-1/system.d/net.reactivated.Fprint.conf)—— 只有 root 和 wheel 组成员可以与守护进程通信;其他所有人都会在代理处遇到 AccessDenied,守护进程根本看不到调用fprintd.servicer503d.service该脚本是幂等的 —— 每次执行 cargo build --release 后重新运行它,即可重新部署新的二进制文件。
新刷写的 Nano 是未配对的 —— 守护进程可以跟它通信,但固件会拒绝每个帧命令。从以下两种流程中选择一种;两种流程最终都能得到配对的 Nano 和工作的守护进程。如果你的主机有 TPM2,推荐使用 TPM 密封流程(参见前提条件中的快速检查)。
两种流程中使用的 opt-in 文件(/etc/r503d/allow-pair)存在的目的是阻止攻击者将自己带来的 Nano 快速抢占到你的桌面上 —— 没有 root 权限就无法进行配对。r503d --pair 会在发送密钥到 Nano 之前删除该标记:如果主机在 Nano 端提交之后、主机端持久化之前崩溃,门已经关闭,因此下一次配对尝试需要管理员再次 touch 该标记。发送前的退出(没有标记,或“已经配对”)会保留标记以便重试。
如果你没有 TPM2 设备,或者不需要离线磁盘攻击防护,请使用此方式。```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
(此块内容为空)```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)
相同的流程,加上--seal-tpm。生成的密钥被密封到PCR7(安全启动策略+密钥)并写入/var/lib/r503d/key.tpm,而不是明文key文件。离线磁盘攻击者(对未挂载分区执行dd,将SSD交换到恶意主机)只能得到密文。```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
(由于输入内容为空,无翻译内容)```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)
内核更新、initrd更新、fwupd UEFI固件更新以及grub2更新 不会 改变PCR7,因此无需重新密封。PCR7仅会在安全启动策略编辑、MOK注册或将磁盘移动到其他主机时发生变化——此时守护进程会因TPM_RC_POLICY_FAIL而拒绝启动,而dist/reseal-tpm.sh会在约90秒内完成恢复。详见恢复:PCR7已更改。
fprintd-enroll mat
fprintd-verify mat
sudo whoami
Both KDE Settings (Plasma 6) and GNOME Control Center's user-account
fingerprint dialogs drive `r503d` exactly as they drive upstream `fprintd`.
### 重新配对 / 密钥轮换