From 7729575825a7ae33ffd0ad8d620901b90a2197d6 Mon Sep 17 00:00:00 2001 From: peg Date: Wed, 19 Nov 2025 15:24:14 +0100 Subject: [PATCH 1/3] Use SCALE rather than JSON for encoding attestation payloads --- Cargo.lock | 1 + Cargo.toml | 1 + src/attestation/mod.rs | 18 +++++++++++++++++- src/lib.rs | 20 +++++++++++++------- 4 files changed, 32 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d0666eb..a214f53 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -141,6 +141,7 @@ dependencies = [ "http-body-util", "hyper", "hyper-util", + "parity-scale-codec", "pem-rfc7468", "rand_core 0.6.4", "rcgen", diff --git a/Cargo.toml b/Cargo.toml index 7061684..7e31e2e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ bytes = "1.10.1" http = "1.3.1" serde_json = "1.0.145" serde = "1.0.228" +parity-scale-codec = "3.7.5" [dev-dependencies] rcgen = "0.14.5" diff --git a/src/attestation/mod.rs b/src/attestation/mod.rs index ae0cfca..05779a5 100644 --- a/src/attestation/mod.rs +++ b/src/attestation/mod.rs @@ -1,6 +1,7 @@ pub mod measurements; use measurements::{CvmImageMeasurements, MeasurementRecord, Measurements, PlatformMeasurements}; +use parity_scale_codec::{Decode, Encode}; use serde::{Deserialize, Serialize}; use std::{ fmt::{self, Display, Formatter}, @@ -22,7 +23,7 @@ use x509_parser::prelude::*; /// For fetching collateral directly from intel, if no PCCS is specified const PCS_URL: &str = "https://api.trustedservices.intel.com"; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, Encode, Decode)] pub struct AttesationPayload { pub attestation_type: AttestationType, pub attestation: Vec, @@ -85,6 +86,21 @@ impl AttestationType { } } +impl Encode for AttestationType { + fn encode(&self) -> Vec { + self.as_str().encode() + } +} + +impl Decode for AttestationType { + fn decode( + input: &mut I, + ) -> Result { + let s: String = String::decode(input)?; + serde_json::from_str(&format!("\"{s}\"")).map_err(|_| "Failed to decode enum".into()) + } +} + impl Display for AttestationType { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.write_str(self.as_str()) diff --git a/src/lib.rs b/src/lib.rs index 5317d6b..c79dcd5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,8 @@ use http_body_util::BodyExt; use hyper::service::service_fn; use hyper::Response; use hyper_util::rt::TokioIo; +use parity_scale_codec::Decode; +use parity_scale_codec::Encode; use thiserror::Error; use tokio::sync::{mpsc, oneshot}; use tokio_rustls::rustls::server::{VerifierBuilderError, WebPkiClientVerifier}; @@ -192,11 +194,12 @@ impl ProxyServer { // 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( + AttesationPayload::from_attestation_generator( &cert_chain, exporter, local_quote_generator, - )?)? + )? + .encode() } else { Vec::new() }; @@ -218,7 +221,7 @@ impl ProxyServer { // 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)?; + let remote_attestation_payload = AttesationPayload::decode(&mut &buf[..])?; let remote_attestation_type = remote_attestation_payload.attestation_type; ( @@ -607,7 +610,7 @@ impl ProxyClient { let mut buf = vec![0; length]; tls_stream.read_exact(&mut buf).await?; - let remote_attestation_payload: AttesationPayload = serde_json::from_slice(&buf)?; + let remote_attestation_payload = AttesationPayload::decode(&mut &buf[..])?; let remote_attestation_type = remote_attestation_payload.attestation_type; // Verify the remote attestation against our accepted measurements @@ -617,11 +620,12 @@ impl ProxyClient { // 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( + AttesationPayload::from_attestation_generator( &cert_chain.ok_or(ProxyError::NoClientAuth)?, exporter, local_quote_generator, - )?)? + )? + .encode() } else { Vec::new() }; @@ -705,7 +709,7 @@ async fn get_tls_cert_with_config( let mut buf = vec![0; length]; tls_stream.read_exact(&mut buf).await?; - let remote_attestation_payload: AttesationPayload = serde_json::from_slice(&buf)?; + let remote_attestation_payload = AttesationPayload::decode(&mut &buf[..])?; let _measurements = attestation_verifier .verify_attestation(remote_attestation_payload, &remote_cert_chain, exporter) @@ -741,6 +745,8 @@ pub enum ProxyError { OneShotRecv(#[from] oneshot::error::RecvError), #[error("Failed to send request, connection to proxy-server dropped")] MpscSend, + #[error("Serialization: {0}")] + Serialization(#[from] parity_scale_codec::Error), } impl From> for ProxyError { From 98bf7bdfeaddfda6c5991ddaf98a47b7380e1d9e Mon Sep 17 00:00:00 2001 From: peg Date: Wed, 19 Nov 2025 16:06:47 +0100 Subject: [PATCH 2/3] Tidy --- src/attestation/mod.rs | 7 ++++++ src/lib.rs | 55 +++++++++++++++++------------------------- src/main.rs | 2 ++ 3 files changed, 31 insertions(+), 33 deletions(-) diff --git a/src/attestation/mod.rs b/src/attestation/mod.rs index 05779a5..9d068f3 100644 --- a/src/attestation/mod.rs +++ b/src/attestation/mod.rs @@ -40,6 +40,13 @@ impl AttesationPayload { attestation: attesation_generator.create_attestation(cert_chain, exporter)?, }) } + + pub fn without_attestation() -> Self { + Self { + attestation_type: AttestationType::None, + attestation: Vec::new(), + } + } } /// Type of attestaion used diff --git a/src/lib.rs b/src/lib.rs index c79dcd5..d83bc8f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,13 +4,10 @@ use attestation::{measurements::Measurements, AttestationError, AttestationType} pub use attestation::{DcapTdxQuoteGenerator, NoQuoteGenerator, QuoteGenerator}; use bytes::Bytes; use http::HeaderValue; -use http_body_util::combinators::BoxBody; -use http_body_util::BodyExt; -use hyper::service::service_fn; -use hyper::Response; +use http_body_util::{combinators::BoxBody, BodyExt}; +use hyper::{service::service_fn, Response}; use hyper_util::rt::TokioIo; -use parity_scale_codec::Decode; -use parity_scale_codec::Encode; +use parity_scale_codec::{Decode, Encode}; use thiserror::Error; use tokio::sync::{mpsc, oneshot}; use tokio_rustls::rustls::server::{VerifierBuilderError, WebPkiClientVerifier}; @@ -193,16 +190,12 @@ impl ProxyServer { 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 { - AttesationPayload::from_attestation_generator( - &cert_chain, - exporter, - local_quote_generator, - )? - .encode() - } else { - Vec::new() - }; + let attestation = AttesationPayload::from_attestation_generator( + &cert_chain, + exporter, + local_quote_generator, + )? + .encode(); // Write our attestation to the channel, with length prefix let attestation_length_prefix = length_prefix(&attestation); @@ -218,24 +211,20 @@ impl ProxyServer { let mut buf = vec![0; length]; tls_stream.read_exact(&mut buf).await?; + let remote_attestation_payload = AttesationPayload::decode(&mut &buf[..])?; + let remote_attestation_type = remote_attestation_payload.attestation_type; + // 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::decode(&mut &buf[..])?; - - let remote_attestation_type = remote_attestation_payload.attestation_type; - ( - attestation_verifier - .verify_attestation( - remote_attestation_payload, - &remote_cert_chain.ok_or(ProxyError::NoClientAuth)?, - exporter, - ) - .await?, - remote_attestation_type, - ) + let measurements = if attestation_verifier.has_remote_attestion() { + attestation_verifier + .verify_attestation( + remote_attestation_payload, + &remote_cert_chain.ok_or(ProxyError::NoClientAuth)?, + exporter, + ) + .await? } else { - (None, AttestationType::None) + None }; // Setup an HTTP server @@ -627,7 +616,7 @@ impl ProxyClient { )? .encode() } else { - Vec::new() + AttesationPayload::without_attestation().encode() }; // Send our attestation (or zero bytes) prefixed with length diff --git a/src/main.rs b/src/main.rs index d265ca5..dd3fcc2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -243,11 +243,13 @@ fn load_tls_cert_and_key( Ok(TlsCertAndKey { key, cert_chain }) } +/// load certificates from a PEM-encoded file fn load_certs_pem(path: PathBuf) -> std::io::Result>> { rustls_pemfile::certs(&mut std::io::BufReader::new(File::open(path)?)) .collect::, _>>() } +/// load TLS private key from a PEM-encoded file fn load_private_key_pem(path: PathBuf) -> anyhow::Result> { let mut reader = std::io::BufReader::new(File::open(path)?); From 69eb974ab336848c17906c56117d92c46c905721 Mon Sep 17 00:00:00 2001 From: peg Date: Wed, 19 Nov 2025 16:32:11 +0100 Subject: [PATCH 3/3] Comments --- src/attestation/mod.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/attestation/mod.rs b/src/attestation/mod.rs index 9d068f3..0cb9b0e 100644 --- a/src/attestation/mod.rs +++ b/src/attestation/mod.rs @@ -23,13 +23,19 @@ use x509_parser::prelude::*; /// For fetching collateral directly from intel, if no PCCS is specified const PCS_URL: &str = "https://api.trustedservices.intel.com"; +/// This is the type sent over the channel to provide an attestation #[derive(Debug, Serialize, Deserialize, Encode, Decode)] pub struct AttesationPayload { + /// What CVM platform is used (including none) pub attestation_type: AttestationType, + /// The attestation evidence as bytes - in the case of DCAP this is a quote pub attestation: Vec, } impl AttesationPayload { + /// Given an attestation generator (quote generation function for a specific platform) + /// return an attestation + /// This also takes the certificate chain and exporter as they are given as input to the attestation pub fn from_attestation_generator( cert_chain: &[CertificateDer<'_>], exporter: [u8; 32], @@ -41,6 +47,8 @@ impl AttesationPayload { }) } + /// Create an empty attestation payload for the case that we are running in a non-confidential + /// environment pub fn without_attestation() -> Self { Self { attestation_type: AttestationType::None, @@ -81,6 +89,7 @@ impl AttestationType { } } + /// Get a quote generator for this type of platform pub fn get_quote_generator(&self) -> Result, AttestationError> { match self { AttestationType::None => Ok(Arc::new(NoQuoteGenerator)), @@ -93,12 +102,14 @@ impl AttestationType { } } +/// SCALE encode (used over the wire) impl Encode for AttestationType { fn encode(&self) -> Vec { self.as_str().encode() } } +/// SCALE decode impl Decode for AttestationType { fn decode( input: &mut I, @@ -127,9 +138,15 @@ pub trait QuoteGenerator: Send + Sync + 'static { ) -> Result, AttestationError>; } +/// Allows remote attestations to be verified #[derive(Clone, Debug)] pub struct AttestationVerifier { + /// The measurement values we accept + /// + /// If this is empty, anything will be accepted - but measurements are always injected into HTTP + /// headers, so that they can be verified upstream accepted_measurements: Vec, + /// A PCCS service to use - defaults to Intel PCS pccs_url: Option, }