1
//! Implementation for using `native_tls`
2

            
3
use crate::{
4
    tls::{TlsAcceptorSettings, UnimplementedTls},
5
    traits::{CertifiedConn, StreamOps, TlsConnector, TlsProvider},
6
};
7

            
8
use async_trait::async_trait;
9
use futures::{AsyncRead, AsyncWrite};
10
use native_tls_crate as native_tls;
11
use std::{
12
    borrow::Cow,
13
    io::{Error as IoError, Result as IoResult},
14
};
15
use 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]
29
pub struct NativeTlsProvider {}
30

            
31
impl<S> CertifiedConn for async_native_tls::TlsStream<S>
32
where
33
    S: AsyncRead + AsyncWrite + Unpin,
34
{
35
8
    fn peer_certificate(&self) -> IoResult<Option<Cow<'_, [u8]>>> {
36
8
        let cert = self.peer_certificate();
37
8
        match cert {
38
8
            Ok(Some(c)) => {
39
8
                let der = c.to_der().map_err(IoError::other)?;
40
8
                Ok(Some(Cow::from(der)))
41
            }
42
            Ok(None) => Ok(None),
43
            Err(e) => Err(IoError::other(e)),
44
        }
45
8
    }
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

            
69
impl<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`.
80
pub 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]
88
impl<S> TlsConnector<S> for NativeTlsConnector<S>
89
where
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

            
105
impl<S> TlsProvider<S> for NativeTlsProvider
106
where
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
24
    fn tls_connector(&self) -> Self::Connector {
117
24
        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
24
        builder
123
24
            .danger_accept_invalid_certs(true)
124
24
            .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
24
        builder.disable_built_in_roots(true);
129

            
130
24
        let connector = builder.into();
131

            
132
24
        NativeTlsConnector {
133
24
            connector,
134
24
            _phantom: std::marker::PhantomData,
135
24
        }
136
24
    }
137

            
138
8
    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
8
        Err(crate::tls::TlsServerUnsupported {}.into())
142
8
    }
143

            
144
    fn supports_keying_material_export(&self) -> bool {
145
        false
146
    }
147
}