From 367ff8b10ed9131badaf8e8be721f5f61f5e2e5e Mon Sep 17 00:00:00 2001 From: Bastian Gruber Date: Sun, 23 Nov 2025 16:27:24 -0400 Subject: [PATCH] Re-Add length bytes to keys endpoint --- examples/verify_key_format.rs | 63 +++++++++++++++++++++++++++++++++++ src/key_manager.rs | 6 ++-- tests/config_handler_tests.rs | 10 ++++-- tests/integration_tests.rs | 12 ++++--- tests/key_manager_tests.rs | 10 ++++-- 5 files changed, 89 insertions(+), 12 deletions(-) create mode 100644 examples/verify_key_format.rs diff --git a/examples/verify_key_format.rs b/examples/verify_key_format.rs new file mode 100644 index 0000000..fa40419 --- /dev/null +++ b/examples/verify_key_format.rs @@ -0,0 +1,63 @@ +use ohttp_gateway::key_manager::{KeyManager, KeyManagerConfig}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Create a key manager with default configuration + let config = KeyManagerConfig::default(); + let manager = KeyManager::new(config).await?; + + // Get the encoded configuration + let encoded_config = manager.get_encoded_config().await?; + + println!("Key configuration format verification:"); + println!("====================================="); + + // Check that we have at least 2 bytes for the length prefix + if encoded_config.len() < 2 { + println!("❌ ERROR: Configuration too short (< 2 bytes)"); + return Ok(()); + } + + // Extract the length prefix (first 2 bytes in network byte order) + let length_prefix = u16::from_be_bytes([encoded_config[0], encoded_config[1]]); + let actual_config_length = encoded_config.len() - 2; // Total length minus 2-byte prefix + + println!( + "Total encoded config length: {} bytes", + encoded_config.len() + ); + println!("Length prefix value: {} bytes", length_prefix); + println!("Actual config data length: {} bytes", actual_config_length); + + // Verify the length prefix matches the actual config data length + if length_prefix as usize == actual_config_length { + println!("✅ SUCCESS: Length prefix matches actual config data length"); + println!("✅ SUCCESS: Key configuration format is RFC 9458 compliant"); + } else { + println!( + "❌ ERROR: Length prefix ({}) doesn't match actual config data length ({})", + length_prefix, actual_config_length + ); + return Ok(()); + } + + // Display the first few bytes of the configuration for inspection + println!("\nFirst 16 bytes of encoded configuration (hex):"); + let display_bytes = std::cmp::min(16, encoded_config.len()); + for (i, byte) in encoded_config[..display_bytes].iter().enumerate() { + if i == 2 { + print!(" | "); // Separator between length prefix and config data + } else if i > 2 && (i - 2) % 4 == 0 { + print!(" "); + } + print!("{:02x}", byte); + } + println!(); + println!(" ^^ ^^"); + println!(" Length prefix"); + println!(" (2 bytes, big-endian)"); + + println!("\nFormat verification complete!"); + + Ok(()) +} diff --git a/src/key_manager.rs b/src/key_manager.rs index 1da1463..29ac566 100644 --- a/src/key_manager.rs +++ b/src/key_manager.rs @@ -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, Box> { let keys = self.keys.read().await; let active_id = self.active_key_id.read().await; @@ -216,7 +216,9 @@ impl KeyManager { .config .encode()?; - let mut out = Vec::with_capacity(cfg_bytes.len()); + 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) } diff --git a/tests/config_handler_tests.rs b/tests/config_handler_tests.rs index 85fffc2..cab6090 100644 --- a/tests/config_handler_tests.rs +++ b/tests/config_handler_tests.rs @@ -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] diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 55b1e04..f632ca9 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -162,15 +162,19 @@ async fn test_config_serialization_format() { let encoded_config = manager.get_encoded_config().await.unwrap(); - // Verify we have config data (no length prefix anymore) + // Verify we have config data with 2-byte length prefix per RFC 9458 assert!(!encoded_config.is_empty()); - assert!(encoded_config.len() > 10); + assert!(encoded_config.len() > 12); // At least 2 bytes for length + some config data + + // 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 reasonable OHTTP key configuration data assert!(encoded_config.len() < 1000); // Reasonable upper bound - // The config should be the raw encoded configuration - let config_data = &encoded_config; + // The config should be the length-prefixed encoded configuration + let config_data = &encoded_config[2..]; // Skip the length prefix assert!(!config_data.is_empty()); } diff --git a/tests/key_manager_tests.rs b/tests/key_manager_tests.rs index 99bc8ab..6b8b1cf 100644 --- a/tests/key_manager_tests.rs +++ b/tests/key_manager_tests.rs @@ -112,11 +112,15 @@ async fn test_get_encoded_config() { let encoded_config = manager.get_encoded_config().await.unwrap(); - // Should have some config data (no length prefix anymore) + // Should have config data with 2-byte length prefix per RFC 9458 assert!(!encoded_config.is_empty()); - assert!(encoded_config.len() > 0); + assert!(encoded_config.len() > 2); - // The encoded config should be the raw config bytes without length prefix + // 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