Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AS/verifier: support AA eventlog in TDX #408

Merged
merged 3 commits into from
Jul 15, 2024
Merged
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 1 addition & 4 deletions attestation-service/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,7 @@ fn flatten_helper(parent: &mut Map<String, Value>, child: &serde_json::Value, pr
let _ = parent.insert(prefix, Value::String(str.clone()));
}
Value::Array(arr) => {
for (i, v) in arr.iter().enumerate() {
let sub_prefix = format!("{prefix}.{i}");
flatten_helper(parent, v, sub_prefix);
}
let _ = parent.insert(prefix, Value::Array(arr.clone()));
}
Value::Object(obj) => {
for (k, v) in obj {
Expand Down
1 change: 1 addition & 0 deletions deps/verifier/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ serde.workspace = true
serde_json.workspace = true
serde_with = { workspace = true, optional = true }
sev = { version = "3.1.1", features = ["openssl", "snp"], optional = true }
sha2.workspace = true
tokio = { workspace = true, optional = true, default-features = false }
intel-tee-quote-verification-rs = { git = "https://github.com/intel/SGXDataCenterAttestationPrimitives", tag = "DCAP_1.21", optional = true }
strum.workspace = true
Expand Down
2 changes: 1 addition & 1 deletion deps/verifier/src/az_tdx_vtpm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ impl Verifier for AzTdxVtpm {

verify_hcl_var_data(&hcl_report, &td_quote)?;

let mut claim = generate_parsed_claim(td_quote, None)?;
let mut claim = generate_parsed_claim(td_quote, None, None)?;
extend_claim_with_tpm_quote(&mut claim, &evidence.tpm_quote)?;

Ok(claim)
Expand Down
22 changes: 22 additions & 0 deletions deps/verifier/src/eventlog/hash.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (c) 2024 Alibaba Cloud
//
// SPDX-License-Identifier: Apache-2.0
//

use strum::{AsRefStr, EnumString};

/// Hash algorithms used to calculate eventlog
#[derive(EnumString, AsRefStr, Clone)]
pub enum HashAlgorithm {
#[strum(ascii_case_insensitive)]
#[strum(serialize = "sha256")]
Sha256,

#[strum(ascii_case_insensitive)]
#[strum(serialize = "sha384")]
Sha384,

#[strum(ascii_case_insensitive)]
#[strum(serialize = "sha512")]
Sha512,
}
189 changes: 189 additions & 0 deletions deps/verifier/src/eventlog/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
// Copyright (c) 2024 Alibaba Cloud
//
// SPDX-License-Identifier: Apache-2.0
//

mod hash;

use std::str::FromStr;

use anyhow::{anyhow, bail, Context, Result};
use hash::HashAlgorithm;
use serde_json::{Map, Value};
use sha2::{digest::FixedOutput, Digest, Sha256, Sha384, Sha512};

#[derive(Clone)]
pub struct AAEvent {
pub domain: String,
pub operation: String,
pub content: String,
}

impl FromStr for AAEvent {
type Err = anyhow::Error;

fn from_str(input: &str) -> Result<Self> {
let input_trimed = input.trim_end();
let sections: Vec<&str> = input_trimed.split(' ').collect();
if sections.len() != 3 {
bail!("Illegal AA event entry format. Should be `<domain> <operation> <content>`");
}
Ok(Self {
domain: sections[0].into(),
operation: sections[1].into(),
content: sections[2].into(),
})
}
}

#[derive(Clone)]
pub struct AAEventlog {
pub hash_algorithm: HashAlgorithm,
pub init_state: Vec<u8>,
pub events: Vec<AAEvent>,
}

impl FromStr for AAEventlog {
type Err = anyhow::Error;

fn from_str(input: &str) -> Result<Self> {
let all_lines = input.lines().collect::<Vec<&str>>();

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::<Vec<&str>>();
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 `<sha-algorithm>/<init-PCR-value>` as content after `INIT`"
))?;

let hash_algorithm = hash_algorithm
.try_into()
.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| AAEvent::from_str(line))
.collect::<Result<Vec<AAEvent>>>()?;

Ok(Self {
events,
hash_algorithm,
init_state,
})
}
}

impl AAEventlog {
fn accumulate_hash<D: Digest + FixedOutput>(&self) -> Vec<u8> {
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.domain.as_bytes());
Digest::update(&mut event_hasher, b" ");
Digest::update(&mut event_hasher, event.operation.as_bytes());
Digest::update(&mut event_hasher, b" ");
Digest::update(&mut event_hasher, event.content.as_bytes());
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. The digest should be the same
/// as the input `rtmr`, or the integrity check will fail.
pub fn integrity_check(&self, rtmr: &[u8]) -> Result<()> {
let result = match self.hash_algorithm {
HashAlgorithm::Sha256 => self.accumulate_hash::<Sha256>(),
HashAlgorithm::Sha384 => self.accumulate_hash::<Sha384>(),
HashAlgorithm::Sha512 => self.accumulate_hash::<Sha512>(),
};

if rtmr != result {
bail!(
"AA eventlog does not pass check. AAEL value : {}, Quote value {}",
hex::encode(result),
hex::encode(rtmr)
);
}

Ok(())
}

pub fn to_parsed_claims(&self) -> Map<String, Value> {
let mut aael = Map::new();
for eventlog in &self.events {
let key = format!("{}/{}", eventlog.domain, eventlog.operation);
let item = Value::String(eventlog.content.clone());
match aael.get_mut(&key) {
Some(value) => value
.as_array_mut()
.expect("Only array can be inserted")
.push(item),
None => {
// This insertion will ensure the value in AAEL always be
// `Array`s. This will make `as_array_mut()` always result
// in `Some`.
aael.insert(key, Value::Array(vec![item]));
}
}
}

aael
}
}

#[cfg(test)]
mod tests {
use std::fs;

use rstest::rstest;

#[rstest]
#[case("./test_data/aael/AAEL_data_1", b"71563a23b430b8637970b866169052815ef9434056516dc9f78c1b3bfb745cee18a2ca92aa53c8122be5cbe59a100764")]
#[case("./test_data/aael/AAEL_data_2", b"31fa17881137923029b1da5b368e92d8b22b14bbb4deaa360da61fce7aa530bd2f4c59ac7bd27021ef64104ff4dd04f9")]
#[case("./test_data/aael/AAEL_data_3", b"0de62b45b29775495d278c85ad63ff45e59406e509506b26c545a5419316e1c4bd2b00a4e803051fa98b550767e13f06")]
fn aael_integrity_check(#[case] aael_path: &str, #[case] sum: &[u8]) {
use std::str::FromStr;

use super::AAEventlog;

let aael_bin = fs::read_to_string(aael_path).unwrap();
let aael = AAEventlog::from_str(&aael_bin).unwrap();
let sum = hex::decode(sum).unwrap();
aael.integrity_check(&sum).unwrap();
}
}
2 changes: 2 additions & 0 deletions deps/verifier/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ use log::debug;

pub mod sample;

pub mod eventlog;

#[cfg(feature = "az-snp-vtpm-verifier")]
pub mod az_snp_vtpm;

Expand Down
32 changes: 0 additions & 32 deletions deps/verifier/src/sgx/claims.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@
//! ```

use anyhow::*;
use byteorder::{LittleEndian, ReadBytesExt};
use serde_json::{Map, Value};

use crate::TeeEvidenceParsedClaim;
Expand Down Expand Up @@ -112,37 +111,6 @@ pub fn generate_parsed_claims(quote: sgx_quote3_t) -> Result<TeeEvidenceParsedCl
Ok(Value::Object(claims) as TeeEvidenceParsedClaim)
}

/// Kernel Commandline Event inside Eventlog
pub struct TdShimPlatformConfigInfo<'a> {
pub descriptor: [u8; 16],
pub info_length: u32,
pub data: &'a [u8],
}

impl<'a> TryFrom<&'a [u8]> for TdShimPlatformConfigInfo<'a> {
type Error = anyhow::Error;

fn try_from(data: &'a [u8]) -> std::result::Result<Self, Self::Error> {
if data.len() < core::mem::size_of::<[u8; 16]>() + core::mem::size_of::<u32>() {
bail!("give data slice is too short");
}

let descriptor = data[0..core::mem::size_of::<[u8; 16]>()].try_into()?;
let info_length = (&data[core::mem::size_of::<[u8; 16]>()
..core::mem::size_of::<[u8; 16]>() + core::mem::size_of::<u32>()])
.read_u32::<LittleEndian>()?;
let data = &data[core::mem::size_of::<[u8; 16]>() + core::mem::size_of::<u32>()
..core::mem::size_of::<[u8; 16]>()
+ core::mem::size_of::<u32>()
+ info_length as usize];
Ok(Self {
descriptor,
info_length,
data,
})
}
}

#[cfg(test)]
mod tests {
use assert_json_diff::assert_json_eq;
Expand Down
12 changes: 10 additions & 2 deletions deps/verifier/src/tdx/claims.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ use byteorder::{LittleEndian, ReadBytesExt};
use log::{debug, warn};
use serde_json::{Map, Value};

use crate::{tdx::quote::QuoteV5Body, TeeEvidenceParsedClaim};
use crate::{eventlog::AAEventlog, tdx::quote::QuoteV5Body, TeeEvidenceParsedClaim};

use super::{
eventlog::{CcEventLog, MeasuredEntity},
Expand All @@ -72,6 +72,7 @@ macro_rules! parse_claim {
pub fn generate_parsed_claim(
quote: Quote,
cc_eventlog: Option<CcEventLog>,
aa_eventlog: Option<AAEventlog>,
) -> Result<TeeEvidenceParsedClaim> {
let mut quote_map = Map::new();
let mut quote_body = Map::new();
Expand Down Expand Up @@ -172,6 +173,13 @@ pub fn generate_parsed_claim(
}

let mut claims = Map::new();

// Claims from AA eventlog
if let Some(aael) = aa_eventlog {
let aael_map = aael.to_parsed_claims();
parse_claim!(claims, "aael", aael_map);
}

parse_claim!(claims, "quote", quote_map);
parse_claim!(claims, "ccel", ccel_map);

Expand Down Expand Up @@ -329,7 +337,7 @@ mod tests {
let ccel_bin = std::fs::read("./test_data/CCEL_data").expect("read ccel failed");
let quote = parse_tdx_quote(&quote_bin).expect("parse quote");
let ccel = CcEventLog::try_from(ccel_bin).expect("parse ccel");
let claims = generate_parsed_claim(quote, Some(ccel)).expect("parse claim failed");
let claims = generate_parsed_claim(quote, Some(ccel), None).expect("parse claim failed");
let expected = json!({
"ccel": {
"kernel": "5b7aa6572f649714ff00b6a2b9170516a068fd1a0ba72aa8de27574131d454e6396d3bfa1727d9baf421618a942977fa",
Expand Down
5 changes: 1 addition & 4 deletions deps/verifier/src/tdx/eventlog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,8 @@ impl CcEventLog {
if rtmr_from_quote.rtmr0 != rtmr_eventlog.rtmr0
|| rtmr_from_quote.rtmr1 != rtmr_eventlog.rtmr1
|| rtmr_from_quote.rtmr2 != rtmr_eventlog.rtmr2
|| rtmr_from_quote.rtmr3 != rtmr_eventlog.rtmr3
{
return Err(anyhow!(
"RTMR values from TD quote is not equal with the values from EventLog\n"
));
bail!("RTMR 0, 1, 2 values from TD quote is not equal with the values from EventLog");
}

Ok(())
Expand Down
Loading
Loading