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#[cfg_attr(
21    docsrs,
22    doc(cfg(all(
23        feature = "native-tls",
24        any(feature = "tokio", feature = "async-std", feature = "smol")
25    )))
26)]
27#[derive(Default, Clone)]
28#[non_exhaustive]
29pub struct NativeTlsProvider {}
30
31impl<S> CertifiedConn for async_native_tls::TlsStream<S>
32where
33    S: AsyncRead + AsyncWrite + Unpin,
34{
35    fn peer_certificate(&self) -> IoResult<Option<Cow<'_, [u8]>>> {
36        let cert = self.peer_certificate();
37        match cert {
38            Ok(Some(c)) => {
39                let der = c.to_der().map_err(IoError::other)?;
40                Ok(Some(Cow::from(der)))
41            }
42            Ok(None) => Ok(None),
43            Err(e) => Err(IoError::other(e)),
44        }
45    }
46
47    fn export_keying_material(
48        &self,
49        _len: usize,
50        _label: &[u8],
51        _context: Option<&[u8]>,
52    ) -> IoResult<Vec<u8>> {
53        Err(std::io::Error::new(
54            std::io::ErrorKind::Unsupported,
55            tor_error::bad_api_usage!("native-tls does not support exporting keying material"),
56        ))
57    }
58
59    fn own_certificate(&self) -> IoResult<Option<Cow<'_, [u8]>>> {
60        // This is a client stream, so (as we build them currently) we know we didn't present a
61        // certificate.
62        //
63        // TODO: If we ever implement server-side native_tls support, we need to change this.
64        // But first we'd need an implementation for export_keying_material.
65        Ok(None)
66    }
67}
68
69impl<S: AsyncRead + AsyncWrite + StreamOps + Unpin> StreamOps for async_native_tls::TlsStream<S> {
70    fn set_tcp_notsent_lowat(&self, notsent_lowat: u32) -> IoResult<()> {
71        self.get_ref().set_tcp_notsent_lowat(notsent_lowat)
72    }
73
74    fn new_handle(&self) -> Box<dyn StreamOps + Send + Unpin> {
75        self.get_ref().new_handle()
76    }
77}
78
79/// An implementation of [`TlsConnector`] built with `native_tls`.
80pub struct NativeTlsConnector<S> {
81    /// The inner connector object.
82    connector: async_native_tls::TlsConnector,
83    /// Phantom data to ensure proper variance.
84    _phantom: std::marker::PhantomData<fn(S) -> S>,
85}
86
87#[async_trait]
88impl<S> TlsConnector<S> for NativeTlsConnector<S>
89where
90    S: AsyncRead + AsyncWrite + StreamOps + Unpin + Send + 'static,
91{
92    type Conn = async_native_tls::TlsStream<S>;
93
94    #[instrument(skip_all, level = "trace")]
95    async fn negotiate_unvalidated(&self, stream: S, sni_hostname: &str) -> IoResult<Self::Conn> {
96        let conn = self
97            .connector
98            .connect(sni_hostname, stream)
99            .await
100            .map_err(IoError::other)?;
101        Ok(conn)
102    }
103}
104
105impl<S> TlsProvider<S> for NativeTlsProvider
106where
107    S: AsyncRead + AsyncWrite + StreamOps + Unpin + Send + 'static,
108{
109    type Connector = NativeTlsConnector<S>;
110
111    type TlsStream = async_native_tls::TlsStream<S>;
112
113    type Acceptor = UnimplementedTls;
114    type TlsServerStream = UnimplementedTls;
115
116    fn tls_connector(&self) -> Self::Connector {
117        let mut builder = native_tls::TlsConnector::builder();
118        // These function names are scary, but they just mean that we
119        // aren't checking whether the signer of this cert
120        // participates in the web PKI, and we aren't checking the
121        // hostname in the cert.
122        builder
123            .danger_accept_invalid_certs(true)
124            .danger_accept_invalid_hostnames(true);
125
126        // We don't participate in the web PKI, so there is no reason for us to load the standard
127        // list of CAs and CRLs. This can save us an megabyte or two.
128        builder.disable_built_in_roots(true);
129
130        let connector = builder.into();
131
132        NativeTlsConnector {
133            connector,
134            _phantom: std::marker::PhantomData,
135        }
136    }
137
138    fn tls_acceptor(&self, _settings: TlsAcceptorSettings) -> IoResult<Self::Acceptor> {
139        // TODO: In principle, there's nothing preventing us from implementing this,
140        // except for the fact we decided to base our relay support on rustls.
141        Err(crate::tls::TlsServerUnsupported {}.into())
142    }
143
144    fn supports_keying_material_export(&self) -> bool {
145        false
146    }
147}