use crate::{attestation, AttestationParams};
use axum::{routing::get, Router};
use base64::{engine::general_purpose::STANDARD, Engine as _};
use log::info;
use quinn::{Endpoint, ServerConfig};
use rcgen::{CertificateParams, CustomExtension, KeyPair, SanType};
use rustls::pki_types::{pem::PemObject, CertificateDer, PrivateKeyDer};
use std::net::SocketAddr;
use std::sync::Arc;
use time::{Duration, OffsetDateTime};
use tower_service::Service;
const ATTESTATION_OID: &[u64] = &[1, 3, 6, 1, 4, 1, 99999, 1];
#[derive(Clone, Debug)]
pub struct Evidence {
pub nitro: Vec<u8>,
pub eat: Vec<u8>,
}
pub fn create_cert_with_attestation(
key_pair: &KeyPair,
common_name: &str,
attestation_doc: &[u8], validity_days: i64,
) -> Result<String, Box<dyn std::error::Error>> {
let mut params = CertificateParams::default();
let now = OffsetDateTime::now_utc();
params.not_before = now;
params.not_after = now + Duration::days(validity_days);
params.subject_alt_names = vec![SanType::DnsName(common_name.try_into()?)];
let der_encoded_payload = wrap_in_asn1_octet_string(attestation_doc);
let mut attestation_ext =
CustomExtension::from_oid_content(ATTESTATION_OID, der_encoded_payload);
attestation_ext.set_criticality(false);
params.custom_extensions.push(attestation_ext);
let cert = params.self_signed(key_pair)?;
Ok(cert.pem())
}
fn wrap_in_asn1_octet_string(data: &[u8]) -> Vec<u8> {
let mut encoded = Vec::new();
encoded.push(0x04);
let len = data.len();
if len < 128 {
encoded.push(len as u8);
} else if len <= 0xFF {
encoded.push(0x81);
encoded.push(len as u8);
} else if len <= 0xFFFF {
encoded.push(0x82);
encoded.extend_from_slice(&(len as u16).to_be_bytes());
} else {
encoded.push(0x84);
encoded.extend_from_slice(&(len as u32).to_be_bytes());
}
encoded.extend_from_slice(data);
encoded
}
type BoxError = Box<dyn std::error::Error>;
const LISTEN_ADDR: &str = "0.0.0.0:4433";
pub async fn run() -> Result<(), BoxError> {
let server = Server::bind(LISTEN_ADDR.parse()?)?;
info!("Server listening on {} (QUIC/HTTP/3)", server.local_addr()?);
server.serve().await;
Ok(())
}
pub struct Server {
endpoint: Endpoint,
app: Router,
}
impl Server {
pub fn bind(addr: SocketAddr) -> Result<Self, BoxError> {
info!("Initializing Nitro Enclave HTTP/3 Server...");
let _ = rustls::crypto::ring::default_provider().install_default();
let key_pair = KeyPair::generate()?;
info!("Generated ephemeral TLS certificate.");
let eat_bytes = generate_evidence(&key_pair)?;
let tls_config = build_tls_config(&key_pair, &eat_bytes)?;
let app = build_router(Arc::new(Evidence {
nitro: eat_bytes.clone(),
eat: eat_bytes,
}));
let quic_config = ServerConfig::with_crypto(Arc::new(
quinn::crypto::rustls::QuicServerConfig::try_from(tls_config)?,
));
let endpoint = Endpoint::server(quic_config, addr)?;
Ok(Self { endpoint, app })
}
pub fn local_addr(&self) -> std::io::Result<SocketAddr> {
self.endpoint.local_addr()
}
pub async fn serve(self) {
while let Some(incoming) = self.endpoint.accept().await {
tokio::spawn(handle_connection(incoming, self.app.clone()));
}
}
}
fn generate_evidence(key_pair: &KeyPair) -> Result<Vec<u8>, BoxError> {
let params = AttestationParams::new().with_user_data_hash(&key_pair.public_key_der());
let provider = attestation::detect()?;
info!("Using attestation provider: {}", provider.name());
let eat_bytes = provider.generate_document(¶ms)?.to_cbor_bytes()?;
info!(
"Wrapped Attestation Document as RFC 9711 EAT token ({} bytes).",
eat_bytes.len()
);
Ok(eat_bytes)
}
fn build_tls_config(
key_pair: &KeyPair,
eat_bytes: &[u8],
) -> Result<rustls::ServerConfig, BoxError> {
let cert_pem = create_cert_with_attestation(key_pair, "enclave.internal", eat_bytes, 30)?;
let key_pem = key_pair.serialize_pem();
let certs =
CertificateDer::pem_slice_iter(cert_pem.as_bytes()).collect::<Result<Vec<_>, _>>()?;
let key = PrivateKeyDer::from_pem_slice(key_pem.as_bytes())?;
let mut config = rustls::ServerConfig::builder()
.with_no_client_auth()
.with_single_cert(certs, key)?;
config.alpn_protocols = vec![b"h3".to_vec()];
Ok(config)
}
fn build_router(evidence: Arc<Evidence>) -> Router {
let nitro_b64 = STANDARD.encode(&evidence.nitro);
let eat_b64 = STANDARD.encode(&evidence.eat);
let text = |body: String| get(move || async move { body });
Router::new()
.route("/", get(|| async { "Hello from Enclave over HTTP/3!" }))
.route("/hello", get(|| async { "Hello from inside the Enclave!" }))
.route("/evidence", text(nitro_b64.clone()))
.route("/attestation", text(nitro_b64))
.route("/evidence.eat", text(eat_b64))
}
async fn handle_connection(incoming: quinn::Incoming, app: Router) {
let conn = match incoming.await {
Ok(conn) => conn,
Err(err) => return eprintln!("Handshake failed: {err}"),
};
let mut h3_conn =
match h3::server::Connection::<_, axum::body::Bytes>::new(h3_quinn::Connection::new(conn))
.await
{
Ok(h3) => h3,
Err(e) => return eprintln!("H3 setup failed: {e}"),
};
while let Ok(Some((req, stream))) = h3_conn.accept().await {
let app = app.clone();
tokio::spawn(async move {
let req = req.map(|_| axum::body::Body::empty());
respond(app, req, stream).await;
});
}
}
async fn respond(
mut app: Router,
req: axum::http::Request<axum::body::Body>,
mut stream: h3::server::RequestStream<
h3_quinn::BidiStream<axum::body::Bytes>,
axum::body::Bytes,
>,
) {
let response = match app.call(req).await {
Ok(response) => response,
Err(e) => return eprintln!("App call error: {e}"),
};
let (parts, body) = response.into_parts();
if let Err(e) = stream
.send_response(axum::http::Response::from_parts(parts, ()))
.await
{
return eprintln!("Failed to send response headers: {e}");
}
match axum::body::to_bytes(body, usize::MAX).await {
Ok(bytes) if !bytes.is_empty() => {
if let Err(e) = stream.send_data(bytes).await {
return eprintln!("Failed to send response body: {e}");
}
}
Ok(_) => {}
Err(e) => eprintln!("Failed to read response body: {e}"),
}
if let Err(e) = stream.finish().await {
eprintln!("Failed to finish stream: {e}");
}
}