From de5cee480804025e349eeba43abaed0331de3eb1 Mon Sep 17 00:00:00 2001 From: peg Date: Fri, 21 Nov 2025 09:03:46 +0100 Subject: [PATCH] Allow PCCS url to be passed on command line --- src/attestation/measurements.rs | 26 +++++++++++++++++------- src/attestation/mod.rs | 4 ++-- src/main.rs | 35 +++++++++++++++++++++++---------- 3 files changed, 46 insertions(+), 19 deletions(-) diff --git a/src/attestation/measurements.rs b/src/attestation/measurements.rs index 164bd6e..b776b82 100644 --- a/src/attestation/measurements.rs +++ b/src/attestation/measurements.rs @@ -1,4 +1,4 @@ -use crate::attestation::{AttestationError, AttestationType, AttestationVerifier}; +use crate::attestation::{AttestationError, AttestationType}; use std::{collections::HashMap, path::PathBuf}; use dcap_qvl::quote::Report; @@ -9,11 +9,14 @@ use thiserror::Error; /// Measurements determined by the CVM platform #[derive(Clone, PartialEq, Debug)] pub struct PlatformMeasurements { + /// MRTD register value pub mrtd: [u8; 48], + /// RTMR0 register value pub rtmr0: [u8; 48], } impl PlatformMeasurements { + /// Given a quote from the dcap_qvl library, extract the platform measurements pub fn from_dcap_qvl_quote(quote: &dcap_qvl::quote::Quote) -> Result { let report = match quote.report { Report::TD10(report) => report, @@ -36,15 +39,19 @@ impl PlatformMeasurements { } } -/// Measurements determined by the CVM image +/// Measurements determined by the CVM image or application #[derive(Clone, PartialEq, Debug)] pub struct CvmImageMeasurements { + /// RTMR1 register value pub rtmr1: [u8; 48], + /// RTMR2 register value pub rtmr2: [u8; 48], + /// RTMR3 register value pub rtmr3: [u8; 48], } impl CvmImageMeasurements { + /// Given a quote from the dcap_qvl library, extract the CVM image / application measurements pub fn from_dcap_qvl_quote(quote: &dcap_qvl::quote::Quote) -> Result { let report = match quote.report { Report::TD10(report) => report, @@ -69,6 +76,7 @@ impl CvmImageMeasurements { } } +/// A full set of measurement register values #[derive(Debug, Clone, PartialEq)] pub struct Measurements { pub platform: PlatformMeasurements, @@ -76,6 +84,7 @@ pub struct Measurements { } impl Measurements { + /// Convert to the JSON format used in HTTP headers pub fn to_header_format(&self) -> Result { let mut measurements_map = HashMap::new(); measurements_map.insert(0, hex::encode(self.platform.mrtd)); @@ -88,6 +97,7 @@ impl Measurements { )?)?) } + /// Parse the JSON used in HTTP headers pub fn from_header_format(input: &str) -> Result { let measurements_map: HashMap = serde_json::from_str(input)?; let measurements_map: HashMap = measurements_map @@ -126,6 +136,7 @@ impl Measurements { } } +/// An error when converting measurements / to or from HTTP header format #[derive(Error, Debug)] pub enum MeasurementFormatError { #[error("JSON: {0}")] @@ -144,17 +155,21 @@ pub enum MeasurementFormatError { BadLength, } +/// An accepted measurement value given in the measurements file #[derive(Clone, Debug)] pub struct MeasurementRecord { + /// An identifier, for example the name and version of the corresponding OS image pub measurement_id: String, + /// The associated attestation platform pub attestation_type: AttestationType, + /// The expected measurement register values pub measurements: Measurements, } /// Given the path to a JSON file containing measurements, return a [Vec] pub async fn get_measurements_from_file( measurement_file: PathBuf, -) -> Result { +) -> Result, MeasurementFormatError> { #[derive(Debug, Deserialize)] struct MeasurementRecordSimple { measurement_id: String, @@ -202,10 +217,7 @@ pub async fn get_measurements_from_file( }); } - Ok(AttestationVerifier { - accepted_measurements: measurements, - pccs_url: None, - }) + Ok(measurements) } #[cfg(test)] diff --git a/src/attestation/mod.rs b/src/attestation/mod.rs index 0cb9b0e..6264924 100644 --- a/src/attestation/mod.rs +++ b/src/attestation/mod.rs @@ -145,9 +145,9 @@ pub struct AttestationVerifier { /// /// 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, + pub accepted_measurements: Vec, /// A PCCS service to use - defaults to Intel PCS - pccs_url: Option, + pub pccs_url: Option, } impl AttestationVerifier { diff --git a/src/main.rs b/src/main.rs index d78c8ba..88f3012 100644 --- a/src/main.rs +++ b/src/main.rs @@ -49,9 +49,12 @@ enum CliCommand { /// Optional path to file containing JSON measurements to be enforced on the server #[arg(long)] server_measurements: Option, - #[arg(long)] /// Additional CA certificate to verify against (PEM) Defaults to no additional TLS certs. + #[arg(long)] tls_ca_certificate: Option, + /// The URL of a PCCS to use when verifying DCAP attestations. Defaults to Intel PCS. + #[arg(long)] + pccs_url: Option, // TODO missing: // Name: "dev-dummy-dcap", // EnvVars: []string{"DEV_DUMMY_DCAP"}, @@ -81,6 +84,9 @@ enum CliCommand { /// Optional path to file containing JSON measurements to be enforced on the client #[arg(long)] client_measurements: Option, + /// The URL of a PCCS to use when verifying DCAP attestations. Defaults to Intel PCS. + #[arg(long)] + pccs_url: Option, // TODO missing: // Name: "listen-addr-healthcheck", // EnvVars: []string{"LISTEN_ADDR_HEALTHCHECK"}, @@ -98,6 +104,9 @@ enum CliCommand { /// Optional path to file containing JSON measurements to be enforced on the server #[arg(long)] server_measurements: Option, + /// The URL of a PCCS to use when verifying DCAP attestations. Defaults to Intel PCS. + #[arg(long)] + pccs_url: Option, }, } @@ -132,6 +141,7 @@ async fn main() -> anyhow::Result<()> { client_attestation_type, server_measurements, tls_ca_certificate, + pccs_url, } => { let target_addr = target_addr .strip_prefix("https://") @@ -153,9 +163,10 @@ async fn main() -> anyhow::Result<()> { }; let attestation_verifier = match server_measurements { - Some(server_measurements) => { - get_measurements_from_file(server_measurements).await? - } + Some(server_measurements) => AttestationVerifier { + accepted_measurements: get_measurements_from_file(server_measurements).await?, + pccs_url, + }, None => AttestationVerifier::do_not_verify(), }; @@ -199,6 +210,7 @@ async fn main() -> anyhow::Result<()> { client_auth, server_attestation_type, client_measurements, + pccs_url, } => { let tls_cert_and_chain = load_tls_cert_and_key(tls_certificate_path, tls_private_key_path)?; @@ -210,9 +222,10 @@ async fn main() -> anyhow::Result<()> { let local_attestation_generator = server_attestation_type.get_quote_generator()?; let attestation_verifier = match client_measurements { - Some(client_measurements) => { - get_measurements_from_file(client_measurements).await? - } + Some(client_measurements) => AttestationVerifier { + accepted_measurements: get_measurements_from_file(client_measurements).await?, + pccs_url, + }, None => AttestationVerifier::do_not_verify(), }; @@ -235,11 +248,13 @@ async fn main() -> anyhow::Result<()> { CliCommand::GetTlsCert { server, server_measurements, + pccs_url, } => { let attestation_verifier = match server_measurements { - Some(server_measurements) => { - get_measurements_from_file(server_measurements).await? - } + Some(server_measurements) => AttestationVerifier { + accepted_measurements: get_measurements_from_file(server_measurements).await?, + pccs_url, + }, None => AttestationVerifier::do_not_verify(), }; let cert_chain = get_tls_cert(server, attestation_verifier).await?;