Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 17 additions & 16 deletions crates/attestation/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,30 @@ repository = "https://github.com/flashbots/attested-tls"
keywords = ["attestation", "CVM", "TDX"]

[dependencies]
dcap-qvl = { workspace = true, features = ["danger-allow-tcb-override"] }
pccs = { workspace = true }
tokio = { workspace = true, features = ["fs"] }
tokio-rustls = { workspace = true, default-features = false }
x509-parser = "0.18.0"
thiserror = "2.0.17"

anyhow = "1.0.100"
pem-rfc7468 = { version = "0.7.0", features = ["std"] }
base64 = "0.22.1"
configfs-tsm = "0.0.2"
rand_core = { version = "0.6.4", features = ["getrandom"] }
dcap-qvl = { workspace = true, features = ["danger-allow-tcb-override"] }
hex = "0.4.3"
http = "1.3.1"
serde_json = "1.0.145"
num-bigint = "0.4.6"
once_cell = "1.21.3"
parity-scale-codec = "3.7.5"
pem-rfc7468 = { version = "0.7.0", features = ["std"] }
rand_core = { version = "0.6.4", features = ["getrandom"] }
reqwest = { version = "0.12.23", default-features = false, features = [ "rustls-tls-webpki-roots-no-provider" ] }
serde = "1.0.228"
base64 = "0.22.1"
reqwest = { version = "0.12.23", default-features = false, features = [
"rustls-tls-webpki-roots-no-provider",
] }
serde_json = "1.0.145"
thiserror = "2.0.17"
time = "0.3.47"
tracing = "0.1.41"
parity-scale-codec = "3.7.5"
num-bigint = "0.4.6"
ureq = "2.12.1"
webpki = { package = "rustls-webpki", version = "0.103.8" }
time = "0.3.47"
once_cell = "1.21.3"
x509-parser = "0.18.0"

# Used for azure vTPM attestation support
az-tdx-vtpm = { version = "0.7.4", optional = true }
Expand All @@ -42,10 +42,11 @@ openssl = { version = "0.10.75", optional = true }
tdx-quote = { version = "0.0.5", features = ["mock"], optional = true }

[dev-dependencies]
tempfile = "3.23.0"
tdx-quote = { version = "0.0.5", features = ["mock"] }
tokio-rustls = { workspace = true, default-features = true }

serde-saphyr = "0.0.22"
tdx-quote = { version = "0.0.5", features = ["mock"] }
tempfile = "3.23.0"

[features]
default = []
Expand Down
72 changes: 37 additions & 35 deletions crates/attestation/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub mod measurements;

use std::{
fmt::{self, Display, Formatter},
io::Read,
net::IpAddr,
time::{Duration, SystemTime, UNIX_EPOCH},
};
Expand Down Expand Up @@ -54,7 +55,7 @@ impl AttestationExchangeMessage {
Err(AttestationError::AttestationTypeNotSupported)
}
}
_ => {
AttestationType::DcapTdx | AttestationType::GcpTdx | AttestationType::QemuTdx => {
Comment thread
0x416e746f6e marked this conversation as resolved.
#[cfg(any(test, feature = "mock"))]
{
let quote = tdx_quote::Quote::from_bytes(&self.attestation)
Expand Down Expand Up @@ -103,7 +104,7 @@ impl AttestationType {
}

/// Detect what platform we are on by attempting an attestation
pub async fn detect() -> Result<Self, AttestationError> {
pub fn detect() -> Result<Self, AttestationError> {
// First attempt azure, if the feature is present
#[cfg(feature = "azure")]
{
Expand All @@ -114,7 +115,7 @@ impl AttestationType {
// Otherwise try DCAP quote - this internally checks that the quote provider
// is `tdx_guest`
if configfs_tsm::create_tdx_quote([0; 64]).is_ok() {
if running_on_gcp().await? {
if running_on_gcp()? {
return Ok(AttestationType::GcpTdx);
} else {
return Ok(AttestationType::DcapTdx);
Expand Down Expand Up @@ -170,8 +171,8 @@ impl AttestationGenerator {

/// Detect what confidential compute platform is present and create the
/// appropriate attestation generator
pub async fn detect() -> Result<Self, AttestationError> {
Self::new_with_detection(None, None).await
pub fn detect() -> Result<Self, AttestationError> {
Self::new_with_detection(None, None)
}

/// Do not generate attestations
Expand All @@ -181,7 +182,7 @@ impl AttestationGenerator {

/// Create an [AttestationGenerator] detecting the attestation type if
/// it is not given
pub async fn new_with_detection(
pub fn new_with_detection(
attestation_type_string: Option<String>,
attestation_provider_url: Option<String>,
) -> Result<Self, AttestationError> {
Expand All @@ -196,7 +197,7 @@ impl AttestationGenerator {
let attestation_type_string = attestation_type_string.unwrap_or_else(|| "auto".to_string());
let attestation_type = if attestation_type_string == "auto" {
tracing::info!("Doing attestation type detection...");
AttestationType::detect().await?
AttestationType::detect()?
} else {
serde_json::from_value(serde_json::Value::String(attestation_type_string))?
};
Expand All @@ -206,12 +207,12 @@ impl AttestationGenerator {
}

/// Generate an attestation exchange message with given input data
pub async fn generate_attestation(
pub fn generate_attestation(
&self,
input_data: [u8; 64],
) -> Result<AttestationExchangeMessage, AttestationError> {
if let Some(url) = &self.attestation_provider_url {
Self::use_attestation_provider(url, self.attestation_type, input_data).await
Self::use_attestation_provider(url, self.attestation_type, input_data)
} else {
Ok(AttestationExchangeMessage {
attestation_type: self.attestation_type,
Expand Down Expand Up @@ -241,33 +242,37 @@ impl AttestationGenerator {
Err(AttestationError::AttestationTypeNotSupported)
}
}
_ => dcap::create_dcap_attestation(input_data),
AttestationType::DcapTdx | AttestationType::GcpTdx | AttestationType::QemuTdx => {
dcap::create_dcap_attestation(input_data)
}
}
}

/// Generate an attestation by using an external service for the
/// attestation generation
async fn use_attestation_provider(
fn use_attestation_provider(
url: &str,
attestation_type: AttestationType,
input_data: [u8; 64],
) -> Result<AttestationExchangeMessage, AttestationError> {
let url = format!("{}/attest/{}", url, hex::encode(input_data));

let response = reqwest::get(url)
.await
let mut response = ureq::get(&url)
.timeout(Duration::from_millis(1000))
.call()
.map_err(|err| AttestationError::AttestationProvider(err.to_string()))?
.bytes()
.await
.map_err(|err| AttestationError::AttestationProvider(err.to_string()))?
.to_vec();
.into_reader();
let mut body = Vec::new();
response
.read_to_end(&mut body)
.map_err(|err| AttestationError::AttestationProvider(err.to_string()))?;

// If the response is not already wrapped in an attestation exchange
// message, wrap it in one
if let Ok(message) = AttestationExchangeMessage::decode(&mut &response[..]) {
if let Ok(message) = AttestationExchangeMessage::decode(&mut &body[..]) {
Ok(message)
} else {
Ok(AttestationExchangeMessage { attestation_type, attestation: response })
Ok(AttestationExchangeMessage { attestation_type, attestation: body })
}
}
}
Expand Down Expand Up @@ -391,7 +396,7 @@ impl AttestationVerifier {
return Err(AttestationError::AttestationTypeNotSupported);
}
}
_ => {
AttestationType::DcapTdx | AttestationType::GcpTdx | AttestationType::QemuTdx => {
dcap::verify_dcap_attestation(
attestation_exchange_message.attestation,
expected_input_data,
Expand Down Expand Up @@ -497,14 +502,13 @@ fn log_attestation(attestation: &AttestationExchangeMessage) {

/// Test whether it looks like we are running on GCP by hitting the metadata
/// API
async fn running_on_gcp() -> Result<bool, AttestationError> {
let client = reqwest::Client::builder().timeout(Duration::from_millis(200)).build()?;

let resp = client.get(GCP_METADATA_API).send().await;
fn running_on_gcp() -> Result<bool, AttestationError> {
let agent = ureq::AgentBuilder::new().timeout(Duration::from_millis(200)).build();
let resp = agent.get(GCP_METADATA_API).call();

if let Ok(r) = resp {
return Ok(r.status().is_success() &&
r.headers().get("Metadata-Flavor").map(|v| v == "Google").unwrap_or(false));
return Ok(r.status() == 200 &&
r.header("Metadata-Flavor").map(|v| v == "Google").unwrap_or(false));
}

Ok(false)
Expand Down Expand Up @@ -632,19 +636,19 @@ mod tests {
addr
}

#[tokio::test]
async fn attestation_detection_does_not_panic() {
#[test]
fn attestation_detection_does_not_panic() {
// We dont enforce what platform the test is run on, only that the function
// does not panic
let _ = AttestationGenerator::new_with_detection(None, None).await;
let _ = AttestationGenerator::new_with_detection(None, None);
}

#[tokio::test]
async fn running_on_gcp_check_does_not_panic() {
let _ = running_on_gcp().await;
#[test]
fn running_on_gcp_check_does_not_panic() {
let _ = running_on_gcp();
}

#[tokio::test]
#[tokio::test(flavor = "multi_thread")]
async fn attestation_provider_response_is_wrapped_if_needed() {
let input_data = [0u8; 64];

Expand All @@ -661,7 +665,6 @@ mod tests {
AttestationType::GcpTdx,
input_data,
)
.await
.unwrap();
assert_eq!(decoded.attestation_type, AttestationType::None);
assert_eq!(decoded.attestation, vec![1, 2, 3]);
Expand All @@ -673,7 +676,6 @@ mod tests {
AttestationType::DcapTdx,
input_data,
)
.await
.unwrap();
assert_eq!(wrapped.attestation_type, AttestationType::DcapTdx);
assert_eq!(wrapped.attestation, vec![9, 8]);
Expand Down
14 changes: 9 additions & 5 deletions crates/attestation/src/measurements.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ impl MultiMeasurements {
let measurements_map: HashMap<u8, String> = serde_json::from_str(input)?;

Ok(match attestation_type {
AttestationType::None => Self::NoAttestation,
AttestationType::AzureTdx => Self::Azure(
measurements_map
.into_iter()
Expand All @@ -179,8 +180,7 @@ impl MultiMeasurements {
})
.collect::<Result<_, MeasurementFormatError>>()?,
),
AttestationType::None => Self::NoAttestation,
_ => {
AttestationType::DcapTdx | AttestationType::GcpTdx | AttestationType::QemuTdx => {
Comment thread
0x416e746f6e marked this conversation as resolved.
let measurements_map = measurements_map
.into_iter()
.map(|(k, v)| {
Expand Down Expand Up @@ -304,7 +304,9 @@ impl MeasurementRecord {
measurements: match attestation_type {
AttestationType::None => ExpectedMeasurements::NoAttestation,
AttestationType::AzureTdx => ExpectedMeasurements::Azure(HashMap::new()),
_ => ExpectedMeasurements::Dcap(HashMap::new()),
AttestationType::DcapTdx | AttestationType::GcpTdx | AttestationType::QemuTdx => {
ExpectedMeasurements::Dcap(HashMap::new())
}
},
}
}
Expand Down Expand Up @@ -524,6 +526,7 @@ impl MeasurementPolicy {

if let Some(measurements) = record.measurements {
let expected_measurements = match attestation_type {
AttestationType::None => ExpectedMeasurements::NoAttestation,
AttestationType::AzureTdx => {
let azure_measurements = measurements
.iter()
Expand All @@ -535,8 +538,9 @@ impl MeasurementPolicy {
)?;
ExpectedMeasurements::Azure(azure_measurements)
}
AttestationType::None => ExpectedMeasurements::NoAttestation,
_ => ExpectedMeasurements::Dcap(
AttestationType::DcapTdx |
AttestationType::GcpTdx |
AttestationType::QemuTdx => ExpectedMeasurements::Dcap(
measurements
.iter()
.map(|(index_str, entry)| {
Expand Down
Loading
Loading