note/canokey

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 > [email protected] > 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 不到一秒,附两张图。

About Me