打开一个HTTPS网站,浏览器会判断连接是否安全。判断依据是:谁给这张站点证书签了字。操作系统和浏览器各自维护一份受信任的根证书颁发机构(CA)清单,Let's Encrypt、DigiCert 的根证书就在里面。站点证书只要能链回其中一张根证书,连接就算可信。
有人想从内部看清这套机制,于是动手做了一遍:自己创建一个根CA,用它给测试站点 mytestsite.local 签发证书,再让 Chrome 和 curl 都信任它。环境是一台 Windows 笔记本,服务器跑在 WSL2 里(Ubuntu 20.04,OpenSSL 1.1.1f),浏览器是 Windows 上的 Chrome。
关于主机名有个提醒:mytestsite.local 在本机跑通了,但复现时建议换成 mytestsite.test。.local 保留给多播DNS(mDNS),在某些网络上会引发域名解析冲突;.test 则是 RFC 6761 保留给测试用途的。换名字时,每条命令里的主机名都要一起替换。
信任是怎么串起来的
整套流程的骨架是一条链:本地根CA自签名,标记为 CA:TRUE;根CA给站点签发证书;客户端只要信任根,就信任了站点。证书本身不会自动获得信任,信任取决于客户端信任库里存了什么。
签发环节需要两个配置文件,一个给根CA(ca.cnf),一个给站点证书扩展(v3.ext)。服务器端用 Python 起一个极简 HTTPS 服务:响应体返回一段 HTML,SSL 上下文加载站点证书和私钥,把 socket 包成 TLS 服务端,监听 127.0.0.1 的 443 端口。低于 1024 的端口需要 root 权限,所以启动命令前面加了 sudo。
域名解析:Windows 和 WSL 各管各的
mytestsite.local 没有 DNS 记录,得手动映射。Windows 侧改 hosts 文件(C:\Windows\System32\drivers\etc\hosts),加一行 127.0.0.1 mytestsite.local。WSL2 默认网络模式下,Windows 能通过 localhost 访问 WSL 里的服务,所以 Windows 上的 Chrome 能连到 WSL 里跑的 Python 服务器。
WSL 里的 curl 也要能解析这个名字。先用 getent hosts 检查。WSL 会自己生成 /etc/hosts(由 /etc/wsl.conf 里的 generateHosts 控制,默认开启),而生成的文件不包含 Windows 侧的自定义条目。实际操作中,这个名字在 WSL 里居然能解析,可能是 WSL 的 DNS 转发经由 Windows 完成了查询——但这依赖 WSL 版本和网络配置,不能当作可靠前提。如果 getent 什么都没打印,就在 WSL 里也补一条映射。
注意:generateHosts 开启时,WSL 重启可能重写 /etc/hosts。条目消失就再加一次,或者把 generateHosts 设为 false。
两个信任库,得分别喂
Windows 和 WSL 各有独立的信任库,只认你亲手加进去的东西。先把根证书复制到 Windows 能访问的位置——只复制 .crt,绝不复制 .key。WSL 把 C 盘挂在 /mnt/c,一条 cp 命令即可。
然后以管理员身份运行 PowerShell,用 Import-Certificate 把证书导入 Windows 的受信任根证书颁发机构存储(Cert:\LocalMachine\Root)。Chrome 和 Edge 使用这个库;Firefox 默认用自己的库。
Windows 信任了,WSL 里的 curl 照样报错:curl: (60) SSL certificate problem: unable to get local issuer certificate。Ubuntu 不读 Windows 的信任库。把根证书复制到 /usr/local/share/ca-certificates/ 再执行 update-ca-certificates,输出 1 added, 0 removed。文件名必须以 .crt 结尾,否则这条命令会直接跳过它。
命令还会打出一行 rehash: warning: skipping ca-certificates.crt。这不是问题:那个文件是工具自己拼出来的合并包,不是单张证书。另外,Python 的 requests 库默认用自带的 certifi 证书包,想让它改用 Ubuntu 系统包,设置环境变量 REQUESTS_CA_BUNDLE 指向 /etc/ssl/certs/ca-certificates.crt。
验证与收尾
curl 在 CA 被信任前失败,执行 update-ca-certificates 后成功返回页面内容。重启 Chrome 后页面正常加载,没有安全警告。点地址栏旁的图标,依次进入“连接安全”“证书有效”,能看到链条:My Local Root CA → mytestsite.local。
根CA的权限很大:任何拿到 myRootCA.key 的人,都能为任意域名签发证书,包括 google.com,而这台笔记本会照单全收。所以实验做完要清理:
- WSL 里删除 /usr/local/share/ca-certificates/myRootCA.crt,再执行 update-ca-certificates --fresh
- Windows 里用 PowerShell 从 Cert:\LocalMachine\Root 中筛出 CN=My Local Root CA 并删除
- 删掉 Windows hosts 文件和 WSL /etc/hosts 里的对应行,以及 C:\Users\Public\myRootCA.crt
- 删除 myRootCA.key 和服务器私钥,若要复用实验环境则放到受保护的位置
清理后同一站点重新报警:Chrome 标记“不安全”并划掉 https,WSL 里的 curl 再次报 unable to get local issuer certificate。信任确实没了。
这趟折腾留下的几条结论
- HTTPS 信任是一条链:客户端信任根,根(通常经由中间证书)签发站点,客户端因此信任站点。
- 证书不会自己获得信任,信任取决于客户端信任库里有什么。
- 一台笔记本可以同时存在多个信任库:Windows、WSL/Ubuntu、Firefox、Python 的 certifi。
- 浏览器拿主机名匹配的是 SAN,不是 CN。
- openssl verify -verify_hostname 一条命令同时校验链和名字。
- 根CA私钥是整个搭建里最敏感的文件。
热门跟贴