4. 自签名证书

说实话,很多团队在开发环境里用自签名证书,图的就是快。但你真去生产环境也这么干?那我就要劝你慎重了。不过话说回来,理解自签名证书的完整流程,能帮你把TLS的信任链看得更透。

我自己带项目的时候,要求每个新人都亲手走一遍这个流程。为什么?因为只有亲手创建过根CA、签发过服务器证书,你才能真正理解“信任”二字在TLS里是怎么传递的。

整体思路:先有根,再有枝

自签名证书体系,说白了就是一个迷你CA。你既是CA又是证书使用者。流程分三步:

  1. 创建根CA证书 – 这是信任的起点,自己签名自己。
  2. 用根CA签发服务器证书 – 生成CSR,让根CA给它签名。
  3. 把根CA证书导入浏览器 – 浏览器信任根CA,顺便也信任它签发的服务器证书。

嗯,核心逻辑就这些。下面咱们一步一步来。

自签名证书信任链流程图 1. 创建根CA证书 自签名,自己信任自己 2. 签发服务器证书 生成CSR → 根CA签名 3. 导入浏览器 信任根CA → 信任所有子证书 提示: 自签名CA没有上级CA背书,所以浏览器默认不信任。你需要手动导入根CA到“受信任的根证书颁发机构”。 本质上和商业CA流程一致,只不过签字的人换成了你自己。

第一步:创建根CA证书

根CA证书是信任的锚点。我习惯用OpenSSL来做,因为它几乎无处不在。先准备一个配置文件,帮我们组织好证书信息。

以下是我常用的步骤:

  1. 生成RSA私钥(4096位,别省)
  2. 创建自签名根证书 – 签发者就是自己

核心命令:

# 1. 生成根CA私钥
openssl genrsa -out ca.key 4096

# 2. 使用私钥自签名根证书(有效期10年)
openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 \
  -subj "/C=CN/ST=Beijing/L=Beijing/O=MyLab/CN=My Root CA" \
  -out ca.crt

你看,这里用了 -x509 标志,意思是直接生成自签名证书,而不是CSR。CN(通用名称)我写了“My Root CA”,你可以改成任何标识。但记住——浏览器最终会根据这个CN来判断是否匹配,所以你最好写一个自己能记住的名字。

个人习惯:我把根CA的有效期设成10年。生产环境我建议3-5年就够了,毕竟密钥泄露风险。但我自己测试环境懒,10年省事。你想想看,开发机上的证书过期了,又得重新签一堆,麻烦。

第二步:用根CA签发服务器证书

现在我们有了根CA,接下来要给具体的服务器(比如你的本地Web站点)签发证书。这一步需要先生成一个CSR(证书签名请求),然后交给根CA去签名。

2.1 生成服务器私钥和CSR

# 生成服务器私钥(2048位够用,你也可以用4096)
openssl genrsa -out server.key 2048

# 生成CSR
openssl req -new -key server.key -sha256 \
  -subj "/C=CN/ST=Beijing/L=Beijing/O=MyLab/CN=localhost" \
  -out server.csr

这里有个坑——CN必须和你访问服务器时用的域名或IP一致。比如你用 https://localhost 访问,CN就写 localhost;如果你用 https://192.168.1.10,CN就写 192.168.1.10。我曾经有个同事,CN写了公司域名,本地测试时一直报证书不匹配,查了半天才发现是CN写成了 example.com,但实际访问的是 localhost。嗯,教训就是:别偷懒,写对。

2.2 用根CA给CSR签名

这里我们需要一个扩展配置文件,用来指定证书用途。自签名证书如果不加扩展,浏览器可能会报“证书用途无效”。

创建扩展文件(v3.ext):

authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names

[alt_names]
DNS.1 = localhost
DNS.2 = 127.0.0.1
IP.1 = 127.0.0.1

然后执行签名:

openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key \
  -CAcreateserial -out server.crt -days 365 -sha256 \
  -extfile v3.ext

执行完你会得到 server.crt(服务器证书)和 ca.srl(序列号文件,CA内部管理用)。到此,你的服务器证书已经由根CA签发了。

第三步:在浏览器中信任自签名证书

浏览器可不会自动信任你的根CA。你需要手动把 ca.crt 导入到“受信任的根证书颁发机构”中。不同浏览器其实都依赖操作系统的证书存储,所以操作一次就能全局生效(除Firefox外,它有自己的证书库)。

3.1 Windows系统

  1. 双击 ca.crt → 选择“安装证书”。
  2. 选择“本地计算机”(需要管理员权限)。
  3. 选择“将所有证书放入下列存储” → 浏览 → 选择“受信任的根证书颁发机构”。
  4. 完成。重启浏览器。

3.2 macOS系统

  1. 打开“钥匙串访问” → 将 ca.crt 拖入“系统”钥匙串。
  2. 双击该证书 → 展开“信任” → 将“使用此证书时”改为“始终信任”。
  3. 关闭窗口,输入密码。完成。

3.3 Linux系统(以Ubuntu为例)

sudo cp ca.crt /usr/local/share/ca-certificates/my-root-ca.crt
sudo update-ca-certificates

完事后,重启浏览器,访问你的HTTPS站点,应该能看到小锁标志了。

注意:千万不要把自签名证书的生产根CA私钥泄露给任何人。一旦泄露,攻击者可以签发任何域名的证书。我在项目中见过一次内部工具泄密,不得不紧急吊销整个自签名CA。所以要像保护银行卡密码一样保护你的根CA私钥。

验证证书链

证书安装完后,可以用下面的命令验证服务器证书是否被根CA正确签署:

openssl verify -CAfile ca.crt server.crt

如果输出 server.crt: OK,说明链是完整的。如果报错,八成是SAN(主题备用名称)没写对,或者根证书没导入。

常见证书参数对比

参数 根CA证书 服务器证书
签发者 自己(自签名) 根CA
有效期 3-10年(推荐5年) 1-2年
密钥长度 4096位 2048位(也可4096)
用途扩展 CA:TRUE CA:FALSE + SAN
浏览器信任 需要手动导入 自动信任(根已导入)

为什么服务器证书有效期短?说白了,私钥泄露风险。服务器证书经常暴露在公网上,可能被提取。根CA的私钥你锁在机器里,泄露概率低很多。所以长有效期给根CA,短有效期给服务器证书,这是安全实践。

避坑指南:我曾经踩过的几个坑

  • 漏写SAN:Chrome从58版本开始,必须要有SAN扩展,否则直接报“NET::ERR_CERT_COMMON_NAME_INVALID”。我见过有人调试了一下午,最后发现就是v3.ext里忘了加SAN段。
  • 私钥权限:生成私钥后,记得 chmod 600 *.key,不然启动Web服务器时提示“权限太开放”。
  • 浏览器缓存:导入根证书后如果还是报错,先清除SSL状态或者重启浏览器。我记得有一次折腾半天,最后发现是浏览器缓存了旧的证书状态。
  • Firefox不信:Firefox不使用系统证书存储,需要单独导入。路径:偏好设置 → 隐私与安全 → 证书 → 查看证书 → 导入。

小结一下

自签名证书的流程,说穿了就是:创建根CA → 签发服务器证书 → 信任根CA。你手里的 ca.crt 和 ca.key 是整个信任链的根基,务必保管好。

这套方法在开发测试、内网服务加密、CI/CD环境中非常实用。我自己的实验室里,所有内部服务都跑着自签名证书,省去了申请商业证书的麻烦。当然,线上环境千万别这么干——用户浏览器不会信任你的根CA,你得买正经的CA签发的证书。