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