From a01097708ef4e781b7b49c2a8590811c917567b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Laignel?= Date: Sun, 26 Jul 2026 20:07:04 +0200 Subject: [PATCH 1/2] qbzd: install the rustls process-level `CryptoProvider` The rustls crypto provider was not installed for `qbzd`, which caused the process to panic: > thread 'tokio-rt-worker' (15737) panicked at [...]/rustls-0.23.37/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. The function `ensure_crypto_provider()` was defined in the `qbz-cast` crate which is obviously not a dependency of `qbzd`. This commit moves `ensure_crypto_provider()` to `qbz-app`, calls it from `qbzd` and updates `qbz` accordingly. --- crates/Cargo.lock | 2 +- crates/qbz-app/Cargo.toml | 3 +++ crates/qbz-app/src/lib.rs | 17 +++++++++++++++++ crates/qbz-cast/Cargo.toml | 6 ------ crates/qbz-cast/src/chromecast/thread.rs | 3 --- crates/qbz-cast/src/dlna/device.rs | 3 --- crates/qbz-cast/src/lib.rs | 22 ---------------------- crates/qbz/src/main.rs | 13 ++----------- crates/qbzd/src/main.rs | 3 +++ 9 files changed, 26 insertions(+), 46 deletions(-) diff --git a/crates/Cargo.lock b/crates/Cargo.lock index 11f471f64..b71992866 100644 --- a/crates/Cargo.lock +++ b/crates/Cargo.lock @@ -5943,6 +5943,7 @@ dependencies = [ "rand 0.10.1", "reqwest", "rusqlite", + "rustls", "serde", "serde_json", "tokio", @@ -5998,7 +5999,6 @@ dependencies = [ "mdns-sd", "rupnp", "rust_cast", - "rustls", "serde", "serde_json", "thiserror 2.0.18", diff --git a/crates/qbz-app/Cargo.toml b/crates/qbz-app/Cargo.toml index fe80866c1..2e5ff6e1b 100644 --- a/crates/qbz-app/Cargo.toml +++ b/crates/qbz-app/Cargo.toml @@ -24,3 +24,6 @@ chrono = { workspace = true } md-5 = { workspace = true } base64 = { workspace = true } rand = "0.10" + +# TLS provider selection +rustls = { version = "0.23", features = ["aws_lc_rs"] } diff --git a/crates/qbz-app/src/lib.rs b/crates/qbz-app/src/lib.rs index 9e375788d..56e5084ed 100644 --- a/crates/qbz-app/src/lib.rs +++ b/crates/qbz-app/src/lib.rs @@ -10,3 +10,20 @@ pub mod session_store; pub mod shell; pub mod settings; pub mod user_data; + +/// Install the rustls process-level `CryptoProvider` (aws-lc-rs) exactly once. +/// +/// Idempotent (a second call is a no-op); harmless if some other component +/// already installed a default (`install_default` then returns Err, ignored). +pub fn ensure_crypto_provider() { + use std::sync::Once; + static INIT: Once = Once::new(); + INIT.call_once(|| { + if rustls::crypto::aws_lc_rs::default_provider() + .install_default() + .is_err() + { + log::debug!("[app] rustls CryptoProvider already installed"); + } + }); +} diff --git a/crates/qbz-cast/Cargo.toml b/crates/qbz-cast/Cargo.toml index 0c1331e43..d772065a0 100644 --- a/crates/qbz-cast/Cargo.toml +++ b/crates/qbz-cast/Cargo.toml @@ -8,12 +8,6 @@ description = "Casting support for QBZ - Chromecast, DLNA" # Chromecast rust_cast = "0.21" -# TLS provider selection. rust_cast uses rustls; in the multi-crate binary both -# the aws-lc-rs and ring providers end up enabled (reqwest pulls ring), so -# rustls can't auto-pick one and panics on connect. We install aws-lc-rs -# explicitly before connecting (see `ensure_crypto_provider`). -rustls = { version = "0.23", features = ["aws_lc_rs"] } - # mDNS discovery mdns-sd = "0.11" diff --git a/crates/qbz-cast/src/chromecast/thread.rs b/crates/qbz-cast/src/chromecast/thread.rs index ecfc30fde..00f2477d9 100644 --- a/crates/qbz-cast/src/chromecast/thread.rs +++ b/crates/qbz-cast/src/chromecast/thread.rs @@ -75,9 +75,6 @@ impl ChromecastHandle { /// Connect to a Chromecast device pub fn connect(&self, ip: String, port: u16) -> Result<(), CastError> { - // rust_cast opens a rustls TLS channel; make sure a CryptoProvider is - // installed first (the worker thread would otherwise panic). - crate::ensure_crypto_provider(); let (reply_tx, reply_rx) = mpsc::channel(); self.sender .send(CastCommand::Connect { diff --git a/crates/qbz-cast/src/dlna/device.rs b/crates/qbz-cast/src/dlna/device.rs index 6c363f21f..9d90a48f4 100644 --- a/crates/qbz-cast/src/dlna/device.rs +++ b/crates/qbz-cast/src/dlna/device.rs @@ -60,9 +60,6 @@ pub struct DlnaConnection { impl DlnaConnection { /// Connect to a DLNA device and discover service URLs pub async fn connect(device: DiscoveredDlnaDevice) -> Result { - // Defensive: the device-description fetch may go over TLS in some - // setups; ensure a rustls CryptoProvider is installed (idempotent). - crate::ensure_crypto_provider(); let device_url: Uri = device .url .parse() diff --git a/crates/qbz-cast/src/lib.rs b/crates/qbz-cast/src/lib.rs index d81106c5c..f13c8b52b 100644 --- a/crates/qbz-cast/src/lib.rs +++ b/crates/qbz-cast/src/lib.rs @@ -38,25 +38,3 @@ pub use dlna::{ /// Cast device type alias for backwards compatibility pub type CastDevice = CastDeviceConnection; -/// Install the rustls process-level `CryptoProvider` (aws-lc-rs) exactly once. -/// -/// `rust_cast` opens a TLS control channel to the Chromecast via rustls. In the -/// full QBZ binary BOTH the `aws-lc-rs` and `ring` providers are enabled (reqwest -/// pulls `ring`, rust_cast pulls `aws-lc-rs`), so rustls can't auto-select one -/// and panics with "Could not automatically determine the process-level -/// CryptoProvider". Installing one explicitly before any TLS use fixes it. -/// -/// Idempotent (a second call is a no-op); harmless if some other component -/// already installed a default (`install_default` then returns Err, ignored). -pub fn ensure_crypto_provider() { - use std::sync::Once; - static INIT: Once = Once::new(); - INIT.call_once(|| { - if rustls::crypto::aws_lc_rs::default_provider() - .install_default() - .is_err() - { - log::debug!("[cast] rustls CryptoProvider already installed"); - } - }); -} diff --git a/crates/qbz/src/main.rs b/crates/qbz/src/main.rs index 8feab7e6a..747ce77f7 100644 --- a/crates/qbz/src/main.rs +++ b/crates/qbz/src/main.rs @@ -8120,17 +8120,8 @@ fn main() -> Result<(), Box> { // Large/XL (and shrink RAM at Small). Set before any artwork job runs. crate::artwork::set_ui_scale_factor(ui_scale_factor); - // Install the rustls process-level CryptoProvider ONCE, before ANY TLS use. - // The full binary compiles BOTH rustls providers (aws-lc-rs via qbz-cast's - // rust_cast, ring via reqwest), so rustls cannot auto-select one and panics - // ("Could not automatically determine the process-level CryptoProvider") the - // first time a TLS connection opens. Previously this was only installed lazily - // on the cast paths, so QConnect's tokio-tungstenite WebSocket (it opens a - // rustls TLS channel on its own worker thread) panicked when the user joined a - // session WITHOUT ever casting. Installing here at startup covers every TLS - // consumer — QConnect, cast, reqwest — regardless of which connects first. - // Idempotent (the cast paths still call it; the second call is a no-op). - qbz_cast::ensure_crypto_provider(); + // Install the rustls process-level `CryptoProvider` + qbz_app::ensure_crypto_provider(); let tokio_rt = tokio::runtime::Runtime::new()?; let _enter = tokio_rt.enter(); diff --git a/crates/qbzd/src/main.rs b/crates/qbzd/src/main.rs index a4285f2c3..9f7ba4013 100644 --- a/crates/qbzd/src/main.rs +++ b/crates/qbzd/src/main.rs @@ -256,6 +256,9 @@ enum ConfigCmd { Path, Show { #[arg(long)] json: bool } } #[tokio::main] async fn main() { + // Install the rustls process-level `CryptoProvider` + qbz_app::ensure_crypto_provider(); + let cli = Cli::parse(); let code = match cli.cmd { Cmd::Version { json } => { From 62cc4f9200b5ac1e79c146a80f79827790477de4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Laignel?= Date: Sun, 26 Jul 2026 21:06:01 +0200 Subject: [PATCH 2/2] remove ring from the dependency tree The crate `ring` was transitively pulled due to the feature `rustls-tls` being selected for `reqwest`. In order to ensure `ring` is not pulled, `rustls-tls-webpki-roots-no-provider` must be used instead. --- crates/Cargo.lock | 68 ------------------------------ crates/Cargo.toml | 2 +- crates/qbz-integrations/Cargo.toml | 2 +- 3 files changed, 2 insertions(+), 70 deletions(-) diff --git a/crates/Cargo.lock b/crates/Cargo.lock index b71992866..64aa5fa61 100644 --- a/crates/Cargo.lock +++ b/crates/Cargo.lock @@ -2615,10 +2615,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", - "js-sys", "libc", "wasi", - "wasm-bindgen", ] [[package]] @@ -2628,11 +2626,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", - "js-sys", "libc", "r-efi 5.3.0", "wasip2", - "wasm-bindgen", ] [[package]] @@ -4212,12 +4208,6 @@ dependencies = [ "hashbrown 0.17.1", ] -[[package]] -name = "lru-slab" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" - [[package]] name = "lyon_algorithms" version = "1.0.20" @@ -6526,61 +6516,6 @@ dependencies = [ "serde", ] -[[package]] -name = "quinn" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" -dependencies = [ - "bytes", - "cfg_aliases", - "pin-project-lite", - "quinn-proto", - "quinn-udp", - "rustc-hash 2.1.1", - "rustls", - "socket2 0.6.3", - "thiserror 2.0.18", - "tokio", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-proto" -version = "0.11.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" -dependencies = [ - "bytes", - "getrandom 0.3.4", - "lru-slab", - "rand 0.9.3", - "ring", - "rustc-hash 2.1.1", - "rustls", - "rustls-pki-types", - "slab", - "thiserror 2.0.18", - "tinyvec", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-udp" -version = "0.5.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" -dependencies = [ - "cfg_aliases", - "libc", - "once_cell", - "socket2 0.6.3", - "tracing", - "windows-sys 0.60.2", -] - [[package]] name = "quote" version = "1.0.45" @@ -7001,7 +6936,6 @@ dependencies = [ "log", "percent-encoding", "pin-project-lite", - "quinn", "rustls", "rustls-pki-types", "serde", @@ -7280,7 +7214,6 @@ dependencies = [ "aws-lc-rs", "log", "once_cell", - "ring", "rustls-pki-types", "rustls-webpki", "subtle", @@ -7305,7 +7238,6 @@ version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" dependencies = [ - "web-time", "zeroize", ] diff --git a/crates/Cargo.toml b/crates/Cargo.toml index 90de0dfc6..4b7fec01a 100644 --- a/crates/Cargo.toml +++ b/crates/Cargo.toml @@ -59,7 +59,7 @@ serde = { version = "1", features = ["derive"] } serde_json = "1" # HTTP -reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls", "cookies", "stream"] } +reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls-webpki-roots-no-provider", "cookies", "stream"] } # Logging log = "0.4" diff --git a/crates/qbz-integrations/Cargo.toml b/crates/qbz-integrations/Cargo.toml index aeef7ea4f..ae26fb93a 100644 --- a/crates/qbz-integrations/Cargo.toml +++ b/crates/qbz-integrations/Cargo.toml @@ -10,7 +10,7 @@ license = "MIT" tokio = { version = "1", features = ["sync", "time"] } # HTTP client -reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] } +reqwest = { workspace = true, default-features = false, features = ["json", "rustls-tls-webpki-roots-no-provider"] } # Serialization serde = { version = "1", features = ["derive"] }