echw

Crates.ioechw
lib.rsechw
version0.1.0
created_at2025-08-19 03:34:11.130896+00
updated_at2025-08-19 03:34:11.130896+00
descriptionEncrypted Client Hello Wrapper - Library for making HTTP requests with ECH support
homepage
repository
max_upload_size
id1801333
size90,785
(grim3)

documentation

README

ECHW - Encrypted Client Hello Wrapper

A Rust library for making HTTP GET and POST requests with Encrypted Client Hello (ECH) support using rustls and hickory-dns.

Features

Quick Start

Add to your Cargo.toml:

[dependencies]
echw = "0.1.0"
tokio = { version = "1.0", features = ["full"] }
env_logger = "0.11"

Basic GET Request

use echw::EchClient;
use std::error::Error;

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    env_logger::init();

    let client = EchClient::builder(
        "public.test.defo.ie".to_string(),   // outer hostname
        "min-ng.test.defo.ie".to_string(),   // inner hostname (ECH protected)
    )
    .port(443)
    .build();

    let response = client.get("echstat.php?format=json").await?;
    
    println!("Status: {}", response.status);
    println!("ECH Status: {:?}", response.ech_status);
    println!("Body: {}", response.body_as_string()?);
    
    Ok(())
}

POST Request with JSON

use echw::EchClient;
use std::error::Error;

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    env_logger::init();

    let client = EchClient::builder(
        "public.test.defo.ie".to_string(),
        "min-ng.test.defo.ie".to_string(),
    )
    .build();

    let json_data = r#"{"message": "Hello ECH!"}"#;
    let headers = vec![
        ("Content-Type".to_string(), "application/json".to_string()),
    ];

    let response = client.post_with_headers("api/test", json_data, headers).await?;
    
    println!("Response: {}", response.body_as_string()?);
    
    Ok(())
}

API Reference

EchClient

The main client for making ECH-enabled HTTP requests.

Builder Methods

  • EchClient::builder(outer_hostname, inner_hostname) - Create a new client builder
  • .port(port) - Set the port (default: 443)
  • .cloudflare_dns() - Use Cloudflare DNS for ECH config lookup (default: Google DNS)
  • .grease() - Use GREASE ECH for testing
  • .ech_config_file(path) - Load ECH config from file
  • .ca_file(path) - Use custom CA certificate file

Request Methods

  • client.get(path) - Make a GET request
  • client.post(path, body) - Make a POST request
  • client.post_with_headers(path, body, headers) - Make a POST request with custom headers

EchResponse

Response object containing:

  • status: String - HTTP status line
  • headers: Vec<String> - Response headers
  • body: Vec<u8> - Response body as bytes
  • ech_status: EchStatus - ECH negotiation status
  • body_as_string() - Convert body to UTF-8 string

EchStatus

ECH negotiation status:

  • EchStatus::Accepted - ECH was successfully negotiated
  • EchStatus::Grease - GREASE ECH extension was used
  • EchStatus::NotOffered - ECH was not offered

Examples

Run the included examples:

# Simple GET request
cargo run --example simple_get

# JSON POST request  
cargo run --example json_post

ECH (Encrypted Client Hello)

ECH is a TLS extension that encrypts the Server Name Indication (SNI) and other identifying parts of the TLS Client Hello message. This prevents network observers from seeing which specific server within a host the client is trying to reach.

How it works

  1. The client looks up ECH configuration for the target domain using DNS-over-HTTPS (using hickory-dns).
  2. The client connects to the "outer" server but encrypts the real "inner" server name using ECH (using rustls).
  3. The outer server forwards the connection to the inner server after decrypting the ECH extension
  4. The TLS handshake completes with the inner server while protecting the SNI from network observation

Testing

The library includes support for ECH GREASE extensions for testing environments where real ECH configs are not available.

License

MIT License - see LICENSE file for details.

Commit count: 0

cargo fmt