diff --git a/crates/common/src/request_signing/discovery.rs b/crates/common/src/request_signing/discovery.rs new file mode 100644 index 0000000..b5de837 --- /dev/null +++ b/crates/common/src/request_signing/discovery.rs @@ -0,0 +1,88 @@ +//! Discovery endpoint for trusted-server. +//! +//! This module provides a standardized discovery mechanism similar to the IAB's +//! Data Subject Rights framework. The `.well-known/trusted-server.json` endpoint +//! allows partners to discover JWKS keys for signature verification. + +use serde::Serialize; + +/// Main discovery document returned by `.well-known/trusted-server.json` +#[derive(Debug, Serialize)] +pub struct TrustedServerDiscovery { + /// Version of the discovery document format + pub version: String, + + /// JSON Web Key Set containing public keys for signature verification + pub jwks: serde_json::Value, +} + +impl TrustedServerDiscovery { + /// Creates a new discovery document with the given JWKS + pub fn new(jwks_value: serde_json::Value) -> Self { + Self { + version: "1.0".to_string(), + jwks: jwks_value, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + + #[test] + fn test_discovery_document_structure() { + let jwks = json!({ + "keys": [ + { + "kty": "OKP", + "crv": "Ed25519", + "x": "test_key", + "kid": "test-kid" + } + ] + }); + + let discovery = TrustedServerDiscovery::new(jwks); + + assert_eq!(discovery.version, "1.0"); + assert!(discovery.jwks.is_object()); + } + + #[test] + fn test_discovery_document_serialization() { + let jwks = json!({ + "keys": [] + }); + + let discovery = TrustedServerDiscovery::new(jwks); + let serialized = serde_json::to_string(&discovery).unwrap(); + + // Verify it's valid JSON + let parsed: serde_json::Value = serde_json::from_str(&serialized).unwrap(); + + assert_eq!(parsed["version"], "1.0"); + assert!(parsed.get("jwks").is_some()); + assert!(parsed.get("endpoints").is_none()); + } + + #[test] + fn test_discovery_includes_jwks() { + let jwks = json!({ + "keys": [ + { + "kty": "OKP", + "kid": "test-key" + } + ] + }); + + let discovery = TrustedServerDiscovery::new(jwks); + let serialized = serde_json::to_string(&discovery).unwrap(); + let parsed: serde_json::Value = serde_json::from_str(&serialized).unwrap(); + + assert!(parsed["jwks"]["keys"].is_array()); + assert_eq!(parsed["jwks"]["keys"][0]["kid"], "test-key"); + } +} diff --git a/crates/common/src/request_signing/endpoints.rs b/crates/common/src/request_signing/endpoints.rs index 9bd5132..c3073ac 100644 --- a/crates/common/src/request_signing/endpoints.rs +++ b/crates/common/src/request_signing/endpoints.rs @@ -8,24 +8,43 @@ use fastly::{Request, Response}; use serde::{Deserialize, Serialize}; use crate::error::TrustedServerError; +use crate::request_signing::discovery::TrustedServerDiscovery; use crate::request_signing::rotation::KeyRotationManager; use crate::request_signing::signing; use crate::settings::Settings; -/// Retrieves and returns active jwks public keys. -pub fn handle_jwks_endpoint( +/// Retrieves and returns the trusted-server discovery document. +/// +/// This endpoint provides a standardized discovery mechanism following the IAB +/// Data Subject Rights framework pattern. It returns JWKS keys and API endpoints +/// in a single discoverable location. +pub fn handle_trusted_server_discovery( _settings: &Settings, _req: Request, ) -> Result> { + // Get JWKS let jwks_json = crate::request_signing::jwks::get_active_jwks().change_context( TrustedServerError::Configuration { message: "Failed to retrieve JWKS".into(), }, )?; + let jwks_value: serde_json::Value = + serde_json::from_str(&jwks_json).change_context(TrustedServerError::Configuration { + message: "Failed to parse JWKS JSON".into(), + })?; + + let discovery = TrustedServerDiscovery::new(jwks_value); + + let json = serde_json::to_string_pretty(&discovery).change_context( + TrustedServerError::Configuration { + message: "Failed to serialize discovery document".into(), + }, + )?; + Ok(Response::from_status(200) .with_content_type(fastly::mime::APPLICATION_JSON) - .with_body_text_plain(&jwks_json)) + .with_body_text_plain(&json)) } #[derive(Debug, Deserialize, Serialize)] @@ -514,4 +533,33 @@ mod tests { assert_eq!(req.kid, "old-key"); assert!(req.delete); } + + #[test] + fn test_handle_trusted_server_discovery() { + let settings = crate::test_support::tests::create_test_settings(); + let req = Request::new( + Method::GET, + "https://test.com/.well-known/trusted-server.json", + ); + + let result = handle_trusted_server_discovery(&settings, req); + match result { + Ok(mut resp) => { + assert_eq!(resp.get_status(), StatusCode::OK); + let body = resp.take_body_str(); + + // Parse the discovery document + let discovery: serde_json::Value = serde_json::from_str(&body).unwrap(); + + // Verify structure - only version and jwks + assert_eq!(discovery["version"], "1.0"); + assert!(discovery["jwks"].is_object()); + + // Verify no extra fields + assert!(discovery.get("endpoints").is_none()); + assert!(discovery.get("capabilities").is_none()); + } + Err(e) => println!("Expected error in test environment: {}", e), + } + } } diff --git a/crates/common/src/request_signing/mod.rs b/crates/common/src/request_signing/mod.rs index efa9ba7..c6904e4 100644 --- a/crates/common/src/request_signing/mod.rs +++ b/crates/common/src/request_signing/mod.rs @@ -3,11 +3,13 @@ //! This module provides cryptographic signing capabilities using Ed25519 keys, //! including JWKS management, key rotation, and signature verification. +pub mod discovery; pub mod endpoints; pub mod jwks; pub mod rotation; pub mod signing; +pub use discovery::*; pub use endpoints::*; pub use jwks::*; pub use rotation::*; diff --git a/crates/fastly/src/main.rs b/crates/fastly/src/main.rs index 69c0b2b..183fea9 100644 --- a/crates/fastly/src/main.rs +++ b/crates/fastly/src/main.rs @@ -12,7 +12,8 @@ use trusted_server_common::proxy::{ }; use trusted_server_common::publisher::{handle_publisher_request, handle_tsjs_dynamic}; use trusted_server_common::request_signing::{ - handle_deactivate_key, handle_jwks_endpoint, handle_rotate_key, handle_verify_signature, + handle_deactivate_key, handle_rotate_key, handle_trusted_server_discovery, + handle_verify_signature, }; use trusted_server_common::settings::Settings; use trusted_server_common::settings_data::get_settings; @@ -62,8 +63,10 @@ async fn route_request( handle_tsjs_dynamic(&settings, req) } - // JWKS endpoint for public key distribution - (Method::GET, "/.well-known/ts.jwks.json") => handle_jwks_endpoint(&settings, req), + // Discovery endpoint for trusted-server capabilities and JWKS + (Method::GET, "/.well-known/trusted-server.json") => { + handle_trusted_server_discovery(&settings, req) + } // Signature verification endpoint (Method::POST, "/verify-signature") => handle_verify_signature(&settings, req),