Canary Workshop

Whatever is worth doing at all is worth doing well

使用 Gnuk 和 ST-Link v2 制作 GPG 硬件密钥

市场上常见的 GPG 硬件密钥(即 GPG 智能卡)主要是 YubiKey 和 Nitrokey,这些产品常常比较昂贵(对作为 GPG 密钥来说)。经过搜寻,发现了利用的 ST-Link v2 与 Gnuk 制作密钥的方案。在万能的某宝上 ST-Link v2 只需不到20元即可买到。ST-Link v2 的主要部分是一枚 STM32 芯片,同时搭配有 128KB 的存储空间(虽然设计上是 64KB,但实际上能买到的都是 128KB),正好可以存入 Gnuk 的二进制程序码。

我使用了 Arch Linux 环境编译并写入。下面是完整的流程。

环境准备

安装交叉编译器和刷写工具

1
sudo pacman -S arm-none-eabi-gcc arm-none-eabi-newlib openocd

然后准备 openocd 的配置文件。将该文件存为 openocd.cfg 备用:

1
2
3
4
5
telnet_port 4444
source [find interface/stlink-v2.cfg]
set WORKAREASIZE 0x1000
source [find target/stm32f1x.cfg]
reset_config none
编译
1
2
3
4
5
6
7
8
git clone https://salsa.debian.org/gnuk-team/gnuk/gnuk.git
cd gnuk
git submodule update --init
cd src
./configure --vidpid=234b:0000 --target=ST_DONGLE --enable-factory-reset
#这里的第一个参数为 USB 设备标识,一般使用 Gnuk 开发者组织 FSIJ 的标识。但是根据用户协议,用户不允许分发含有该标识的二进制文件。因此请勿传播稍后编译得到的二进制文件。
#这里的第三个参数为启用恢复出厂设置功能。使用该功能会将硬件密钥上的所有信息清空,如果不需要可以去掉该参数。
make

此时我们会得到 build/gnuk.bin 文件,即编译完成的二进制。拷贝走这个文件备用。此时可以移除交叉编译器及整个源码目录。

写入

这里遭遇到了一个麻烦点:与能查找到的资料不同,某宝购入的 ST-Link v2 并没有资料中所提到的插口(即连接 SWCLKSWDIO 的插口。根据一份在网上找到的 STM32F103 芯片的引脚图 image

和实际的芯片引脚,我们可以试图手动连接这两个引脚。方法如下:

首先将用作刷写器的 ST-Link v2 连接在 USB 端口,然后将四根杜邦线按外壳标识插入尾部的 SWCLK SWDIO GND 3.3V 接口备用。拆除目标设备的外壳备用。

其次将 GND3.3V 直接连接至目标设备尾部相应接头。此时,目标设备的指示灯应当会有反应,表面供电已经连接。

最后是重点 SWCLKSWDIO 接头。芯片上的引脚十分微小,使用电烙铁也不是很方便。因此这里使用暴力方法用手摁连接。

下图给出了实物上这两个引脚的位置。image

我们可以将一根公头杜邦线拆掉,将其中一两根铜丝拉出,勾在 SWDIO 引脚,另一端连接母头杜邦线。再取一根杜邦线,一端连接母头,另一端直接用手摁在 SWCLK 引脚上。该引脚在最外侧,直接摁住较为方便。当然,两个引脚都用铜丝缠绕连接或摁住也是可行的。一只手紧紧摁住保持不动(防止中途连接断开),或者请他人协助,然后进入下一步刷写。

刷写

这里打开一个 screen ,执行命令 openocd -s tcl -f openocd.cfg 。假如上一步中的连接完全正确,则当前进程不会退出并输出类似如下文字:

1
2
3
4
5
6
7
8
9
10
11
12
adapter speed: 1000 kHz
adapter_nsrst_delay: 100
srst_only separate srst_nogate srst_open_drain connect_assert_srst
srst_only separate srst_nogate srst_open_drain connect_assert_srst
3333
Info : Unable to match requested speed 1000 kHz, using 950 kHz
Info : Unable to match requested speed 1000 kHz, using 950 kHz
Info : clock speed 950 kHz
Info : STLINK v2 JTAG v23 API v2 SWIM v4 VID 0x0483 PID 0x3748
Info : using stlink api v2
Info : Target voltage: 3.xxxx
Info : stm32f1x.cpu: hardware has 6 breakpoints, 4 watchpoints

不过因为上述连接不是很牢固,大多数时候会报错退出,这个时候可以更换用力角度等不断尝试,直到连接正确。连接正确后务必死死定住连接防止断开。

切出 screen 开始刷写。为了减少刷写用时防止手滑断开,可以提前将以下指令写成脚本一次执行。注意,如果写成脚本,每行指令中间需要睡眠少量时间来让指令完成执行,写入命令行后需要睡眠约5s来保证命令完成执行。

1
2
3
4
5
6
7
8
9
10
telnet localhost 4444
#使用 telnet 连接 openocd 开始操作。以下在 telnet 中执行
reset halt
stm32f1x unlock 0
reset halt
stm32f1x mass_erase 0
flash write_bank 0 gnuk.bin 0
#上面这一行即是写入,gnuk.bin 为二进制文件路径。这里直接放到了 /home 下操作。如果使用脚本,该行后务必睡眠5s左右
stm32f1x lock 0
reset halt

如果一切顺利,上面指令不该出现任何报错而顺利终止。此时便刷写成功,可以拆除线缆退出 openocd 程序。

我们已经得到了一枚刷入了 Gnuk 的 ST-Link v2。现在我们可以将外壳装上插入 USB 端口。系统应当会正确识别到一个智能卡。可以执行 gpg --card-status 来查看详细信息。

最后要使用该硬件密钥,请参考 Gnuk官方文档来配置和写入自己的 GPG 私钥。