use arti_client::DataStream;
use crate::config::TlsVersion;
use crate::error::{Error, Result};
use crate::stream::TorStream;
#[derive(Debug, Clone)]
pub struct TlsConfig {
pub verify_certificates: bool,
pub min_version: TlsVersion,
}
impl Default for TlsConfig {
fn default() -> Self {
Self {
verify_certificates: true,
min_version: TlsVersion::Tls12,
}
}
}
#[cfg(all(feature = "native-tls", not(feature = "rustls")))]
pub async fn wrap_tls_native(
stream: DataStream,
host: &str,
config: &TlsConfig,
) -> Result<TorStream> {
use tokio_native_tls::TlsConnector;
use tokio_native_tls::native_tls::{Protocol, TlsConnector as NativeTlsConnector};
let mut builder = NativeTlsConnector::builder();
let min_protocol = match config.min_version {
TlsVersion::Tls12 => Protocol::Tlsv12,
TlsVersion::Tls13 => Protocol::Tlsv12, };
builder.min_protocol_version(Some(min_protocol));
if !config.verify_certificates {
builder.danger_accept_invalid_certs(true);
builder.danger_accept_invalid_hostnames(true);
}
let connector = builder.build().map_err(|e| Error::TlsConfig {
message: e.to_string(),
})?;
let connector = TlsConnector::from(connector);
let tls_handshake = connector.connect(host, stream);
let tls_stream = tokio::time::timeout(std::time::Duration::from_secs(30), tls_handshake)
.await
.map_err(|_| Error::TlsConfig {
message: format!("TLS handshake timed out for {}", host),
})?
.map_err(|e| Error::tls_handshake(host, e))?;
Ok(TorStream::native_tls(tls_stream))
}
#[cfg(feature = "rustls")]
pub async fn wrap_tls_rustls(
stream: DataStream,
host: &str,
_config: &TlsConfig,
) -> Result<TorStream> {
use std::sync::Arc;
use tokio_rustls::TlsConnector;
use tokio_rustls::rustls::ClientConfig;
use tokio_rustls::rustls::RootCertStore;
use tokio_rustls::rustls::crypto::ring::default_provider;
use tokio_rustls::rustls::pki_types::ServerName;
let _ = default_provider().install_default();
let mut root_store = RootCertStore::empty();
let certs_result = rustls_native_certs::load_native_certs();
for cert in certs_result.certs {
let _ = root_store.add(cert);
}
let config = ClientConfig::builder()
.with_root_certificates(root_store)
.with_no_client_auth();
let connector = TlsConnector::from(Arc::new(config));
let server_name = ServerName::try_from(host.to_string())
.map_err(|_| Error::invalid_url(host, "invalid hostname for TLS"))?;
let tls_handshake = connector.connect(server_name, stream);
let tls_stream = tokio::time::timeout(std::time::Duration::from_secs(30), tls_handshake)
.await
.map_err(|_| Error::TlsConfig {
message: format!("TLS handshake timed out for {}", host),
})?
.map_err(|e| Error::tls_handshake(host, e))?;
Ok(TorStream::rustls(tls_stream))
}
pub async fn wrap_tls(stream: DataStream, host: &str, config: &TlsConfig) -> Result<TorStream> {
#[cfg(feature = "rustls")]
{
return wrap_tls_rustls(stream, host, config).await;
}
#[cfg(all(feature = "native-tls", not(feature = "rustls")))]
{
return wrap_tls_native(stream, host, config).await;
}
#[cfg(not(any(feature = "native-tls", feature = "rustls")))]
{
let _ = (stream, host, config);
Err(Error::TlsConfig {
message: "No TLS backend enabled. Enable 'native-tls' or 'rustls' feature.".to_string(),
})
}
}