1 Commits

Author SHA1 Message Date
geoffsee
3c057be552 update crate readme 2025-08-15 11:11:56 -04:00
2 changed files with 248 additions and 194 deletions

View File

@@ -1,13 +1,13 @@
[package] [package]
name = "hyper-custom-cert" name = "hyper-custom-cert"
version = "0.3.0" version = "0.3.1"
edition = "2024" 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." 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" license = "MIT OR Apache-2.0"
repository = "https://github.com/seemueller-io/hyper-custom-cert" repository = "https://github.com/seemueller-io/hyper-custom-cert"
documentation = "https://docs.rs/hyper-custom-cert" documentation = "https://docs.rs/hyper-custom-cert"
homepage = "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"] keywords = ["hyper", "http-client", "tls", "rustls", "self-signed"]
categories = ["asynchronous", "network-programming", "web-programming::http-client"] categories = ["asynchronous", "network-programming", "web-programming::http-client"]

View File

@@ -1,232 +1,286 @@
# hyper-custom-cert # hyper-custom-cert
[![Crates.io](https://img.shields.io/crates/v/hyper-custom-cert.svg)](https://crates.io/crates/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) [![Documentation](https://docs.rs/hyper-custom-cert/badge.svg)](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) [![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` ## Quick Start
- Uses the operating system trust store via `hyper-tls`/`native-tls`.
- Secure default for connecting to standard, publicly trusted endpoints.
- Optional: `rustls` `cargo add hyper-custom-cert`
- 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).
- Optional: `insecure-dangerous` ### Basic Usage (Secure Default)
- 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.
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): // Make requests to publicly trusted endpoints
```bash client.request("https://httpbin.org/get").await?;
cargo build -p hyper-custom-cert
cargo run -p hyper-custom-cert --example self-signed-certs
```
- With rustls (custom Root CA support): Ok(())
```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
```
- Insecure (dangerous, dev only): ### Custom Root CA (Production)
```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
# With rustls For connecting to services with custom/private Certificate Authorities:
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
```
## 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 hyper_custom_cert::HttpClient;
use std::time::Duration; use std::time::Duration;
use std::collections::HashMap; use std::collections::HashMap;
let mut headers = HashMap::new(); 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() let client = HttpClient::builder()
.with_timeout(Duration::from_secs(10)) .with_timeout(Duration::from_secs(30))
.with_default_headers(headers); .with_default_headers(headers)
.with_root_ca_file("custom-ca.pem") // Requires 'rustls' feature
// When the `rustls` feature is enabled, you can add a custom Root CA:
#[cfg(feature = "rustls")]
{
// 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(); .build();
}
``` ```
## Selecting features ### Available Methods
- Native TLS (default): | Method | Feature Required | Description |
- `cargo add hyper-custom-cert` (or no extra flags if in this workspace) |--------|-----------------|-------------|
- `cargo build` | `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 |
- Rustls: ## Feature Flags
- `cargo build --no-default-features --features rustls`
- Insecure (dangerous, dev only): ### `native-tls` (Default)
- With native TLS: `cargo build --features insecure-dangerous`
- With rustls: `cargo build --no-default-features --features rustls,insecure-dangerous`
## WASM Support - **Default**: ✅ Enabled
- **Security**: ✅ Secure - Uses OS trust store
- **Use Case**: Public HTTPS endpoints with standard certificates
- **Dependencies**: `hyper-tls`, `native-tls`
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. ### `rustls`
### Edge Runtime Usage (Primary Use Case) - **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()`
Edge runtimes provide a more capable WASM environment compared to browsers, often supporting custom certificate configuration and advanced TLS features: ### `insecure-dangerous`
**Capabilities in Edge Runtimes:** - **Default**: ❌ Disabled
- **Custom Root CA Support:** Methods like `with_root_ca_pem()` and `with_root_ca_file()` are typically supported - **Security**: ❌ **EXTREMELY DANGEROUS**
- **Certificate Pinning:** The `with_pinned_cert_sha256()` method may be available depending on the runtime - **Use Case**: **Development/testing ONLY**
- **Flexible TLS Configuration:** Full control over certificate validation and TLS settings - **Warning**: **NEVER enable in production**
- **No Same-Origin Policy:** Direct network access without browser security restrictions - **Enables**: `insecure_accept_invalid_certs()`, `with_self_signed_certs()`
**Recommended Approach for Edge Runtimes:** ## WebAssembly (WASM) Support
```rust,ignore
This crate supports WebAssembly targets with important security considerations:
```rust
// WASM builds will compile, but certain operations are restricted
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
{ {
// For edge runtimes, full custom CA support is typically available let client = HttpClient::new(); // ✅ Works
#[cfg(feature = "rustls")] // Custom CA operations may return WasmNotImplemented errors
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:** **WASM Limitations:**
- **Cloudflare Workers:** Full WASM support with network capabilities - Custom Root CA installation requires browser/OS-level certificate management
- **Deno Deploy:** TypeScript/JavaScript runtime with WASM modules - Some TLS configuration options may not be available
- **Vercel Edge Functions:** Next.js edge runtime environment - Certificate pinning may be limited by browser security policies
- **Fastly Compute@Edge:** High-performance edge computing platform
- **AWS Lambda@Edge:** Serverless edge functions
### Browser Usage (Limited Support) **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
When running in browser environments, WASM operates under significant security restrictions: ## Error Handling
**Browser Limitations:** ```rust
- **No Custom Root CA Support:** Methods like `with_root_ca_pem()` and `with_root_ca_file()` may return `WasmNotImplemented` errors use hyper_custom_cert::{HttpClient, ClientError};
- **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:** match client.request("https://example.com").await {
```rust,ignore Ok(_) => println!("Request successful"),
#[cfg(target_arch = "wasm32")] Err(ClientError::WasmNotImplemented) => {
{ println!("This operation isn't supported in WASM");
// For browser WASM, rely on browser's built-in certificate validation }
let client = HttpClient::builder() Err(e) => {
.with_timeout(Duration::from_secs(10)) println!("Request failed: {}", e);
}
}
```
## Security Best Practices
### Production Recommendations
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
### Development vs Production
```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(); .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. ## Examples
### Environment Detection See the `examples/` directory for complete working examples:
To handle both edge runtime and browser environments gracefully: - `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
```rust,ignore ## Testing
#[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")] ```bash
{ # Test with default features
// Try to use custom CA - this will work in edge runtimes cargo test
// 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");
}
}
}
let client = builder.build(); # 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
``` ```
### Production Considerations ## Contributing
**For Edge Runtimes:** 1. Fork the repository
- Leverage full TLS configuration capabilities available in your edge platform 2. Create a feature branch
- Use custom CAs and certificate pinning for enhanced security 3. Make your changes
- Test certificate handling across different edge runtime providers 4. Add tests for new functionality
- Consider platform-specific TLS optimizations 5. Ensure all tests pass: `cargo test --all-features`
6. Submit a pull request
**For Browser Applications:** ## License
- 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
## Security Notes This project is licensed under either of:
- Prefer the default `native-tls` or the `rustls` feature for production. - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE))
- The `insecure-dangerous` feature must never be enabled in production; it bypasses certificate validation and exposes you to active MITM risk. - MIT License ([LICENSE-MIT](LICENSE-MIT))
- 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.
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.