rustls 源码解析
rustls 是一个用 Rust 写的 TLS 库,实现 TLS 1.2 和 TLS 1.3 的客户端与服务端。它不开套接字:ClientConnection / ServerConnection 只是一段「加密管道」,调用方用 read_tls 把从网络读到的字节交给它,用 write_tls 取走要发出去的字节,再用 process_new_packets 让它处理。README 对自己的定位是「aims to provide a good level of cryptographic security, requires no configuration to achieve that security, and provides no unsafe features or obsolete cryptography by default」。握手是一串 Box<dyn State>,每个 Expect* 结构只接受它等的那条消息;TLS 1.3 的密钥日程是一串 KeySchedule* 类型,上一步被消耗才能得到下一步;所有密码学原语都来自一个可替换的 CryptoProvider(默认 aws-lc-rs,可选 ring);证书路径校验交给单独的 crate rustls-webpki。这些说法落在哪些代码上、和 OpenSSL 比能测出什么,见rustls 解决什么问题。
锁定的版本
| 项 | 本卷 | 说明 |
|---|---|---|
| rustls | 0.23.46,tag v/0.23.46 → f04804fa(2026-10-10,「Delay saving KX hint until handshake confirmation」) | tag 名里带斜杠;crates.io 上 rustls-0.23.46 包的 .cargo_vcs_info.json 记的就是这个 commit |
| 分支 | 0.23.x 在 rel-0.23 分支上发版 | tag 不是 main 的祖先,两者的分叉点是 4e0b5fed(2025-06-30,「Bump version to 0.23.29」);main 是 0.24 的开发线,本卷不讲 |
| 历史 | 首个提交 2016-05-02,tag 上可达 4134 个提交 | 仓库根目录的 CHANGELOG.md 只有一句话,指向 GitHub releases 页 |
| rustls-webpki | 0.103.16,tag v/0.103.16 → 8ecdcc7(2026-10-09) | 证书路径校验,0.23.46 实际解析到的版本 |
| rustls-pki-types | 1.15.1,tag v/1.15.1 → e29ad15(2026-07-22) | CertificateDer、PrivateKeyDer、ServerName、UnixTime、PEM 解析 |
| 密码学后端 | ring 0.17.14、aws-lc-rs 1.19.0(aws-lc-sys 0.47.0) | 只讲 rustls 怎么接它们,不讲它们的内部 |
源码链接一律指向 tag,形如 KeyScheduleHandshakeStart;webpki 和 pki-types 的链接指向各自仓库的 tag。
讲什么
- 连接与 sans-IO 接口:
ConnectionCommon、CommonState、read_tls/write_tls/process_new_packets、Stream/StreamOwned、不缓冲的unbuffered接口。 - 记录层:编解码(
Codec)、去帧与输入缓冲、加密与序列号、分片与发送缓冲。 - 握手:
Statetrait 与Expect*状态、客户端与服务端的 TLS 1.3、密钥日程、会话恢复与 0-RTT;TLS 1.2、ECH、QUIC、kTLS 各一节。 - 证书校验:
WebPkiServerVerifier怎么调 rustls-webpki,路径构建和它的预算,客户端证书与自定义校验器。 - 密码学提供者:
CryptoProvider的字段、进程级默认值、两个内置 provider 的套件与密钥交换组。 - 只提一句:bogo / openssl-tests / fuzz 等测试设施、FIPS 认证本身(只讲
fipsfeature 改了什么)、rustls-post-quantum实验 crate。
三条主线
全卷沿三条主线展开,每页开头标出自己在哪条线上:
- 握手主线(一次 TLS 1.3 完整握手):
ClientConnection::new发出 ClientHello →ServerConnection选参数,回 ServerHello 和一条加密记录(EncryptedExtensions、Certificate、CertificateVerify、Finished)→ 客户端逐个Expect*状态校验,推进密钥日程,发 Finished → 双方进入应用数据阶段 → 服务端发两张会话票据。 - 记录主线(一段应用数据的往返):
writer().write_all→ 分片 → 加密(序列号与 nonce)→write_tls交给调用方 → 对端read_tls进缓冲 →process_new_packets去帧、解密 →reader().read。 - 校验主线(一张服务器证书怎么被信任):
WebPkiServerVerifier::verify_server_cert→ rustls-webpki 的路径构建(签发者匹配、约束、有效期、预算上限)→ 名字匹配 → CertificateVerify 的签名 → 吊销检查(可选)。
每条线的每一步落在哪个函数、哪一页讲,见怎么读这一卷。握手主线和记录主线在本机上的逐条记录实测见一次握手。
flowchart TB intro[00 导读] --> overview[01 全景] overview --> records[02 记录层] records --> hs[03 握手] hs --> verify[04 证书校验] verify --> crypto["05 密码学提供者"] crypto --> design[06 设计取舍] design --> pit[07 常见坑] pit --> site[08 在本站中] site --> hands[09 动手]
实验环境
关于行为的说法(握手几轮、每条记录多长、用哪个密钥加密、报什么错、耗时多少)都配一个小程序和它的真实输出。平台是 M2 Max、macOS 27、rustc 1.99.0(aarch64)。实验程序依赖 crates.io 上的 rustls = "=0.23.46",同时打开 ring 和 aws_lc_rs 两个 feature,所以每个配置都用 builder_with_provider 显式指定 provider。所有握手都在进程内存里或 127.0.0.1 上完成,不连外网;对照 OpenSSL 用本机的 openssl 命令(Homebrew 的 OpenSSL 4.1.0,不是 macOS 自带的 LibreSSL)。
各部分
- 00 导读:锁定版本;三条主线每一步落在哪;feature 表和它们互相牵连的地方;
manual/模块怎么当资料读;rustls/src各目录分工、依赖方向与工作区里的其他 crate。 - 01 全景:rustls 不做什么(实测:CA 证书不能直接当服务器证书);两个 provider 的握手耗时与二进制大小,和 OpenSSL 在 127.0.0.1 上的对照;sans-IO 的驱动循环和漏掉一步时的症状;一次握手的 9 条记录,每条用哪个密钥加密。
- 02 记录层:编解码、去帧与缓冲、加密与序列号、发送路径。
- 03 握手:状态机、客户端 TLS 1.3、服务端 TLS 1.3、密钥日程、会话恢复与 0-RTT、TLS 1.2 与 ECH / QUIC / kTLS。
- 04 证书校验:服务器证书校验器、rustls-webpki 的路径构建、客户端证书与自定义校验。
- 05 密码学提供者:
CryptoProvider、套件与密钥交换组。 - 06 设计取舍:sans-IO、类型驱动的状态机与密钥日程、不实现旧协议、可替换的密码学后端、校验放在独立 crate。
- 07 常见坑:十八个坑,报错原文做标题,每个都先跑出最小复现。
- 08 在本站中:本站的 sqlx、lettre 经 ring provider 用 rustls 0.23.46;reqwest 同时编进了两种 TLS。
- 09 动手:不依赖 rustls,按 RFC 8446 写 TLS 1.3 的密钥日程,用 RFC 8448 的测试向量核对,再解开本卷抓到的一条加密记录。
适合谁读
- 用过 rustls(或 tokio-rustls、reqwest、sqlx 的 rustls feature)、想知道一次
connect背后发生了什么的读者:先读 01 全景 的四页,再读 03 握手。 - 熟悉 OpenSSL 的读者:rustls 解决什么问题 列出了它不做的事和能测的差别,sans-IO 讲清楚为什么没有
SSL_read/SSL_write那样的接口。 - 需要的前置知识:TLS 1.3 握手的大致流程(ClientHello、ServerHello、Finished);对称加密、Diffie-Hellman 密钥交换、数字签名各是做什么的;Rust 的 trait 对象和
std::io::{Read, Write}。不需要 async:rustls 本身是同步的,异步运行时由 tokio-rustls 这样的外层 crate 对接。