Rust in Everything
基于 rustls 0.23.46

怎么读这一卷

主线位置:三条主线的总览。本页把握手、记录、校验三条主线各拆成几步,标出每一步落在哪个函数、哪一页讲它;再逐个核对 rustls/Cargo.toml 里的 feature,其中 prefer-post-quantum 在默认 feature 里、又隐含 aws_lc_rs,所以「默认 feature 加上 ring」会让两个 provider 同时编进来,进程级默认 provider 就选不出来了;最后说明 manual/ 模块怎么读。一次握手在本机上把前两条线跑一遍。

flowchart TB
  start[从这里开始] --> q{读这一卷为了什么}
  q -->|会用| use1["01 全景 再到 03"]
  q -->|读懂实现| all[00 到 09 按顺序]
  q -->|只关心握手| h["一次握手 再到 03"]
  q -->|只关心证书| v["04 证书校验"]

锁定版本

本卷读三个仓库,各锁一个 tag。三个 tag 名里都带斜杠:

仓库tagcommit说明
rustlsv/0.23.46f04804fa(2026-10-10,「Delay saving KX hint until handshake confirmation」)crates.io 包 rustls-0.23.46 的 .cargo_vcs_info.json 记的就是它;包内路径是 rustls/ 子目录
rustls-webpkiv/0.103.168ecdcc7(2026-10-09)crates.io 包同样指向这个 commit
rustls-pki-typesv/1.15.1e29ad15(2026-07-22)同上

rustls 的 0.23.x 在 rel-0.23 分支上发版,git merge-base --is-ancestor v/0.23.46 origin/main 返回假:main 已经是 0.24 的开发线,两条线在 4e0b5fed(0.23.29)分开。所以「这段代码在 tag 之后改过没有」要用 git diff v/0.23.46 origin/main -- <文件> 看,git log v/0.23.46..origin/main 在这里没有意义。

源码链接一律指向 tag:https://github.com/rustls/rustls/blob/v/0.23.46/<路径>#L<行>,行号是条目开始的那一行。本地仓库的工作区在 main 上,读代码要用 git show "v/0.23.46:rustls/src/<文件>"。zsh 里把 tag 放进变量时写 "${T}:rustls/...",写成 "$T:rustls/..." 会被 zsh 当成 :r 修饰符,静悄悄地取错路径。

三条主线

握手主线:一次 TLS 1.3 完整握手

flowchart TB
  new["ClientConnection new"] --> ch["发 ClientHello"]
  ch --> sch["ExpectClientHello"]
  sch --> sflight["ServerHello 加密飞行"]
  sflight --> csh["ExpectServerHello"]
  csh --> cee["EE 到 Finished"]
  cee --> cfin["客户端 Finished"]
  cfin --> sfin["服务端 ExpectFinished"]
  sfin --> tick["两张票据"]
步骤源码讲它的页
1. ClientConnection::new → start_handshake → emit_client_hello_for_retry:选初始密钥共享(initial_key_share),拼扩展,下一个状态是 ExpectServerHelloOrHelloRetryRequestclient/hs.rs一次握手、客户端 TLS 1.3
2. 服务端 ExpectClientHello 选版本、套件、组、证书,交给 CompleteClientHelloHandling::handle_client_hello:emit_server_hello、假 CCS、emit_encrypted_extensions、证书、emit_certificate_verify_tls13、emit_finished_tls13server/hs.rs、server/tls13.rs服务端 TLS 1.3
3. 客户端 handle_server_hello 算出握手密钥,然后依次是 ExpectEncryptedExtensions → ExpectCertificateOrCertReq → ExpectCertificate → ExpectCertificateVerify → ExpectFinishedclient/tls13.rs状态机、客户端 TLS 1.3
4. 每一步推进密钥日程:KeyScheduleEarly → KeyScheduleHandshakeStart → … → KeyScheduleTraffic,转录哈希在 HandshakeHashtls13/key_schedule.rs、hash_hs.rs密钥日程
5. 客户端发 Finished,进入 ExpectTraffic;服务端 ExpectFinished 校验后用 emit_ticket 发票据(默认 send_tls13_tickets = 2),进入 ExpectTrafficclient/tls13.rs、server/tls13.rs会话恢复与 0-RTT

记录主线:一段应用数据的往返

步骤源码讲它的页
1. writer() 返回的 Writer 把明文交给连接;握手没完成时先缓冲在 sendable_plaintextconn.rs、common_state.rs发送路径
2. send_appdata_encrypt 按 MessageFragmenter 切片,send_single_fragment 调 RecordLayer::encrypt_outgoingmsgs/fragmenter.rs、record_layer.rs加密与序列号、发送路径
3. 密文进 ChunkVecBuffer,write_tls 把它写给调用方的 io::Writevecbuf.rs、conn.rssans-IO、发送路径
4. 对端 read_tls 把字节读进 DeframerVecBuffermsgs/deframer/去帧与缓冲
5. process_new_packets → DeframerIter 按 5 字节头切记录 → RecordLayer::decrypt_incoming → process_msg 交给当前状态conn.rs、record_layer.rs去帧与缓冲、加密与序列号
6. reader() 读出明文;没有明文时返回 WouldBlockconn.rssans-IO

校验主线:一张服务器证书怎么被信任

步骤源码讲它的页
1. 客户端 ExpectCertificateVerify 里调配置的校验器;默认是 WebPkiServerVerifier,根证书来自 RootCertStoreclient/tls13.rs、webpki/服务器证书
2. verify_server_cert → verify_server_cert_signed_by_trust_anchor → rustls-webpki 的 EndEntityCert::verify_for_usagewebpki/verify.rs服务器证书
3. 路径构建:ChainOptions::build_chain,签名检查次数受 Budget 限制webpki src/verify_cert.rs路径构建
4. 名字匹配:verify_server_name → verify_is_valid_for_subject_namewebpki src/subject_name/服务器证书
5. CertificateVerify 的签名:verify_tls13_signaturewebpki/verify.rs服务器证书
6. 吊销(可选):CRL,RevocationOptionswebpki src/crl/客户端证书与自定义校验

三条线在 ConnectionCore 交汇:记录主线把字节变成一条条消息,交给握手主线的当前状态;握手主线在 Certificate 和 CertificateVerify 两步上调用校验主线。

实验环境

项本卷
平台M2 Max、macOS 27、rustc 1.99.0,aarch64;计时用 release,取多次里的最小值并注明负载(写作时有其他实验并行在跑,uptime 负载在 4 到 9 之间)
rustlscrates.io 上的 =0.23.46,打开 ring、aws_lc_rs、logging(加上默认 feature);另有 rustls-pki-types 1、webpki-roots 1、rcgen 0.14(造测试证书)、tokio-rustls 0.26
默认握手进程内存里一个客户端、一个服务端,ring provider,rcgen 生成的 localhost 自签名证书(ECDSA P-256),客户端只信任这一张证书
OpenSSLHomebrew 的 openssl 命令,OpenSSL 4.1.0 23 Oct 2026;/usr/bin/openssl 是 macOS 自带的 LibreSSL,本卷不用
网络不连外网,所有握手都在内存或 127.0.0.1 上

会随运行变化的数字页面上会注明:服务端第一轮的字节数在 662 到 664 之间浮动(证书和 CertificateVerify 里的 ECDSA 签名是 DER 编码,长度差一两个字节);随机数、密钥、密文每次都不同。

feature 各打开什么

rustls/Cargo.toml 的 [features],默认是 aws_lc_rs、logging、prefer-post-quantum、std、tls12:

feature打开什么关掉或打开时的变化
aws_lc_rs(默认,别名 aws-lc-rs)依赖 aws-lc-rs(带 aws-lc-sys 和 prebuilt-nasm),并打开 webpki/aws-lc-rs编进 crypto::aws_lc_rs provider;aws-lc-sys 要编 C 代码
ring依赖 ring,并打开 webpki/ring编进 crypto::ring provider
prefer-post-quantum(默认)= ["aws_lc_rs"]只改 aws_lc_rs::DEFAULT_KX_GROUPS 的顺序,把 X25519MLKEM768 排第一;对 ring provider 没有作用,却会把 aws-lc-rs 拉进依赖
fipsaws_lc_rs 加 aws-lc-rs?/fips 和 webpki/aws-lc-rs-fips配置构建器里 require_ems 的默认值取 provider.fips(),FIPS provider 下要求扩展主密钥
custom-provider空 featurefrom_crate_features 不再按 feature 选内置 provider,必须自己 install_default
std(默认)打开 webpki/std、pki-types/std、once_cell/std关掉后没有 Connection、Stream、KeyLogFile,只剩 unbuffered 接口;crate 本身是 #![no_std] 加 alloc
tls12(默认)空 feature,按 cfg 编进 tls12 模块、version::TLS12 和 TLS 1.2 的套件文档提醒:feature 是叠加的,依赖图里别人可能又把它打开,要禁 TLS 1.2 应该在配置里只选 TLS 1.3
logging(默认)依赖 log关掉后 trace! / debug! 等变成空宏(lib.rs 里的 mod log)
brotli / zlib依赖 brotli 或 zlib-rsRFC 8879 证书压缩
read_buf构建依赖 rustversion,加 stdbuild.rs 只在 nightly 上设 cfg(read_buf),稳定版上什么都不做

hashbrown 不在 [features] 里,但它是可选依赖,自动生成同名 feature;lib.rs 在没有 std 时用它提供 HashMap。

feature 之间互相牵连的地方:

  • 两个 provider 同时在,就没有进程级默认值。 from_crate_features 只在「恰好开了一个、且没开 custom-provider」时返回 provider。本卷的实验 crate 两个都开,直接调 ClientConfig::builder()(release):
get_default before: false

thread 'main' (7435046) panicked at ~/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rustls-0.23.46/src/crypto/mod.rs:249:14:

Could not automatically determine the process-level CryptoProvider from Rustls crate features.
Call CryptoProvider::install_default() before this point to select a provider manually, or make sure exactly one of the 'aws-lc-rs' and 'ring' features is enabled.
See the documentation of the CryptoProvider type for more information.
  • 「我只要 ring」要先关默认 feature。 prefer-post-quantum 在默认 feature 里且隐含 aws_lc_rs,所以 rustls = { features = ["ring"] } 编进来的是两个 provider,结果就是上面这条 panic。crates.io 上 sqlx-core 0.8.6 和 lettre 0.11.23 依赖 rustls 时都写了 default-features = false,provider 由它们自己的 feature 再打开;本站选的是哪个,见 08 在本站中。
  • fips 和 prefer-post-quantum 都会悄悄打开 aws_lc_rs。 在一个 crate 里开 fips,等于也开了 aws-lc-rs。

provider 怎么选、怎么装成默认值,见 CryptoProvider。

manual 模块当资料读

rustls/src/manual/ 是编进 rustdoc 的设计说明,六节:implvulns.rs(别家实现的漏洞和 rustls 怎么避开)、tlsvulns.rs(协议层面的漏洞:CBC、BEAST、CRIME、Logjam、DROWN、Poodle、重协商、3SHAKE、KCI 等)、howto.rs(UnexpectedEof 的含义、怎么开日志、怎么用 SSLKEYLOGFILE 抓包)、features.rs(支持和不支持的协议特性)、defaults.rs(默认值的理由)、fips.rs。仓库根目录还有 README.md、ROADMAP.md、BENCHMARKING.md、examples/。

本卷把它们当资料读,引用前在源码或实验里核实。它们和 tag 上的代码有几处对不上:

  • implvulns.rs 用一串 TLS 1.2 客户端状态说明状态机,写的是 tls12::ExpectServerKX、tls12::ExpectCCS;代码里是 ExpectServerKx、ExpectCcs,而且 ExpectCertificate 之后先进的是 ExpectCertificateStatusOrServerKx,清单里没有这一步。
  • README 和 lib.rs 说定制 provider「using the with_crypto_provider method on the respective config builder types」,0.23.46 里没有这个方法,入口是 ClientConfig::builder_with_provider / ServerConfig::builder_with_provider。
  • lib.rs 的文档里 [write_tls()]: Connection::read_tls,write_tls 的链接指向了 read_tls。
  • features.rs 里「跳过所有认证」的示例链接指向 v/0.23.23 的 tlsclient-mio.rs,不是当前版本。

不讲什么

  • bogo(BoringSSL 的测试套件适配层)、openssl-tests、fuzz、ci-bench 的细节:源码地图 里各一句。
  • FIPS 认证本身:只讲 fips feature 改了什么,见 套件与密钥交换。
  • rustls-post-quantum crate(0.2.2,262 行):0.23.x 里 X25519MLKEM768 已经在 aws-lc-rs provider 里,本卷不再讲它。
  • ring 和 aws-lc-rs 的内部实现。

阅读路线

前置知识

  • TLS 1.3 的握手:ClientHello 带着密钥共享(key share),服务端回 ServerHello 后,其余握手消息都已加密;RFC 8446 是唯一的权威,本卷引用节号。
  • 记录层(record layer):每条记录 5 字节头(类型、版本、长度)加最多 2^14 字节的内容,TLS 1.3 里加密记录的外层类型一律写成应用数据。
  • 密钥日程(key schedule):HKDF 从共享秘密一步步派生出握手密钥和应用密钥。
  • Rust:trait 对象、Box<Self> 接收者、std::io::{Read, Write}。读过 hyper 卷的 IO trait 会帮助理解「库不自己做 IO」的设计。

小结

  • 三个 tag:rustls v/0.23.46(f04804fa)、webpki v/0.103.16、pki-types v/1.15.1,都和 crates.io 包一致;0.23.x 在 rel-0.23 分支上,不是 main 的祖先。
  • 握手主线:ClientConnection::new → ExpectClientHello → 服务端一次发出 ServerHello 和加密飞行 → 客户端五个 Expect* → Finished → 票据。
  • 记录主线:writer → 分片 → encrypt_outgoing → write_tls → read_tls → 去帧 → decrypt_incoming → reader。
  • 校验主线:WebPkiServerVerifier → webpki 路径构建(有预算)→ 名字匹配 → CertificateVerify 签名 → 可选的 CRL。
  • 默认 feature 含 prefer-post-quantum,它隐含 aws_lc_rs;只想用 ring 必须关默认 feature,否则两个 provider 都在,进程级默认值选不出来,panic 在 crypto/mod.rs:249。
  • manual/ 和 README 是资料:TLS 1.2 状态名、with_crypto_provider、write_tls 的文档链接和代码对不上。

💬 关联讨论

发起讨论
加载中...