Skip to main content

proxychains_masq/proxy/
https.rs

1//! HTTP CONNECT over TLS (HTTPS proxy).
2//!
3//! Two modes are available:
4//! - **Verified** (`skip_verify = false`): loads the system CA store and performs full chain +
5//!   hostname validation.  Use the `https` proxy type for this.
6//! - **Insecure** (`skip_verify = true`): skips certificate validation entirely.  Intended only
7//!   for corporate proxies that present self-signed or internal-CA certificates.  Use the
8//!   `https_insecure` proxy type and ensure you trust the proxy host by other means.
9
10use std::{net::IpAddr, sync::{Arc, OnceLock}};
11
12use anyhow::{bail, Context, Result};
13use rustls::{
14    client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier},
15    pki_types::{CertificateDer, IpAddr as PkiIpAddr, ServerName, UnixTime},
16    DigitallySignedStruct, Error as TlsError, RootCertStore, SignatureScheme,
17};
18use tokio_rustls::TlsConnector;
19
20use super::{http, BoxStream, Target};
21
22// ─── Certificate verifiers ────────────────────────────────────────────────────
23
24/// A [`ServerCertVerifier`] that accepts any certificate without validation.
25///
26/// Only used when the caller has explicitly opted in via `ProxyType::HttpsInsecure`.
27#[derive(Debug)]
28struct SkipCertVerify;
29
30impl ServerCertVerifier for SkipCertVerify {
31    fn verify_server_cert(
32        &self,
33        _end_entity: &CertificateDer<'_>,
34        _intermediates: &[CertificateDer<'_>],
35        _server_name: &ServerName<'_>,
36        _ocsp_response: &[u8],
37        _now: UnixTime,
38    ) -> Result<ServerCertVerified, TlsError> {
39        Ok(ServerCertVerified::assertion())
40    }
41
42    fn verify_tls12_signature(
43        &self,
44        _message: &[u8],
45        _cert: &CertificateDer<'_>,
46        _dss: &DigitallySignedStruct,
47    ) -> Result<HandshakeSignatureValid, TlsError> {
48        Ok(HandshakeSignatureValid::assertion())
49    }
50
51    fn verify_tls13_signature(
52        &self,
53        _message: &[u8],
54        _cert: &CertificateDer<'_>,
55        _dss: &DigitallySignedStruct,
56    ) -> Result<HandshakeSignatureValid, TlsError> {
57        Ok(HandshakeSignatureValid::assertion())
58    }
59
60    fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
61        rustls::crypto::ring::default_provider()
62            .signature_verification_algorithms
63            .supported_schemes()
64    }
65}
66
67// ─── Public API ───────────────────────────────────────────────────────────────
68
69/// Connect through an HTTPS CONNECT proxy.
70///
71/// 1. Wraps `stream` in a TLS session to the proxy.
72/// 2. Sends an HTTP `CONNECT target HTTP/1.0` request over the TLS channel.
73/// 3. Returns the TLS stream positioned after the proxy's `200` response,
74///    ready to relay tunnel traffic.
75///
76/// # Arguments
77///
78/// * `stream` — a connected TCP stream to the proxy's HTTPS port.
79/// * `target` — the tunnel destination requested in the CONNECT line.
80/// * `username` / `password` — optional `Proxy-Authorization: Basic` credentials.
81/// * `proxy_addr` — the proxy's IP address, used as the TLS SNI value.
82/// * `skip_verify` — when `true`, certificate validation is skipped entirely
83///   (equivalent to `https_insecure` proxy type).  When `false`, the system CA
84///   store is used and the connection fails if the certificate is untrusted.
85///
86/// # Errors
87///
88/// Returns an error if the TLS handshake or HTTP CONNECT negotiation fails.
89pub async fn connect(
90    stream: BoxStream,
91    target: &Target,
92    username: Option<&str>,
93    password: Option<&str>,
94    proxy_addr: IpAddr,
95    skip_verify: bool,
96) -> Result<BoxStream> {
97    let tls_stream = if skip_verify {
98        tls_wrap_insecure(stream, proxy_addr).await?
99    } else {
100        tls_wrap_verified(stream, proxy_addr).await?
101    };
102    http::connect(Box::new(tls_stream), target, username, password).await
103}
104
105// ─── TLS helpers ─────────────────────────────────────────────────────────────
106
107/// Returns a `ClientConfig` backed by the system CA store, building and caching it on first call.
108///
109/// The config is process-global: the CA bundle is read from disk exactly once regardless of
110/// how many HTTPS proxy connections are made.
111fn verified_tls_config() -> Result<Arc<rustls::ClientConfig>> {
112    static CONFIG: OnceLock<Arc<rustls::ClientConfig>> = OnceLock::new();
113
114    // `get_or_try_init` isn't stable yet, so we initialise manually.
115    if let Some(cfg) = CONFIG.get() {
116        return Ok(Arc::clone(cfg));
117    }
118
119    let native = rustls_native_certs::load_native_certs();
120    let mut root_store = RootCertStore::empty();
121    let (added, _failed) = root_store.add_parsable_certificates(native.certs);
122    if added == 0 {
123        bail!("HTTPS proxy: no trusted CA certificates found in the system store");
124    }
125
126    let cfg = Arc::new(
127        rustls::ClientConfig::builder()
128            .with_root_certificates(root_store)
129            .with_no_client_auth(),
130    );
131    // Another thread may have raced us; either way the stored value is equivalent.
132    Ok(Arc::clone(CONFIG.get_or_init(|| cfg)))
133}
134
135/// Wrap `stream` in TLS, validating the proxy certificate against the system CA store.
136async fn tls_wrap_verified(
137    stream: BoxStream,
138    proxy_addr: IpAddr,
139) -> Result<tokio_rustls::client::TlsStream<BoxStream>> {
140    tls_connect(verified_tls_config()?, stream, proxy_addr).await
141}
142
143/// Wrap `stream` in TLS, skipping all certificate validation.
144async fn tls_wrap_insecure(
145    stream: BoxStream,
146    proxy_addr: IpAddr,
147) -> Result<tokio_rustls::client::TlsStream<BoxStream>> {
148    let config = Arc::new(
149        rustls::ClientConfig::builder()
150            .dangerous()
151            .with_custom_certificate_verifier(Arc::new(SkipCertVerify))
152            .with_no_client_auth(),
153    );
154    tls_connect(config, stream, proxy_addr).await
155}
156
157fn make_server_name(proxy_addr: IpAddr) -> ServerName<'static> {
158    match proxy_addr {
159        IpAddr::V4(v4) => ServerName::IpAddress(PkiIpAddr::V4(v4.octets().into())),
160        IpAddr::V6(v6) => ServerName::IpAddress(PkiIpAddr::V6(v6.into())),
161    }
162}
163
164async fn tls_connect(
165    config: Arc<rustls::ClientConfig>,
166    stream: BoxStream,
167    proxy_addr: IpAddr,
168) -> Result<tokio_rustls::client::TlsStream<BoxStream>> {
169    TlsConnector::from(config)
170        .connect(make_server_name(proxy_addr), stream)
171        .await
172        .context("HTTPS proxy: TLS handshake failed")
173}