跳转到内容

最后一次,彻底搞懂 HTTPS 的证书链

上一篇文章用通俗易懂的语言,解释了 HTTPS 的加密和证书两大核心功能的原理。这一次,我们从理论到实践,手动在本地模拟证书链的验证过程。

还是一样,尽量不扯那些乱七八糟的概念,只按照自己的理解来讲。

自己创建一个证书

❓ 能不能不通过“权威机构”,自己给自己的服务器颁发一个证书?

答案是可以的。

创建一个证书,通常只需要 3 步:

1. 生成私钥

bash
openssl genrsa -out server.key.pem 2048

2. 生成证书签名请求 CSR

bash
openssl req -new -key server.key.pem -out server.csr.pem

先不用管输入提示,直接一路回车即可。

3. 生成自签名证书

bash
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服务:

ts
// 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");
  },
};

启动服务:

bash
bun server.ts

使用浏览器访问:https://localhost,然后就看到大家都非常熟悉的一幕:

访问 HTTPS

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

不安全提示

浏览器虽然不信任,但还是将选择权交给了用户,让用户自行斟酌其安全风险。

❓ 为什么浏览器会报“不安全”提示?

👉 很简单,就是因为我们的这个“证书”是不被浏览器信任的。

你自己随便就搞了一个证书,浏览器它肯定不能信任你啊,否则也就没有安全可言了。

还记得上一期讲的👉 证书信任链吗?你自己生成的证书根本就没有上一级证书“信任”你,你这个证书是“自签名”的。

要想证书被认可,就必须使用上一级证书“给你进行签名”,上一级证书又需要上上一级证书的签名,一直持续到系统内置的根证书。

如何解决证书“不安全”的问题?

根据“证书链”的这个原理,只要让我们的证书由可信任的上一级证书进行签发,问题不就解决了吗?

但是上一级证书又如何让系统内置的根证书信任呢?我们是没有系统根证书的私钥信息的,没法实现自己用系统根证书进行签发。

其实按照这个逻辑,我们只要👉 给浏览器导入一个我们自己的根证书,问题就能够解决。

所以,我们只需要创建两个证书

  • 根证书:需要导入浏览器,作为内置可信任的证书;
  • 服务器证书:由根证书签发,部署在服务器上。

还是按照之前生成证书的3 个步骤来。

👉 一、生成根证书

bash
# 生成根证书 私钥
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作为输出文件,便于区分。

根证书就是自签名的,需要作为浏览器内置的可信任证书。

👉 二、生成服务器证书

前面两步操作还是一模一样,一路回车不用管:

bash
# 生成服务器证书 私钥
openssl genrsa -out server.key.pem 2048
# 生成服务器证书 csr
openssl req -new -key server.key.pem -out server.csr.pem

最后签名的步骤👉 需要使用根证书进行签名

bash
# 生成服务器证书
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
  • -CAkeyrootCA.key.pem
  • -extfile: server.ext

注意其中-extfile参数需要我们在当前目录下新建一个server.ext文件,其内容是:

txt
subjectAltName = @alt_names

[alt_names]
DNS = localhost

❗️ 注意

server.ext文件主要是为了设置证书的subjectAltName字段,这是现在浏览器必须要求的一个字段,用来匹配服务器的域名、IP等信息。

多个域名和 IP 通常可以这样写:

bash
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,进入证书管理页面

导入证书

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

选择根证书

👉 四、服务验证

然后修改之前的代码,指向新生成的服务器证书,并重启服务。

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");
  },
};
bash
bun server.ts

最后在浏览器输入https://localhost,回车:这下浏览器就没有“不安全”的提示信息了,完美!

正常访问 HTTPS

总结一下吧

本次我们对证书信任链的理论逻辑,在本地进行了模拟,是完全可以跑通一个正常的 HTTPS 的。

如果你坚持看完了,并且也跟着动手“操作”了一遍,相信一定会加深你对 HTTPS 证书的理解。

如果你仅仅只是想快速在本地开发环境搭一个 HTTPS 的服务,那么可以借助第三方工具mkcert快速生成证书。

bash
# 在本地安装根证书
mkcert -install

# 为 localhost 域名生成证书
mkcert localhost

其原理也是一样:先在本地安装一个可信任的根证书,然后由根证书签发服务器证书。