Skip to main content

ttk_server/service/
server.rs

1//! RATS (RFC 9334) role mapping for this server:
2//!
3//! - **Attester**: this process, running inside a Nitro Enclave. It holds a
4//!   hardware-rooted identity via the Nitro Security Module (NSM).
5//! - **Evidence**: the NSM Attestation Document produced by [`attestation::detect`],
6//!   with `user_data` bound to the SHA-256 hash of the ephemeral TLS key's
7//!   SubjectPublicKeyInfo so a Relying Party can tie the Evidence to the TLS session.
8//! - **Endorsements**: the AWS Nitro certificate chain embedded in the Attestation
9//!   Document, rooted at the AWS Nitro Enclaves root certificate.
10//! - **Verifier** / **Relying Party**: the external client fetching Evidence as an
11//!   RFC 9711 EAT over `/evidence.eat` (or from the RA-TLS certificate). Appraisal against
12//!   Reference Values (expected PCR measurements) and issuance of an Attestation Result
13//!   happen outside this server.
14//!
15//! This module owns attestation, the RA-TLS certificate and the QUIC / HTTP/3 transport; the
16//! HTTP routes, including the `POST /faf` relay, live in [`super::router`].
17
18use super::router::{build_router, RelayVerifierFactory, ALLOW_MOCK_RELAY_ENV};
19use crate::client::EnclaveCertVerifier;
20use crate::{attestation, AttestationParams};
21use axum::http::StatusCode;
22use axum::response::IntoResponse;
23use axum::Router;
24use bytes::{Buf, Bytes, BytesMut};
25use log::{info, warn};
26use quinn::{Endpoint, ServerConfig};
27use rcgen::{CertificateParams, CustomExtension, KeyPair, SanType};
28use rustls::pki_types::{pem::PemObject, CertificateDer, PrivateKeyDer};
29use std::net::SocketAddr;
30use std::sync::Arc;
31use time::{Duration, OffsetDateTime};
32use tower_service::Service;
33
34pub use super::router::Evidence;
35
36/// OID of the X.509 extension carrying the attestation document (placeholder, not a registered PEN).
37const ATTESTATION_OID: &[u64] = &[1, 3, 6, 1, 4, 1, 99999, 1];
38
39/// Creates a self-signed certificate for `key_pair` with `attestation_doc` embedded as a
40/// non-critical X.509 extension, and returns it PEM-encoded.
41pub fn create_cert_with_attestation(
42    key_pair: &KeyPair,
43    common_name: &str,
44    attestation_doc: &[u8], // <--- Attestation document passed as a parameter
45    validity_days: i64,
46) -> Result<String, Box<dyn std::error::Error>> {
47    let mut params = CertificateParams::default();
48
49    // Set Subject Name
50    // let mut dn = DistinguishName::new();
51    // dn.push(DnType::CommonName, common_name);
52    // params.distinguished_name = dn;
53
54    // Set Validity Period
55    let now = OffsetDateTime::now_utc();
56    params.not_before = now;
57    params.not_after = now + Duration::days(validity_days);
58
59    // Subject Alternative Name
60    params.subject_alt_names = vec![SanType::DnsName(common_name.try_into()?)];
61
62    // --------------------------------------------------------------------
63    // WRAP AND ATTACH ATTESTATION DOCUMENT AS X.509 EXTENSION
64    // --------------------------------------------------------------------
65    // Wrap the raw attestation bytes into an ASN.1 OCTET STRING header
66    let der_encoded_payload = wrap_in_asn1_octet_string(attestation_doc);
67
68    let mut attestation_ext =
69        CustomExtension::from_oid_content(ATTESTATION_OID, der_encoded_payload);
70
71    // Set to false unless you want parsers to fail if they don't recognize the OID
72    attestation_ext.set_criticality(false);
73
74    params.custom_extensions.push(attestation_ext);
75
76    // Sign the certificate
77    let cert = params.self_signed(key_pair)?;
78    Ok(cert.pem())
79}
80
81/// Helper to wrap raw binary bytes in an ASN.1 OCTET STRING TLV header
82fn wrap_in_asn1_octet_string(data: &[u8]) -> Vec<u8> {
83    let mut encoded = Vec::new();
84    encoded.push(0x04); // ASN.1 Tag for OCTET STRING
85
86    let len = data.len();
87    if len < 128 {
88        encoded.push(len as u8);
89    } else if len <= 0xFF {
90        encoded.push(0x81);
91        encoded.push(len as u8);
92    } else if len <= 0xFFFF {
93        encoded.push(0x82);
94        encoded.extend_from_slice(&(len as u16).to_be_bytes());
95    } else {
96        encoded.push(0x84);
97        encoded.extend_from_slice(&(len as u32).to_be_bytes());
98    }
99
100    encoded.extend_from_slice(data);
101    encoded
102}
103
104/// Boxed error type used by the server functions.
105type BoxError = Box<dyn std::error::Error>;
106
107/// Boxed error type that can cross task boundaries.
108type SendError = Box<dyn std::error::Error + Send + Sync>;
109
110/// Default address the QUIC endpoint binds to, unless [`LISTEN_ADDR_ENV`] overrides it.
111const LISTEN_ADDR: &str = "0.0.0.0:4433";
112
113/// Environment variable that overrides the default listen address `0.0.0.0:4433`
114/// (a socket address such as `127.0.0.1:4444`).
115pub const LISTEN_ADDR_ENV: &str = "TTK_LISTEN_ADDR";
116
117/// Largest request body the server reads; larger requests get `413 Payload Too Large`.
118pub const MAX_REQUEST_BODY: usize = 1024 * 1024;
119
120/// Runs the server: attests, builds the RA-TLS identity, then serves HTTP/3 on
121/// `TTK_LISTEN_ADDR` (default `0.0.0.0:4433`) until the endpoint closes.
122///
123/// Relays must present genuine TEE attestation unless `TTK_ALLOW_MOCK_ATTESTATION=1`.
124pub async fn run() -> Result<(), BoxError> {
125    let listen_addr = std::env::var(LISTEN_ADDR_ENV).unwrap_or_else(|_| LISTEN_ADDR.to_string());
126    let listen_addr: SocketAddr = listen_addr
127        .parse()
128        .map_err(|e| format!("invalid {LISTEN_ADDR_ENV} {listen_addr:?}: {e}"))?;
129    let mut server = Server::bind(listen_addr)?;
130    if std::env::var(ALLOW_MOCK_RELAY_ENV).is_ok_and(|v| v == "1") {
131        warn!("Accepting MOCK attestation from relay servers ({ALLOW_MOCK_RELAY_ENV}=1)");
132        server = server.with_relay_verifier(|| EnclaveCertVerifier::new().allow_mock());
133    }
134    info!("Server listening on {} (QUIC/HTTP/3)", server.local_addr()?);
135    server.serve().await;
136    Ok(())
137}
138
139/// An attested HTTP/3 server bound to a QUIC endpoint.
140pub struct Server {
141    endpoint: Endpoint,
142    evidence: Arc<Evidence>,
143    relay_verifier: RelayVerifierFactory,
144}
145
146/// Setup and serving.
147impl Server {
148    /// Attests, builds the RA-TLS identity and binds the QUIC endpoint to `addr`.
149    ///
150    /// Must be called within a Tokio runtime. Binding port 0 picks a free port; see
151    /// [`local_addr`](Self::local_addr).
152    pub fn bind(addr: SocketAddr) -> Result<Self, BoxError> {
153        info!("Initializing Nitro Enclave HTTP/3 Server...");
154
155        // Install the default cryptographic provider for rustls 0.23
156        let _ = rustls::crypto::ring::default_provider().install_default();
157
158        let key_pair = KeyPair::generate()?;
159        info!("Generated ephemeral TLS certificate.");
160
161        let eat_bytes = generate_evidence(&key_pair)?;
162        let tls_config = build_tls_config(&key_pair, &eat_bytes)?;
163        let evidence = Arc::new(Evidence {
164            nitro: eat_bytes.clone(),
165            eat: eat_bytes,
166        });
167
168        let quic_config = ServerConfig::with_crypto(Arc::new(
169            quinn::crypto::rustls::QuicServerConfig::try_from(tls_config)?,
170        ));
171        let endpoint = Endpoint::server(quic_config, addr)?;
172        Ok(Self {
173            endpoint,
174            evidence,
175            relay_verifier: Arc::new(EnclaveCertVerifier::new),
176        })
177    }
178
179    /// Sets the policy for attesting relay servers in `POST /faf`.
180    ///
181    /// Defaults to [`EnclaveCertVerifier::new`], which accepts only genuine TEE evidence.
182    pub fn with_relay_verifier(
183        mut self,
184        verifier: impl Fn() -> EnclaveCertVerifier + Send + Sync + 'static,
185    ) -> Self {
186        self.relay_verifier = Arc::new(verifier);
187        self
188    }
189
190    /// Returns the address the endpoint is bound to.
191    pub fn local_addr(&self) -> std::io::Result<SocketAddr> {
192        self.endpoint.local_addr()
193    }
194
195    /// Accepts QUIC connections and serves HTTP/3 until the endpoint closes.
196    pub async fn serve(self) {
197        let app = build_router(self.evidence, self.relay_verifier);
198        while let Some(incoming) = self.endpoint.accept().await {
199            tokio::spawn(handle_connection(incoming, app.clone()));
200        }
201    }
202}
203
204/// Requests evidence from the detected attestation provider, bound to the TLS public key,
205/// and returns it as CBOR-encoded RFC 9711 EAT bytes.
206fn generate_evidence(key_pair: &KeyPair) -> Result<Vec<u8>, BoxError> {
207    let params = AttestationParams::new().with_user_data_hash(&key_pair.public_key_der());
208
209    let provider = attestation::detect()?;
210    info!("Using attestation provider: {}", provider.name());
211    let eat_bytes = provider.generate_document(&params)?.to_cbor_bytes()?;
212    info!(
213        "Wrapped Attestation Document as RFC 9711 EAT token ({} bytes).",
214        eat_bytes.len()
215    );
216    Ok(eat_bytes)
217}
218
219/// Builds the rustls config using a self-signed RA-TLS certificate carrying `eat_bytes`.
220fn build_tls_config(
221    key_pair: &KeyPair,
222    eat_bytes: &[u8],
223) -> Result<rustls::ServerConfig, BoxError> {
224    let cert_pem = create_cert_with_attestation(key_pair, "enclave.internal", eat_bytes, 30)?;
225    let key_pem = key_pair.serialize_pem();
226
227    let certs =
228        CertificateDer::pem_slice_iter(cert_pem.as_bytes()).collect::<Result<Vec<_>, _>>()?;
229    let key = PrivateKeyDer::from_pem_slice(key_pem.as_bytes())?;
230
231    let mut config = rustls::ServerConfig::builder()
232        .with_no_client_auth()
233        .with_single_cert(certs, key)?;
234    // Enable ALPN for HTTP/3 ("h3")
235    config.alpn_protocols = vec![b"h3".to_vec()];
236    Ok(config)
237}
238
239/// Drives a single QUIC connection, dispatching each HTTP/3 request to `app`.
240async fn handle_connection(incoming: quinn::Incoming, app: Router) {
241    let conn = match incoming.await {
242        Ok(conn) => conn,
243        Err(err) => return eprintln!("Handshake failed: {err}"),
244    };
245
246    let mut h3_conn =
247        match h3::server::Connection::<_, axum::body::Bytes>::new(h3_quinn::Connection::new(conn))
248            .await
249        {
250            Ok(h3) => h3,
251            Err(e) => return eprintln!("H3 setup failed: {e}"),
252        };
253
254    while let Ok(Some((req, stream))) = h3_conn.accept().await {
255        let app = app.clone();
256        tokio::spawn(respond(app, req, stream));
257    }
258}
259
260/// HTTP/3 request stream on the server side.
261type ServerStream =
262    h3::server::RequestStream<h3_quinn::BidiStream<axum::body::Bytes>, axum::body::Bytes>;
263
264/// Reads the request body from `stream`. Returns `None` if it exceeds [`MAX_REQUEST_BODY`].
265async fn read_body(stream: &mut ServerStream) -> Result<Option<Bytes>, SendError> {
266    let mut body = BytesMut::new();
267    while let Some(mut chunk) = stream.recv_data().await? {
268        if body.len() + chunk.remaining() > MAX_REQUEST_BODY {
269            return Ok(None);
270        }
271        while chunk.has_remaining() {
272            let slice = chunk.chunk();
273            body.extend_from_slice(slice);
274            let len = slice.len();
275            chunk.advance(len);
276        }
277    }
278    Ok(Some(body.freeze()))
279}
280
281/// Reads the body of `req`, runs it through `app` and streams the response back over the
282/// HTTP/3 `stream`.
283async fn respond(mut app: Router, req: axum::http::Request<()>, mut stream: ServerStream) {
284    let response = match read_body(&mut stream).await {
285        Ok(Some(body)) => match app.call(req.map(|()| axum::body::Body::from(body))).await {
286            Ok(response) => response,
287            Err(e) => return eprintln!("App call error: {e}"),
288        },
289        Ok(None) => (StatusCode::PAYLOAD_TOO_LARGE, "request body too large").into_response(),
290        Err(e) => return eprintln!("Failed to read request body: {e}"),
291    };
292
293    let (parts, body) = response.into_parts();
294    if let Err(e) = stream
295        .send_response(axum::http::Response::from_parts(parts, ()))
296        .await
297    {
298        return eprintln!("Failed to send response headers: {e}");
299    }
300    match axum::body::to_bytes(body, usize::MAX).await {
301        Ok(bytes) if !bytes.is_empty() => {
302            if let Err(e) = stream.send_data(bytes).await {
303                return eprintln!("Failed to send response body: {e}");
304            }
305        }
306        Ok(_) => {}
307        Err(e) => eprintln!("Failed to read response body: {e}"),
308    }
309    if let Err(e) = stream.finish().await {
310        eprintln!("Failed to finish stream: {e}");
311    }
312}