From bb6b1b721b0c03a9a0059a7254d9d20294712941 Mon Sep 17 00:00:00 2001 From: Tyler Fanelli Date: Sun, 15 Dec 2024 01:13:14 -0500 Subject: [PATCH] aproxy: Complete attestation with KBS protocol With serialized TEE evidence and key, the proxy can now transfer these to the attestation server for evaluation. KBS will first transfer the evidence to the /attest endpoint for evaluation. Upon successful attestation, the client can then retrieve resources from the server. Co-developed-by: Stefano Garzarella Signed-off-by: Tyler Fanelli --- aproxy/src/attest.rs | 20 ++++++++++ aproxy/src/backend/kbs.rs | 80 ++++++++++++++++++++++++++++++++++++++- aproxy/src/backend/mod.rs | 13 +++++++ 3 files changed, 112 insertions(+), 1 deletion(-) diff --git a/aproxy/src/attest.rs b/aproxy/src/attest.rs index a423a9634..a682b76e1 100644 --- a/aproxy/src/attest.rs +++ b/aproxy/src/attest.rs @@ -17,6 +17,7 @@ use std::{ /// Attest an SVSM client session. pub fn attest(stream: &mut UnixStream, http: &mut backend::HttpClient) -> anyhow::Result<()> { negotiation(stream, http)?; + attestation(stream, http)?; Ok(()) } @@ -44,6 +45,25 @@ fn negotiation(stream: &mut UnixStream, http: &mut backend::HttpClient) -> anyho Ok(()) } +/// Attestation phase of SVSM attestation. SVSM will send an attestation request containing the TEE +/// evidence. Proxy will respond with an attestation response containing the status +/// (success/failure) and an optional secret upon successful attestation. +fn attestation(stream: &mut UnixStream, http: &backend::HttpClient) -> anyhow::Result<()> { + let request: AttestationRequest = { + let payload = proxy_read(stream)?; + serde_json::from_slice(&payload) + .context("unable to deserialize attestation request from JSON")? + }; + + // Attest the TEE evidence with the server. + let response: AttestationResponse = http.attestation(request)?; + + // Write the response from the attestation server to SVSM. + proxy_write(stream, response)?; + + Ok(()) +} + /// Read bytes from the UNIX socket connected to SVSM. With each write, SVSM first writes an 8-byte /// header indicating the length of the buffer. Once the length is read, the buffer can be read. fn proxy_read(stream: &mut UnixStream) -> anyhow::Result> { diff --git a/aproxy/src/backend/kbs.rs b/aproxy/src/backend/kbs.rs index ba48cd374..d4d3712bc 100644 --- a/aproxy/src/backend/kbs.rs +++ b/aproxy/src/backend/kbs.rs @@ -7,7 +7,8 @@ use super::*; use anyhow::Context; -use kbs_types::{Challenge, Request, Tee}; +use kbs_types::{Attestation, Challenge, Request, Response, Tee, TeePubKey}; +use reqwest::StatusCode; use serde_json::Value; #[derive(Clone, Copy, Debug, Default)] @@ -68,4 +69,81 @@ impl AttestationProtocol for KbsProtocol { Ok(resp) } + + /// With the serialized TEE evidence and key, complete the attestation. Serialize the evidence + /// and send it to the /attest endpoint of the KBS server. Upon a successful attestation, fetch + /// a secret (identified as "svsm_secret"). If able to successfully fetch the secret, return a + /// successful AttestationResponse with the secret included. + fn attestation( + &self, + http: &HttpClient, + request: AttestationRequest, + ) -> anyhow::Result { + // Create a KBS attestation object from the TEE evidence and key. + let attestation = Attestation { + tee_pubkey: match request.key { + AttestationKey::EC { + crv, + x_b64url, + y_b64url, + } => TeePubKey::EC { + crv, + alg: "EC".to_string(), + x: x_b64url, + y: y_b64url, + }, + }, + tee_evidence: Value::String(request.evidence), + }; + + // Attest TEE evidence at KBS /attest endpoint. + let http_resp = http + .cli + .post(format!("{}/kbs/v0/attest", http.url)) + .json(&attestation) + .send() + .context("unable to POST to KBS /attest endpoint")?; + + // The JSON response from the /attest endpoint is basically ignored here. Instead, we check + // the HTTP status to indicate successful attestation. + // + // FIXME + if http_resp.status() != StatusCode::OK { + return Ok(AttestationResponse { + success: false, + secret: None, + pub_key: None, + }); + } + + // Successful attestation. Fetch the secret (which should be stored as "svsm_secret" within + // the KBS's RVPS. + let http_resp = http + .cli + .post(format!("{}/kbs/v0/svsm_secret", http.url)) + .send() + .context("unable to POST to KBS /attest endpoint")?; + + // Unsuccessful attempt at retrieving secret. + if http_resp.status() != StatusCode::OK { + return Ok(AttestationResponse { + success: false, + secret: None, + pub_key: None, + }); + } + + let text = http_resp + .text() + .context("unable to read KBS /resource response")?; + + let resp: Response = serde_json::from_str(&text) + .context("unable to convert KBS /resource response to KBS Response object")?; + + Ok(AttestationResponse { + success: true, + secret: Some(resp.ciphertext), + pub_key: Some(resp.encrypted_key), + }) + } } diff --git a/aproxy/src/backend/mod.rs b/aproxy/src/backend/mod.rs index e0fa5475d..a8674d570 100644 --- a/aproxy/src/backend/mod.rs +++ b/aproxy/src/backend/mod.rs @@ -38,6 +38,14 @@ impl HttpClient { Protocol::Kbs(mut kbs) => kbs.negotiation(self, req), } } + + pub fn attestation(&self, req: AttestationRequest) -> anyhow::Result { + // Depending on the underlying protocol of the attestation server, attest TEE evidence + // accoridngly. + match self.protocol { + Protocol::Kbs(kbs) => kbs.attestation(self, req), + } + } } /// Attestation Protocol identifier. @@ -65,4 +73,9 @@ pub trait AttestationProtocol { client: &mut HttpClient, req: NegotiationRequest, ) -> anyhow::Result; + fn attestation( + &self, + client: &HttpClient, + req: AttestationRequest, + ) -> anyhow::Result; }