//! 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 CertifiedConn for async_native_tls::TlsStream where S: AsyncRead + AsyncWrite + Unpin, { fn peer_certificate(&self) -> IoResult>> { 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> { 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>> { // 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 StreamOps for async_native_tls::TlsStream { fn set_tcp_notsent_lowat(&self, notsent_lowat: u32) -> IoResult<()> { self.get_ref().set_tcp_notsent_lowat(notsent_lowat) } fn new_handle(&self) -> Box { self.get_ref().new_handle() } } /// An implementation of [`TlsConnector`] built with `native_tls`. pub struct NativeTlsConnector { /// The inner connector object. connector: async_native_tls::TlsConnector, /// Phantom data to ensure proper variance. _phantom: std::marker::PhantomData S>, } #[async_trait] impl TlsConnector for NativeTlsConnector where S: AsyncRead + AsyncWrite + StreamOps + Unpin + Send + 'static, { type Conn = async_native_tls::TlsStream; #[instrument(skip_all, level = "trace")] async fn negotiate_unvalidated(&self, stream: S, sni_hostname: &str) -> IoResult { let conn = self .connector .connect(sni_hostname, stream) .await .map_err(IoError::other)?; Ok(conn) } } impl TlsProvider for NativeTlsProvider where S: AsyncRead + AsyncWrite + StreamOps + Unpin + Send + 'static, { type Connector = NativeTlsConnector; type TlsStream = async_native_tls::TlsStream; 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 { // 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 } }