A HTTP proxy server library intended to be a backend of application like Burp proxy
MIT License
A HTTP proxy server library intended to be a backend of application like Burp proxy.
use std::path::PathBuf;
use clap::{Args, Parser};
use futures::StreamExt;
use http_mitm_proxy::{default_client::Upgrade, DefaultClient, MitmProxy};
use moka::sync::Cache;
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())
.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 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 client = DefaultClient::new(
tokio_native_tls::native_tls::TlsConnector::builder()
// You must set ALPN if you want to support HTTP/2
.request_alpns(&["h2", "http/1.1"])
.build()
.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());
if let Some(upgrade) = upgrade {
// If the response is an upgrade, e.g. Websocket, you can see traffic.
// Modifying upgraded traffic is not supported yet.
// You can try https://echo.websocket.org/.ws to test websocket.
// But you need to disable alpn of DefaultClient to disable HTTP2 because echo.websocket.org does not support HTTP/2 for Websocket.
// It should be match incoming and outgoing HTTP version on DefaultClient, I'll fix this later. #54
println!("Upgrade connection");
let Upgrade {
mut client_to_server,
mut server_to_client,
} = upgrade;
let url = uri.to_string();
tokio::spawn(async move {
while let Some(data) = client_to_server.next().await {
println!("Client -> Server: {} {:?}", url, data);
}
});
let url = uri.to_string();
tokio::spawn(async move {
while let Some(data) = server_to_client.next().await {
println!("Server -> Client: {} {:?}", url, data);
}
});
}
// 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;
}