Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Proxy server serving HTTPS #64

Merged
merged 3 commits into from
Nov 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
166 changes: 166 additions & 0 deletions examples/https.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
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<ExternalCert>,
}

#[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("<HTTP-MITM-PROXY CA>".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();

// Reusing the same root cert for proxy server
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::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

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

println!("HTTPS Proxy is listening on https://127.0.0.1:3003");

println!();
println!("Trust this cert if you want to use HTTPS");
println!();
println!("{}", root_cert_pem);
println!();

/*
You can test HTTPS proxy with curl like this:
curl -x https://localhost:3003 https://example.com --insecure --proxy-insecure
*/

println!("Private key");
println!("{}", root_cert_key);

server.await;
}
20 changes: 15 additions & 5 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,12 @@ impl<C: Borrow<rcgen::CertifiedKey> + Send + Sync + 'static> MitmProxy<C> {
.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()
Expand All @@ -98,8 +103,12 @@ impl<C: Borrow<rcgen::CertifiedKey> + Send + Sync + 'static> MitmProxy<C> {
})
}

async fn proxy<S, B, E, E2, F>(
proxy: Arc<MitmProxy<C>>,
/// 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<S, B, E, E2, F>(
proxy: Arc<Self>,
client_addr: SocketAddr,
req: Request<Incoming>,
service: S,
Expand Down Expand Up @@ -178,8 +187,9 @@ impl<C: Borrow<rcgen::CertifiedKey> + Send + Sync + 'static> MitmProxy<C> {
.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
Expand Down
Loading