📘 第七章 确保Web安全的HTTPS
📄 一句话概括

证书解决的是「这把公钥属于谁」。它把公钥和域名绑在一起,再由一个受信的机构盖章。 验证时客户端顺着签发关系一路追到受信根,追不到就不认。

📝 本节和前面几节的关系

上一节有了钥匙,这一节解决「怎么确认钥匙的主人」。没有这一步,前面所有加密都可以对着中间人做。

一、证书里到底有什么

Demo 用 openssl 现场造了两张:一张自签的根 CN=Demo Root CA, 一张由它签发的服务器证书 CN=demo.local。 看四个字段就能读出关系:服务器证书的 Issuer 正好等于根证书的 Subject; 根证书的 Issuer 等于自己的 Subject(所以叫自签); 两张的签名算法都是 sha256WithRSAEncryption,公钥算法都是 rsaEncryption。 真实证书还多两样东西:SAN(这个证书对哪些域名有效)和有效期。 SAN 是现代校验的真正依据,CN 早就只作参考了——所以只写 CN 不写 SAN 的证书,浏览器会直接拒。

二、链是怎么验的

客户端拿到服务器证书,先看 Issuer 是谁,再去自己的信任库里找那张证书; 找到之后用它的公钥验签名,验过了就再看下一层,直到碰到一个「本来就在信任库里」的根。 Demo 里两组对照:信任 ca.pem 时通过,收到 103 字节; 不信任时被拒,报错是 unable to get local issuer certificate——意思是「链追到一半断了, 找不到签发者」。 注意这个报错和 7.1.2 里的 Hostname mismatch 是两码事。一个是链断了,一个是名字不对。 排查证书问题时先分清是哪一种,能省很多时间。

三、现实里的证书从哪来

浏览器和操作系统内置了一百多个根证书。 正规站点不需要你手动加根,它的链能追到某个内置根上——中间可能还有一两层中间证书。 签发证书是要花钱的,直到 2015 年 Let's Encrypt 出现,把「域名验证型」证书做到了免费、自动化、 90 天一期。这件事直接推动了全网的 HTTPS 普及率从一半涨到九成以上。 自签证书并没有消失,它在内网、测试、物联网设备里大量使用——代价是每台客户端都得手动信任它。 这也解释了为什么「请安装我们的根证书」这种要求在企業内网很常见,在公网就很可疑。

四、动手 Demo:造一条两级链,看信任库起什么作用

用 openssl 生成一个自签根和一个由它签发的服务器证书, 打印两张证书的 Subject/Issuer/序列号/签名算法; 然后起一个真 TLS 服务端,客户端分别带着和不带根证书去连。

# 1. 自签根
openssl req -x509 -newkey rsa:2048 -keyout ca.key -out ca.pem \
        -days 2 -nodes -subj '/CN=Demo Root CA' \
        -addext 'basicConstraints=critical,CA:TRUE'

# 2. 服务器证书,由根签发
openssl req -newkey rsa:2048 -keyout srv.key -out srv.csr -nodes \
        -subj '/CN=demo.local'
openssl x509 -req -in srv.csr -CA ca.pem -CAkey ca.key \
        -CAcreateserial -out srv.pem -days 2 -extfile srv.cnf

# 3. 客户端只做一件事:决定信任库里放不放那张根
ctx.load_verify_locations(ca_pem)

实跑输出:

--------------------------------------------------------------------
证书:把公钥和名字绑在一起,再盖个章
--------------------------------------------------------------------
  先用 openssl 现场造一条两级链:一个自己当根、一个由它签发。
  根证书 ca.pem:
    subject=CN=Demo Root CA
    issuer=CN=Demo Root CA
    serial=74F7A6B3EEA0A07939B056CD4908F82142915896
    notBefore=Oct 25 06:50:40 2026 GMT
    notAfter=Oct 27 06:50:40 2026 GMT
    Signature Algorithm: sha256WithRSAEncryption
    Public Key Algorithm: rsaEncryption

  服务器证书 srv.pem:
    subject=CN=demo.local
    issuer=CN=Demo Root CA
    serial=4BC4AD268A2096D9E53F29E8E8A2353235D95A35
    notBefore=Oct 25 06:50:40 2026 GMT
    notAfter=Oct 27 06:50:40 2026 GMT
    Signature Algorithm: sha256WithRSAEncryption
    Public Key Algorithm: rsaEncryption

  两份证书的关系,靠这三个字段就能看出来:
    srv.pem 的 Issuer    = ca.pem 的 Subject
    srv.pem 上带 CA 的签名,用 ca.pem 的公钥能验过
    ca.pem 是自签的(Issuer 等于 Subject),所以它必须出现在信任库里

  客户端手里有 / 没有那张根证书,结果完全不同:
    信任 ca.pem                通过,收到 103 字节
    不信任 ca.pem               拒绝:unable to get local issuer certificate

  第一条通过,是因为客户端把 ca.pem 放进了信任库,
  于是 `srv.pem → ca.pem` 这条链能追到底,签名也验得过。
  第二条拒绝,因为链断在了半路——`srv.pem` 的签发者不在信任库里。

  这就是证书的作用:**它不是加密用的,是用来证明「这把公钥属于这个名字」**。
  没有它,公钥交换就只能靠「我相信第一个给我钥匙的人」,
  而那正是中间人最擅长的场景。

  再补一句现实情况:浏览器内置了一百多个根证书,
  所以正规站点不用你手动加根——它的链能追到某个内置根上。

根证书:Subject 和 Issuer 都是 CN=Demo Root CA,序列号一长串十六进制。 服务器证书:Subject CN=demo.local,Issuer CN=Demo Root CA——这两个字段对上了,链就连起来了。 信任根时通过,收到 103 字节;不信任时被拒,报错 unable to get local issuer certificate。 同一对密钥、同一张证书,只因为客户端信任库不同,结果就完全相反。 这说明「合法」不是证书自身的属性,而是证书加信任库共同决定的。

30 秒实验

  1. 把 -days 2 改成 -days 0,看报错变成什么。
  2. 给服务器证书加一个 SAN 为 other.local,用 demo.local 去连,看是否被拒。
  3. 想一想:为什么企业内网要求你安装根证书时,要格外小心?

常见误区

  1. 「证书是用来加密的」——它用来证明公钥归属,加密由协商出的会话密钥做。
  2. 「CN 写了域名就够了」——现代校验看 SAN,只写 CN 会被拒。
  3. 「自签证书不能用」——内网、测试、设备里大量使用,只是要手动信任。
自测三问(点开对答案) 1. 怎么从字段上判断两张证书是一对签发关系? —— 子证书的 Issuer 等于父证书的 Subject。 2. `unable to get local issuer certificate` 是什么意思? —— 链断了:签发者不在客户端的信任库里。 3. 证书本身是靠什么保证不被伪造的? —— 签发者的签名。要伪造就得先攻破签发者的私钥。

材料齐了:三样保护、外壳位置、两把钥匙、证书。最后把它们串成一次完整握手。

相关笔记