Rust in Everything
基于 rustls 0.23.46

只开 ring,却编进了 aws-lc-rs

主线位置:握手主线开始之前,编译期。哪个 provider 进二进制由 rustls 的 crate feature 决定;两个都进来时,不带 provider 的入口会 panic(上一页)。feature 表见怎么读这一卷,两个 provider 的差别见 CryptoProvider。

flowchart TB
  a["features ring 未关默认"] --> d["rustls default"]
  b["ring 加 prefer-post-quantum"] --> pq["prefer-post-quantum"]
  c["依赖用默认 feature"] --> aws["aws_lc_rs"]
  d --> pq
  d --> aws
  pq --> aws
  aws --> sys["aws-lc-sys 要 C 编译器"]
  aws --> two["两个 provider"]
  two --> panic["builder() panic"]
  two --> size["二进制变大"]

实验 crate:exp-rustls/pita-feat(同上一页,out-tree-short.txt、out-size.txt)。M2 Max、macOS 27、rustc 1.99.0,release。

现象

想用 ring 的人常写成下面三种之一,每一种都把 aws-lc-rs 带了进来:

# 1. 忘了关默认 feature
rustls = { version = "0.23", features = ["ring"] }
# 2. 关了默认 feature,又想要后量子
rustls = { version = "0.23", default-features = false, features = ["ring", "std", "tls12", "prefer-post-quantum"] }
# 3. 自己写对了,但另一个依赖用默认 feature
tokio-rustls = "0.26"

实验里分别用 rustls-defaults = ["rustls/default"]、pq = ["rustls/prefer-post-quantum"]、dep-defaults 三个 feature 模拟这三行,看 rustls 最后开了什么,以及 cargo tree -i aws-lc-rs 能不能找到这个包:

### none
rustls v0.23.46 [log,logging,ring,std,tls12]
tree -i aws-lc-rs exit=101
### dep-defaults
rustls v0.23.46 [aws-lc-rs,aws_lc_rs,log,logging,ring,std,tls12]
tree -i aws-lc-rs exit=0
### rustls-defaults
rustls v0.23.46 [aws-lc-rs,aws_lc_rs,default,log,logging,prefer-post-quantum,ring,std,tls12]
tree -i aws-lc-rs exit=0
### pq
rustls v0.23.46 [aws-lc-rs,aws_lc_rs,log,logging,prefer-post-quantum,ring,std,tls12]
tree -i aws-lc-rs exit=0
### dep-ring
rustls v0.23.46 [log,logging,ring,std,tls12]
tree -i aws-lc-rs exit=101

三种写法都能编过,不报错也不警告。代价分三处出现:

  • 运行时:ClientConfig::builder() panic,三种写法的输出一字不差,见上一页。
  • 构建:多编一个 aws-lc-sys,它要 C 编译器(没有 C 编译器),构建脚本比 ring 慢得多(设计取舍实测 12.8 秒对 2.9 秒)。
  • 体积:即使在 main 里装了 ring、aws-lc-rs 一次都没被调用,它也留在二进制里。同一个程序,install 和 pq,install 两种 feature 各编一次,strip 之后:
994600 bins/pita-feat-install.stripped
1690208 bins/pita-feat-pq,install.stripped
install: symbols matching aws_lc: 0
pq,install: symbols matching aws_lc: 178

多出 695608 字节(约 0.66 MB),未 strip 的版本里有 178 个名字带 aws_lc 的符号。

原因

rustls 0.23.46 的 Cargo.toml 里这几行决定了一切:

[features]
default = ["aws_lc_rs", "logging", "prefer-post-quantum", "std", "tls12"]
# ...
aws_lc_rs = ["dep:aws-lc-rs", "webpki/aws-lc-rs", "aws-lc-rs/aws-lc-sys", "aws-lc-rs/prebuilt-nasm"]
# ...
prefer-post-quantum = ["aws_lc_rs"]
# ...
ring = ["dep:ring", "webpki/ring"]
  • 写法 1:features = ["ring"] 是在默认 feature 之上追加 ring,默认的 aws_lc_rs 还在。
  • 写法 2:prefer-post-quantum 不是独立的开关,它直接依赖 aws_lc_rs。lib.rs 的 feature 说明只写了它「merely alters the order of rustls::crypto::aws_lc_rs::DEFAULT_KX_GROUPS」,没提它会打开 aws-lc-rs。
  • 写法 3:tokio-rustls 0.26 的默认 feature 含 aws_lc_rs,转发成 rustls/aws_lc_rs。Cargo 对同一个包的 feature 取并集,依赖图里只有一份 rustls,谁开的都算。

aws_lc_rs 还会经 webpki/aws-lc-rs 让 rustls-webpki 也编进 aws-lc-rs 的签名校验算法。ring 那一侧对称地是 webpki/ring。

📝NOTE
没有蠢问题

问: 我只用 ring,打开 prefer-post-quantum 至少能让 ring provider 也优先后量子吧?

答: 不能。这个 feature 只调整 aws-lc-rs provider 的 DEFAULT_KX_GROUPS 顺序,ring provider 没有 ML-KEM。实验里 pq,install(开了 prefer-post-quantum,main 里装 ring)的输出:

install_default(ring) -> Ok(())
config ok: secure_random Ring, kx_groups [X25519, secp256r1, secp384r1]

组列表和没开时完全一样,ClientHello 也就不会出现 X25519MLKEM768。想要混合组只能用 aws-lc-rs provider,见套件与密钥交换。

修法

对自己写的每一行依赖 rustls 的声明,都关掉默认 feature,再逐个列出要的:

rustls = { version = "0.23", default-features = false, features = ["ring", "std", "tls12", "logging"] }
tokio-rustls = { version = "0.26", default-features = false, features = ["ring", "logging", "tls12"] }

上面输出里的 dep-ring 就是这样写的:rustls 只剩 [log,logging,ring,std,tls12],cargo tree -i aws-lc-rs 找不到这个包:

error: package ID specification `aws-lc-rs` did not match any packages

default-features = false 之后 std 和 tls12 也没了,要自己加回来;不加 std 就没有 builder()、Stream、DefaultTimeProvider 这些 #[cfg(feature = "std")] 的东西。

如果 aws-lc-rs 来自一个你改不了的依赖,又没有办法关掉它的默认 feature,就只能接受它在二进制里,按上一页的修法二或修法三避开 panic。反过来,真想要后量子混合组,就干脆选 aws-lc-rs、不开 ring。

怎么检查

cargo tree -i aws-lc-rs 的退出码就是一道门:包不存在时 cargo 报错退出 101,存在时退出 0。

if cargo tree -i aws-lc-rs --depth 0 >/dev/null 2>&1; then
  echo "aws-lc-rs is in the dependency graph"; exit 1
fi

这道门两个方向都验过:none 和 dep-ring 的退出码是 101(门放行),dep-defaults、rustls-defaults、pq 都是 0(门拦下)。拦下之后用 cargo tree -e features -i aws-lc-rs 找来源:

aws-lc-rs v1.19.0
├── rustls v0.23.46
│   ├── rustls feature "aws-lc-rs"
│   │   └── rustls feature "aws_lc_rs"
│   │       ├── rustls feature "default"
│   │       │   └── pita-feat feature "rustls-defaults" (command-line)
│   │       └── rustls feature "prefer-post-quantum"
│   │           └── rustls feature "default" (*)
...

cargo tree 默认只按当前平台过滤依赖。只在别的 target(比如部署用的 Linux)下才出现的 aws-lc-rs,要加 --target all 才看得到。

小结

  • rustls 0.23.46 的默认 feature 含 aws_lc_rs 和 prefer-post-quantum,而 prefer-post-quantum = ["aws_lc_rs"]。
  • 「features = ["ring"] 不关默认」「ring 加 prefer-post-quantum」「某个依赖用默认 feature」三种写法都会把 aws-lc-rs 编进来,编译不报错。
  • 后果:builder() panic、多一个要 C 编译器的构建脚本、即使不用也多 0.66 MB。
  • prefer-post-quantum 对 ring provider 没有任何作用。
  • 每一行 rustls / tokio-rustls 都写 default-features = false;用 cargo tree -i aws-lc-rs 的退出码守住。

💬 关联讨论

发起讨论
加载中...