Compare commits
10 commits
dd80e6b831
...
66d3471a0d
| Author | SHA1 | Date | |
|---|---|---|---|
| 66d3471a0d | |||
| c4bbebbc3e | |||
| 367ff8b10e | |||
| 8a68df9e54 | |||
| d14ce407fa | |||
| 9fa095873c | |||
| b7a20e936c | |||
| b07ec6f35c | |||
| 15f8be4227 | |||
| 2ce5b5fd67 |
12 changed files with 242 additions and 312 deletions
67
CHANGELOG.md
Normal file
67
CHANGELOG.md
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [1.0.0] - 2024-11-23
|
||||
|
||||
### Added
|
||||
- RFC 9458 compliant OHTTP gateway implementation
|
||||
- Automatic key rotation with configurable intervals
|
||||
- Key management with deterministic and random key generation
|
||||
- Comprehensive security middleware with rate limiting
|
||||
- Prometheus metrics integration for observability
|
||||
- Health check endpoints for monitoring
|
||||
- Docker support for containerized deployment
|
||||
- Configurable target origin allowlists
|
||||
- Request validation and security controls
|
||||
- Binary HTTP (BHTTP) message handling
|
||||
- HPKE encryption/decryption for OHTTP protocol
|
||||
- Graceful shutdown handling
|
||||
- Structured logging with JSON support
|
||||
- Configuration through environment variables
|
||||
- Support for multiple cipher suites (X25519, HKDF-SHA256, AES-128-GCM, ChaCha20-Poly1305)
|
||||
|
||||
### Fixed
|
||||
- **BREAKING**: Key configuration format now includes required 2-byte length prefix per RFC 9458 Section 3.2
|
||||
- Proper handling of key expiration and cleanup
|
||||
- Correct OHTTP key configuration encoding with length prefixes
|
||||
- Memory safety and thread safety improvements
|
||||
|
||||
### Technical Details
|
||||
- Built with Rust 2024 edition
|
||||
- Uses `ohttp` crate v0.7.1 for RFC 9458 compliance
|
||||
- Uses `bhttp` crate v0.7.1 for binary HTTP message handling
|
||||
- Comprehensive test suite with 28+ tests covering all major functionality
|
||||
- Production-ready error handling and logging
|
||||
|
||||
### Dependencies
|
||||
- axum 0.7 for HTTP server framework
|
||||
- tokio 1.48 for async runtime
|
||||
- hyper 1.8 for HTTP implementation
|
||||
- reqwest 0.12 for backend HTTP client
|
||||
- ohttp 0.7.1 for OHTTP protocol implementation
|
||||
- bhttp 0.7.1 for binary HTTP messages
|
||||
- prometheus for metrics collection
|
||||
- tracing for structured logging
|
||||
- chrono for time handling
|
||||
|
||||
### Security
|
||||
- HPKE-based encryption using industry-standard algorithms
|
||||
- Request size limits and validation
|
||||
- Origin-based access control
|
||||
- Rate limiting with configurable thresholds
|
||||
- Secure key rotation and management
|
||||
- Protection against replay attacks
|
||||
- Comprehensive input validation
|
||||
|
||||
### Performance
|
||||
- Connection reuse between relay and gateway
|
||||
- Efficient binary HTTP message processing
|
||||
- Optimized cryptographic operations
|
||||
- Configurable timeouts and limits
|
||||
- Memory-efficient key storage
|
||||
|
||||
This is the first stable release suitable for production use in OHTTP deployments.
|
||||
348
Cargo.lock
generated
348
Cargo.lock
generated
|
|
@ -17,16 +17,6 @@ version = "2.0.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||
|
||||
[[package]]
|
||||
name = "aead"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aead"
|
||||
version = "0.5.2"
|
||||
|
|
@ -37,18 +27,6 @@ dependencies = [
|
|||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aes"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cipher 0.3.0",
|
||||
"cpufeatures 0.2.17",
|
||||
"opaque-debug",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aes"
|
||||
version = "0.8.4"
|
||||
|
|
@ -56,22 +34,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cipher 0.4.4",
|
||||
"cpufeatures 0.2.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aes-gcm"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc3be92e19a7ef47457b8e6f90707e12b6ac5d20c6f3866584fa3be0787d839f"
|
||||
dependencies = [
|
||||
"aead 0.4.3",
|
||||
"aes 0.7.5",
|
||||
"cipher 0.3.0",
|
||||
"ctr 0.7.0",
|
||||
"ghash 0.4.4",
|
||||
"subtle",
|
||||
"cipher",
|
||||
"cpufeatures",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -80,11 +44,11 @@ version = "0.10.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1"
|
||||
dependencies = [
|
||||
"aead 0.5.2",
|
||||
"aes 0.8.4",
|
||||
"cipher 0.4.4",
|
||||
"ctr 0.9.2",
|
||||
"ghash 0.5.1",
|
||||
"aead",
|
||||
"aes",
|
||||
"cipher",
|
||||
"ctr",
|
||||
"ghash",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
|
|
@ -360,11 +324,11 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
|||
|
||||
[[package]]
|
||||
name = "bhttp"
|
||||
version = "0.5.4"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2bc657efe5aa3821f1cacfb47665c32849e09820844bff9f5066227312829fa3"
|
||||
checksum = "44c659d6e2707aa970a0482d3f1a0f23ff470a423b0f52961632bbf3000b7ff7"
|
||||
dependencies = [
|
||||
"thiserror 1.0.69",
|
||||
"thiserror 2.0.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -376,15 +340,6 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
|
|
@ -448,18 +403,6 @@ version = "1.0.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
|
||||
|
||||
[[package]]
|
||||
name = "chacha20"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fee7ad89dc1128635074c268ee661f90c3f7e83d9fd12910608c36b47d6c3412"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cipher 0.3.0",
|
||||
"cpufeatures 0.1.5",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chacha20"
|
||||
version = "0.9.1"
|
||||
|
|
@ -467,21 +410,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cipher 0.4.4",
|
||||
"cpufeatures 0.2.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chacha20poly1305"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1580317203210c517b6d44794abfbe600698276db18127e37ad3e69bf5e848e5"
|
||||
dependencies = [
|
||||
"aead 0.4.3",
|
||||
"chacha20 0.7.1",
|
||||
"cipher 0.3.0",
|
||||
"poly1305 0.7.2",
|
||||
"zeroize",
|
||||
"cipher",
|
||||
"cpufeatures",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -490,10 +420,10 @@ version = "0.10.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35"
|
||||
dependencies = [
|
||||
"aead 0.5.2",
|
||||
"chacha20 0.9.1",
|
||||
"cipher 0.4.4",
|
||||
"poly1305 0.8.0",
|
||||
"aead",
|
||||
"chacha20",
|
||||
"cipher",
|
||||
"poly1305",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
|
|
@ -511,15 +441,6 @@ dependencies = [
|
|||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cipher"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cipher"
|
||||
version = "0.4.4"
|
||||
|
|
@ -592,7 +513,7 @@ dependencies = [
|
|||
"rust-ini",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"toml 0.8.23",
|
||||
"toml",
|
||||
"yaml-rust2",
|
||||
]
|
||||
|
||||
|
|
@ -641,15 +562,6 @@ version = "0.8.7"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66c99696f6c9dd7f35d486b9d04d7e6e202aa3e8c40d553f2fdf5e7e0c6a71ef"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.17"
|
||||
|
|
@ -687,36 +599,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"rand_core",
|
||||
"rand_core 0.6.4",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-mac"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25fab6889090c8133f3deb8f73ba3c65a7f456f66436fc012a1b1e272b1e103e"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctr"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a232f92a03f37dd7d7dd2adc67166c77e9cd88de5b019b9a9eecfaeaf7bfd481"
|
||||
dependencies = [
|
||||
"cipher 0.3.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctr"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835"
|
||||
dependencies = [
|
||||
"cipher 0.4.4",
|
||||
"cipher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -726,7 +619,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "373b7c5dbd637569a2cca66e8d66b8c446a1e7bf064ea321d265d7b3dfe7c97e"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures 0.2.17",
|
||||
"cpufeatures",
|
||||
"curve25519-dalek-derive",
|
||||
"fiat-crypto",
|
||||
"rustc_version",
|
||||
|
|
@ -788,22 +681,13 @@ dependencies = [
|
|||
"powerfmt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer 0.10.4",
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
"subtle",
|
||||
]
|
||||
|
|
@ -1019,16 +903,6 @@ dependencies = [
|
|||
"wasi 0.14.2+wasi-0.2.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ghash"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99"
|
||||
dependencies = [
|
||||
"opaque-debug",
|
||||
"polyval 0.5.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ghash"
|
||||
version = "0.5.1"
|
||||
|
|
@ -1036,7 +910,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1"
|
||||
dependencies = [
|
||||
"opaque-debug",
|
||||
"polyval 0.6.2",
|
||||
"polyval",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1107,33 +981,13 @@ version = "0.4.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "hkdf"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01706d578d5c281058480e673ae4086a9f4710d8df1ad80a5b03e39ece5f886b"
|
||||
dependencies = [
|
||||
"digest 0.9.0",
|
||||
"hmac 0.11.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hkdf"
|
||||
version = "0.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7"
|
||||
dependencies = [
|
||||
"hmac 0.12.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hmac"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b"
|
||||
dependencies = [
|
||||
"crypto-mac",
|
||||
"digest 0.9.0",
|
||||
"hmac",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1142,25 +996,24 @@ version = "0.12.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
|
||||
dependencies = [
|
||||
"digest 0.10.7",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hpke"
|
||||
version = "0.11.0"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e04a5933a381bb81f00b083fce6b4528e16d735dbeecbb2bdb45e0dbbf3f7e17"
|
||||
checksum = "f65d16b699dd1a1fa2d851c970b0c971b388eeeb40f744252b8de48860980c8f"
|
||||
dependencies = [
|
||||
"aead 0.5.2",
|
||||
"aes-gcm 0.10.3",
|
||||
"byteorder",
|
||||
"chacha20poly1305 0.10.1",
|
||||
"digest 0.10.7",
|
||||
"aead",
|
||||
"aes-gcm",
|
||||
"chacha20poly1305",
|
||||
"digest",
|
||||
"generic-array",
|
||||
"hkdf 0.12.4",
|
||||
"hmac 0.12.1",
|
||||
"rand_core",
|
||||
"sha2 0.10.9",
|
||||
"hkdf",
|
||||
"hmac",
|
||||
"rand_core 0.9.3",
|
||||
"sha2",
|
||||
"subtle",
|
||||
"x25519-dalek",
|
||||
"zeroize",
|
||||
|
|
@ -1806,30 +1659,31 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "ohttp"
|
||||
version = "0.5.4"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10a20082b908632960d0aa59af61e2771502b40249d55986e8bdbcd06d723ea5"
|
||||
checksum = "e482d6d044fb97988130b6719a91c846006030c9424f943c817b81adf3969126"
|
||||
dependencies = [
|
||||
"aead 0.4.3",
|
||||
"aes-gcm 0.9.2",
|
||||
"aead",
|
||||
"aes-gcm",
|
||||
"byteorder",
|
||||
"chacha20poly1305 0.8.0",
|
||||
"chacha20poly1305",
|
||||
"futures",
|
||||
"hex",
|
||||
"hkdf 0.11.0",
|
||||
"hkdf",
|
||||
"hpke",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"rand",
|
||||
"pin-project",
|
||||
"rand 0.9.2",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"sha2 0.9.9",
|
||||
"thiserror 1.0.69",
|
||||
"toml 0.5.11",
|
||||
"sha2",
|
||||
"thiserror 2.0.12",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ohttp-gateway"
|
||||
version = "0.1.5"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
|
|
@ -1846,7 +1700,7 @@ dependencies = [
|
|||
"jsonwebtoken",
|
||||
"ohttp",
|
||||
"prometheus",
|
||||
"rand",
|
||||
"rand 0.8.5",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
|
@ -2025,7 +1879,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "edd1101f170f5903fde0914f899bb503d9ff5271d7ba76bbb70bea63690cc0d5"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"sha2 0.10.9",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2066,38 +1920,15 @@ version = "0.3.32"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
||||
|
||||
[[package]]
|
||||
name = "poly1305"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede"
|
||||
dependencies = [
|
||||
"cpufeatures 0.2.17",
|
||||
"opaque-debug",
|
||||
"universal-hash 0.4.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "poly1305"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf"
|
||||
dependencies = [
|
||||
"cpufeatures 0.2.17",
|
||||
"cpufeatures",
|
||||
"opaque-debug",
|
||||
"universal-hash 0.5.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "polyval"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures 0.2.17",
|
||||
"opaque-debug",
|
||||
"universal-hash 0.4.0",
|
||||
"universal-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2107,9 +1938,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures 0.2.17",
|
||||
"cpufeatures",
|
||||
"opaque-debug",
|
||||
"universal-hash 0.5.1",
|
||||
"universal-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2233,8 +2064,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
"rand_chacha 0.3.1",
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
|
||||
dependencies = [
|
||||
"rand_chacha 0.9.0",
|
||||
"rand_core 0.9.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2244,7 +2085,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core 0.9.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2256,6 +2107,15 @@ dependencies = [
|
|||
"getrandom 0.2.16",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
|
||||
dependencies = [
|
||||
"getrandom 0.3.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "raw-cpuid"
|
||||
version = "11.5.0"
|
||||
|
|
@ -2577,19 +2437,6 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.9.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800"
|
||||
dependencies = [
|
||||
"block-buffer 0.9.0",
|
||||
"cfg-if",
|
||||
"cpufeatures 0.2.17",
|
||||
"digest 0.9.0",
|
||||
"opaque-debug",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.9"
|
||||
|
|
@ -2597,8 +2444,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures 0.2.17",
|
||||
"digest 0.10.7",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2936,15 +2783,6 @@ dependencies = [
|
|||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.5.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.23"
|
||||
|
|
@ -3184,16 +3022,6 @@ version = "1.12.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
|
||||
|
||||
[[package]]
|
||||
name = "universal-hash"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8326b2c654932e3e4f9196e69d08fdf7cfd718e1dc6f66b347e6024a0c961402"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "universal-hash"
|
||||
version = "0.5.1"
|
||||
|
|
@ -3688,7 +3516,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277"
|
||||
dependencies = [
|
||||
"curve25519-dalek",
|
||||
"rand_core",
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
[package]
|
||||
authors = ["Bastian Gruber <foreach@me.com>"]
|
||||
version = "0.1.5"
|
||||
version = "1.0.0"
|
||||
edition = "2024"
|
||||
name = "ohttp-gateway"
|
||||
categories = ["web-programming", "web-programming::http-server"]
|
||||
description = "A OHTTP Gateway server, meant to run between a OHTTP Relay and a target web service."
|
||||
documentation = "https://docs.rs/ohttp-gateway"
|
||||
keywords = ["ohttp", "gateway", "server", "privacy"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
license = "MPL-2.0"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/gruberb/ohttp-gateway"
|
||||
|
||||
|
|
@ -22,8 +22,8 @@ hyper-util = { version = "0.1", features = ["full"] }
|
|||
reqwest = { version = "0.12", features = ["json", "stream"] }
|
||||
|
||||
# OHTTP implementation - Using the martinthomson/ohttp crate
|
||||
ohttp = { version = "0.5", features = ["rust-hpke"] }
|
||||
bhttp = "0.5"
|
||||
ohttp = { version = "0.7.1", features = ["rust-hpke"] }
|
||||
bhttp = "0.7.1"
|
||||
|
||||
# Middleware and utilities
|
||||
tower = "0.4"
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ OHTTP enables clients to make HTTP requests without revealing their identity to
|
|||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
Client -> Relay -> Gateway -> Target Server
|
||||
| |
|
||||
| v
|
||||
|
|
@ -18,7 +19,7 @@ Client -> Relay -> Gateway -> Target Server
|
|||
| [Encrypt Response]
|
||||
| |
|
||||
<--------+
|
||||
|
||||
```
|
||||
|
||||
This implementation serves as the Gateway component, handling:
|
||||
- HPKE-encrypted request decapsulation
|
||||
|
|
@ -43,7 +44,7 @@ The gateway is configured via environment variables:
|
|||
|
||||
### Basic Configuration
|
||||
```bash
|
||||
LISTEN_ADDR=0.0.0.0:8080 # Server bind address
|
||||
PORT="8080" # Server port
|
||||
BACKEND_URL=http://localhost:8080 # Default backend URL
|
||||
REQUEST_TIMEOUT=30 # Request timeout in seconds
|
||||
MAX_BODY_SIZE=10485760 # Maximum request body size (10MB)
|
||||
|
|
@ -108,7 +109,7 @@ export ALLOWED_TARGET_ORIGINS=httpbin.org
|
|||
|
||||
### Key Configuration
|
||||
|
||||
- `GET /ohttp-keys` - Retrieve current key configuration
|
||||
- `GET /ohttp-configs` - Retrieve current key configuration
|
||||
- Returns `application/ohttp-keys` content type
|
||||
- Used by clients to obtain encryption keys
|
||||
|
||||
|
|
@ -123,7 +124,7 @@ Clients need the key configuration to encrypt requests:
|
|||
|
||||
```bash
|
||||
# Fetch key configuration
|
||||
curl -H "Accept: application/ohttp-keys" https://gateway:8080/ohttp-keys
|
||||
curl -H "Accept: application/ohttp-keys" https://gateway:8080/ohttp-configs
|
||||
|
||||
# Send OHTTP request (encrypted)
|
||||
curl -X POST \
|
||||
|
|
|
|||
|
|
@ -37,15 +37,6 @@ pub async fn get_ohttp_keys(State(state): State<AppState>) -> Result<Response, S
|
|||
}
|
||||
}
|
||||
|
||||
/// Legacy endpoint for backward compatibility
|
||||
/// Some older clients may still use /ohttp-configs
|
||||
pub async fn get_legacy_ohttp_configs(
|
||||
State(state): State<AppState>,
|
||||
) -> Result<Response, StatusCode> {
|
||||
// Just forward to the main handler
|
||||
get_ohttp_keys(State(state)).await
|
||||
}
|
||||
|
||||
/// Calculate appropriate cache duration for key configurations
|
||||
fn calculate_cache_max_age(state: &AppState) -> u64 {
|
||||
// Cache for 10% of rotation interval, minimum 1 hour, maximum 24 hours
|
||||
|
|
|
|||
|
|
@ -12,9 +12,7 @@ pub fn routes() -> Router<AppState> {
|
|||
Router::new()
|
||||
// OHTTP endpoints
|
||||
.route("/gateway", post(ohttp::handle_ohttp_request))
|
||||
.route("/ohttp-keys", get(keys::get_ohttp_keys))
|
||||
// Legacy endpoints for backward compatibility
|
||||
.route("/ohttp-configs", get(keys::get_legacy_ohttp_configs))
|
||||
.route("/ohttp-configs", get(keys::get_ohttp_keys))
|
||||
// Health and monitoring
|
||||
.route("/health", get(health::health_check))
|
||||
.route("/health/keys", get(keys::key_health_check))
|
||||
|
|
|
|||
|
|
@ -90,6 +90,8 @@ async fn handle_ohttp_request_inner(
|
|||
GatewayError::DecryptionError(format!("Failed to decapsulate: {e}"))
|
||||
})?;
|
||||
|
||||
debug!("Request: {:#?}", bhttp_request);
|
||||
|
||||
debug!(
|
||||
"Successfully decapsulated request, {} bytes",
|
||||
bhttp_request.len()
|
||||
|
|
@ -178,6 +180,8 @@ fn validate_ohttp_request(
|
|||
/// Parse binary HTTP message with error handling
|
||||
fn parse_bhttp_message(data: &[u8]) -> Result<Message, GatewayError> {
|
||||
let mut cursor = std::io::Cursor::new(data);
|
||||
debug!("Cursor: std::io::Cursor::new(data): {:?}", cursor);
|
||||
|
||||
Message::read_bhttp(&mut cursor)
|
||||
.map_err(|e| GatewayError::InvalidRequest(format!("Failed to parse binary HTTP: {e}")))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -206,7 +206,7 @@ impl KeyManager {
|
|||
keys.get(&key_id).map(|info| info.server.clone())
|
||||
}
|
||||
|
||||
/// Get encoded config for backward compatibility
|
||||
/// Get encoded config with length prefix per RFC 9458 Section 3.2
|
||||
pub async fn get_encoded_config(&self) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
|
||||
let keys = self.keys.read().await;
|
||||
let active_id = self.active_key_id.read().await;
|
||||
|
|
@ -216,8 +216,9 @@ impl KeyManager {
|
|||
.config
|
||||
.encode()?;
|
||||
|
||||
let mut out = Vec::with_capacity(cfg_bytes.len() + 2);
|
||||
out.extend_from_slice(&(cfg_bytes.len() as u16).to_be_bytes()); // 2-byte length
|
||||
let mut out = Vec::with_capacity(2 + cfg_bytes.len());
|
||||
// Add 2-byte length prefix in network byte order per RFC 9458
|
||||
out.extend_from_slice(&(cfg_bytes.len() as u16).to_be_bytes());
|
||||
out.extend_from_slice(&cfg_bytes);
|
||||
Ok(out)
|
||||
}
|
||||
|
|
@ -296,11 +297,11 @@ impl KeyManager {
|
|||
loop {
|
||||
interval.tick().await;
|
||||
|
||||
if manager.should_rotate().await {
|
||||
if let Err(e) = manager.rotate_keys().await {
|
||||
if manager.should_rotate().await
|
||||
&& let Err(e) = manager.rotate_keys().await
|
||||
{
|
||||
error!("Key rotation failed: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Also clean up expired keys
|
||||
manager.cleanup_expired_keys().await;
|
||||
|
|
|
|||
|
|
@ -164,15 +164,15 @@ pub async fn request_validation_middleware(
|
|||
}
|
||||
|
||||
// Validate User-Agent
|
||||
if let Some(user_agent) = headers.get(header::USER_AGENT) {
|
||||
if let Ok(ua_str) = user_agent.to_str() {
|
||||
if let Some(user_agent) = headers.get(header::USER_AGENT)
|
||||
&& let Ok(ua_str) = user_agent.to_str()
|
||||
{
|
||||
// Block known bad user agents
|
||||
if ua_str.is_empty() || ua_str.contains("bot") || ua_str.contains("crawler") {
|
||||
info!("Blocked suspicious user agent: {}", ua_str);
|
||||
return Err(StatusCode::FORBIDDEN);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(next.run(request).await)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use hyper::StatusCode;
|
||||
use axum::http::StatusCode;
|
||||
use rand::Rng;
|
||||
|
||||
use ohttp_gateway::{key_manager::KeyManager, key_manager::KeyManagerConfig};
|
||||
|
|
@ -90,9 +90,13 @@ async fn test_config_handler() {
|
|||
let cache_control = response.get_header("Cache-Control").unwrap();
|
||||
validate_cache_control_header(cache_control).unwrap();
|
||||
|
||||
// Check body is not empty and has expected structure
|
||||
// Check body is not empty and has expected structure with 2-byte length prefix
|
||||
assert!(!response.body.is_empty());
|
||||
assert!(response.body.len() >= 4); // At least length prefix + some config data
|
||||
assert!(response.body.len() >= 4); // At least 2-byte length prefix + some config data
|
||||
|
||||
// Verify the length prefix is correct per RFC 9458
|
||||
let length_prefix = u16::from_be_bytes([response.body[0], response.body[1]]);
|
||||
assert_eq!(length_prefix as usize, response.body.len() - 2);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
|
|
|||
|
|
@ -162,15 +162,19 @@ async fn test_config_serialization_format() {
|
|||
|
||||
let encoded_config = manager.get_encoded_config().await.unwrap();
|
||||
|
||||
// Verify basic structure: length prefix + config data
|
||||
assert!(encoded_config.len() >= 4);
|
||||
// Verify we have config data with 2-byte length prefix per RFC 9458
|
||||
assert!(!encoded_config.is_empty());
|
||||
assert!(encoded_config.len() > 12); // At least 2 bytes for length + some config data
|
||||
|
||||
let length = u16::from_be_bytes([encoded_config[0], encoded_config[1]]);
|
||||
assert_eq!(length as usize, encoded_config.len() - 2);
|
||||
// Verify the length prefix is correct
|
||||
let length_prefix = u16::from_be_bytes([encoded_config[0], encoded_config[1]]);
|
||||
assert_eq!(length_prefix as usize, encoded_config.len() - 2);
|
||||
|
||||
// Verify it contains expected OHTTP key configuration elements
|
||||
// The exact format would depend on your implementation
|
||||
let config_data = &encoded_config[2..];
|
||||
// Verify it contains reasonable OHTTP key configuration data
|
||||
assert!(encoded_config.len() < 1000); // Reasonable upper bound
|
||||
|
||||
// The config should be the length-prefixed encoded configuration
|
||||
let config_data = &encoded_config[2..]; // Skip the length prefix
|
||||
assert!(!config_data.is_empty());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
use bhttp::Message;
|
||||
use ohttp_gateway::key_manager::{CipherSuiteConfig, KeyManager, KeyManagerConfig};
|
||||
use std::io::Cursor;
|
||||
use std::time::Duration;
|
||||
use tokio;
|
||||
|
||||
// Your key manager module - adjust the import path as needed
|
||||
use ohttp_gateway::key_manager::{CipherSuiteConfig, KeyManager, KeyManagerConfig};
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_key_generation() {
|
||||
let config = KeyManagerConfig::default();
|
||||
|
|
@ -112,12 +112,18 @@ async fn test_get_encoded_config() {
|
|||
|
||||
let encoded_config = manager.get_encoded_config().await.unwrap();
|
||||
|
||||
// Should have at least 4 bytes (2 bytes length + some config data)
|
||||
assert!(encoded_config.len() >= 4);
|
||||
// Should have config data with 2-byte length prefix per RFC 9458
|
||||
assert!(!encoded_config.is_empty());
|
||||
assert!(encoded_config.len() > 2);
|
||||
|
||||
// First 2 bytes should be length in big endian
|
||||
let length = u16::from_be_bytes([encoded_config[0], encoded_config[1]]);
|
||||
assert_eq!(length as usize, encoded_config.len() - 2);
|
||||
// Verify the length prefix is correct
|
||||
let length_prefix = u16::from_be_bytes([encoded_config[0], encoded_config[1]]);
|
||||
assert_eq!(length_prefix as usize, encoded_config.len() - 2);
|
||||
|
||||
// The encoded config should be the length-prefixed config bytes
|
||||
// We can't easily verify the exact content without duplicating the encoding logic,
|
||||
// but we can at least verify it's reasonable in size
|
||||
assert!(encoded_config.len() < 1000); // Reasonable upper bound
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
|
@ -170,3 +176,29 @@ async fn test_cleanup_expired_keys() {
|
|||
// Should have cleaned up the expired key
|
||||
assert!(final_stats.total_keys <= 2);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_bhttp_parsing() {
|
||||
// let data = &[
|
||||
// 2, 3, 71, 69, 84, 5, 104, 116, 116, 112, 115, 9, 108, 111, 99, 97, 108, 104, 111, 115, 116,
|
||||
// 4, 47, 103, 101, 116, 10, 117, 115, 101, 114, 45, 97, 103, 101, 110, 116, 21, 79, 72, 84,
|
||||
// 84, 80, 45, 84, 101, 115, 116, 45, 67, 108, 105, 101, 110, 116, 47, 49, 46, 48, 6, 97, 99,
|
||||
// 99, 101, 112, 116, 16, 97, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 47, 106, 115,
|
||||
// 111, 110, 0, 0,
|
||||
// ];
|
||||
// let mut cursor = std::io::Cursor::new(data);
|
||||
//
|
||||
// let m = Message::read_bhttp(&mut cursor).unwrap();
|
||||
//
|
||||
// println!("TEST {:?}", m);
|
||||
|
||||
const REQUEST: &[u8] = &[
|
||||
2, 3, 71, 69, 84, 5, 104, 116, 116, 112, 115, 9, 108, 111, 99, 97, 108, 104, 111, 115, 116,
|
||||
4, 47, 103, 101, 116, 10, 117, 115, 101, 114, 45, 97, 103, 101, 110, 116, 21, 79, 72, 84,
|
||||
84, 80, 45, 84, 101, 115, 116, 45, 67, 108, 105, 101, 110, 116, 47, 49, 46, 48, 6, 97, 99,
|
||||
99, 101, 112, 116, 16, 97, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 47, 106, 115,
|
||||
111, 110, 0, 0, 0,
|
||||
];
|
||||
let m = Message::read_bhttp(&mut Cursor::new(REQUEST)).unwrap();
|
||||
assert!(m.header().get(b"accept").is_some());
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue