跳到主要内容
ecdsa.com
← 全部文章

SSH 证书与 authorized_keys 对比:CA 和主体

authorized_keys 的扩展方式是把密钥复制到每台服务器上,而且永远不能忘记删除。OpenSSH 证书改用一个受信任的 CA、短期有效的已签名密钥,并用名称代替指纹。下面是完整的配置过程,已在 OpenSSH 10.0 上测试,并列出了它会产生的每一种失败。

简短回答:使用 authorized_keys 时,每台服务器都要列出每一个允许登录的公钥,访问权限一直有效,直到有人删掉对应的那一行。使用 OpenSSH 证书时,服务器信任一个用户 CA(TrustedUserCAKeys)。用户自己的密钥经 ssh-keygen -s 签名,签名时写入身份(-I)、可登录的账号(主体,即 principal,用 -n 指定)和有效期(-V)。客户端信任一个主机 CA(known_hosts 中的 @cert-authority),而不是在首次连接时逐台接受主机指纹。短期证书让人员离开后的权限回收自动完成,其余情况由密钥吊销列表(RevokedKeys)处理。代价是你现在要运营一个 CA,并且必须保护好它的密钥。

authorized_keys 做不到什么

~/.ssh/authorized_keys 很简单,没有需要维护的环节,所以随处可见。人员和服务器一多,它的局限就显现出来了:

  • 访问权限永不过期。2023年为外包人员添加的密钥到2026年仍然有效,除非有人在它被复制到的每一台服务器上把它删掉。(每一行都可以设置 expiry-time=,但得有人去设。)
  • 没有身份信息。每一行只是一个密钥加一段注释。它属于哪个人、哪个团队或哪个作业,即便有记录,也只记在某张电子表格里。
  • 分发要你自己解决。添加或移除一个人,就要动每一台服务器,靠配置管理工具或者手工操作。
  • 主机密钥靠首次使用时信任。每个客户端都在第一次连接时接受每台服务器的指纹,而重建一台服务器会让所有客户端都报出 REMOTE HOST IDENTIFICATION HAS CHANGED。

OpenSSH 证书从 OpenSSH 5.4(2010年)开始提供,这四个问题都能解决。它不是 X.509:格式是 OpenSSH 自己定义的,即一个公钥加上身份和约束条件,由 CA 密钥签名,而 CA 密钥本身也只是一个普通的 SSH 密钥。2025年,格式说明从 OpenSSH 源代码中的 PROTOCOL.certkeys 文件移到了一份互联网草案(Internet-Draft)中,也就是现在 IETF SSHM 工作组的 draft-ietf-sshm-cert。

两个 CA,两个方向

你需要两个独立的签名密钥,因为它们回答的是不同的问题:

  • 用户 CA 回答“这个密钥能否以这个账号登录”。信任它的是服务器。
  • 主机 CA 回答“这台主机真的是 web01 吗”。信任它的是客户端。
创建 CA(OpenSSH 10.0p2)
$ ssh-keygen -q -t ed25519 -f user_ca -C "Example user CA" -N ""
$ ssh-keygen -q -t ed25519 -f host_ca -C "Example host CA" -N ""

这里用 -N "" 只是为了让实验保持简单。真正的 CA 密钥应当放在 HSM、硬件令牌或签名服务中(HashiCorp Vault 的 SSH 机密引擎、smallstep、Teleport 以及各云服务商的同类服务都能做到)。谁持有用户 CA 密钥,谁就能以服务器允许的任何用户身份登录。

用户证书

为 Alice 现有的公钥签名,有效期 8 小时,适用于 alice 和 deploy 两个账号:

ssh-keygen -s:签发用户证书
$ ssh-keygen -s user_ca -I alice@example.test -n alice,deploy -V -5m:+8h -z 1001 id_ed25519.pub
Signed user key /home/alice-laptop/id_ed25519-cert.pub: id "alice@example.test" serial 1001 for alice,deploy valid from 2026-10-20T13:14:08 to 2026-10-20T21:19:08
  • -I 是密钥 ID。sshd 每次登录都会把它写入日志,所以应当用人员或作业命名,而不是主机名。
  • -n 列出主体,即这张证书可以代表的名称。ssh-keygen 手册说,没有主体的证书“对所有用户或主机有效”,但通过 TrustedUserCAKeys 信任 CA 时,sshd 10.0 会拒绝这样的用户证书(Certificate lacks principal list)。始终设置 -n。
  • -V -5m:+8h 把有效期设为从 5 分钟前(应对时钟偏差)到 8 小时后。
  • -z 设置序列号,吊销时可以引用它。

ssh-keygen -L 会显示签名的内容:

ssh-keygen -L -f id_ed25519-cert.pub
/home/alice-laptop/id_ed25519-cert.pub:
        Type: ssh-ed25519-cert-v01@openssh.com user certificate
        Public key: ED25519-CERT SHA256:9Mr4hhu4+c6LnVoj+n8tVj4AKD/AP037BQ5pyEAv07s
        Signing CA: ED25519 SHA256:aG9VR0NrG4he7YusGsggg/Gt3WviRbg3Anbfz4HJe60 (using ssh-ed25519)
        Key ID: "alice@example.test"
        Serial: 1001
        Valid: from 2026-10-20T13:14:08 to 2026-10-20T21:19:08
        Principals: 
                alice
                deploy
        Critical Options: (none)
        Extensions: 
                permit-X11-forwarding
                permit-agent-forwarding
                permit-port-forwarding
                permit-pty
                permit-user-rc

默认情况下,用户证书授予全部五项扩展。-O clear 会把它们全部去掉,之后可以只加回需要的扩展,例如 -O clear -O permit-pty。关键选项用于限制证书。force-command 会替换客户端请求的任何命令,source-address 限制证书可以从哪些地址使用。设置 source-address=10.0.0.0/8 后,来自 127.0.0.1 的登录会被拒绝。来源地址在允许范围内时,无论客户端请求什么命令,运行的都是强制命令:

关键选项的实际效果(sshd 日志行来自 LogLevel VERBOSE)
        Critical Options: 
                force-command echo backup only
                source-address 10.0.0.0/8
$ ssh deploy@web01.example.test
deploy@web01.example.test: Permission denied (publickey).
cert: Authentication tried for deploy with valid certificate but not from a permitted source address (127.0.0.1).
Refused by certificate options
...
$ ssh deploy@web01.example.test "rm -rf /tmp/anything"
backup only
Starting session: forced-command (key-option) 'echo backup only' for deploy from 127.0.0.1 port 48976 id 0

证书以 id_ed25519-cert.pub 为文件名放在私钥旁边时,ssh 客户端会自动找到它;否则用 CertificateFile 指定它的位置。ssh-add 会把私钥和证书一起加载到 ssh-agent 中。

配置 sshd

服务器端的全部配置只有几行。下面这个附加配置文件通过了 sshd -t 检查,本页的所有测试都使用它:

/etc/ssh/sshd_config.d/ca.conf
HostCertificate /etc/ssh/ssh_host_ed25519_key-cert.pub
TrustedUserCAKeys /etc/ssh/user_ca.pub
AuthorizedPrincipalsFile /etc/ssh/auth_principals/%u
RevokedKeys /etc/ssh/revoked_keys
PasswordAuthentication no
LogLevel VERBOSE
  • TrustedUserCAKeys 包含一个或多个 CA 公钥。轮换 CA 期间,把新旧两个都列出来。
  • AuthorizedPrincipalsFile 把账号映射到主体。这里 /etc/ssh/auth_principals/deploy 只有一行 deploy,因此只有列出主体 deploy 的证书才能以 deploy 登录。没有这个选项时,sshd 要求主体中包含账号名本身。映射更灵活:可以允许 oncall 这样的主体登录多个账号,AuthorizedPrincipalsCommand 还可以到目录服务中查询主体。
  • RevokedKeys 指向吊销列表(见下文)。
  • LogLevel VERBOSE 会记录每次登录所用证书的密钥 ID 和序列号。这正是 authorized_keys 从来没有的审计记录。

用 Alice 的证书登录,客户端和服务器两边看到的情况:

ssh -v(客户端)和 sshd 日志(服务器),有删节
Server host certificate: ssh-ed25519-cert-v01@openssh.com SHA256:IuzvI4QsLmSx1+6p3yrvlRx2wdl+LUCY1zXqSzssLAc, serial 0 ID "web01" CA ssh-ed25519 SHA256:+iie/YHPulJReHN2GQ8UvrorBVrtayD2T1MGxeg/QCQ valid from 2026-10-20T13:14:08 to 2027-10-19T13:19:08
Host 'web01.example.test' is known and matches the ED25519-CERT host certificate.
Offering public key: /home/alice-laptop/id_ed25519 ED25519-CERT SHA256:9Mr4hhu4+c6LnVoj+n8tVj4AKD/AP037BQ5pyEAv07s explicit
Server accepts key: /home/alice-laptop/id_ed25519 ED25519-CERT SHA256:9Mr4hhu4+c6LnVoj+n8tVj4AKD/AP037BQ5pyEAv07s explicit
Authenticated to web01.example.test ([127.0.0.1]:22) using "publickey".

Accepted publickey for deploy from 127.0.0.1 port 57432 ssh2: ED25519-CERT SHA256:9Mr4hhu4+c6LnVoj+n8tVj4AKD/AP037BQ5pyEAv07s ID alice@example.test (serial 1001) CA ED25519 SHA256:aG9VR0NrG4he7YusGsggg/Gt3WviRbg3Anbfz4HJe60

如果无法修改 sshd_config,单个账号可以通过自己的 authorized_keys 信任 CA。principals= 选项的作用与主体文件相同:

~deploy/.ssh/authorized_keys:为单个账号信任 CA(密钥已截短)
cert-authority,principals="deploy-team" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK63...
$ ssh deploy@web01.example.test 'echo logged in as $(whoami)'
logged in as deploy

主机证书

用主机 CA 为每台服务器的主机密钥签名。-h 表示签发的是主机证书,主体则是客户端连接时使用的名称:

为主机密钥签名
$ ssh-keygen -s host_ca -I web01 -h -n web01.example.test,web01 -V -5m:+52w /etc/ssh/ssh_host_ed25519_key.pub
Signed host key /etc/ssh/ssh_host_ed25519_key-cert.pub: id "web01" serial 0 for web01.example.test,web01 valid from 2026-10-20T13:14:08 to 2027-10-19T13:19:08

然后让 HostCertificate 指向生成的证书(与上面的 sshd 配置文件一样),并在客户端的 known_hosts(或 /etc/ssh/ssh_known_hosts)中加一行,取代每台主机一条指纹:

known_hosts:为一个域名信任主机 CA
@cert-authority *.example.test ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBtSsqoqxwcl31DTJg3kTPsv9eLuuw5gU1BcvYybODlu Example host CA

这样,新建和重建的服务器在第一次连接时就会被信任,不会弹出提示,前提是它们的证书列出了客户端所用的名称。最后这个条件常常出人意料。用一个不在主体中的名称连接时,客户端会退回到普通主机密钥,而启用严格检查时连接就此中止:

以 db01.example.test 连接证书只列出 web01 的主机
$ ssh deploy@db01.example.test
Certificate invalid: name is not a listed principal
No ED25519 host key is known for db01.example.test and you have requested strict checking.
Host key verification failed.

列出客户端会用到的每一个名称,包括短名称、CNAME、IP 地址(如果有人用 IP 连接)以及负载均衡器的名称。ssh-keyscan -c 可以获取服务器的证书供检查,known_hosts 中的 @revoked 行可以拒绝某个特定的主机密钥或 CA。

失败时的表现

对于用户证书的问题,客户端只会显示 Permission denied (publickey).。原因记录在服务器日志中,需要 LogLevel VERBOSE:

情况sshd 日志(OpenSSH 10.0p2)
账号不在证书的主体中(经过映射之后)Certificate does not contain an authorized principal
证书签发时没有使用 -n,也没有配置 AuthorizedPrincipalsFileCertificate lacks principal list
证书已超出 -V 设定的有效期Certificate invalid: expired
序列号或密钥在 KRL 中Authentication key ED25519-CERT SHA256:... revoked by file /etc/ssh/revoked_keys
source-address 不匹配cert: Authentication tried for deploy with valid certificate but not from a permitted source address (127.0.0.1).

有两种情况看起来像证书问题,其实不是:

  • 时钟偏差。有效期是按服务器的时钟检查的。签发证书时,让有效期从几分钟之前开始(-V -5m:...),并让服务器通过 NTP 同步时间。
  • 多次失败触发的惩罚。从 OpenSSH 9.8 起,sshd 会惩罚身份验证不断失败的地址(PerSourcePenalties,默认开启)。在同一台机器上测试证书时,一次失败的尝试导致下一次连接在身份验证之前就被断开:
一次证书登录失败之后的 sshd 10.0p2
srclimit_penalise: 127.0.0.1/32: activating ipv4 penalty of 19 seconds for penalty: failed authentication
$ ssh deploy@web01.example.test
Connection closed by 127.0.0.1 port 22
drop connection #0 from [127.0.0.1]:55080 on [127.0.0.1]:22 penalty: failed authentication

如果在连续几次失败之后 ssh 紧接着报出 Connection closed,先等惩罚期过去,再修改配置。

用 KRL 吊销

短有效期是主要的吊销手段:有效期 8 小时的证书会自行失效。其余情况可以用 OpenSSH 的密钥吊销列表(KRL,从 OpenSSH 6.2 起提供),这是一种紧凑的二进制格式,可以按序列号、密钥 ID 或密钥吊销。吊销 Alice 序列号为 1001 的证书:

ssh-keygen -k:生成 KRL,再用它检查证书
$ printf 'serial: 1001\n' > /tmp/krl-spec
$ ssh-keygen -k -f /etc/ssh/revoked_keys -s user_ca.pub /tmp/krl-spec
Revoking from /tmp/krl-spec
$ ssh-keygen -Q -f /etc/ssh/revoked_keys id_ed25519-cert.pub
/home/alice-laptop/id_ed25519-cert.pub (alice@laptop): REVOKED

sshd 每次身份验证时都会读取 RevokedKeys,因此无需重启,下一次登录就会失败。加上 -u 可以更新已有的 KRL,而不是替换它。用分发 CA 密钥的同一套工具分发 KRL。KRL 过时的服务器会继续接受它还不知道已被吊销的证书或密钥。这也是要让证书保持短期有效、只把 KRL 当作剩余几个小时内的兜底手段的又一个原因。

对比一览

authorized_keys证书
授予访问权限把密钥复制到每台服务器为密钥签名一次
收回访问权限在每台服务器上删除密钥等它过期,或者加入 KRL
过期每行可选(expiry-time=)每张证书都有有效期
日志中的身份密钥指纹密钥 ID 和序列号
限制每行的 from=、command=、restrict每张证书的 source-address、force-command、扩展
主机信任每台主机一个指纹(TOFU)一行 @cert-authority
最关键的机密没有集中的机密CA 私钥
需要维护的环节无签发、CA 保护、KRL 分发

authorized_keys 的每行限制确实有效,也在同一台服务器上测试过:

authorized_keys 选项,OpenSSH 10.0p2(密钥已截短)
restrict,from="10.0.0.0/8",command="echo backup only" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA...
$ ssh deploy@web01.example.test id
deploy@web01.example.test: Permission denied (publickey).
/home/deploy/.ssh/authorized_keys:1: Authentication tried for deploy with correct key but not from a permitted host (host=127.0.0.1, ip=127.0.0.1, required=10.0.0.0/8).

expiry-time="20261001" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI...
$ ssh deploy@web01.example.test id
deploy@web01.example.test: Permission denied (publickey).

如果只有少数几台服务器和几个人,配合 restrict 和 expiry-time 使用 authorized_keys 就够了。一旦人员入职、离职和主机重建每周都在发生,或者你需要知道是谁登录的、而不只是哪个密钥登录的,证书就物有所值了。

从 authorized_keys 迁移

不需要选定某一天一次性全部切换。sshd 会同时检查 authorized_keys 和受信任的 CA,因此迁移期间两种登录方式都能用:

  1. 先部署 CA 信任。把 TrustedUserCAKeys、主体文件和一个空的 KRL 推送到所有服务器,authorized_keys 保持不动。
  2. 签发证书,先发给第一个团队,让他们用上几周。现在每次登录都会以 ID ... (serial ...) 的形式出现在日志中。
  3. 找出还没迁移的登录。通过 authorized_keys 的登录在日志中没有证书 ID,例如 Accepted publickey for deploy from 127.0.0.1 port 58770 ssh2: ED25519 SHA256:tT5X32Wds5InYaLSh7mV+QRqAtxA4KXwlKV3hkf0YkI。这些通常是自动化任务:CI 运行器、备份作业、监控。为每一个分配专属的证书,要么短期有效,要么限制范围很窄。
  4. 停用密钥文件:确认再没有正当用途依赖它们之后,用 AuthorizedKeysFile none 停用。保留一条应急通道(break-glass),例如控制台访问或一个离线保存的独立应急 CA,以备签名服务宕机的那一天。
  5. 主机端单独处理。为主机密钥签名,并把 @cert-authority 行加入客户端的全局 /etc/ssh/ssh_known_hosts。原有的按主机记录的 known_hosts 条目可以保留,在主机密钥变更之前它们会一直有效。

生产实践

  • 通过身份提供商签发证书。签名服务检查用户的 SSO 登录(OIDC),然后返回一张带有该用户主体、有效期为几小时的证书。没有人接触 CA 密钥;有人离开公司后,最迟到第二天就会失去 SSH 访问权限,不需要动任何一台服务器。
  • 尽可能把用户密钥放在硬件中。ssh-keygen -t ed25519-sk 会生成基于 FIDO2 的密钥。证书用于这种密钥,和用于其他密钥没有区别。
  • 轮换 CA 时保留重叠期。把新的用户 CA 作为第二行加入 TrustedUserCAKeys,或者把新的主机 CA 作为第二行 @cert-authority 加入。先用新 CA 签发证书,再移除旧 CA。
  • 主机端也要盯着。在主机证书过期之前重新为主机密钥签名。主机证书过期后,客户端会退回到普通主机密钥,而客户端通常并没有记录这些主机密钥:
ssh 连接一台证书昨天已过期的主机
Certificate invalid: expired
No ED25519 host key is known for web01.example.test and you have requested strict checking.
Host key verification failed.

如果想在不运行任何命令的情况下查看密钥或证书文件,SSH 公钥查看器可以解码 OpenSSH 密钥,并显示其类型、长度和指纹。SSH 服务器审计会检查服务器提供的内容,包括主机密钥算法、证书算法和后量子密钥交换。

参考资料

  • OpenSSH 手册页:ssh-keygen(1)(CERTIFICATES 和 KEY REVOCATION LISTS 两节)、sshd_config(5)(TrustedUserCAKeys、AuthorizedPrincipalsFile、RevokedKeys、HostCertificate、PerSourcePenalties)、sshd(8)(authorized_keys 和 known_hosts 格式)。
  • draft-ietf-sshm-cert:SSH Certificate Format(OpenSSH PROTOCOL.certkeys 的后继文档)。PROTOCOL.krl:KRL 格式。
  • OpenSSH 版本说明:5.4(证书)、6.2(KRL)、9.8(PerSourcePenalties)。

本页所有输出均来自2026年10月20日在 debian:trixie-20261005-slim 中运行的 OpenSSH 10.0p2(Debian 13 软件包),测试密钥均在容器内生成。