最近入手了一个 Yubikey 5 。这个 U 盘大小的小玩意就像是一把钥匙,一些需要输入传统密码的地方可以用插入 Yubikey 来代替。
我琢磨出了一些使用 Yubikey 装逼的方法,在这里做一些总结。
这并不是 Yubikey 的使用教程,只是一些另类的使用方法的总结。
本文所述内容,除特殊说明外,均使用了 Yubikey 的 FIDO 功能,需要预先设置好并打开 FIDO 功能。
配置 PAM 模块
以下操作需要先安装 pam-u2f 包,并且需要预先配置好 Yubikey 的 FIDO 功能。
添加密钥
用 pamu2fcfg
工具添加密钥:
mkdir ~/.config/Yubico
pamu2fcfg -o pam://hostname -i pam://hostname > ~/.config/Yubico/u2f_keys
触摸Yubikey以确认添加。
如果主机名发生变化,例如由于不同网络中的 DHCP ,您将无法登录。
为了防止这种情况,建议指定上述选项并替换 hostname 为实际的主机名。
如果您拥有多个密钥,可使用以下指令附加: 我试过了,会有问题
pamu2fcfg -o pam://hostname -i pam://hostname -n >> ~/.config/Yubico/u2f_keys
创建预设
新建文件 /etc/pam.d/auth-yubikey
并添加如下内容:
auth sufficient pam_u2f.so nouserok origin=pam://hostname appid=pam://hostname
这里的文件名可以随意修改,但必须在 /etc/pam.d/ 目录中。
请务必将其中的 hostname 修改为自己的主机名!
ArchWiki中将上述条目添加到各个 PAM 配置文件的第一行。这里还可以将这条比较长的文本写入一个单独的配置再 include 。
sudo 验证
在更改配置之前,请使用 ROOT 权限(例如 sudo -s)启动一个单独的 shell 。
这样,如果出现问题还可以还原更改。
设置
打开 /etc/pam.d/sudo
并添加以下内容作为第一行
auth include auth-yubikey # 注意这里需要填之前创建的文件名
使用
插入 Yubikey 并执行 sudo
,Yubikey 的指示灯会闪烁,屏幕上不会有提示信息。此时会有一个超时,触摸后命令被执行,若未触摸而超时,会继续要求输入密码。
如果没有此时没有插入设备,会要求输入密码。
这项设置也适用于其他调用 sudo 操作的程序,比如 yay 。
Polkit 验证
打开 /etc/pam.d/polkit-1
,添加如上条目。
当弹出 Polkit 授权窗口时,Yubikey 指示灯会闪烁,直接触摸可完成授权。
有一个不算 BUG 的 BUG :
闪烁时如果想输入密码,输入完毕后窗口会变灰等待 Yubikey 响应。
即一旦插入了 Yubikey ,就必须使用 Yubikey 授权。
解决方法很简单,拔掉密钥即可。
SDDM 登陆
即 KDE 的开机登陆界面。
打开 /etc/pam.d/sddm
,添加如上条目。
与 Polkit
不一样,这里有密码框但不会立刻触发 Yubikey 验证,需要敲击回车才能触发。可输入任意字符。
敲击回车后触摸可完成验证。
KDE5 锁屏验证
和 SDDM 长的一样但是它由不同的模块负责。
打开 /etc/pam.d/kde
,添加相同条目。
效果与 SDDM 一致。
TTY 登陆
打开 /etc/pam.d/system-login
,添加相同条目。
登陆 TTY 时输入完用户名不会要求输入密码,此处会触发 Yubikey 验证,触摸即可进入 Shell 。
LUKS 开机解密
参考我的另一篇博客。
SSH 密钥
SSH 中 Yubikey 有多种用途,你想找的是:
- 为 SSH 密钥添加双因子认证
- 作为密钥进行 SSH 验证
- 在远程服务器上进行 PAM 验证
作为 SSH 密钥的双因子认证
这个简单。
需要客户端和服务端都支持 ecdsa-sk/ed25519-sk
密钥类型,OpenSSH 8.2 之后的版本都可以。
GitHub 也支持 ecdsa-sk/ed25519-sk
类型的公钥。
客户端需要安装 libfido2
包。
生成密钥对
插入 Yubikey ,并执行下面的指令。
生成 ECDSA 密钥
ssh-keygen -t ecdsa-sk
生成 Ed25519 密钥
ssh-keygen -t ed25519-sk
Ed25519-sk 密钥类型需要固件版本 5.2.3
以上。
和正常生成密钥对一样,只是需要输入 PIN 并触摸。
使用私钥登陆
登陆时,私钥、私钥密码字段(如果有的话)、Yubikey 缺一不可,即在传统的 SSH 密钥登陆上多了一个 Yubikey 验证的步骤,提高了安全性。
作为密钥进行 SSH 验证
Yubikey 可以用作基于 OpenPGP 或 PIV 的 SSH 硬件密钥。即将硬件作为私钥载体,随插随用。
配置前需要打开 Yubikey 的 PIV 功能。
PIV
有两种方式:
OpenPGP 密钥
需要使用具有 A 功能的密钥对,设置很方便,可以兼容传统的密钥登陆,而且可以即插即用,免密登陆(只需要验证第一次)。
接下来介绍 GPG 密钥的设置方法。
准备密钥
验证 SSH 连接需要一个具有 A 功能的密钥对。如果已经创建了主密钥,可以创建一个 A 功能的子密钥。如果还没有创建主密钥,可以直接生成一个带有 A 功能的主密钥。
创建主密钥
gpg --full-gen-key
GPG 默认只能创建 RSA 、 DSA/Elgamal 类型的密钥,如果想创建 ECC 类型的密钥或者创建带 A 功能的主密钥可以添加 --expert
参数。
gpg --expert --full-gen-key
此处会提示:
请选择您要使用的密钥类型:
(1) RSA 和 RSA (默认)
(2) DSA 和 Elgamal
(3) DSA(仅用于签名)
(4) RSA(仅用于签名)
(7) DSA(自定义用途)
(8) RSA(自定义用途)
(9) ECC 和 ECC
(10) ECC(仅用于签名)
(11) ECC(自定义用途)
(13) 现存的密钥
(14)卡中现有密钥
您的选择是?
这里可以选择 11 ,然后直接创建一个带A功能的主密钥。这里我选择 9 ,方便之后演示生成子密钥。
请选择您想要使用的椭圆曲线:
(1) Curve 25519
(3) NIST P-256
(4) NIST P-384
(5) NIST P-521
(6) Brainpool P-256
(7) Brainpool P-384
(8) Brainpool P-512
(9) secp256k1
您的选择是?
第一个椭圆曲线对应的算法就是熟悉的 Ed25519 ,生成的 SSH 公钥也是 ssh-ed25519
格式的,此处选择这个。当然也可以选择其他的选项。
接下来填写相关的信息,一个主密钥就创建好了。
如果主密钥需要转移到 Yubikey 中,它必须设置为永不过期。
创建子密钥
如果已经创建好 A 功能的主密钥,可以跳过此步骤。
GPG 默认只能创建 S 和 E 功能的子密钥,如果想自定义子密钥,需要添加 --expert
参数。注意参数放的位置,必须放在 --edit-key
前面。
gpg --expert --edit-key <KEY_ID>
进入 GPG 命令行后输入 addkey
创建子密钥。反馈如下
请选择您要使用的密钥类型:
(3) DSA(仅用于签名)
(4) RSA(仅用于签名)
(5) ElGamal(仅用于加密)
(6) RSA(仅用于加密)
(7) DSA(自定义用途)
(8) RSA(自定义用途)
(10) ECC(仅用于签名)
(11) ECC(自定义用途)
(12) ECC(仅用于加密)
(13) 现存的密钥
(14)卡中现有密钥
您的选择是?
这里我们要创建一个具有 A 功能的子密钥,只能选择 7 、8 或 11 。这里选择 11 。
ECDSA/EdDSA 密钥的可实现的功能: 签名(Sign) 身份验证(Authenticate)
目前启用的功能: 签名(Sign)
(S) 签名功能开关
(A) 身份验证功能开关
(Q) 已完成
您的选择是?
这里我们只需要 A 功能,建议仅开启 A 功能,关闭其他功能。如果需要其他的功能,最好创建单独的子密钥。因为 Yubikey 的不同 GPG 插槽只具备一个功能,将密钥导入某个特定功能的插槽后,该密钥携带的其他功能将不可用。
请选择您想要使用的椭圆曲线:
(1) Curve 25519
(3) NIST P-256
(4) NIST P-384
(5) NIST P-521
(6) Brainpool P-256
(7) Brainpool P-384
(8) Brainpool P-512
(9) secp256k1
您的选择是?
根据喜好选择,我选择 1 。然后填写其他信息,带有 A 功能的子密钥就创建好了。
配置 GPG-Agent 和 SSH 验证
OpenSSH 本身并不支持 OpenPGP 物理密钥的验证,需要借助 GPG-Agent 来进行。这个工具类似 SSH-Agent ,开启前最好先关闭 SSH-Agent 。
GPG 带有默认启用的 systemd 用户 Socket ,会在需要的时候自动调用,并不需要手动启动服务。
打开 GPG-Agent 的 SSH 支持。打开 ~/.gnupg/gpg-agent.conf
,添加一行文本:
enable-ssh-support
由于我们关闭了 SSH-Agent ,本意上是要 GPG-Agent 接管 SSH-Agent 的工作。如果配置了 SSH-Agent 的环境变量记得先删除。
添加环境变量,使默认的 SSH-Agent 指向 GPG-Agent 。编辑 ~/.pam_environment
。
SSH_AGENT_PID DEFAULT=
SSH_AUTH_SOCK DEFAULT="${XDG_RUNTIME_DIR}/gnupg/S.gpg-agent.ssh"
如果系统中安装了 Gnome-Keyring ,还需要停用其 SSH 组件,否则它将覆盖之前的环境变量。
在 ~/.pam_environment
中添加
GSM_SKIP_SSH_AGENT_WORKAROUND DEFAULT=1
然后,我们需要将 GPG 密钥的指纹添加到 GPG-Agent 中。
添加指纹非必要步骤。添加后如未插入 Yubikey ,系统会提示插入。不添加则不会。
执行以下指令
gpg --with-keygrip -K
复制带有 A 标签的密钥的 Keygrip 值,将其放入~/.gnupg/sshcontrol
文件中。
就好了。
应用 GPG-Agent 设置
最简单的方法是重启电脑。
获得 SSH 公钥
使用以下指令
gpg --export-ssh-key <KEY_ID>
即可导出 SSH 公钥,将其放到目标主机的 ~/.ssh/authorized_keys
中即可,也可以添加到 GitHub 中。
下面都以 GitHub 为例来进行测试。
也可以通过 GPG-Agent 来获得公钥。执行
ssh-add -L
应该会获得和上一条指令一样的输出,这就说明配置成功了。可以连个 GitHub 测试一下了。
应该会看到这样的提示:
Hi nwntech! You've successfully authenticated, but GitHub does not provide shell access.
Connection to github.com closed.
说明 SSH 密钥配置正确了。
转移密钥
转移主密钥到 Yubikey 中
先备份一下密钥
gpg --export-secret-key --armor KEY_ID > 文件名
--armor
参数代表导出 ASCII 格式的文本,参数的位置也必须要在密钥 ID 之前。
进入 GPG 命令行
gpg --edit-key KEY_ID
执行
toggle
keytocard
转移完后,本机不再包含真正的密钥,只有一个指示它存储在智能卡上的指针。你依然可以在列表中看到它,但每次操作主密钥时都会要求插入密钥并验证 PIN 。
将子密钥转移到 Yubikey 中
此时拔除 Yubikey ,测试 SSH 的连接,发现还是可以连通的,不过等到转移完子密钥之后,就需要验证密钥和 PIN 了。
进入 GPG 命令行:
gpg --edit-key <KEY_ID>
提示
gpg (GnuPG) 2.2.29; Copyright (C) 2021 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
私钥可用。
sec ed25519/97F30xxxxxxAC7DD
创建于:2021-08-22 有效至:永不 可用于:SC
卡号: 0xxx 1xxxxxxx
信任度:绝对 有效性:绝对
ssb cv25519/5346xxxxxx8AFC1
创建于:2021-08-22 有效至:永不 可用于:E
ssb ed25519/5553xxxxxxB50825
创建于:2021-08-22 有效至:永不 可用于:A
[ 绝对 ] (1). xxxxx <x@x>
gpg>
找到 A 功能所在子密钥的位置,即前缀为 "ssb" 的条目中 A 功能子密钥的顺序。如上, A 功能的子密钥序号为 2 。
选择子密钥。注意导入子密钥只能一个一个导入,如果不选择子密钥就会导入主密钥。
key 2
导入
keytocard
就完成了。接下来也可以将其他子密钥导入 Yubikey 中。
同样的,本机也不再包含子密钥,只有一个指示它存储在智能卡上的指针。
测试连接
此时再连接 SSH 时就应该要求插入物理密钥了。
小结
这个方法其实和我随插随用的想法有些区别,将其插入其他设备上使用时还是需要进行额外的配置,但是相比其他方法,只输入一次密码还是要比每次都输入密码要方便得多的。
在远程服务器上进行 PAM 验证
应该不会有人会这么做吧,啊?