最近入手了一个 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

    有两种方式:

    • 一种是在 Yubikey 中生成自签证书,并使用这个证书进行证书登陆。这个方法涉及证书、 CA 以及 SSH 的证书验证方式,与传统的密钥验证有一定差别。关键的是,这种方法生成的公钥并不能用来登陆 GitHub ,暂时先不做研究。
    • 另一种是使用 PKCS#11 进行密钥验证。这种证书生成方便,且兼容 GitHub 。美中不足的是,每次登陆都需要输入 PIN 。当然可以搭配 SSH-Agent 来实现只输入一次 PIN 就能多次使用的效果,但是和我的预想还是相去甚远。
  • 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 验证

应该不会有人会这么做吧,啊?

参考资料

Universal_2nd_Factor - ArchWiki

Yubikey - ArchWiki

GnuPG#SSH_agent - ArchWiki

Importing keys - dev.Yubico

使用 GPG 密钥进行 SSH 认证 - HydricAcid