Replace `rustls-native-certs` with `rustls-platform-verifier` (#24656)

Antonio Scandurra , Bennet , and Mikayla Maki created

closes https://github.com/zed-industries/zed/issues/19620.

I am not 100% sure on how to test this though. @elithrar: would you mind
giving this branch a shot and seeing if it works for you? I kicked off
bundling for this pull request and you should be able to download a DMG
from the CI artifacts as soon as it's done building.

Release Notes:

- Fixed a bug that caused OS-level CA certificate bundles to not be
respected.

---------

Co-authored-by: Bennet <bennet@zed.dev>
Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com>

Change summary

Cargo.lock                                  | 46 ++++++++++++++++++++--
Cargo.toml                                  |  2 
crates/client/Cargo.toml                    |  2 -
crates/client/src/client.rs                 | 17 --------
crates/http_client/Cargo.toml               |  2 +
crates/http_client/src/http_client.rs       | 21 ++++++++++
crates/reqwest_client/src/reqwest_client.rs |  5 ++
7 files changed, 69 insertions(+), 26 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -2625,8 +2625,6 @@ dependencies = [
  "rand 0.8.5",
  "release_channel",
  "rpc",
- "rustls 0.23.22",
- "rustls-native-certs 0.8.1",
  "schemars",
  "serde",
  "serde_json",
@@ -6014,6 +6012,8 @@ dependencies = [
  "futures 0.3.31",
  "http 1.2.0",
  "log",
+ "rustls 0.23.22",
+ "rustls-platform-verifier",
  "serde",
  "serde_json",
  "url",
@@ -10502,16 +10502,16 @@ dependencies = [
 
 [[package]]
 name = "quinn-udp"
-version = "0.5.8"
+version = "0.5.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "52cd4b1eff68bf27940dd39811292c49e007f4d0b4c357358dc9b0197be6b527"
+checksum = "1c40286217b4ba3a71d644d752e6a0b71f13f1b6a2c5311acfcbe0c2418ed904"
 dependencies = [
  "cfg_aliases 0.2.1",
  "libc",
  "once_cell",
  "socket2",
  "tracing",
- "windows-sys 0.59.0",
+ "windows-sys 0.52.0",
 ]
 
 [[package]]
@@ -11492,6 +11492,33 @@ dependencies = [
  "web-time",
 ]
 
+[[package]]
+name = "rustls-platform-verifier"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e012c45844a1790332c9386ed4ca3a06def221092eda277e6f079728f8ea99da"
+dependencies = [
+ "core-foundation 0.10.0",
+ "core-foundation-sys",
+ "jni",
+ "log",
+ "once_cell",
+ "rustls 0.23.22",
+ "rustls-native-certs 0.8.1",
+ "rustls-platform-verifier-android",
+ "rustls-webpki 0.102.8",
+ "security-framework 3.0.1",
+ "security-framework-sys",
+ "webpki-root-certs",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "rustls-platform-verifier-android"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
+
 [[package]]
 name = "rustls-webpki"
 version = "0.101.7"
@@ -15398,6 +15425,15 @@ dependencies = [
  "wasm-bindgen",
 ]
 
+[[package]]
+name = "webpki-root-certs"
+version = "0.26.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09aed61f5e8d2c18344b3faa33a4c837855fe56642757754775548fee21386c4"
+dependencies = [
+ "rustls-pki-types",
+]
+
 [[package]]
 name = "webpki-roots"
 version = "0.26.7"

Cargo.toml 🔗

@@ -482,7 +482,7 @@ rustc-demangle = "0.1.23"
 rust-embed = { version = "8.4", features = ["include-exclude"] }
 rustc-hash = "2.1.0"
 rustls = { version = "0.23.22" }
-rustls-native-certs = "0.8.0"
+rustls-platform-verifier = "0.5.0"
 schemars = { version = "0.8", features = ["impl_json_schema", "indexmap2"] }
 semver = "1.0"
 serde = { version = "1.0", features = ["derive", "rc"] }

crates/client/Cargo.toml 🔗

@@ -33,8 +33,6 @@ postage.workspace = true
 rand.workspace = true
 release_channel.workspace = true
 rpc = { workspace = true, features = ["gpui"] }
-rustls-native-certs.workspace = true
-rustls.workspace = true
 schemars.workspace = true
 serde.workspace = true
 serde_json.workspace = true

crates/client/src/client.rs 🔗

@@ -146,8 +146,6 @@ pub fn init_settings(cx: &mut App) {
 }
 
 pub fn init(client: &Arc<Client>, cx: &mut App) {
-    let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();
-
     let client = Arc::downgrade(client);
     cx.on_action({
         let client = client.clone();
@@ -1126,24 +1124,11 @@ impl Client {
 
             match url_scheme {
                 Https => {
-                    let client_config = {
-                        let mut root_store = rustls::RootCertStore::empty();
-
-                        let root_certs = rustls_native_certs::load_native_certs();
-                        for error in root_certs.errors {
-                            log::warn!("error loading native certs: {:?}", error);
-                        }
-                        root_store.add_parsable_certificates(root_certs.certs);
-                        rustls::ClientConfig::builder()
-                            .with_root_certificates(root_store)
-                            .with_no_client_auth()
-                    };
-
                     let (stream, _) =
                         async_tungstenite::async_tls::client_async_tls_with_connector(
                             request,
                             stream,
-                            Some(client_config.into()),
+                            Some(http_client::tls_config().into()),
                         )
                         .await?;
                     Ok(Connection::new(

crates/http_client/Cargo.toml 🔗

@@ -25,3 +25,5 @@ log.workspace = true
 serde.workspace = true
 serde_json.workspace = true
 url.workspace = true
+rustls.workspace = true
+rustls-platform-verifier.workspace = true

crates/http_client/src/http_client.rs 🔗

@@ -8,14 +8,33 @@ pub use http::{self, Method, Request, Response, StatusCode, Uri};
 
 use futures::future::BoxFuture;
 use http::request::Builder;
+use rustls::ClientConfig;
+use rustls_platform_verifier::ConfigVerifierExt;
 #[cfg(feature = "test-support")]
 use std::fmt;
 use std::{
     any::type_name,
-    sync::{Arc, Mutex},
+    sync::{Arc, Mutex, OnceLock},
 };
 pub use url::Url;
 
+static TLS_CONFIG: OnceLock<rustls::ClientConfig> = OnceLock::new();
+
+pub fn tls_config() -> ClientConfig {
+    TLS_CONFIG
+        .get_or_init(|| {
+            // rustls uses the `aws_lc_rs` provider by default
+            // This only errors if the default provider has already
+            // been installed. We can ignore this `Result`.
+            rustls::crypto::aws_lc_rs::default_provider()
+                .install_default()
+                .ok();
+
+            ClientConfig::with_platform_verifier()
+        })
+        .clone()
+}
+
 #[derive(Default, Debug, Clone, PartialEq, Eq, Hash)]
 pub enum RedirectPolicy {
     #[default]

crates/reqwest_client/src/reqwest_client.rs 🔗

@@ -51,7 +51,10 @@ impl ReqwestClient {
         }) {
             client = client.proxy(proxy);
         }
-        let client = client.build()?;
+
+        let client = client
+            .use_preconfigured_tls(http_client::tls_config())
+            .build()?;
         let mut client: ReqwestClient = client.into();
         client.proxy = proxy;
         Ok(client)