给2019年的MateBook13装指纹登录:一次走到底的失败
硬件是好的,社区驱动差一代固件,密钥拿不到,此路不通。一次从头查到尾的失败复盘。
一句话结论:硬件是好的,社区驱动差一代固件,密钥拿不到,此路不通。
写下来是因为中间有几处可复用的东西(无 root 构建、TLS-PSK 记录层、libgpiod v2 的坑),还有两处我踩错的坑,值得记。
一、起点:这颗传感器在 Linux 上根本不存在
机器是 HUAWEI WRT-WX9(MateBook 13 2019)。指纹模块是 Goodix GXFP51A7,挂在 SPI 上:
/sys/bus/spi/devices/spi-GXFP51A7:00 ACPI: GXFP51A7 @ \_SB.PCI0.SPI1.SPBA
fprintd-list 直接返回 No devices available。原因很清楚:libfprint 里的 Goodix 驱动(goodixmoc)只认 USB 款,SPI 这颗没有上游支持。PAM 那边其实已经铺好了(/etc/pam.d/gdm-fingerprint 存在),只是底下没有设备可接。
二、找到社区驱动:差一代固件的坑就在这里
社区项目 libfprint-goodixtls 是给 MateBook X Pro(MACH-WX9)的 GXFP5187 写的 TOD 驱动,协议全逆向,0xF2 命令从传感器 RAM 里读 48 字节 PSK,不需要 Windows 或 Intel ME/SGX。
PR #4 加了 GXFP51A7,参数写得和我的机器一模一样:MilanL 芯片 0x2205、复位 gpiochip0 line 264、PSK 地址 0x20007f14,作者还写了真机验证过 enroll/verify。
但作者那台的固件是 GF3288_ST411SEC_APP_14003,我这台报的是 GF_ST411SEC_APP_14115。 当时没在意这一行——后来它就是全部答案。
三、没有 root 也能把依赖装齐
构建要 libfprint-2-tod-dev、libssl-dev、libudev-dev、libgusb-dev 等等,但没有 sudo。办法是把 deb 下下来解包,拼一棵本地依赖树:
apt-get download libfprint-2-tod-dev libssl-dev libudev-dev libfprint-2-dev \
libgusb-dev libusb-1.0-0-dev libjson-glib-dev meson ninja-build
for f in *.deb; do dpkg-deb -x "$f" deps; done
# 再把 deps 里的 .pc 文件里的 /usr 改写成 deps 前缀,PKG_CONFIG_PATH 指过去
三个坑,都是同类问题(发行版打包差异):
openssl/opensslconf.h不在/usr/include/openssl/,在 multiarch 子目录x86_64-linux-gnu/openssl/,得手动补-I;- Ubuntu 的
libudev.pc里没有udev这个名字,也没有udevdir变量(systemd 上游的udev.pc才有),meson 的dependency('udev')直接失败——补个别名的.pc和一行变量就行; - OpenSSL 一度链成了静态
.a,把 jitterentropy 的符号拖进模块,dlopen时报undefined symbol: jent_entropy_init_ex。原因是我先前把 dev 包的.so符号链接指向了不存在的目标,链接器只好退到.a。重建即可。
四、装上之后:第一道墙是密码套件
驱动装进 TOD 目录、spidev 绑上、fprintd 撑起来之后,GetDevices 能看到设备了。但一录入就断:
TLS handshake failed (-1/5) error:0A0000C1:SSL routines::no shared cipher
no shared cipher 只说明两边没交集,不告诉你对方提了什么。所以我给驱动加了 SSL_CTX_set_client_hello_cb,把传感器的 ClientHello 抄下来:
goodixtls: ClientHello legacy_version=0x0303, 4 bytes of suites: 00a8 00ff
0x00ff 是重协商 SCSV,不是套件。真正的套件是 0x00a8。
这里我错了一次:凭记忆把 0x00a8 当成 AES-256-GCM-SHA384,改完照样失败。一条命令就能查清:
$ openssl ciphers -V 'PSK-AES128-GCM-SHA256:PSK-AES256-GCM-SHA384:PSK-AES128-CBC-SHA256'
0x00,0xA8 - PSK-AES128-GCM-SHA256 TLSv1.2 Enc=AESGCM(128) Mac=AEAD
0x00,0xA9 - PSK-AES256-GCM-SHA384 TLSv1.2 Enc=AESGCM(256) Mac=AEAD
0x00,0xAE - PSK-AES128-CBC-SHA256 TLSv1 Enc=AES(128) Mac=SHA256
驱动原本写死的是 0x00ae(CBC)。传感器要的是 0x00a8(AES-128-GCM)。
五、补 GCM:套件、密钥派生、记录层
这个驱动之所以要自己解记录层,是因为传感器把 22 kB 的图像塞进单个 TLS 记录,超过 16384 的规格上限,任何合规库都会拒收;它让 OpenSSL 只做握手和小写入,大记录自己解。换成 GCM 之后这套逻辑要跟着改三处:
- 套件列表加上
PSK-AES128-GCM-SHA256; - 密钥派生:AES-128-GCM 用 PRF-SHA256,key block 是
client_key|server_key|client_IV|server_IV(AEAD 没有 MAC 密钥,IV 只有 4 字节固定前缀); - 记录解密:
explicit_nonce(8) || ciphertext || tag(16),nonce = 固定 IV + explicit,用EVP_aes_128_gcm校验 tag,无 padding、无序列号。
改完,握手走到了 Finished:
goodixtls: handshake err: error:0A000119:SSL routines::decryption failed or bad record mac
有交集了,但 MAC 不对——意味着握着的那把 PSK 是假的。
六、第二道墙:这代固件不按地址返回内存
驱动从 0x20007f14 读 48 字节当 PSK。读是“成功”的,长度正好 48:
14 7f 00 18 30 00 00 00 | d4 bf 63 ce 6e 74 79 5d ...
前 8 字节是地址和长度的回显,后面才是数据。于是试了几个地址,结果是这样:
| 请求 | 返回(前 16 字节) |
|---|---|
0x20007f14 |
14 7f 00 18 30 00 00 00 d4 bf 63 ce 6e 74 79 5d |
0x20000000 |
00 00 00 18 40 00 00 00 57 bf 63 ce 6e 74 79 5d |
0x20008000 |
00 80 00 18 40 00 00 00 d7 bf 63 ce 6e 74 79 5d |
64 字节里只有 2 个字节不同:地址回显,和一个像是状态/随机值的首字节。其余 63 字节完全一致。
也就是说,这代固件的 0xF2 不理会地址,返回的是一块固定内容。PSK 不在那条老路径上了——驱动能走到 Finished,全靠手里那把假钥匙。
七、结论
- 硬件没坏(Windows 下一直能用),坏的是“协议还差一代”。
- 我这台:
GF_ST411SEC_APP_14115,社区驱动覆盖的是14003,前缀都变了。 - 要往下走只有两条:逆向新固件的密钥获取,或者从 Windows 侧抓一次真实握手。都不划算。
想要指纹登录的替代方案,是买个 libfprint 支持的外接 USB 读卡器——插上就能 fprintd-enroll,GDM 登录和 sudo 都覆盖。
八、两个我自己的错误
记下来,因为都是“该先验证却先动手”:
- 套件编号靠记忆猜,白白多跑一轮。
openssl ciphers -V就在手边。 - 日志埋点放在成功路径上,读失败时函数提前返回,日志根本没执行,我据此还以为“没读”。埋点要埋在失败分支。
另外两个操作层面的坑:gpioset 在 libgpiod v2 里没有 -m 选项,而且默认不会退出(它要维持电平)——要么用 timeout 杀掉进程,要么用 -p/--hold-period 配合 -t。用错的表现是脚本卡死。
工具链细节:Ubuntu 26.04、kernel 7.0.0-38、libfprint 1.95.1+tod1。
有AI相关的想法,但不知道怎么落地?
我是国冰,独立AI开发者。十多年技术一线——半导体显示、智慧城市到自动驾驶;现在一个人+AI,接AI落地项目,从梳理需求到系统落地,陪你走通。
看我能做什么