Skip to content
Open
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
68 changes: 68 additions & 0 deletions dstack/dstack-attest/src/attestation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -870,6 +870,40 @@ impl TdxAttestationExt for AttestationV1 {
}

impl AttestationV1 {
/// Convert a V1 dstack attestation back to the legacy SCALE schema.
///
/// This is only lossless for the original dstack stack with V1 runtime
/// events. Pod payloads and newer event encodings must remain on the V1
/// msgpack wire format.
pub fn try_into_legacy(self) -> Result<Attestation> {
let Self {
platform, stack, ..
} = self;
let StackEvidence::Dstack {
report_data,
runtime_events,
config,
} = stack
else {
bail!("dstack-pod attestation cannot be represented by the legacy schema");
};
if runtime_events
.iter()
.any(|event| !matches!(event.version, EventLogVersion::V1))
{
bail!("non-V1 runtime events cannot be represented by the legacy schema");
}
Ok(Attestation {
quote: platform_into_legacy_quote(platform),
runtime_events,
report_data: report_data
.try_into()
.map_err(|_| anyhow!("stack.report_data must be 64 bytes"))?,
config,
report: (),
})
}

/// Decode the VM config from the external or embedded config.
pub fn decode_vm_config<'a>(&'a self, config: &'a str) -> Result<VmConfig> {
decode_vm_config_with_fallback(config, self.stack.config())
Expand Down Expand Up @@ -2819,6 +2853,40 @@ mod tests {
assert!(matches!(upgraded.stack, StackEvidence::Dstack { .. }));
}

#[test]
fn v1_dstack_with_v1_events_converts_losslessly_to_legacy() {
let mut legacy = dummy_tdx_attestation([0x5a; 64]);
legacy.runtime_events.push(cc_eventlog::RuntimeEvent::new(
"legacy-event".into(),
vec![1, 2, 3],
cc_eventlog::EventLogVersion::V1,
));
let converted = legacy.clone().into_v1().try_into_legacy().unwrap();
assert_eq!(converted.report_data, legacy.report_data);
assert_eq!(converted.runtime_events.len(), 1);
assert!(matches!(
converted.into_versioned(),
VersionedAttestation::V0 { .. }
));
}

#[test]
fn v1_conversion_rejects_lossy_legacy_projection() {
let pod = dummy_tdx_attestation([0x5b; 64])
.into_v1()
.into_dstack_pod("payload".into());
assert!(pod.try_into_legacy().is_err());
let mut v2 = dummy_tdx_attestation([0x5c; 64]).into_v1();
if let StackEvidence::Dstack { runtime_events, .. } = &mut v2.stack {
runtime_events.push(cc_eventlog::RuntimeEvent::new(
"v2-event".into(),
vec![4, 5, 6],
cc_eventlog::EventLogVersion::V2,
));
}
assert!(v2.try_into_legacy().is_err());
}

#[test]
fn versioned_v0_projects_to_v1() {
let projected = dummy_tdx_attestation([5u8; 64]).into_versioned().into_v1();
Expand Down
3 changes: 2 additions & 1 deletion dstack/guest-agent-simulator/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,10 +181,11 @@ mod tests {
}

#[test]
fn simulator_attest_response_uses_supplied_report_data() {
fn simulator_attest_response_preserves_legacy_wire_format() {
let platform = load_fixture_platform();
let report_data = [0x5a; 64];
let response = platform.attest_response(report_data).unwrap();
assert_eq!(response.attestation.first(), Some(&0x00));
let patched = VersionedAttestation::from_bytes(&response.attestation)
.unwrap()
.into_v1();
Expand Down
27 changes: 16 additions & 11 deletions dstack/guest-agent-simulator/src/simulator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,23 +58,24 @@ pub fn simulated_quote_response(
}

pub fn simulated_attest_response(
attestation: &VersionedAttestation,
source: &VersionedAttestation,
report_data: [u8; 64],
patch_report_data: bool,
generator: Option<&TdxGenerator>,
) -> Result<AttestResponse> {
let mut attestation = prepare_attestation(
attestation,
report_data,
patch_report_data,
generator,
"attest",
)?;
let preserve_legacy = matches!(source, VersionedAttestation::V0 { .. });
let mut attestation =
prepare_attestation(source, report_data, patch_report_data, generator, "attest")?;
if let Some(event_log) = attestation.platform.tdx_event_log_mut() {
cc_eventlog::tdx::fill_v2_preimages(event_log);
}
let attestation = if preserve_legacy {
attestation.try_into_legacy()?.into_versioned()
} else {
VersionedAttestation::V1 { attestation }
};
Ok(AttestResponse {
attestation: VersionedAttestation::V1 { attestation }.to_bytes()?,
attestation: attestation.to_bytes()?,
})
}

Expand All @@ -83,19 +84,23 @@ pub fn simulated_info_attestation(attestation: &VersionedAttestation) -> Version
}

pub fn simulated_certificate_attestation(
attestation: &VersionedAttestation,
source: &VersionedAttestation,
pubkey: &[u8],
patch_report_data: bool,
generator: Option<&TdxGenerator>,
) -> Result<VersionedAttestation> {
let preserve_legacy = matches!(source, VersionedAttestation::V0 { .. });
let report_data = QuoteContentType::RaTlsCert.to_report_data(pubkey);
let attestation = prepare_attestation(
attestation,
source,
report_data,
patch_report_data,
generator,
"certificate_attestation",
)?;
if preserve_legacy {
return Ok(attestation.try_into_legacy()?.into_versioned());
}
Ok(VersionedAttestation::V1 { attestation })
}

Expand Down