use super::connect::Protocols;
#[cfg(tls_rustls)]
mod engine {
use std::sync::Arc;
use futures_io::{AsyncRead, AsyncWrite};
use rustls::{ClientConfig, crypto::ring, pki_types::ServerName};
use rustls_pki_types::CertificateDer;
#[cfg(not(android_verifier))]
use rustls_platform_verifier::Verifier;
use super::Protocols;
use crate::Error;
pub type TlsStream<S> = futures_rustls::client::TlsStream<S>;
#[cfg(not(android_verifier))]
fn verifier(
extra_roots: &[CertificateDer<'static>],
provider: Arc<rustls::crypto::CryptoProvider>,
) -> Result<Arc<dyn rustls::client::danger::ServerCertVerifier>, Error> {
let verifier = if extra_roots.is_empty() {
Verifier::new(provider)
} else {
Verifier::new_with_extra_roots(extra_roots.iter().cloned(), provider)
}
.map_err(Error::tls)?;
Ok(Arc::new(verifier))
}
#[cfg(android_verifier)]
fn verifier(
extra_roots: &[CertificateDer<'static>],
provider: Arc<rustls::crypto::CryptoProvider>,
) -> Result<Arc<dyn rustls::client::danger::ServerCertVerifier>, Error> {
let mut store = super::super::android::system_roots()?;
for root in extra_roots {
store.add(root.clone()).map_err(Error::tls)?;
}
let verifier =
rustls::client::WebPkiServerVerifier::builder_with_provider(Arc::new(store), provider)
.build()
.map_err(Error::tls)?;
Ok(verifier)
}
fn alpn_protocols(protocols: Protocols) -> Vec<Vec<u8>> {
match protocols {
Protocols::Http1 => vec![b"http/1.1".to_vec()],
#[cfg(feature = "http2")]
Protocols::Http2OrHttp1 => vec![b"h2".to_vec(), b"http/1.1".to_vec()],
#[cfg(not(feature = "http2"))]
Protocols::Http2OrHttp1 => vec![b"http/1.1".to_vec()],
}
}
#[derive(Clone)]
pub struct TlsConnector {
#[cfg(http3)]
base: Arc<ClientConfig>,
http1: futures_rustls::TlsConnector,
http2_or_http1: futures_rustls::TlsConnector,
}
impl TlsConnector {
pub fn new(extra_roots: &[CertificateDer<'static>]) -> Result<Self, Error> {
let provider = Arc::new(ring::default_provider());
let verifier = verifier(extra_roots, Arc::clone(&provider))?;
let base = ClientConfig::builder_with_provider(provider)
.with_safe_default_protocol_versions()
.map_err(Error::tls)?
.dangerous()
.with_custom_certificate_verifier(verifier)
.with_no_client_auth();
let mut http1 = base.clone();
http1.alpn_protocols = alpn_protocols(Protocols::Http1);
#[cfg(http3)]
let mut http2_or_http1 = base.clone();
#[cfg(not(http3))]
let mut http2_or_http1 = base;
http2_or_http1.alpn_protocols = alpn_protocols(Protocols::Http2OrHttp1);
Ok(Self {
#[cfg(http3)]
base: Arc::new(base),
http1: futures_rustls::TlsConnector::from(Arc::new(http1)),
http2_or_http1: futures_rustls::TlsConnector::from(Arc::new(http2_or_http1)),
})
}
#[cfg(http3)]
pub const fn client_config(&self) -> &Arc<ClientConfig> {
&self.base
}
pub async fn connect<S>(
&self,
host: &str,
stream: S,
protocols: Protocols,
) -> Result<TlsStream<S>, Error>
where
S: AsyncRead + AsyncWrite + Unpin,
{
let server_name = ServerName::try_from(host.to_owned()).map_err(Error::tls)?;
let connector = match protocols {
Protocols::Http1 => &self.http1,
Protocols::Http2OrHttp1 => &self.http2_or_http1,
};
connector
.connect(server_name, stream)
.await
.map_err(Error::tls)
}
}
#[allow(clippy::unnecessary_wraps)] pub fn negotiated_alpn<S>(stream: &TlsStream<S>) -> Result<Option<Vec<u8>>, Error> {
let (_, connection) = stream.get_ref();
Ok(connection.alpn_protocol().map(<[u8]>::to_vec))
}
}
#[cfg(tls_native)]
mod engine {
use futures_io::{AsyncRead, AsyncWrite};
use rustls_pki_types::CertificateDer;
use super::{
super::native_tls_stream::{self, TlsConnector as Connector},
Protocols,
};
use crate::Error;
pub type TlsStream<S> = native_tls_stream::TlsStream<S>;
const ALPN_HTTP1: &[&str] = &["http/1.1"];
#[cfg(feature = "http2")]
const ALPN_HTTP2_OR_HTTP1: &[&str] = &["h2", "http/1.1"];
#[cfg(not(feature = "http2"))]
const ALPN_HTTP2_OR_HTTP1: &[&str] = ALPN_HTTP1;
fn connector(
extra_roots: &[CertificateDer<'static>],
alpns: &[&str],
) -> Result<Connector, Error> {
let mut builder = native_tls::TlsConnector::builder();
for root in extra_roots {
let certificate = native_tls::Certificate::from_der(root).map_err(Error::tls)?;
builder.add_root_certificate(certificate);
}
builder.request_alpns(alpns);
Ok(Connector::new(builder.build().map_err(Error::tls)?))
}
#[derive(Clone)]
pub struct TlsConnector {
http1: Connector,
http2_or_http1: Connector,
}
impl TlsConnector {
pub fn new(extra_roots: &[CertificateDer<'static>]) -> Result<Self, Error> {
Ok(Self {
http1: connector(extra_roots, ALPN_HTTP1)?,
http2_or_http1: connector(extra_roots, ALPN_HTTP2_OR_HTTP1)?,
})
}
pub async fn connect<S>(
&self,
host: &str,
stream: S,
protocols: Protocols,
) -> Result<TlsStream<S>, Error>
where
S: AsyncRead + AsyncWrite + Unpin,
{
let connector = match protocols {
Protocols::Http1 => &self.http1,
Protocols::Http2OrHttp1 => &self.http2_or_http1,
};
connector.connect(host, stream).await.map_err(Error::tls)
}
}
pub fn negotiated_alpn<S>(stream: &TlsStream<S>) -> Result<Option<Vec<u8>>, Error>
where
S: AsyncRead + AsyncWrite + Unpin,
{
stream.negotiated_alpn().map_err(Error::tls)
}
}
pub use engine::{TlsConnector, TlsStream, negotiated_alpn};
impl std::fmt::Debug for TlsConnector {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TlsConnector").finish_non_exhaustive()
}
}