proxychains-masq 0.1.5

TUN-based proxy chain engine — routes TCP flows through SOCKS4/5, HTTP CONNECT, and HTTPS CONNECT proxy chains via a userspace network stack.
Documentation
//! HTTP CONNECT over TLS (HTTPS proxy).
//!
//! Two modes are available:
//! - **Verified** (`skip_verify = false`): loads the system CA store and performs full chain +
//!   hostname validation.  Use the `https` proxy type for this.
//! - **Insecure** (`skip_verify = true`): skips certificate validation entirely.  Intended only
//!   for corporate proxies that present self-signed or internal-CA certificates.  Use the
//!   `https_insecure` proxy type and ensure you trust the proxy host by other means.

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};

// ─── Certificate verifiers ────────────────────────────────────────────────────

/// A [`ServerCertVerifier`] that accepts any certificate without validation.
///
/// Only used when the caller has explicitly opted in via `ProxyType::HttpsInsecure`.
#[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()
    }
}

// ─── Public API ───────────────────────────────────────────────────────────────

/// Connect through an HTTPS CONNECT proxy.
///
/// 1. Wraps `stream` in a TLS session to the proxy.
/// 2. Sends an HTTP `CONNECT target HTTP/1.0` request over the TLS channel.
/// 3. Returns the TLS stream positioned after the proxy's `200` response,
///    ready to relay tunnel traffic.
///
/// # Arguments
///
/// * `stream` — a connected TCP stream to the proxy's HTTPS port.
/// * `target` — the tunnel destination requested in the CONNECT line.
/// * `username` / `password` — optional `Proxy-Authorization: Basic` credentials.
/// * `proxy_addr` — the proxy's IP address, used as the TLS SNI value.
/// * `skip_verify` — when `true`, certificate validation is skipped entirely
///   (equivalent to `https_insecure` proxy type).  When `false`, the system CA
///   store is used and the connection fails if the certificate is untrusted.
///
/// # Errors
///
/// Returns an error if the TLS handshake or HTTP CONNECT negotiation fails.
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
}

// ─── TLS helpers ─────────────────────────────────────────────────────────────

/// Returns a `ClientConfig` backed by the system CA store, building and caching it on first call.
///
/// The config is process-global: the CA bundle is read from disk exactly once regardless of
/// how many HTTPS proxy connections are made.
fn verified_tls_config() -> Result<Arc<rustls::ClientConfig>> {
    static CONFIG: OnceLock<Arc<rustls::ClientConfig>> = OnceLock::new();

    // `get_or_try_init` isn't stable yet, so we initialise manually.
    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(),
    );
    // Another thread may have raced us; either way the stored value is equivalent.
    Ok(Arc::clone(CONFIG.get_or_init(|| cfg)))
}

/// Wrap `stream` in TLS, validating the proxy certificate against the system CA store.
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
}

/// Wrap `stream` in TLS, skipping all certificate validation.
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")
}