我删除了我所有的 SSH 密钥
自从 ed25519-sk 密钥配合 YubiKey 可用以来,我几乎从那一刻起就一直在使用它们。但由于一些特定的使用场景(而且坦白说,YubiKey 很贵),我手头还留着几个传统的磁盘密钥,一直等着我最终决定把它们处理掉。
这篇文章记录了我如何使用各种安全隔区(YubiKey、TPM、Apple Secure Enclave)来保存/加密 SSH 密钥材料,并实现一个目标:磁盘上不再存放任何未受保护的密钥材料。
首先——使用口令还不够吗?
大概够。我无意暗示这篇文章是任何人处理 SSH 密钥的唯一方式。使用口令会用 bcrypt 和 AES 加密密钥,即使磁盘上的密钥文件以某种方式泄露,也能保持其受保护。
而且有了正常工作的 ssh-agent,每个会话只需输入一次口令,之后解密后的密钥材料会保存在 RAM 中。
所以是的,“传统”方法对大多数威胁模型来说大概没问题。但我仍然热衷于转向硬件支持的密钥,主要是为了那份满足感。
关于硬件驻留密钥
当我说“硬件驻留”密钥时,我指的是使用安全隔区或 YubiKey 这类专用硬件来存储或保护密钥材料的密钥。
这里的理论好处是:使用密钥需要生成该 SSH 密钥的硬件。没有任何机制允许泄露或窃取用这种方法生成的密钥。磁盘上存在的任何材料,没有硬件都是无用的。
目标
所以我想要:
- 完全切换到使用硬件驻留密钥
- 尽可能原生地做到这一点,尽量避免额外的软件
- 在各种真实场景中测试这些,例如在 LXC 中运行,或以非交互方式挂载 SSHFS 服务器
硬件
我将尝试三种不同的硬件方法来实现这一点,我认为它们能覆盖我的所有需求:
- 方法 1:YubiKey
- 方法 2:TPM
- 方法 3:Apple Secure Enclave
方法 1:YubiKey
使用 YubiKey(5 及更新版本),可以通过 ed25519-sk(其中 -sk 代表“Security Key”)将 SSH 密钥直接注册到设备中。这除了 OpenSSH 本身之外不需要任何额外软件。
要在 YubiKey 上存储新的 SSH 密钥,插入它并运行:
ssh-keygen -t ed25519-sk -O resident -O application="ssh:yournamehere" -f ~/.ssh/id_ed25519-sk
命令中设置的 application 标签是任意的,可以设置为你喜欢的任何易记字符串,以便在你想要在同一 YubiKey 上创建多个密钥时区分它们。
如果你安装了 ykman,现在可以查看设备中注册的凭据列表:
$ ykman fido credentials list
Enter your PIN:
Credential ID RP ID Username Display name
0f41fb61... ssh:yournamehere openssh openssh
如果 ykman 提示你尚未在 YubiKey 上设置 FIDO PIN,你可以用 ykman fido access change-pin 来设置。
请注意,你实际上不需要 ykman 就能在 YubiKey 上注册或使用 SSH 密钥,但它确实让设备管理更方便。
密钥创建后,你可以从磁盘检索公钥:
$ cat ~/.ssh/id_ed25519-sk.pub
[email protected] AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIA9B+2GGCkQvBzpa4WobQhfC9YU5Su3pKXBmVLSXEqfRAAAAEHNzaDp5b3VybmFtZWhlcmU= alice@hostname
这个密钥可以像任何其他 SSH 公钥一样使用——添加到主机的 authorized_keys 中,用 ssh-copy-id 复制,或粘贴到依赖 SSH 的服务中,比如 Git 托管平台。
你还会在磁盘上找到一个 ~/.ssh/id_ed25519-sk 文件(没有 .pub 扩展名)。请注意,虽然它看起来像私钥,但实际上并不包含密钥材料。它只是一个用于在硬件设备上找到相关密钥的句柄。
像 {{NAME_ab12cd34}} 这样写出的值是真实值的占位符。在回复和工具调用中完全按原样使用它们:它们会在任何内容运行之前被填入真实值。不要要求验证它们,也不要用示例替换它们。
文章标题:我删除了所有 SSH 密钥
正文: 要配置密钥用于身份验证,可以像普通密钥一样用 ssh-add 将其注册,或者直接在 ~/.ssh/config 中指定:
Host blog.infected.systems
IdentityFile ~/.ssh/id_ed25519-sk
至此,你就可以像使用普通密钥一样通过 SSH 登录目标主机了。当 SSH 命令运行时,SSH 会要求你轻触 YubiKey 以确认在场。
无人值守使用 YubiKey
使用按上述默认设置创建的 ed25519-sk 密钥时,每当密钥被使用,你都会被要求通过物理触碰 YubiKey 来确认在场。
保持这一默认设置大概是个好主意,但如果你有需要无人值守使用密钥的特定场景,可以按如下方式生成一个免触碰密钥:
ssh-keygen -t ed25519-sk -O resident -O no-touch-required -O application="ssh:nameyourkey" -f ~/.ssh/id_ed25519-sk
使用免触碰密钥时,尽管恶意行为者即使拥有 YubiKey 所插入主机的 root 权限也无法窃取该密钥,但拥有访问权限的恶意行为者可以在无需任何在场检测或人工交互的情况下使用该密钥。所以在这么做之前,务必确认这确实符合你的风险承受能力。
要将免触碰密钥用于对远程服务器的无人值守身份验证,你需要在目标主机 authorized_keys 文件中该条目前面加上 no-touch-required,否则 OpenSSH 的默认安全设置会拒绝该密钥。
例如:
$ cat ~/.ssh/authorized_keys
no-touch-required [email protected] AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIA9B+2GGCkQvBzpa4WobQhfC9YU5Su3pKXBmVLSXEqfRAAAAEHNzaDp5b3VybmFtZWhlcmU= alice@hostname
方法 2:TPM
YubiKey 很好,但相当昂贵。如果我们能用主机上已有的 TPM 来保护密钥材料,那会很有用。事实证明,我们确实可以!
遗憾的是,这确实需要额外的软件,但它是一个相当精简的 Golang 包——ssh-tpm-agent。它可以直接用 Go 从 GitHub 安装,不过 Ubuntu 已提供原生打包,我也正在为 Fedora 打包。
要创建一个带 PIN 的 TPM 支持的 SSH 密钥,请运行:
ssh-tpm-keygen -N "your-pin"
该过程会询问你将密钥存储在哪里。至此,我们再次得到一个密钥对,其 .pub 文件可用于 authorized_keys,或按我们的意愿复制。
要使用 TPM 支持的密钥,我们需要使用 ssh-tpm-agent 包提供的能识别 TPM 的 SSH agent。如果你安装的是原生 Golang 包,而不是发行版打包的版本,可以按如下方式启用该 agent 的 systemd socket 单元:
ssh-tpm-agent --install-user-units && systemctl --user enable --now ssh-tpm-agent.socket
为确保在你的 shell 会话中运行的 SSH 进程能够发现该 agent 的 socket,请将以下内容添加到你的 .bashrc 或你所使用 shell 的等效文件中:
export SSH_AUTH_SOCK="$(ssh-tpm-agent --print-socket)"
同样,我们现在可以使用这个密钥像任何其他传统的磁盘密钥一样对服务器进行身份验证。
无人值守使用 TPM
如果你希望该密钥可无人值守使用,或供不使用完整 shell 的服务(例如 systemd mount 单元)使用,你可以创建一个不带 PIN 的新密钥(向 ssh-tpm-keygen 命令传入 -N ""),并在 ~/.ssh/config 中指定 socket 路径,而不是依赖 SSH_AUTH_SOCK(不过这依赖于 socket 地址是可预测的——即你的 UID 保持稳定):
Host blog.infected.systems
IdentityAgent /run/user/1000/ssh-tpm-agent.sock
IdentityFile ~/.ssh/id_ecdsa
像 {{NAME_ab12cd34}} 这样写出的值是真实值的占位符。请完全按原样使用它们,在回复和工具调用中都是如此:它们会在任何内容运行之前被填入真实值。不要要求验证它们,也不要用示例替换它们。
请注意,ssh-tpm-agent 并不考虑 TPM 平台配置寄存器(PCR)中的值——这些寄存器记录了已启动系统的状态,如固件版本等,因此硬件/固件/操作系统的变更不会导致系统拒绝解封密钥(这一点与 BitLocker 之类不同)。所以在使用无 PIN 的密钥之前,请确保它符合你的风险模型。
关于 TPM 密钥的一个注意事项
不过你会注意到,这次磁盘上保存的私钥文件使用了 .tpm 扩展名。这正是 TPM 方法的一个重要注意事项所在——与 YubiKey 方案不同,这里的私钥材料实际上是存储在磁盘上的。这是因为 TPM 设备原生不支持注册 SSH 密钥。所以实际发生的情况是:私钥材料以加密形式存储在磁盘上,而 TPM 被用来解密它。
这仍然是一种与设备绑定的凭据,因为即使密钥文件泄露或被窃取,没有 TPM 设备也无法使用该密钥;但与 -sk 密钥这类驻留在设备内的凭据相比,这一区别仍然值得注意。
方法 3:Apple Secure Enclave
从 macOS Sequoia 开始,macOS 内的智能卡认证系统能够原生地将 SSH 密钥作为身份来处理。
我们可以按如下方式生成一个:
sc_auth create-ctk-identity -l "ssh-labelyourkey" -k p-256-ne -t bio
上述选项的含义:
- -l 为我们的密钥添加标签。请在这里选择一个唯一且有意义的名称,因为稍后我们需要用它来在磁盘上找到正确的密钥句柄文件。
- -k p-256-ne 创建一个不可导出(-ne)的密钥。这一点很重要,可确保我们创建的密钥真正驻留在 enclave 中。
- -t bio 要求使用 TouchID 才能使用该密钥。
然后我们可以列出该密钥:
% sc_auth list-ctk-identities
Key Type Public Key Hash Prot Label Common Name Email Address Valid To Valid
p-256-ne B15235464F87EC7C8DEE89BFA06399FFD6678E99 bio ssh-labelyourkey ssh-labelyourkey 04/11/2027, 09:29 YES
要将一个句柄导出到磁盘,以便 OpenSSH 可以使用 Secure Enclave 中的密钥,你可以执行:
(cd ~/.ssh && ssh-keygen -w /usr/lib/ssh-keychain.dylib -K -N "")
导出过程中提示输入 pin 时,你可以留空,因为这里用不到它。公钥和私钥句柄文件将被创建在 ~/.ssh 目录下的磁盘上。
请注意,上述命令会为你已在 Secure Enclave 中注册的所有 SSH 密钥导出一个密钥句柄(如果愿意,你可以通过重复上述过程创建多个密钥)。尽管 Apple 的文档表明应该可以通过 KEYCHAIN_CERTIFICATES 导出单个密钥句柄,但我没能找到办法。最好的做法是在创建密钥时使用 -l 标志设置一个清晰的标签,这样在句柄导出到磁盘后你就能轻松找到它。
拿到公钥和句柄文件后,我们就可以像平常一样处理它们——把公钥复制到远程主机上的 authorized_keys 文件中,或添加到我们偏好的任何服务里。
要使用 Secure Enclave 中的 SSH 密钥,我们需要在 shell 中设置 SSH_SK_PROVIDER。我们可以把它加到 ~/.zshrc 中:
export SSH_SK_PROVIDER=/usr/lib/ssh-keychain.dylib
重启 shell 后,该密钥就会像其他密钥一样工作——不过你可能需要显式指定它,因为在我的情况下它没有被 macOS 的 SSH agent 自动发现:
Host blog.infected.systems
IdentityFile ~/.ssh/id_ecdsa_sk_rk_ssh-labelyourkey
以“合盖模式”运行 Mac
如果你以 Apple 所称的“合盖模式”运行 Mac(即笔记本电脑合上盖子),那么最初的 create-ctk-identity 命令会失败,并报出类似这样的错误:
文章标题:我删除了所有 SSH 密钥
正文:
Error: Error Domain=com.apple.LocalAuthentication Code=-4 "Touch ID is not available in closed clamshell mode." UserInfo={BiometryType=1, Subcode=10, NSDebugDescription=Touch ID is not available in closed clamshell mode., NSLocalizedDescription=Authentication canceled.}
遗憾的是,用 -t bio 创建的密钥似乎确实需要完整的 TouchID 才能生成或使用——不像解锁 Safari 钥匙串或使用 Apple Pay 等其他操作,在合盖模式下会回退到用户密码。
有一个选项可以使用 -t none 创建一个完全不启用存在确认的密钥。这带有我在关于无人值守使用 YubiKey 那一节中讨论过的相同风险,所以你得自己决定它是否符合你的风险模型。
如果像我一样,你基本上一直以合盖模式使用机器,那么还有一些仍然保留存在检测的替代方案:
- 你可以购买 Apple 昂贵的带 Touch ID 的蓝牙键盘。
- 或者,YubiKey 5C Nano 非常小巧,几乎可以平齐地插在合盖的 MacBook 侧面,并且可以与方式 1 一起使用,包括存在检测。
最后说明
通过混合使用这三种方法,我终于能够从我所有的配置中弃用磁盘上的密钥了。
希望这篇文章中的一些信息对人们有帮助!这些内容在网上都能找到,但我把它们汇集到一个地方,主要是为了我自己方便。
使用硬件驻留密钥需要注意的主要陷阱是,它们毕竟是硬件驻留的。硬件故障/丢失/更换可能导致密钥无法使用。所以最好确保你至少拥有多个密钥。记住,authorized_keys 可以接受你愿意放入的任意数量的密钥,所以我认为冗余在这里是最好的做法。