hudsucker 0.25.0

MITM HTTP/S proxy
Documentation
use hudsucker::{
    certificate_authority::OpensslAuthority,
    openssl::{hash::MessageDigest, pkey::PKey, x509::X509},
    rustls::crypto::aws_lc_rs,
};
use std::sync::atomic::Ordering;

mod common;

fn build_ca() -> OpensslAuthority {
    let private_key_bytes: &[u8] = include_bytes!("../examples/ca/hudsucker.key");
    let ca_cert_bytes: &[u8] = include_bytes!("../examples/ca/hudsucker.cer");
    let private_key =
        PKey::private_key_from_pem(private_key_bytes).expect("Failed to parse private key");
    let ca_cert = X509::from_pem(ca_cert_bytes).expect("Failed to parse CA certificate");

    OpensslAuthority::new(
        private_key,
        ca_cert,
        MessageDigest::sha256(),
        1_000,
        aws_lc_rs::default_provider(),
    )
}

#[tokio::test]
async fn https_rustls() {
    let (proxy_addr, handler, stop_proxy) = common::start_proxy(
        build_ca(),
        common::rustls_http_connector(),
        common::rustls_websocket_connector(),
    )
    .await
    .unwrap();

    let (server_addr, stop_server) = common::start_https_server(build_ca()).await.unwrap();
    let client = common::build_client(&proxy_addr.to_string());

    let res = client
        .get(format!("https://localhost:{}/hello", server_addr.port()))
        .send()
        .await
        .unwrap();

    assert_eq!(res.status(), 200);
    assert_eq!(handler.request_counter.load(Ordering::Relaxed), 2);
    assert_eq!(handler.response_counter.load(Ordering::Relaxed), 1);

    stop_server.send(()).unwrap();
    stop_proxy.send(()).unwrap();
}

#[tokio::test]
async fn https_native_tls() {
    let (proxy_addr, handler, stop_proxy) = common::start_proxy(
        build_ca(),
        common::native_tls_http_connector(),
        common::native_tls_websocket_connector(),
    )
    .await
    .unwrap();

    let (server_addr, stop_server) = common::start_https_server(build_ca()).await.unwrap();
    let client = common::build_client(&proxy_addr.to_string());

    let res = client
        .get(format!("https://localhost:{}/hello", server_addr.port()))
        .send()
        .await
        .unwrap();

    assert_eq!(res.status(), 200);
    assert_eq!(handler.request_counter.load(Ordering::Relaxed), 2);
    assert_eq!(handler.response_counter.load(Ordering::Relaxed), 1);

    stop_server.send(()).unwrap();
    stop_proxy.send(()).unwrap();
}

#[tokio::test]
async fn without_connect_intercept() {
    let (proxy_addr, handler, stop_proxy) = common::start_proxy_without_connect_intercept(
        build_ca(),
        common::http_connector(),
        common::plain_websocket_connector(),
    )
    .await
    .unwrap();

    let (server_addr, stop_server) = common::start_https_server(build_ca()).await.unwrap();
    let client = common::build_client(&proxy_addr.to_string());

    let res = client
        .get(format!("https://localhost:{}/hello", server_addr.port()))
        .send()
        .await
        .unwrap();

    assert_eq!(res.status(), 200);
    assert_eq!(handler.request_counter.load(Ordering::Relaxed), 1);
    assert_eq!(handler.response_counter.load(Ordering::Relaxed), 0);

    stop_server.send(()).unwrap();
    stop_proxy.send(()).unwrap();
}

#[tokio::test]
async fn without_intercept_tls() {
    let (proxy_addr, handler, stop_proxy) = common::start_proxy_without_tls_intercept(
        build_ca(),
        common::http_connector(),
        common::plain_websocket_connector(),
    )
    .await
    .unwrap();

    let (server_addr, stop_server) = common::start_https_server(build_ca()).await.unwrap();
    let client = common::build_client(&proxy_addr.to_string());

    let res = client
        .get(format!("https://localhost:{}/hello", server_addr.port()))
        .send()
        .await
        .unwrap();

    assert_eq!(res.status(), 200);
    assert_eq!(handler.request_counter.load(Ordering::Relaxed), 1);
    assert_eq!(handler.response_counter.load(Ordering::Relaxed), 0);

    stop_server.send(()).unwrap();
    stop_proxy.send(()).unwrap();
}

#[tokio::test]
async fn decodes_response() {
    let (proxy_addr, _, stop_proxy) = common::start_proxy(
        build_ca(),
        common::native_tls_http_connector(),
        common::native_tls_websocket_connector(),
    )
    .await
    .unwrap();

    let (server_addr, stop_server) = common::start_http_server().await.unwrap();
    let client = common::build_client(&proxy_addr.to_string());

    let res = client
        .get(format!("http://{}/hello/gzip", server_addr))
        .send()
        .await
        .unwrap();

    assert_eq!(res.status(), 200);
    assert_eq!(res.bytes().await.unwrap(), common::HELLO_WORLD);

    stop_server.send(()).unwrap();
    stop_proxy.send(()).unwrap();
}

#[tokio::test]
async fn noop() {
    let (proxy_addr, stop_proxy) = common::start_noop_proxy(build_ca()).await.unwrap();
    let (server_addr, stop_server) = common::start_http_server().await.unwrap();
    let client = common::build_client(&proxy_addr.to_string());

    let res = client
        .get(format!("http://{}/hello", server_addr))
        .send()
        .await
        .unwrap();

    assert_eq!(res.status(), 200);
    assert_eq!(res.bytes().await.unwrap(), common::HELLO_WORLD);

    stop_server.send(()).unwrap();
    stop_proxy.send(()).unwrap();
}