diff --git a/Cargo.lock b/Cargo.lock index c1f3904..d0666eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -902,6 +902,25 @@ dependencies = [ "subtle", ] +[[package]] +name = "h2" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.16.0" @@ -1031,6 +1050,7 @@ dependencies = [ "bytes", "futures-channel", "futures-core", + "h2", "http", "http-body", "httparse", @@ -2410,6 +2430,19 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-util" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + [[package]] name = "toml_datetime" version = "0.7.3" diff --git a/Cargo.toml b/Cargo.toml index 3140040..06b72fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ configfs-tsm = "0.0.2" rand_core = { version = "0.6.4", features = ["getrandom"] } dcap-qvl = "0.3.4" hex = "0.4.3" -hyper = { version = "1.7.0", features = ["server"] } +hyper = { version = "1.7.0", features = ["server", "http2"] } hyper-util = "0.1.17" http-body-util = "0.1.3" bytes = "1.10.1" diff --git a/src/lib.rs b/src/lib.rs index 26609fc..5317d6b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,17 +6,18 @@ use bytes::Bytes; use http::HeaderValue; use http_body_util::combinators::BoxBody; use http_body_util::BodyExt; -use hyper::server::conn::http1::Builder; use hyper::service::service_fn; use hyper::Response; use hyper_util::rt::TokioIo; use thiserror::Error; +use tokio::sync::{mpsc, oneshot}; use tokio_rustls::rustls::server::{VerifierBuilderError, WebPkiClientVerifier}; #[cfg(test)] mod test_helpers; use std::num::TryFromIntError; +use std::time::Duration; use std::{net::SocketAddr, sync::Arc}; use tokio::io::{self, AsyncReadExt, AsyncWriteExt}; use tokio::net::{TcpListener, TcpStream, ToSocketAddrs}; @@ -32,11 +33,21 @@ use crate::attestation::{AttesationPayload, AttestationVerifier}; /// The label used when exporting key material from a TLS session const EXPORTER_LABEL: &[u8; 24] = b"EXPORTER-Channel-Binding"; +/// The header name for giving attestation type const ATTESTATION_TYPE_HEADER: &str = "X-Flashbots-Attestation-Type"; /// The header name for giving measurements const MEASUREMENT_HEADER: &str = "X-Flashbots-Measurement"; +/// The longest time in seconds to wait between reconnection attempts +const SERVER_RECONNECT_MAX_BACKOFF_SECS: u64 = 120; + +type RequestWithResponseSender = ( + http::Request, + oneshot::Sender>, hyper::Error>>, +); +type Http2Sender = hyper::client::conn::http2::SendRequest; + /// TLS Credentials pub struct TlsCertAndKey { /// Der-encoded TLS certificate chain @@ -45,19 +56,14 @@ pub struct TlsCertAndKey { pub key: PrivateKeyDer<'static>, } -/// Inner struct used by [ProxyClient] and [ProxyServer] -struct Proxy { +/// A TLS over TCP server which provides an attestation before forwarding traffic to a given target address +pub struct ProxyServer { /// The underlying TCP listener listener: TcpListener, /// Quote generation type to use (including none) local_quote_generator: Arc, /// Verifier for remote attestation (including none) attestation_verifier: AttestationVerifier, -} - -/// A TLS over TCP server which provides an attestation before forwarding traffic to a given target address -pub struct ProxyServer { - inner: Proxy, /// The certificate chain cert_chain: Vec>, /// For accepting TLS connections @@ -118,32 +124,28 @@ impl ProxyServer { let acceptor = tokio_rustls::TlsAcceptor::from(server_config); let listener = TcpListener::bind(local).await?; - let inner = Proxy { + Ok(Self { listener, local_quote_generator, attestation_verifier, - }; - - Ok(Self { acceptor, target, - inner, cert_chain, }) } - /// Accept an incoming connection + /// Accept an incoming connection and handle it in a seperate task pub async fn accept(&self) -> Result<(), ProxyError> { - let (inbound, _client_addr) = self.inner.listener.accept().await?; + let (inbound, _client_addr) = self.listener.accept().await?; let acceptor = self.acceptor.clone(); let target = self.target; let cert_chain = self.cert_chain.clone(); - let local_quote_generator = self.inner.local_quote_generator.clone(); - let attestation_verifier = self.inner.attestation_verifier.clone(); + let local_quote_generator = self.local_quote_generator.clone(); + let attestation_verifier = self.attestation_verifier.clone(); tokio::spawn(async move { if let Err(err) = Self::handle_connection( - inbound, // TODO should be AttestationType + inbound, acceptor, target, cert_chain, @@ -159,10 +161,12 @@ impl ProxyServer { Ok(()) } + /// Helper to get the socket address of the underlying TCP listener pub fn local_addr(&self) -> std::io::Result { - self.inner.listener.local_addr() + self.listener.local_addr() } + /// Handle an incoming connection from a proxy-client async fn handle_connection( inbound: TcpStream, acceptor: TlsAcceptor, @@ -171,9 +175,11 @@ impl ProxyServer { local_quote_generator: Arc, attestation_verifier: AttestationVerifier, ) -> Result<(), ProxyError> { + // Do TLS handshake let mut tls_stream = acceptor.accept(inbound).await?; let (_io, connection) = tls_stream.get_ref(); + // Compute an exporter unique to the session let mut exporter = [0u8; 32]; connection.export_keying_material( &mut exporter, @@ -181,8 +187,10 @@ impl ProxyServer { None, // context )?; + // Get the TLS certficate chain of the client, if there is one let remote_cert_chain = connection.peer_certificates().map(|c| c.to_owned()); + // If we are in a CVM, generate an attestation let attestation = if local_quote_generator.attestation_type() != AttestationType::None { serde_json::to_vec(&AttesationPayload::from_attestation_generator( &cert_chain, @@ -193,12 +201,13 @@ impl ProxyServer { Vec::new() }; + // Write our attestation to the channel, with length prefix let attestation_length_prefix = length_prefix(&attestation); - tls_stream.write_all(&attestation_length_prefix).await?; - tls_stream.write_all(&attestation).await?; + // Now read a length-prefixed attestation from the remote peer + // In the case of no client attestation this will be zero bytes let mut length_bytes = [0; 4]; tls_stream.read_exact(&mut length_bytes).await?; let length: usize = u32::from_be_bytes(length_bytes).try_into()?; @@ -206,6 +215,7 @@ impl ProxyServer { let mut buf = vec![0; length]; tls_stream.read_exact(&mut buf).await?; + // If we expect an attestaion from the client, verify it and get measurements let (measurements, remote_attestation_type) = if attestation_verifier.has_remote_attestion() { let remote_attestation_payload: AttesationPayload = serde_json::from_slice(&buf)?; @@ -225,9 +235,12 @@ impl ProxyServer { (None, AttestationType::None) }; - let http = Builder::new(); + // Setup an HTTP server + let http = hyper::server::conn::http2::Builder::new(TokioExecutor); + + // Setup a request handler let service = service_fn(move |mut req| { - // If we have measurements, add them to the request header + // If we have measurements, from the remote peer, add them to the request header let measurements = measurements.clone(); if let Some(measurements) = measurements { let headers = req.headers_mut(); @@ -244,7 +257,8 @@ impl ProxyServer { } headers.insert( ATTESTATION_TYPE_HEADER, - HeaderValue::from_str(remote_attestation_type.as_str()).unwrap(), + HeaderValue::from_str(remote_attestation_type.as_str()) + .expect("Attestation type should be able to be encoded as a header value"), ); } @@ -254,7 +268,7 @@ impl ProxyServer { Ok::>, hyper::Error>(res) } Err(e) => { - eprintln!("send_request error: {e}"); + eprintln!("Failed to handle a request from a proxy-client: {e}"); let mut resp = Response::new(full(format!("Request failed: {e}"))); *resp.status_mut() = hyper::StatusCode::BAD_GATEWAY; Ok(resp) @@ -263,6 +277,7 @@ impl ProxyServer { } }); + // Serve this connection using the request handler defined above let io = TokioIo::new(tls_stream); http.serve_connection(io, service).await?; @@ -274,11 +289,13 @@ impl ProxyServer { req: hyper::Request, target: SocketAddr, ) -> Result>, ProxyError> { + // Connect to the target server let outbound = TcpStream::connect(target).await?; let outbound_io = TokioIo::new(outbound); let (mut sender, conn) = hyper::client::conn::http1::Builder::new() .handshake::<_, hyper::body::Incoming>(outbound_io) .await?; + // Drive the connection tokio::spawn(async move { if let Err(e) = conn.await { @@ -286,6 +303,7 @@ impl ProxyServer { } }); + // Forward the request from the proxy-client to the target server match sender.send_request(req).await { Ok(resp) => Ok(resp.map(|b| b.boxed())), Err(e) => { @@ -298,22 +316,23 @@ impl ProxyServer { } } +/// Helper to create a binary http body fn full>(chunk: T) -> BoxBody { http_body_util::Full::new(chunk.into()) .map_err(|never| match never {}) .boxed() } +/// A proxy client which forwards http traffic to a proxy-server pub struct ProxyClient { - inner: Proxy, - connector: TlsConnector, - /// The host and port of the proxy server - target: String, - /// Certificate chain for client auth - cert_chain: Option>>, + /// The underlying TCP listener + listener: TcpListener, + /// A channel for sending requests to the connection to the proxy-server + requests_tx: mpsc::Sender, } impl ProxyClient { + /// Start with optional TLS client auth pub async fn new( cert_and_key: Option, address: impl ToSocketAddrs, @@ -322,12 +341,14 @@ impl ProxyClient { attestation_verifier: AttestationVerifier, remote_certificate: Option>, ) -> Result { + // If we will provide attestation, we must also use client auth if local_quote_generator.attestation_type() != AttestationType::None && cert_and_key.is_none() { return Err(ProxyError::NoClientAuth); } + // If a remote CA cert was given, use it as the root store, otherwise use webpki_roots let root_store = match remote_certificate { Some(remote_certificate) => { let mut root_store = RootCertStore::empty(); @@ -337,6 +358,7 @@ impl ProxyClient { None => RootCertStore::from_iter(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()), }; + // Setup TLS client configuration, with or without client auth let client_config = if let Some(ref cert_and_key) = cert_and_key { ClientConfig::builder() .with_root_certificates(root_store) @@ -361,7 +383,7 @@ impl ProxyClient { .await } - /// Create a new proxy with given TLS configuration + /// Create a new proxy client with given TLS configuration /// /// This is private as it allows dangerous configuration but is used in tests async fn new_with_tls_config( @@ -372,83 +394,126 @@ impl ProxyClient { attestation_verifier: AttestationVerifier, cert_chain: Option>>, ) -> Result { + // Setup TCP server and TLS client let listener = TcpListener::bind(local).await?; let connector = TlsConnector::from(client_config.clone()); - let inner = Proxy { - listener, - local_quote_generator, - attestation_verifier, - }; + // Process the hostname / port provided by the user + let target = host_to_host_with_port(&target_name); + + // Channel for getting incoming requests from the source client + let (requests_tx, mut requests_rx) = mpsc::channel::<( + http::Request, + oneshot::Sender< + Result>, hyper::Error>, + >, + )>(1024); + + // Connect to the proxy server and provide / verify attestation + let (mut sender, mut measurements, mut remote_attestation_type) = Self::setup_connection( + connector.clone(), + target.clone(), + cert_chain.clone(), + local_quote_generator.clone(), + attestation_verifier.clone(), + ) + .await?; + + tokio::spawn(async move { + // Read an incoming request from the channel (from the source client) + while let Some((req, response_tx)) = requests_rx.recv().await { + // Attempt to forward it to the proxy server + let (response, should_reconnect) = match sender.send_request(req).await { + Ok(mut resp) => { + // If we have measurements from the proxy-server, inject them into the + // response header + if let Some(measurements) = measurements.clone() { + let headers = resp.headers_mut(); + match measurements.to_header_format() { + Ok(header_value) => { + headers.insert(MEASUREMENT_HEADER, header_value); + } + Err(e) => { + // This error is highly unlikely - that the measurement values fail to + // encode to JSON or fit in an HTTP header + eprintln!("Failed to encode measurement values: {e}"); + } + } + headers.insert( + ATTESTATION_TYPE_HEADER, + HeaderValue::from_str(remote_attestation_type.as_str()) + .expect("Attestation type should be able to be encoded as a header value"), + ); + } + (Ok(resp.map(|b| b.boxed())), false) + } + Err(e) => { + eprintln!("Failed to send request to proxy-server: {e}"); + let mut resp = Response::new(full(format!("Request failed: {e}"))); + *resp.status_mut() = hyper::StatusCode::BAD_GATEWAY; + + (Ok(resp), true) + } + }; + + // Send the response back to the source client + if response_tx.send(response).is_err() { + eprintln!("Failed to forward response to source client, probably they dropped the connection"); + } + + // If the connection to the proxy server failed, reconnect + if should_reconnect { + // Reconnect to the server - retrying indefinately with a backoff + (sender, measurements, remote_attestation_type) = + Self::setup_connection_with_backoff( + connector.clone(), + target.clone(), + cert_chain.clone(), + local_quote_generator.clone(), + attestation_verifier.clone(), + ) + .await; + } + } + }); Ok(Self { - inner, - connector, - target: host_to_host_with_port(&target_name), - cert_chain, + listener, + requests_tx, }) } - /// Accept an incoming connection and handle it + /// Helper to return the local socket address from the underlying TCP listener + pub fn local_addr(&self) -> std::io::Result { + self.listener.local_addr() + } + + /// Accept an incoming connection and handle it in a separate task pub async fn accept(&self) -> io::Result<()> { - let (inbound, _client_addr) = self.inner.listener.accept().await?; + let (inbound, _client_addr) = self.listener.accept().await?; - let connector = self.connector.clone(); - let target = self.target.clone(); - let local_quote_generator = self.inner.local_quote_generator.clone(); - let attestation_verifier = self.inner.attestation_verifier.clone(); - let cert_chain = self.cert_chain.clone(); + let requests_tx = self.requests_tx.clone(); tokio::spawn(async move { - if let Err(err) = Self::handle_connection( - inbound, - connector, - target, - cert_chain, - local_quote_generator, - attestation_verifier, - ) - .await - { - eprintln!("Failed to handle connection: {err}"); + if let Err(err) = Self::handle_connection(inbound, requests_tx).await { + eprintln!("Failed to handle connection from source client: {err}"); } }); Ok(()) } - /// Helper to return the local socket address from the underlying TCP listener - pub fn local_addr(&self) -> std::io::Result { - self.inner.listener.local_addr() - } - - /// Handle an incoming connection + /// Handle an incoming connection from the source client async fn handle_connection( inbound: TcpStream, - connector: TlsConnector, - target: String, - cert_chain: Option>>, - local_quote_generator: Arc, - attestation_verifier: AttestationVerifier, + requests_tx: mpsc::Sender, ) -> Result<(), ProxyError> { - let http = Builder::new(); + // Setup http server and handler + let http = hyper::server::conn::http1::Builder::new(); let service = service_fn(move |req| { - let connector = connector.clone(); - let target = target.clone(); - let cert_chain = cert_chain.clone(); - let local_quote_generator = local_quote_generator.clone(); - let attestation_verifier = attestation_verifier.clone(); + let requests_tx = requests_tx.clone(); async move { - match Self::handle_http_request( - req, - connector, - target, - cert_chain, - local_quote_generator, - attestation_verifier, - ) - .await - { + match Self::handle_http_request(req, requests_tx).await { Ok(res) => { Ok::>, hyper::Error>(res) } @@ -468,20 +533,51 @@ impl ProxyClient { Ok(()) } + // Attempt connection and handshake with the proxy-server + // If it fails retry with a backoff (indefinately) + async fn setup_connection_with_backoff( + connector: TlsConnector, + target: String, + cert_chain: Option>>, + local_quote_generator: Arc, + attestation_verifier: AttestationVerifier, + ) -> (Http2Sender, Option, AttestationType) { + let mut delay = Duration::from_secs(1); + let max_delay = Duration::from_secs(SERVER_RECONNECT_MAX_BACKOFF_SECS); + + loop { + match Self::setup_connection( + connector.clone(), + target.clone(), + cert_chain.clone(), + local_quote_generator.clone(), + attestation_verifier.clone(), + ) + .await + { + Ok(output) => { + return output; + } + Err(e) => { + eprintln!("Reconnect failed: {e}. Retrying in {:#?}...", delay); + tokio::time::sleep(delay).await; + + // increase delay for next time (exponential), but clamp to max_delay + delay = std::cmp::min(delay * 2, max_delay); + } + } + } + } + + /// Connect to the proxy-server, do TLS handshake and remote attestation async fn setup_connection( connector: TlsConnector, target: String, cert_chain: Option>>, local_quote_generator: Arc, attestation_verifier: AttestationVerifier, - ) -> Result< - ( - tokio_rustls::client::TlsStream, - Option, - AttestationType, - ), - ProxyError, - > { + ) -> Result<(Http2Sender, Option, AttestationType), ProxyError> { + // Make a TCP client connection and TLS handshake let out = TcpStream::connect(&target).await?; let mut tls_stream = connector .connect(server_name_from_host(&target)?, out) @@ -489,6 +585,7 @@ impl ProxyClient { let (_io, server_connection) = tls_stream.get_ref(); + // Compute an exporter unique to the channel let mut exporter = [0u8; 32]; server_connection.export_keying_material( &mut exporter, @@ -496,11 +593,13 @@ impl ProxyClient { None, // context )?; + // Get the TLS certificate chain of the server let remote_cert_chain = server_connection .peer_certificates() .ok_or(ProxyError::NoCertificate)? .to_owned(); + // Read a length prefixed attestation from the proxy-server let mut length_bytes = [0; 4]; tls_stream.read_exact(&mut length_bytes).await?; let length: usize = u32::from_be_bytes(length_bytes).try_into()?; @@ -511,10 +610,12 @@ impl ProxyClient { let remote_attestation_payload: AttesationPayload = serde_json::from_slice(&buf)?; let remote_attestation_type = remote_attestation_payload.attestation_type; + // Verify the remote attestation against our accepted measurements let measurements = attestation_verifier .verify_attestation(remote_attestation_payload, &remote_cert_chain, exporter) .await?; + // If we are in a CVM, provide an attestation let attestation = if local_quote_generator.attestation_type() != AttestationType::None { serde_json::to_vec(&AttesationPayload::from_attestation_generator( &cert_chain.ok_or(ProxyError::NoClientAuth)?, @@ -525,36 +626,15 @@ impl ProxyClient { Vec::new() }; + // Send our attestation (or zero bytes) prefixed with length let attestation_length_prefix = length_prefix(&attestation); - tls_stream.write_all(&attestation_length_prefix).await?; - tls_stream.write_all(&attestation).await?; - Ok((tls_stream, measurements, remote_attestation_type)) - } - - // Handle a request from the source client to the proxy server - async fn handle_http_request( - req: hyper::Request, - connector: TlsConnector, - target: String, - cert_chain: Option>>, - local_quote_generator: Arc, - attestation_verifier: AttestationVerifier, - ) -> Result>, ProxyError> { - let (tls_stream, measurements, remote_attestation_type) = Self::setup_connection( - connector, - target, - cert_chain, - local_quote_generator, - attestation_verifier, - ) - .await?; + // The attestation exchange is now complete - now setup an HTTP client - // Now the attestation is done, forward the request to the proxy server let outbound_io = TokioIo::new(tls_stream); - let (mut sender, conn) = hyper::client::conn::http1::Builder::new() + let (sender, conn) = hyper::client::conn::http2::Builder::new(TokioExecutor) .handshake::<_, hyper::body::Incoming>(outbound_io) .await?; @@ -565,34 +645,18 @@ impl ProxyClient { } }); - match sender.send_request(req).await { - Ok(mut resp) => { - if let Some(measurements) = measurements { - let headers = resp.headers_mut(); - match measurements.to_header_format() { - Ok(header_value) => { - headers.insert(MEASUREMENT_HEADER, header_value); - } - Err(e) => { - // This error is highly unlikely - that the measurement values fail to - // encode to JSON or fit in an HTTP header - eprintln!("Failed to encode measurement values: {e}"); - } - } - headers.insert( - ATTESTATION_TYPE_HEADER, - HeaderValue::from_str(remote_attestation_type.as_str()).unwrap(), - ); - } - Ok(resp.map(|b| b.boxed())) - } - Err(e) => { - eprintln!("send_request error: {e}"); - let mut resp = Response::new(full(format!("Request failed: {e}"))); - *resp.status_mut() = hyper::StatusCode::BAD_GATEWAY; - Ok(resp) - } - } + // Return the HTTP client, as well as remote measurements + Ok((sender, measurements, remote_attestation_type)) + } + + // Handle a request from the source client to the proxy server + async fn handle_http_request( + req: hyper::Request, + requests_tx: mpsc::Sender, + ) -> Result>, ProxyError> { + let (response_tx, response_rx) = oneshot::channel(); + requests_tx.send((req, response_tx)).await?; + Ok(response_rx.await??) } } @@ -673,6 +737,16 @@ pub enum ProxyError { Hyper(#[from] hyper::Error), #[error("JSON: {0}")] Json(#[from] serde_json::Error), + #[error("Could not forward response - sender was dropped")] + OneShotRecv(#[from] oneshot::error::RecvError), + #[error("Failed to send request, connection to proxy-server dropped")] + MpscSend, +} + +impl From> for ProxyError { + fn from(_err: mpsc::error::SendError) -> Self { + Self::MpscSend + } } /// Given a byte array, encode its length as a 4 byte big endian u32 @@ -681,6 +755,7 @@ fn length_prefix(input: &[u8]) -> [u8; 4] { len.to_be_bytes() } +/// If no port was provided, default to 443 fn host_to_host_with_port(host: &str) -> String { if host.contains(':') { host.to_string() @@ -689,6 +764,7 @@ fn host_to_host_with_port(host: &str) -> String { } } +/// Given a hostname with or without port number, create a TLS [ServerName] with just the host part fn server_name_from_host( host: &str, ) -> Result, tokio_rustls::rustls::pki_types::InvalidDnsNameError> { @@ -702,6 +778,22 @@ fn server_name_from_host( ServerName::try_from(host_part.to_string()) } +/// An Executor for hyper that uses the tokio runtime +#[derive(Clone)] +struct TokioExecutor; + +// Implement the `hyper::rt::Executor` trait for `TokioExecutor` so that it can be used to spawn +// tasks in the hyper runtime. +impl hyper::rt::Executor for TokioExecutor +where + F: std::future::Future + Send + 'static, + F::Output: Send + 'static, +{ + fn execute(&self, fut: F) { + tokio::task::spawn(fut); + } +} + #[cfg(test)] mod tests { use super::*; @@ -808,6 +900,7 @@ mod tests { let proxy_addr = proxy_server.local_addr().unwrap(); tokio::spawn(async move { + // Accept one connection, then finish proxy_server.accept().await.unwrap(); }); @@ -827,6 +920,8 @@ mod tests { let proxy_client_addr = proxy_client.local_addr().unwrap(); tokio::spawn(async move { + // Accept two connections, then finish + proxy_client.accept().await.unwrap(); proxy_client.accept().await.unwrap(); }); @@ -852,6 +947,30 @@ mod tests { // handler puts them there) let measurements = Measurements::from_header_format(&res_body).unwrap(); assert_eq!(measurements, default_measurements()); + + // Now do another request - to check that the connection has stayed open + let res = reqwest::get(format!("http://{}", proxy_client_addr.to_string())) + .await + .unwrap(); + + let headers = res.headers(); + let measurements_json = headers.get(MEASUREMENT_HEADER).unwrap().to_str().unwrap(); + let measurements = Measurements::from_header_format(measurements_json).unwrap(); + assert_eq!(measurements, default_measurements()); + + let attestation_type = headers + .get(ATTESTATION_TYPE_HEADER) + .unwrap() + .to_str() + .unwrap(); + assert_eq!(attestation_type, AttestationType::DcapTdx.as_str()); + + let res_body = res.text().await.unwrap(); + + // The response body shows us what was in the request header (as the test http server + // handler puts them there) + let measurements = Measurements::from_header_format(&res_body).unwrap(); + assert_eq!(measurements, default_measurements()); } #[tokio::test]