From c17cb44a992871a6d4f161952e1921b4cffad8d1 Mon Sep 17 00:00:00 2001 From: Xynnn007 Date: Fri, 15 Nov 2024 12:54:07 +0800 Subject: [PATCH] AA: fix eventlog idempotence This patch fixes the idempotence of eventlog. Before this, when AA restarts and eventlog is activated, the originally recorded aael will be truncated and the INIT event will be recorded repeatedly. This patch will check whether there is an existing AAEL when AA is restarted. If so, it will skip creating and recording the INIT event. At the same time, a synchronization mechanism is used to ensure that RTMR expansion will not occur repeatedly after AA abnormally interrupts execution. Signed-off-by: Xynnn007 --- Cargo.lock | 1 + .../attestation-agent/Cargo.toml | 1 + .../attestation-agent/src/eventlog.rs | 183 ---------- .../attestation-agent/src/eventlog/event.rs | 104 ++++++ .../attestation-agent/src/eventlog/mod.rs | 325 ++++++++++++++++++ .../attestation-agent/src/lib.rs | 38 +- attestation-agent/attester/src/lib.rs | 7 + attestation-agent/attester/src/tdx/mod.rs | 69 ++-- attestation-agent/attester/src/tdx/report.rs | 37 ++ .../attester/test/tdx_report_1.bin | Bin 0 -> 1024 bytes .../deps/crypto/src/algorithms.rs | 18 +- 11 files changed, 548 insertions(+), 235 deletions(-) delete mode 100644 attestation-agent/attestation-agent/src/eventlog.rs create mode 100644 attestation-agent/attestation-agent/src/eventlog/event.rs create mode 100644 attestation-agent/attestation-agent/src/eventlog/mod.rs create mode 100644 attestation-agent/attester/test/tdx_report_1.bin diff --git a/Cargo.lock b/Cargo.lock index 293429d0f..ce9ae6bf4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -345,6 +345,7 @@ dependencies = [ "const_format", "crypto", "env_logger 0.11.5", + "hex", "kbs-types", "kbs_protocol", "log", diff --git a/attestation-agent/attestation-agent/Cargo.toml b/attestation-agent/attestation-agent/Cargo.toml index 59b563d86..1fb195255 100644 --- a/attestation-agent/attestation-agent/Cargo.toml +++ b/attestation-agent/attestation-agent/Cargo.toml @@ -23,6 +23,7 @@ clap = { workspace = true, features = ["derive"], optional = true } config.workspace = true const_format.workspace = true env_logger = { workspace = true, optional = true } +hex.workspace = true kbs_protocol = { path = "../kbs_protocol", default-features = false, optional = true } kbs-types.workspace = true log.workspace = true diff --git a/attestation-agent/attestation-agent/src/eventlog.rs b/attestation-agent/attestation-agent/src/eventlog.rs deleted file mode 100644 index 4b165f622..000000000 --- a/attestation-agent/attestation-agent/src/eventlog.rs +++ /dev/null @@ -1,183 +0,0 @@ -// Copyright (c) 2024 Alibaba Cloud -// -// SPDX-License-Identifier: Apache-2.0 -// - -use std::{fmt::Display, fs::File, io::Write}; - -use anyhow::{bail, Context, Result}; -use const_format::concatcp; - -use crypto::HashAlgorithm; - -/// AA's eventlog will be put into this parent directory -pub const EVENTLOG_PARENT_DIR_PATH: &str = "/run/attestation-agent"; - -/// AA's eventlog will be stored inside the file -pub const EVENTLOG_PATH: &str = concatcp!(EVENTLOG_PARENT_DIR_PATH, "/eventlog"); - -pub struct EventLog { - writer: Box, -} - -trait Writer: Sync + Send { - fn append(&mut self, entry: &LogEntry) -> Result<()>; -} - -pub struct FileWriter { - file: File, -} - -impl Writer for FileWriter { - fn append(&mut self, entry: &LogEntry) -> Result<()> { - writeln!(self.file, "{entry}").context("failed to write log")?; - self.file - .flush() - .context("failed to flush log to I/O media")?; - Ok(()) - } -} - -impl EventLog { - pub fn new() -> Result { - std::fs::create_dir_all(EVENTLOG_PARENT_DIR_PATH).context("create eventlog parent dir")?; - let file = File::create(EVENTLOG_PATH).context("create eventlog")?; - let writer = Box::new(FileWriter { file }); - Ok(Self { writer }) - } - - pub fn write_log(&mut self, entry: &LogEntry) -> Result<()> { - self.writer.append(entry) - } -} - -pub struct Content<'a>(&'a str); - -impl<'a> TryFrom<&'a str> for Content<'a> { - type Error = anyhow::Error; - - fn try_from(value: &'a str) -> Result { - if value.chars().any(|c| c == '\n') { - bail!("content contains newline"); - } - Ok(Content(value)) - } -} - -pub enum LogEntry<'a> { - Event { - domain: &'a str, - operation: &'a str, - content: Content<'a>, - }, - Init(HashAlgorithm), -} - -impl LogEntry<'_> { - /// Calculate the LogEntry's digest with the given [`HashAlgorithm`] - pub fn digest_with(&self, hash_alg: HashAlgorithm) -> Vec { - let log_entry = self.to_string(); - hash_alg.digest(log_entry.as_bytes()) - } -} - -impl Display for LogEntry<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - LogEntry::Event { - domain, - operation, - content, - } => { - write!(f, "{} {} {}", domain, operation, content.0) - } - LogEntry::Init(hash_alg) => { - // TODO: We should get the current platform's evidence to - // see the RTMR value. Here we assume RTMR is not polluted - // thus all be set `\0` - let (sha, zeroes) = match hash_alg { - HashAlgorithm::Sha256 => ("sha256", "0".repeat(64)), - HashAlgorithm::Sha384 => ("sha384", "0".repeat(96)), - HashAlgorithm::Sha512 => ("sha512", "0".repeat(128)), - }; - write!(f, "INIT {}/{}", sha, zeroes) - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use rstest::rstest; - use std::sync::{Arc, Mutex}; - - struct TestWriter(Arc>>); - - impl Writer for TestWriter { - fn append(&mut self, entry: &LogEntry) -> Result<()> { - self.0.lock().unwrap().push(entry.to_string()); - Ok(()) - } - } - - #[test] - fn test_content() { - let a_str = "hello"; - let _: Content = a_str.try_into().unwrap(); - let b_str = "hello\nworld"; - let content: Result = b_str.try_into(); - assert!(content.is_err()); - } - - #[test] - fn test_log_events() { - let lines = Arc::new(Mutex::new(vec![])); - let tw = TestWriter(lines.clone()); - let mut el = EventLog { - writer: Box::new(tw), - }; - let i = LogEntry::Init(HashAlgorithm::Sha256); - el.write_log(&i).unwrap(); - let i_line = concat!( - "INIT sha256/00000000000000000000000000", - "00000000000000000000000000000000000000" - ); - assert_eq!(lines.lock().unwrap().join("\n"), i_line); - let ev = LogEntry::Event { - domain: "one", - operation: "two", - content: "three".try_into().unwrap(), - }; - el.write_log(&ev).unwrap(); - let e_line = "one two three"; - assert_eq!(lines.lock().unwrap()[1], e_line); - } - - #[rstest] - #[case( - "domain", - "operation", - "content", - "65aad3b1620d4fe224d727579db2db87ff5c033f3e4424ae0fd72eb1149d3bd5", - HashAlgorithm::Sha256 - )] - #[case("domain", "operation", "content", "26d944cb8d99096590252283b8c807b9508329b068703bdb7bac7eb6efe5b32fc0fadf1462662b95d2c708aa49c0bfe1", HashAlgorithm::Sha384)] - #[case("domain", "operation", "content", "6e75837e0fbf8367fa4550254b8f0f52eb659be0901340357ed91dda97f0ebca10537540a021eec78df9d29ade51609a01eaaa46d32e0218cdac1644dc9933b0", HashAlgorithm::Sha512)] - fn test_event_digest( - #[case] domain: &str, - #[case] operation: &str, - #[case] content: &str, - #[case] digest: &str, - #[case] hash_alg: HashAlgorithm, - ) { - let event = LogEntry::Event { - domain, - operation, - content: content.try_into().unwrap(), - }; - let dig = event.digest_with(hash_alg); - let dig_hex = dig.iter().map(|c| format!("{c:02x}")).collect::(); - assert_eq!(dig_hex, digest); - } -} diff --git a/attestation-agent/attestation-agent/src/eventlog/event.rs b/attestation-agent/attestation-agent/src/eventlog/event.rs new file mode 100644 index 000000000..86a78ff05 --- /dev/null +++ b/attestation-agent/attestation-agent/src/eventlog/event.rs @@ -0,0 +1,104 @@ +// Copyright (c) 2024 Alibaba Cloud +// +// SPDX-License-Identifier: Apache-2.0 +// + +use std::str::FromStr; + +use anyhow::{anyhow, bail, Context, Result}; +use crypto::HashAlgorithm; +use sha2::{digest::FixedOutput, Digest, Sha256, Sha384, Sha512}; + +#[derive(Clone)] +pub struct AAEventlog { + pub hash_algorithm: HashAlgorithm, + pub init_state: Vec, + pub events: Vec, +} + +impl FromStr for AAEventlog { + type Err = anyhow::Error; + + fn from_str(input: &str) -> Result { + let all_lines = input.lines().collect::>(); + + let (initline, eventlines) = all_lines + .split_first() + .ok_or(anyhow!("at least one line should be included in AAEL"))?; + + // Init line looks like + // INIT sha256/0000000000000000000000000000000000000000000000000000000000000000 + let init_line_items = initline.split_ascii_whitespace().collect::>(); + if init_line_items.len() != 2 { + bail!("Illegal INIT event record."); + } + + if init_line_items[0] != "INIT" { + bail!("INIT event should start with `INIT` key word"); + } + + let (hash_algorithm, init_state) = init_line_items[1].split_once('/').ok_or(anyhow!( + "INIT event should have `/` as content after `INIT`" + ))?; + + let hash_algorithm = HashAlgorithm::from_str(hash_algorithm) + .context("parse Hash Algorithm in INIT entry")?; + let init_state = hex::decode(init_state).context("parse init state in INIT entry")?; + + let events = eventlines + .iter() + .map(|line| line.trim_end().to_string()) + .collect(); + + Ok(Self { + events, + hash_algorithm, + init_state, + }) + } +} + +impl AAEventlog { + fn accumulate_hash(&self) -> Vec { + let mut state = self.init_state.clone(); + + let mut init_event_hasher = D::new(); + let init_event = format!( + "INIT {}/{}", + self.hash_algorithm.as_ref(), + hex::encode(&self.init_state) + ); + Digest::update(&mut init_event_hasher, init_event.as_bytes()); + let init_event_hash = init_event_hasher.finalize(); + + let mut hasher = D::new(); + Digest::update(&mut hasher, &state); + + Digest::update(&mut hasher, init_event_hash); + state = hasher.finalize().to_vec(); + + self.events.iter().for_each(|event| { + let mut event_hasher = D::new(); + Digest::update(&mut event_hasher, event); + let event_hash = event_hasher.finalize(); + + let mut hasher = D::new(); + Digest::update(&mut hasher, &state); + Digest::update(&mut hasher, event_hash); + state = hasher.finalize().to_vec(); + }); + + state + } + + /// Check the integrity of the AAEL, and gets a digest. Return whether the rtmr is the same as the digest. + pub fn integrity_check(&self, rtmr: &[u8]) -> bool { + let result = match self.hash_algorithm { + HashAlgorithm::Sha256 => self.accumulate_hash::(), + HashAlgorithm::Sha384 => self.accumulate_hash::(), + HashAlgorithm::Sha512 => self.accumulate_hash::(), + }; + + rtmr == result + } +} diff --git a/attestation-agent/attestation-agent/src/eventlog/mod.rs b/attestation-agent/attestation-agent/src/eventlog/mod.rs new file mode 100644 index 000000000..79b2f5f7d --- /dev/null +++ b/attestation-agent/attestation-agent/src/eventlog/mod.rs @@ -0,0 +1,325 @@ +// Copyright (c) 2024 Alibaba Cloud +// +// SPDX-License-Identifier: Apache-2.0 +// + +pub mod event; + +use std::{ + fmt::Display, + fs::{File, OpenOptions}, + io::Write, + path::Path, + str::FromStr, + sync::Arc, +}; + +use anyhow::{bail, Context, Result}; +use attester::BoxedAttester; +use const_format::concatcp; + +use crypto::HashAlgorithm; +use event::AAEventlog; +use log::debug; + +/// AA's eventlog will be put into this parent directory +pub const EVENTLOG_PARENT_DIR_PATH: &str = "/run/attestation-agent"; + +/// AA's eventlog will be stored inside the file +pub const EVENTLOG_PATH: &str = concatcp!(EVENTLOG_PARENT_DIR_PATH, "/eventlog"); + +pub struct EventLog { + writer: Box, + rtmr_extender: Arc, + alg: HashAlgorithm, + pcr: u64, +} + +trait Writer: Sync + Send { + fn append(&mut self, entry: &LogEntry) -> Result<()>; +} + +pub struct FileWriter { + file: File, +} + +impl Writer for FileWriter { + fn append(&mut self, entry: &LogEntry) -> Result<()> { + writeln!(self.file, "{entry}").context("failed to write log")?; + self.file + .flush() + .context("failed to flush log to I/O media")?; + Ok(()) + } +} + +impl EventLog { + pub async fn new( + rtmr_extender: Arc, + alg: HashAlgorithm, + pcr: u64, + ) -> Result { + tokio::fs::create_dir_all(EVENTLOG_PARENT_DIR_PATH) + .await + .context("create eventlog parent dir")?; + if Path::new(EVENTLOG_PATH).exists() { + debug!("Previous AAEL found. Skip INIT entry recording..."); + let content = tokio::fs::read_to_string(EVENTLOG_PATH) + .await + .context("Read AAEL")?; + + // The content of AAEL can be empty when the previous AA created this file + // but did not do anything. + if content.is_empty() { + let file = File::open(EVENTLOG_PATH).context("open eventlog")?; + let mut eventlog = Self { + writer: Box::new(FileWriter { file }), + rtmr_extender, + alg, + pcr, + }; + eventlog + .extend_init_entry() + .await + .context("extend INIT entry")?; + return Ok(eventlog); + } + + let aael = AAEventlog::from_str(&content).context("Parse AAEL")?; + let rtmr = rtmr_extender + .get_runtime_measurement(pcr) + .await + .context("Get RTMR failed")?; + + // The integrity check might fail when previous AA record the entry into + // aael but failed to extend RTMR. This check will try to catch this case + // and do then unfinished RTMR extending. + match aael.integrity_check(&rtmr) { + true => debug!("Existing RTMR is consistent with current AAEL"), + false => { + debug!( + "Existing RTMR is not consistent with current AAEL, do a RTMR extending..." + ); + let digest = match aael.events.is_empty() { + true => alg.digest( + format!( + "INIT {}/{:0>width$}", + aael.hash_algorithm, + hex::encode(aael.init_state), + width = aael.hash_algorithm.digest_len() + ) + .as_bytes(), + ), + false => alg.digest(aael.events[0].as_bytes()), + }; + rtmr_extender + .extend_runtime_measurement(digest, pcr) + .await + .context("Extend RTMR failed")?; + } + } + + let file = OpenOptions::new() + .append(true) + .open(EVENTLOG_PATH) + .context("open eventlog")?; + + return Ok(Self { + writer: Box::new(FileWriter { file }), + rtmr_extender, + alg, + pcr, + }); + } + + debug!("No AA eventlog exists, creating a new one and do INIT entry recording..."); + let file = File::create(EVENTLOG_PATH).context("create eventlog")?; + let writer = Box::new(FileWriter { file }); + let mut eventlog = Self { + writer, + rtmr_extender, + alg, + pcr, + }; + eventlog + .extend_init_entry() + .await + .context("extend INIT entry")?; + Ok(eventlog) + } + + pub async fn extend_entry(&mut self, log_entry: LogEntry<'_>, pcr: u64) -> Result<()> { + let digest = log_entry.digest_with(self.alg); + // The order must be ensured to keep consistency. s.t. first write AAEL + // and then extend RTMR. + self.writer.append(&log_entry).context("write log entry")?; + self.rtmr_extender + .extend_runtime_measurement(digest, pcr) + .await?; + + Ok(()) + } + + pub async fn extend_init_entry(&mut self) -> Result<()> { + let pcr = self.rtmr_extender.get_runtime_measurement(self.pcr).await?; + let init_value = hex::encode(pcr); + let init_value = format!("{:0>width$}", init_value, width = self.alg.digest_len()); + let init_entry = LogEntry::Init { + hash_alg: self.alg, + value: &init_value, + }; + let digest = init_entry.digest_with(self.alg); + self.writer + .append(&init_entry) + .context("write INIT log entry")?; + + self.rtmr_extender + .extend_runtime_measurement(digest, self.pcr) + .await + .context("write INIT entry")?; + Ok(()) + } +} + +pub struct Content<'a>(&'a str); + +impl<'a> TryFrom<&'a str> for Content<'a> { + type Error = anyhow::Error; + + fn try_from(value: &'a str) -> Result { + if value.chars().any(|c| c == '\n') { + bail!("content contains newline"); + } + Ok(Content(value)) + } +} + +pub enum LogEntry<'a> { + Event { + domain: &'a str, + operation: &'a str, + content: Content<'a>, + }, + Init { + hash_alg: HashAlgorithm, + value: &'a str, + }, +} + +impl LogEntry<'_> { + /// Calculate the LogEntry's digest with the given [`HashAlgorithm`] + pub fn digest_with(&self, hash_alg: HashAlgorithm) -> Vec { + let log_entry = self.to_string(); + hash_alg.digest(log_entry.as_bytes()) + } +} + +impl Display for LogEntry<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + LogEntry::Event { + domain, + operation, + content, + } => { + write!(f, "{} {} {}", domain, operation, content.0) + } + LogEntry::Init { hash_alg, value } => { + let (sha, init_value) = match hash_alg { + HashAlgorithm::Sha256 => ("sha256", value), + HashAlgorithm::Sha384 => ("sha384", value), + HashAlgorithm::Sha512 => ("sha512", value), + }; + write!(f, "INIT {}/{}", sha, init_value) + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use attester::detect_tee_type; + use rstest::rstest; + use std::sync::{Arc, Mutex}; + + struct TestWriter(Arc>>); + + impl Writer for TestWriter { + fn append(&mut self, entry: &LogEntry) -> Result<()> { + self.0.lock().unwrap().push(entry.to_string()); + Ok(()) + } + } + + #[test] + fn test_content() { + let a_str = "hello"; + let _: Content = a_str.try_into().unwrap(); + let b_str = "hello\nworld"; + let content: Result = b_str.try_into(); + assert!(content.is_err()); + } + + /// The eventlog will influence the underlying hardware + #[ignore] + #[tokio::test] + async fn test_log_events() { + let lines = Arc::new(Mutex::new(vec![])); + let tw = TestWriter(lines.clone()); + let tee = detect_tee_type(); + let rtmr_extender = Arc::new(tee.try_into().unwrap()); + let mut el = EventLog { + writer: Box::new(tw), + pcr: 17, + rtmr_extender, + alg: HashAlgorithm::Sha256, + }; + let i = LogEntry::Init { + hash_alg: HashAlgorithm::Sha256, + value: "0000000000000000000000000000000000000000000000000000000000000000", + }; + + el.extend_entry(i, 17).await.unwrap(); + let i_line = concat!( + "INIT sha256/00000000000000000000000000", + "00000000000000000000000000000000000000" + ); + assert_eq!(lines.lock().unwrap().join("\n"), i_line); + let ev = LogEntry::Event { + domain: "one", + operation: "two", + content: "three".try_into().unwrap(), + }; + el.extend_entry(ev, 17).await.unwrap(); + let e_line = "one two three"; + assert_eq!(lines.lock().unwrap()[1], e_line); + } + + #[rstest] + #[case( + "domain", + "operation", + "content", + "65aad3b1620d4fe224d727579db2db87ff5c033f3e4424ae0fd72eb1149d3bd5", + HashAlgorithm::Sha256 + )] + #[case("domain", "operation", "content", "26d944cb8d99096590252283b8c807b9508329b068703bdb7bac7eb6efe5b32fc0fadf1462662b95d2c708aa49c0bfe1", HashAlgorithm::Sha384)] + #[case("domain", "operation", "content", "6e75837e0fbf8367fa4550254b8f0f52eb659be0901340357ed91dda97f0ebca10537540a021eec78df9d29ade51609a01eaaa46d32e0218cdac1644dc9933b0", HashAlgorithm::Sha512)] + fn test_event_digest( + #[case] domain: &str, + #[case] operation: &str, + #[case] content: &str, + #[case] digest: &str, + #[case] hash_alg: HashAlgorithm, + ) { + let event = LogEntry::Event { + domain, + operation, + content: content.try_into().unwrap(), + }; + let dig = event.digest_with(hash_alg); + let dig_hex = dig.iter().map(|c| format!("{c:02x}")).collect::(); + assert_eq!(dig_hex, digest); + } +} diff --git a/attestation-agent/attestation-agent/src/lib.rs b/attestation-agent/attestation-agent/src/lib.rs index a9e114648..1e40092b6 100644 --- a/attestation-agent/attestation-agent/src/lib.rs +++ b/attestation-agent/attestation-agent/src/lib.rs @@ -7,7 +7,7 @@ use anyhow::{bail, Context, Result}; use async_trait::async_trait; use attester::{detect_tee_type, BoxedAttester}; use kbs_types::Tee; -use std::{io::Write, str::FromStr}; +use std::{io::Write, str::FromStr, sync::Arc}; use tokio::sync::{Mutex, RwLock}; pub use attester::InitDataResult; @@ -78,7 +78,7 @@ pub trait AttestationAPIs { /// Attestation agent to provide attestation service. pub struct AttestationAgent { config: RwLock, - attester: BoxedAttester, + attester: Arc, eventlog: Option>, tee: Tee, } @@ -87,18 +87,12 @@ impl AttestationAgent { pub async fn init(&mut self) -> Result<()> { let config = self.config.read().await; if config.eventlog_config.enable_eventlog { - let alg = config.eventlog_config.eventlog_algorithm; - let pcr = config.eventlog_config.init_pcr; - - let init_entry = LogEntry::Init(alg); - let digest = init_entry.digest_with(alg); - let mut eventlog = EventLog::new()?; - eventlog.write_log(&init_entry).context("write INIT log")?; - - self.attester - .extend_runtime_measurement(digest, pcr) - .await - .context("write INIT entry")?; + let eventlog = EventLog::new( + self.attester.clone(), + config.eventlog_config.eventlog_algorithm, + config.eventlog_config.init_pcr, + ) + .await?; self.eventlog = Some(Mutex::new(eventlog)); } @@ -122,6 +116,7 @@ impl AttestationAgent { let tee = detect_tee_type(); let attester: BoxedAttester = tee.try_into()?; + let attester = Arc::new(attester); Ok(AttestationAgent { config, @@ -199,7 +194,7 @@ impl AttestationAPIs for AttestationAgent { bail!("Extend eventlog not enabled when launching!"); }; - let (pcr, log_entry, alg) = { + let (pcr, log_entry) = { let config = self.config.read().await; let pcr = register_index.unwrap_or_else(|| { @@ -215,21 +210,12 @@ impl AttestationAPIs for AttestationAgent { operation, content, }; - let alg = config.eventlog_config.eventlog_algorithm; - (pcr, log_entry, alg) + (pcr, log_entry) }; - let digest = log_entry.digest_with(alg); - { - // perform atomicly in this block - let mut eventlog = eventlog.lock().await; - self.attester - .extend_runtime_measurement(digest, pcr) - .await?; + eventlog.lock().await.extend_entry(log_entry, pcr).await?; - eventlog.write_log(&log_entry)?; - } Ok(()) } diff --git a/attestation-agent/attester/src/lib.rs b/attestation-agent/attester/src/lib.rs index 090208998..195e79eb3 100644 --- a/attestation-agent/attester/src/lib.rs +++ b/attestation-agent/attester/src/lib.rs @@ -92,6 +92,13 @@ pub trait Attester { async fn bind_init_data(&self, _init_data_digest: &[u8]) -> Result { Ok(InitDataResult::Unsupported) } + + /// This function is used to get the runtime measurement registry value of + /// the given PCR register index. Different platforms have different mapping + /// relationship between PCR and platform RTMR. + async fn get_runtime_measurement(&self, _pcr_index: u64) -> Result> { + bail!("Unimplemented") + } } // Detect which TEE platform the KBC running environment is. diff --git a/attestation-agent/attester/src/tdx/mod.rs b/attestation-agent/attester/src/tdx/mod.rs index 7f99add51..70507876d 100644 --- a/attestation-agent/attester/src/tdx/mod.rs +++ b/attestation-agent/attester/src/tdx/mod.rs @@ -11,6 +11,7 @@ use crate::utils::pad; use crate::InitDataResult; use anyhow::*; use base64::Engine; +use report::TdReport; use scroll::Pread; use serde::{Deserialize, Serialize}; use std::fs; @@ -71,6 +72,40 @@ struct TdxEvidence { #[derive(Debug, Default)] pub struct TdxAttester {} +impl TdxAttester { + fn get_report() -> Result { + let mut report = tdx_report_t { d: [0; 1024] }; + match tdx_attest_rs::tdx_att_get_report(None, &mut report) { + tdx_attest_rs::tdx_attest_error_t::TDX_ATTEST_SUCCESS => { + log::debug!("Successfully got report") + } + error_code => { + bail!( + "TDX Attester: Failed to get TD report. Error code: {:?}", + error_code + ); + } + }; + + let td_report = report + .d + .pread::(0) + .context("Parse TD report failed")?; + + Ok(td_report) + } + + fn pcr_to_rtmr(register_index: u64) -> u64 { + // The match follows https://github.com/confidential-containers/td-shim/blob/main/doc/tdshim_spec.md#td-event-log + match register_index { + 1 | 7 => 0, + 2..=6 => 1, + 8..=15 => 2, + _ => 3, + } + } +} + #[async_trait::async_trait] impl Attester for TdxAttester { async fn get_evidence(&self, mut report_data: Vec) -> Result { @@ -128,13 +163,7 @@ impl Attester for TdxAttester { bail!("TDX Attester: Cannot extend runtime measurement on this system"); } - // The match follows https://github.com/confidential-containers/td-shim/blob/main/doc/tdshim_spec.md#td-event-log - let rtmr_index = match register_index { - 1 | 7 => 0, - 2..=6 => 1, - 8..=15 => 2, - _ => 3, - }; + let rtmr_index = Self::pcr_to_rtmr(register_index); let extend_data: [u8; 48] = pad(&event_digest); let event: Vec = TdxRtmrEvent::default() @@ -158,24 +187,7 @@ impl Attester for TdxAttester { } async fn bind_init_data(&self, init_data_digest: &[u8]) -> Result { - let mut report = tdx_report_t { d: [0; 1024] }; - match tdx_attest_rs::tdx_att_get_report(None, &mut report) { - tdx_attest_rs::tdx_attest_error_t::TDX_ATTEST_SUCCESS => { - log::debug!("Successfully get report") - } - error_code => { - bail!( - "TDX Attester: Failed to get TD report. Error code: {:?}", - error_code - ); - } - }; - - let td_report = report - .d - .pread::(0) - .context("Parse TD report failed")?; - + let td_report = Self::get_report()?; let init_data: [u8; 48] = pad(init_data_digest); if init_data != td_report.tdinfo.mrconfigid { bail!("Init data does not match!"); @@ -183,6 +195,13 @@ impl Attester for TdxAttester { Ok(InitDataResult::Ok) } + + async fn get_runtime_measurement(&self, pcr_index: u64) -> Result> { + let td_report = Self::get_report()?; + let rtmr_index = Self::pcr_to_rtmr(pcr_index) as usize; + + Ok(td_report.get_rtmr(rtmr_index)) + } } #[cfg(test)] diff --git a/attestation-agent/attester/src/tdx/report.rs b/attestation-agent/attester/src/tdx/report.rs index 5c7a39c52..7ec35e951 100644 --- a/attestation-agent/attester/src/tdx/report.rs +++ b/attestation-agent/attester/src/tdx/report.rs @@ -97,3 +97,40 @@ pub struct TdReport { /// Measurements and configuration data of size 512 bytes. pub tdinfo: TdInfo, } + +impl TdReport { + pub fn get_rtmr(&self, rtmr_index: usize) -> Vec { + let mut rtmr_u8 = Vec::new(); + let rtmr = &self.tdinfo.rtmr[rtmr_index * 6..(rtmr_index + 1) * 6]; + for i in rtmr { + rtmr_u8.extend_from_slice(&i.to_le_bytes()); + } + + rtmr_u8 + } +} + +#[cfg(test)] +mod test { + use rstest::rstest; + use scroll::Pread; + + use crate::tdx::{report::TdReport, TdxAttester}; + + /// This test uses a fixture of tdx-report to check if the get_runtime_measurement function works correctly. + /// The following test PCRs are mapping to TDX RTMR 0, 1, 2 and 3 + #[rstest] + #[case(1, "f4ec1a04670fe7926cd5de4aef9aaa7689ab4ceaa132d7c5242b47f67dfaaea64c372a17ad68fef9a6ac99aabbddabdc")] + #[case(2, "4e5f8826653198ab4bc5156fbe4bc99db054c0b8239a16c4b59249fb427f4acc50eed1b46a85c7d526c4e1e47621b14c")] + #[case(8, "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")] + #[case(16, "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")] + fn get_runtime_measurement(#[case] pcr_index: u64, #[case] expected: &str) { + let report_bin = include_bytes!("../../test/tdx_report_1.bin"); + let rtmr_index = TdxAttester::pcr_to_rtmr(pcr_index) as usize; + + let expected = hex::decode(expected).unwrap(); + let td_report = report_bin.pread::(0).unwrap(); + + assert_eq!(td_report.get_rtmr(rtmr_index), expected); + } +} diff --git a/attestation-agent/attester/test/tdx_report_1.bin b/attestation-agent/attester/test/tdx_report_1.bin new file mode 100644 index 0000000000000000000000000000000000000000..4bdfd290951965cd2f190224aefec11d9c923f17 GIT binary patch literal 1024 zcmZoNnKU}9eDcyE21Z*}b;Q?jP1!FiCdy_Wr9H=QGP@Txq`8 zbl~{a8~d`CObZd-e>&^^9BJpsw?5|;l@)d${_!AD{GQ+K{zq@jX0i3&o$|m?#9lmt zw=LSRwtSz}WY$ZJ@4h%)gxNzY+9~pTkw<0kgdJBIvJR;IwmtB%=Fg7%_fqHBH)o$T zv~&N@$PBicm5~X>2{OkPR&Ag8a@pix$Ahz<$q2oZ<6pgYg)Yy`c^qfnX|2e8Ud>VP zHR5Q&^Z!p{bKQTjplBw6gKjSgTB)T}0A$c}CrFgf*nN1Oo%)v3>Th!u+qosmEx!33 z@t$u#`5N=)sFr5}cecb!=T;u`IKICx}OMGp{zn{z2%v`nm?&>>!@f~WZhBH=s9~I5t=Y4YS zhL8h0lxK+@**eMdw^O~>nSgf}w`8>*zp8fR;gd4OjXu(R;k(+d7@|-MKeB{`I;i&Eweo?evueyjQBCm~C;HL##Le|6R(- literal 0 HcmV?d00001 diff --git a/attestation-agent/deps/crypto/src/algorithms.rs b/attestation-agent/deps/crypto/src/algorithms.rs index 924ea5561..68873c695 100644 --- a/attestation-agent/deps/crypto/src/algorithms.rs +++ b/attestation-agent/deps/crypto/src/algorithms.rs @@ -10,11 +10,16 @@ use std::fmt; use std::str::FromStr; /// Hash algorithms used to calculate runtime/init data binding -#[derive(Serialize, Deserialize, Clone, Debug, Display, Copy, PartialEq)] +#[derive(AsRefStr, Serialize, Deserialize, Clone, Debug, Display, Copy, PartialEq)] #[serde(rename_all = "lowercase")] pub enum HashAlgorithm { + #[strum(serialize = "sha256")] Sha256, + + #[strum(serialize = "sha384")] Sha384, + + #[strum(serialize = "sha512")] Sha512, } @@ -29,6 +34,15 @@ fn hash_reportdata(material: &[u8]) -> Vec { } impl HashAlgorithm { + /// Return the hash value length in bytes + pub fn digest_len(&self) -> usize { + match self { + HashAlgorithm::Sha256 => 32, + HashAlgorithm::Sha384 => 48, + HashAlgorithm::Sha512 => 64, + } + } + pub fn digest(&self, material: &[u8]) -> Vec { match self { HashAlgorithm::Sha256 => hash_reportdata::(material), @@ -57,6 +71,8 @@ impl fmt::Display for ParseHashAlgorithmError { } } +impl std::error::Error for ParseHashAlgorithmError {} + impl FromStr for HashAlgorithm { type Err = ParseHashAlgorithmError;