在Agent请求sudo的时候,我之前一直让他使用pkexec,但是这样每天都要重复几十次的操作。密码冗长、容易输错,在 sudo -S 脚本里处理密码管道也需要格外谨慎。我一直希望有一个像手机解锁一样流畅的方案:手机在手边,指纹一按,命令即放行。
于是我写了 FingerKey:一个 PAM 认证模块 + 本地 daemon + Android app。安装之后,sudo、pkexec、SSH 登录,乃至任何经过 PAM 的认证,都会把请求推送到手机,用 BiometricPrompt 弹出指纹验证,批准则放行,拒绝则回退到密码。整个过程不依赖任何云服务。
动机:为什么要再写一个认证工具
Linux 桌面上想免密码提权,现有的方案我都不太满意:
- 硬件密钥(YubiKey 一类)需要额外购买、随身携带,遗失后恢复成本高;
- fprintd 依赖笔记本内置的指纹传感器,桌面台式机大多不具备,驱动兼容性也参差不齐;
- 云端方案 意味着认证请求要离开内网、经过厂商服务器,这是我难以接受的。
手机几乎是人人都有、随身携带的设备,指纹识别技术也已经相当成熟。它和电脑通常处于同一个局域网。把”批准”这个动作放到手机上,等于免费得到了一台”第二因子”设备,无需购买任何新硬件。
工作原理:一条请求的旅程
FingerKey 由三个部分组成:
| 组件 | 语言 | 作用 |
|---|---|---|
fingerkeyd | Go | 本地 daemon,持有待批准会话、签发 nonce、校验签名 |
pam_fingerkey.so | C | PAM 认证模块,创建会话并等待手机决策 |
| FingerKey app | Flutter | 扫码配对、钉住证书指纹、弹出指纹验证 |
三个组件之间的连接关系:
一次 sudo 的完整流程如下:
PAM 配置中,auth sufficient pam_fingerkey.so 这一行放在 @include common-auth 之前。sufficient 的语义是:批准即跳过后续认证;其他任何结果(拒绝、超时、手机不可达)都会自动回退到正常的密码流程。因此 FingerKey 永远不会把系统锁死。
安全设计:凭什么信任手机
这是整个项目里我投入最多精力的部分。核心原则是:不信任网络,只信任密码学。
证书指纹 pinning,而不是 CA。 daemon 持有一张自签名的 ECDSA P-256 证书,手机在配对时记录这张证书的 SHA-256 指纹(小写十六进制)。此后每次连接,手机都会拒绝任何指纹不符的服务器。没有 CA 可以被攻破,也没有公网 DNS 可以被劫持——局域网内不持有私钥的机器无法冒充 daemon。
决策使用 Ed25519 签名,且不可重放。 手机签名的字节序列是:
"phone-fprint-auth/v1" || 0x00 || action || 0x00
|| u8len(user) || user || u8len(service) || service || u8len(tty) || tty
|| noncenonce 是一次性的 32 字节随机数,会话 60 秒后过期,且用户/服务/终端上下文都被绑定在签名中。因此即使攻击者截获了某个批准报文,也无法用它重放给另一个请求。
密码永不传输。 认证的三个环节——PAM 模块、daemon、手机——都不会接触你的密码。daemon 上只保存公钥,手机私钥留在手机本地。即便 daemon 被攻破,也无法获得任何能签出”批准”的凭据。
默认快速回退。 如果没有已配对的手机、或手机未连接,daemon 直接返回 503,PAM 立即回退到密码提示,而不是等待 60 秒。这条设计从一开始就确定了:宁可回退到密码,也不把系统锁死。
几个有意思的取舍
- 不依赖任何云。 电脑和手机通过局域网 TCP/TLS 直连,服务发现使用 mDNS(
_phonefprint._tcp),由 daemon 内置,无需 Avahi 之类的额外依赖。整个系统没有出网流量。 - 用 PAM 而非 hook sudo。 PAM 是 Linux 认证的统一入口,一个模块即可覆盖
sudo、pkexec、su、login、sshd、桌面登录管理器等所有 PAM 服务,无需为每个工具单独打补丁。 - daemon 运行在非 root 用户下。 它以
phonefprint系统用户运行(无 home、nologin),unix socket 为 root-only 的 0700,只有 root 或 setuid-root 的 PAM 模块能够创建会话。
现状与用法
项目已经开源(MIT),首个版本 v0.1.0 已发布。CI 使用 GitHub Actions:推送一个 v* 的 tag,即自动编译 Linux 二进制和 Android APK,并发布到 Release 页面。
安装非常直接:
# 从源码装(Ubuntu/Debian)
sudo ./scripts/install.sh
# 打印配对二维码
sudo fingerkey pair-qr my-phone
# 手机装 app-release.apk,扫码配对,然后随便跑个 sudo 试试
sudo true配对之后,每次提权手机都会弹出指纹验证框,显示请求来自哪个用户、哪个服务、哪个终端。批准则放行,拒绝或超时则回退到密码。
结语
这个项目的意义在于,它把三个截然不同的技术栈结合在了一起:C 编写的 PAM 模块、Go 编写的并发 daemon、Flutter 编写的安卓 app,中间通过一条自定义的 TLS 帧协议衔接。如果它对你有用,欢迎 star、试用、提交 issue;如果你对某个设计细节感兴趣,仓库里的 README 和 threat model 有详细的说明,代码也配备了完整的单元测试和一套无需真机的端到端测试脚本。