Skip to content
Merged
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: 17 additions & 1 deletion Cargo.lock

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

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ members = [
"crates/tee-context",
"crates/tee-launcher",
"crates/tee-verifier",
"crates/tee-verifier-conversions",
"crates/tee-verifier-interface",
"crates/test-migration-contract",
"crates/test-parallel-contract",
Expand Down Expand Up @@ -67,6 +68,7 @@ near-mpc-sdk = { path = "crates/near-mpc-sdk", version = "0.0.1" }
near-mpc-signature-verifier = { path = "crates/near-mpc-signature-verifier", version = "0.0.1" }
node-types = { path = "crates/node-types" }
tee-authority = { path = "crates/tee-authority" }
tee-verifier-conversions = { path = "crates/tee-verifier-conversions" }
tee-verifier-interface = { path = "crates/tee-verifier-interface" }
test-port-allocator = { path = "crates/test-port-allocator" }
test-utils = { path = "crates/test-utils" }
Expand Down
2 changes: 1 addition & 1 deletion crates/attestation-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ anyhow = { workspace = true }
attestation = { workspace = true }
bs58 = { workspace = true }
clap = { workspace = true }
mpc-attestation = { workspace = true }
mpc-attestation = { workspace = true, features = ["local-verify"] }
Comment thread
netrome marked this conversation as resolved.
mpc-primitives = { workspace = true }
node-types = { workspace = true }
reqwest = { workspace = true }
Expand Down
2 changes: 1 addition & 1 deletion crates/attestation-cli/src/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ pub fn verify_at_timestamp(
let AcceptedAttestation {
attestation: verified_attestation,
advisory_ids,
} = attestation.verify(
} = attestation.verify_locally(
report_data.into(),
timestamp_seconds,
&cli.allowed_image_hashes,
Expand Down
15 changes: 13 additions & 2 deletions crates/attestation/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,35 @@ license = { workspace = true }
edition = { workspace = true }

[features]
borsh-schema = ["borsh/unstable__schema"]
borsh-schema = ["borsh/unstable__schema", "tee-verifier-interface/borsh-schema"]
dstack-conversions = ["dep:dstack-sdk-types"]
test-utils = []
# Pulls in `dcap-qvl` for full local DCAP + post-DCAP verification. Meant for
# off-chain callers; `mpc-contract` enables it today.
# TODO(#3264): contract drops this once DCAP moves to the verifier contract.
local-verify = ["dep:dcap-qvl", "dep:tee-verifier-conversions"]

[dependencies]
borsh = { workspace = true }
dcap-qvl = { workspace = true }
dcap-qvl = { workspace = true, optional = true }
derive_more = { workspace = true }
dstack-sdk-types = { workspace = true, optional = true }
hex = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
serde_with = { workspace = true }
sha2 = { workspace = true }
tee-verifier-conversions = { workspace = true, optional = true }
tee-verifier-interface = { workspace = true, features = ["serde"] }
thiserror = { workspace = true }

[dev-dependencies]
assert_matches = { workspace = true }
attestation = { path = ".", features = [
"local-verify",
"test-utils",
"dstack-conversions",
] }
dstack-sdk-types = { workspace = true }
rstest = { workspace = true }
test-utils = { workspace = true }
Expand Down
94 changes: 58 additions & 36 deletions crates/attestation/src/attestation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ use core::fmt;
use derive_more::Constructor;
use serde::{Deserialize, Serialize};
use sha2::{Digest as _, Sha256, Sha384};
use tee_verifier_interface::{TDReport10, VerifiedReport};

#[cfg(feature = "local-verify")]
use crate::dcap_conversions::{IntoDcapType as _, IntoInterfaceType as _};

/// Expected TCB status for a successfully verified TEE quote.
const EXPECTED_QUOTE_STATUS: &str = "UpToDate";
Expand All @@ -37,9 +41,10 @@ pub struct DstackAttestation {
pub tcb_info: TcbInfo,
}

/// Result of a successful [`DstackAttestation::verify`] call.
/// Result of successfully verifying an attestation.
#[derive(Clone, Debug)]
pub struct AcceptedDstackAttestation {
/// The accepted measurement set this attestation matched.
pub measurements: ExpectedMeasurements,
/// Informational advisory IDs (e.g. `INTEL-DOC-10000` post-ESU) surfaced by
/// Intel's PCS alongside an `UpToDate` TCB status. They are not a security
Expand Down Expand Up @@ -123,33 +128,20 @@ impl fmt::Debug for DstackAttestation {
}

impl DstackAttestation {
/// Checks whether this attestation is valid
/// with respect to expected values of:
/// - report_data: must be measured correctly in RTMR3
/// - timestamp_seconds: current UNIX time in seconds
/// - accepted_measurements: set of accepted RTMRs and key-provider event digest.
/// If any element in the set is valid, the function accepts the attestation as
/// valid.
///
/// On success, returns the matched measurements along with any informational
/// advisory IDs surfaced alongside an `UpToDate` TCB status.
pub fn verify(
/// Runs the post-DCAP checks against an already-verified report.
pub fn verify_with_report(
&self,
report: &VerifiedReport,
expected_report_data: ReportData,
timestamp_seconds: u64,
accepted_measurements: &[ExpectedMeasurements],
) -> Result<AcceptedDstackAttestation, VerificationError> {
let verification_result =
dcap_qvl::verify::verify(&self.quote, &self.collateral, timestamp_seconds)
.map_err(|e| VerificationError::DcapVerification(e.to_string()))?;

let report_data = verification_result
let report_data = report
.report
.as_td10()
.ok_or(VerificationError::ReportNotTd10)?;

// Verify all attestation components
let advisory_ids = Self::verify_tcb_status(&verification_result)?;
let advisory_ids = Self::verify_tcb_status(report)?;
self.verify_report_data(&expected_report_data, report_data)?;

self.verify_rtmr3(report_data, &self.tcb_info)?;
Expand All @@ -163,6 +155,35 @@ impl DstackAttestation {
})
}

/// Full local verification: runs `dcap_qvl::verify::verify` and then the
/// post-DCAP checks via [`Self::verify_with_report`].
#[cfg(feature = "local-verify")]
pub fn verify_locally(
&self,
expected_report_data: ReportData,
timestamp_seconds: u64,
accepted_measurements: &[ExpectedMeasurements],
) -> Result<AcceptedDstackAttestation, VerificationError> {
let report = self.verify_dcap_quote(timestamp_seconds)?;
self.verify_with_report(&report, expected_report_data, accepted_measurements)
}

/// Runs only the DCAP step (`dcap_qvl::verify::verify`) and returns the
/// resulting report as the `tee-verifier-interface` mirror — the same value
/// the `tee-verifier` contract returns on-chain.
#[cfg(feature = "local-verify")]
pub fn verify_dcap_quote(
&self,
timestamp_seconds: u64,
) -> Result<VerifiedReport, VerificationError> {
let collateral = self.collateral.clone().into_dcap_type();
Ok(
dcap_qvl::verify::verify(&self.quote.0, &collateral, timestamp_seconds)
.map_err(|e| VerificationError::DcapVerification(e.to_string()))?
.into_interface_type(),
)
}

/// Replays RTMR3 from the event log by hashing all relevant events together and verifies all
/// digests are correct
fn verify_event_log_rtmr3(
Expand Down Expand Up @@ -241,21 +262,18 @@ impl DstackAttestation {
/// after a product's Extended Servicing Updates date). These may appear with
/// `UpToDate` and do not indicate a vulnerability; they are returned so the
/// caller can log/expose them.
fn verify_tcb_status(
verification_result: &dcap_qvl::verify::VerifiedReport,
) -> Result<Vec<String>, VerificationError> {
(verification_result.status == EXPECTED_QUOTE_STATUS).or_err(|| {
VerificationError::TcbStatusNotUpToDate(verification_result.status.clone())
})?;
fn verify_tcb_status(report: &VerifiedReport) -> Result<Vec<String>, VerificationError> {
(report.status == EXPECTED_QUOTE_STATUS)
.or_err(|| VerificationError::TcbStatusNotUpToDate(report.status.clone()))?;

Ok(verification_result.advisory_ids.clone())
Ok(report.advisory_ids.clone())
}

/// Verifies report data matches expected values.
fn verify_report_data(
&self,
expected: &ReportData,
actual: &dcap_qvl::quote::TDReport10,
actual: &TDReport10,
) -> Result<(), VerificationError> {
// Check if sha384(tls_public_key) matches the hash in report_data. This check effectively
// proves that tls_public_key was included in the quote's report_data by an app running
Expand All @@ -267,7 +285,7 @@ impl DstackAttestation {
/// On success, returns the matched measurements.
fn verify_any_measurements(
&self,
report_data: &dcap_qvl::quote::TDReport10,
report_data: &TDReport10,
tcb_info: &TcbInfo,
accepted_measurements: &[ExpectedMeasurements],
) -> Result<ExpectedMeasurements, VerificationError> {
Expand All @@ -292,7 +310,7 @@ impl DstackAttestation {
/// Verifies static RTMRs match expected values.
fn verify_static_rtmrs(
&self,
report_data: &dcap_qvl::quote::TDReport10,
report_data: &TDReport10,
tcb_info: &TcbInfo,
expected_measurements: &ExpectedMeasurements,
) -> Result<(), VerificationError> {
Expand Down Expand Up @@ -346,7 +364,7 @@ impl DstackAttestation {
/// Verifies RTMR3 by replaying event log.
fn verify_rtmr3(
&self,
report_data: &dcap_qvl::quote::TDReport10,
report_data: &TDReport10,
tcb_info: &TcbInfo,
) -> Result<(), VerificationError> {
compare_hashes("rtmr3", tcb_info.rtmr3.as_slice(), &report_data.rt_mr3)?;
Expand Down Expand Up @@ -489,10 +507,8 @@ mod tests {
use super::*;

use alloc::{string::ToString, vec, vec::Vec};
use dcap_qvl::{
quote::{EnclaveReport, Report},
tcb_info::{TcbStatus, TcbStatusWithAdvisory},
verify::VerifiedReport,
use tee_verifier_interface::{
EnclaveReport, Report, TcbStatus, TcbStatusWithAdvisory, VerifiedReport,
};

fn verified_report(status: &str, advisory_ids: Vec<String>) -> VerifiedReport {
Expand All @@ -516,8 +532,14 @@ mod tests {
report_data: [0u8; 64],
}),
ppid: Vec::new(),
qe_status: TcbStatusWithAdvisory::new(TcbStatus::UpToDate, Vec::new()),
platform_status: TcbStatusWithAdvisory::new(TcbStatus::UpToDate, Vec::new()),
qe_status: TcbStatusWithAdvisory {
status: TcbStatus::UpToDate,
advisory_ids: Vec::new(),
},
platform_status: TcbStatusWithAdvisory {
status: TcbStatus::UpToDate,
advisory_ids: Vec::new(),
},
}
}

Expand Down
Loading
Loading