Skip to main content

max / makenotwork

Move the server off aws-lc-rs onto ring The last crate in the tree still naming a C crypto backend. 257e768e moved viewer, wam, mnw-cli, bento, multithreaded, ops-exec and s3-storage; the server was out of its scope. The three named sites (rustls features, async-stripe's provider feature, the install call in crypto.rs) are not sufficient on their own. reqwest 0.13's `default` pulls `default-tls` = `rustls`, which is a hard alias for `__rustls-aws-lc-rs`, and cargo unifies features across the graph, so that one line put aws-lc-rs in the binary whatever rustls's own features said. Declaring reqwest `default-features = false` with `rustls-no-provider` is what actually removes it: aws-lc-rs and aws-lc-sys are gone from the lock. That has a cost worth knowing about. Without a provider feature, reqwest's `Client::builder().build()` panics rather than choosing, so every client constructor installs the provider first, the way StripeClient::new already did. Five lib tests and sixteen health tests found this; a compile did not. 3422 tests pass, including the 47 stripe_webhooks and the 10 passkey workflow tests. clippy and fmt clean.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-08-08 17:51 UTC
Signed with PGP, not checked
Commit: 3932f894cb82d248d67d1cc5d50ba79b686a5d3f
Parent: 257e768
13 files changed, +51 insertions, -55 deletions
@@ -781,29 +781,6 @@
781 781 "zeroize",
782 782 ]
783 783
784 - [[package]]
785 - name = "aws-lc-rs"
786 - version = "1.17.1"
787 - source = "registry+https://github.com/rust-lang/crates.io-index"
788 - checksum = "4342d8937fc7e5dd9b1c60292261c0670c882a2cd1719cfc11b1af41731e32ad"
789 - dependencies = [
790 - "aws-lc-sys",
791 - "zeroize",
792 - ]
793 -
794 - [[package]]
795 - name = "aws-lc-sys"
796 - version = "0.42.0"
797 - source = "registry+https://github.com/rust-lang/crates.io-index"
798 - checksum = "6d9ceb1da931507a12f4fccea479dccd00da1943e1b4ae72d8e502d707361444"
799 - dependencies = [
800 - "cc",
801 - "cmake",
802 - "dunce",
803 - "fs_extra",
804 - "pkg-config",
805 - ]
806 -
807 784 [[package]]
808 785 name = "aws-runtime"
809 786 version = "1.7.5"
@@ -1705,15 +1682,6 @@
1705 1682 "hashbrown 0.16.1",
1706 1683 ]
1707 1684
1708 - [[package]]
1709 - name = "cmake"
1710 - version = "0.1.58"
1711 - source = "registry+https://github.com/rust-lang/crates.io-index"
1712 - checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678"
1713 - dependencies = [
1714 - "cc",
1715 - ]
1716 -
1717 1685 [[package]]
1718 1686 name = "cmov"
1719 1687 version = "0.5.4"
@@ -3062,12 +3030,6 @@
3062 3030 "winapi",
3063 3031 ]
3064 3032
3065 - [[package]]
3066 - name = "fs_extra"
3067 - version = "1.3.0"
3068 - source = "registry+https://github.com/rust-lang/crates.io-index"
3069 - checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
3070 -
3071 3033 [[package]]
3072 3034 name = "funty"
3073 3035 version = "2.0.0"
@@ -6601,7 +6563,6 @@
6601 6563 source = "registry+https://github.com/rust-lang/crates.io-index"
6602 6564 checksum = "4fcb935c5bec503c2f0e306bdd3e58bb9029dcb14fa8d9ac76e3a5256ac0763e"
6603 6565 dependencies = [
6604 - "aws-lc-rs",
6605 6566 "bytes",
6606 6567 "getrandom 0.3.4",
6607 6568 "lru-slab",
@@ -7002,7 +6963,6 @@
7002 6963 "mime",
7003 6964 "percent-encoding",
7004 6965 "pin-project-lite",
7005 - "quinn",
7006 6966 "rustls",
7007 6967 "rustls-pki-types",
7008 6968 "rustls-platform-verifier",
@@ -7203,7 +7163,6 @@
7203 7163 source = "registry+https://github.com/rust-lang/crates.io-index"
7204 7164 checksum = "6b92b125634d9b795e7beca796cc790df15a7fb38323bf3196fda83292d06b1f"
7205 7165 dependencies = [
7206 - "aws-lc-rs",
7207 7166 "log",
7208 7167 "once_cell",
7209 7168 "ring",
@@ -7290,7 +7249,6 @@
7290 7249 source = "registry+https://github.com/rust-lang/crates.io-index"
7291 7250 checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
7292 7251 dependencies = [
7293 - "aws-lc-rs",
7294 7252 "ring",
7295 7253 "rustls-pki-types",
7296 7254 "untrusted",
@@ -86,7 +86,7 @@
86 86 # Security
87 87 # Direct only so `crypto::install_default_crypto_provider` can name a provider;
88 88 # every TLS client here reaches rustls transitively.
89 - rustls = { version = "0.23", default-features = false, features = ["aws-lc-rs"] }
89 + rustls = { version = "0.23", default-features = false, features = ["ring"] }
90 90 rand = "0.10"
91 91 hmac = "0.13.0"
92 92 sha1 = "0.11.0"
@@ -164,7 +164,7 @@
164 164 # reqwest's platform verifier elsewhere in the tree) and carries the `__hyper`
165 165 # feature the client needs; the provider must be named separately or
166 166 # async-stripe's connector.rs refuses to compile.
167 - async-stripe = { version = "1.0.0-rc.6", default-features = false, features = ["rustls-tls-native", "rustls-aws-lc-rs"] }
167 + async-stripe = { version = "1.0.0-rc.6", default-features = false, features = ["rustls-tls-native", "rustls-ring"] }
168 168 async-stripe-shared = { version = "1.0.0-rc.6", features = ["deserialize"] }
169 169 async-stripe-billing = { version = "1.0.0-rc.6", features = ["deserialize", "subscription", "billing_portal_session"] }
170 170 async-stripe-checkout = { version = "1.0.0-rc.6", features = ["deserialize", "checkout_session"] }
@@ -173,7 +173,16 @@
173 173 async-stripe-payment = { version = "1.0.0-rc.6", features = ["deserialize"] }
174 174 async-stripe-product = { version = "1.0.0-rc.6", features = ["deserialize", "product", "price"] }
175 175 async-stripe-types = { version = "1.0.0-rc.6", features = ["deserialize"] }
176 - reqwest = { version = "0.13", features = ["json", "cookies", "form", "query"] }
176 + # reqwest 0.13's `rustls` feature is an alias for `__rustls-aws-lc-rs` and there
177 + # is no ring counterpart, so the default feature set is what would drag the C
178 + # backend back in. `rustls-no-provider` keeps the platform verifier and lets
179 + # `crypto::install_default_crypto_provider` supply ring; the rest of this list is
180 + # reqwest's own default set (`charset`, `http2`, `system-proxy`) restated, since
181 + # naming a TLS feature means turning defaults off.
182 + reqwest = { version = "0.13", default-features = false, features = [
183 + "json", "cookies", "form", "query",
184 + "charset", "http2", "system-proxy", "rustls-no-provider",
185 + ] }
177 186 urlencoding = "2.1.3"
178 187
179 188 # URL parsing
@@ -9,16 +9,26 @@
9 9 /// TLS connection.
10 10 ///
11 11 /// rustls 0.23 picks a provider from its own crate features, and refuses to
12 - /// guess when more than one is compiled in. Both are: `aws-lc-rs` arrives with
13 - /// rustls itself, `ring` through apple-codesign's signing stack. So the
14 - /// automatic path panics, and it panics lazily, at the first connector build
15 - /// rather than at startup. That put the failure on the Stripe path, which is
16 - /// the worst place to discover it.
12 + /// guess when more than one is compiled in. That refusal is a lazy panic at the
13 + /// first connector build rather than an error at startup, which once put the
14 + /// failure on the Stripe path — the worst place to discover it. Naming a
15 + /// provider here makes the choice explicit and moves any failure to process
16 + /// start.
17 17 ///
18 - /// Naming a provider here makes the choice explicit and moves the failure to
19 - /// process start. Every other rustls consumer in the tree (reqwest via
20 - /// `rustls-platform-verifier`, the AWS SDK) selects its own provider directly
21 - /// and is unaffected either way.
18 + /// `ring` is the provider because the tree's standing choice is the most-Rust
19 + /// backend on offer (`rust_crypto` > `ring` > `aws-lc-rs`), and rustls does not
20 + /// offer `rust_crypto` at this layer. Keeping `aws-lc-rs` out matters beyond
21 + /// taste: it is a C backend, so it would put a C toolchain on the build path
22 + /// for every architecture built natively, and it is what stops miri from ever
23 + /// reaching a verdict here.
24 + ///
25 + /// reqwest depends on this call. It is declared `rustls-no-provider`, which is
26 + /// the only way to keep `aws-lc-rs` out of the graph — its `rustls` feature is
27 + /// a hard alias for `__rustls-aws-lc-rs`, and cargo unifies features, so one
28 + /// consumer asking for it puts the C backend in every binary here. The cost is
29 + /// that `reqwest::Client::builder().build()` panics outright when no provider
30 + /// is installed, so every client constructor in this crate calls this first
31 + /// rather than trusting boot order. The AWS SDK still selects its own.
22 32 ///
23 33 /// Idempotent: a second call is a no-op, so tests can call it per-case without
24 34 /// racing. Not `expect`-ing the result is deliberate. `install_default` returns
@@ -27,7 +37,7 @@
27 37 ///
28 38 /// [`CryptoProvider`]: rustls::crypto::CryptoProvider
29 39 pub fn install_default_crypto_provider() {
30 - let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();
40 + let _ = rustls::crypto::ring::default_provider().install_default();
31 41 }
32 42
33 43 /// Version-tagged prefix on an encrypted-at-rest TOTP secret. Its presence is
@@ -93,6 +93,10 @@
93 93 impl MtClient {
94 94 /// Create a new MT client with the given base URL and shared secret.
95 95 pub fn new(base_url: String, secret: String) -> Self {
96 + // `build()` constructs the rustls connector, which reads the
97 + // process-wide provider and panics if none is installed. Idempotent.
98 + crate::crypto::install_default_crypto_provider();
99 +
96 100 let http = reqwest::Client::builder()
97 101 .timeout(std::time::Duration::from_secs(5))
98 102 .connect_timeout(std::time::Duration::from_secs(3))
@@ -31,6 +31,10 @@
31 31 /// Build a client for `base_url`. `token`, when `Some`, is sent as a bearer
32 32 /// token on every request (matching WAM's shared-secret auth).
33 33 pub fn new(base_url: String, token: Option<String>) -> Self {
34 + // `build()` constructs the rustls connector, which reads the
35 + // process-wide provider and panics if none is installed. Idempotent.
36 + crate::crypto::install_default_crypto_provider();
37 +
34 38 let http = reqwest::Client::builder()
35 39 .timeout(std::time::Duration::from_secs(5))
36 40 .connect_timeout(std::time::Duration::from_secs(3))
@@ -17,6 +17,10 @@
17 17
18 18 impl TestClient {
19 19 fn new() -> Self {
20 + // reqwest is `rustls-no-provider`, so building a client without a
21 + // process-wide provider panics. Idempotent; see `crypto`'s own docs.
22 + makenotwork::crypto::install_default_crypto_provider();
23 +
20 24 TestClient {
21 25 client: reqwest::Client::builder()
22 26 .timeout(Duration::from_secs(10))
@@ -360,6 +360,7 @@
360 360
361 361 impl PostmarkTransport {
362 362 fn new(config: EmailConfig, pool: Option<sqlx::PgPool>) -> Self {
363 + crate::crypto::install_default_crypto_provider();
363 364 let http_client = reqwest::Client::builder()
364 365 .timeout(std::time::Duration::from_secs(10))
365 366 .build()
@@ -13,6 +13,7 @@
13 13 /// call sites may still set a tighter per-request `.timeout()`, which wins.
14 14 /// (Clients that need custom config, like Postmark and the PoM probe, keep their own.)
15 15 pub static HTTP_CLIENT: std::sync::LazyLock<reqwest::Client> = std::sync::LazyLock::new(|| {
16 + crate::crypto::install_default_crypto_provider();
16 17 reqwest::Client::builder()
17 18 .timeout(std::time::Duration::from_secs(10))
18 19 .build()
@@ -54,6 +54,7 @@
54 54 async fn query_hash(sha256: &str, auth_key: &str) -> Result<LayerResult, String> {
55 55 // Explicit request + connect timeouts (Perf-S2); a default client has neither.
56 56 static CLIENT: std::sync::LazyLock<reqwest::Client> = std::sync::LazyLock::new(|| {
57 + crate::crypto::install_default_crypto_provider();
57 58 reqwest::Client::builder()
58 59 .timeout(std::time::Duration::from_secs(
59 60 crate::constants::SCAN_MALWAREBAZAAR_TIMEOUT_SECS,
@@ -63,6 +63,7 @@
63 63 async fn query_hash(sha256: &str, api_key: &str) -> Result<LayerResult, String> {
64 64 // Explicit request + connect timeouts (Perf-S2); a default client has neither.
65 65 static CLIENT: std::sync::LazyLock<reqwest::Client> = std::sync::LazyLock::new(|| {
66 + crate::crypto::install_default_crypto_provider();
66 67 reqwest::Client::builder()
67 68 .timeout(std::time::Duration::from_secs(
68 69 crate::constants::SCAN_MALWAREBAZAAR_TIMEOUT_SECS,
@@ -124,6 +124,7 @@
124 124 // so a hung host could block past the layer's own per-host timeout. Falls back
125 125 // to a default client only if the builder somehow fails.
126 126 static CLIENT: std::sync::LazyLock<reqwest::Client> = std::sync::LazyLock::new(|| {
127 + crate::crypto::install_default_crypto_provider();
127 128 reqwest::Client::builder()
128 129 .timeout(Duration::from_secs(
129 130 constants::SCAN_MALWAREBAZAAR_TIMEOUT_SECS,