Skip to content

Commit

Permalink
feat: selective MITM based on SNI
Browse files Browse the repository at this point in the history
  • Loading branch information
zu1k committed Jul 21, 2023
1 parent 9f385e3 commit e0ad482
Show file tree
Hide file tree
Showing 6 changed files with 446 additions and 9 deletions.
28 changes: 28 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions crates/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ license = "MIT"
[dependencies]
async-trait = "0.1"
bytes = { version = "1", features = ["serde"] }
byteorder = "1.4"
cfg-if = "1"
http = "0.2"
hyper = { version = "0.14", features = ["http1", "http2", "server", "stream", "tcp", "runtime"] }
Expand All @@ -19,6 +20,7 @@ hyper-tls = { version = "0.5", optional = true }
log = "0.4"
moka = { version = "0.11", features = ["future"] }
openssl = { version = "0.10", features = ["vendored"], optional = true }
pin-project = "1"
rcgen = { version = "0.10", features = ["x509-parser"] }
serde = { version = "1.0", features = ["derive"] }
thiserror = "1"
Expand Down
12 changes: 11 additions & 1 deletion crates/core/src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ impl<D: CustomContextData> MitmFilter<D> {
}
}

pub async fn filter(&self, _ctx: &HttpContext<D>, req: &Request<Body>) -> bool {
pub async fn filter_req(&self, _ctx: &HttpContext<D>, req: &Request<Body>) -> bool {
let host = req.uri().host().unwrap_or_default();
let list = self.filters.read().unwrap();
for m in list.iter() {
Expand All @@ -55,4 +55,14 @@ impl<D: CustomContextData> MitmFilter<D> {
}
false
}

pub async fn filter(&self, host: &str) -> bool {
let list = self.filters.read().unwrap();
for m in list.iter() {
if m.matches(host) {
return true;
}
}
false
}
}
1 change: 1 addition & 0 deletions crates/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ mod error;
pub mod handler;
mod http_client;
pub mod mitm;
mod sni_reader;

#[derive(TypedBuilder)]
pub struct Proxy<F, H, D>
Expand Down
47 changes: 39 additions & 8 deletions crates/core/src/mitm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,21 @@ use crate::{
ca::CertificateAuthority,
handler::{CustomContextData, HttpHandler, MitmFilter},
http_client::HttpClient,
sni_reader::{
read_sni_host_name_from_client_hello, HandshakeRecordReader, PrefixedReaderWriter,
RecordingBufReader,
},
};
use http::{header, uri::Scheme, HeaderValue, Uri};
use hyper::{
body::HttpBody, server::conn::Http, service::service_fn, upgrade::Upgraded, Body, Method,
Request, Response,
body::HttpBody, server::conn::Http, service::service_fn, Body, Method, Request, Response,
};
use log::*;
use std::{marker::PhantomData, sync::Arc};
use std::{marker::PhantomData, sync::Arc, time::Duration};
use tokio::{
io::{AsyncRead, AsyncWrite},
net::TcpStream,
pin,
};
use tokio_rustls::TlsAcceptor;

Expand Down Expand Up @@ -150,7 +154,7 @@ where
..Default::default()
};

if self.mitm_filter.filter(&ctx, &req).await {
if self.mitm_filter.filter_req(&ctx, &req).await {
tokio::task::spawn(async move {
let authority = req
.uri()
Expand All @@ -175,10 +179,34 @@ where
Ok(Response::new(Body::empty()))
}

pub async fn serve_tls<IO: AsyncRead + AsyncWrite + Unpin + Send + 'static>(self, stream: IO) {
pub async fn serve_tls<IO: AsyncRead + AsyncWrite + Unpin + Send + 'static>(
self,
mut stream: IO,
) {
// Read SNI hostname.
let mut recording_reader = RecordingBufReader::new(&mut stream);
let reader = HandshakeRecordReader::new(&mut recording_reader);
pin!(reader);
let sni_hostname = tokio::time::timeout(
Duration::from_secs(5),
read_sni_host_name_from_client_hello(reader),
)
.await
.unwrap()
.unwrap();

let read_buf = recording_reader.buf();
let client_stream = PrefixedReaderWriter::new(stream, read_buf);

if !self.mitm_filter.filter(&sni_hostname).await {
let remote_addr = format!("{sni_hostname}:443");
tokio::task::spawn(async move { tunnel(client_stream, remote_addr).await });
return;
}

let server_config = self.ca.clone().gen_server_config();

match TlsAcceptor::from(server_config).accept(stream).await {
match TlsAcceptor::from(server_config).accept(client_stream).await {
Ok(stream) => {
if let Err(e) = Http::new()
.http1_preserve_header_case(true)
Expand Down Expand Up @@ -239,8 +267,11 @@ fn host_addr(uri: &http::Uri) -> Option<String> {
uri.authority().map(|auth| auth.to_string())
}

async fn tunnel(mut upgraded: Upgraded, addr: String) -> std::io::Result<()> {
async fn tunnel<A>(mut client_stream: A, addr: String) -> std::io::Result<()>
where
A: AsyncRead + AsyncWrite + Unpin,
{
let mut server = TcpStream::connect(addr).await?;
tokio::io::copy_bidirectional(&mut upgraded, &mut server).await?;
tokio::io::copy_bidirectional(&mut client_stream, &mut server).await?;
Ok(())
}
Loading

1 comment on commit e0ad482

@vercel
Copy link

@vercel vercel bot commented on e0ad482 Jul 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

good-mitm – ./

good-mitm-git-master-zu1k.vercel.app
good-mitm-zu1k.vercel.app
good-mitm.vercel.app

Please sign in to comment.