Compare commits

...

10 commits

Author SHA1 Message Date
66d3471a0d
Release v1.0.0: RFC 9458 compliant OHTTP gateway with key configuration fixes
- Update dependencies to ohttp 0.7.1 and bhttp 0.7.1
- Fix key configuration format to include required 2-byte length prefix per RFC 9458
- Update license to match actual MPL-2.0 license file
- Fix clippy warnings for better code quality
- Add comprehensive CHANGELOG.md for v1.0.0 release

This release provides a production-ready, RFC 9458 compliant OHTTP gateway
with proper key management, security controls, and observability features.
2025-11-23 16:33:09 -04:00
c4bbebbc3e
Delete example file 2025-11-23 16:28:17 -04:00
367ff8b10e
Re-Add length bytes to keys endpoint 2025-11-23 16:27:24 -04:00
8a68df9e54
fix tests 2025-07-24 13:21:37 +02:00
d14ce407fa
Remove added length byte for keys 2025-07-24 13:09:17 +02:00
9fa095873c
Update Cargo.lock 2025-07-22 17:46:46 +02:00
b7a20e936c
Update README 2025-07-22 17:46:27 +02:00
b07ec6f35c
Update to version 0.2.0 2025-07-22 17:45:06 +02:00
15f8be4227
Change endpoint to /ohttp-configs 2025-07-22 17:44:43 +02:00
2ce5b5fd67
update README 2025-07-22 13:51:55 +02:00
12 changed files with 242 additions and 312 deletions

67
CHANGELOG.md Normal file
View 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
View file

@ -17,16 +17,6 @@ version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" 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]] [[package]]
name = "aead" name = "aead"
version = "0.5.2" version = "0.5.2"
@ -37,18 +27,6 @@ dependencies = [
"generic-array", "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]] [[package]]
name = "aes" name = "aes"
version = "0.8.4" version = "0.8.4"
@ -56,22 +34,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"cipher 0.4.4", "cipher",
"cpufeatures 0.2.17", "cpufeatures",
]
[[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",
] ]
[[package]] [[package]]
@ -80,11 +44,11 @@ version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1"
dependencies = [ dependencies = [
"aead 0.5.2", "aead",
"aes 0.8.4", "aes",
"cipher 0.4.4", "cipher",
"ctr 0.9.2", "ctr",
"ghash 0.5.1", "ghash",
"subtle", "subtle",
] ]
@ -360,11 +324,11 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]] [[package]]
name = "bhttp" name = "bhttp"
version = "0.5.4" version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bc657efe5aa3821f1cacfb47665c32849e09820844bff9f5066227312829fa3" checksum = "44c659d6e2707aa970a0482d3f1a0f23ff470a423b0f52961632bbf3000b7ff7"
dependencies = [ dependencies = [
"thiserror 1.0.69", "thiserror 2.0.12",
] ]
[[package]] [[package]]
@ -376,15 +340,6 @@ dependencies = [
"serde", "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]] [[package]]
name = "block-buffer" name = "block-buffer"
version = "0.10.4" version = "0.10.4"
@ -448,18 +403,6 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" 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]] [[package]]
name = "chacha20" name = "chacha20"
version = "0.9.1" version = "0.9.1"
@ -467,21 +410,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"cipher 0.4.4", "cipher",
"cpufeatures 0.2.17", "cpufeatures",
]
[[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",
] ]
[[package]] [[package]]
@ -490,10 +420,10 @@ version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35"
dependencies = [ dependencies = [
"aead 0.5.2", "aead",
"chacha20 0.9.1", "chacha20",
"cipher 0.4.4", "cipher",
"poly1305 0.8.0", "poly1305",
"zeroize", "zeroize",
] ]
@ -511,15 +441,6 @@ dependencies = [
"windows-link", "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]] [[package]]
name = "cipher" name = "cipher"
version = "0.4.4" version = "0.4.4"
@ -592,7 +513,7 @@ dependencies = [
"rust-ini", "rust-ini",
"serde", "serde",
"serde_json", "serde_json",
"toml 0.8.23", "toml",
"yaml-rust2", "yaml-rust2",
] ]
@ -641,15 +562,6 @@ version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "cpufeatures"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66c99696f6c9dd7f35d486b9d04d7e6e202aa3e8c40d553f2fdf5e7e0c6a71ef"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "cpufeatures" name = "cpufeatures"
version = "0.2.17" version = "0.2.17"
@ -687,36 +599,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [ dependencies = [
"generic-array", "generic-array",
"rand_core", "rand_core 0.6.4",
"typenum", "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]] [[package]]
name = "ctr" name = "ctr"
version = "0.9.2" version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835"
dependencies = [ dependencies = [
"cipher 0.4.4", "cipher",
] ]
[[package]] [[package]]
@ -726,7 +619,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "373b7c5dbd637569a2cca66e8d66b8c446a1e7bf064ea321d265d7b3dfe7c97e" checksum = "373b7c5dbd637569a2cca66e8d66b8c446a1e7bf064ea321d265d7b3dfe7c97e"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"cpufeatures 0.2.17", "cpufeatures",
"curve25519-dalek-derive", "curve25519-dalek-derive",
"fiat-crypto", "fiat-crypto",
"rustc_version", "rustc_version",
@ -788,22 +681,13 @@ dependencies = [
"powerfmt", "powerfmt",
] ]
[[package]]
name = "digest"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
dependencies = [
"generic-array",
]
[[package]] [[package]]
name = "digest" name = "digest"
version = "0.10.7" version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [ dependencies = [
"block-buffer 0.10.4", "block-buffer",
"crypto-common", "crypto-common",
"subtle", "subtle",
] ]
@ -1019,16 +903,6 @@ dependencies = [
"wasi 0.14.2+wasi-0.2.4", "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]] [[package]]
name = "ghash" name = "ghash"
version = "0.5.1" version = "0.5.1"
@ -1036,7 +910,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1"
dependencies = [ dependencies = [
"opaque-debug", "opaque-debug",
"polyval 0.6.2", "polyval",
] ]
[[package]] [[package]]
@ -1107,33 +981,13 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 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]] [[package]]
name = "hkdf" name = "hkdf"
version = "0.12.4" version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7"
dependencies = [ dependencies = [
"hmac 0.12.1", "hmac",
]
[[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",
] ]
[[package]] [[package]]
@ -1142,25 +996,24 @@ version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
dependencies = [ dependencies = [
"digest 0.10.7", "digest",
] ]
[[package]] [[package]]
name = "hpke" name = "hpke"
version = "0.11.0" version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e04a5933a381bb81f00b083fce6b4528e16d735dbeecbb2bdb45e0dbbf3f7e17" checksum = "f65d16b699dd1a1fa2d851c970b0c971b388eeeb40f744252b8de48860980c8f"
dependencies = [ dependencies = [
"aead 0.5.2", "aead",
"aes-gcm 0.10.3", "aes-gcm",
"byteorder", "chacha20poly1305",
"chacha20poly1305 0.10.1", "digest",
"digest 0.10.7",
"generic-array", "generic-array",
"hkdf 0.12.4", "hkdf",
"hmac 0.12.1", "hmac",
"rand_core", "rand_core 0.9.3",
"sha2 0.10.9", "sha2",
"subtle", "subtle",
"x25519-dalek", "x25519-dalek",
"zeroize", "zeroize",
@ -1806,30 +1659,31 @@ dependencies = [
[[package]] [[package]]
name = "ohttp" name = "ohttp"
version = "0.5.4" version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10a20082b908632960d0aa59af61e2771502b40249d55986e8bdbcd06d723ea5" checksum = "e482d6d044fb97988130b6719a91c846006030c9424f943c817b81adf3969126"
dependencies = [ dependencies = [
"aead 0.4.3", "aead",
"aes-gcm 0.9.2", "aes-gcm",
"byteorder", "byteorder",
"chacha20poly1305 0.8.0", "chacha20poly1305",
"futures",
"hex", "hex",
"hkdf 0.11.0", "hkdf",
"hpke", "hpke",
"lazy_static",
"log", "log",
"rand", "pin-project",
"rand 0.9.2",
"serde", "serde",
"serde_derive", "serde_derive",
"sha2 0.9.9", "sha2",
"thiserror 1.0.69", "thiserror 2.0.12",
"toml 0.5.11", "toml",
] ]
[[package]] [[package]]
name = "ohttp-gateway" name = "ohttp-gateway"
version = "0.1.5" version = "1.0.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"axum", "axum",
@ -1846,7 +1700,7 @@ dependencies = [
"jsonwebtoken", "jsonwebtoken",
"ohttp", "ohttp",
"prometheus", "prometheus",
"rand", "rand 0.8.5",
"reqwest", "reqwest",
"serde", "serde",
"serde_json", "serde_json",
@ -2025,7 +1879,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edd1101f170f5903fde0914f899bb503d9ff5271d7ba76bbb70bea63690cc0d5" checksum = "edd1101f170f5903fde0914f899bb503d9ff5271d7ba76bbb70bea63690cc0d5"
dependencies = [ dependencies = [
"pest", "pest",
"sha2 0.10.9", "sha2",
] ]
[[package]] [[package]]
@ -2066,38 +1920,15 @@ version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" 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]] [[package]]
name = "poly1305" name = "poly1305"
version = "0.8.0" version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf"
dependencies = [ dependencies = [
"cpufeatures 0.2.17", "cpufeatures",
"opaque-debug", "opaque-debug",
"universal-hash 0.5.1", "universal-hash",
]
[[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",
] ]
[[package]] [[package]]
@ -2107,9 +1938,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"cpufeatures 0.2.17", "cpufeatures",
"opaque-debug", "opaque-debug",
"universal-hash 0.5.1", "universal-hash",
] ]
[[package]] [[package]]
@ -2233,8 +2064,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [ dependencies = [
"libc", "libc",
"rand_chacha", "rand_chacha 0.3.1",
"rand_core", "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]] [[package]]
@ -2244,7 +2085,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [ dependencies = [
"ppv-lite86", "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]] [[package]]
@ -2256,6 +2107,15 @@ dependencies = [
"getrandom 0.2.16", "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]] [[package]]
name = "raw-cpuid" name = "raw-cpuid"
version = "11.5.0" version = "11.5.0"
@ -2577,19 +2437,6 @@ dependencies = [
"serde", "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]] [[package]]
name = "sha2" name = "sha2"
version = "0.10.9" version = "0.10.9"
@ -2597,8 +2444,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"cpufeatures 0.2.17", "cpufeatures",
"digest 0.10.7", "digest",
] ]
[[package]] [[package]]
@ -2936,15 +2783,6 @@ dependencies = [
"tokio", "tokio",
] ]
[[package]]
name = "toml"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "toml" name = "toml"
version = "0.8.23" version = "0.8.23"
@ -3184,16 +3022,6 @@ version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" 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]] [[package]]
name = "universal-hash" name = "universal-hash"
version = "0.5.1" version = "0.5.1"
@ -3688,7 +3516,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277"
dependencies = [ dependencies = [
"curve25519-dalek", "curve25519-dalek",
"rand_core", "rand_core 0.6.4",
] ]
[[package]] [[package]]

View file

@ -1,13 +1,13 @@
[package] [package]
authors = ["Bastian Gruber <foreach@me.com>"] authors = ["Bastian Gruber <foreach@me.com>"]
version = "0.1.5" version = "1.0.0"
edition = "2024" edition = "2024"
name = "ohttp-gateway" name = "ohttp-gateway"
categories = ["web-programming", "web-programming::http-server"] categories = ["web-programming", "web-programming::http-server"]
description = "A OHTTP Gateway server, meant to run between a OHTTP Relay and a target web service." description = "A OHTTP Gateway server, meant to run between a OHTTP Relay and a target web service."
documentation = "https://docs.rs/ohttp-gateway" documentation = "https://docs.rs/ohttp-gateway"
keywords = ["ohttp", "gateway", "server", "privacy"] keywords = ["ohttp", "gateway", "server", "privacy"]
license = "MIT OR Apache-2.0" license = "MPL-2.0"
readme = "README.md" readme = "README.md"
repository = "https://github.com/gruberb/ohttp-gateway" 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"] } reqwest = { version = "0.12", features = ["json", "stream"] }
# OHTTP implementation - Using the martinthomson/ohttp crate # OHTTP implementation - Using the martinthomson/ohttp crate
ohttp = { version = "0.5", features = ["rust-hpke"] } ohttp = { version = "0.7.1", features = ["rust-hpke"] }
bhttp = "0.5" bhttp = "0.7.1"
# Middleware and utilities # Middleware and utilities
tower = "0.4" tower = "0.4"

View file

@ -10,6 +10,7 @@ OHTTP enables clients to make HTTP requests without revealing their identity to
## Architecture ## Architecture
```
Client -> Relay -> Gateway -> Target Server Client -> Relay -> Gateway -> Target Server
| | | |
| v | v
@ -18,7 +19,7 @@ Client -> Relay -> Gateway -> Target Server
| [Encrypt Response] | [Encrypt Response]
| | | |
<--------+ <--------+
```
This implementation serves as the Gateway component, handling: This implementation serves as the Gateway component, handling:
- HPKE-encrypted request decapsulation - HPKE-encrypted request decapsulation
@ -43,7 +44,7 @@ The gateway is configured via environment variables:
### Basic Configuration ### Basic Configuration
```bash ```bash
LISTEN_ADDR=0.0.0.0:8080 # Server bind address PORT="8080" # Server port
BACKEND_URL=http://localhost:8080 # Default backend URL BACKEND_URL=http://localhost:8080 # Default backend URL
REQUEST_TIMEOUT=30 # Request timeout in seconds REQUEST_TIMEOUT=30 # Request timeout in seconds
MAX_BODY_SIZE=10485760 # Maximum request body size (10MB) MAX_BODY_SIZE=10485760 # Maximum request body size (10MB)
@ -108,7 +109,7 @@ export ALLOWED_TARGET_ORIGINS=httpbin.org
### Key Configuration ### Key Configuration
- `GET /ohttp-keys` - Retrieve current key configuration - `GET /ohttp-configs` - Retrieve current key configuration
- Returns `application/ohttp-keys` content type - Returns `application/ohttp-keys` content type
- Used by clients to obtain encryption keys - Used by clients to obtain encryption keys
@ -123,7 +124,7 @@ Clients need the key configuration to encrypt requests:
```bash ```bash
# Fetch key configuration # 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) # Send OHTTP request (encrypted)
curl -X POST \ curl -X POST \

View file

@ -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 /// Calculate appropriate cache duration for key configurations
fn calculate_cache_max_age(state: &AppState) -> u64 { fn calculate_cache_max_age(state: &AppState) -> u64 {
// Cache for 10% of rotation interval, minimum 1 hour, maximum 24 hours // Cache for 10% of rotation interval, minimum 1 hour, maximum 24 hours

View file

@ -12,9 +12,7 @@ pub fn routes() -> Router<AppState> {
Router::new() Router::new()
// OHTTP endpoints // OHTTP endpoints
.route("/gateway", post(ohttp::handle_ohttp_request)) .route("/gateway", post(ohttp::handle_ohttp_request))
.route("/ohttp-keys", get(keys::get_ohttp_keys)) .route("/ohttp-configs", get(keys::get_ohttp_keys))
// Legacy endpoints for backward compatibility
.route("/ohttp-configs", get(keys::get_legacy_ohttp_configs))
// Health and monitoring // Health and monitoring
.route("/health", get(health::health_check)) .route("/health", get(health::health_check))
.route("/health/keys", get(keys::key_health_check)) .route("/health/keys", get(keys::key_health_check))

View file

@ -90,6 +90,8 @@ async fn handle_ohttp_request_inner(
GatewayError::DecryptionError(format!("Failed to decapsulate: {e}")) GatewayError::DecryptionError(format!("Failed to decapsulate: {e}"))
})?; })?;
debug!("Request: {:#?}", bhttp_request);
debug!( debug!(
"Successfully decapsulated request, {} bytes", "Successfully decapsulated request, {} bytes",
bhttp_request.len() bhttp_request.len()
@ -178,6 +180,8 @@ fn validate_ohttp_request(
/// Parse binary HTTP message with error handling /// Parse binary HTTP message with error handling
fn parse_bhttp_message(data: &[u8]) -> Result<Message, GatewayError> { fn parse_bhttp_message(data: &[u8]) -> Result<Message, GatewayError> {
let mut cursor = std::io::Cursor::new(data); let mut cursor = std::io::Cursor::new(data);
debug!("Cursor: std::io::Cursor::new(data): {:?}", cursor);
Message::read_bhttp(&mut cursor) Message::read_bhttp(&mut cursor)
.map_err(|e| GatewayError::InvalidRequest(format!("Failed to parse binary HTTP: {e}"))) .map_err(|e| GatewayError::InvalidRequest(format!("Failed to parse binary HTTP: {e}")))
} }

View file

@ -206,7 +206,7 @@ impl KeyManager {
keys.get(&key_id).map(|info| info.server.clone()) 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>> { pub async fn get_encoded_config(&self) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let keys = self.keys.read().await; let keys = self.keys.read().await;
let active_id = self.active_key_id.read().await; let active_id = self.active_key_id.read().await;
@ -216,8 +216,9 @@ impl KeyManager {
.config .config
.encode()?; .encode()?;
let mut out = Vec::with_capacity(cfg_bytes.len() + 2); let mut out = Vec::with_capacity(2 + cfg_bytes.len());
out.extend_from_slice(&(cfg_bytes.len() as u16).to_be_bytes()); // 2-byte length // 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); out.extend_from_slice(&cfg_bytes);
Ok(out) Ok(out)
} }
@ -296,10 +297,10 @@ impl KeyManager {
loop { loop {
interval.tick().await; interval.tick().await;
if manager.should_rotate().await { if manager.should_rotate().await
if let Err(e) = manager.rotate_keys().await { && let Err(e) = manager.rotate_keys().await
error!("Key rotation failed: {}", e); {
} error!("Key rotation failed: {}", e);
} }
// Also clean up expired keys // Also clean up expired keys

View file

@ -164,13 +164,13 @@ pub async fn request_validation_middleware(
} }
// Validate User-Agent // Validate User-Agent
if let Some(user_agent) = headers.get(header::USER_AGENT) { if let Some(user_agent) = headers.get(header::USER_AGENT)
if let Ok(ua_str) = user_agent.to_str() { && 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") { // Block known bad user agents
info!("Blocked suspicious user agent: {}", ua_str); if ua_str.is_empty() || ua_str.contains("bot") || ua_str.contains("crawler") {
return Err(StatusCode::FORBIDDEN); info!("Blocked suspicious user agent: {}", ua_str);
} return Err(StatusCode::FORBIDDEN);
} }
} }

View file

@ -1,4 +1,4 @@
use hyper::StatusCode; use axum::http::StatusCode;
use rand::Rng; use rand::Rng;
use ohttp_gateway::{key_manager::KeyManager, key_manager::KeyManagerConfig}; 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(); let cache_control = response.get_header("Cache-Control").unwrap();
validate_cache_control_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.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] #[tokio::test]

View file

@ -162,15 +162,19 @@ async fn test_config_serialization_format() {
let encoded_config = manager.get_encoded_config().await.unwrap(); let encoded_config = manager.get_encoded_config().await.unwrap();
// Verify basic structure: length prefix + config data // Verify we have config data with 2-byte length prefix per RFC 9458
assert!(encoded_config.len() >= 4); 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]]); // Verify the length prefix is correct
assert_eq!(length as usize, encoded_config.len() - 2); 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 // Verify it contains reasonable OHTTP key configuration data
// The exact format would depend on your implementation assert!(encoded_config.len() < 1000); // Reasonable upper bound
let config_data = &encoded_config[2..];
// The config should be the length-prefixed encoded configuration
let config_data = &encoded_config[2..]; // Skip the length prefix
assert!(!config_data.is_empty()); assert!(!config_data.is_empty());
} }

View file

@ -1,9 +1,9 @@
use bhttp::Message;
use ohttp_gateway::key_manager::{CipherSuiteConfig, KeyManager, KeyManagerConfig};
use std::io::Cursor;
use std::time::Duration; use std::time::Duration;
use tokio; use tokio;
// Your key manager module - adjust the import path as needed
use ohttp_gateway::key_manager::{CipherSuiteConfig, KeyManager, KeyManagerConfig};
#[tokio::test] #[tokio::test]
async fn test_key_generation() { async fn test_key_generation() {
let config = KeyManagerConfig::default(); let config = KeyManagerConfig::default();
@ -112,12 +112,18 @@ async fn test_get_encoded_config() {
let encoded_config = manager.get_encoded_config().await.unwrap(); let encoded_config = manager.get_encoded_config().await.unwrap();
// Should have at least 4 bytes (2 bytes length + some config data) // Should have config data with 2-byte length prefix per RFC 9458
assert!(encoded_config.len() >= 4); assert!(!encoded_config.is_empty());
assert!(encoded_config.len() > 2);
// First 2 bytes should be length in big endian // Verify the length prefix is correct
let length = u16::from_be_bytes([encoded_config[0], encoded_config[1]]); let length_prefix = u16::from_be_bytes([encoded_config[0], encoded_config[1]]);
assert_eq!(length as usize, encoded_config.len() - 2); 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] #[tokio::test]
@ -170,3 +176,29 @@ async fn test_cleanup_expired_keys() {
// Should have cleaned up the expired key // Should have cleaned up the expired key
assert!(final_stats.total_keys <= 2); 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());
}