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
///
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]
39
pub struct NativeTlsProvider {}
40

            
41
impl<S> CertifiedConn for async_native_tls::TlsStream<S>
42
where
43
    S: AsyncRead + AsyncWrite + Unpin,
44
{
45
4
    fn peer_certificate(&self) -> IoResult<Option<Cow<'_, [u8]>>> {
46
4
        let cert = self.peer_certificate();
47
4
        match cert {
48
4
            Ok(Some(c)) => {
49
4
                let der = c.to_der().map_err(IoError::other)?;
50
4
                Ok(Some(Cow::from(der)))
51
            }
52
            Ok(None) => Ok(None),
53
            Err(e) => Err(IoError::other(e)),
54
        }
55
4
    }
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

            
79
impl<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`.
90
pub 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]
98
impl<S> TlsConnector<S> for NativeTlsConnector<S>
99
where
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

            
115
impl<S> TlsProvider<S> for NativeTlsProvider
116
where
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
6
    fn tls_connector(&self) -> Self::Connector {
127
6
        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
6
        builder
133
6
            .danger_accept_invalid_certs(true)
134
6
            .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
6
        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
6
        let connector = builder.into();
144

            
145
6
        NativeTlsConnector {
146
6
            connector,
147
6
            _phantom: std::marker::PhantomData,
148
6
        }
149
6
    }
150

            
151
4
    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
4
        Err(crate::tls::TlsServerUnsupported {}.into())
155
4
    }
156

            
157
    fn supports_keying_material_export(&self) -> bool {
158
        false
159
    }
160
}