IPsec 配置备忘 Part2 - 证书

作为系列的第二篇文章,讲解基本的证书原理和配置方法。没看过第一部分的快去看~
传送门:IPsec 配置备忘 Part1

证书认证基础

我们在 Part1 中看到,PSK 认证的基本思路是使用一个只有通信双方才知道的暗号,如果能确认对方确实知道这个暗号,那么认证就成功了。证书认证的思路非常不同:假设 A 需要向 B 证明自己的身份,同时 A 知道 B 信任 C,那么 A 可以向 C 索取一份“介绍信”,当 B 询问 A 的身份时,A 可以向 B 展示这份 C 出具的“介绍信”,如果 B 能够确认这份“介绍信”确实是由 C 出具的,那么认证就成功了。注意这个认证是单向的,假设 A 也信任 C,那么 B 也可以通过向 C 索取“介绍信”来向 A 证明自己的身份。在 PKI 体系中,A 和 B 持有各自的“私钥”,C 作为 Certificate Authority (CA) 向 A/B 颁发证书(即“介绍信”)。同时,CA 也会向自己颁发一份证书并分发给 A/B,A/B 使用 CA 的证书来确认 B/A 出示的证书确实为 C 所颁发。

使用 certtool 创建证书

我使用 certtool 没有什么特别的理由,你也可以用openssl或者 strongSwan 自带的pki工具。我在之前的一篇文章里介绍过如何使用 certtool 创建证书:借助IPsec和strongSwan建立隧道并分配IPv6地址。不过我还是决定再写一遍现在的配置。我们的配置场景和 Part1 中的相同,只不过把 PSK 认证换成了证书认证。

首先给 HostA, HostB 和 CA 分别创建私钥,我这里用的是 ed25519,一些设备可能不支持,请自行参考文档换成 RSA:

certtool --generate-privkey --key-type ed25519 --outfile ca-key.pem
certtool --generate-privkey --key-type ed25519 --outfile hosta-key.pem
certtool --generate-privkey --key-type ed25519 --outfile hostb-key.pem

生成证书时,我们需要手动指定一些证书的信息,比如证书的名称,过期时间等等。证书的 Distinguished Name (DN) 会被用来和 IKEv2 的身份标识符进行匹配,以决定具体向对方出示哪份证书(对于发送者)以及是否接受对方的证书(对于接受者)。这些信息需要写成一个 template 文件才能被 certtool 读取,详细的 template 文件格式可以在 certtool 的帮助文档里查到。

ca.tmpl
1
2
3
4
5
6
7
8
9
10
# Common name, 是 DN 的一部分
cn = "CA_COMMON_NAME"
# 组织名,是 DN 的一部分
organization = "ORG_NAME"

ca
# 生效与过期时间
activation_date = "2020-01-01 00:00:00 UTC+0"
expiration_date = "2030-01-01 00:00:00 UTC+0"
cert_signing_key
hosta.tmpl
1
2
3
cn = "HOSTA_COMMON_NAME"
activation_date = "2020-01-01 00:00:00 UTC+0"
expiration_date = "2021-01-01 00:00:00 UTC+0"
hostb.tmpl
1
2
3
cn = "HOSTB_COMMON_NAME"
activation_date = "2020-01-01 00:00:00 UTC+0"
expiration_date = "2021-01-01 00:00:00 UTC+0"

然后创建证书:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 生成自签名 CA 证书
certtool --generate-self-signed \
--template ca.tmpl \
--load-privkey ca-key.pem \
--outfile ca-cert.pem

# CA 向 HostA 颁发证书
certtool --generate-certificate \
--template hosta.tmpl \
--load-ca-certificate ca-cert.pem \
--load-ca-privkey ca-key.pem \
--load-privkey hosta-key.pem \
--outfile hosta-cert.pem

# CA 向 HostB 颁发证书
certtool --generate-certificate \
--template hostb.tmpl \
--load-ca-certificate ca-cert.pem \
--load-ca-privkey ca-key.pem \
--load-privkey hostb-key.pem \
--outfile hostb-cert.pem

最终会生成的 6 个文件,你可以使用certtool --key-info < hosta-key.pem来查看私钥信息,用certtool --certificate-info < hosta-cert.pem来查看证书信息,用certtool --verify --load-ca-certificate ca-cert.pem < hosta-cert.pem来检查证书是否确实是由 CA 签发的。其中:

  • hosta-key.pem, hosta-cert.pem, ca-cert.pem 需要拷贝到 HostA 上
  • hostb-key.pem, hostb-cert.pem, ca-cert.pem 需要拷贝到 HostB 上
  • ca-key.pem 留在本地好好保管不要交给任何人。

对于 strongSwan,私钥*-key.pem需要放置在/etc/swanctl/private,私钥对应的证书host*-cert.pem需要放置在/etc/swanctl/x509,CA 证书ca-cert.pem需要放置在/etc/swanctl/x509ca

配置文件

基本上和 Part1 中的配置一样,有不同之处已经加了注释

hosta.conf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
connections {
conn_hosta_hostb {
version = 2
local_addrs = fd00::1
remote_addrs = fd00::2
proposals = aes256gcm128-sha512-x25519
local {
# id 是证书的 DN,由于我们在 hosta.tmpl 里只指定了 CN,
# 所以 DN 就是 "CN={...}"。这个 id 会被发送给对方,
# 同时对应的证书/密钥对(hosta-cert/key.pem)会被选中,
# 作为对 id 的证明。
id = CN=HOSTA_COMMON_NAME
# 使用证书作为身份标识符的证明方式
auth = pubkey
}
remote {
# 接受任何 id,只要这个 id 能通过证书验证
# i.e. 只要对方出示的证书确实是由某个 CA 签署的即可。
id = %any
auth = pubkey
}
children {
child_sa {
local_ts = fd00::1/128
remote_ts = fd00::2/128
mode = transport
esp_proposals = aes256gcm128-sha512-x25519
}
}
}
}

# 不需要 secrets 指定 PSK 了。

remote.id设置成%any会有一定的安全问题,比如 HostA 是服务器,HostB 和 HostC 是客户端,如果 HostB 连接 HostA 的时候不检查 id,那么如果 HostC 能劫持 IP 地址,它就能假装成 HostA。毕竟 HostB 不关心它连接的到底是 A 还是 C。解决方法也很简单,指定remote.idCN=HOSTB_COMMON_NAME即可。

HostB 的配置除了local.id外完全一致

hostb.conf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
connections {
conn_hosta_hostb {
version = 2
local_addrs = fd00::1
remote_addrs = fd00::2
proposals = aes256gcm128-sha512-x25519
local {
id = CN=HOSTB_COMMON_NAME # 使用 HostB 的证书的 DN
auth = pubkey
}
remote {
id = %any
auth = pubkey
}
children {
child_sa {
local_ts = fd00::1/128
remote_ts = fd00::2/128
mode = transport
esp_proposals = aes256gcm128-sha512-x25519
}
}
}
}

链接测试

同 Part1,在任意一侧使用sudo swanctl -i -c child_sa建立连接即可。连接建立后抓包即可看到加密流量。