update crate readme

This commit is contained in:
geoffsee
2025-08-15 11:11:56 -04:00
parent 5165fe791a
commit 3c057be552
2 changed files with 248 additions and 194 deletions

View File

@@ -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"]

View File

@@ -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, securityfocused 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<dyn std::error::Error>> {
// Uses OS trust store by default - secure for public HTTPS endpoints
let client = HttpClient::new();
- Default (native-tls):
```bash
cargo build -p hyper-custom-cert
cargo run -p hyper-custom-cert --example self-signed-certs
```
// Make requests to publicly trusted endpoints
client.request("https://httpbin.org/get").await?;
- 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
```
Ok(())
}
```
- 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
### Custom Root CA (Production)
# 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
```
For connecting to services with custom/private Certificate Authorities:
## Builder API overview
```toml
[dependencies]
hyper-custom-cert = { version = "0.1.0", features = ["rustls"] }
```
```rust,ignore
```rust
use hyper_custom_cert::HttpClient;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// 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(())
}
```
### 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<dyn std::error::Error>> {
// 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<dyn std::error::Error>> {
// ⚠️ 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.