Skip to main content

tor_rtcompat/impls/
native_tls.rs

1//! Implementation for using `native_tls`
2
3use crate::{
4    tls::{TlsAcceptorSettings, UnimplementedTls},
5    traits::{CertifiedConn, StreamOps, TlsConnector, TlsProvider},
6};
7
8use async_trait::async_trait;
9use futures::{AsyncRead, AsyncWrite};
10use native_tls_crate as native_tls;
11use std::{
12    borrow::Cow,
13    io::{Error as IoError, Result as IoResult},
14};
15use tracing::instrument;
16
17/// A [`TlsProvider`] that uses `native_tls`.
18///
19/// It supports wrapping any reasonable stream type that implements `AsyncRead` + `AsyncWrite`.
20///
21/// # Limitations
22///
23/// `native_tls` doesn't let us configure TLS session resumption, so depending on the
24/// platform's TLS library, our ClientHello may advertise support for session tickets.
25/// The tor-spec says implementations SHOULD NOT allow session resumption.
26/// As far as we know, `native_tls` never stores or reuses a session, so no resumption
27/// actually happens, but this still makes our TLS fingerprint differ from C tor's.
28/// If that matters to you, use `RustlsProvider` instead, which disables resumption.
29/// See [#580](https://gitlab.torproject.org/tpo/core/arti/-/issues/580).
30#[cfg_attr(
31    docsrs,
32    doc(cfg(all(
33        feature = "native-tls",
34        any(feature = "tokio", feature = "async-std", feature = "smol")
35    )))
36)]
37#[derive(Default, Clone)]
38#[non_exhaustive]
39pub struct NativeTlsProvider {}
40
41impl<S> CertifiedConn for async_native_tls::TlsStream<S>
42where
43    S: AsyncRead + AsyncWrite + Unpin,
44{
45    fn peer_certificate(&self) -> IoResult<Option<Cow<'_, [u8]>>> {
46        let cert = self.peer_certificate();
47        match cert {
48            Ok(Some(c)) => {
49                let der = c.to_der().map_err(IoError::other)?;
50                Ok(Some(Cow::from(der)))
51            }
52            Ok(None) => Ok(None),
53            Err(e) => Err(IoError::other(e)),
54        }
55    }
56
57    fn export_keying_material(
58        &self,
59        _len: usize,
60        _label: &[u8],
61        _context: Option<&[u8]>,
62    ) -> IoResult<Vec<u8>> {
63        Err(std::io::Error::new(
64            std::io::ErrorKind::Unsupported,
65            tor_error::bad_api_usage!("native-tls does not support exporting keying material"),
66        ))
67    }
68
69    fn own_certificate(&self) -> IoResult<Option<Cow<'_, [u8]>>> {
70        // This is a client stream, so (as we build them currently) we know we didn't present a
71        // certificate.
72        //
73        // TODO: If we ever implement server-side native_tls support, we need to change this.
74        // But first we'd need an implementation for export_keying_material.
75        Ok(None)
76    }
77}
78
79impl<S: AsyncRead + AsyncWrite + StreamOps + Unpin> StreamOps for async_native_tls::TlsStream<S> {
80    fn set_tcp_notsent_lowat(&self, notsent_lowat: u32) -> IoResult<()> {
81        self.get_ref().set_tcp_notsent_lowat(notsent_lowat)
82    }
83
84    fn new_handle(&self) -> Box<dyn StreamOps + Send + Unpin> {
85        self.get_ref().new_handle()
86    }
87}
88
89/// An implementation of [`TlsConnector`] built with `native_tls`.
90pub struct NativeTlsConnector<S> {
91    /// The inner connector object.
92    connector: async_native_tls::TlsConnector,
93    /// Phantom data to ensure proper variance.
94    _phantom: std::marker::PhantomData<fn(S) -> S>,
95}
96
97#[async_trait]
98impl<S> TlsConnector<S> for NativeTlsConnector<S>
99where
100    S: AsyncRead + AsyncWrite + StreamOps + Unpin + Send + 'static,
101{
102    type Conn = async_native_tls::TlsStream<S>;
103
104    #[instrument(skip_all, level = "trace")]
105    async fn negotiate_unvalidated(&self, stream: S, sni_hostname: &str) -> IoResult<Self::Conn> {
106        let conn = self
107            .connector
108            .connect(sni_hostname, stream)
109            .await
110            .map_err(IoError::other)?;
111        Ok(conn)
112    }
113}
114
115impl<S> TlsProvider<S> for NativeTlsProvider
116where
117    S: AsyncRead + AsyncWrite + StreamOps + Unpin + Send + 'static,
118{
119    type Connector = NativeTlsConnector<S>;
120
121    type TlsStream = async_native_tls::TlsStream<S>;
122
123    type Acceptor = UnimplementedTls;
124    type TlsServerStream = UnimplementedTls;
125
126    fn tls_connector(&self) -> Self::Connector {
127        let mut builder = native_tls::TlsConnector::builder();
128        // These function names are scary, but they just mean that we
129        // aren't checking whether the signer of this cert
130        // participates in the web PKI, and we aren't checking the
131        // hostname in the cert.
132        builder
133            .danger_accept_invalid_certs(true)
134            .danger_accept_invalid_hostnames(true);
135
136        // We don't participate in the web PKI, so there is no reason for us to load the standard
137        // list of CAs and CRLs. This can save us an megabyte or two.
138        builder.disable_built_in_roots(true);
139
140        // We'd like to disable session resumption here, as the rustls provider does,
141        // but native_tls has no API for it. See the docs on `NativeTlsProvider`.
142
143        let connector = builder.into();
144
145        NativeTlsConnector {
146            connector,
147            _phantom: std::marker::PhantomData,
148        }
149    }
150
151    fn tls_acceptor(&self, _settings: TlsAcceptorSettings) -> IoResult<Self::Acceptor> {
152        // TODO: In principle, there's nothing preventing us from implementing this,
153        // except for the fact we decided to base our relay support on rustls.
154        Err(crate::tls::TlsServerUnsupported {}.into())
155    }
156
157    fn supports_keying_material_export(&self) -> bool {
158        false
159    }
160}