最后一次,彻底搞懂 HTTPS 的证书链
上一篇文章用通俗易懂的语言,解释了 HTTPS 的加密和证书两大核心功能的原理。这一次,我们从理论到实践,手动在本地模拟证书链的验证过程。
还是一样,尽量不扯那些乱七八糟的概念,只按照自己的理解来讲。
自己创建一个证书
❓ 能不能不通过“权威机构”,自己给自己的服务器颁发一个证书?
答案是可以的。
创建一个证书,通常只需要 3 步:
1. 生成私钥
openssl genrsa -out server.key.pem 20482. 生成证书签名请求 CSR
openssl req -new -key server.key.pem -out server.csr.pem先不用管输入提示,直接一路回车即可。
3. 生成自签名证书
openssl x509 -req -days 365 -in server.csr.pem -signkey server.key.pem -out server.crt.pem👉 这 3 步操作分别得到3 个文件:
- 私钥:
server.key.pem - 证书签名请求:
server.csr.pem - 证书:
server.crt.pem
我们真正需要的就是私钥和证书两个文件。
“
📌 小提示
上一期文章中提到的用于给“预主密钥”加、解密的“长期私钥+公钥”就是这俩玩意儿,公钥包含在“证书”文件中。
❓ 这玩意儿真的能用吗?
OK,我们现在直接上服务去器验证一下吧。
为了简单,我就直接使用Bun在本地搭一个Hello World服务:
// server.ts
export default {
port: 443,
tls: {
key: Bun.file('./ssl/server.key.pem'),
cert: Bun.file('./ssl/server.crt.pem'),
},
fetch() {
return new Response("Hello World");
},
};启动服务:
bun server.ts使用浏览器访问:https://localhost,然后就看到大家都非常熟悉的一幕:

不过也不用怕,展开“高级”按钮,然后点击“继续前往”,一样能够正常访问我们的服务。

浏览器虽然不信任,但还是将选择权交给了用户,让用户自行斟酌其安全风险。
❓ 为什么浏览器会报“不安全”提示?
👉 很简单,就是因为我们的这个“证书”是不被浏览器信任的。
你自己随便就搞了一个证书,浏览器它肯定不能信任你啊,否则也就没有安全可言了。
“
还记得上一期讲的👉 证书信任链吗?你自己生成的证书根本就没有上一级证书“信任”你,你这个证书是“自签名”的。
要想证书被认可,就必须使用上一级证书“给你进行签名”,上一级证书又需要上上一级证书的签名,一直持续到系统内置的根证书。
如何解决证书“不安全”的问题?
根据“证书链”的这个原理,只要让我们的证书由可信任的上一级证书进行签发,问题不就解决了吗?
但是上一级证书又如何让系统内置的根证书信任呢?我们是没有系统根证书的私钥信息的,没法实现自己用系统根证书进行签发。
其实按照这个逻辑,我们只要👉 给浏览器导入一个我们自己的根证书,问题就能够解决。
所以,我们只需要创建两个证书:
- 根证书:需要导入浏览器,作为内置可信任的证书;
- 服务器证书:由根证书签发,部署在服务器上。
还是按照之前生成证书的3 个步骤来。
👉 一、生成根证书
# 生成根证书 私钥
openssl genrsa -out rootCA.key.pem 2048
# 生成根证书 csr
openssl req -new -key rootCA.key.pem -out rootCA.csr.pem
# 生成自签名根证书
openssl x509 -req -days 365 -in rootCA.csr.pem -signkey rootCA.key.pem -out rootCA.crt.pem与上面生成证书的方式一模一样,一路回车,唯一不同的是使用了rootCA作为输出文件,便于区分。
根证书就是自签名的,需要作为浏览器内置的可信任证书。
👉 二、生成服务器证书
前面两步操作还是一模一样,一路回车不用管:
# 生成服务器证书 私钥
openssl genrsa -out server.key.pem 2048
# 生成服务器证书 csr
openssl req -new -key server.key.pem -out server.csr.pem最后签名的步骤👉 需要使用根证书进行签名:
# 生成服务器证书
openssl x509 -req -days 365 -in server.csr.pem -CAkey rootCA.key.pem -CA rootCA.crt.pem -extfile server.ext -out server.crt.pem中间多了一些参数:-CAkey rootCA.key.pem -CA rootCA.crt.pem -extfile server.ext。
-CA:rootCA.crt.pem-CAkey:rootCA.key.pem-extfile:server.ext
注意其中-extfile参数需要我们在当前目录下新建一个server.ext文件,其内容是:
subjectAltName = @alt_names
[alt_names]
DNS = localhost“
❗️ 注意
server.ext文件主要是为了设置证书的subjectAltName字段,这是现在浏览器必须要求的一个字段,用来匹配服务器的域名、IP等信息。
多个域名和 IP 通常可以这样写:
subjectAltName = @alt_names
[alt_names]
DNS.1 = localhost
DNS.2 = test1.com
DNS.3 = test2.com
IP.1 = 127.0.0.1浏览器会根据这些信息来判断域名、IP 是否一致,如果不一致也会报“不安全”提示。
👉 三、配置根证书
现在,在浏览器地址栏输入chrome://certificate-manager/localcerts/usercerts,进入证书管理页面。

在“可信证书”一行,点击“导入”按钮,选择我们生成的“根证书”即可。

👉 四、服务验证
然后修改之前的代码,指向新生成的服务器证书,并重启服务。
export default {
port: 443,
tls: {
key: Bun.file('./ssl/server.key.pem'),
cert: Bun.file('./ssl/server.crt.pem'),
},
fetch() {
return new Response("Hello World");
},
};bun server.ts最后在浏览器输入https://localhost,回车:这下浏览器就没有“不安全”的提示信息了,完美!

总结一下吧
本次我们对证书信任链的理论逻辑,在本地进行了模拟,是完全可以跑通一个正常的 HTTPS 的。
如果你坚持看完了,并且也跟着动手“操作”了一遍,相信一定会加深你对 HTTPS 证书的理解。
如果你仅仅只是想快速在本地开发环境搭一个 HTTPS 的服务,那么可以借助第三方工具mkcert快速生成证书。
# 在本地安装根证书
mkcert -install
# 为 localhost 域名生成证书
mkcert localhost其原理也是一样:先在本地安装一个可信任的根证书,然后由根证书签发服务器证书。