From 50ec5d333958a05e16ef8ae41444a29beca12c16 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Wed, 21 Oct 2026 07:29:25 -0400 Subject: [PATCH] jouleclaw-attest-vendor: read TDX and SNP fields at their published offsets The parser read the wrong bytes for most of the fields it emits. Its test fixtures were built from the same constants, so nothing could catch it: TDX: - The header is 48 bytes, not 52; qe_vendor_id sits at byte 12. - The TD 1.0 report body is 584 bytes, not 528. - mrtd is at body+136, not body+0, which is TEE_TCB_SVN and MRSEAM. - rtmr0..3 are at body+328/376/424/472, not 168/216/264/312. - report_data is at body+520, not body+376, which is RTMR1. - A v5 quote is not "backward-compatible on the header fields": a 6-byte body descriptor (type, size) comes before its body. SNP: - The launch measurement is at 0x090, not 0x130, which lies inside author_key_digest. - id_key_digest is at 0x0E0, not 0x160, which is report_id_ma. - report_data at 0x050 and host_data at 0x0C0 were already right. Every offset is the one sandbox-tee's verifier uses, and agrees with the published layouts. The parser now also refuses a quote whose tee_type is not TDX (0x81), and a v5 quote whose body is not a TD report (type 2, 584 bytes, or type 3, 648 bytes, with a matching size), rather than reading them at TD offsets. The fixtures are rewritten from literal published offsets, independent of the parser's constants. Every field gets its own marker, with decoys at the offsets that were read before. The vec_init_then_push pattern and a module-doc list item that clippy flagged are fixed in the same functions; the doc no longer claims a tcb_svn field it never emitted. No crate in the tree depends on this one, so the defect was latent. Tests: 12. Mutation testing: 13 mutants, all killed. Six put back an old offset, which is exactly the code this replaces. The other seven remove the tee-type check, the v5 descriptor and size checks, the TD 1.5 length, v5 support, or the short-body check. Verified on the build host: clippy -D warnings and rustdoc -D warnings clean on jouleclaw-attest-vendor. Co-Authored-By: Claude Opus 5.5 --- .../crates/jouleclaw-attest-vendor/src/lib.rs | 399 +++++++++++------- 1 file changed, 244 insertions(+), 155 deletions(-) diff --git a/jouleclaw/jouleclaw-rs/crates/jouleclaw-attest-vendor/src/lib.rs b/jouleclaw/jouleclaw-rs/crates/jouleclaw-attest-vendor/src/lib.rs index 757580c69..49c998523 100644 --- a/jouleclaw/jouleclaw-rs/crates/jouleclaw-attest-vendor/src/lib.rs +++ b/jouleclaw/jouleclaw-rs/crates/jouleclaw-attest-vendor/src/lib.rs @@ -5,10 +5,10 @@ //! by shipping a sidecar crate that handles the three concrete //! formats with stable published specs: //! -//! - **Intel TDX TD Quote** — header per Intel's -//! `TDX 1.5 DCAP Spec`; we parse the version + tcb_svn + mrtd -//! + rtmr0..3 + report_data fields and emit them as -//! [`jouleclaw_attest::Measurement`] records keyed by +//! - **Intel TDX TD Quote** — per Intel's DCAP quote format, versions 4 +//! and 5; we check the version and the TEE type, read the mrtd, the +//! four rtmrs and the report_data from the TD report body, and emit +//! them as [`jouleclaw_attest::Measurement`] records keyed by //! `tdx.mrtd`, `tdx.rtmr0`..`tdx.rtmr3`, `tdx.report_data`. //! - **AMD SEV-SNP attestation report** — per AMD's SNP firmware //! spec; we parse the launch measurement + host_data + @@ -163,35 +163,84 @@ fn measurement(key: &str, digest: &[u8], alg: &str) -> Measurement { // Intel TDX TD Quote parser // ───────────────────────────────────────────────────────────────────── -/// Intel TDX TD Quote v4/v5 header layout (we parse v4; v5 is -/// backward-compat on the header fields we use). +/// Intel TDX TD Quote layout (DCAP quote format, versions 4 and 5). /// -/// Bytes 0..=1 : version (u16 LE) -/// Bytes 2..=3 : attestation_key_type (u16 LE) -/// Bytes 4..=7 : tee_type (u32 LE) -/// Bytes 8..=15 : reserved -/// Bytes 16..=31 : qe_vendor_id -/// Bytes 32..=51 : user_data -/// Bytes 52.. : TD report body +/// The 48-byte header: /// -/// The TD report body (528 bytes for v4) contains: -/// - mrtd (48 bytes at offset 0 in the body) -/// - rtmr0..rtmr3 (each 48 bytes, at offsets 168/216/264/312) -/// - report_data (64 bytes at offset 376) +/// | Bytes | Field | +/// |---|---| +/// | 0..2 | version (u16 LE): 4 or 5 | +/// | 2..4 | attestation_key_type (u16 LE) | +/// | 4..8 | tee_type (u32 LE): `0x81` for TDX | +/// | 8..12 | reserved | +/// | 12..28 | qe_vendor_id | +/// | 28..48 | user_data | /// -/// We extract these into [`Measurement`] records using SHA-384 -/// (the TDX measurement algorithm). -const TDX_HEADER_LEN: usize = 52; -const TDX_REPORT_BODY_LEN: usize = 528; -const TDX_MRTD_OFFSET: usize = 0; -const TDX_RTMR0_OFFSET: usize = 168; +/// A version 4 quote's TD report body follows at byte 48. A version 5 quote +/// puts a 6-byte body descriptor there first, `body_type` (u16 LE) and +/// `body_size` (u32 LE): type 2 is a 584-byte TD 1.0 report and type 3 a +/// 648-byte TD 1.5 report, whose first 584 bytes are laid out as TD 1.0's. +/// +/// The TD report body fields this parser reads, at their offsets in the body: +/// +/// | Offset | Field | +/// |---|---| +/// | 136 | mrtd (48) | +/// | 328, 376, 424, 472 | rtmr0..rtmr3 (48 each) | +/// | 520 | report_data (64) | +/// +/// The measurements are SHA-384, the TDX measurement algorithm. +const TDX_HEADER_LEN: usize = 48; +const TDX_TEE_TYPE: u32 = 0x81; +const TDX_V5_DESCRIPTOR_LEN: usize = 6; +const TDX_TD10_BODY_LEN: usize = 584; +const TDX_TD15_BODY_LEN: usize = 648; +const TDX_MRTD_OFFSET: usize = 136; +const TDX_RTMR0_OFFSET: usize = 328; const TDX_RTMR1_OFFSET: usize = TDX_RTMR0_OFFSET + 48; const TDX_RTMR2_OFFSET: usize = TDX_RTMR1_OFFSET + 48; const TDX_RTMR3_OFFSET: usize = TDX_RTMR2_OFFSET + 48; -const TDX_REPORT_DATA_OFFSET: usize = 376; +const TDX_REPORT_DATA_OFFSET: usize = 520; const TDX_MEASUREMENT_LEN: usize = 48; // SHA-384 const TDX_REPORT_DATA_LEN: usize = 64; +/// Where a TD quote's report body starts, from its header and, for version +/// 5, its body descriptor. A quote that is not a TD quote, or whose body is +/// not a TD report, is refused rather than read at the wrong offsets. +fn tdx_body_start(bytes: &[u8]) -> Result { + if bytes.len() < TDX_HEADER_LEN { + return Err(QuoteError::TooShort { need: TDX_HEADER_LEN, got: bytes.len() }); + } + let version = u16::from_le_bytes([bytes[0], bytes[1]]); + if version != 4 && version != 5 { + return Err(QuoteError::UnsupportedVersion(version)); + } + let tee_type = u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]); + if tee_type != TDX_TEE_TYPE { + return Err(QuoteError::Malformed(format!("tee_type {tee_type:#x} is not a TD quote"))); + } + if version == 4 { + return Ok(TDX_HEADER_LEN); + } + let start = TDX_HEADER_LEN + TDX_V5_DESCRIPTOR_LEN; + if bytes.len() < start { + return Err(QuoteError::TooShort { need: start, got: bytes.len() }); + } + let body_type = u16::from_le_bytes([bytes[48], bytes[49]]); + let body_size = u32::from_le_bytes([bytes[50], bytes[51], bytes[52], bytes[53]]) as usize; + let expected = match body_type { + 2 => TDX_TD10_BODY_LEN, + 3 => TDX_TD15_BODY_LEN, + t => return Err(QuoteError::Malformed(format!("quote body type {t} is not a TD report"))), + }; + if body_size != expected { + return Err(QuoteError::Malformed(format!( + "a type {body_type} body is {expected} bytes, the descriptor says {body_size}" + ))); + } + Ok(start) +} + /// Parser for [`QuoteKind::IntelTdx`]. #[derive(Debug, Default, Clone, Copy)] pub struct IntelTdxParser; @@ -208,51 +257,44 @@ impl QuoteParser for IntelTdxParser { }); } let bytes = decode_hex("e.bytes_hex)?; - let need = TDX_HEADER_LEN + TDX_REPORT_BODY_LEN; + let start = tdx_body_start(&bytes)?; + let need = start + TDX_TD10_BODY_LEN; if bytes.len() < need { - return Err(QuoteError::TooShort { - need, - got: bytes.len(), - }); + return Err(QuoteError::TooShort { need, got: bytes.len() }); } - // version little-endian, bytes 0..2 - let version = u16::from_le_bytes([bytes[0], bytes[1]]); - if version != 4 && version != 5 { - return Err(QuoteError::UnsupportedVersion(version)); - } - let body = &bytes[TDX_HEADER_LEN..TDX_HEADER_LEN + TDX_REPORT_BODY_LEN]; - let mut out = Vec::with_capacity(6); - out.push(measurement( - "tdx.mrtd", - &body[TDX_MRTD_OFFSET..TDX_MRTD_OFFSET + TDX_MEASUREMENT_LEN], - "sha384", - )); - out.push(measurement( - "tdx.rtmr0", - &body[TDX_RTMR0_OFFSET..TDX_RTMR0_OFFSET + TDX_MEASUREMENT_LEN], - "sha384", - )); - out.push(measurement( - "tdx.rtmr1", - &body[TDX_RTMR1_OFFSET..TDX_RTMR1_OFFSET + TDX_MEASUREMENT_LEN], - "sha384", - )); - out.push(measurement( - "tdx.rtmr2", - &body[TDX_RTMR2_OFFSET..TDX_RTMR2_OFFSET + TDX_MEASUREMENT_LEN], - "sha384", - )); - out.push(measurement( - "tdx.rtmr3", - &body[TDX_RTMR3_OFFSET..TDX_RTMR3_OFFSET + TDX_MEASUREMENT_LEN], - "sha384", - )); - out.push(measurement( - "tdx.report_data", - &body[TDX_REPORT_DATA_OFFSET..TDX_REPORT_DATA_OFFSET + TDX_REPORT_DATA_LEN], - "raw", - )); - Ok(out) + let body = &bytes[start..need]; + Ok(vec![ + measurement( + "tdx.mrtd", + &body[TDX_MRTD_OFFSET..TDX_MRTD_OFFSET + TDX_MEASUREMENT_LEN], + "sha384", + ), + measurement( + "tdx.rtmr0", + &body[TDX_RTMR0_OFFSET..TDX_RTMR0_OFFSET + TDX_MEASUREMENT_LEN], + "sha384", + ), + measurement( + "tdx.rtmr1", + &body[TDX_RTMR1_OFFSET..TDX_RTMR1_OFFSET + TDX_MEASUREMENT_LEN], + "sha384", + ), + measurement( + "tdx.rtmr2", + &body[TDX_RTMR2_OFFSET..TDX_RTMR2_OFFSET + TDX_MEASUREMENT_LEN], + "sha384", + ), + measurement( + "tdx.rtmr3", + &body[TDX_RTMR3_OFFSET..TDX_RTMR3_OFFSET + TDX_MEASUREMENT_LEN], + "sha384", + ), + measurement( + "tdx.report_data", + &body[TDX_REPORT_DATA_OFFSET..TDX_REPORT_DATA_OFFSET + TDX_REPORT_DATA_LEN], + "raw", + ), + ]) } } @@ -260,24 +302,27 @@ impl QuoteParser for IntelTdxParser { // AMD SEV-SNP attestation report parser // ───────────────────────────────────────────────────────────────────── -/// AMD SEV-SNP attestation report layout (per -/// `SNP_GET_REPORT` spec). The report is 1184 bytes; we extract -/// the four most-load-bearing measurements: +/// AMD SEV-SNP attestation report layout (the `ATTESTATION_REPORT` +/// structure of the SNP firmware ABI). The report is 1184 bytes; we extract +/// the four most load-bearing fields: /// -/// - launch_measurement (48 bytes at offset 0x130 = 304) -/// - host_data (32 bytes at offset 0xC0 = 192) -/// - id_key_digest (48 bytes at offset 0x160 = 352) -/// - report_data (64 bytes at offset 0x50 = 80) +/// | Offset | Field | +/// |---|---| +/// | 0x050 | report_data (64) | +/// | 0x090 | launch measurement (48, SHA-384) | +/// | 0x0C0 | host_data (32) | +/// | 0x0E0 | id_key_digest (48, SHA-384) | /// -/// All are SHA-384 / SHA-256 / raw per AMD's spec. +/// The neighbouring fields this must not be confused with are +/// author_key_digest at 0x110, report_id at 0x140 and report_id_ma at 0x160. const SNP_REPORT_LEN: usize = 1184; const SNP_REPORT_DATA_OFFSET: usize = 0x50; const SNP_REPORT_DATA_LEN: usize = 64; const SNP_HOST_DATA_OFFSET: usize = 0xC0; const SNP_HOST_DATA_LEN: usize = 32; -const SNP_LAUNCH_MEASUREMENT_OFFSET: usize = 0x130; +const SNP_LAUNCH_MEASUREMENT_OFFSET: usize = 0x090; const SNP_LAUNCH_MEASUREMENT_LEN: usize = 48; -const SNP_ID_KEY_DIGEST_OFFSET: usize = 0x160; +const SNP_ID_KEY_DIGEST_OFFSET: usize = 0x0E0; const SNP_ID_KEY_DIGEST_LEN: usize = 48; /// Parser for [`QuoteKind::AmdSevSnp`]. @@ -302,29 +347,29 @@ impl QuoteParser for AmdSevSnpParser { got: bytes.len(), }); } - let mut out = Vec::with_capacity(4); - out.push(measurement( - "snp.launch_measurement", - &bytes[SNP_LAUNCH_MEASUREMENT_OFFSET - ..SNP_LAUNCH_MEASUREMENT_OFFSET + SNP_LAUNCH_MEASUREMENT_LEN], - "sha384", - )); - out.push(measurement( - "snp.host_data", - &bytes[SNP_HOST_DATA_OFFSET..SNP_HOST_DATA_OFFSET + SNP_HOST_DATA_LEN], - "sha256", - )); - out.push(measurement( - "snp.id_key_digest", - &bytes[SNP_ID_KEY_DIGEST_OFFSET..SNP_ID_KEY_DIGEST_OFFSET + SNP_ID_KEY_DIGEST_LEN], - "sha384", - )); - out.push(measurement( - "snp.report_data", - &bytes[SNP_REPORT_DATA_OFFSET..SNP_REPORT_DATA_OFFSET + SNP_REPORT_DATA_LEN], - "raw", - )); - Ok(out) + Ok(vec![ + measurement( + "snp.launch_measurement", + &bytes[SNP_LAUNCH_MEASUREMENT_OFFSET + ..SNP_LAUNCH_MEASUREMENT_OFFSET + SNP_LAUNCH_MEASUREMENT_LEN], + "sha384", + ), + measurement( + "snp.host_data", + &bytes[SNP_HOST_DATA_OFFSET..SNP_HOST_DATA_OFFSET + SNP_HOST_DATA_LEN], + "sha256", + ), + measurement( + "snp.id_key_digest", + &bytes[SNP_ID_KEY_DIGEST_OFFSET..SNP_ID_KEY_DIGEST_OFFSET + SNP_ID_KEY_DIGEST_LEN], + "sha384", + ), + measurement( + "snp.report_data", + &bytes[SNP_REPORT_DATA_OFFSET..SNP_REPORT_DATA_OFFSET + SNP_REPORT_DATA_LEN], + "raw", + ), + ]) } } @@ -386,54 +431,85 @@ impl QuoteParser for InTotoSlsaParser { mod tests { use super::*; - fn synthesize_tdx_v4_quote() -> Quote { - let total = TDX_HEADER_LEN + TDX_REPORT_BODY_LEN; - let mut bytes = vec![0u8; total]; - // version = 4 (little-endian) - bytes[0] = 4; - bytes[1] = 0; - // MRTD = 0xAA * 48 at body offset 0 - for i in 0..TDX_MEASUREMENT_LEN { - bytes[TDX_HEADER_LEN + TDX_MRTD_OFFSET + i] = 0xAA; - } - // RTMR0 = 0xBB * 48 - for i in 0..TDX_MEASUREMENT_LEN { - bytes[TDX_HEADER_LEN + TDX_RTMR0_OFFSET + i] = 0xBB; - } - // report_data = 0xCC * 64 - for i in 0..TDX_REPORT_DATA_LEN { - bytes[TDX_HEADER_LEN + TDX_REPORT_DATA_OFFSET + i] = 0xCC; - } - Quote { - kind: QuoteKind::IntelTdx, - bytes_hex: encode_hex(&bytes), - } + /// Fill `len` bytes at `at` with `marker`. + fn mark(bytes: &mut [u8], at: usize, len: usize, marker: u8) { + bytes[at..at + len].fill(marker); } - fn synthesize_snp_report() -> Quote { - let mut bytes = vec![0u8; SNP_REPORT_LEN]; - for i in 0..SNP_LAUNCH_MEASUREMENT_LEN { - bytes[SNP_LAUNCH_MEASUREMENT_OFFSET + i] = 0xDD; + /// A TD quote laid out by the published format, written from literal + /// offsets rather than the parser's constants, so a wrong constant cannot + /// agree with a fixture built from it. Every field the parser reads, and + /// the fields beside the offsets it used to read, carries its own marker. + fn published_td_quote(version: u16) -> Vec { + let start = if version == 5 { 54 } else { 48 }; + let mut q = vec![0u8; start + 584]; + q[0..2].copy_from_slice(&version.to_le_bytes()); + q[4..8].copy_from_slice(&0x81u32.to_le_bytes()); + if version == 5 { + q[48..50].copy_from_slice(&2u16.to_le_bytes()); + q[50..54].copy_from_slice(&584u32.to_le_bytes()); } - for i in 0..SNP_HOST_DATA_LEN { - bytes[SNP_HOST_DATA_OFFSET + i] = 0xEE; - } - Quote { - kind: QuoteKind::AmdSevSnp, - bytes_hex: encode_hex(&bytes), + let body = &mut q[start..]; + mark(body, 0, 16, 0x01); // TEE_TCB_SVN + mark(body, 16, 48, 0x02); // MRSEAM + mark(body, 136, 48, 0xAA); // MRTD + mark(body, 184, 48, 0x03); // MRCONFIGID, which the old rtmr0 read straddled + for (k, at) in [328, 376, 424, 472].into_iter().enumerate() { + mark(body, at, 48, 0xB0 + k as u8); // RTMR0..3 } + mark(body, 520, 64, 0xCC); // REPORTDATA + q } + fn td_quote(bytes: &[u8]) -> Quote { + Quote { kind: QuoteKind::IntelTdx, bytes_hex: encode_hex(bytes) } + } + + fn digest_of<'m>(m: &'m [Measurement], key: &str) -> &'m str { + &m.iter().find(|x| x.key == key).unwrap().digest + } + + /// Each field is read from its published offset: MRTD at 136, not the + /// TEE_TCB_SVN at 0, and report_data at 520, not RTMR1 at 376. #[test] - fn tdx_parser_extracts_five_measurements_plus_report_data() { - let q = synthesize_tdx_v4_quote(); - let m = IntelTdxParser.parse(&q).unwrap(); - assert_eq!(m.len(), 6); - let mrtd = m.iter().find(|x| x.key == "tdx.mrtd").unwrap(); - assert_eq!(mrtd.digest, "aa".repeat(TDX_MEASUREMENT_LEN)); - assert_eq!(mrtd.alg, "sha384"); - let rd = m.iter().find(|x| x.key == "tdx.report_data").unwrap(); - assert_eq!(rd.digest, "cc".repeat(TDX_REPORT_DATA_LEN)); + fn tdx_parser_reads_each_field_at_its_published_offset() { + for version in [4u16, 5] { + let m = IntelTdxParser.parse(&td_quote(&published_td_quote(version))).unwrap(); + assert_eq!(m.len(), 6); + assert_eq!(digest_of(&m, "tdx.mrtd"), "aa".repeat(48), "v{version}"); + for k in 0..4 { + assert_eq!(digest_of(&m, &format!("tdx.rtmr{k}")), format!("{:02x}", 0xB0 + k).repeat(48), "v{version}"); + } + assert_eq!(digest_of(&m, "tdx.report_data"), "cc".repeat(64), "v{version}"); + assert_eq!(m.iter().find(|x| x.key == "tdx.mrtd").unwrap().alg, "sha384"); + } + } + + /// A quote whose header says it is not a TD quote, or whose version 5 + /// body is not a TD report, is refused rather than read at TD offsets. + #[test] + fn tdx_parser_refuses_what_is_not_a_td_report() { + let mut sgx = published_td_quote(4); + sgx[4..8].copy_from_slice(&0u32.to_le_bytes()); + assert!(matches!(IntelTdxParser.parse(&td_quote(&sgx)), Err(QuoteError::Malformed(_)))); + + let mut enclave = published_td_quote(5); + enclave[48..50].copy_from_slice(&1u16.to_le_bytes()); + assert!(matches!(IntelTdxParser.parse(&td_quote(&enclave)), Err(QuoteError::Malformed(_)))); + + let mut missized = published_td_quote(5); + missized[50..54].copy_from_slice(&648u32.to_le_bytes()); + assert!(matches!(IntelTdxParser.parse(&td_quote(&missized)), Err(QuoteError::Malformed(_)))); + + let mut td15 = published_td_quote(5); + td15[48..50].copy_from_slice(&3u16.to_le_bytes()); + td15[50..54].copy_from_slice(&648u32.to_le_bytes()); + td15.resize(54 + 648, 0); + let m = IntelTdxParser.parse(&td_quote(&td15)).unwrap(); + assert_eq!(digest_of(&m, "tdx.report_data"), "cc".repeat(64), "a TD 1.5 body reads as TD 1.0's"); + + let short = &published_td_quote(4)[..48 + 583]; + assert!(matches!(IntelTdxParser.parse(&td_quote(short)), Err(QuoteError::TooShort { need: 632, .. }))); } #[test] @@ -450,8 +526,7 @@ mod tests { #[test] fn tdx_parser_rejects_unsupported_version() { - let total = TDX_HEADER_LEN + TDX_REPORT_BODY_LEN; - let mut bytes = vec![0u8; total]; + let mut bytes = published_td_quote(4); bytes[0] = 99; let q = Quote { kind: QuoteKind::IntelTdx, @@ -475,19 +550,33 @@ mod tests { )); } + /// An SNP report laid out by the published `ATTESTATION_REPORT` format, + /// from literal offsets, with a marker in each field the parser reads and + /// in the fields at the offsets it used to read. + fn published_snp_report() -> Quote { + let mut r = vec![0u8; 1184]; + mark(&mut r, 0x050, 64, 0x50); // REPORT_DATA + mark(&mut r, 0x090, 48, 0x90); // MEASUREMENT + mark(&mut r, 0x0C0, 32, 0xC0); // HOST_DATA + mark(&mut r, 0x0E0, 48, 0xE0); // ID_KEY_DIGEST + mark(&mut r, 0x110, 48, 0x11); // AUTHOR_KEY_DIGEST, where the measurement was read + mark(&mut r, 0x140, 32, 0x14); // REPORT_ID + mark(&mut r, 0x160, 32, 0x16); // REPORT_ID_MA, where id_key_digest was read + Quote { kind: QuoteKind::AmdSevSnp, bytes_hex: encode_hex(&r) } + } + + /// Each field is read from its published offset: the launch measurement + /// at 0x90, not inside author_key_digest, and id_key_digest at 0xE0, not + /// report_id_ma. #[test] - fn snp_parser_extracts_four_measurements() { - let q = synthesize_snp_report(); - let m = AmdSevSnpParser.parse(&q).unwrap(); + fn snp_parser_reads_each_field_at_its_published_offset() { + let m = AmdSevSnpParser.parse(&published_snp_report()).unwrap(); assert_eq!(m.len(), 4); - let lm = m - .iter() - .find(|x| x.key == "snp.launch_measurement") - .unwrap(); - assert_eq!(lm.digest, "dd".repeat(SNP_LAUNCH_MEASUREMENT_LEN)); - let hd = m.iter().find(|x| x.key == "snp.host_data").unwrap(); - assert_eq!(hd.alg, "sha256"); - assert_eq!(hd.digest, "ee".repeat(SNP_HOST_DATA_LEN)); + assert_eq!(digest_of(&m, "snp.report_data"), "50".repeat(64)); + assert_eq!(digest_of(&m, "snp.launch_measurement"), "90".repeat(48)); + assert_eq!(digest_of(&m, "snp.host_data"), "c0".repeat(32)); + assert_eq!(digest_of(&m, "snp.id_key_digest"), "e0".repeat(48)); + assert_eq!(m.iter().find(|x| x.key == "snp.host_data").unwrap().alg, "sha256"); } #[test] @@ -572,7 +661,7 @@ mod tests { Box::new(AmdSevSnpParser), Box::new(InTotoSlsaParser), ]; - let q = synthesize_tdx_v4_quote(); + let q = td_quote(&published_td_quote(4)); let m = dispatch(&parsers, &q).unwrap(); assert_eq!(m.len(), 6); -- 2.54.0 (Apple Git-157)