CanoKey
CanoKey 有两个版本,一个是软硬件完全开源的 canokey-stm32,一个是即将发售、核心代码开源的 CanoKey Pigeon。因为主控芯片,后者比前者安全性更高。不过因为还没有发售,所以本文记录的是 DIY 的 canokey-stm32。
canokey-stm32
canokey-stm32 的硬件可以在 canokeys/canokey-hardware 找到,用的是立创 EDA。我用的是 NFC-A 版本,板厚 2.4。
canokey-stm32 的固件可以在 canokeys/canokey-stm32 找到。STM32L432KC 的 bootloader 支持 DFU,应该可以 USB 直接下载固件。不过当时我不知道,PCB 正面有 4 个触点看图是 SWD 于是焊了 4 根线用 J-Link 下载了。
user$ JLinkExe -device STM32L432KC -if swd -speed 1000
JLink> r
JLink> loadfile /home/chuang/Documents/canokey-stm32/canokey.bin
然后初始化硬件
root# pacman -S pcsc-tools
root# systemctl start pcscd.socket
root# pcsc_scan
Using reader plug'n play mechanism
Scanning present readers...
0: Canokeys Canokey [OpenPGP PIV OATH] (12345678) 00 00
user$ canokey-stm32/utils/device-config-init.sh "Canokeys Canokey [OpenPGP PIV OATH] (12345678) 00 00"
NixOS
{
services.udev.packages = [
(pkgs.writeTextFile {
name = "canokey-udev-rules";
text = ''
SUBSYSTEM!="usb", GOTO="canokeys_rules_end"
ACTION!="add|change", GOTO="canokeys_rules_end"
ATTRS{idVendor}=="20a0", ATTRS{idProduct}=="42d4", ENV{ID_SMARTCARD_READER}="1"
LABEL="canokeys_rules_end"
KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="20a0", ATTRS{idProduct}=="42d4", TAG+="uaccess", GROUP="plugdev", MODE="0660"
SUBSYSTEMS=="usb", ATTR{idVendor}=="20a0", ATTR{idProduct}=="42d4", MODE:="0666"
'';
destination = "/etc/udev/rules.d/69-canokeys.rules";
})
];
services.pcscd.enable = true;
environment.systemPackages = with pkgs; [ ccid ];
}
这样配置后 U2F 就可以用了,Firefox 打开支持 2FA 的网站的帐号设置就可以添加 key 了。记得添加至少两种 2FA 方法,如使用 Aegis Authenticator 或生成 recovery code 写到纸上,不然 key 丢了就没法登陆帐号了。
{
security.pam.u2f.enable = true;
}
user$ mkdir -p ~/.config/Yubico/
user$ pamu2fcfg > ~/.config/Yubico/u2f_keys
这样登陆、解锁、sudo 都可以 CanoKey 摸一下通过了。
{
programs.gnupg.agent = {
enable = true;
pinentryFlavor = "qt";
enableSSHSupport = true;
};
}
这样就可以通过 GnuPG 验证登录 SSH 了。
OpenPGP applet
drduh/YubiKey-Guide 多数操作都适用于 CanoKey
CanoKey 支持 OpenPGP,可以通过 GnuPG 交互。Smart card 有 3 个密码,其中 PIN 默认值为 123456
,admin PIN 默认为 12345678
,reset code 默认没设置。可以通过 gpg --edit-card
设置它们,它们之间的关系如下:
flowchart LR a[Remember PIN] --Yes, change PIN--> b[passwd] a --No--> c[Remember admin PIN] c --Yes, change PIN--> d[admin > passwd > 2] c --Yes, change admin PIN--> h[admin > passwd > 3] c --Yes, set reset code--> i[admin > passwd > 4] c --No--> e[Has reset code] e --Yes--> f[unblock] e --No--> g[Canokey Admin Applet]
GPG 的钥匙用了这些奇妙的缩写:
pub
: 公钥sub
: 子公钥sec
: 密钥ssb
: 子密钥
GPG 中密钥有这些状态:
sec
: 该密钥在本机上sec#
: 该密钥不在本机上sec>
: 该密钥被移动到了 smart card 上
GPG 中密钥的功能有:
[C]
: 主密钥,用来签发子密钥,这些 ssb 也表示了你的身份。这样你可以平时只使用 ssb,将 sec 保存在一个安全的地方。[S]
: 签名密钥[E]
: 加密密钥,用来解密别人发给你的密文[A]
: 验证密钥,用来通过 gpg-ssh-agent 登陆 SSH
在有 CanoKey 之前我是一个密钥包揽所有功能,定期换密钥。但这不是 GPG 的正确使用方法!正确使用方法是使用一个永不过期的主密钥,将它保存到一个安全的地方,签发会过期的子密钥作为日常使用。不同子密钥不同用途,这样一个泄漏了可以直接吊销掉,不会影响其他。CanoKey 有 PGP slot,正好可以 S E A 各存一个。
gpg --edit-key <keyid>
进入编辑,expire
选择 0
将主密钥设为不会过期。是的,过期时间是可以使用主密钥编辑的,因此给主密钥本身设置过期时间没什么用。然后使用 addkey
添加子密钥,过期时间设为一年。save
保存退出。
如果你将主密钥保存在 SD 卡里,给子密钥设置过期时间可以提醒你每年掏出 SD 卡签新的子密钥,能起到给 SD 卡定期通电防止数据丢失的作用。
然后要把主密钥保存到其他地方,首先用 gpg --export-secret-subkeys > ssb.gpg
把子密钥导出来,待会还要用的。然后用 gpg --export-secret-keys --armor > sec.txt
导出主密钥,这个主密钥就可以放别的地方好好保存起来了,可以是 SD 卡,也可以打印到纸上夹书里藏起来。
rm -rf ~/.gnupg/
删掉 GPG 的数据库,然后 gpg --import ssb.gpg
。这时运行 gpg --list-secret-keys
就可以看到 sec
后面跟了个 #
,表示主密钥已经不在本机数据库里了。
sec# rsa4096 2021-04-26 [C]
5D03A5E60754A3E3CA575037E838CED81CFFD3F9
uid [ unknown] Zhu Chuang <chuang at melty dot land>
ssb rsa4096 2021-09-24 [S] [expires: 2022-09-24]
ssb rsa4096 2021-09-24 [E] [expires: 2022-09-24]
ssb rsa4096 2021-09-24 [A] [expires: 2022-09-24]
最后把子密钥迁移到 CanoKey 中,gpg --edit-key <keyid>
(keyid 为主密钥的 ID)。key 1
选择第一个子密钥(被选中的密钥后会显示 *
),keytocard
转移进 smart card 中。然后 key 1
取消选择第一个,key 2
选择第二个,keytocard
,以此类推。save
退出,此时本地数据库中就没有这些子密钥了。gpg --list-secret-keys
,可见 ssb
后出现 >
。
sec# rsa4096 2021-04-26 [C]
5D03A5E60754A3E3CA575037E838CED81CFFD3F9
uid [ unknown] Zhu Chuang <chuang at melty dot land>
ssb> rsa4096 2021-09-24 [S] [expires: 2022-09-24]
ssb> rsa4096 2021-09-24 [E] [expires: 2022-09-24]
ssb> rsa4096 2021-09-24 [A] [expires: 2022-09-24]
gpg --send-key <keyid>
更新自己的公钥。可以在 gpg --edit-card
> admin
> url
把自己公钥的下载地址存在 CanoKey 中,这样在一些 OpenPGP 客户端中可以即插即用。
Thunderbird
Thunderbird 支持 OpenPGP smart card,只是要稍微配置一下。汉堡 > Preferences > 最底下的 Config Editor... > mail.openpgp.allow_external_gnupg
改为 true
。然后在 Preferences > you@example.org > End-To-End Encryption,点击 OpenPGP Key Manager,导入 gpg --export-armor
导出的公钥。然后点击 Add Key... > Use your external key though GnuPG > Continue,输入 gpg --list-key --keyid-format LONG
的主钥 ID。
Thunderbird 不支持签名输 smart card 的 PIN。 支持的,将 gpg-agent 的 --pinentry-program
设置为一个图形化的,如 pinentry-qt
即可。
发件时选择 Security > Digitally Sign This Message,并在 Security > OpenPGP > Attach My Public Key。
XFCE
XFCE 桌面环境会在登录时启动 ssh-agent,抢占了 gpg-agent-ssh 的位置。可以用如下命令关掉。
$: xfconf-query --create -c xfce4-session -p /startup/ssh-agent/enabled -t bool -s false
GNOME
GNOME keyring daemon 的 SSH 组件也会自动启动,抢占 ssh-agent,可使用下面的内容创建 ~/.config/autostart/gnome-keyring-ssh.desktop
。
[Desktop Entry]
Type=Application
Hidden=true
Android
Android 下最多人用的 OpenPGP 应用是 OpenKeychain。在 Settings > Experimental Features 中打开 Allow untested USB Devices,然后插入 key(我的手机 NFC 功率太小,扫不到小卡)、点击允许然后导入就可以自动配置好。
我喜欢使用 Termux 在 Android 上 SSH 进服务器和处理 git 仓库。使用 OkcAgent 可以在 Termux 中用 OpenKeychain 验证登录 SSH。打开 OkcAgent App 配置好后在 Termux:
$: pkg install okc-agents
$: vim .bashrc
if ! pgrep okc-ssh-agent > /dev/null; then
okc-ssh-agent > "$PREFIX/tmp/okc-ssh-agent.env"
fi
source "$PREFIX/tmp/okc-ssh-agent.env"
Admin applet
Admin applet 也有一个独立的密码,默认为 123456
。可以使用该密码在 CanoKey Web Console(需使用 Chromium 访问)重置其他 applet、设置硬件属性或开启 DFU。
开启 DFU 后,可以在 WebUSB DFU 读写固件,从而读出 canokey-stm32 上的密钥!请一定记得在 Web Console 修改该密码。
Admin applet 的密码丢失后无法恢复,只能使用该脚本清空所有数据重置。传入的参数是 pcsc_scan
的结果,在 CanoKey LED 闪烁时触摸,一共需要触摸五次。
CanoKey Pigeon
更新:CanoKey Pigeon 到啦,功能方面完全一致就不另外介绍啦。加解密速度很快,canokey-stm32 要闪十几秒 Pigeon 不到一秒,附两张图。