Skip to content

Commit a1f4cc2

Browse files
committed
refactor: drop merged CCEL event-log export
Remove the event_log_ccel GetQuote field and the TCG merge/encode/parse machinery. The core of this change is the v2 self-describing digest pre-image; the merged binary CCEL export can land separately if a TCG-native verifier integration needs it. This also restores TdxEvent::digest() to always recompute runtime-event digests from the structured (name, payload, version) tuple: the stored- digest precedence existed only for records parsed back from the merged CCEL, which no longer occurs. - remove build_ccel_event_log / encode_runtime_events_as_tcg / ccel_content_len / read_ccel_raw and the round-trip test; the tcg module is private again - remove Attestation::get_tdx_event_log_ccel and the guest-agent fail-open plumbing - drop event_log_ccel from agent_rpc.proto and the four SDKs (runtime-event version/preimage fields are unchanged)
1 parent fac3031 commit a1f4cc2

12 files changed

Lines changed: 15 additions & 220 deletions

File tree

dstack/cc-eventlog/src/lib.rs

Lines changed: 1 addition & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,13 @@ pub use tdx::TdxEvent;
1010

1111
mod codecs;
1212
mod runtime_events;
13-
pub mod tcg;
13+
mod tcg;
1414
pub mod tdx;
1515
pub mod tpm;
1616

1717
#[cfg(test)]
1818
mod tests {
1919
use super::*;
20-
use dstack_types::EventLogVersion;
21-
use ez_hash::{Hasher, Sha384};
22-
use tdx::TdxEvent;
2320

2421
#[test]
2522
fn parse_ccel() {
@@ -30,63 +27,4 @@ mod tests {
3027
let json = serde_json::to_string_pretty(&tdx_event_logs).unwrap();
3128
insta::assert_snapshot!(json);
3229
}
33-
34-
#[test]
35-
fn encode_runtime_events_roundtrip() {
36-
// Synthesize a CCEL: sample boot-time bytes + encoded runtime events.
37-
// Decode with the same TcgEventLog parser and verify:
38-
// 1. all boot-time events are preserved
39-
// 2. runtime events appear with the expected pcrIndex / event_type / digest
40-
// 3. sha384(event_data) == digest (the property we document)
41-
let boot_raw = include_bytes!("../samples/ccel.bin");
42-
let valid = tcg::ccel_content_len(boot_raw).unwrap();
43-
44-
let runtime_v1: TdxEvent = RuntimeEvent::new(
45-
"app-id".to_string(),
46-
vec![0xaa, 0xbb, 0xcc],
47-
EventLogVersion::V1,
48-
)
49-
.into();
50-
let runtime_v2: TdxEvent = RuntimeEvent::new(
51-
"compose-hash".to_string(),
52-
vec![0xde, 0xad, 0xbe, 0xef],
53-
EventLogVersion::V2,
54-
)
55-
.into();
56-
let runtime_events = vec![runtime_v1.clone(), runtime_v2.clone()];
57-
58-
let mut merged = boot_raw[..valid].to_vec();
59-
merged.extend_from_slice(&tcg::encode_runtime_events_as_tcg(&runtime_events));
60-
merged.extend_from_slice(&0xFFFF_FFFFu32.to_le_bytes());
61-
62-
let parsed = tcg::TcgEventLog::decode(&mut merged.as_slice()).unwrap();
63-
let boot_count = tcg::TcgEventLog::decode(&mut boot_raw.as_slice())
64-
.unwrap()
65-
.event_logs
66-
.len();
67-
assert_eq!(parsed.event_logs.len(), boot_count + 2);
68-
69-
// Convert and verify runtime tail matches what we put in.
70-
let converted = parsed.to_cc_event_log().unwrap();
71-
let tail = &converted[converted.len() - 2..];
72-
for (orig, got) in runtime_events.iter().zip(tail.iter()) {
73-
assert_eq!(got.imr, orig.imr);
74-
assert_eq!(got.event_type, orig.event_type);
75-
assert_eq!(got.digest, orig.digest());
76-
// The event_payload carried in TdxEvent after TCG round-trip is the
77-
// preimage bytes we stored as TCG event data.
78-
let runtime = orig.to_runtime_event().unwrap();
79-
assert_eq!(got.event_payload, runtime.preimage());
80-
assert_eq!(
81-
got.preimage.as_deref(),
82-
Some(hex::encode(&got.event_payload).as_str())
83-
);
84-
// Decoded CCEL records do not retain the structured dstack event
85-
// tuple, so digest() must use the authoritative stored digest.
86-
assert_eq!(got.digest(), got.digest);
87-
// Property: sha384(event_data) == digest
88-
let h = Sha384::hash([got.event_payload.as_slice()]);
89-
assert_eq!(h.as_slice(), got.digest.as_slice());
90-
}
91-
}
9230
}

dstack/cc-eventlog/src/tcg.rs

Lines changed: 9 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
//
55
// SPDX-License-Identifier: Apache-2.0
66

7-
use crate::{codecs::VecOf, runtime_events::DSTACK_RUNTIME_EVENT_TYPE, tdx::TdxEvent};
7+
use crate::{codecs::VecOf, tdx::TdxEvent};
88
use anyhow::{bail, Context, Result};
99
use scale::Decode;
1010
use std::path::PathBuf;
@@ -302,8 +302,10 @@ impl TcgEventLog {
302302
}
303303

304304
pub fn decode_from_ccel_file() -> Result<Self> {
305-
let data = read_ccel_raw()?;
306-
Self::decode(&mut data.as_slice())
305+
let path = ccel_file_path()?;
306+
let data = fs_err::read(&path)
307+
.with_context(|| format!("failed to read CCEL from {}", path.display()))?;
308+
Self::decode(&mut data.as_slice()).context("failed to decode CCEL")
307309
}
308310

309311
pub fn to_cc_event_log(&self) -> Result<Vec<TdxEvent>> {
@@ -318,8 +320,8 @@ impl TcgEventLog {
318320

319321
/// Resolve the CCEL path, honoring `DSTACK_CCEL_FILE` when set.
320322
///
321-
/// Overrides must be non-empty absolute paths so relative values cannot
322-
/// silently resolve against the process working directory.
323+
/// Overrides must be non-empty absolute paths so relative values cannot silently
324+
/// resolve against the process working directory.
323325
fn ccel_file_path() -> Result<PathBuf> {
324326
let Some(value) = std::env::var_os(CCEL_FILE_ENV) else {
325327
return Ok(PathBuf::from(CCEL_FILE));
@@ -337,52 +339,6 @@ fn ccel_file_path() -> Result<PathBuf> {
337339
Ok(path)
338340
}
339341

340-
/// Read the raw ACPI CCEL table bytes.
341-
pub fn read_ccel_raw() -> Result<Vec<u8>> {
342-
let path = ccel_file_path()?;
343-
fs_err::read(&path).with_context(|| format!("failed to read CCEL from {}", path.display()))
344-
}
345-
346-
/// Return the length of the valid TCG event log prefix within a raw CCEL buffer.
347-
///
348-
/// ACPI CCEL tables are fixed-size regions padded with 0xFF; the event stream
349-
/// either ends with a 0xFFFFFFFF terminator or by running into that padding.
350-
/// This parses the buffer to find where real events end so the trailer can be
351-
/// stripped before appending runtime events.
352-
pub fn ccel_content_len(raw: &[u8]) -> Result<usize> {
353-
let input = &mut &raw[..];
354-
TcgEventLog::decode(input)?;
355-
Ok(raw.len() - input.len())
356-
}
357-
358-
/// Encode dstack runtime events as TCG_PCR_EVENT2 records.
359-
///
360-
/// Non-runtime events in the slice are skipped. Each runtime event becomes:
361-
/// - pcrIndex = imr + 1 (0-based TdxEvent -> 1-based TCG pcrIndex)
362-
/// - eventType = DSTACK_RUNTIME_EVENT_TYPE
363-
/// - digests = [{ TPM_ALG_SHA384, <48-byte digest> }]
364-
/// - event = the digest pre-image bytes (`preimage`), so that
365-
/// sha384(event) == digest holds for any TCG parser.
366-
pub fn encode_runtime_events_as_tcg(events: &[TdxEvent]) -> Vec<u8> {
367-
let mut out = Vec::new();
368-
for event in events {
369-
let Some(runtime) = event.to_runtime_event() else {
370-
continue;
371-
};
372-
let preimage = runtime.preimage();
373-
let digest = event.digest();
374-
let pcr_index = event.imr.saturating_add(1);
375-
out.extend_from_slice(&pcr_index.to_le_bytes());
376-
out.extend_from_slice(&event.event_type.to_le_bytes());
377-
out.extend_from_slice(&1u32.to_le_bytes());
378-
out.extend_from_slice(&TPM_ALG_SHA384.to_le_bytes());
379-
out.extend_from_slice(&digest);
380-
out.extend_from_slice(&(preimage.len() as u32).to_le_bytes());
381-
out.extend_from_slice(&preimage);
382-
}
383-
out
384-
}
385-
386342
fn parse_spec_id_event_log<I: scale::Input>(
387343
input: &mut I,
388344
) -> Result<(TcgEvent, TcgEfiSpecIdEvent)> {
@@ -430,9 +386,6 @@ impl TryFrom<TcgEvent> for TdxEvent {
430386
.next()
431387
.context("digest not found")?
432388
.hash;
433-
let event_payload: Vec<u8> = value.event.into();
434-
let preimage =
435-
(value.event_type == DSTACK_RUNTIME_EVENT_TYPE).then(|| hex::encode(&event_payload));
436389
Ok(TdxEvent {
437390
imr: value
438391
.imr_index
@@ -441,29 +394,13 @@ impl TryFrom<TcgEvent> for TdxEvent {
441394
event_type: value.event_type,
442395
digest,
443396
event: Default::default(),
444-
event_payload,
397+
event_payload: value.event.into(),
445398
version: Default::default(),
446-
preimage,
399+
preimage: None,
447400
})
448401
}
449402
}
450403

451-
/// Build a merged TCG binary event log: raw ACPI CCEL (boot-time) followed by
452-
/// the given runtime events encoded as TCG_PCR_EVENT2 records.
453-
///
454-
/// Non-runtime entries in `events` are ignored; only events with
455-
/// `event_type == DSTACK_RUNTIME_EVENT_TYPE` are appended.
456-
pub fn build_ccel_event_log(events: &[TdxEvent]) -> Result<Vec<u8>> {
457-
let raw = read_ccel_raw()?;
458-
let end = ccel_content_len(&raw)?;
459-
let mut out = raw[..end].to_vec();
460-
out.extend_from_slice(&encode_runtime_events_as_tcg(events));
461-
// Append the 0xFFFFFFFF terminator so parsers know where the event
462-
// stream ends (the original ACPI table has trailing 0xFF padding).
463-
out.extend_from_slice(&0xFFFF_FFFFu32.to_le_bytes());
464-
Ok(out)
465-
}
466-
467404
#[cfg(test)]
468405
mod tests {
469406
use super::*;

dstack/cc-eventlog/src/tdx.rs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -122,11 +122,6 @@ impl TdxEvent {
122122
}
123123

124124
pub fn digest(&self) -> Vec<u8> {
125-
// Parsed TCG/CCEL records already carry the authoritative digest, but
126-
// do not necessarily retain dstack's structured event name/payload.
127-
if !self.digest.is_empty() {
128-
return self.digest.clone();
129-
}
130125
if let Some(runtime_event) = self.to_runtime_event() {
131126
return runtime_event.sha384_digest().to_vec();
132127
}

dstack/dstack-attest/src/attestation.rs

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1483,18 +1483,6 @@ impl<T> Attestation<T> {
14831483
.and_then(|q| Quote::parse(&q.quote).ok())
14841484
.and_then(|quote| quote.report.as_td10().cloned())
14851485
}
1486-
1487-
/// Get the merged TCG binary CCEL event log (boot-time CCEL + runtime
1488-
/// events encoded as TCG_PCR_EVENT2 records).
1489-
///
1490-
/// Returns an empty `Vec` on platforms without a TDX quote (e.g. Nitro).
1491-
/// Propagates errors from reading or parsing the ACPI CCEL file.
1492-
pub fn get_tdx_event_log_ccel(&self) -> Result<Vec<u8>> {
1493-
let Some(q) = self.tdx_quote() else {
1494-
return Ok(Vec::new());
1495-
};
1496-
cc_eventlog::tcg::build_ccel_event_log(&q.event_log)
1497-
}
14981486
}
14991487

15001488
pub trait GetDeviceId {

dstack/guest-agent-simulator/src/simulator.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ pub fn simulated_quote_response(
4545
report_data: report_data.to_vec(),
4646
vm_config: vm_config.to_string(),
4747
attestation: versioned,
48-
event_log_ccel: Vec::new(),
4948
})
5049
}
5150

dstack/guest-agent/rpc/proto/agent_rpc.proto

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -210,27 +210,6 @@ message GetQuoteResponse {
210210
// Platform-adaptive versioned attestation (SCALE/msgpack encoded). Populated
211211
// on non-TDX platforms; TDX uses quote + event_log above.
212212
bytes attestation = 5;
213-
// TCG binary Confidential Computing Event Log (CCEL) containing both
214-
// boot-time events (from the ACPI CCEL table) and dstack runtime events
215-
// (RTMR3) merged into a single stream.
216-
//
217-
// Encoding per TCG PC Client Platform Firmware Profile:
218-
// - Starts with a Spec ID header event (TCG_PCClientPCREvent).
219-
// - Boot-time events are copied verbatim from the ACPI CCEL table
220-
// (TCG_PCR_EVENT2 with the digests the firmware wrote).
221-
// - Runtime events (event_type = 0x08000001) are appended as
222-
// TCG_PCR_EVENT2 records with:
223-
// * pcrIndex = imr + 1 (so RTMR3 becomes 4)
224-
// * eventType = 0x08000001
225-
// * digests = [{ algId: TPM_ALG_SHA384 (0xC), hash: 48 bytes }]
226-
// * event = the digest pre-image bytes, so that
227-
// sha384(event) == digest holds independently of the
228-
// dstack schema. The pre-image is:
229-
// V1: event_type_le || ":" || name || ":" || payload
230-
// V2: canonical JSON (UTF-8)
231-
//
232-
// Empty on platforms without an ACPI CCEL table (e.g. Nitro Enclaves).
233-
bytes event_log_ccel = 6;
234213
}
235214

236215
// The request to derive a key

dstack/guest-agent/src/backend.rs

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,6 @@ use anyhow::{Context, Result};
66
use dstack_guest_agent_rpc::{AttestResponse, GetQuoteResponse};
77
use ra_tls::attestation::Attestation;
88
use ra_tls::attestation::{QuoteContentType, VersionedAttestation};
9-
use tracing::warn;
10-
11-
fn event_log_ccel_or_empty(result: Result<Vec<u8>>) -> Vec<u8> {
12-
match result {
13-
Ok(event_log) => event_log,
14-
Err(err) => {
15-
warn!(error = ?err, "failed to build TDX CCEL event log");
16-
Vec::new()
17-
}
18-
}
19-
}
209

2110
pub trait PlatformBackend: Send + Sync {
2211
fn attestation_for_info(&self) -> Result<VersionedAttestation>;
@@ -46,7 +35,6 @@ impl PlatformBackend for RealPlatform {
4635
let attestation = Attestation::quote(&report_data).context("Failed to get quote")?;
4736
let tdx_quote = attestation.get_tdx_quote_bytes();
4837
let tdx_event_log = attestation.get_tdx_event_log_string();
49-
let event_log_ccel = event_log_ccel_or_empty(attestation.get_tdx_event_log_ccel());
5038
let versioned = if tdx_quote.is_some() {
5139
Vec::new()
5240
} else {
@@ -61,7 +49,6 @@ impl PlatformBackend for RealPlatform {
6149
report_data: report_data.to_vec(),
6250
vm_config: vm_config.to_string(),
6351
attestation: versioned,
64-
event_log_ccel,
6552
})
6653
}
6754

@@ -74,14 +61,3 @@ impl PlatformBackend for RealPlatform {
7461
})
7562
}
7663
}
77-
78-
#[cfg(test)]
79-
mod tests {
80-
use super::*;
81-
82-
#[test]
83-
fn ccel_export_failure_is_fail_open() {
84-
let event_log = event_log_ccel_or_empty(Err(anyhow::anyhow!("CCEL unavailable")));
85-
assert!(event_log.is_empty());
86-
}
87-
}

dstack/guest-agent/src/rpc_service.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -885,7 +885,6 @@ pNs85uhOZE8z2jr8Pg==
885885
report_data: report_data.to_vec(),
886886
vm_config: vm_config.to_string(),
887887
attestation: Vec::new(),
888-
event_log_ccel: Vec::new(),
889888
})
890889
}
891890

sdk/go/dstack/client.go

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -86,12 +86,11 @@ func (r *GetKeyResponse) DecodeSignatureChain() ([][]byte, error) {
8686

8787
// Represents the response from a quote request.
8888
type GetQuoteResponse struct {
89-
Quote string `json:"quote"`
90-
EventLog string `json:"event_log"`
91-
ReportData string `json:"report_data"`
92-
VmConfig string `json:"vm_config"`
93-
Attestation string `json:"attestation"`
94-
EventLogCCEL string `json:"event_log_ccel"`
89+
Quote string `json:"quote"`
90+
EventLog string `json:"event_log"`
91+
ReportData string `json:"report_data"`
92+
VmConfig string `json:"vm_config"`
93+
Attestation string `json:"attestation"`
9594
}
9695

9796
// DecodeQuote returns the quote bytes
@@ -115,10 +114,6 @@ func (r *GetQuoteResponse) DecodeEventLog() ([]EventLog, error) {
115114
return events, err
116115
}
117116

118-
// DecodeEventLogCCEL returns the TCG binary CCEL event log bytes.
119-
func (r *GetQuoteResponse) DecodeEventLogCCEL() ([]byte, error) {
120-
return hex.DecodeString(r.EventLogCCEL)
121-
}
122117

123118
// Represents the response from an attestation request.
124119
type AttestResponse struct {

sdk/js/src/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,6 @@ export interface GetQuoteResponse {
101101
report_data?: Hex
102102
vm_config?: string
103103
attestation?: Hex
104-
event_log_ccel?: Hex
105104

106105
replayRtmrs: () => string[]
107106
}

0 commit comments

Comments
 (0)