ttk_server/service/
server.rs1use crate::{attestation, AttestationParams};
17use axum::{routing::get, Router};
18use base64::{engine::general_purpose::STANDARD, Engine as _};
19use log::info;
20use quinn::{Endpoint, ServerConfig};
21use rcgen::{CertificateParams, CustomExtension, KeyPair, SanType};
22use rustls::pki_types::{pem::PemObject, CertificateDer, PrivateKeyDer};
23use std::net::SocketAddr;
24use std::sync::Arc;
25use time::{Duration, OffsetDateTime};
26use tower_service::Service;
27
28const ATTESTATION_OID: &[u64] = &[1, 3, 6, 1, 4, 1, 99999, 1];
30
31#[derive(Clone, Debug)]
33pub struct Evidence {
34 pub nitro: Vec<u8>,
36 pub eat: Vec<u8>,
38}
39
40pub fn create_cert_with_attestation(
43 key_pair: &KeyPair,
44 common_name: &str,
45 attestation_doc: &[u8], validity_days: i64,
47) -> Result<String, Box<dyn std::error::Error>> {
48 let mut params = CertificateParams::default();
49
50 let now = OffsetDateTime::now_utc();
57 params.not_before = now;
58 params.not_after = now + Duration::days(validity_days);
59
60 params.subject_alt_names = vec![SanType::DnsName(common_name.try_into()?)];
62
63 let der_encoded_payload = wrap_in_asn1_octet_string(attestation_doc);
68
69 let mut attestation_ext =
70 CustomExtension::from_oid_content(ATTESTATION_OID, der_encoded_payload);
71
72 attestation_ext.set_criticality(false);
74
75 params.custom_extensions.push(attestation_ext);
76
77 let cert = params.self_signed(key_pair)?;
79 Ok(cert.pem())
80}
81
82fn wrap_in_asn1_octet_string(data: &[u8]) -> Vec<u8> {
84 let mut encoded = Vec::new();
85 encoded.push(0x04); let len = data.len();
88 if len < 128 {
89 encoded.push(len as u8);
90 } else if len <= 0xFF {
91 encoded.push(0x81);
92 encoded.push(len as u8);
93 } else if len <= 0xFFFF {
94 encoded.push(0x82);
95 encoded.extend_from_slice(&(len as u16).to_be_bytes());
96 } else {
97 encoded.push(0x84);
98 encoded.extend_from_slice(&(len as u32).to_be_bytes());
99 }
100
101 encoded.extend_from_slice(data);
102 encoded
103}
104
105type BoxError = Box<dyn std::error::Error>;
107
108const LISTEN_ADDR: &str = "0.0.0.0:4433";
110
111pub async fn run() -> Result<(), BoxError> {
114 let server = Server::bind(LISTEN_ADDR.parse()?)?;
115 info!("Server listening on {} (QUIC/HTTP/3)", server.local_addr()?);
116 server.serve().await;
117 Ok(())
118}
119
120pub struct Server {
122 endpoint: Endpoint,
123 app: Router,
124}
125
126impl Server {
128 pub fn bind(addr: SocketAddr) -> Result<Self, BoxError> {
133 info!("Initializing Nitro Enclave HTTP/3 Server...");
134
135 let _ = rustls::crypto::ring::default_provider().install_default();
137
138 let key_pair = KeyPair::generate()?;
139 info!("Generated ephemeral TLS certificate.");
140
141 let eat_bytes = generate_evidence(&key_pair)?;
142 let tls_config = build_tls_config(&key_pair, &eat_bytes)?;
143 let app = build_router(Arc::new(Evidence {
144 nitro: eat_bytes.clone(),
145 eat: eat_bytes,
146 }));
147
148 let quic_config = ServerConfig::with_crypto(Arc::new(
149 quinn::crypto::rustls::QuicServerConfig::try_from(tls_config)?,
150 ));
151 let endpoint = Endpoint::server(quic_config, addr)?;
152 Ok(Self { endpoint, app })
153 }
154
155 pub fn local_addr(&self) -> std::io::Result<SocketAddr> {
157 self.endpoint.local_addr()
158 }
159
160 pub async fn serve(self) {
162 while let Some(incoming) = self.endpoint.accept().await {
163 tokio::spawn(handle_connection(incoming, self.app.clone()));
164 }
165 }
166}
167
168fn generate_evidence(key_pair: &KeyPair) -> Result<Vec<u8>, BoxError> {
171 let params = AttestationParams::new().with_user_data_hash(&key_pair.public_key_der());
172
173 let provider = attestation::detect()?;
174 info!("Using attestation provider: {}", provider.name());
175 let eat_bytes = provider.generate_document(¶ms)?.to_cbor_bytes()?;
176 info!(
177 "Wrapped Attestation Document as RFC 9711 EAT token ({} bytes).",
178 eat_bytes.len()
179 );
180 Ok(eat_bytes)
181}
182
183fn build_tls_config(
185 key_pair: &KeyPair,
186 eat_bytes: &[u8],
187) -> Result<rustls::ServerConfig, BoxError> {
188 let cert_pem = create_cert_with_attestation(key_pair, "enclave.internal", eat_bytes, 30)?;
189 let key_pem = key_pair.serialize_pem();
190
191 let certs =
192 CertificateDer::pem_slice_iter(cert_pem.as_bytes()).collect::<Result<Vec<_>, _>>()?;
193 let key = PrivateKeyDer::from_pem_slice(key_pem.as_bytes())?;
194
195 let mut config = rustls::ServerConfig::builder()
196 .with_no_client_auth()
197 .with_single_cert(certs, key)?;
198 config.alpn_protocols = vec![b"h3".to_vec()];
200 Ok(config)
201}
202
203fn build_router(evidence: Arc<Evidence>) -> Router {
205 let nitro_b64 = STANDARD.encode(&evidence.nitro);
206 let eat_b64 = STANDARD.encode(&evidence.eat);
207
208 let text = |body: String| get(move || async move { body });
209
210 Router::new()
211 .route("/", get(|| async { "Hello from Enclave over HTTP/3!" }))
212 .route("/hello", get(|| async { "Hello from inside the Enclave!" }))
213 .route("/evidence", text(nitro_b64.clone()))
214 .route("/attestation", text(nitro_b64))
215 .route("/evidence.eat", text(eat_b64))
216}
217
218async fn handle_connection(incoming: quinn::Incoming, app: Router) {
220 let conn = match incoming.await {
221 Ok(conn) => conn,
222 Err(err) => return eprintln!("Handshake failed: {err}"),
223 };
224
225 let mut h3_conn =
226 match h3::server::Connection::<_, axum::body::Bytes>::new(h3_quinn::Connection::new(conn))
227 .await
228 {
229 Ok(h3) => h3,
230 Err(e) => return eprintln!("H3 setup failed: {e}"),
231 };
232
233 while let Ok(Some((req, stream))) = h3_conn.accept().await {
234 let app = app.clone();
235 tokio::spawn(async move {
236 let req = req.map(|_| axum::body::Body::empty());
237 respond(app, req, stream).await;
238 });
239 }
240}
241
242async fn respond(
244 mut app: Router,
245 req: axum::http::Request<axum::body::Body>,
246 mut stream: h3::server::RequestStream<
247 h3_quinn::BidiStream<axum::body::Bytes>,
248 axum::body::Bytes,
249 >,
250) {
251 let response = match app.call(req).await {
252 Ok(response) => response,
253 Err(e) => return eprintln!("App call error: {e}"),
254 };
255
256 let (parts, body) = response.into_parts();
257 if let Err(e) = stream
258 .send_response(axum::http::Response::from_parts(parts, ()))
259 .await
260 {
261 return eprintln!("Failed to send response headers: {e}");
262 }
263 match axum::body::to_bytes(body, usize::MAX).await {
264 Ok(bytes) if !bytes.is_empty() => {
265 if let Err(e) = stream.send_data(bytes).await {
266 return eprintln!("Failed to send response body: {e}");
267 }
268 }
269 Ok(_) => {}
270 Err(e) => eprintln!("Failed to read response body: {e}"),
271 }
272 if let Err(e) = stream.finish().await {
273 eprintln!("Failed to finish stream: {e}");
274 }
275}