jouleclaw-attest-vendor: read TDX and SNP fields at their published offsets #389
1 changed files with 243 additions and 154 deletions
|
|
@ -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<usize, QuoteError> {
|
||||
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<u8> {
|
||||
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);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue