From cb225d5918f8b7af823327a5b2c347ac71a16f57 Mon Sep 17 00:00:00 2001 From: hatoo Date: Sun, 3 Nov 2024 22:04:17 +0900 Subject: [PATCH 1/3] https example --- examples/https.rs | 183 ++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 2 +- 2 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 examples/https.rs diff --git a/examples/https.rs b/examples/https.rs new file mode 100644 index 0000000..08aef96 --- /dev/null +++ b/examples/https.rs @@ -0,0 +1,183 @@ +use std::{path::PathBuf, sync::Arc}; + +use clap::{Args, Parser}; +use http_mitm_proxy::{DefaultClient, MitmProxy}; +use hyper::service::service_fn; +use hyper_util::rt::TokioIo; +use moka::sync::Cache; +use rustls::{pki_types::PrivatePkcs8KeyDer, ServerConfig}; +use tokio::net::TcpListener; +use tokio_rustls::TlsAcceptor; +use tracing_subscriber::EnvFilter; + +#[derive(Parser)] +struct Opt { + #[clap(flatten)] + external_cert: Option, +} + +#[derive(Args, Debug)] +struct ExternalCert { + #[arg(required = false)] + cert: PathBuf, + #[arg(required = false)] + private_key: PathBuf, +} + +fn make_root_cert() -> rcgen::CertifiedKey { + let mut param = rcgen::CertificateParams::default(); + + param.distinguished_name = rcgen::DistinguishedName::new(); + param.distinguished_name.push( + rcgen::DnType::CommonName, + rcgen::DnValue::Utf8String("".to_string()), + ); + param.key_usages = vec![ + rcgen::KeyUsagePurpose::KeyCertSign, + rcgen::KeyUsagePurpose::CrlSign, + ]; + param.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained); + + let key_pair = rcgen::KeyPair::generate().unwrap(); + let cert = param.self_signed(&key_pair).unwrap(); + + rcgen::CertifiedKey { cert, key_pair } +} + +#[tokio::main] +async fn main() { + let opt = Opt::parse(); + + tracing_subscriber::fmt() + .with_env_filter(EnvFilter::from_default_env()) + .with_line_number(true) + .init(); + + let root_cert = if let Some(external_cert) = opt.external_cert { + // Use existing key + let param = rcgen::CertificateParams::from_ca_cert_pem( + &std::fs::read_to_string(&external_cert.cert).unwrap(), + ) + .unwrap(); + let key_pair = + rcgen::KeyPair::from_pem(&std::fs::read_to_string(&external_cert.private_key).unwrap()) + .unwrap(); + + let cert = param.self_signed(&key_pair).unwrap(); + + rcgen::CertifiedKey { cert, key_pair } + } else { + make_root_cert() + }; + + let root_cert_pem = root_cert.cert.pem(); + let root_cert_key = root_cert.key_pair.serialize_pem(); + + let mut server_config = ServerConfig::builder() + .with_no_client_auth() + .with_single_cert( + vec![root_cert.cert.der().clone()], + rustls::pki_types::PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from( + root_cert.key_pair.serialize_der(), + )), + ) + .unwrap(); + server_config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec(), b"http/1.0".to_vec()]; + + let proxy = MitmProxy::new( + // This is the root cert that will be used to sign the fake certificates + Some(root_cert), + Some(Cache::new(128)), + ); + let proxy = Arc::new(proxy); + + let client = DefaultClient::new().unwrap(); + + let listener = TcpListener::bind(("127.0.0.1", 3003)).await.unwrap(); + + let tls_acceptor = TlsAcceptor::from(Arc::new(server_config)); + + let server = async move { + loop { + let (stream, client_addr) = listener.accept().await.unwrap(); + let proxy = proxy.clone(); + let client = client.clone(); + let tls_acceptor = tls_acceptor.clone(); + + tokio::spawn(async move { + let service = service_fn(move |req| { + let proxy = proxy.clone(); + let client = client.clone(); + + MitmProxy::proxy(proxy.clone(), client_addr, req, move |_client_addr, req| { + let client = client.clone(); + async move { + let uri = req.uri().clone(); + + // You can modify request here + // or You can just return response anywhere + + let (res, _upgrade) = client.send_request(req).await?; + + println!("{} -> {}", uri, res.status()); + + // You can modify response here + + Ok::<_, http_mitm_proxy::default_client::Error>(res) + } + }) + }); + + let stream = tls_acceptor.accept(stream).await.unwrap(); + hyper::server::conn::http1::Builder::new() + .preserve_header_case(true) + .title_case_headers(true) + .serve_connection(TokioIo::new(stream), service) + .with_upgrades() + .await + .unwrap(); + }); + } + }; + + /* + let server = proxy + .bind(("127.0.0.1", 3003), move |_client_addr, req| { + let client = client.clone(); + async move { + let uri = req.uri().clone(); + + // You can modify request here + // or You can just return response anywhere + + let (res, _upgrade) = client.send_request(req).await?; + + println!("{} -> {}", uri, res.status()); + + // You can modify response here + + Ok::<_, http_mitm_proxy::default_client::Error>(res) + } + }) + .await + .unwrap(); + */ + + println!("HTTP Proxy is listening on http://127.0.0.1:3003"); + + println!(); + println!("Trust this cert if you want to use HTTPS"); + println!(); + println!("{}", root_cert_pem); + println!(); + + /* + Save this cert to ca.crt and use it with curl like this: + curl https://www.google.com -x http://127.0.0.1:3003 --cacert ca.crt + */ + + println!("Private key"); + println!("{}", root_cert_key); + + server.await; +} diff --git a/src/lib.rs b/src/lib.rs index 238706d..0e9a055 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -98,7 +98,7 @@ impl + Send + Sync + 'static> MitmProxy { }) } - async fn proxy( + pub async fn proxy( proxy: Arc>, client_addr: SocketAddr, req: Request, From b24296a38428e59dab9b9acbf91878d8541915c1 Mon Sep 17 00:00:00 2001 From: hatoo Date: Sun, 3 Nov 2024 22:14:01 +0900 Subject: [PATCH 2/3] wip --- examples/https.rs | 59 +++++++++++++++++------------------------------ src/lib.rs | 14 +++++++---- 2 files changed, 31 insertions(+), 42 deletions(-) diff --git a/examples/https.rs b/examples/https.rs index 08aef96..0c806d2 100644 --- a/examples/https.rs +++ b/examples/https.rs @@ -73,6 +73,7 @@ async fn main() { let root_cert_pem = root_cert.cert.pem(); let root_cert_key = root_cert.key_pair.serialize_pem(); + // Reusing the same root cert for proxy server let mut server_config = ServerConfig::builder() .with_no_client_auth() .with_single_cert( @@ -109,23 +110,28 @@ async fn main() { let proxy = proxy.clone(); let client = client.clone(); - MitmProxy::proxy(proxy.clone(), client_addr, req, move |_client_addr, req| { - let client = client.clone(); - async move { - let uri = req.uri().clone(); + MitmProxy::hyper_service( + proxy.clone(), + client_addr, + req, + move |_client_addr, req| { + let client = client.clone(); + async move { + let uri = req.uri().clone(); - // You can modify request here - // or You can just return response anywhere + // You can modify request here + // or You can just return response anywhere - let (res, _upgrade) = client.send_request(req).await?; + let (res, _upgrade) = client.send_request(req).await?; - println!("{} -> {}", uri, res.status()); + println!("{} -> {}", uri, res.status()); - // You can modify response here + // You can modify response here - Ok::<_, http_mitm_proxy::default_client::Error>(res) - } - }) + Ok::<_, http_mitm_proxy::default_client::Error>(res) + } + }, + ) }); let stream = tls_acceptor.accept(stream).await.unwrap(); @@ -140,30 +146,7 @@ async fn main() { } }; - /* - let server = proxy - .bind(("127.0.0.1", 3003), move |_client_addr, req| { - let client = client.clone(); - async move { - let uri = req.uri().clone(); - - // You can modify request here - // or You can just return response anywhere - - let (res, _upgrade) = client.send_request(req).await?; - - println!("{} -> {}", uri, res.status()); - - // You can modify response here - - Ok::<_, http_mitm_proxy::default_client::Error>(res) - } - }) - .await - .unwrap(); - */ - - println!("HTTP Proxy is listening on http://127.0.0.1:3003"); + println!("HTTPS Proxy is listening on https://127.0.0.1:3003"); println!(); println!("Trust this cert if you want to use HTTPS"); @@ -172,8 +155,8 @@ async fn main() { println!(); /* - Save this cert to ca.crt and use it with curl like this: - curl https://www.google.com -x http://127.0.0.1:3003 --cacert ca.crt + You can test HTTPS proxy with curl like this: + curl -x https://localhost:3003 https://example.com --insecure --proxy-insecure */ println!("Private key"); diff --git a/src/lib.rs b/src/lib.rs index 0e9a055..ac16d4d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -85,7 +85,12 @@ impl + Send + Sync + 'static> MitmProxy { .serve_connection( TokioIo::new(stream), service_fn(|req| { - Self::proxy(proxy.clone(), client_addr, req, service.clone()) + Self::hyper_service( + proxy.clone(), + client_addr, + req, + service.clone(), + ) }), ) .with_upgrades() @@ -98,7 +103,7 @@ impl + Send + Sync + 'static> MitmProxy { }) } - pub async fn proxy( + pub async fn hyper_service( proxy: Arc>, client_addr: SocketAddr, req: Request, @@ -178,8 +183,9 @@ impl + Send + Sync + 'static> MitmProxy { .await }; - if let Err(err) = res { - tracing::error!("Error in proxy: {}", err); + if let Err(_err) = res { + // Suppress error because if we serving HTTPS proxy server and forward to HTTPS server, it will always error when closing connection. + // tracing::error!("Error in proxy: {}", err); } } else { let Ok(mut server) = TcpStream::connect(connect_authority.as_str()).await From b87d80bb9b159e2fa21b7d7a0767d129721dbc2c Mon Sep 17 00:00:00 2001 From: hatoo Date: Sun, 3 Nov 2024 22:19:44 +0900 Subject: [PATCH 3/3] doc --- src/lib.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index ac16d4d..b34cb19 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -103,8 +103,12 @@ impl + Send + Sync + 'static> MitmProxy { }) } + /// A service that can be used with hyper server. + /// See `examples/https.rs` for usage. + /// If you want to serve simple HTTP proxy server, you can use `bind` method instead. + /// `bind` will call this method internally. pub async fn hyper_service( - proxy: Arc>, + proxy: Arc, client_addr: SocketAddr, req: Request, service: S,