YubiKey 支持多种协议,或者说使用方式、模式,ykman 里称作「application」(应用程序)。很多程序支持多种 application。本文按 application 分节,记录一些自己的研究结果,并不全面。要全面的话,还请参考 ArchWiki 的 YubiKey 页面或者 YubiKey 官方文档。
在 Arch Linux 上,YubiKey 插上就可以用了,不需要特别的驱动方面的设置。有可能某些程序需要装个 libfido2 包来使用它。
Yubico OTP
插上之后,它会有一个键盘设备。摸一下,它就发送一长串字符然后回车。这串字符每次都不一样,并且需要与 Yubico 的服务器通信来验证是否有效。这串字符使用 AES 对称加密,也就意味着 Yubico 服务器也有私钥(你也可以自建服务器自己用)。
所以这是个没什么用的功能。并且在拔下设备时很容易误触,插到 Android 设备上之后输入法的屏幕键盘还可能不出来。所以我把它给禁用了:
ykman config mode FIDO+CCID
FIDO2 / U2F
这个 application 在 Android 上第一次使用的时候会提示设置 PIN。我已经设置好了,也不知道在电脑上会如何。需要注意的是,这个 PIN 可以使用字母,并不需要是纯数字。最多可以连续输错八次,但没有 PIN 被锁之后用来解锁的 PUK。
WebAuthn / Passkey
插上就可以在火狐和 Google Chrome 里使用了。可以在 https://webauthn.io/ 测试。作为可以直接登录的 passkey 使用的话,会要求输入 PIN 和触摸。如果仅仅是作为二步验证使用(比如 GitHub),则只需要触摸即可。
Android 上也是差不多的。不过 Android 支持把 passkey 存储在设备里(还会通过 Google 账号同步),使用 YubiKey 时需要从弹窗中选取「使用其它设备」。如果网站已经在设备里存储了 passkey,那么没有使用 YubiKey 的机会。
SSH
OpenSSH 客户端需要安装 libfido2 包才能支持这些 -sk 结尾的 key。服务端不需要这个库。
有多个选项,具体参见 SSH Resident Key Guide。我总结了两种比较好的使用方式:
ssh-keygen -t ed25519-sk -O resident -O verify-required ssh-keygen -t ed25519-sk -O no-touch-required
可以选择的 key 类型只有ecdsa-sk
和ed25519-sk
,并不支持 RSA。resident
选项是把 key 存储到 YubiKey 上,之后可以通过ssh-keygen -K
下载回来。如果不加这个选项的话,那么仅凭 YubiKey 是无法使用的,得同时有生成的文件。verify-required
是验证 PIN。默认是需要触摸的,可以用no-touch-required
选项关闭,但是需要服务端在 authorized_keys 里设置这个选项。
从安全角度考虑,如果 YubiKey 丢失,那么仅凭该设备不应当能获得任何权限——所以在使用 resident 密钥时必须验证 PIN(我总不能赌偷或者捡到的人猜不中我的用户名或者访问的服务器吧)。这与自动化执行 SSH 命令相冲突。另一种使用方式,不需要 PIN、不需要触摸,倒是很方便自动化,也可以防止私钥被运行的程序偷走或者自己失误泄露,但是需要服务端设置no-touch-required
选项,而 GitHub 和 GitLab 并不支持。倒是可以不同场合使用不同的 key,但是管理起来太复杂了。
resident 密钥倒是可以使用 ssh-add 加载到 ssh-agent 里,之后应该就不需要交互即可使用了。但我现在启动系统要输入硬盘密码,登录到桌面并日常使用的话,还要输入用户密码和火狐主密码,已经够多了,不想再加一个 PIN。所以我还是不用了吧。
我倒是想给 termux 里的 ssh 用 YubiKey,毕竟手机上一堆乱七八糟的闭源程序,外加系统已经失去更新,感觉不怎么安全。但是搜了一圈看起来并不支持。
PAM
安装 pam_u2f 包,然后使用 pamu2fcfg 生成个文件。最后去改 PAM 配置就好啦,比如在 /etc/pam.d/sudo 开头加上
auth sufficient pam_u2f.so cue userpresence=1
这样会用触摸 YubiKey 来认证用户。如果把 YubiKey 拔了,pam_u2f 会被跳过。但是 YubiKey 正常的情况下,没有办法跳过 pam_u2f,所以通过 ssh 登录的时候会很难受……好吧,用 pam_exec 还是有办法跳过的,但是它似乎读不到环境变量,只能放个文件来控制,所以还是很麻烦。最好的办法是我在 pam_u2f 运行的时候按一下 Ctrl-C,它就放弃掉就好了,但这个 issue 已经等了快要六年了。
LUKS
cryptsetup 并不直接支持 FIDO2。要使用 systemd-cryptenroll 来添加 keyslot:
sudo systemd-cryptenroll --fido2-device=auto /dev/disk/by-partlabel/XXX
可以用sudo cryptsetup luksDump /dev/disk/by-partlabel/XXX
命令看到 systemd 不光添加了一个 keyslot,还同时添加了一个 token 用于存储一些配置信息。
解密:
sudo systemd-cryptsetup attach XXX /dev/disk/by-partlabel/XXX '' fido2-device=auto
或者用 cryptsetup open 也行。但因为添加的 slot 是需要 PIN 的,cryptsetup open 不加 token 相关的选项时会跳过该 slot,直接问你密码。
sudo cryptsetup open --token-only /dev/disk/by-partlabel/XXX XXX
配置好之后,解密 LUKS 设备就不需要输入又长又复杂的密码啦。不过最好还是时不时验证一下自己还记得密码,要是需要用的时候才发现密码因为长期不用而遗忘了就不妙了。我的系统硬盘本来解密的次数就少,就不用它了,只给备份硬盘用了。
OpenPGP
ykman 要管理 OpenPGP 智能卡应用,需要启用 pcscd 服务,但是 GnuPG 可以不用它。
sudo systemctl enable --now pcscd.socket
要让 ykman 和 GnuPG 能同时访问 YubiKey,可能还需要以下设置:
pcsc-driver /usr/lib/libpcsclite.so card-timeout 5 disable-ccid pcsc-shared
YubiKey 所有不同 application 的 PIN 是分开的。OpenPGP application 有 PIN 和管理 PIN,默认各能试三次。使用 key 的时候会用到 PIN,导入 key 的时候会用到管理 PIN。初次使用的时候记得用ykman openpgp access
命令把默认的 123456 和 12345678 都给改了(不知道为什么我没找到在gpg --card-edit
里更改管理 PIN 的方法)。导入的教程可以参考官方文档的 Importing keys。我的型号是 YubiKey 5C Nano,是支持 ed25519 / cv25519 算法的。
把 key 导入到 YubiKey 之后,可以再用ykman openpgp keys set-touch
设置一下哪些操作需要触摸。默认是都不需要的。然后正常使用就可以了。
要注意的是,YubiKey 只存储了私钥,所以本地要有公钥才可以正常使用。所以要换个系统使用的话,一种办法是把公钥上传到 OpenPGP 服务器上然后导入,另一种办法是自己导出成文件再导入。
SSH 也可以用 OpenPGP 密钥,所以也能用 YubiKey 上的 OpenPGP 密钥。甚至还能把现有的 ed25519 SSH key 导入进去用(不过我没有尝试)。
PIV
这个 PIV 涉及 PKCS#11,有点复杂。暂时不想研究。