diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..9a74d12 --- /dev/null +++ b/CHANGELOG.md @@ -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. \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index e7160fd..c1fc9fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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.6.1" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16fc24bc615b9fd63148f59b218ea58a444b55762f8845da910e23aca686398b" +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,29 +1659,31 @@ dependencies = [ [[package]] name = "ohttp" -version = "0.6.1" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622b8959bde5da6c70b0a49e8aa359e0c79c5e8ffd23eb3781c0cc575903d862" +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", "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.2.5" +version = "1.0.0" dependencies = [ "anyhow", "axum", @@ -1845,7 +1700,7 @@ dependencies = [ "jsonwebtoken", "ohttp", "prometheus", - "rand", + "rand 0.8.5", "reqwest", "serde", "serde_json", @@ -2024,7 +1879,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edd1101f170f5903fde0914f899bb503d9ff5271d7ba76bbb70bea63690cc0d5" dependencies = [ "pest", - "sha2 0.10.9", + "sha2", ] [[package]] @@ -2065,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]] @@ -2106,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]] @@ -2232,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]] @@ -2243,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]] @@ -2255,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" @@ -2576,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" @@ -2596,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]] @@ -2935,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" @@ -3183,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" @@ -3687,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]] diff --git a/Cargo.toml b/Cargo.toml index a3252fd..e0bf758 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,13 +1,13 @@ [package] authors = ["Bastian Gruber "] -version = "0.2.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.6", features = ["rust-hpke"] } -bhttp = "0.6" +ohttp = { version = "0.7.1", features = ["rust-hpke"] } +bhttp = "0.7.1" # Middleware and utilities tower = "0.4" diff --git a/src/key_manager.rs b/src/key_manager.rs index 29ac566..1c70371 100644 --- a/src/key_manager.rs +++ b/src/key_manager.rs @@ -297,10 +297,10 @@ impl KeyManager { loop { interval.tick().await; - if manager.should_rotate().await { - if let Err(e) = manager.rotate_keys().await { - error!("Key rotation failed: {}", e); - } + if manager.should_rotate().await + && let Err(e) = manager.rotate_keys().await + { + error!("Key rotation failed: {}", e); } // Also clean up expired keys diff --git a/src/middleware/security.rs b/src/middleware/security.rs index f010d5f..583812a 100644 --- a/src/middleware/security.rs +++ b/src/middleware/security.rs @@ -164,13 +164,13 @@ 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() { - // 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); - } + 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); } }