Skip to content

Commit

Permalink
AS/verifier: support AA eventlog in TDX
Browse files Browse the repository at this point in the history
This is an alignment with guest-components side AA eventlog.

Signed-off-by: Xynnn007 <[email protected]>
  • Loading branch information
Xynnn007 committed Jun 18, 2024
1 parent fb96ea1 commit 0ee3742
Show file tree
Hide file tree
Showing 13 changed files with 256 additions and 11 deletions.
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.

1 change: 1 addition & 0 deletions attestation-service/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 attestation-service/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 attestation-service/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,
}
177 changes: 177 additions & 0 deletions attestation-service/verifier/src/eventlog/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
// 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 event_lines = input.lines().collect::<Vec<&str>>();

let (initline, eventline) = event_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 = eventline
.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);
aael.insert(key, serde_json::Value::String(eventlog.content.clone()));
}

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 attestation-service/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
12 changes: 10 additions & 2 deletions attestation-service/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 attestation-service/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
38 changes: 34 additions & 4 deletions attestation-service/verifier/src/tdx/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use std::str::FromStr;

use anyhow::anyhow;
use log::{debug, error, info, warn};

use crate::tdx::claims::generate_parsed_claim;
use crate::{eventlog::AAEventlog, tdx::claims::generate_parsed_claim};

use super::*;
use async_trait::async_trait;
Expand All @@ -21,6 +23,8 @@ struct TdxEvidence {
cc_eventlog: Option<String>,
// Base64 encoded TD quote.
quote: String,
// Eventlog of Attestation Agent
aa_eventlog: Option<String>,
}

#[derive(Debug, Default)]
Expand Down Expand Up @@ -105,15 +109,32 @@ async fn verify_evidence(
}
}

// Verify Integrity of AA eventlog
let aael = match &evidence.aa_eventlog {
Some(el) => {
let aael =
AAEventlog::from_str(el).context("failed to parse AA Eventlog from evidence")?;
// We assume we always use PCR 17, rtmr 3 for the application side events.

aael.integrity_check(quote.rtmr_3())?;
info!("CCEL integrity check succeeded.");
Some(aael)
}
None => {
warn!("No AA Eventlog included inside the TDX evidence.");
None
}
};

// Return Evidence parsed claim
generate_parsed_claim(quote, ccel_option)
generate_parsed_claim(quote, ccel_option, aael)
}

#[cfg(test)]
mod tests {

use super::*;
use std::fs;
use std::{fs, str::FromStr};

#[test]
fn test_generate_parsed_claim() {
Expand All @@ -122,12 +143,21 @@ mod tests {
let quote_bin = fs::read("./test_data/tdx_quote_4.dat").unwrap();
let quote = parse_tdx_quote(&quote_bin).unwrap();

let parsed_claim = generate_parsed_claim(quote, Some(ccel));
let parsed_claim = generate_parsed_claim(quote, Some(ccel), None);
assert!(parsed_claim.is_ok());

let _ = fs::write(
"./test_data/evidence_claim_output.txt",
format!("{:?}", parsed_claim.unwrap()),
);
}

#[test]
fn test_aael_binding() {
let aael_bin = fs::read_to_string("./test_data/aael/AAEL_data_1").unwrap();
let aael = AAEventlog::from_str(&aael_bin).unwrap();
let quote_bin = fs::read("./test_data/aael/AAEL_quote_tdx").unwrap();
let quote = parse_tdx_quote(&quote_bin).unwrap();
aael.integrity_check(quote.rtmr_3()).unwrap();
}
}
2 changes: 2 additions & 0 deletions attestation-service/verifier/test_data/aael/AAEL_data_1
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
INIT sha384/000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
image-rs CreateContainer docker.io/library/alpine
2 changes: 2 additions & 0 deletions attestation-service/verifier/test_data/aael/AAEL_data_2
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
INIT sha384/000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
github.com/confidential-containers CreateContainer docker.io/library/alpine
3 changes: 3 additions & 0 deletions attestation-service/verifier/test_data/aael/AAEL_data_3
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
INIT sha384/000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
github.com/confidential-containers CreateContainer docker.io/library/alpine
github.com/confidential-containers CreateContainer docker.io/library/busybox
Binary file not shown.

0 comments on commit 0ee3742

Please sign in to comment.