5 Commits

7 changed files with 335 additions and 281 deletions

View File

@@ -70,3 +70,8 @@ jobs:
if [ -n "${{ matrix.features }}" ]; then FLAGS="$FLAGS --features ${{ matrix.features }}"; fi if [ -n "${{ matrix.features }}" ]; then FLAGS="$FLAGS --features ${{ matrix.features }}"; fi
echo "Running: cargo test $FLAGS -- --nocapture" echo "Running: cargo test $FLAGS -- --nocapture"
cargo test $FLAGS -- --nocapture cargo test $FLAGS -- --nocapture
- name: Build Docs
shell: bash
run: |
cargo doc -p hyper-custom-cert --no-deps

View File

@@ -1,76 +0,0 @@
name: Documentation
on:
push:
tags:
- 'v*'
jobs:
docs:
name: Build and validate documentation
runs-on: ubuntu-latest
defaults:
run:
working-directory: crates/hyper-custom-cert
strategy:
fail-fast: false
matrix:
include:
- name: default-features
features: ""
no-default-features: false
- name: no-default-features
features: ""
no-default-features: true
- name: rustls
features: "rustls"
no-default-features: true
- name: insecure-dangerous
features: "insecure-dangerous"
no-default-features: false
- name: all-features
features: "rustls,insecure-dangerous"
no-default-features: true
steps:
- name: Checkout
uses: actions/checkout@v4
- uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- name: Setup Rust
run: rustup update stable && rustup default stable
- name: Build documentation
shell: bash
run: |
FLAGS=""
if [ "${{ matrix.no-default-features }}" = "true" ]; then FLAGS="$FLAGS --no-default-features"; fi
if [ -n "${{ matrix.features }}" ]; then FLAGS="$FLAGS --features ${{ matrix.features }}"; fi
echo "Running: cargo doc $FLAGS --no-deps"
cargo doc $FLAGS --no-deps
- name: Check documentation warnings
shell: bash
run: |
FLAGS=""
if [ "${{ matrix.no-default-features }}" = "true" ]; then FLAGS="$FLAGS --no-default-features"; fi
if [ -n "${{ matrix.features }}" ]; then FLAGS="$FLAGS --features ${{ matrix.features }}"; fi
echo "Running: cargo doc $FLAGS --no-deps"
RUSTDOCFLAGS="-D warnings" cargo doc $FLAGS --no-deps
- name: Test documentation examples
shell: bash
run: |
FLAGS=""
if [ "${{ matrix.no-default-features }}" = "true" ]; then FLAGS="$FLAGS --no-default-features"; fi
if [ -n "${{ matrix.features }}" ]; then FLAGS="$FLAGS --features ${{ matrix.features }}"; fi
echo "Running: cargo test --doc $FLAGS"
cargo test --doc $FLAGS

View File

@@ -9,9 +9,79 @@ env:
CARGO_TERM_COLOR: always CARGO_TERM_COLOR: always
jobs: jobs:
docs:
name: Build and validate documentation
runs-on: ubuntu-latest
defaults:
run:
working-directory: crates/hyper-custom-cert
strategy:
fail-fast: false
matrix:
include:
- name: default-features
features: ""
no-default-features: false
- name: no-default-features
features: ""
no-default-features: true
- name: rustls
features: "rustls"
no-default-features: true
- name: insecure-dangerous
features: "insecure-dangerous"
no-default-features: false
- name: all-features
features: "rustls,insecure-dangerous"
no-default-features: true
steps:
- name: Checkout
uses: actions/checkout@v4
- uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- name: Setup Rust
run: rustup update stable && rustup default stable
- name: Build documentation
shell: bash
run: |
FLAGS=""
if [ "${{ matrix.no-default-features }}" = "true" ]; then FLAGS="$FLAGS --no-default-features"; fi
if [ -n "${{ matrix.features }}" ]; then FLAGS="$FLAGS --features ${{ matrix.features }}"; fi
echo "Running: cargo doc $FLAGS --no-deps"
cargo doc $FLAGS --no-deps
- name: Check documentation warnings
shell: bash
run: |
FLAGS=""
if [ "${{ matrix.no-default-features }}" = "true" ]; then FLAGS="$FLAGS --no-default-features"; fi
if [ -n "${{ matrix.features }}" ]; then FLAGS="$FLAGS --features ${{ matrix.features }}"; fi
echo "Running: cargo doc $FLAGS --no-deps"
RUSTDOCFLAGS="-D warnings" cargo doc $FLAGS --no-deps
- name: Test documentation examples
shell: bash
run: |
FLAGS=""
if [ "${{ matrix.no-default-features }}" = "true" ]; then FLAGS="$FLAGS --no-default-features"; fi
if [ -n "${{ matrix.features }}" ]; then FLAGS="$FLAGS --features ${{ matrix.features }}"; fi
echo "Running: cargo test --doc $FLAGS"
cargo test --doc $FLAGS
test: test:
name: Test before release name: Test before release
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: docs
defaults: defaults:
run: run:
working-directory: crates/hyper-custom-cert working-directory: crates/hyper-custom-cert
@@ -75,9 +145,12 @@ jobs:
echo "Running: cargo test $FLAGS -- --nocapture" echo "Running: cargo test $FLAGS -- --nocapture"
cargo test $FLAGS -- --nocapture cargo test $FLAGS -- --nocapture
publish: publish:
name: Publish to crates.io name: Publish to crates.io
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
id-token: write # Required for OIDC token exchange https://crates.io/docs/trusted-publishing
needs: test needs: test
defaults: defaults:
run: run:
@@ -108,10 +181,13 @@ jobs:
exit 1 exit 1
fi fi
- name: Publish to crates.io # See Trusted publishing: https://crates.io/docs/trusted-publishing
- uses: rust-lang/crates-io-auth-action@v1
id: auth
- run: cargo publish
env: env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}
run: cargo publish
release: release:
name: Create GitHub Release name: Create GitHub Release

2
Cargo.lock generated
View File

@@ -364,7 +364,7 @@ dependencies = [
[[package]] [[package]]
name = "hyper-custom-cert" name = "hyper-custom-cert"
version = "0.1.6" version = "0.3.0"
dependencies = [ dependencies = [
"hyper-rustls", "hyper-rustls",
"hyper-tls", "hyper-tls",

View File

@@ -17,12 +17,7 @@ A small, ergonomic HTTP client wrapper around hyper with optional support for cu
## Quick Start ## Quick Start
Add this to your `Cargo.toml`: `cargo add hyper-custom-cert`
```toml
[dependencies]
hyper-custom-cert = "0.1.0"
```
### Basic Usage (Secure Default) ### Basic Usage (Secure Default)

View File

@@ -1,7 +1,7 @@
[package] [package]
name = "hyper-custom-cert" name = "hyper-custom-cert"
version = "0.1.8" version = "0.3.1"
edition = "2021" 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"
@@ -38,5 +38,5 @@ rustls = ["dep:hyper-rustls", "dep:rustls-pemfile"]
insecure-dangerous = [] insecure-dangerous = []
[package.metadata.docs.rs] [package.metadata.docs.rs]
all-features = false all-features = true
no-default-features = false no-default-features = false

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();
// Make requests to publicly trusted endpoints
client.request("https://httpbin.org/get").await?;
Ok(())
}
```
- Default (native-tls): ### Custom Root CA (Production)
```bash
cargo build -p hyper-custom-cert
cargo run -p hyper-custom-cert --example self-signed-certs
```
- With rustls (custom Root CA support): For connecting to services with custom/private Certificate Authorities:
```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): ```toml
```bash [dependencies]
# With native-tls hyper-custom-cert = { version = "0.1.0", features = ["rustls"] }
cargo build -p hyper-custom-cert --features insecure-dangerous ```
cargo run -p hyper-custom-cert --features insecure-dangerous --example self-signed-certs
# With rustls ```rust
cargo build -p hyper-custom-cert --no-default-features --features rustls,insecure-dangerous use hyper_custom_cert::HttpClient;
cargo run -p hyper-custom-cert --no-default-features --features rustls,insecure-dangerous --example self-signed-certs
```
## Builder API overview #[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(())
}
```
```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<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
.build();
```
// When the `rustls` feature is enabled, you can add a custom Root CA: ### Available Methods
#[cfg(feature = "rustls")]
| 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 let client = HttpClient::new(); // ✅ Works
builder = builder.with_root_ca_pem(include_bytes!("../examples-data/root-ca.pem")); // Custom CA operations may return WasmNotImplemented errors
// 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();
} }
``` ```
## 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): **Browser Certificate Installation:**
- `cargo add hyper-custom-cert` (or no extra flags if in this workspace) 1. Download your organization's Root CA certificate
- `cargo build` 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: ## Error Handling
- `cargo build --no-default-features --features rustls`
- Insecure (dangerous, dev only): ```rust
- With native TLS: `cargo build --features insecure-dangerous` use hyper_custom_cert::{HttpClient, ClientError};
- With rustls: `cargo build --no-default-features --features rustls,insecure-dangerous`
## WASM Support match client.request("https://example.com").await {
Ok(_) => println!("Request successful"),
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. Err(ClientError::WasmNotImplemented) => {
println!("This operation isn't supported in WASM");
### Edge Runtime Usage (Primary Use Case) }
Err(e) => {
Edge runtimes provide a more capable WASM environment compared to browsers, often supporting custom certificate configuration and advanced TLS features: println!("Request failed: {}", e);
**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");
}
}
} }
let client = builder.build();
} }
``` ```
### Production Considerations ## Security Best Practices
**For Edge Runtimes:** ### Production Recommendations
- 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
**For Browser Applications:** 1. **Use Default Mode**: Stick with `native-tls` for public endpoints
- Always use proper SSL/TLS certificates from trusted CAs 2. **Custom CA Only When Needed**: Only use `rustls` feature when connecting to private CAs
- Consider using Let's Encrypt or other automated certificate management solutions 3. **Never Use `insecure-dangerous`**: This feature should never be enabled in production
- Document any certificate requirements clearly for end users 4. **Keep Dependencies Updated**: Monitor for security advisories
- Plan for browser security policy limitations 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. ```rust
- The `insecure-dangerous` feature must never be enabled in production; it bypasses certificate validation and exposes you to active MITM risk. // ✅ GOOD: Production configuration
- 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. #[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.