自己签发数字证书

为了解决网站 HTTPS/HTTP2 访问的问题,Let’s Encrypt 提供了免费数字证书,可以很方便地进行申请和部署。但有一些特殊情况仍然需要进行自签证书,比如:

  1. 在本地搭建开发测试环境(包含网站或 APP 签名等)
  2. 制作 HTTPS 网站的部分内容镜像(比如镜像 docs.python.org 网站的部分内容)。

自签证书的方法大致有两种。第一种方法是直接在服务器上签发证书。这种方法的优点是操作简单,缺点是不能为使用 HSTS 的网站制作镜像,必须为每个不同的证书单独导入客户端/浏览器。此外还可以在服务器上搭建 CA,然后通过用这个 CA 签发证书。这种方法的优点是功能没有被阉割,缺点是初始化操作繁琐。

本文介绍第二种方法,即先搭建 CA, 然后再由这个 CA 签发证书。操作系统环境是 Ubuntu 16.04 LTS。

图:自建 CA 签发证书的时序图

0. 准备工作

证书存放在 /etc/ssl/demoCA 中。这个目录的结构如下:

demoCA/
  |--private/     存放 CA 密钥。安全原因,这个目录的权限为 0700
  |     \...
  |--newcerts/    由 CA 签发的证书的备份
  |     \...
  |--index.txt    完成签发的证书记录
  +--serial       每次签发证书时使用序列号,签发成功后会自动增加

首先,修改 /etc/ssl/openssl.cnf

...
[ CA_default ]
dir             = /etc/ssl/demoCA
unique_subject  = no
copy_extensions = copy
policy          = policy_anything
...
[ req ]
req_extensions  = v3_req
...
[ v3_req ]
subjectAltName  = @alternate_names

[ alternate_names ]
# 手工增加此“节”。多个域名可以存放到此“节”,此后一个证书可以用于多个域名。
# DNS.0001 = host1.example.com
# DNS.0002 = host2.example.com
DNS.9000 = 127.0.0.1
DNS.9001 = localhost
DNS.9001 = localhost.localdomain
DNS.9003 = ::1
...

然后,创建目录结构,并初始化内容。

sudo mkdir -p /etc/ssl/demoCA/private \
              /etc/ssl/demoCA/newcerts \
              /etc/nginx/certs \
              /etc/nginx/snippets
sudo chmod 0700 /etc/ssl/demoCA/private
sudo touch /etc/ssl/demoCA/index.txt
echo 01 | sudo tee /etc/ssl/demoCA/serial >/dev/null

1. 自建 CA 服务器

1.1. 生成自建 CA 密钥

sudo openssl genrsa -out /etc/ssl/demoCA/private/cakey.pem 2048

1.2. 生成自建 CA 证书

sudo openssl req -new -x509 -days 3652 \
    -key /etc/ssl/demoCA/private/cakey.pem \
    -out /etc/ssl/demoCA/cacert.pem \
    -subj '/O=Leenware Inc/CN=Leenware Self-signed Authority'

参数 -subj 的内容使用 / 作为开始以及字段的间隔。O= 表示签发证书的单位名称,在生成 CA 证书时 CN= 用于表示证书的通用名称。当然还可以根据 RFC 4514 的定义增加其他字段。

可以使用命令 openssl x509 -in /etc/ssl/demoCA/cacert.pem -text 查看生成的根证书。

2. 导入 CA 证书

2.1. 向系统导入 CA 证书

cat /etc/ssl/demoCA/cacert.pem | sudo tee -a /etc/ssl/certs/ca-bundle.crt >/dev/null

在系统中导入自建 CA 后可以用 curl 直接访问 HTTPS,否则必须在 curl 中使用参数 --cacert /etc/ssl/demoCA/cacert.pem

2.2. 向 Firefox 导入 CA 证书

firefox --new-window /etc/ssl/demoCA/cacert.pem

运行上面的命令会打开一个新的 Firefox 窗口,请求确认导入 CA 证书。

图:向 Firefox 导入 CA 证书

3. 在 nginx 中部署 HTTPS/HTTP2

注意:除非特殊说明,以下命令在 Web 服务器上运行。

3.1. 生成 SSL 密钥

sudo openssl genrsa -out /etc/nginx/certs/self-signed.key 2048

3.2. 生成证书签发请求

sudo openssl req -new \
    -key /etc/nginx/certs/self-signed.key \
    -out /etc/nginx/certs/self-signed.csr \
    -subj '/O=Leenware Inc/CN=localhost'

-subj 参见 生成自建 CA 证书。不同的地方是:生成请求时 CN 字段应填写域名。

可以使用命令 openssl req -in /etc/nginx/certs/self-signed.csr -text 查看生成的证书签发请求。

3.3. 自建 CA 根据请求签发证书

将生成的证书签发请求(文件 self-signed.csr)复制或上传到 CA 服务器,然后在 CA 服务器运行以下命令:

sudo openssl ca -days 730 \
    -in /etc/nginx/certs/self-signed.csr \
    -out /etc/nginx/certs/self-signed.crt

可以使用命令 openssl x509 -in /etc/nginx/certs/self-signed.crt -text 查看已完成签发的证书。

将签发成功的证书(文件 self-signed.crt)复制或下载到 Web 服务器,完成证书签发。

3.4. 创建 nginx 片段(可选)

cat <<EOF | sudo tee /etc/nginx/snippets/self-signed.conf >/dev/null
ssl_certificate certs/self-signed.crt;
ssl_certificate_key certs/self-signed.key;
EOF

使用 nginx 片段可以简化配置过程。在 /etc/nginx/sites-avaliable 目录下配置文件中通过 include snippets/self-signed.conf; 使用这个片段。

4. 重签、新签证书

重复 3. 在 nginx 中部署 HTTPS/HTTP2 的步骤即可。

5. 我后悔了,要清除刚才的操作

sudo python -c 'cacert = open("/etc/ssl/demoCA/cacert.pem", "r").read(); bundle = open("/etc/ssl/certs/ca-bundle.crt", "r").read(); open("/etc/ssl/certs/ca-bundle.crt", "w").write(bundle.replace(cacert, ""))'
sudo rm -rf /etc/ssl/demoCA
sudo rm -rf /etc/nginx/certs/self-signed.*
sudo rm -rf /etc/nginx/snippets/self-signed.conf

注意:Firefox 中导入的 CA 证书需要手工删除。


RFC 4514 支持的字段及含义

String X.500 AttributeType
CN commonName (2.5.4.3)
L localityName (2.5.4.7)
ST stateOrProvinceName (2.5.4.8)
O organizationName (2.5.4.10)
OU organizationalUnitName (2.5.4.11)
C countryName (2.5.4.6)
STREET streetAddress (2.5.4.9)
DC domainComponent (0.9.2342.19200300.100.1.25)
UID userId (0.9.2342.19200300.100.1.1)

参考文献