Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions crates/common/src/request_signing/discovery.rs
Original file line number Diff line number Diff line change
@@ -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");
}
}
54 changes: 51 additions & 3 deletions crates/common/src/request_signing/endpoints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Response, Report<TrustedServerError>> {
// 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)]
Expand Down Expand Up @@ -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),
}
}
}
2 changes: 2 additions & 0 deletions crates/common/src/request_signing/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand Down
9 changes: 6 additions & 3 deletions crates/fastly/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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),
Expand Down