证书是由另一个密钥签名的密钥[1]。用于签名的密钥称为证书颁发机构(Certificate Authority,CA)。它是提前生成并保留下来的,仅用于签名其他密钥。其他方使用签名密钥的公钥部分来验证被签名密钥的真实性,这些密钥用于服务器身份验证(主机证书[2])或登录验证(用户证书[3])。

为了实现特权分离,如果同时使用主机证书和用户证书,应为每种证书类型创建独立的证书颁发机构。截至本文撰写时,椭圆曲线算法是一个不错的选择。

SSH证书概述

X.509是国际电信联盟(ITU)电信标准化部门的标准,定义了公钥证书的格式。

使用证书时,客户端或服务器会预先配置为接受由另一个专门用于签名的密钥签署的密钥。这些被签署的密钥由证书颁发机构(CA)验证。与常规密钥一样,证书也用于生成用于身份验证的签名。不过,证书的验证方式是,公钥存储在带有签名的文件中,签名用于验证公钥的有效性,然后通过公钥确认与持有匹配私钥的客户端进行连接。因此,使用证书的前提是对普通SSH有一定的了解。关于公钥认证的细节可以参考相关章节。普通的密钥认证机制在该章节的简介中有简要说明。SSH使用自己简化的证书格式,而非X.509证书格式。

证书相比普通密钥的两个主要优点是:它们可以设置到期日期,甚至可以设置有效的日期范围;并且它们消除了首次使用时的信任问题或复杂的密钥验证方法。证书主要是为了促进大规模部署,通过简化密钥审批和分发流程,提供比将相同的主机密钥复制到多个目的地更好的方案。

SSH用户证书

用户证书用于验证用户身份,允许用户或脚本登录到服务器或其他远程设备。使用用户证书进行客户端认证意味着服务器上的 authorized_keys 文件不再需要。相反,用户证书(实际上是签名密钥)会与证书颁发机构验证签名是否有效。如果有效,则可以继续登录过程。另一个优点是,签名可以被指定为仅在某个日期范围内有效。因此,在实践中,可以为用户证书设置到期日期。用户证书还与特定账户(称为“主体”)绑定,只有指定的主体才能使用该签名密钥进行认证,否则服务器将不会接受该密钥,即使它是有效签名的。

还可以使用类似于普通SSH公钥的选项,如 force-commandsource-address,来限制连接的来源或强制执行特定命令。这些限制在签名时进行设置。如果这些选项需要更改,则必须重新签名密钥,否则该密钥将变为无效。

与SSH用户证书相关的文件

与用户证书相关的文件有五个。证书颁发机构必须妥善保管,并且与其他系统分开存储。如果证书颁发机构丢失,则无法向证书池中添加新的用户账户,必须重新创建一个证书颁发机构。如果证书颁发机构落入不当之手,攻击者可能利用它伪造用户账户。

  • 证书颁发机构:用于签署其他密钥的私钥
  • 证书公共密钥:证书颁发机构的公钥组件(存放在服务器上)
  • 私钥:用于认证到服务器或远程设备的私钥
  • 公钥:与证书颁发机构签名的公钥
  • 用户证书:用于验证用户公钥的证书

用户证书和其密钥可以通过 ssh_config 文件永久与远程服务器或设备相关联,可以全局或按账户设置,使用 CertificateFileIdentityFile 指令。

使用SSH用户证书的步骤

使用用户证书有四个步骤。步骤一:创建证书颁发机构,针对每个用户或脚本池只需执行一次。步骤二:调整服务器配置,可以为多个服务器系统或远程设备重复执行。步骤三:为每个用户账户签名密钥,只需执行一次。步骤四:部署用户证书,可以根据主体和任何源地址限制,允许多个客户端使用。

  1. 创建证书颁发机构 创建用户证书时,建议为主机和用户使用不同的证书颁发机构。因此,出于特权分离的考虑,即使已有主机证书颁发机构,也应为用户证书创建一个单独的证书颁发机构。

    $ ssh-keygen -t ed25519 -f ~/.ssh/user_ca_key \
            -C 'User Certificate Authority for *.example.com'
    

    创建的私钥应该存放在服务器之外,服务器只需要能够访问公钥部分,以便验证客户端提交的签名。

  2. 将证书颁发机构的公钥存储在服务器上 服务器需要能够查找匹配的证书,以验证用户密钥上的签名。需要在SSH守护进程的配置中进行设置。将主机证书复制到与主机密钥相同的位置,然后使用 sshd_config(5) 中的 TrustedUserCAKeys 指令将证书颁发机构的公钥路径指向服务器。

    TrustedUserCAKeys /etc/ssh/user_ca_key.pub
    

    检查文件权限确保其正确。

    $ ls -lhn /etc/ssh/user_ca_key.pub
    -rw-r--r--  1 0  0   114B May  4 16:38 /etc/ssh/user_ca_key.pub
    
  3. 签署用户密钥 通过证书颁发机构为用户密钥签名。一旦签名完成,用户证书就准备好用于验证用户身份。

  4. 部署用户证书 将签署的用户证书部署到客户端系统,确保它符合证书的主体要求,并考虑源地址限制等设置。

3. 处理现有的公钥

用户必须已经生成了一对密钥,然后将公钥部分提交进行签名。签名后,将签名副本传回给正确的人。应立即删除该过程中的任何临时文件,因为额外的公钥会导致杂乱无章,最多也只会造成不便。以下是一个名为 server01.ed25519.pub 的公钥已经被接受,并使用该公钥生成证书的示例:

$ ssh-keygen -s user_ca_key -I 'edcba' -z '0002' -n fred \
        server01.ed25519.pub

生成的证书将命名为 server01.ed25519-cert.pub,并具有内部ID“edcba”和内部序列号“2”。ID和序列号需要外部计算。在成功登录时,证书的ID和序列号会包含在日志中。有关更详细的信息,请参阅《日志记录与故障排除》部分。即使对于用户证书,也强烈建议列出证书的主体。证书中列出的主体必须与将要登录的账户匹配。

尽管公钥在客户端登录时并非严格必需,但保留公钥对于客户端来说是有益的。如果公钥丢失,可以通过私钥重新生成,但反之则不可。当私钥丢失时,它就永远丢失了。因此,保持正确的备份计划非常重要。如果存在名为公钥的文件,它必须是公钥本身,否则登录尝试将会失败。

公钥获取和证书传递的具体操作不在本书范围内。但此时,生成的证书应传回给提交密钥的人。

4. 使用SSH用户证书登录

客户端,用户证书和相应的私钥都需要才能进行登录。

$ ssh -o CertificateFile=server01.ed25519-cert.pub -i server01.ed25519 \
        fred@server01.example.org

一旦手动设置成功,就可以通过在客户端ssh_config(5) 文件中创建快捷方式。如果使用代理且代理中有多个密钥,可能还需要使用 IdentitiesOnly

Host server01 server01.example.org
        Hostname server01.example.org
        User fred
        IdentitiesOnly yes
        IdentityFile /home/fred/.ssh/server01.ed25519
        CertificateFile /home/fred/.ssh/server01.ed25519-cert.pub

通过这些设置,在客户端运行 ssh server01 时,将尝试使用指定的密钥及其相应的用户证书和指定的主体。

SSH主机证书

OpenSSH使用密钥的主要目的之一是验证远程主机对客户端的身份认证,以便客户端可以确认它连接的确实是正确的系统,而不是冒充者或中间人攻击。为此,一旦确认,known_hosts 文件通常会保存主机公钥,并将其与主机名或IP地址配对。

主机密钥的难点在于为大量客户端机器填充 known_hosts 文件,或者在第一次连接新系统时,如何处理主机密钥。在数据中心、实验室或物联网部署中,机器总是在变动。这意味着每次都会生成新的SSH主机密钥。如果涉及的主机数量很多,那么 known_hosts 注册表中的密钥会迅速增加。相反,使用主机证书后,使用相同证书颁发机构的任意数量主机只需在 known_hosts 注册表中添加一条条目,即使新的主机不断加入。通过签署主机密钥,新的服务器或设备在使用新密钥时,仍可以让客户端在第一次连接时正确、安全地识别它们,从而避免中间人攻击的风险。

通过使用主机证书,主机的身份密钥被签名,且可以根据约定的证书颁发机构验证该签名,这大大简化了第一次连接时收集和验证主机公钥的过程。

与SSH主机证书相关的文件

使用主机证书时涉及五个文件。与用户证书一样,证书颁发机构必须妥善保管。对于主机而言,和用户证书一样,必须采取相同的安全措施:

  • 证书颁发机构:用于签署其他密钥的私钥
  • 接下来三个文件保存在服务器本身:
    • 证书公钥:证书颁发机构的公钥组件
    • 主机公钥:SSH守护进程用于向客户端标识自己的实际密钥
    • 主机证书:使用证书颁发机构为主机公钥生成的签名
  • 然后在客户端,文件保存在客户端注册表或系统范围的已知主机注册表中:
    • known_hosts:包含对主机证书及其主体的引用

客户端找到并使用有效的主机证书时,known_hosts 注册表中不会添加该主机的个别条目。

使用SSH主机证书的步骤

使用主机证书有四个主要步骤。步骤一:创建证书颁发机构,仅需为每个服务器池或设备池执行一次。步骤二:为每个服务器设备签署主机密钥,并且步骤三与之相同。步骤四:配置客户端,可以为所需的多个客户端机器或单个登录账户重复执行。

每一步都需要注意文件路径的设置。没有统一的解决方案,因此需要决定文件的存放位置。

1. 创建证书颁发机构

再次强调,证书颁发机构(CA)仅是另一个SSH密钥。不同之处在于,它不直接用于认证服务器或客户端,而是用来签署并验证其他密钥,后者实际上会用于认证。

$ ssh-keygen -t ecdsa -f ~/.ssh/ca_key \
        -C 'Host Certificate Authority for *.example.com'

在此步骤中创建的私钥应保存在与将要使用的服务器不同的地方。

此步骤中的公钥必须通过安全的方式分发给客户端客户端将使用它来验证主机身份。

2. 获取并签署主机密钥

只有公钥会被签名。通过可靠的方法获取远程主机的公钥,进行签名,然后将生成的证书上传到服务器。-h 选项表示这是一个主机证书,-s 选项指明用于签名的密钥。以下是从服务器获取了主机密钥 ssh_host_ecdsa_key.pub 并将在本地处理的示例:

$ ssh-keygen -h -s ~/.ssh/ca_key -V '+1d' -I abcd -z 00001 \
         -n server.example.com ./ssh_host_ecdsa_key.pub
Enter passphrase: 
Signed host key /etc/ssh/ssh_host_ecdsa_key-cert.pub: id "abcd" serial 1 for server.example.com valid from 2020-05-05T09:51:00 to 2020-05-06T09:52:01

-V 选项设置的有效期是相对于密钥签名时的日期和时间的时间跨度。因此,可以使用类似 -V '+1d2h:+1d3h' 的公式,使证书在明天的一个小时内有效。如果没有设置开始时间,则值被解释为结束时间。如果需要特定的结束时间或日期,最好通过脚本计算并调用 ssh-keygen。如果没有指定时间,证书将被视为无限期有效。

-I 选项为证书分配一个标签,用于标识该证书。

-z 选项手动为证书分配一个序列号。如果要保持序列号顺序,必须从旧证书中提取并递增该序列号。默认情况下,不会为证书分配序列号。-n 选项为主机证书分配一组主体,即哪些主机可以使用该证书。

可以使用 -L 选项查看证书内容:

$ ssh-keygen -L -f ssh_host_ecdsa_key-cert.pub                   
ssh_host_ecdsa_key-cert.pub:
        Type: ecdsa-sha2-nistp256-cert-v01@openssh.com host certificate
        Public key: ECDSA-CERT SHA256:kVSFLH5MP/3uJWU57JxD8xVFs7ia8Pww8/ro+pq4S50
        Signing CA: ECDSA SHA256:INewUSvbnfVbgUhtLBhh+XKL0uN99qbXjsi0jvD/IGI (using ecdsa-sha2-nistp256)
        Key ID: "abcd"
        Serial: 1
        Valid: from 2020-05-05T09:51:00 to 2020-05-06T09:52:01
        Principals: 
                server.example.com
        Critical Options: (none)
        Extensions: (none)

公钥证书必须传输到服务器可以使用的地方。通常这意味着与常规公钥存放在同一目录中,默认情况下是 /etc/ssh/。复制后,请确保证书具有正确的权限。

$ ls /etc/ssh/ssh_host_ecdsa_key*.pub
ls -nlh /etc/ssh/ssh_host_ecdsa_key*.pub
-rw-r--r--  1 0  0   653 May  4 16:49 /etc/ssh/ssh_host_ecdsa_key-cert.pub
-rw-r--r--  1 0  0   172 Feb 21 16:09 /etc/ssh/ssh_host_ecdsa_key.pub

3. 发布主机证书

一旦主机证书到位,远程主机上的SSH守护进程必须指向该主机证书。更多信息请参见 sshd_config(5)

HostCertificate /etc/ssh/ssh_host_ecdsa_key-cert.pub

然后,必须重新加载SSH守护进程的配置文件。具体方法因系统而异,但最终守护进程会接收到一个 HUP 信号。

4. 更新客户端以承认指定的证书颁发机构

最后,在客户端known_hosts 文件中添加证书颁发机构的引用:

@cert-authority *.example.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFJQHK0uoOpBfynyKrjF/SjsLMFewUAihosD6UL3/HkaFPI1n3XAg9D7xePyUWf8thR2e0QVl5TeGLdFiGyCgt0=

就这样。然而,重要的是要意识到,上述每一步可能会无声地失败,客户端会回退到通常的验证主机身份的方法,而不会报告任何错误。任何有用的调试信息将出现在守护进程的日志中,但即便如此,这些信息也通常有限。更多调试信息可以参考《日志记录与故障排除》章节。

证书部署的自动化

自动部署证书,无论是用户证书还是主机证书,超出了本书的范围。有许多方法可以实现自动化,并且涉及的因素众多。细节不仅取决于使用的编排软件,还取决于终端的具体发行版或操作系统。简而言之,首先要手动完成部署过程,然后利用部署脚本或软件提供的工作流来实现自动化。

不过,需要注意的是,证书颁发机构可以保存在代理中。可以研究 -U 选项以了解如何进行签名。

限制用户证书

可以在用户证书本身内绑定各种限制。这些限制大多数通过签署密钥时使用 -O 选项来指定,包含有效期、强制命令、源地址范围、禁用伪终端分配等。有关权威的完整列表,请参阅 ssh-keygen(1) 手册页。这些限制可以组合使用,也可以单独使用。下面的示例将逐一阐明它们。

用户证书的时间限制

证书可以设定有效期,这里称为有效期间隔。有效期可以有一个特定的开始日期和时间、结束日期和时间,或同时包含这两者。然而,每个证书只能有一个有效期。

有效期通过 -V 选项指定,可以使用绝对的日期和时间范围,也可以使用相对的时间范围。以下是用户证书部分中使用的示例,限制证书的有效期:它在 2020 年 6 月 24 日下午 1:55 至 2:00 之间的五分钟内有效。

$ ssh-keygen -V '202006241355:202006241400' \
        -s user_ca_key -I 'edcba' -z '0003' -n fred \
        server01.ed25519.pub

请仔细查看结果输出,以确保范围正确。

相对时间间隔也可以使用。以下是一个证书,它仅在五分钟内有效,从现在开始:

$ ssh-keygen -V ':+5m' \
        -s user_ca_key -I 'edcba' -z '0004' -n fred \
        server01.ed25519.pub

注意,秒数会被计入,并且是从签名开始的时间算起,而不是输入密码短语并完成签名的时间。因此,如果签名在某分钟的第 35 秒开始,那么到期时间也将在第五分钟的第 35 秒。再次提醒,查看输出结果时要特别留意。

用户证书的强制命令

可以通过在创建证书时使用 -O 选项,将证书绑定到服务器上的特定命令。在这个例子中,证书每次连接到 SSH 服务器时都会显示时间和日期:

$ ssh-keygen -O force-command='/bin/date +"%T %F"' \
        -s user_ca_key -I 'edcba' -z '0005' -n fred \
        server01.ed25519.pub

如果证书和 sshd_config(5) 中都存在强制命令,则以配置文件中的命令为准。传递的任何命令行参数都会被覆盖,但可以在 SSH_ORIGINAL_COMMAND 环境变量中找到。来自证书内的命令不会影响 SSH_ORIGINAL_COMMAND 变量,并且必须从证书本身中解析出来,这个证书会被存储在由 SSH_USER_AUTH 环境变量指向的临时文件中。

$ awk '/^publickey/ {print $2,$3}' ${SSH_USER_AUTH} \
        | ssh-keygen -Lf -

该文件仅在会话打开时存在。SSH_USER_AUTH 变量本身只有在 SSH 服务器配置了 ExposeAuthInfo 并且设置为 yes 时才会被设置,默认值为 no

用户证书的源地址限制

可以将证书限制为特定的 CIDR 地址范围。

$ ssh-keygen -O source-address='198.51.100.0/24,203.0.113.0/26' \
        -s user_ca_key -I 'edcba' -z '0006' -n fred \
        server01.ed25519.pub

CIDR 地址范围必须有效。

查看用户证书的限制

如果手头有证书,可以详细查看并看到适用的限制(在证书端)。以下是帐户 fred 的一个示例证书,包含两个局域网范围和一个小时多一点的访问权限。

$ ssh-keygen -Lf server01.ed25519-cert.pub

server01.ed25519-cert.pub:
        Type: ssh-ed25519-cert-v01@openssh.com user certificate
        Public key: ED25519-CERT SHA256:hSy7QrAApIU1LgDCUrtBK2F2TZxhvincnJ0djSDow7I
        Signing CA: ED25519 SHA256:dVgTW1INbhvHjHbeAe10R9Niu8BpejifO286RZ7/niU (using ssh-ed25519)
        Key ID: "edcba"
        Serial: 7
        Valid: from 2020-06-24T15:17:00 to 2020-06-24T16:23:47
        Principals: 
                fred
        Critical Options: 
                force-command /bin/date +"%T",source-address=192.168.0.0/16,10.11.9.0/26
        Extensions: 
                permit-X11-forwarding
                permit-agent-forwarding
                permit-port-forwarding
                permit-pty
                permit-user-rc

显然,证书本身无法显示 SSH 服务器配置中的任何附加限制。

如果手头没有用户证书,但它用于身份验证,则可以使用 sshd_config(5) 中的 ExposeAuthInfo 选项提供的 SSH_USER_AUTH 变量,从服务器获取证书,并查看其中的限制和所有其他嵌入的特性。证书本身不会更改 SSH_ORIGINAL_COMMAND 变量,因此只有临时文件才能查看证书中的内容。再次提醒,指向证书的文件仅在会话打开时存在。

Last modified: Sunday, 19 January 2025, 9:46 PM