hudsucker

repository·main·Indexed 18 days ago

https://github.com/omjadas/hudsucker

A MITM HTTP/S proxy written in Rust for intercepting and modifying HTTP/S requests, responses, and WebSocket messages. It features a type-state ProxyBuilder for configuration, support for multiple CertificateAuthority implementations (OpenSSL and rcgen), and helper functions for decoding compressed request and response bodies.

Tokens
6.5K
Snippets
17
Records
27
Agent score
62%

What's inside hudsucker

  1. Overview of Hudsucker capabilities

    main

    Hudsucker is a MITM (Man-in-the-Middle) HTTP/S proxy written in Rust. It is designed to intercept and manipulate network traffic, specifically allowing you to:

    • Modify HTTP/S requests
    • Modify HTTP/S responses
    • Modify WebSocket messages
  2. Get started with Hudsucker usage

    main
    To learn how to implement the proxy, refer to the official examples provided in the repository. These examples demonstrate how to set up the proxy and implement request/response modification logic.
  3. Use HttpContext and WebSocketContext for metadata

    main

    When implementing handlers, Hudsucker provides context objects containing metadata about the connection:

    • HttpContext: Contains the client_addr (the SocketAddr of the client).
    • WebSocketContext: An enum representing the direction of the message:
      • ClientToServer: Contains the client's src address and the server's dst URI.
      • ServerToClient: Contains the server's src URI and the client's dst address.
  4. Use OpenSSL or rcgen for Certificate Authority

    main

    Hudsucker provides two primary implementations for the CertificateAuthority trait, controlled via Cargo features:

    • openssl-ca: Uses OpenSSL to manage and issue certificates.
    • rcgen-ca: Uses the rcgen crate to generate certificates.

    Depending on which feature is enabled in your Cargo.toml, you can use the corresponding authority modules.

  5. Configure and build a Proxy using ProxyBuilder

    main

    The ProxyBuilder uses a type-state pattern to ensure a Proxy is configured correctly before being built. To create a proxy, you must provide an address (or a TcpListener), a CertificateAuthority, and a connector. You can then optionally customize handlers for HTTP and WebSockets, the client/server builders, and a graceful shutdown future.

    Required Steps:

    1. Call Proxy::builder() or ProxyBuilder::new().
    2. Provide an address via .with_addr(SocketAddr) or a listener via .with_listener(TcpListener).
    3. Provide a certificate authority via .with_ca(CA).
    4. Provide a connector via .with_rustls_connector(provider), .with_native_tls_connector(), or .with_http_connector(connector).
    5. Call .build() to consume the builder and return a Result<Proxy, Error>.
    let proxy = Proxy::builder()
        .with_addr(std::net::SocketAddr::from(([127, 0, 0, 1], 0)))
        .with_ca(ca)
        .with_rustls_connector(aws_lc_rs::default_provider())
        .build()
        .expect("Failed to create proxy");
  6. Initialize and start a Proxy server

    main

    A Proxy server must be constructed using the ProxyBuilder. Once configured, you can call .start() to begin listening for connections. The start method is an async function that runs a loop to accept incoming TCP connections and handles them using the provided HTTP and WebSocket handlers.

    To run the proxy, it is common to tokio::spawn the start() future so it can run in the background while your main application logic continues.

    use hudsucker::Proxy;
    // ... imports for CA and connectors ...
    
    let (stop, done) = tokio::sync::oneshot::channel();
    
    let proxy = Proxy::builder()
        .with_addr(std::net::SocketAddr::from(([127, 0, 0, 1], 0)))
        .with_ca(ca)
        .with_rustls_connector(aws_lc_rs::default_provider())
        .with_graceful_shutdown(async {
            done.await.unwrap_or_default();
        })
        .build()
        .expect("Failed to create proxy");
    
    tokio::spawn(proxy.start());
    
    // To trigger shutdown:
    // stop.send(()).unwrap();
  7. Configure Hudsucker features

    main

    Hudsucker uses Cargo features to enable specific capabilities. You can select these in your Cargo.toml:

    • decoder: Enables decode_request and decode_response helpers (enabled by default).
    • full: Enables all features.
    • http2: Enables HTTP/2 support.
    • native-tls-client: Enables ProxyBuilder::with_native_tls_connector.
    • openssl-ca: Enables certificate_authority::OpensslAuthority.
    • rcgen-ca: Enables certificate_authority::RcgenAuthority (enabled by default).
    • rustls-client: Enables ProxyBuilder::with_rustls_connector (enabled by default).
  8. Decode a response body with `decode_response`

    main

    Use decode_response to automatically decompress the body of a hyper::Response<Body> based on the Content-Encoding header. This function handles multiple encodings in sequence, removes the Content-Encoding and Content-Length headers from the response, and returns a Result.

    Supported encodings:

    • gzip / x-gzip
    • deflate (Zlib)
    • br (Brotli)
    • zstd
    • identity (no-op)

    Errors

    Returns an error if an unsupported encoding is specified in the headers.

    use hudsucker::{Body, HttpContext, HttpHandler, decode_response, hyper::Response};
    
    #[derive(Clone)]
    pub struct MyHandler;
    
    impl HttpHandler for MyHandler {
        async fn handle_response(
            &mut self, 
            _ctx: &HttpContext, 
            res: Response<Body>
        ) -> Response<Body> {
            // Automatically decodes the body if Content-Encoding is present
            let res = decode_response(res).unwrap();
    
            // Do something with the decoded response
    
            res
        }
    }
  9. Configure HTTP and WebSocket handlers

    main

    Once the core proxy components are configured, you can inject custom logic for handling traffic using handlers.

    • with_http_handler<H2: HttpHandler>(http_handler: H2): Sets the logic for processing HTTP requests/responses.
    • with_websocket_handler<W2: WebSocketHandler>(websocket_handler: W2): Sets the logic for processing WebSocket connections.
    • with_websocket_connector(connector: Connector): Sets a custom connector specifically for WebSocket traffic.
  10. Initialize an OpensslAuthority

    main

    Use OpensslAuthority::new to create a certificate authority that issues TLS certificates for communicating with clients. This implementation uses the openssl crate for certificate generation and supports in-memory caching of generated ServerConfig objects to improve performance.

    To initialize it, you need to provide a private key, a CA certificate, a hash algorithm (e.g., MessageDigest::sha256()), a maximum cache size, and a CryptoProvider (such as aws_lc_rs::default_provider()).

    use hudsucker::;
    use hudsucker::certificate_authority::OpensslAuthority;
    use hudsucker::openssl::{hash::MessageDigest, pkey::PKey, x509::X509};
    use hudsucker::rustls::crypto::aws_lc_rs;
    
    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).unwrap();
    let ca_cert = X509::from_pem(ca_cert_bytes).unwrap();
    
    let ca = OpensslAuthority::new(
        private_key,
        ca_cert,
        MessageDigest::sha256(),
        1_000, // cache_size
        aws_lc_rs::default_provider(),
    );
  11. Set the listening address or listener

    main

    Use these methods to define where the proxy server will listen for incoming connections.

    • with_addr(addr: SocketAddr): Sets the specific IP and port to listen on.
    • with_listener(listener: TcpListener): Uses an existing TcpListener to manage the connection acceptance.
    // Using an address
    builder.with_addr(std::net::SocketAddr::from(([127, 0, 0, 1], 8080)));
    
    // Using a listener
    let listener = tokio::net::TcpListener::bind("127.0.0.1:8080").await.unwrap();
    builder.with_listener(listener);
  12. Initialize an RcgenAuthority for TLS certificates

    main

    Use RcgenAuthority to issue certificates for communicating with clients over TLS. It uses the rcgen crate for certificate generation and caches the resulting ServerConfig in memory to improve performance.

    To create a new instance, you must provide an Issuer (constructed from your CA certificate and private key), a cache_size (maximum number of authorities to cache), and a CryptoProvider (such as aws_lc_rs::default_provider()).

    use hudsucker::{certificate_authority::RcgenAuthority, rustls::crypto::aws_lc_rs};
    use rcgen::{Issuer, KeyPair};
    
    // Load your CA credentials
    let key_pair_pem = include_str!("../../examples/ca/hudsucker.key");
    let ca_cert_pem = include_str!("../../examples/ca/hudsucker.cer");
    
    let key_pair = KeyPair::from_pem(key_pair_pem).expect("Failed to parse private key");
    let issuer = Issuer::from_ca_cert_pem(ca_cert_pem, key_pair).expect("Failed to parse CA certificate");
    
    // Create the authority with a cache size of 1,000 and the default AWS LC crypto provider
    let ca = RcgenAuthority::new(issuer, 1_000, aws_lc_rs::default_provider());