ttk_server/service/
router.rs1use crate::client::{EnclaveCertVerifier, TtkClient};
13use axum::extract::State;
14use axum::http::StatusCode;
15use axum::routing::{get, post};
16use axum::{Json, Router};
17use base64::{engine::general_purpose::STANDARD, Engine as _};
18use log::{info, warn};
19use serde::{Deserialize, Serialize};
20use std::sync::Arc;
21
22type SendError = Box<dyn std::error::Error + Send + Sync>;
24
25#[derive(Clone, Debug)]
27pub struct Evidence {
28 pub nitro: Vec<u8>,
30 pub eat: Vec<u8>,
32}
33
34pub const DEFAULT_RELAY_PORT: u16 = 4433;
36
37pub const RELAY_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
39
40pub const RELAY_CONNECT_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(3);
42
43pub const ALLOW_MOCK_RELAY_ENV: &str = "TTK_ALLOW_MOCK_ATTESTATION";
45
46pub type RelayVerifierFactory = Arc<dyn Fn() -> EnclaveCertVerifier + Send + Sync>;
49
50#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
56pub struct FafRequest {
57 #[serde(default, skip_serializing_if = "Option::is_none")]
60 pub relay_server: Option<String>,
61 pub message: String,
63 pub key: String,
65}
66
67pub(crate) fn build_router(
69 evidence: Arc<Evidence>,
70 relay_verifier: RelayVerifierFactory,
71) -> Router {
72 let eat_b64 = STANDARD.encode(&evidence.eat);
73
74 Router::new()
75 .route("/", get(|| async { "Hello from Enclave over HTTP/3!" }))
76 .route("/evidence.eat", get(move || async move { eat_b64 }))
77 .route("/faf", post(faf))
78 .with_state(relay_verifier)
79}
80
81async fn faf(
88 State(relay_verifier): State<RelayVerifierFactory>,
89 Json(request): Json<FafRequest>,
90) -> (StatusCode, String) {
91 let FafRequest {
92 relay_server,
93 message,
94 key,
95 } = request;
96
97 let Some(relay_server) = relay_server else {
98 info!(
100 "/faf: accepted a {}-byte message as the last hop",
101 message.len()
102 );
103 return (StatusCode::OK, "delivered".to_string());
104 };
105
106 let (host, port) = match parse_relay_server(&relay_server) {
107 Ok(target) => target,
108 Err(e) => {
109 return (
110 StatusCode::BAD_REQUEST,
111 format!("invalid relay_server: {e}"),
112 )
113 }
114 };
115 let forward = FafRequest {
116 relay_server: None,
117 message,
118 key,
119 };
120
121 let relayed = tokio::time::timeout(
122 RELAY_TIMEOUT,
123 forward_to_relay(&host, port, relay_verifier(), &forward),
124 )
125 .await;
126 match relayed {
127 Ok(Ok(StatusCode::OK)) => {
128 info!("/faf: relayed to {host}:{port}");
129 (StatusCode::OK, "relayed".to_string())
130 }
131 Ok(Ok(status)) => {
132 warn!("/faf: relay {host}:{port} answered {status}");
133 (StatusCode::BAD_GATEWAY, format!("relay answered {status}"))
134 }
135 Ok(Err(e)) => {
136 warn!("/faf: relaying to {host}:{port} failed: {e}");
137 (StatusCode::BAD_GATEWAY, format!("relay failed: {e}"))
138 }
139 Err(_) => {
140 warn!("/faf: relay {host}:{port} timed out");
141 (StatusCode::GATEWAY_TIMEOUT, "relay timed out".to_string())
142 }
143 }
144}
145
146pub fn parse_relay_server(relay_server: &str) -> Result<(String, u16), String> {
149 let uri: axum::http::Uri = relay_server
150 .trim()
151 .parse()
152 .map_err(|e| format!("{relay_server:?}: {e}"))?;
153 if let Some(scheme) = uri.scheme_str() {
154 if scheme != "https" {
155 return Err(format!(
156 "unsupported scheme {scheme:?}; the relay speaks HTTP/3"
157 ));
158 }
159 }
160 let authority = uri
161 .authority()
162 .ok_or_else(|| format!("{relay_server:?} has no host"))?;
163 if authority.as_str().contains('@') {
164 return Err("user info is not allowed".to_string());
165 }
166 let host = authority
167 .host()
168 .trim_start_matches('[')
169 .trim_end_matches(']');
170 if host.is_empty() {
171 return Err(format!("{relay_server:?} has no host"));
172 }
173 let port = authority.port_u16().unwrap_or(DEFAULT_RELAY_PORT);
174 Ok((host.to_string(), port))
175}
176
177async fn connect_to_relay(
181 host: &str,
182 port: u16,
183 verifier: EnclaveCertVerifier,
184) -> Result<TtkClient, SendError> {
185 let mut last_error: SendError = format!("{host} did not resolve").into();
186 for addr in tokio::net::lookup_host((host, port)).await? {
187 let connecting = TtkClient::connect_with_verifier(addr, host, verifier.clone());
188 match tokio::time::timeout(RELAY_CONNECT_TIMEOUT, connecting).await {
189 Ok(Ok(client)) => return Ok(client),
190 Ok(Err(e)) => last_error = e,
191 Err(_) => last_error = format!("connecting to {addr} timed out").into(),
192 }
193 }
194 Err(last_error)
195}
196
197async fn forward_to_relay(
200 host: &str,
201 port: u16,
202 verifier: EnclaveCertVerifier,
203 request: &FafRequest,
204) -> Result<StatusCode, SendError> {
205 let mut client = connect_to_relay(host, port, verifier).await?;
206 let response = client.post_json("/faf", request).await;
207 let _ = client.close().await;
208 Ok(response?.status)
209}