Skip to content
This repository was archived by the owner on Jun 10, 2026. It is now read-only.

Commit 603c6ee

Browse files
authored
fix: implement GcpTdx and NitroEnclave support in V1 attestation (#10)
* fix: implement GcpTdx and NitroEnclave support in V1 attestation The upstream merge left V1 AttestationV1's decode_app_info_ex and verify_with_time with bail!() stubs for GcpTdx and NitroEnclave, since upstream only has empty placeholders for these platforms. This commit adds full implementations: - decode_app_info_ex: GcpTdx uses TPM PCR-based MR computation, NitroEnclave uses NSM PCR-based MR computation - verify_with_time: GcpTdx performs TDX quote + TPM quote verification with PCR replay, NitroEnclave performs COSE signature + cert chain verification All callers (guest-agent, KMS, verifier, ra-rpc) use into_v1() and were broken on these platforms without this fix. * fix: mark vsock listener bind test as ignored in CI The test requires vsock kernel support which is not available in GitHub Actions runners.
1 parent 2083e43 commit 603c6ee

3 files changed

Lines changed: 104 additions & 12 deletions

File tree

dstack-attest/src/attestation.rs

Lines changed: 102 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -574,8 +574,17 @@ impl AttestationV1 {
574574
PlatformEvidence::Tdx { quote, .. } => {
575575
decode_mr_tdx_from_quote(boottime_mr, &mr_key_provider, quote, runtime_events)?
576576
}
577-
PlatformEvidence::GcpTdx { .. } | PlatformEvidence::NitroEnclave { .. } => {
578-
bail!("Unsupported attestation quote");
577+
PlatformEvidence::GcpTdx { tpm_quote, .. } => decode_mr_gcp_tpm_from_v1(
578+
boottime_mr,
579+
&mr_key_provider,
580+
&os_image_hash,
581+
tpm_quote,
582+
runtime_events,
583+
)?,
584+
PlatformEvidence::NitroEnclave { nsm_quote } => {
585+
decode_mr_nitro_nsm_from_v1(&DstackNitroQuote {
586+
nsm_quote: nsm_quote.clone(),
587+
})?
579588
}
580589
};
581590
let compose_hash = if platform_attestation_mode(&self.platform).is_composable() {
@@ -640,11 +649,60 @@ impl AttestationV1 {
640649
verify_tdx_quote_with_events(pccs_url, quote, &runtime_events, &report_data)
641650
.await?,
642651
),
643-
PlatformEvidence::GcpTdx { .. } | PlatformEvidence::NitroEnclave { .. } => {
644-
bail!(
645-
"Unsupported attestation mode: {:?}",
646-
platform_attestation_mode(&platform)
647-
);
652+
PlatformEvidence::GcpTdx {
653+
quote, tpm_quote, ..
654+
} => {
655+
let tdx_report =
656+
verify_tdx_quote_with_events(pccs_url, quote, &runtime_events, &report_data)
657+
.await?;
658+
let tpm_report = tpm_qvl::get_collateral_and_verify(tpm_quote)
659+
.await
660+
.context("failed to verify TPM quote")?;
661+
let qualifying_data = sha256(quote);
662+
if tpm_report.attest.qualified_data != qualifying_data[..] {
663+
bail!("tpm qualified_data mismatch");
664+
}
665+
let pcr_ind: u32 = 14; // GcpTdx runtime PCR
666+
let replayed_rt_pcr = cc_eventlog::replay_events::<Sha256>(&runtime_events, None);
667+
let quoted_rt_pcr = tpm_report
668+
.get_pcr(pcr_ind)
669+
.context("no runtime PCR in TPM report")?;
670+
if replayed_rt_pcr != quoted_rt_pcr[..] {
671+
bail!(
672+
"PCR{pcr_ind} mismatch, quoted: {}, replayed: {}",
673+
hex::encode(quoted_rt_pcr),
674+
hex::encode(replayed_rt_pcr),
675+
);
676+
}
677+
DstackVerifiedReport::DstackGcpTdx {
678+
tdx_report,
679+
tpm_report,
680+
}
681+
}
682+
PlatformEvidence::NitroEnclave { nsm_quote } => {
683+
let nsm = DstackNitroQuote {
684+
nsm_quote: nsm_quote.clone(),
685+
};
686+
let verified_report = nsm_qvl::verify_attestation(
687+
&nsm.nsm_quote,
688+
nsm_qvl::AWS_NITRO_ENCLAVES_ROOT_G1,
689+
None,
690+
_now,
691+
)
692+
.context("NSM attestation verification failed")?;
693+
let Some(user_data) = verified_report.user_data.clone() else {
694+
bail!("NSM attestation document does not contain user_data");
695+
};
696+
if user_data != report_data[..] {
697+
bail!("NSM user_data does not match report_data");
698+
}
699+
let pcrs = nsm.decode_pcrs().context("failed to decode nitro pcrs")?;
700+
DstackVerifiedReport::DstackNitroEnclave(NitroVerifiedReport {
701+
module_id: verified_report.module_id,
702+
pcrs,
703+
user_data,
704+
timestamp: verified_report.timestamp,
705+
})
648706
}
649707
};
650708

@@ -854,6 +912,43 @@ struct Mrs {
854912
mr_aggregated: [u8; 32],
855913
}
856914

915+
fn decode_mr_gcp_tpm_from_v1(
916+
boottime_mr: bool,
917+
mr_key_provider: &[u8],
918+
os_image_hash: &[u8],
919+
tpm_quote: &TpmQuote,
920+
runtime_events: &[RuntimeEvent],
921+
) -> Result<Mrs> {
922+
let mr_system = sha256([os_image_hash, mr_key_provider]);
923+
let pcr0 = tpm_quote
924+
.pcr_values
925+
.iter()
926+
.find(|p| p.index == 0)
927+
.context("PCR 0 not found")?;
928+
let pcr2 = tpm_quote
929+
.pcr_values
930+
.iter()
931+
.find(|p| p.index == 2)
932+
.context("PCR 2 not found")?;
933+
let runtime_pcr =
934+
cc_eventlog::replay_events::<Sha256>(runtime_events, boottime_mr.then_some("boot-mr-done"));
935+
let mr_aggregated = sha256([&pcr0.value[..], &pcr2.value, &runtime_pcr]);
936+
Ok(Mrs {
937+
mr_system,
938+
mr_aggregated,
939+
})
940+
}
941+
942+
fn decode_mr_nitro_nsm_from_v1(nsm_quote: &DstackNitroQuote) -> Result<Mrs> {
943+
let pcrs = nsm_quote.decode_pcrs()?;
944+
let mr_system = sha256([&pcrs.pcr0, &pcrs.pcr1, &pcrs.pcr2]);
945+
let mr_aggregated = mr_system;
946+
Ok(Mrs {
947+
mr_system,
948+
mr_aggregated,
949+
})
950+
}
951+
857952
fn decode_mr_tdx_from_quote(
858953
boottime_mr: bool,
859954
mr_key_provider: &[u8],

dstack-attest/tests/nitro_verify.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@
44

55
//! Integration test: verify Nitro Enclave attestation end-to-end
66
7-
use dstack_attest::attestation::{
8-
AttestationQuote, DstackVerifiedReport, VersionedAttestation,
9-
};
7+
use dstack_attest::attestation::{AttestationQuote, DstackVerifiedReport, VersionedAttestation};
108
use nsm_qvl::{AttestationDocument, CoseSign1};
119
use std::time::{Duration, SystemTime};
1210

rocket-vsock-listener/src/lib.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -258,12 +258,11 @@ mod tests {
258258
}
259259

260260
#[tokio::test]
261+
#[ignore = "requires vsock support (not available in CI)"]
261262
async fn test_vsock_listener_bind() {
262263
let endpoint = VsockEndpoint { cid: 1, port: 5000 };
263264
let result = VsockListener::bind(&endpoint);
264265

265-
// Note: This test might fail if you don't have vsock permissions
266-
// or if the port is already in use
267266
assert!(result.is_ok());
268267
}
269268

0 commit comments

Comments
 (0)