10 Commits

11 changed files with 564 additions and 376 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,166 +0,0 @@
name: Documentation
on:
push:
branches: [main]
paths:
- 'crates/hyper-custom-cert/src/**'
- 'crates/hyper-custom-cert/README.md'
- 'README.md'
- 'crates/hyper-custom-cert/Cargo.toml'
- '.github/workflows/docs.yml'
pull_request:
branches: [main]
paths:
- 'crates/hyper-custom-cert/src/**'
- 'crates/hyper-custom-cert/README.md'
- 'README.md'
- 'crates/hyper-custom-cert/Cargo.toml'
- '.github/workflows/docs.yml'
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
readme-sync:
name: Check README synchronization
runs-on: ubuntu-latest
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: Install cargo-readme
run: cargo install cargo-readme
- name: Check README is up to date
working-directory: crates/hyper-custom-cert
run: |
# Generate README from lib.rs documentation
cargo readme > README_generated.md
# Compare with existing README
if ! diff -u README.md README_generated.md; then
echo "ERROR: README.md is not synchronized with lib.rs documentation"
echo "Run 'cargo readme > README.md' in crates/hyper-custom-cert/ to update"
exit 1
fi
# Clean up
rm README_generated.md
link-check:
name: Check documentation links
runs-on: ubuntu-latest
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
working-directory: crates/hyper-custom-cert
run: cargo doc --all-features --no-deps
- name: Install lychee
run: |
curl -sSL https://github.com/lycheeverse/lychee/releases/latest/download/lychee-x86_64-unknown-linux-gnu.tar.gz | tar xz
sudo mv lychee /usr/local/bin/
- name: Check links in documentation
run: |
# Check links in generated documentation
lychee 'crates/hyper-custom-cert/target/doc/**/*.html' --exclude-path target --base crates/hyper-custom-cert/target/doc
# Check links in README files
lychee README.md crates/hyper-custom-cert/README.md

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.4" version = "0.3.0"
dependencies = [ dependencies = [
"hyper-rustls", "hyper-rustls",
"hyper-tls", "hyper-tls",

14
LICENSE Normal file
View File

@@ -0,0 +1,14 @@
Licensed under either of
* Apache License, Version 2.0
(LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
* MIT license
(LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.
## Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
dual licensed as above, without any additional terms or conditions.

193
LICENSE-APACHE Normal file
View File

@@ -0,0 +1,193 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(which shall not include communications that are conspicuously
marked or otherwise designated in writing by the copyright owner
as "Not a Contribution").
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control
systems, and issue tracking systems that are managed by, or on behalf
of, the Licensor for the purpose of discussing and improving the Work,
but excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution".
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to use, reproduce, modify, publicly display,
publicly perform, sublicense, and distribute the Work and such
Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, trademark, patent,
attribution and other notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright notice and additional terms or conditions
for use, reproduction, or distribution of Your Work or for any such
Derivative Works as a whole, provided Your use, reproduction, and
distribution of the Work otherwise complies with the conditions stated
in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Support. When redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional support.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

21
LICENSE-MIT Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 hyper-custom-cert contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

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)
@@ -47,7 +42,7 @@ For connecting to services with custom/private Certificate Authorities:
```toml ```toml
[dependencies] [dependencies]
hyper-custom-cert = { version = "0.1.0", features = ["rustls"] } hyper-custom-cert = { version = "<latest>", features = ["rustls"] }
``` ```
```rust ```rust

View File

@@ -1,7 +1,7 @@
[package] [package]
name = "hyper-custom-cert" name = "hyper-custom-cert"
version = "0.1.5" version = "0.3.2"
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/http_client/actions/workflows/ci.yml/badge.svg)](https://github.com/seemueller-io/http_client/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.

View File

@@ -6,10 +6,6 @@
//! - A production-grade path to trust a custom Root CA by providing PEM bytes //! - A production-grade path to trust a custom Root CA by providing PEM bytes
//! - Clear security boundaries and feature flags //! - Clear security boundaries and feature flags
//! //!
//! This crate is derived from a reference implementation located under
//! `reference-implementation/hyper-custom-cert` in this repository. The reference
//! implementation remains unchanged and serves as inspiration and verification.
//!
//! Note: Networking internals are intentionally abstracted for now; this crate //! Note: Networking internals are intentionally abstracted for now; this crate
//! focuses on a robust and secure configuration API surfaced via a builder. //! focuses on a robust and secure configuration API surfaced via a builder.
//! //!