diff --git a/crates/hyper-custom-cert/Cargo.toml b/crates/hyper-custom-cert/Cargo.toml index 72bae1f..3e7846e 100644 --- a/crates/hyper-custom-cert/Cargo.toml +++ b/crates/hyper-custom-cert/Cargo.toml @@ -1,13 +1,13 @@ [package] name = "hyper-custom-cert" -version = "0.3.0" +version = "0.3.1" edition = "2024" description = "A small, ergonomic HTTP client wrapper around hyper with optional support for custom Root CAs and a dev-only insecure mode for self-signed certificates." license = "MIT OR Apache-2.0" repository = "https://github.com/seemueller-io/hyper-custom-cert" documentation = "https://docs.rs/hyper-custom-cert" homepage = "https://docs.rs/hyper-custom-cert" -readme = "../../README.md" +readme = "README.md" keywords = ["hyper", "http-client", "tls", "rustls", "self-signed"] categories = ["asynchronous", "network-programming", "web-programming::http-client"] diff --git a/crates/hyper-custom-cert/README.md b/crates/hyper-custom-cert/README.md index 066defd..74ec491 100644 --- a/crates/hyper-custom-cert/README.md +++ b/crates/hyper-custom-cert/README.md @@ -1,232 +1,286 @@ # hyper-custom-cert [![Crates.io](https://img.shields.io/crates/v/hyper-custom-cert.svg)](https://crates.io/crates/hyper-custom-cert) -[![docs.rs](https://img.shields.io/docsrs/hyper-custom-cert)](https://docs.rs/hyper-custom-cert) -[![CI](https://github.com/seemueller-io/hyper-custom-cert/actions/workflows/ci.yml/badge.svg)](https://github.com/seemueller-io/hyper-custom-cert/actions) +[![Documentation](https://docs.rs/hyper-custom-cert/badge.svg)](https://docs.rs/hyper-custom-cert) +[![License](https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-blue.svg)](LICENSE) -A reusable HTTP client builder API with clear, security‑focused feature flags for selecting your TLS backend and security posture. +A small, ergonomic HTTP client wrapper around hyper with optional support for custom Root CAs and a dev-only insecure mode for self-signed certificates. -This crate is derived from a reference implementation in this repository (under `reference-implementation/`), but is designed as a reusable library with a more robust and explicit configuration surface. Networking internals are intentionally abstracted for now; the focus is on a secure, ergonomic API. +## Features -## Features and TLS strategy +- **Secure by Default**: Uses the operating system's native trust store via `native-tls` +- **Custom CA Support**: Optional `rustls` feature for connecting to services with custom Certificate Authorities +- **Development Mode**: Optional `insecure-dangerous` feature for testing with self-signed certificates (⚠️ **NEVER use in production**) +- **WebAssembly Compatible**: Proper WASM support with appropriate security constraints +- **Certificate Pinning**: Advanced security feature for production environments +- **Builder Pattern**: Ergonomic configuration with sensible defaults -- Default: `native-tls` - - Uses the operating system trust store via `hyper-tls`/`native-tls`. - - Secure default for connecting to standard, publicly trusted endpoints. +## Quick Start -- Optional: `rustls` - - Uses `hyper-rustls`. - - Activates the `with_root_ca_pem` method on the builder, allowing you to trust a custom Root CA (recommended approach for custom/private CAs). +`cargo add hyper-custom-cert` -- Optional: `insecure-dangerous` - - Unlocks `insecure_accept_invalid_certs(true)` and `HttpClient::with_self_signed_certs()`. - - IMPORTANT: This is for local development/testing only and must NEVER be used in production. +### Basic Usage (Secure Default) -See SECURITY.md for a thorough discussion of these modes and when to use them. +```rust +use hyper_custom_cert::HttpClient; -## Quick start +#[tokio::main] +async fn main() -> Result<(), Box> { + // Uses OS trust store by default - secure for public HTTPS endpoints + let client = HttpClient::new(); + + // Make requests to publicly trusted endpoints + client.request("https://httpbin.org/get").await?; + + Ok(()) +} +``` -- Default (native-tls): - ```bash - cargo build -p hyper-custom-cert - cargo run -p hyper-custom-cert --example self-signed-certs - ``` +### Custom Root CA (Production) -- With rustls (custom Root CA support): - ```bash - cargo build -p hyper-custom-cert --no-default-features --features rustls - cargo run -p hyper-custom-cert --no-default-features --features rustls --example self-signed-certs - ``` +For connecting to services with custom/private Certificate Authorities: -- Insecure (dangerous, dev only): - ```bash - # With native-tls - cargo build -p hyper-custom-cert --features insecure-dangerous - cargo run -p hyper-custom-cert --features insecure-dangerous --example self-signed-certs +```toml +[dependencies] +hyper-custom-cert = { version = "0.1.0", features = ["rustls"] } +``` - # With rustls - cargo build -p hyper-custom-cert --no-default-features --features rustls,insecure-dangerous - cargo run -p hyper-custom-cert --no-default-features --features rustls,insecure-dangerous --example self-signed-certs - ``` +```rust +use hyper_custom_cert::HttpClient; -## Builder API overview +#[tokio::main] +async fn main() -> Result<(), Box> { + // Load your organization's Root CA + let client = HttpClient::builder() + .with_root_ca_file("path/to/your-org-root-ca.pem") + .build(); + + // Now you can connect to services signed by your custom CA + client.request("https://internal.your-org.com/api").await?; + + Ok(()) +} +``` -```rust,ignore +### Certificate Pinning (Enhanced Security) + +For high-security environments where you want to pin specific certificates: + +```rust +use hyper_custom_cert::HttpClient; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // SHA-256 fingerprints of certificates you want to accept + let pin1 = [0x12, 0x34, /* ... 30 more bytes */]; + let pin2 = [0xab, 0xcd, /* ... 30 more bytes */]; + + let client = HttpClient::builder() + .with_pinned_cert_sha256(vec![pin1, pin2]) + .build(); + + // Only accepts connections to certificates matching the pins + client.request("https://secure-api.example.com").await?; + + Ok(()) +} +``` + +### Development/Testing Only (⚠️ Dangerous) + +**WARNING**: This mode disables certificate validation. Only use for local development and testing. + +```toml +[dependencies] +hyper-custom-cert = { version = "0.1.0", features = ["insecure-dangerous"] } +``` + +```rust +use hyper_custom_cert::HttpClient; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // ⚠️ EXTREMELY DANGEROUS - Only for local development + let client = HttpClient::builder() + .insecure_accept_invalid_certs(true) + .build(); + + // Can connect to self-signed certificates (NOT for production!) + client.request("https://localhost:8443").await?; + + Ok(()) +} +``` + +## Configuration Options + +### Builder Methods + +```rust use hyper_custom_cert::HttpClient; use std::time::Duration; use std::collections::HashMap; let mut headers = HashMap::new(); -headers.insert("x-app".into(), "demo".into()); +headers.insert("User-Agent".to_string(), "MyApp/1.0".to_string()); -let mut builder = HttpClient::builder() - .with_timeout(Duration::from_secs(10)) - .with_default_headers(headers); +let client = HttpClient::builder() + .with_timeout(Duration::from_secs(30)) + .with_default_headers(headers) + .with_root_ca_file("custom-ca.pem") // Requires 'rustls' feature + .build(); +``` -// When the `rustls` feature is enabled, you can add a custom Root CA: -#[cfg(feature = "rustls")] +### Available Methods + +| Method | Feature Required | Description | +|--------|-----------------|-------------| +| `new()` | None | Creates client with OS trust store (secure default) | +| `builder()` | None | Returns a builder for custom configuration | +| `with_timeout(Duration)` | None | Sets request timeout | +| `with_default_headers(HashMap)` | None | Sets default headers for all requests | +| `with_root_ca_pem(&[u8])` | `rustls` | Adds custom CA from PEM bytes | +| `with_root_ca_file(Path)` | `rustls` | Adds custom CA from PEM file | +| `with_pinned_cert_sha256(Vec<[u8; 32]>)` | `rustls` | Enables certificate pinning | +| `insecure_accept_invalid_certs(bool)` | `insecure-dangerous` | ⚠️ Disables certificate validation | +| `with_self_signed_certs()` | `insecure-dangerous` | ⚠️ Convenience for self-signed certs | + +## Feature Flags + +### `native-tls` (Default) + +- **Default**: ✅ Enabled +- **Security**: ✅ Secure - Uses OS trust store +- **Use Case**: Public HTTPS endpoints with standard certificates +- **Dependencies**: `hyper-tls`, `native-tls` + +### `rustls` + +- **Default**: ❌ Disabled +- **Security**: ✅ Secure - Custom CA validation +- **Use Case**: Private/custom Certificate Authorities +- **Dependencies**: `hyper-rustls`, `rustls-pemfile` +- **Enables**: `with_root_ca_pem()`, `with_root_ca_file()`, `with_pinned_cert_sha256()` + +### `insecure-dangerous` + +- **Default**: ❌ Disabled +- **Security**: ❌ **EXTREMELY DANGEROUS** +- **Use Case**: **Development/testing ONLY** +- **Warning**: **NEVER enable in production** +- **Enables**: `insecure_accept_invalid_certs()`, `with_self_signed_certs()` + +## WebAssembly (WASM) Support + +This crate supports WebAssembly targets with important security considerations: + +```rust +// WASM builds will compile, but certain operations are restricted +#[cfg(target_arch = "wasm32")] { - // Option 1: Load CA certificate from raw PEM bytes - builder = builder.with_root_ca_pem(include_bytes!("../examples-data/root-ca.pem")); - - // Option 2: Load CA certificate from a file path - builder = builder.with_root_ca_file("path/to/root-ca.pem"); - - // Option 3: Using std::path::Path - use std::path::Path; - let ca_path = Path::new("certs/custom-ca.pem"); - builder = builder.with_root_ca_file(ca_path); - - // Option 4: Certificate pinning for additional security - let pin1: [u8; 32] = [ - 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, - 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, - 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, - 0xa1, 0xb2, 0xc3, 0xd4, 0xe5, 0xf6, 0x07, 0x18 - ]; - let pin2: [u8; 32] = [ - 0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87, - 0x78, 0x69, 0x5a, 0x4b, 0x3c, 0x2d, 0x1e, 0x0f, - 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, - 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff - ]; - builder = builder.with_pinned_cert_sha256(vec![pin1, pin2]); -} - -let client = builder.build(); - -// During local development only: -#[cfg(feature = "insecure-dangerous")] -{ - let dev_client = HttpClient::with_self_signed_certs(); - let dev_client2 = HttpClient::builder() - .insecure_accept_invalid_certs(true) - .build(); + let client = HttpClient::new(); // ✅ Works + // Custom CA operations may return WasmNotImplemented errors } ``` -## Selecting features +**WASM Limitations:** +- Custom Root CA installation requires browser/OS-level certificate management +- Some TLS configuration options may not be available +- Certificate pinning may be limited by browser security policies -- Native TLS (default): - - `cargo add hyper-custom-cert` (or no extra flags if in this workspace) - - `cargo build` +**Browser Certificate Installation:** +1. Download your organization's Root CA certificate +2. Install it in your browser's certificate store +3. Mark it as trusted for websites +4. Your WASM application will then trust endpoints signed by that CA -- Rustls: - - `cargo build --no-default-features --features rustls` +## Error Handling -- Insecure (dangerous, dev only): - - With native TLS: `cargo build --features insecure-dangerous` - - With rustls: `cargo build --no-default-features --features rustls,insecure-dangerous` +```rust +use hyper_custom_cert::{HttpClient, ClientError}; -## WASM Support - -This library's WASM build is **primarily intended for edge runtime environments** such as Cloudflare Workers, Deno Deploy, Vercel Edge Functions, and similar serverless edge computing platforms. - -### Edge Runtime Usage (Primary Use Case) - -Edge runtimes provide a more capable WASM environment compared to browsers, often supporting custom certificate configuration and advanced TLS features: - -**Capabilities in Edge Runtimes:** -- **Custom Root CA Support:** Methods like `with_root_ca_pem()` and `with_root_ca_file()` are typically supported -- **Certificate Pinning:** The `with_pinned_cert_sha256()` method may be available depending on the runtime -- **Flexible TLS Configuration:** Full control over certificate validation and TLS settings -- **No Same-Origin Policy:** Direct network access without browser security restrictions - -**Recommended Approach for Edge Runtimes:** -```rust,ignore -#[cfg(target_arch = "wasm32")] -{ - // For edge runtimes, full custom CA support is typically available - #[cfg(feature = "rustls")] - let client = HttpClient::builder() - .with_timeout(Duration::from_secs(10)) - .with_root_ca_pem(include_bytes!("../certs/root-ca.pem")) - .build(); - - // Certificate pinning for additional security - let pin: [u8; 32] = [/* your certificate SHA-256 hash */]; - let client_with_pinning = HttpClient::builder() - .with_pinned_cert_sha256(vec![pin]) - .build(); -} -``` - -**Popular Edge Runtime Platforms:** -- **Cloudflare Workers:** Full WASM support with network capabilities -- **Deno Deploy:** TypeScript/JavaScript runtime with WASM modules -- **Vercel Edge Functions:** Next.js edge runtime environment -- **Fastly Compute@Edge:** High-performance edge computing platform -- **AWS Lambda@Edge:** Serverless edge functions - -### Browser Usage (Limited Support) - -When running in browser environments, WASM operates under significant security restrictions: - -**Browser Limitations:** -- **No Custom Root CA Support:** Methods like `with_root_ca_pem()` and `with_root_ca_file()` may return `WasmNotImplemented` errors -- **No Certificate Pinning:** The `with_pinned_cert_sha256()` method is not available in browser environments -- **Browser-Controlled Trust:** All certificate validation is handled by the browser's built-in certificate store -- **Same-Origin Policy:** Cross-origin requests are subject to CORS policies and browser security models - -**Browser Development Guidance:** -```rust,ignore -#[cfg(target_arch = "wasm32")] -{ - // For browser WASM, rely on browser's built-in certificate validation - let client = HttpClient::builder() - .with_timeout(Duration::from_secs(10)) - .build(); -} -``` - -For development with self-signed certificates in browsers, you'll need to install certificates in the browser's certificate store rather than configuring them programmatically. - -### Environment Detection - -To handle both edge runtime and browser environments gracefully: - -```rust,ignore -#[cfg(target_arch = "wasm32")] -{ - // Attempt edge runtime configuration, fall back to basic setup - let mut builder = HttpClient::builder() - .with_timeout(Duration::from_secs(10)); - - #[cfg(feature = "rustls")] - { - // Try to use custom CA - this will work in edge runtimes - // but may fail in browsers - match std::panic::catch_unwind(|| { - builder.with_root_ca_pem(include_bytes!("../certs/root-ca.pem")) - }) { - Ok(configured_builder) => builder = configured_builder, - Err(_) => { - // Fallback for browser environments - eprintln!("Custom CA configuration not supported in this WASM environment"); - } - } +match client.request("https://example.com").await { + Ok(_) => println!("Request successful"), + Err(ClientError::WasmNotImplemented) => { + println!("This operation isn't supported in WASM"); + } + Err(e) => { + println!("Request failed: {}", e); } - - let client = builder.build(); } ``` -### Production Considerations +## Security Best Practices -**For Edge Runtimes:** -- Leverage full TLS configuration capabilities available in your edge platform -- Use custom CAs and certificate pinning for enhanced security -- Test certificate handling across different edge runtime providers -- Consider platform-specific TLS optimizations +### Production Recommendations -**For Browser Applications:** -- Always use proper SSL/TLS certificates from trusted CAs -- Consider using Let's Encrypt or other automated certificate management solutions -- Document any certificate requirements clearly for end users -- Plan for browser security policy limitations +1. **Use Default Mode**: Stick with `native-tls` for public endpoints +2. **Custom CA Only When Needed**: Only use `rustls` feature when connecting to private CAs +3. **Never Use `insecure-dangerous`**: This feature should never be enabled in production +4. **Keep Dependencies Updated**: Monitor for security advisories +5. **Certificate Pinning**: Consider pinning for high-security applications -## Security Notes +### Development vs Production -- Prefer the default `native-tls` or the `rustls` feature for production. -- The `insecure-dangerous` feature must never be enabled in production; it bypasses certificate validation and exposes you to active MITM risk. -- On WASM platforms, certificate handling varies by environment: edge runtimes typically support full custom CA configuration, while browser environments manage certificate validation through built-in certificate stores. +```rust +// ✅ GOOD: Production configuration +#[cfg(not(debug_assertions))] +let client = HttpClient::new(); // Uses OS trust store + +// ✅ GOOD: Development configuration +#[cfg(debug_assertions)] +let client = HttpClient::builder() + .insecure_accept_invalid_certs(true) // Only in debug builds + .build(); +``` + +## Examples + +See the `examples/` directory for complete working examples: + +- `examples/self-signed-certs/` - Comprehensive examples for all modes +- Example of connecting to public endpoints (default mode) +- Example of using custom Root CA for private services +- Example of development mode with self-signed certificates + +## Testing + +```bash +# Test with default features +cargo test + +# Test with rustls features +cargo test --features rustls + +# Test with all features (for development) +cargo test --features rustls,insecure-dangerous + +# Test WASM compatibility +cargo test --target wasm32-unknown-unknown +``` + +## Contributing + +1. Fork the repository +2. Create a feature branch +3. Make your changes +4. Add tests for new functionality +5. Ensure all tests pass: `cargo test --all-features` +6. Submit a pull request + +## License + +This project is licensed under either of: + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE)) +- MIT License ([LICENSE-MIT](LICENSE-MIT)) + +at your option. + +## Security Policy + +For security vulnerabilities, please see [SECURITY.md](SECURITY.md) for our responsible disclosure policy. + +--- + +**Remember**: This library prioritizes security by default. The `insecure-dangerous` feature exists solely for development convenience and should never be used in production environments. \ No newline at end of file