aboutsummaryrefslogtreecommitdiff
path: root/crates/tor-rtcompat/src/impls/native_tls.rs
blob: 5724ff00b81174d04dad661ef2a6b395adf13ffa (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
//! Implementation for using `native_tls`

use crate::{
    tls::{TlsAcceptorSettings, UnimplementedTls},
    traits::{CertifiedConn, StreamOps, TlsConnector, TlsProvider},
};

use async_trait::async_trait;
use futures::{AsyncRead, AsyncWrite};
use native_tls_crate as native_tls;
use std::{
    borrow::Cow,
    io::{Error as IoError, Result as IoResult},
};
use tracing::instrument;

/// A [`TlsProvider`] that uses `native_tls`.
///
/// It supports wrapping any reasonable stream type that implements `AsyncRead` + `AsyncWrite`.
#[cfg_attr(
    docsrs,
    doc(cfg(all(
        feature = "native-tls",
        any(feature = "tokio", feature = "async-std", feature = "smol")
    )))
)]
#[derive(Default, Clone)]
#[non_exhaustive]
pub struct NativeTlsProvider {}

impl<S> CertifiedConn for async_native_tls::TlsStream<S>
where
    S: AsyncRead + AsyncWrite + Unpin,
{
    fn peer_certificate(&self) -> IoResult<Option<Cow<'_, [u8]>>> {
        let cert = self.peer_certificate();
        match cert {
            Ok(Some(c)) => {
                let der = c.to_der().map_err(IoError::other)?;
                Ok(Some(Cow::from(der)))
            }
            Ok(None) => Ok(None),
            Err(e) => Err(IoError::other(e)),
        }
    }

    fn export_keying_material(
        &self,
        _len: usize,
        _label: &[u8],
        _context: Option<&[u8]>,
    ) -> IoResult<Vec<u8>> {
        Err(std::io::Error::new(
            std::io::ErrorKind::Unsupported,
            tor_error::bad_api_usage!("native-tls does not support exporting keying material"),
        ))
    }

    fn own_certificate(&self) -> IoResult<Option<Cow<'_, [u8]>>> {
        // This is a client stream, so (as we build them currently) we know we didn't present a
        // certificate.
        //
        // TODO: If we ever implement server-side native_tls support, we need to change this.
        // But first we'd need an implementation for export_keying_material.
        Ok(None)
    }
}

impl<S: AsyncRead + AsyncWrite + StreamOps + Unpin> StreamOps for async_native_tls::TlsStream<S> {
    fn set_tcp_notsent_lowat(&self, notsent_lowat: u32) -> IoResult<()> {
        self.get_ref().set_tcp_notsent_lowat(notsent_lowat)
    }

    fn new_handle(&self) -> Box<dyn StreamOps + Send + Unpin> {
        self.get_ref().new_handle()
    }
}

/// An implementation of [`TlsConnector`] built with `native_tls`.
pub struct NativeTlsConnector<S> {
    /// The inner connector object.
    connector: async_native_tls::TlsConnector,
    /// Phantom data to ensure proper variance.
    _phantom: std::marker::PhantomData<fn(S) -> S>,
}

#[async_trait]
impl<S> TlsConnector<S> for NativeTlsConnector<S>
where
    S: AsyncRead + AsyncWrite + StreamOps + Unpin + Send + 'static,
{
    type Conn = async_native_tls::TlsStream<S>;

    #[instrument(skip_all, level = "trace")]
    async fn negotiate_unvalidated(&self, stream: S, sni_hostname: &str) -> IoResult<Self::Conn> {
        let conn = self
            .connector
            .connect(sni_hostname, stream)
            .await
            .map_err(IoError::other)?;
        Ok(conn)
    }
}

impl<S> TlsProvider<S> for NativeTlsProvider
where
    S: AsyncRead + AsyncWrite + StreamOps + Unpin + Send + 'static,
{
    type Connector = NativeTlsConnector<S>;

    type TlsStream = async_native_tls::TlsStream<S>;

    type Acceptor = UnimplementedTls;
    type TlsServerStream = UnimplementedTls;

    fn tls_connector(&self) -> Self::Connector {
        let mut builder = native_tls::TlsConnector::builder();
        // These function names are scary, but they just mean that we
        // aren't checking whether the signer of this cert
        // participates in the web PKI, and we aren't checking the
        // hostname in the cert.
        builder
            .danger_accept_invalid_certs(true)
            .danger_accept_invalid_hostnames(true);

        // We don't participate in the web PKI, so there is no reason for us to load the standard
        // list of CAs and CRLs. This can save us an megabyte or two.
        builder.disable_built_in_roots(true);

        let connector = builder.into();

        NativeTlsConnector {
            connector,
            _phantom: std::marker::PhantomData,
        }
    }

    fn tls_acceptor(&self, _settings: TlsAcceptorSettings) -> IoResult<Self::Acceptor> {
        // TODO: In principle, there's nothing preventing us from implementing this,
        // except for the fact we decided to base our relay support on rustls.
        Err(crate::tls::TlsServerUnsupported {}.into())
    }

    fn supports_keying_material_export(&self) -> bool {
        false
    }
}