use std::{net::IpAddr, sync::{Arc, OnceLock}};
use anyhow::{bail, Context, Result};
use rustls::{
client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier},
pki_types::{CertificateDer, IpAddr as PkiIpAddr, ServerName, UnixTime},
DigitallySignedStruct, Error as TlsError, RootCertStore, SignatureScheme,
};
use tokio_rustls::TlsConnector;
use super::{http, BoxStream, Target};
#[derive(Debug)]
struct SkipCertVerify;
impl ServerCertVerifier for SkipCertVerify {
fn verify_server_cert(
&self,
_end_entity: &CertificateDer<'_>,
_intermediates: &[CertificateDer<'_>],
_server_name: &ServerName<'_>,
_ocsp_response: &[u8],
_now: UnixTime,
) -> Result<ServerCertVerified, TlsError> {
Ok(ServerCertVerified::assertion())
}
fn verify_tls12_signature(
&self,
_message: &[u8],
_cert: &CertificateDer<'_>,
_dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, TlsError> {
Ok(HandshakeSignatureValid::assertion())
}
fn verify_tls13_signature(
&self,
_message: &[u8],
_cert: &CertificateDer<'_>,
_dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, TlsError> {
Ok(HandshakeSignatureValid::assertion())
}
fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
rustls::crypto::ring::default_provider()
.signature_verification_algorithms
.supported_schemes()
}
}
pub async fn connect(
stream: BoxStream,
target: &Target,
username: Option<&str>,
password: Option<&str>,
proxy_addr: IpAddr,
skip_verify: bool,
) -> Result<BoxStream> {
let tls_stream = if skip_verify {
tls_wrap_insecure(stream, proxy_addr).await?
} else {
tls_wrap_verified(stream, proxy_addr).await?
};
http::connect(Box::new(tls_stream), target, username, password).await
}
fn verified_tls_config() -> Result<Arc<rustls::ClientConfig>> {
static CONFIG: OnceLock<Arc<rustls::ClientConfig>> = OnceLock::new();
if let Some(cfg) = CONFIG.get() {
return Ok(Arc::clone(cfg));
}
let native = rustls_native_certs::load_native_certs();
let mut root_store = RootCertStore::empty();
let (added, _failed) = root_store.add_parsable_certificates(native.certs);
if added == 0 {
bail!("HTTPS proxy: no trusted CA certificates found in the system store");
}
let cfg = Arc::new(
rustls::ClientConfig::builder()
.with_root_certificates(root_store)
.with_no_client_auth(),
);
Ok(Arc::clone(CONFIG.get_or_init(|| cfg)))
}
async fn tls_wrap_verified(
stream: BoxStream,
proxy_addr: IpAddr,
) -> Result<tokio_rustls::client::TlsStream<BoxStream>> {
tls_connect(verified_tls_config()?, stream, proxy_addr).await
}
async fn tls_wrap_insecure(
stream: BoxStream,
proxy_addr: IpAddr,
) -> Result<tokio_rustls::client::TlsStream<BoxStream>> {
let config = Arc::new(
rustls::ClientConfig::builder()
.dangerous()
.with_custom_certificate_verifier(Arc::new(SkipCertVerify))
.with_no_client_auth(),
);
tls_connect(config, stream, proxy_addr).await
}
fn make_server_name(proxy_addr: IpAddr) -> ServerName<'static> {
match proxy_addr {
IpAddr::V4(v4) => ServerName::IpAddress(PkiIpAddr::V4(v4.octets().into())),
IpAddr::V6(v6) => ServerName::IpAddress(PkiIpAddr::V6(v6.into())),
}
}
async fn tls_connect(
config: Arc<rustls::ClientConfig>,
stream: BoxStream,
proxy_addr: IpAddr,
) -> Result<tokio_rustls::client::TlsStream<BoxStream>> {
TlsConnector::from(config)
.connect(make_server_name(proxy_addr), stream)
.await
.context("HTTPS proxy: TLS handshake failed")
}