Skip to main content

tonic/transport/channel/
tls.rs

1/*
2 *
3 * Copyright 2025 gRPC authors.
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a copy
6 * of this software and associated documentation files (the "Software"), to
7 * deal in the Software without restriction, including without limitation the
8 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9 * sell copies of the Software, and to permit persons to whom the Software is
10 * furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be included in
13 * all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21 * IN THE SOFTWARE.
22 *
23 */
24
25use super::service::TlsConnector;
26use crate::transport::{
27    Error,
28    tls::{Certificate, Identity},
29};
30use http::Uri;
31use std::sync::Arc;
32use std::time::Duration;
33use tokio_rustls::rustls::client::danger::ServerCertVerifier;
34use tokio_rustls::rustls::pki_types::TrustAnchor;
35
36/// Configures TLS settings for endpoints.
37#[derive(Debug, Clone, Default)]
38pub struct ClientTlsConfig {
39    domain: Option<String>,
40    certs: Vec<Certificate>,
41    trust_anchors: Vec<TrustAnchor<'static>>,
42    identity: Option<Identity>,
43    assume_http2: bool,
44    #[cfg(feature = "tls-native-roots")]
45    with_native_roots: bool,
46    #[cfg(feature = "tls-webpki-roots")]
47    with_webpki_roots: bool,
48    use_key_log: bool,
49    timeout: Option<Duration>,
50}
51
52impl ClientTlsConfig {
53    /// Creates a new `ClientTlsConfig` using Rustls.
54    pub fn new() -> Self {
55        Self::default()
56    }
57
58    /// Sets the domain name against which to verify the server's TLS certificate.
59    pub fn domain_name(self, domain_name: impl Into<String>) -> Self {
60        ClientTlsConfig {
61            domain: Some(domain_name.into()),
62            ..self
63        }
64    }
65
66    /// Adds the CA Certificate against which to verify the server's TLS certificate.
67    pub fn ca_certificate(self, ca_certificate: Certificate) -> Self {
68        let mut certs = self.certs;
69        certs.push(ca_certificate);
70        ClientTlsConfig { certs, ..self }
71    }
72
73    /// Adds the multiple CA Certificates against which to verify the server's TLS certificate.
74    pub fn ca_certificates(self, ca_certificates: impl IntoIterator<Item = Certificate>) -> Self {
75        let mut certs = self.certs;
76        certs.extend(ca_certificates);
77        ClientTlsConfig { certs, ..self }
78    }
79
80    /// Adds the trust anchor which to verify the server's TLS certificate.
81    pub fn trust_anchor(self, trust_anchor: TrustAnchor<'static>) -> Self {
82        let mut trust_anchors = self.trust_anchors;
83        trust_anchors.push(trust_anchor);
84        ClientTlsConfig {
85            trust_anchors,
86            ..self
87        }
88    }
89
90    /// Adds the multiple trust anchors which to verify the server's TLS certificate.
91    pub fn trust_anchors(
92        mut self,
93        trust_anchors: impl IntoIterator<Item = TrustAnchor<'static>>,
94    ) -> Self {
95        self.trust_anchors.extend(trust_anchors);
96        self
97    }
98
99    /// Sets the client identity to present to the server.
100    pub fn identity(self, identity: Identity) -> Self {
101        ClientTlsConfig {
102            identity: Some(identity),
103            ..self
104        }
105    }
106
107    /// If true, the connector should assume that the server supports HTTP/2,
108    /// even if it doesn't provide protocol negotiation via ALPN.
109    pub fn assume_http2(self, assume_http2: bool) -> Self {
110        ClientTlsConfig {
111            assume_http2,
112            ..self
113        }
114    }
115
116    /// Use key log as specified by the `SSLKEYLOGFILE` environment variable.
117    pub fn use_key_log(self) -> Self {
118        ClientTlsConfig {
119            use_key_log: true,
120            ..self
121        }
122    }
123
124    /// Enables the platform's trusted certs.
125    #[cfg(feature = "tls-native-roots")]
126    pub fn with_native_roots(self) -> Self {
127        ClientTlsConfig {
128            with_native_roots: true,
129            ..self
130        }
131    }
132
133    /// Enables the webpki roots.
134    #[cfg(feature = "tls-webpki-roots")]
135    pub fn with_webpki_roots(self) -> Self {
136        ClientTlsConfig {
137            with_webpki_roots: true,
138            ..self
139        }
140    }
141
142    /// Activates all TLS roots enabled through `tls-*-roots` feature flags
143    pub fn with_enabled_roots(self) -> Self {
144        let config = self;
145
146        #[cfg(feature = "tls-native-roots")]
147        let config = config.with_native_roots();
148        #[cfg(feature = "tls-webpki-roots")]
149        let config = config.with_webpki_roots();
150
151        config
152    }
153
154    /// Sets the timeout for the TLS handshake.
155    pub fn timeout(self, timeout: Duration) -> Self {
156        ClientTlsConfig {
157            timeout: Some(timeout),
158            ..self
159        }
160    }
161
162    pub(crate) fn into_tls_connector(self, uri: &Uri) -> Result<TlsConnector, crate::BoxError> {
163        self.build_tls_connector(uri, None)
164    }
165
166    pub(crate) fn into_tls_connector_with_verifier(
167        self,
168        uri: &Uri,
169        verifier: Arc<dyn ServerCertVerifier>,
170    ) -> Result<TlsConnector, crate::BoxError> {
171        self.build_tls_connector(uri, Some(verifier))
172    }
173
174    fn build_tls_connector(
175        self,
176        uri: &Uri,
177        server_cert_verifier: Option<Arc<dyn ServerCertVerifier>>,
178    ) -> Result<TlsConnector, crate::BoxError> {
179        let domain = if let Some(domain) = &self.domain {
180            domain
181        } else {
182            let host = uri.host().ok_or_else(Error::new_invalid_uri)?;
183            // host() returns the host including brackets if it's an IPv6 address
184            if host.starts_with('[') && host.ends_with(']') {
185                &host[1..host.len() - 1]
186            } else {
187                host
188            }
189        };
190        TlsConnector::new(
191            self.certs,
192            self.trust_anchors,
193            self.identity,
194            server_cert_verifier,
195            domain,
196            self.assume_http2,
197            self.use_key_log,
198            self.timeout,
199            #[cfg(feature = "tls-native-roots")]
200            self.with_native_roots,
201            #[cfg(feature = "tls-webpki-roots")]
202            self.with_webpki_roots,
203        )
204    }
205}
206
207#[cfg(test)]
208mod tests {
209    use super::*;
210
211    #[test]
212    fn test_into_tls_connector_with_ipv4() {
213        let config = ClientTlsConfig::new();
214        let uri = "https://192.168.1.1:443".parse::<Uri>().unwrap();
215        config.into_tls_connector(&uri).unwrap();
216    }
217
218    #[test]
219    fn test_into_tls_connector_with_ipv6_() {
220        let config = ClientTlsConfig::new();
221        let uri = "https://[::1]:443".parse::<Uri>().unwrap();
222
223        config.into_tls_connector(&uri).unwrap();
224    }
225
226    #[test]
227    fn test_into_tls_connector_with_domain_name() {
228        let config = ClientTlsConfig::new();
229        let uri = "https://example.com:443".parse::<Uri>().unwrap();
230
231        config.into_tls_connector(&uri).unwrap();
232    }
233
234    #[test]
235    fn test_into_tls_connector_with_explicit_domain() {
236        let config = ClientTlsConfig::new().domain_name("example.com");
237        let uri = "https://[2001:db8::1]:443".parse::<Uri>().unwrap();
238
239        config.into_tls_connector(&uri).unwrap();
240    }
241
242    #[test]
243    fn test_into_tls_connector_with_verifier_with_ipv6() {
244        use tokio_rustls::rustls::DigitallySignedStruct;
245        use tokio_rustls::rustls::client::danger::{
246            HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier,
247        };
248        use tokio_rustls::rustls::pki_types::{CertificateDer, ServerName, UnixTime};
249
250        #[derive(Debug)]
251        struct DummyVerifier;
252
253        impl ServerCertVerifier for DummyVerifier {
254            fn verify_server_cert(
255                &self,
256                _end_entity: &CertificateDer<'_>,
257                _intermediates: &[CertificateDer<'_>],
258                _server_name: &ServerName<'_>,
259                _ocsp_response: &[u8],
260                _now: UnixTime,
261            ) -> Result<ServerCertVerified, tokio_rustls::rustls::Error> {
262                Ok(ServerCertVerified::assertion())
263            }
264
265            fn verify_tls12_signature(
266                &self,
267                _message: &[u8],
268                _cert: &CertificateDer<'_>,
269                _dss: &DigitallySignedStruct,
270            ) -> Result<HandshakeSignatureValid, tokio_rustls::rustls::Error> {
271                Ok(HandshakeSignatureValid::assertion())
272            }
273
274            fn verify_tls13_signature(
275                &self,
276                _message: &[u8],
277                _cert: &CertificateDer<'_>,
278                _dss: &DigitallySignedStruct,
279            ) -> Result<HandshakeSignatureValid, tokio_rustls::rustls::Error> {
280                Ok(HandshakeSignatureValid::assertion())
281            }
282
283            fn supported_verify_schemes(&self) -> Vec<tokio_rustls::rustls::SignatureScheme> {
284                tokio_rustls::rustls::crypto::ring::default_provider()
285                    .signature_verification_algorithms
286                    .supported_schemes()
287            }
288        }
289
290        let config = ClientTlsConfig::new();
291        let uri = "https://[::1]:443".parse::<Uri>().unwrap();
292        config
293            .into_tls_connector_with_verifier(&uri, Arc::new(DummyVerifier))
294            .unwrap();
295    }
296}