stealthscraper_rs/
proxy.rs1use crate::Error;
7use http_body_util::BodyExt;
8use hyper::server::conn::http1;
9use hyper::service::service_fn;
10use hyper::upgrade::Upgraded;
11use hyper::{Method, Request, Response, StatusCode, body::Incoming};
12use hyper_util::rt::TokioIo;
13use rcgen::{CertifiedKey, generate_simple_self_signed};
14use std::net::SocketAddr;
15use std::sync::{Arc, RwLock};
16use tokio::net::TcpListener;
17use tokio_rustls::TlsAcceptor;
18use tokio_rustls::rustls::{
19 ServerConfig, pki_types::CertificateDer, pki_types::PrivatePkcs8KeyDer,
20};
21use tokio_util::sync::CancellationToken;
22use wreq::Client;
23
24type SharedClient = Arc<RwLock<Arc<Client>>>;
32
33fn empty_response(status: StatusCode) -> Response<wreq::Body> {
38 let mut response = Response::new(wreq::Body::from(""));
39 *response.status_mut() = status;
40 response
41}
42
43fn upstream_response(resp: wreq::Response) -> Response<wreq::Body> {
49 let mut builder = Response::builder().status(resp.status().as_u16());
50 for (key, value) in resp.headers() {
51 builder = builder.header(key.as_str(), value.as_bytes());
52 }
53 match builder.body(wreq::Body::wrap_stream(resp.bytes_stream())) {
54 Ok(response) => response,
55 Err(err) => {
56 log::warn!("dropping upstream response with unrepresentable headers: {err}");
57 empty_response(StatusCode::BAD_GATEWAY)
58 }
59 }
60}
61
62pub struct TlsSpoofingProxy {
69 port: u16,
70 shutdown_tx: Option<tokio::sync::oneshot::Sender<()>>,
71 cancel_token: CancellationToken,
72 client_slot: SharedClient,
73}
74
75impl Drop for TlsSpoofingProxy {
76 fn drop(&mut self) {
77 self.cancel_token.cancel();
78 if let Some(tx) = self.shutdown_tx.take() {
79 let _ = tx.send(()); }
81 }
82}
83
84impl TlsSpoofingProxy {
85 pub async fn start(impersonate_client: Client, debug_mode: bool) -> Result<Self, Error> {
91 let addr = SocketAddr::from(([127, 0, 0, 1], 0));
92 let listener = TcpListener::bind(addr).await?;
93 let port = listener.local_addr()?.port();
94
95 if debug_mode {
96 log::info!("TLS spoofing proxy listening on 127.0.0.1:{port}");
97 }
98
99 let client_slot: SharedClient = Arc::new(RwLock::new(Arc::new(impersonate_client)));
100 let client = Arc::clone(&client_slot);
101 let cancel_token = CancellationToken::new();
102 let loop_token = cancel_token.clone();
103
104 let (shutdown_tx, mut shutdown_rx) = tokio::sync::oneshot::channel::<()>();
105
106 tokio::spawn(async move {
107 loop {
108 tokio::select! {
109 _ = loop_token.cancelled() => {
110 log::debug!("proxy listener loop cancelled");
111 break;
112 }
113 res = listener.accept() => {
114 match res {
115 Ok((stream, addr)) => {
116 if debug_mode {
117 log::debug!("accepted connection from {addr}");
118 }
119 let io = TokioIo::new(stream);
120 let client_clone = Arc::clone(&client);
121 let conn_token = loop_token.clone();
122
123 tokio::task::spawn(async move {
124 let service_token = conn_token.clone();
125 let conn = http1::Builder::new()
126 .preserve_header_case(true)
127 .title_case_headers(true)
128 .serve_connection(io, service_fn(move |req| {
129 let req_token = service_token.clone();
130 Self::handle_request(req, Arc::clone(&client_clone), req_token, debug_mode)
131 }))
132 .with_upgrades();
133
134 tokio::pin!(conn);
135
136 tokio::select! {
137 res = &mut conn => {
138 if let Err(err) = res {
139 log::warn!("failed to serve connection: {err:?}");
140 }
141 }
142 _ = conn_token.cancelled() => {
143 conn.as_mut().graceful_shutdown();
144 }
145 }
146 });
147 }
148 Err(e) => {
149 log::warn!("accept failed: {e}");
150 }
151 }
152 }
153 _ = &mut shutdown_rx => {
154 log::debug!("proxy listener shutting down");
155 break;
156 }
157 }
158 }
159 });
160
161 Ok(Self {
162 port,
163 shutdown_tx: Some(shutdown_tx),
164 cancel_token,
165 client_slot,
166 })
167 }
168
169 pub fn port(&self) -> u16 {
171 self.port
172 }
173
174 pub fn set_upstream_client(&self, client: Client) {
179 *self.client_slot.write().expect("client_slot lock poisoned") = Arc::new(client);
180 }
181
182 async fn handle_request(
183 mut req: Request<Incoming>,
184 client: SharedClient,
185 token: CancellationToken,
186 debug_mode: bool,
187 ) -> Result<Response<wreq::Body>, std::convert::Infallible> {
188 if Method::CONNECT == req.method() {
189 let target_host = req.uri().host().unwrap_or("").to_string();
190
191 if debug_mode {
192 log::debug!("[MITM] intercepting TLS upgrade for {target_host}");
193 }
194
195 tokio::task::spawn(async move {
196 match hyper::upgrade::on(&mut req).await {
197 Ok(upgraded) => {
198 let _ =
199 Self::handle_tunnel(upgraded, target_host, client, token, debug_mode)
200 .await;
201 }
202 Err(e) => log::warn!("upgrade error: {e}"),
203 }
204 });
205
206 Ok(empty_response(StatusCode::OK))
207 } else {
208 let hyper_uri = req.uri().to_string();
209 let method = req.method().clone();
210
211 let Ok(wreq_method) = wreq::Method::from_bytes(method.as_str().as_bytes()) else {
213 return Ok(empty_response(StatusCode::BAD_REQUEST));
214 };
215
216 let client = client.read().expect("client_slot lock poisoned").clone();
218
219 let mut req_builder = client.request(wreq_method, hyper_uri.clone());
221
222 for (key, value) in req.headers() {
223 req_builder = req_builder.header(key.as_str(), value.as_bytes());
224 }
225
226 let req_body = req.into_body().into_data_stream();
228 req_builder = req_builder.body(wreq::Body::wrap_stream(req_body));
229
230 match req_builder.send().await {
231 Ok(resp) => {
232 if debug_mode {
233 log::debug!("[HTTP] {method} {hyper_uri} -> {}", resp.status());
234 }
235 Ok(upstream_response(resp))
236 }
237 Err(e) => {
238 if debug_mode {
239 log::debug!("[HTTP ERROR] {method} {hyper_uri} -> {e:?}");
240 }
241 Ok(empty_response(StatusCode::BAD_GATEWAY))
242 }
243 }
244 }
245 }
246
247 async fn handle_tunnel(
248 upgraded: Upgraded,
249 target_host: String,
250 client: SharedClient,
251 token: CancellationToken,
252 debug_mode: bool,
253 ) -> Result<(), Error> {
254 let subject_alt_names = vec![target_host.clone()];
255
256 let CertifiedKey { cert, signing_key } =
260 tokio::task::spawn_blocking(move || generate_simple_self_signed(subject_alt_names))
261 .await
262 .map_err(|e| Error::JoinError(format!("Join error: {e}")))?
263 .map_err(|e| Error::TlsError(format!("self-signed cert generation failed: {e}")))?;
264
265 let cert_der = cert.der().to_vec();
266 let key_der = signing_key.serialize_der();
267
268 let single_cert = CertificateDer::from(cert_der);
269 let private_key = PrivatePkcs8KeyDer::from(key_der).into();
270
271 let mut config = ServerConfig::builder()
272 .with_no_client_auth()
273 .with_single_cert(vec![single_cert], private_key)
274 .map_err(|e| Error::TlsError(format!("TLS config error: {}", e)))?;
275
276 config.alpn_protocols = vec![b"http/1.1".to_vec()];
277
278 let acceptor = TlsAcceptor::from(Arc::new(config));
279
280 let io = TokioIo::new(upgraded);
281 let tls_stream = acceptor
282 .accept(io)
283 .await
284 .map_err(|e| Error::TlsError(format!("TLS Accept error: {}", e)))?;
285
286 let tls_io = TokioIo::new(tls_stream);
287 let conn_token = token.clone();
288
289 let conn = http1::Builder::new()
290 .preserve_header_case(true)
291 .title_case_headers(true)
292 .serve_connection(
293 tls_io,
294 service_fn(move |inner_req| {
295 let host = target_host.clone();
296 let client_ref = Arc::clone(&client);
297 async move {
298 Self::forward_tls_request(inner_req, host, client_ref, debug_mode).await
299 }
300 }),
301 );
302
303 tokio::pin!(conn);
304
305 tokio::select! {
306 res = &mut conn => {
307 if let Err(err) = res {
308 let err_str = format!("{err:?}");
311 if !err_str.contains("Parse(Method)") && !err_str.contains("IncompleteMessage") {
312 log::warn!("[TLS] connection drop: {err:?}");
313 }
314 }
315 }
316 _ = conn_token.cancelled() => {
317 conn.as_mut().graceful_shutdown();
318 }
319 }
320
321 Ok(())
322 }
323
324 async fn forward_tls_request(
325 req: Request<Incoming>,
326 host: String,
327 client: SharedClient,
328 debug_mode: bool,
329 ) -> Result<Response<wreq::Body>, std::convert::Infallible> {
330 let uri = format!(
331 "https://{}{}",
332 host,
333 req.uri()
334 .path_and_query()
335 .map(|x| x.as_str())
336 .unwrap_or("/")
337 );
338
339 let method = req.method().clone();
340
341 let Ok(wreq_method) = wreq::Method::from_bytes(method.as_str().as_bytes()) else {
343 return Ok(empty_response(StatusCode::BAD_REQUEST));
344 };
345
346 let client = client.read().expect("client_slot lock poisoned").clone();
348
349 let mut req_builder = client.request(wreq_method, uri.clone());
350
351 for (key, value) in req.headers() {
352 if key != "host" {
353 req_builder = req_builder.header(key.as_str(), value.as_bytes());
354 }
355 }
356
357 let req_body = req.into_body().into_data_stream();
358 req_builder = req_builder.body(wreq::Body::wrap_stream(req_body));
359
360 match req_builder.send().await {
361 Ok(resp) => {
362 if debug_mode {
363 log::debug!("[TLS] {method} {uri} -> {}", resp.status());
364 }
365 Ok(upstream_response(resp))
366 }
367 Err(e) => {
368 if debug_mode {
369 log::debug!("[TLS ERROR] {method} {uri} -> {e:?}");
370 }
371 Ok(empty_response(StatusCode::BAD_GATEWAY))
372 }
373 }
374 }
375}
376
377#[cfg(test)]
378mod tests {
379 use super::*;
380
381 #[tokio::test]
382 async fn test_proxy_initialization() {
383 let client = wreq::Client::builder()
384 .build()
385 .expect("Failed to build client");
386
387 let proxy = TlsSpoofingProxy::start(client, false)
388 .await
389 .expect("Failed to start proxy");
390
391 assert!(proxy.port() > 0);
393
394 drop(proxy);
396 }
397
398 #[tokio::test]
399 async fn test_proxy_http_and_https_forwarding() {
400 let _ = tokio_rustls::rustls::crypto::ring::default_provider().install_default();
403
404 let client = wreq::Client::builder()
405 .build()
406 .expect("Failed to build client");
407
408 let proxy = TlsSpoofingProxy::start(client, false)
409 .await
410 .expect("Failed to start proxy");
411
412 let port = proxy.port();
413
414 let req_client = reqwest::Client::builder()
416 .proxy(reqwest::Proxy::all(format!("http://127.0.0.1:{}", port)).unwrap())
417 .danger_accept_invalid_certs(true) .build()
419 .unwrap();
420
421 let http_resp = req_client.get("http://example.com").send().await;
423 assert!(http_resp.is_ok());
424 let http_status = http_resp.unwrap().status();
425 assert!(http_status.is_success() || http_status.is_redirection());
426
427 let https_resp = req_client.get("https://example.com").send().await;
429 assert!(https_resp.is_ok());
430 let https_status = https_resp.unwrap().status();
431
432 assert!(
434 https_status.is_success()
435 || https_status.is_redirection()
436 || https_status.as_u16() == 502
437 );
438 }
439
440 #[tokio::test]
441 async fn test_proxy_shutdown() {
442 let client = wreq::Client::builder().build().unwrap();
443 let proxy = TlsSpoofingProxy::start(client, false).await.unwrap();
444 let port = proxy.port();
445
446 let req_client = reqwest::Client::builder()
447 .proxy(reqwest::Proxy::all(format!("http://127.0.0.1:{}", port)).unwrap())
448 .build()
449 .unwrap();
450
451 drop(proxy);
453
454 tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
456
457 let res = req_client.get("http://example.com").send().await;
459 assert!(res.is_err(), "Request succeeded but proxy should be down");
460 }
461
462 #[tokio::test]
463 async fn test_proxy_502_error_flow() {
464 let mut server = mockito::Server::new_async().await;
466
467 let _mock = server
469 .mock("GET", "/")
470 .with_status(500)
471 .create_async()
472 .await;
473
474 let client = wreq::Client::builder().build().unwrap();
475 let proxy = TlsSpoofingProxy::start(client, false).await.unwrap();
476 let port = proxy.port();
477
478 let req_client = reqwest::Client::builder()
479 .proxy(reqwest::Proxy::all(format!("http://127.0.0.1:{}", port)).unwrap())
480 .build()
481 .unwrap();
482
483 let url = server.url();
485 let res = req_client.get(&url).send().await.unwrap();
486
487 assert_eq!(res.status().as_u16(), 500);
489
490 let bad_url = format!("http://127.0.0.1:{}", server.url().len()); let res2 = req_client.get(&bad_url).send().await;
493
494 if let Ok(response) = res2 {
496 assert_eq!(response.status().as_u16(), 502);
497 }
498 }
499}