From 008bd920183c9752939ca58a4806252115de3e7e Mon Sep 17 00:00:00 2001 From: Jay Gowdy Date: Sun, 21 Jun 2026 11:50:42 -0700 Subject: [PATCH] feat(transport): allow setting a custom rustls CryptoProvider on TLS configs ClientTlsConfig and ServerTlsConfig build their rustls config from CryptoProvider::get_default(), so a non-default provider could only be used by installing it process-wide with install_default. Add a with_provider builder method to both configs to set the provider per channel/server; when set, tonic builds the config with builder_with_provider instead of get_default(). Roots, identity, ALPN, and client auth are layered on top unchanged. The field is optional and defaults to the current behavior, so it is backward compatible. --- tonic/src/transport/channel/service/tls.rs | 24 ++++++++++------ tonic/src/transport/channel/tls.rs | 32 ++++++++++++++++++++++ tonic/src/transport/server/service/tls.rs | 9 ++++-- tonic/src/transport/server/tls.rs | 19 ++++++++++++- 4 files changed, 72 insertions(+), 12 deletions(-) diff --git a/tonic/src/transport/channel/service/tls.rs b/tonic/src/transport/channel/service/tls.rs index de1dc98c9..ace6205a3 100644 --- a/tonic/src/transport/channel/service/tls.rs +++ b/tonic/src/transport/channel/service/tls.rs @@ -35,6 +35,7 @@ impl TlsConnector { trust_anchors: Vec>, identity: Option, server_cert_verifier: Option>, + provider: Option>, domain: &str, assume_http2: bool, use_key_log: bool, @@ -50,15 +51,20 @@ impl TlsConnector { .unwrap() } - #[allow(unreachable_patterns)] - let builder = match crypto::CryptoProvider::get_default() { - Some(provider) => with_provider(provider.clone()), - #[cfg(feature = "tls-ring")] - None => with_provider(Arc::new(crypto::ring::default_provider())), - #[cfg(feature = "tls-aws-lc")] - None => with_provider(Arc::new(crypto::aws_lc_rs::default_provider())), - // somehow tls is enabled, but neither of the crypto features are enabled. - _ => ClientConfig::builder(), + let builder = match provider { + Some(provider) => with_provider(provider), + None => { + #[allow(unreachable_patterns)] + match crypto::CryptoProvider::get_default() { + Some(provider) => with_provider(provider.clone()), + #[cfg(feature = "tls-ring")] + None => with_provider(Arc::new(crypto::ring::default_provider())), + #[cfg(feature = "tls-aws-lc")] + None => with_provider(Arc::new(crypto::aws_lc_rs::default_provider())), + // somehow tls is enabled, but neither of the crypto features are enabled. + _ => ClientConfig::builder(), + } + } }; let builder = match server_cert_verifier { diff --git a/tonic/src/transport/channel/tls.rs b/tonic/src/transport/channel/tls.rs index 8a7cb3664..bb6eb96fc 100644 --- a/tonic/src/transport/channel/tls.rs +++ b/tonic/src/transport/channel/tls.rs @@ -7,6 +7,7 @@ use http::Uri; use std::sync::Arc; use std::time::Duration; use tokio_rustls::rustls::client::danger::ServerCertVerifier; +use tokio_rustls::rustls::crypto::CryptoProvider; use tokio_rustls::rustls::pki_types::TrustAnchor; /// Configures TLS settings for endpoints. @@ -23,6 +24,7 @@ pub struct ClientTlsConfig { with_webpki_roots: bool, use_key_log: bool, timeout: Option, + provider: Option>, } impl ClientTlsConfig { @@ -135,6 +137,19 @@ impl ClientTlsConfig { } } + /// Sets a custom rustls [`CryptoProvider`] used to build this client's TLS + /// configuration. + /// + /// When unset, the process-wide default installed via + /// `CryptoProvider::install_default` is used, falling back to the provider + /// selected by the `tls-ring` or `tls-aws-lc` feature. + pub fn with_provider(self, provider: Arc) -> Self { + ClientTlsConfig { + provider: Some(provider), + ..self + } + } + pub(crate) fn into_tls_connector(self, uri: &Uri) -> Result { self.build_tls_connector(uri, None) } @@ -161,6 +176,7 @@ impl ClientTlsConfig { self.trust_anchors, self.identity, server_cert_verifier, + self.provider, domain, self.assume_http2, self.use_key_log, @@ -172,3 +188,19 @@ impl ClientTlsConfig { ) } } + +#[cfg(all(test, feature = "tls-ring"))] +mod tests { + use super::*; + use tokio_rustls::rustls::crypto::ring; + + #[test] + fn with_provider_is_used_to_build_the_connector() { + // A connector configured with an explicit provider builds successfully + // and does not depend on a process-wide default provider being installed. + let connector = ClientTlsConfig::new() + .with_provider(Arc::new(ring::default_provider())) + .into_tls_connector(&Uri::from_static("https://example.com")); + assert!(connector.is_ok()); + } +} diff --git a/tonic/src/transport/server/service/tls.rs b/tonic/src/transport/server/service/tls.rs index 793016cf4..32ec2aee3 100644 --- a/tonic/src/transport/server/service/tls.rs +++ b/tonic/src/transport/server/service/tls.rs @@ -4,7 +4,7 @@ use tokio::io::{AsyncRead, AsyncWrite}; use tokio::time; use tokio_rustls::{ TlsAcceptor as RustlsAcceptor, - rustls::{RootCertStore, ServerConfig, server::WebPkiClientVerifier}, + rustls::{RootCertStore, ServerConfig, crypto::CryptoProvider, server::WebPkiClientVerifier}, server::TlsStream, }; @@ -29,8 +29,13 @@ impl TlsAcceptor { ignore_client_order: bool, use_key_log: bool, timeout: Option, + provider: Option>, ) -> Result { - let builder = ServerConfig::builder(); + let builder = match provider { + Some(provider) => ServerConfig::builder_with_provider(provider) + .with_safe_default_protocol_versions()?, + None => ServerConfig::builder(), + }; let builder = match client_ca_root { None => builder.with_no_client_auth(), diff --git a/tonic/src/transport/server/tls.rs b/tonic/src/transport/server/tls.rs index 969364e1f..584604d4c 100644 --- a/tonic/src/transport/server/tls.rs +++ b/tonic/src/transport/server/tls.rs @@ -1,4 +1,6 @@ -use std::{fmt, time::Duration}; +use std::{fmt, sync::Arc, time::Duration}; + +use tokio_rustls::rustls::crypto::CryptoProvider; use super::service::TlsAcceptor; use crate::transport::tls::{Certificate, Identity}; @@ -12,6 +14,7 @@ pub struct ServerTlsConfig { ignore_client_order: bool, use_key_log: bool, timeout: Option, + provider: Option>, } impl fmt::Debug for ServerTlsConfig { @@ -82,6 +85,19 @@ impl ServerTlsConfig { } } + /// Sets a custom rustls [`CryptoProvider`] used to build this server's TLS + /// configuration. + /// + /// When unset, the process-wide default installed via + /// `CryptoProvider::install_default` is used, falling back to the provider + /// selected by the `tls-ring` or `tls-aws-lc` feature. + pub fn with_provider(self, provider: Arc) -> Self { + ServerTlsConfig { + provider: Some(provider), + ..self + } + } + pub(crate) fn tls_acceptor(&self) -> Result { TlsAcceptor::new( self.identity.as_ref().unwrap(), @@ -90,6 +106,7 @@ impl ServerTlsConfig { self.ignore_client_order, self.use_key_log, self.timeout, + self.provider.clone(), ) } }