Skip to main content

tor_rtcompat/impls/
rustls.rs

1//! Implementation for using Rustls with a runtime.
2//!
3//! #
4
5#[cfg(feature = "tls-server")]
6pub(crate) mod rustls_server;
7
8use crate::StreamOps;
9use crate::tls::TlsAcceptorSettings;
10use crate::traits::{CertifiedConn, TlsConnector, TlsProvider};
11
12use async_trait::async_trait;
13use futures::{AsyncRead, AsyncWrite};
14use futures_rustls::rustls::{self, crypto::CryptoProvider};
15use rustls::client::danger;
16use rustls::crypto::{WebPkiSupportedAlgorithms, verify_tls12_signature, verify_tls13_signature};
17use rustls::{CertificateError, Error as TLSError};
18use rustls_pki_types::{CertificateDer, ServerName};
19use tracing::instrument;
20use webpki::EndEntityCert; // this is actually rustls_webpki.
21
22use std::borrow::Cow;
23use std::{
24    io::{self, Error as IoError, Result as IoResult},
25    sync::Arc,
26};
27
28/// A [`TlsProvider`] that uses `rustls`.
29///
30/// It supports wrapping any reasonable stream type that implements `AsyncRead` + `AsyncWrite`.
31///
32/// # Cryptographic providers
33///
34/// The application is responsible for calling [`CryptoProvider::install_default()`]
35/// before constructing [`TlsProvider`].  If they do not, rustls will fail,
36/// and arti will not run.
37///
38/// We currently recommend the [aws-lc-rs] provider because,
39/// of the two builtin providers that ship with rustls,
40/// it appears to have better performance,
41/// and it supports hybrid post-quantum handshakes.
42/// It used to have a [problematic license][aws-lc-old-license],
43/// but [this has been fixed][aws-lc-relicense] since March 2026.
44///
45/// See the [rustls documentation][all-providers] for a list of other rustls
46/// cryptography providers.
47///
48/// [ring]: https://crates.io/crates/ring
49/// [aws-lc-rs]: https://github.com/aws/aws-lc-rs
50/// [aws-lc-old-license]: https://github.com/aws/aws-lc/issues/2203
51/// [all-providers]: https://docs.rs/rustls/latest/rustls/#cryptography-providers
52/// [aws-lc-relicense]: https://github.com/aws/aws-lc/pull/3091
53#[cfg_attr(
54    docsrs,
55    doc(cfg(all(
56        feature = "rustls",
57        any(feature = "tokio", feature = "async-std", feature = "smol")
58    )))
59)]
60#[derive(Clone)]
61#[non_exhaustive]
62pub struct RustlsProvider {
63    /// Inner `ClientConfig` logic used to create connectors.
64    config: Arc<futures_rustls::rustls::ClientConfig>,
65}
66
67impl<S> CertifiedConn for futures_rustls::client::TlsStream<S> {
68    fn peer_certificate(&self) -> IoResult<Option<Cow<'_, [u8]>>> {
69        let (_, session) = self.get_ref();
70        Ok(session
71            .peer_certificates()
72            .and_then(|certs| certs.first().map(|c| Cow::from(c.as_ref()))))
73    }
74
75    fn export_keying_material(
76        &self,
77        len: usize,
78        label: &[u8],
79        context: Option<&[u8]>,
80    ) -> IoResult<Vec<u8>> {
81        let (_, session) = self.get_ref();
82        session
83            .export_keying_material(vec![0_u8; len], label, context)
84            .map_err(|e| IoError::new(io::ErrorKind::InvalidData, e))
85    }
86
87    fn own_certificate(&self) -> IoResult<Option<Cow<'_, [u8]>>> {
88        // This is a client stream, so (as we build them currently) we know we didn't present a certificate.
89        Ok(None)
90    }
91}
92
93impl<S: StreamOps> StreamOps for futures_rustls::client::TlsStream<S> {
94    fn set_tcp_notsent_lowat(&self, notsent_lowat: u32) -> IoResult<()> {
95        self.get_ref().0.set_tcp_notsent_lowat(notsent_lowat)
96    }
97
98    fn new_handle(&self) -> Box<dyn StreamOps + Send + Unpin> {
99        self.get_ref().0.new_handle()
100    }
101}
102
103/// An implementation of [`TlsConnector`] built with `rustls`.
104pub struct RustlsConnector<S> {
105    /// The inner connector object.
106    connector: futures_rustls::TlsConnector,
107    /// Phantom data to ensure proper variance.
108    _phantom: std::marker::PhantomData<fn(S) -> S>,
109}
110
111#[async_trait]
112impl<S> TlsConnector<S> for RustlsConnector<S>
113where
114    S: AsyncRead + AsyncWrite + StreamOps + Unpin + Send + 'static,
115{
116    type Conn = futures_rustls::client::TlsStream<S>;
117
118    #[instrument(skip_all, level = "trace")]
119    async fn negotiate_unvalidated(&self, stream: S, sni_hostname: &str) -> IoResult<Self::Conn> {
120        let name: ServerName<'_> = sni_hostname
121            .try_into()
122            .map_err(|e| IoError::new(io::ErrorKind::InvalidInput, e))?;
123        self.connector.connect(name.to_owned(), stream).await
124    }
125}
126
127impl<S> TlsProvider<S> for RustlsProvider
128where
129    S: AsyncRead + AsyncWrite + StreamOps + Unpin + Send + 'static,
130{
131    type Connector = RustlsConnector<S>;
132
133    type TlsStream = futures_rustls::client::TlsStream<S>;
134
135    fn tls_connector(&self) -> Self::Connector {
136        let connector = futures_rustls::TlsConnector::from(Arc::clone(&self.config));
137        RustlsConnector {
138            connector,
139            _phantom: std::marker::PhantomData,
140        }
141    }
142
143    cfg_if::cfg_if! {
144        if #[cfg(feature = "tls-server")] {
145            type Acceptor = rustls_server::RustlsAcceptor<S>;
146            type TlsServerStream = rustls_server::RustlsServerStream<S>;
147            fn tls_acceptor(&self, settings: TlsAcceptorSettings) -> IoResult<Self::Acceptor> {
148                rustls_server::RustlsAcceptor::new(&settings)
149            }
150        } else {
151            type Acceptor = crate::tls::UnimplementedTls;
152            type TlsServerStream = crate::tls::UnimplementedTls;
153            fn tls_acceptor(&self, _settings: TlsAcceptorSettings) -> IoResult<Self::Acceptor> {
154                Err(crate::tls::TlsServerUnsupported{}.into())
155            }
156        }
157    }
158
159    fn supports_keying_material_export(&self) -> bool {
160        true
161    }
162}
163
164/// Try to install a default crypto provider if none has been installed, so that Rustls can operate.
165///
166/// We only use this for testing: It is the application's responsibility to install a CryptoProvider.
167///
168#[cfg(any(test, feature = "testing"))]
169fn ensure_testing_provider_installed() {
170    if CryptoProvider::get_default().is_none() {
171        // If we haven't installed a CryptoProvider at this point, we warn and install
172        // the `ring` provider.  That isn't great, but the alternative would be to
173        // panic.  Right now, that would cause many of our tests to fail.
174        tracing::warn!(
175            "Creating a RustlsRuntime, but no CryptoProvider is installed. The application \
176                        should call CryptoProvider::install_default()"
177        );
178        let _idempotent_ignore = CryptoProvider::install_default(
179            futures_rustls::rustls::crypto::aws_lc_rs::default_provider(),
180        );
181    }
182}
183
184impl RustlsProvider {
185    /// Construct a new [`RustlsProvider`].
186    pub(crate) fn new() -> Self {
187        #[cfg(any(test, feature = "testing"))]
188        ensure_testing_provider_installed();
189
190        // Be afraid: we are overriding the default certificate verification and
191        // TLS signature checking code! See notes on `Verifier` below for
192        // details.
193        //
194        // Note that the `set_certificate_verifier` function is somewhat
195        // misnamed: it overrides not only how certificates are verified, but
196        // also how certificates are used to check the signatures in a TLS
197        // handshake.
198        let mut config = futures_rustls::rustls::client::ClientConfig::builder()
199            .dangerous()
200            .with_custom_certificate_verifier(Arc::new(Verifier(
201                CryptoProvider::get_default()
202                    .expect("CryptoProvider not installed")
203                    .signature_verification_algorithms,
204            )))
205            .with_no_client_auth();
206
207        // tor-spec:
208        // > Implementations SHOULD NOT allow TLS session resumption – it can exacerbate some
209        // > attacks (e.g. the “Triple Handshake” attack from Feb 2013), and it plays havoc with
210        // > forward secrecy guarantees.
211        config.resumption = futures_rustls::rustls::client::Resumption::disabled();
212
213        RustlsProvider {
214            config: Arc::new(config),
215        }
216    }
217}
218
219impl Default for RustlsProvider {
220    fn default() -> Self {
221        Self::new()
222    }
223}
224
225/// A custom [`rustls::client::danger::ServerCertVerifier`]
226///
227/// This verifier is necessary since Tor relays doesn't participate in the web
228/// browser PKI, and as such their certificates won't check out as valid ones.
229///
230/// We enforce that the certificate itself has correctly authenticated the TLS
231/// connection, but nothing else.
232#[derive(Clone, Debug)]
233struct Verifier(pub(crate) WebPkiSupportedAlgorithms);
234
235impl danger::ServerCertVerifier for Verifier {
236    fn verify_server_cert(
237        &self,
238        end_entity: &CertificateDer,
239        _roots: &[CertificateDer],
240        _server_name: &ServerName,
241        _ocsp_response: &[u8],
242        _now: rustls_pki_types::UnixTime,
243    ) -> Result<danger::ServerCertVerified, TLSError> {
244        // We don't check anything about the certificate at this point other
245        // than making sure it is well-formed.
246        //
247        // When we make a channel, we'll check that it's authenticated by the
248        // other party's real identity key, inside the Tor handshake.
249        //
250        // In theory, we shouldn't have to do even this much: rustls should not
251        // allow a handshake  without a certificate, and the certificate's
252        // well-formedness should get checked below in one of the
253        // verify_*_signature functions.  But this check is cheap, so let's
254        // leave it in.
255        let _cert: EndEntityCert<'_> = end_entity
256            .try_into()
257            .map_err(|_| TLSError::InvalidCertificate(CertificateError::BadEncoding))?;
258
259        // Note that we don't even check timeliness or key usage: Tor uses the presented
260        // relay certificate just as a container for the relay's public link
261        // key.  Actual timeliness checks will happen later, on the certificates
262        // that authenticate this one, when we process the relay's CERTS cell in
263        // `tor_proto::channel::handshake`.
264        //
265        // (This is what makes it safe for us _not_ to call
266        // EndEntityCert::verify_for_usage.)
267
268        Ok(danger::ServerCertVerified::assertion())
269    }
270
271    fn verify_tls12_signature(
272        &self,
273        message: &[u8],
274        cert: &CertificateDer,
275        dss: &rustls::DigitallySignedStruct,
276    ) -> Result<danger::HandshakeSignatureValid, TLSError> {
277        verify_tls12_signature(message, cert, dss, &self.0)
278    }
279
280    fn verify_tls13_signature(
281        &self,
282        message: &[u8],
283        cert: &CertificateDer,
284        dss: &rustls::DigitallySignedStruct,
285    ) -> Result<danger::HandshakeSignatureValid, TLSError> {
286        verify_tls13_signature(message, cert, dss, &self.0)
287    }
288
289    fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
290        self.0.supported_schemes()
291    }
292
293    fn root_hint_subjects(&self) -> Option<&[rustls::DistinguishedName]> {
294        // We don't actually want to send any DNs for our root certs,
295        // since they aren't real.
296        None
297    }
298}
299
300#[cfg(test)]
301mod test {
302    // @@ begin test lint list maintained by maint/add_warning @@
303    #![allow(clippy::bool_assert_comparison)]
304    #![allow(clippy::clone_on_copy)]
305    #![allow(clippy::dbg_macro)]
306    #![allow(clippy::mixed_attributes_style)]
307    #![allow(clippy::print_stderr)]
308    #![allow(clippy::print_stdout)]
309    #![allow(clippy::single_char_pattern)]
310    #![allow(clippy::unwrap_used)]
311    #![allow(clippy::unchecked_time_subtraction)]
312    #![allow(clippy::useless_vec)]
313    #![allow(clippy::needless_pass_by_value)]
314    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
315    use super::*;
316
317    /// A certificate returned by a C Tor relay implementation.
318    ///
319    /// We want to have a test for this, since some older versions of `webpki`
320    /// rejected C Tor's certificates as unparsable because they did not contain
321    /// any extensions.  Back then, we had to use `x509_signature`,
322    /// which now appears unmaintained.
323    const TOR_CERTIFICATE: &[u8] = include_bytes!("./tor-generated.der");
324
325    #[test]
326    fn basic_tor_cert() {
327        ensure_testing_provider_installed();
328        let der = CertificateDer::from_slice(TOR_CERTIFICATE);
329        let _cert = EndEntityCert::try_from(&der).unwrap();
330    }
331}