使用 step-ca 搭建私有证书颁发机构

2026-04-01
15 min read
Anonymous
使用 step-ca 搭建私有证书颁发机构

在本文中,您将了解 TLS(传输层安全)和 SSH(安全外壳)如何使用公钥/私钥对来验证您访问的 Web 服务器和您登录的 Linux 机器。您还将了解到主流 Web 浏览器默认安装的 TLS 框架在关键方面未能防止 MITM(中间人)攻击。然后,我们将逐步介绍如何搭建私有 .FEDORA TLD(顶级域名)、使用 smallstep 包搭建您自己的私有 CA,以及使用 acme-tiny 包为该私有 TLD 下的网站颁发证书。

我不会介绍如何使用 Fedora 中打包的您喜欢的 Web 服务器搭建一个简单的"Hello World"网站。要跟着本文操作,您需要已经在 HTTP 上运行了这样一个网站。在本文中,该网站将被命名为 hello.fedora。

遗憾的是,我们还将解释这并不能完全解决 MITM 问题——但这已经是一篇很长的文章了。

公钥如何防止中间人攻击

虽然 NSA 局长 Admiral Bobby 透露情报机构自 20 世纪 60 年代以来就已了解双密钥或公钥密码学,但第一份非保密论文是由 Whitfield Diffie 和 Martin E. Hellman 于 1976 年发表的。在大学里,我记得玩过基于背包问题的密码系统,这些系统存在各种漏洞。真正革新该领域的是 1977 年 RSA 算法的发表。我清楚地记得我在大学图书馆里读这篇论文时的位置。关于"算法不能申请专利"存在一些争议,但 RSA 为其实现申请了专利(实现本身已经受版权保护——但这是另一个话题)。是的,您可以在几分钟内写出一个一行的 Perl 实现(我们都做过)——但一个不会通过各种侧信道泄露私钥的安全实现可绝非易事。

公钥的原始概念是在目录中查找接收者的公钥,并用它来加密消息,只有持有对应私钥的人才能解密。这也可以用来通过证明对方持有相应私钥的协议来验证通信方的身份。基本思想是使用公钥加密一个随机令牌,接收者解密该令牌并用您的公钥加密后发回。具体细节并不简单。主要关注点是 MITM 攻击。SSH 和 TLS 支持几种广泛接受的认证和密钥交换算法。

公钥目录至关重要

仔细想想,那个"目录"至关重要。假设您有一个"安全"的电话应用(不点名),它使用公共目录将电话号码映射到公钥。运行该目录的人可以返回他们自己的公钥(可能每个电话号码不同),解密数据,然后转发数据,用真正接收者的公钥重新加密(反向也同样操作)。这就是经典的 MITM 攻击。这就是为什么这类安全应用通常提供一种通过面对面会议或替代媒介来验证您拥有真实公钥的方式。

那么,如何知道一个安全(https)网站的真实公钥呢?网站提供一个"证书",声明"此公钥属于这些域名"(以及其他我们在此不关心的信息)。任何人都可以创建这样的证书——实际上我们将在本文中这样做——那么如何知道它是可信的呢?证书由证书颁发机构(CA)"签名"。公钥可以用来签名数据。对于 RSA,基本概念是计算证书数据的安全"哈希"(例如 SHA256),然后用 CA 的私钥"解密"它。签名可以通过使用 CA 的公钥"加密"结果来验证——结果应与签名数据的哈希匹配。RSA 的优点是解密和加密是对称的——验证签名与将签名加密给公钥对应的私钥所有者是相同的操作。因此,现在每个 Web 用户不再需要维护域名公钥的私有数据库,浏览器有一个受信任的 CA 列表,这些 CA 在某种程度上验证后对网站证书进行签名。万一私钥泄露,CA 会发布撤销列表(普通用户很少使用),而 TLS 证书总是有过期日期。

请注意,CA 可以认证域名之外的数据,如公司或个人名称。商业 CA 通常对此收取额外费用,但也有像 cacert.org 这样的非营利 CA,通过面对面会议来认证个人详细信息。

主流浏览器如何知道信任哪些 CA

普通用户不会跟踪所有这些,那么"受信任的 CA 列表"从何而来?嗯,存在一个 CA 和浏览器论坛,由流行浏览器软件制造商和商业 CA 的代表组成。他们维护一个受信任的 CA 列表,变更在公开会议上进行投票,会议纪要发布在他们的网页上。Fedora 将此列表安装在 /usr/share/pki 中。浏览器可能有自己的副本。用户可以向 /usr/share/pki 或 /etc/pki/ca-trust 添加额外的受信任 CA,浏览器也可能有自己的方式来添加额外的受信任 CA。

这一切听起来不错,但是。关键的缺陷可以称为序列可靠性。受信任的 CA 可以信任任何域名。因此,任何受信任的 CA(包括您添加的任何 CA)都可以为任何网站伪造证书。DNS 漏洞(缓存投毒等)超出了本文的范围。但我们将搭建一个私有 CA,您可以用它为任何网站伪造证书,并欺骗任何您说服其信任您 CA(并且能篡改其 DNS 和/或 IP 路由)的人。CAB 论坛对其列表非常谨慎。作为敌对行动的一部分,论坛 CA 停止认证 .RU 域名(俄罗斯的 ISO TLD)。俄罗斯迅速推出了自己的国家 CA,任何人都可以将其添加到浏览器信任存储中。普通用户被警告不要这样做,因为俄罗斯的 CA 随后可以为任何域名伪造证书。但稍加思考就会发现,任何 CAB 论坛的 CA 都可能"叛变"并做同样的事情。只需要一个就足够了。

这个问题有解决方案,但这需要另一篇文章来讨论。

使用 bind 创建私有 TLD

为了演示,我们将创建 .FEDORA TLD。每个跟随操作的人将创建该 TLD 的不同实例,而 .FEDORA 下的主机名将根据您将 TLD 指向哪个 DNS 服务器而解析到不同的 IP(或 NXDOMAIN)。这正是创建 ICANN 的动机——一个全球集中的 DNS 根(官方 TLD 列表)。这提供了统一的命名空间,代价是将绝对权力(取消域名和 TLD)赋予了 ICANN。在 ICANN 之前,管理员都维护自己的 DNS 根,并定期(手动或自动)更新像 .COM 等知名 TLD 的域名服务器。ISO 定义了一个官方的 TLD 列表,包括国家代码 TLD(如 .US)。这运作良好。问题出现在像 .FREE 这样更冷门的 TLD 上。试图变得"酷"的公司抱怨并非所有客户都能获得 .FREE 主机名相同的 IP。管理员也喜欢有"其他人"来维护 DNS 根。于是,ICANN 诞生了。还有 Opennic,同样有"其他人"(志愿者)维护根区域,并回退到 ICANN,同时拥有自己的"论坛"(现有 TLD 投票)来批准新的 TLD。

以下是 .FEDORA 的 bind 区域文件:

$TTL 2H

; hello.fedora

@               IN      SOA     ns1     hostadmin.hello.fedora. (
2025122600 ; serial
1H ; refresh
15M ; retry
14D ; expire
6H ; default_ttl
)

@               IN      NS      ns1.fedora.
@               IN      TXT     "v=spf1 -all"
hello           IN      A       192.168.100.31
ns1             IN      A       192.168.100.31
ca              IN      A       192.168.100.31

但这只是一个诱饵和切换——我们实际上不会在本文中使用 bind。配置 bind 超出了本文的范围。假设您已经配置好 bind 或类似服务来提供 .FEDORA TLD,或者使用 /etc/hosts 进行测试。需要注意的是,如果您使用 /etc/hosts,需要添加 hello.fedora 和 ca.fedora 的记录。

安装 step-ca

smallstep 包在标准 Fedora 仓库中可用:

$ sudo dnf install step-ca

现在,实例化 CA。在提示时输入密码。我们使用 FEDORA 作为 CA 名称,稍后 acme-tiny 将使用此名称来请求证书:

$ step ca init --name FEDORA --dns ca.fedora --address :9000 --provisioner acme

--dns ca.fedora 告诉步骤 CA 它将在 ca.fedora 提供服务。address :9000 告诉它在端口 9000 上监听所有接口。--provisioner acme 保留 ACME 协议的默认 provisioning 名称。

现在启动 CA:

$ step-ca $(step path)/config/ca.json

CA 现在应该正在运行并监听端口 9000。您可以通过 https://ca.fedora:9000 访问 API。

设置 Web 服务器

运行 Web 服务器是前提条件。我将以 apache 为例,希望 nginx 和其他 Web 服务器的用户能够自行转换。首先,创建 /etc/httpd/conf.d/hello.conf:

<VirtualHost *:80>
ServerName      hello.fedora
DocumentRoot    "/var/www/html/hello"
#RedirectMatch ^((?!\/\.well-known\/).*)$ https://hello.fedora$1

<Location "/.well-known/acme-challenge/">
Options -Indexes
Order allow,deny
Allow from all
</Location>

<Location "/">
Options FollowSymLinks Indexes
Require all granted
</Location>
</VirtualHost>

本文发表后,Daniel Ferradal 在他的评论中提出了上述 Apache 配置的若干改进。上述配置经过测试并确认可用,但 Fedora Magazine 编辑建议遵循 Daniel Ferradal 关于配置 Apache 的建议。

在获得签名证书之前,重定向被注释掉。假设 httpd 已经在运行,使用 sudo apachectl graceful 加载更改。然后在 /var/www/html/hello/index.html 中放置一个简单的文档:

<html>
<head>
<title> Hello Fedora </title>
</head>
<body>
<h1> Hello Fedora! </h1>
</body>
</html>

使用 acme-tiny 通过 step-ca 签名 TLS 证书

添加私有根 CA

Acme-tiny 需要信任根 CA 才能使用 ACME 服务。step-ca 服务提供了一个方便的 API 来获取根 CA:

$ cd /etc/pki/ca-trust/source/anchors
$ sudo curl https://ca.fedora:9000/roots.pem -o fedora_ca.crt
curl: (60) SSL certificate problem: unable to get local issuer certificate

哎呀!第 22 条军规。您需要根 CA 才能使用获取根 CA 的便捷 API。所以我们必须告诉 curl 接受这个陌生的根证书。(或者使用 rsync、在同一台机器上使用 cp、在终端窗口之间复制/粘贴,或其他更安全的方法。)

$ sudo curl -k https://ca.fedora:9000/roots.pem -o fedora_ca.crt
$ sudo update-ca-trust extract

现在,我们准备运行 acme-tiny。同样,openssl req 会提示输入主题标识符。浏览器唯一关心的是通用名称(Common Name),应该是 "hello.fedora"。但是,当用户使用浏览器功能检查证书时,他们可能会关心其他字段。

$ sudo dnf install acme-tiny
$ sudo apachectl graceful
$ cd /var/lib/acme
$ sudo -u acme bash -l
$ ls
certs  csr  private
$ /usr/libexec/acme-tiny/sign  # 注意:如果需要会生成 account.key
$ ls private
account.key
$ openssl req -new -passout pass:'' -keyout private/hello.key -out csr/hello.csr
$ /usr/sbin/acme_tiny --account-key private/account.key --csr csr/hello.csr --acme-dir /var/www/challenges/ --ca https://ca.fedora:9000/acme/FEDORA >certs/hello.crt
$ exit
$ sudo nano /etc/httpd/conf.d/hello.conf

现在取消注释 RedirectMatch,并将以下 SSL 虚拟主机定义附加到 hello.conf。使用 apachectl graceful 加载更改。

<VirtualHost *:443>
ServerName      hello.fedora:443
SSLEngine on
SSLProtocol all -SSLv2 -SSLv3
SSLCipherSuite HIGH:3DES:!aNULL:!MD5:!SEED:!IDEA
DocumentRoot    "/var/www/html/hello"
SSLCertificateFile /var/lib/acme/certs/hello.crt
SSLCACertificateFile /var/lib/acme/certs/hello.crt
SSLCertificateKeyFile /var/lib/acme/private/hello.key
CustomLog logs/ssl_request_log \
"%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b"
<Location "/">
Options FollowSymLinks Indexes
</Location>
</VirtualHost>

当前 acme-tiny 包仅对 letsencrypt.org CA 自动续签证书。这应该很快会扩展。同时,可以随意添加一些 hacky 的方法。(我会尝试让它从 /etc/sysconfig 或其他地方查找 TLD 以获取自定义 CA URL。)

使用浏览器显示网页

在带有 Web 浏览器的机器上,您需要两样东西:新的根 CA 以及某种方式来查找 .FEDORA TLD 中的名称——要么将 DNS 指向您设置了私有区域的服务器,要么将 ca.fedora 和 hello.fedora 的行追加到 /etc/hosts 中。

现在 curl 应该可以在没有 -k 的情况下工作了。您的浏览器也应该能够显示 https://hello.fedora,尽管可能需要重启。如果浏览器在启动时不读取 Fedora CA 信任存储,您可能需要找到在浏览器菜单中导入 CA 的选项。

$ curl https://hello.fedora
<html>
<head>
<title> Hello Fedora </title>
</head>
<body>
<h1> Hello Fedora! </h1>
</body>
</html>

现在您的根 CA 已经启动并运行,不要忽视如果它叛变能够做什么。让很多人安装它,以便他们能够访问您酷炫的新 TLD。然后开始为任意网站伪造证书,征服世界!!哇哈哈哈!(未来的文章可以讨论 PKCS#11 以及如何在浏览器和其他软件中限制对 CA 的信任。)