diff --git a/0106-support-attest-npu-firmware-file-combined-with-itrus.patch b/0106-support-attest-npu-firmware-file-combined-with-itrus.patch new file mode 100644 index 0000000000000000000000000000000000000000..aae41bf680a15a62c4888ad2a9f10a457455821b --- /dev/null +++ b/0106-support-attest-npu-firmware-file-combined-with-itrus.patch @@ -0,0 +1,941 @@ +From 66a2c55e26b1f8f73d5041134a0fb5612d4cf6cd Mon Sep 17 00:00:00 2001 +Subject: [PATCH] support attest npu firmware file, combined with itrustee token +From: MaxMadMax +Date: Mon, 21 Jul 2025 09:09:56 +0800 + +Reference: https://gitee.com/openeuler/secGear/pulls/342 +--- + service/attestation/README.md | 3 +- + .../attestation/attestation-agent/Cargo.toml | 1 + + .../attestation-agent/agent/src/lib.rs | 2 +- + .../attestation-agent/attester/Cargo.toml | 3 +- + .../attestation-agent/attester/src/ima/mod.rs | 113 ++++++++++++++++++ + .../attester/src/itrustee/mod.rs | 69 +++++++++-- + .../attestation-agent/attester/src/lib.rs | 3 + + .../attester/src/virtcca/mod.rs | 17 +-- + .../attestation-service/service/src/lib.rs | 2 +- + .../verifier/src/ima/itrustee.rs | 72 +++++++++++ + .../verifier/src/ima/mod.rs | 81 +++++++++++++ + .../verifier/src/ima/virtcca.rs | 87 ++++++++++++++ + .../verifier/src/itrustee/mod.rs | 85 +++++++++---- + .../attestation-service/verifier/src/lib.rs | 2 + + .../verifier/src/virtcca/mod.rs | 31 ++--- + .../verifier/src/virtcca/uefi.rs | 3 +- + .../attestation/attestation-types/src/lib.rs | 6 + + 17 files changed, 502 insertions(+), 78 deletions(-) + create mode 100644 service/attestation/attestation-agent/attester/src/ima/mod.rs + create mode 100644 service/attestation/attestation-service/verifier/src/ima/itrustee.rs + create mode 100644 service/attestation/attestation-service/verifier/src/ima/mod.rs + create mode 100644 service/attestation/attestation-service/verifier/src/ima/virtcca.rs + +diff --git a/service/attestation/README.md b/service/attestation/README.md +index 32bfd42..f2080d8 100644 +--- a/service/attestation/README.md ++++ b/service/attestation/README.md +@@ -167,6 +167,7 @@ cd secGear/service/attestation/attestation-agent + ./target/debug/attestation-agent -p https -t cert.pem 2>&1 & + + // you can use -u specified destination which AA connect to , -s specified port which AA listen at ++// notice: use https when set svr_url in aa config file + ./target/debug/attestation-agent -p https -t cert.pem -s server.com:8081 -u server.com:8080 2>&1 & + + ``` +@@ -174,4 +175,4 @@ cd secGear/service/attestation/attestation-agent + ``` + cd secGear/service/attestation/attestation-agent + ./target/debug/aa-test +-``` +\ No newline at end of file ++``` +diff --git a/service/attestation/attestation-agent/Cargo.toml b/service/attestation/attestation-agent/Cargo.toml +index 126a9f4..c5ef7e9 100644 +--- a/service/attestation/attestation-agent/Cargo.toml ++++ b/service/attestation/attestation-agent/Cargo.toml +@@ -21,6 +21,7 @@ thiserror = "1.0" + actix-web = "4.5" + clap = { version = "4.5.7", features = ["derive"] } + scc = "2.1" ++sha2 = "0.10" + + verifier = { path = "../attestation-service/verifier", default-features = false } + attestation-types = { path = "../attestation-types" } +diff --git a/service/attestation/attestation-agent/agent/src/lib.rs b/service/attestation/attestation-agent/agent/src/lib.rs +index 7df0638..41dc9f1 100644 +--- a/service/attestation/attestation-agent/agent/src/lib.rs ++++ b/service/attestation/attestation-agent/agent/src/lib.rs +@@ -19,7 +19,7 @@ pub mod restapi; + pub mod result; + + use actix_web::web::Bytes; +-use anyhow::{anyhow, bail, Context, Result}; ++use anyhow::{anyhow, bail, Result}; + use async_trait::async_trait; + use attestation_types::{resource::ResourceLocation, service::GetResourceOp}; + use attester::{Attester, AttesterAPIs}; +diff --git a/service/attestation/attestation-agent/attester/Cargo.toml b/service/attestation/attestation-agent/attester/Cargo.toml +index 2c6a012..f4e5ef3 100644 +--- a/service/attestation/attestation-agent/attester/Cargo.toml ++++ b/service/attestation/attestation-agent/attester/Cargo.toml +@@ -4,7 +4,7 @@ version = "0.1.0" + edition = "2021" + + [features] +-itrustee-attester = ["base64-url", "rand"] ++itrustee-attester = ["base64-url", "rand", "sha2"] + virtcca-attester = ["base64-url"] + + [dependencies] +@@ -16,3 +16,4 @@ base64-url = { workspace = true, optional = true } + async-trait.workspace = true + log.workspace = true + attestation-types.workspace = true ++sha2 = { workspace = true, optional = true } +diff --git a/service/attestation/attestation-agent/attester/src/ima/mod.rs b/service/attestation/attestation-agent/attester/src/ima/mod.rs +new file mode 100644 +index 0000000..67c2465 +--- /dev/null ++++ b/service/attestation/attestation-agent/attester/src/ima/mod.rs +@@ -0,0 +1,113 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++ ++//! IMA (Integrity Measurement Architecture) module ++//! ++//! This module provides functionality to read and handle IMA logs. ++ ++use anyhow::{bail, Result}; ++use log; ++ ++const IMA_LOG_PATH: &str = "/sys/kernel/security/ima/binary_runtime_measurements"; ++ ++/// IMA log reader and handler ++#[derive(Debug, Default)] ++struct ImaLogReader {} ++ ++impl ImaLogReader { ++ /// Create a new IMA log reader instance ++ fn new() -> Self { ++ Self {} ++ } ++ ++ /// Read IMA log from the system ++ /// ++ /// Returns the IMA log data as a vector of bytes, or None if IMA is not enabled ++ /// or the log cannot be read. ++ fn read_ima_log(&self) -> Result>> { ++ match std::fs::read(IMA_LOG_PATH) { ++ Ok(data) => { ++ log::info!("read ima log success"); ++ Ok(Some(data)) ++ } ++ Err(e) => { ++ log::error!("read IMA log failed: {}", e); ++ bail!("get ima log failed: {}", e); ++ } ++ } ++ } ++ ++ /// Check if IMA is available on the system ++ fn is_ima_available(&self) -> bool { ++ std::path::Path::new(IMA_LOG_PATH).exists() ++ } ++ ++ /// Read IMA log if requested ++ /// ++ /// This function checks the `with_ima` parameter and reads the IMA log ++ /// only if it's requested. ++ fn read_ima_log_if_requested(&self, with_ima: bool) -> Result>> { ++ if with_ima { ++ self.read_ima_log() ++ } else { ++ Ok(None) ++ } ++ } ++} ++ ++/// Convenience function to read IMA log if requested ++/// ++/// This is a standalone function that can be used without creating an ImaLogReader instance. ++pub fn read_ima_log_if_requested(with_ima: bool) -> Result>> { ++ let reader = ImaLogReader::new(); ++ reader.read_ima_log_if_requested(with_ima) ++} ++ ++#[cfg(test)] ++mod tests { ++ use super::*; ++ ++ #[test] ++ fn test_ima_log_reader_creation() { ++ let reader = ImaLogReader::new(); ++ assert!(reader.is_ima_available() || !reader.is_ima_available()); // Should not panic ++ } ++ ++ #[test] ++ fn test_read_ima_log_if_requested_false() { ++ let reader = ImaLogReader::new(); ++ let result = reader.read_ima_log_if_requested(false); ++ assert!(result.is_ok()); ++ assert!(result.unwrap().is_none()); ++ } ++ ++ #[test] ++ fn test_static_functions() { ++ // Test static function for false case ++ let result = read_ima_log_if_requested(false); ++ assert!(result.is_ok()); ++ assert!(result.unwrap().is_none()); ++ ++ // Test static function for true case (may fail if IMA not available, but should not panic) ++ let _result = read_ima_log_if_requested(true); ++ // We don't assert the result here as it depends on system state ++ // Just ensure it doesn't panic ++ } ++ ++ #[test] ++ fn test_ima_availability_check() { ++ let reader = ImaLogReader::new(); ++ let available = reader.is_ima_available(); ++ // This should not panic regardless of system state ++ assert!(available == true || available == false); ++ } ++} +\ No newline at end of file +diff --git a/service/attestation/attestation-agent/attester/src/itrustee/mod.rs b/service/attestation/attestation-agent/attester/src/itrustee/mod.rs +index 22b6afd..aed1956 100644 +--- a/service/attestation/attestation-agent/attester/src/itrustee/mod.rs ++++ b/service/attestation/attestation-agent/attester/src/itrustee/mod.rs +@@ -19,9 +19,12 @@ use base64_url; + use log; + use serde::{Deserialize, Serialize}; + use serde_json; +-use std::path::Path; ++use std::fs; ++use sha2::{Sha256, Digest}; ++use attestation_types::ItrusteeEvidence; + + use crate::EvidenceRequest; ++use crate::ima; + + mod itrustee; + +@@ -41,7 +44,9 @@ impl ItrusteeAttester { + } + + pub fn detect_platform() -> bool { +- Path::new("/usr/bin/tee").exists() ++ fs::read_to_string("/proc/modules") ++ .map(|content| content.lines().any(|line| line.starts_with("tzdriver"))) ++ .unwrap_or(false) + } + + #[derive(Serialize, Deserialize)] +@@ -59,24 +64,63 @@ struct ItrusteeInput { + handler: String, + payload: ReportInputPayload, + } ++ + const MAX_CHALLENGE_LEN: usize = 64; ++const MAX_CHALLENGE_LEN_IMA: usize = 32; + fn itrustee_get_evidence(user_data: EvidenceRequest) -> Result { + let challenge = base64_url::decode(&user_data.challenge)?; + let len = challenge.len(); +- if len <= 0 || len > MAX_CHALLENGE_LEN { ++ let with_ima = match user_data.ima { ++ Some(ima) => ima, ++ None => false, ++ }; ++ // If IMA is enabled, the challenge length is 32 bytes, otherwise it is 64 bytes ++ // As we need 32 bytes to pass IMA log hash to the TEE. ++ let max_challenge_len = if with_ima { ++ MAX_CHALLENGE_LEN_IMA ++ } else { ++ MAX_CHALLENGE_LEN ++ }; ++ if len <= 0 || len > max_challenge_len { + log::error!( +- "challenge len is error, expecting 0 < len <= {}, got {}", +- MAX_CHALLENGE_LEN, ++ "challenge length is wrong, expecting 0 < len <= {}, got {}", ++ max_challenge_len, + len + ); + bail!( +- "challenge len is error, expecting 0 < len <= {}, got {}", +- MAX_CHALLENGE_LEN, ++ "challenge length is wrong, expecting 0 < len <= {}, got {}", ++ max_challenge_len, + len + ); + } ++ ++ let ima_log = if with_ima { ++ ima::read_ima_log_if_requested(with_ima)? ++ } else { ++ None ++ }; ++ ++ let nonce = if with_ima { ++ if ima_log.is_none() { ++ log::error!("ima log is empty"); ++ bail!("ima log is empty"); ++ } ++ ++ // Calculate SHA256 hash of IMA log ++ let mut hasher = Sha256::new(); ++ hasher.update(ima_log.as_ref().unwrap()); ++ let ima_log_hash = hasher.finalize(); ++ ++ // Combine challenge and IMA log hash ++ let mut combined = challenge.clone(); ++ combined.extend_from_slice(&ima_log_hash); ++ base64_url::encode(&combined) ++ } else { ++ String::from_utf8(user_data.challenge)? ++ }; ++ + let payload = ReportInputPayload { +- nonce: String::from_utf8(user_data.challenge)?, ++ nonce: nonce, + uuid: user_data.uuid, + with_tcb: false, + request_key: true, +@@ -84,7 +128,7 @@ fn itrustee_get_evidence(user_data: EvidenceRequest) -> Result { + hash_alg: String::from("HS256"), + }; + +- let itrustee_input: ItrusteeInput = ItrusteeInput { ++ let itrustee_input = ItrusteeInput { + handler: String::from("report-input"), + payload: payload, + }; +@@ -111,8 +155,13 @@ fn itrustee_get_evidence(user_data: EvidenceRequest) -> Result { + report.set_len(out_len); + } + let str_report = String::from_utf8(report)?; ++ let final_report = ItrusteeEvidence { ++ report: str_report, ++ ima_log: ima_log, ++ }; + +- Ok(str_report) ++ let final_report_str = serde_json::to_string(&final_report)?; ++ Ok(final_report_str) + } + + fn itrustee_provision() -> Result<()> { +diff --git a/service/attestation/attestation-agent/attester/src/lib.rs b/service/attestation/attestation-agent/attester/src/lib.rs +index d495a6f..c7d0096 100644 +--- a/service/attestation/attestation-agent/attester/src/lib.rs ++++ b/service/attestation/attestation-agent/attester/src/lib.rs +@@ -23,6 +23,9 @@ mod itrustee; + #[cfg(feature = "virtcca-attester")] + pub mod virtcca; + ++// IMA module for handling IMA logs ++pub mod ima; ++ + #[derive(Debug, Clone)] + pub struct EvidenceRequest { + pub uuid: String, +diff --git a/service/attestation/attestation-agent/attester/src/virtcca/mod.rs b/service/attestation/attestation-agent/attester/src/virtcca/mod.rs +index 87153c5..9be04f0 100644 +--- a/service/attestation/attestation-agent/attester/src/virtcca/mod.rs ++++ b/service/attestation/attestation-agent/attester/src/virtcca/mod.rs +@@ -22,10 +22,10 @@ use std::path::Path; + use self::virtcca::{get_attestation_token, get_dev_cert, tsi_new_ctx}; + use crate::virtcca::virtcca::tsi_free_ctx; + use crate::EvidenceRequest; ++use crate::ima; + + mod virtcca; + +-const IMA_LOG_PATH: &str = "/sys/kernel/security/ima/binary_runtime_measurements"; + const CCEL_TABLE_PATH: &str = "/sys/firmware/acpi/tables/CCEL"; + const CCEL_DATA_PATH: &str = "/sys/firmware/acpi/tables/data/CCEL"; + +@@ -94,19 +94,8 @@ fn virtcca_get_token(user_data: EvidenceRequest) -> Result { + None => false, + }; + +- let ima_log = match with_ima { +- true => match std::fs::read(IMA_LOG_PATH) { +- Ok(d) => { +- log::info!("read ima log success"); +- Some(d) +- } +- Err(e) => { +- log::error!("read IMA log failed: {}", e); +- bail!("get ima log failed"); +- } +- }, +- false => None, +- }; ++ // Use the new IMA module to read IMA log ++ let ima_log = ima::read_ima_log_if_requested(with_ima)?; + + let ccel_table = std::fs::read(CCEL_TABLE_PATH).ok(); + let ccel_data = std::fs::read(CCEL_DATA_PATH).ok(); +diff --git a/service/attestation/attestation-service/service/src/lib.rs b/service/attestation/attestation-service/service/src/lib.rs +index 96328ce..78e9491 100644 +--- a/service/attestation/attestation-service/service/src/lib.rs ++++ b/service/attestation/attestation-service/service/src/lib.rs +@@ -110,7 +110,7 @@ impl AttestationService { + }) + } + +- async fn evaluate_evidence_field(claims_evidence: &Value, field: &str, mut passed: &mut bool) { ++ async fn evaluate_evidence_field(claims_evidence: &Value, field: &str, passed: &mut bool) { + log::debug!( + "claims evidence {}: {:?}", + field, +diff --git a/service/attestation/attestation-service/verifier/src/ima/itrustee.rs b/service/attestation/attestation-service/verifier/src/ima/itrustee.rs +new file mode 100644 +index 0000000..4513508 +--- /dev/null ++++ b/service/attestation/attestation-service/verifier/src/ima/itrustee.rs +@@ -0,0 +1,72 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++ ++use super::{file_reader, ImaVerifier, verify_ima_events}; ++use anyhow::{anyhow, bail, Result}; ++use fallible_iterator::FallibleIterator; ++use ima_measurements::{Event, Parser}; ++use openssl::sha::sha256; ++use serde_json::{json, Value}; ++ ++#[cfg(not(feature = "no_as"))] ++const IMA_REFERENCE_FILE: &str = ++ "/etc/attestation/attestation-service/verifier/itrustee/ima/digest_list_file"; ++ ++// attestation agent local ima reference ++#[cfg(feature = "no_as")] ++const IMA_REFERENCE_FILE: &str = ++ "/etc/attestation/attestation-agent/local_verifier/itrustee/ima/digest_list_file"; ++ ++/// iTrustee specific IMA verifier implementation ++#[derive(Debug, Default)] ++pub struct ItrusteeImaVerify {} ++ ++impl ImaVerifier for ItrusteeImaVerify { ++ fn ima_verify(&self, ima_log: &[u8], ima_log_hash: &[Vec]) -> Result { ++ if ima_log.is_empty() { ++ return Ok(json!({})); ++ } ++ ++ let mut parser = Parser::new(ima_log); ++ let mut events: Vec = Vec::new(); ++ while let Some(event) = parser.next()? { ++ events.push(event); ++ } ++ ++ if events.len() < 2 { ++ bail!("No IMA measurement records for files found."); ++ } ++ ++ // Note: iTrustee does not check pcr_index as it is TPM dependent. ++ // Verify that ima_log_hash array is not empty before accessing ++ if ima_log_hash.is_empty() { ++ bail!("ima_log_hash array is empty"); ++ } ++ ++ // Calculate the sha256sum of the ima log and compare with the expected hash ++ let ima_log_hashsum = sha256(ima_log); ++ if ima_log_hashsum.to_vec() != ima_log_hash[0] { ++ log::error!("ima log hash verification failed, sha256sum not match. \ ++ ima_log_hashsum: {:?}, ima_log_hash: {:?}", ima_log_hashsum, ima_log_hash[0]); ++ bail!("ima log hash verification failed, sha256sum not match. \ ++ ima_log_hashsum: {:?}, ima_log_hash: {:?}", ima_log_hashsum, ima_log_hash[0]); ++ } ++ ++ let ima_refs = file_reader(IMA_REFERENCE_FILE) ++ .map_err(|err| anyhow!("Failed to read {}: {}", IMA_REFERENCE_FILE, err))? ++ .into_iter() ++ .map(String::from) ++ .collect(); ++ // Use the common function to verify IMA events ++ verify_ima_events(&events, &ima_refs) ++ } ++} +\ No newline at end of file +diff --git a/service/attestation/attestation-service/verifier/src/ima/mod.rs b/service/attestation/attestation-service/verifier/src/ima/mod.rs +new file mode 100644 +index 0000000..d92cf7c +--- /dev/null ++++ b/service/attestation/attestation-service/verifier/src/ima/mod.rs +@@ -0,0 +1,81 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++ ++//! IMA verifier module ++//! ++//! This module provides IMA (Integrity Measurement Architecture) verification functionality ++//! for TEE attestation. ++ ++use anyhow::{bail, Result}; ++use ima_measurements::Event; ++use serde_json::{Map, Value}; ++use std::collections::HashSet; ++use std::fs::File; ++use std::io::{self, BufRead, BufReader}; ++ ++pub mod virtcca; ++pub mod itrustee; ++ ++/// File reader utility function ++pub fn file_reader(file_path: &str) -> io::Result> { ++ let file = File::open(file_path)?; ++ let reader = BufReader::new(file); ++ let mut set = HashSet::new(); ++ ++ for line in reader.lines() { ++ let line = line?; ++ set.insert(line.trim_end().to_string()); ++ } ++ ++ Ok(set) ++} ++ ++/// Common function to verify IMA events against reference values ++/// This function handles the common logic of comparing file digests with reference values, ++/// which is shared between different TEE implementations. ++pub fn verify_ima_events(events: &[Event], ima_refs: &HashSet) -> Result { ++ if events.len() < 2 { ++ bail!("No IMA measurement records for files found."); ++ } ++ ++ let mut ima_detail = Map::new(); ++ // parse each file digest in ima log, and compare with reference base value ++ for event in events { ++ let (name, file_digest) = match &event.data { ++ ima_measurements::EventData::ImaNg { digest, name } => (name, &digest.digest), ++ _ => bail!("Invalid event {:?}", event), ++ }; ++ if name == "boot_aggregate" { ++ continue; ++ } ++ let hex_str_digest = hex::encode(file_digest); ++ if ima_refs.contains(&hex_str_digest) { ++ ima_detail.insert(name.clone(), Value::Bool(true)); ++ } else { ++ log::error!( ++ "there is no reference base value of file digest {:?}", ++ hex_str_digest ++ ); ++ ima_detail.insert(name.clone(), Value::Bool(false)); ++ } ++ } ++ ++ let js_ima_detail: Value = ima_detail.into(); ++ log::debug!("ima verify detail result: {:?}", js_ima_detail); ++ ++ Ok(js_ima_detail) ++} ++ ++/// IMA verifier trait for different TEE implementations ++pub trait ImaVerifier { ++ fn ima_verify(&self, ima_log: &[u8], addons: &[Vec]) -> Result; ++} +\ No newline at end of file +diff --git a/service/attestation/attestation-service/verifier/src/ima/virtcca.rs b/service/attestation/attestation-service/verifier/src/ima/virtcca.rs +new file mode 100644 +index 0000000..e453469 +--- /dev/null ++++ b/service/attestation/attestation-service/verifier/src/ima/virtcca.rs +@@ -0,0 +1,87 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++ ++use super::{file_reader, ImaVerifier, verify_ima_events}; ++use anyhow::{anyhow, bail, Result}; ++use fallible_iterator::FallibleIterator; ++use ima_measurements::{Event, Parser}; ++use serde_json::{json, Value}; ++ ++#[cfg(not(feature = "no_as"))] ++const IMA_REFERENCE_FILE: &str = ++ "/etc/attestation/attestation-service/verifier/virtcca/ima/digest_list_file"; ++ ++// attestation agent local ima reference ++#[cfg(feature = "no_as")] ++const IMA_REFERENCE_FILE: &str = ++ "/etc/attestation/attestation-agent/local_verifier/virtcca/ima/digest_list_file"; ++ ++const CVM_REM_ARR_SIZE: usize = 4; ++ ++/// VirtCCA specific IMA verifier implementation ++#[derive(Debug, Default)] ++pub struct VirtCCAImaVerify {} ++ ++impl ImaVerifier for VirtCCAImaVerify { ++ fn ima_verify(&self, ima_log: &[u8], cvm_rem: &[Vec]) -> Result { ++ if ima_log.is_empty() { ++ return Ok(json!({})); ++ } ++ ++ // Parse IMA events ++ let mut parser = Parser::new(ima_log); ++ let mut events: Vec = Vec::new(); ++ while let Some(event) = parser.next()? { ++ events.push(event); ++ } ++ ++ if events.len() < 2 { ++ bail!("No IMA measurement records for files found."); ++ } ++ ++ let pcr_index = events[1].pcr_index; ++ if pcr_index < 1 || pcr_index > CVM_REM_ARR_SIZE as u32 { ++ bail!("Invalid pcr_index for IMA"); ++ } ++ ++ let ima_index = (pcr_index - 1) as usize; ++ let pcr_values = parser.pcr_values(); ++ let pcr_value = pcr_values.get(&pcr_index).expect("PCR not measured"); ++ let string_pcr_sha256 = hex::encode(pcr_value.sha256); ++ let string_ima_log_hash = hex::encode(cvm_rem[ima_index].clone()); ++ ++ log::debug!( ++ "pcr_index: {}, string_pcr_sha256: {}, string_ima_log_hash: {}", ++ pcr_index, ++ string_pcr_sha256, ++ string_ima_log_hash ++ ); ++ ++ if string_pcr_sha256 != string_ima_log_hash { ++ log::error!( ++ "ima log verify failed string_pcr_sha256 {}, string_ima_log_hash {}", ++ string_pcr_sha256, ++ string_ima_log_hash ++ ); ++ bail!("IMA log hash verification failed. Please check the log and reference data, and verify if PCR has been extended to PCR4."); ++ } ++ ++ let ima_refs = file_reader(IMA_REFERENCE_FILE) ++ .map_err(|err| anyhow!("Failed to read {}: {}", IMA_REFERENCE_FILE, err))? ++ .into_iter() ++ .map(String::from) ++ .collect(); ++ ++ // Use the common function to verify IMA events ++ verify_ima_events(&events, &ima_refs) ++ } ++} +\ No newline at end of file +diff --git a/service/attestation/attestation-service/verifier/src/itrustee/mod.rs b/service/attestation/attestation-service/verifier/src/itrustee/mod.rs +index d098cde..e49753b 100644 +--- a/service/attestation/attestation-service/verifier/src/itrustee/mod.rs ++++ b/service/attestation/attestation-service/verifier/src/itrustee/mod.rs +@@ -17,26 +17,36 @@ use log; + use serde_json::json; + use std::ops::Add; + use std::path::Path; +-use serde_json::Value; ++use crate::ima::ImaVerifier; ++use attestation_types::ItrusteeEvidence; + + mod itrustee; + + const ITRUSTEE_REF_VALUE_DIR: &str = +- "/etc/attestation/attestation-service/reference-itrustee/"; ++ "/etc/attestation/attestation-service/verifier/itrustee"; ++const MAX_CHALLENGE_LEN: usize = 64; + + #[derive(Debug, Default)] + pub struct ItrusteeVerifier {} + + impl ItrusteeVerifier { + pub async fn evaluate(&self, user_data: &[u8], evidence: &[u8]) -> Result { +- return evalute_wrapper(user_data, evidence); ++ evaluate_wrapper(user_data, evidence) + } + } +-const MAX_CHALLENGE_LEN: usize = 64; +-fn evalute_wrapper(user_data: &[u8], evidence: &[u8]) -> Result { ++ ++fn evaluate_wrapper(user_data: &[u8], evidence: &[u8]) -> Result { + let challenge = base64_url::decode(user_data)?; ++ let evidence: ItrusteeEvidence = serde_json::from_slice(evidence)?; ++ ++ log::debug!("{}", serde_json::to_string_pretty(&evidence).unwrap()); ++ ++ let report = evidence.report; ++ let js_evidence: serde_json::Value = serde_json::from_str(&report)?; ++ let with_ima = evidence.ima_log.is_some(); ++ let ima_log = evidence.ima_log.unwrap_or_default(); + let len = challenge.len(); +- if len <= 0 || len > MAX_CHALLENGE_LEN { ++ if len == 0 || len > MAX_CHALLENGE_LEN { + log::error!( + "challenge len is error, expecting 0 < len <= {}, got {}", + MAX_CHALLENGE_LEN, +@@ -48,8 +58,29 @@ fn evalute_wrapper(user_data: &[u8], evidence: &[u8]) -> Result { + len + ); + } ++ ++ let mut ima = serde_json::Value::Null; + let mut in_data = challenge.to_vec(); +- let mut in_evidence = evidence.to_vec(); ++ if with_ima { ++ let report_nonce = js_evidence["payload"]["nonce"].as_str().unwrap(); ++ let nonce_all = base64_url::decode(&report_nonce)?; ++ if nonce_all.len() != MAX_CHALLENGE_LEN { ++ log::error!("IMA verification: nonce length is not 64 bytes, got {}", nonce_all.len()); ++ bail!("IMA verification: nonce length is not 64 bytes, got {}", nonce_all.len()); ++ } ++ let nonce_expected = &nonce_all[..32]; // 前32字节是challenge ++ let ima_log_hash = &nonce_all[32..]; // 后32字节是ima_log_hash ++ if nonce_expected != challenge { ++ log::error!("IMA verification: nonce and challenge mismatch"); ++ bail!("IMA verification: nonce and challenge mismatch"); ++ } ++ ima = crate::ima::itrustee::ItrusteeImaVerify::default() ++ .ima_verify(&ima_log, &[ima_log_hash.to_vec()])?; ++ in_data = nonce_all.to_vec(); ++ } ++ ++ // let mut in_data = challenge.to_vec(); ++ let mut in_evidence = report.as_bytes().to_vec(); + let mut data_buf: itrustee::buffer_data = itrustee::buffer_data { + size: in_evidence.len() as ::std::os::raw::c_uint, + buf: in_evidence.as_mut_ptr() as *mut ::std::os::raw::c_uchar, +@@ -58,32 +89,31 @@ fn evalute_wrapper(user_data: &[u8], evidence: &[u8]) -> Result { + size: in_data.len() as ::std::os::raw::c_uint, + buf: in_data.as_mut_ptr() as *mut ::std::os::raw::c_uchar, + }; +- // parse uuid from evidence to find it's basevalue file that store in ITRUSTEE_REF_VALUE_DIR +- let evidence_json:Value = serde_json::from_slice(evidence)?; +- println!("{}", serde_json::to_string_pretty(&evidence_json).unwrap()); ++ ++ // 1: verify ta_img; 2: verfiy ta_mem; 3: verify ta_img and ta_mem hash; ++ let policy: std::os::raw::c_int = 1; ++ + let uuid; +- if let Some(v)= evidence_json.get("payload") +- .and_then(|v|v.get("uuid")) +- .and_then(|v|v.as_str()) { ++ if let Some(v) = js_evidence.get("payload") ++ .and_then(|v|v.get("uuid")) ++ .and_then(|v|v.as_str()) { + uuid = v; ++ } else { ++ log::error!("Parse TA uuid from evidence failed."); ++ bail!("Parse TA uuid from evidence failed."); + } +- else { +- log::error!("parse uuid from evidence failed"); +- bail!("parse uuid from evidence faild"); +- } +- let policy: std::os::raw::c_int = 1; // 1: verify ta_imag; 2: verfiy ta_mem; 3: verify ta_img and ta_mem hash; +- let ref_value_file = ITRUSTEE_REF_VALUE_DIR.to_string() + "itrustee_" + uuid; +- if !Path::new(&ref_value_file).exists() { ++ let ref_file = ITRUSTEE_REF_VALUE_DIR.to_string() + "/itrustee_" + uuid; ++ if !Path::new(&ref_file).exists() { + log::error!( + "itrustee verify report {} not exists", +- ref_value_file ++ ref_file + ); + bail!( + "itrustee verify report {} not exists", +- ref_value_file ++ ref_file + ); + } +- let ref_file = String::from(ref_value_file); ++ + let mut file = ref_file.add("\0"); + let basevalue = file.as_mut_ptr() as *mut ::std::os::raw::c_char; + unsafe { +@@ -93,7 +123,7 @@ fn evalute_wrapper(user_data: &[u8], evidence: &[u8]) -> Result { + bail!("itrustee verify report failed ret:{}", ret); + } + } +- let js_evidence: serde_json::Value = serde_json::from_slice(evidence)?; ++ + let payload = json!({ + "itrustee.nonce": js_evidence["payload"]["nonce"].clone(), + "itrustee.hash_alg": js_evidence["payload"]["hash_alg"].clone(), +@@ -103,9 +133,14 @@ fn evalute_wrapper(user_data: &[u8], evidence: &[u8]) -> Result { + "itrustee.uuid": js_evidence["payload"]["uuid"].clone(), + "itrustee.version": js_evidence["payload"]["version"].clone(), + }); ++ + let claim = json!({ + "tee": "itrustee", + "payload" : payload, ++ "ima" : ima, + }); ++ ++ log::debug!("claim: {}", serde_json::to_string_pretty(&claim).unwrap()); ++ + Ok(claim as TeeClaim) +-} ++} +\ No newline at end of file +diff --git a/service/attestation/attestation-service/verifier/src/lib.rs b/service/attestation/attestation-service/verifier/src/lib.rs +index bb02cc8..a256ed1 100644 +--- a/service/attestation/attestation-service/verifier/src/lib.rs ++++ b/service/attestation/attestation-service/verifier/src/lib.rs +@@ -28,6 +28,8 @@ pub mod virtcca; + #[cfg(feature = "rustcca-verifier")] + pub mod rustcca; + ++pub mod ima; ++ + pub type TeeClaim = serde_json::Value; + + #[derive(Debug, Default)] +diff --git a/service/attestation/attestation-service/verifier/src/virtcca/mod.rs b/service/attestation/attestation-service/verifier/src/virtcca/mod.rs +index a8c0959..9d152e9 100644 +--- a/service/attestation/attestation-service/verifier/src/virtcca/mod.rs ++++ b/service/attestation/attestation-service/verifier/src/virtcca/mod.rs +@@ -12,6 +12,7 @@ + + //! virtcca verifier plugin + use super::TeeClaim; ++use crate::ima::ImaVerifier; + + use anyhow::{anyhow, bail, Result}; + use ciborium; +@@ -26,7 +27,6 @@ use openssl::x509; + use serde_json::json; + + pub use attestation_types::{UefiLog, VirtccaEvidence}; +-pub mod ima; + pub mod uefi; + + #[cfg(not(feature = "no_as"))] +@@ -55,7 +55,7 @@ impl VirtCCAVerifier { + pub async fn evaluate(&self, user_data: &[u8], evidence: &[u8]) -> Result { + let challenge = base64_url::decode(user_data)?; + let len = challenge.len(); +- if len <= 0 || len > MAX_CHALLENGE_LEN { ++ if len == 0 || len > MAX_CHALLENGE_LEN { + log::error!( + "challenge len is error, expecting 0 < len <= {}, got {}", + MAX_CHALLENGE_LEN, +@@ -67,7 +67,7 @@ impl VirtCCAVerifier { + len + ); + } +- return Evidence::verify(&challenge.to_vec(), evidence); ++ Evidence::verify(&challenge.to_vec(), evidence) + } + } + +@@ -136,8 +136,8 @@ impl Evidence { + } + }; + +- let ima: serde_json::Value = +- ima::ImaVerify::default().ima_verify(&ima_log, &evidence.cvm_token.rem)?; ++ let ima: serde_json::Value = crate::ima::virtcca::VirtCCAImaVerify::default() ++ .ima_verify(&ima_log, &evidence.cvm_token.rem)?; + + // verify uefi + let uefi_log = if let Some(uefi_log) = virtcca_ev.uefi_log { +@@ -183,8 +183,8 @@ impl Evidence { + uefi: serde_json::Value, + ) -> Result { + let payload = json!({ +- "vcca.cvm.challenge": hex::encode(self.cvm_token.challenge.clone()), +- "vcca.cvm.rpv": hex::encode(self.cvm_token.rpv.clone()), ++ "vcca.cvm.challenge": hex::encode(self.cvm_token.challenge), ++ "vcca.cvm.rpv": hex::encode(self.cvm_token.rpv), + "vcca.cvm.rim": hex::encode(self.cvm_token.rim.clone()), + "vcca.cvm.rem.0": hex::encode(self.cvm_token.rem[0].clone()), + "vcca.cvm.rem.1": hex::encode(self.cvm_token.rem[1].clone()), +@@ -545,20 +545,3 @@ mod tests { + } + } + } +- +-use std::collections::HashSet; +-use std::fs::File; +-use std::io::{self, BufRead, BufReader}; +- +-pub fn file_reader(file_path: &str) -> io::Result> { +- let file = File::open(file_path)?; +- let reader = BufReader::new(file); +- let mut set = HashSet::new(); +- +- for line in reader.lines() { +- let line = line?; +- set.insert(line.trim_end().to_string()); +- } +- +- Ok(set) +-} +diff --git a/service/attestation/attestation-service/verifier/src/virtcca/uefi.rs b/service/attestation/attestation-service/verifier/src/virtcca/uefi.rs +index e2397ae..89d61ec 100644 +--- a/service/attestation/attestation-service/verifier/src/virtcca/uefi.rs ++++ b/service/attestation/attestation-service/verifier/src/virtcca/uefi.rs +@@ -10,7 +10,8 @@ + * See the Mulan PSL v2 for more details. + */ + +-use super::{file_reader, CVM_REM_ARR_SIZE}; ++use super::CVM_REM_ARR_SIZE; ++use crate::ima::file_reader; + use anyhow::{bail, Result}; + use attestation_types::UefiLog; + use eventlog_rs::{self, Eventlog}; +diff --git a/service/attestation/attestation-types/src/lib.rs b/service/attestation/attestation-types/src/lib.rs +index 5f3d43b..d28837d 100644 +--- a/service/attestation/attestation-types/src/lib.rs ++++ b/service/attestation/attestation-types/src/lib.rs +@@ -40,6 +40,12 @@ pub enum TeeType { + Invalid, + } + ++#[derive(Debug, Serialize, Deserialize)] ++pub struct ItrusteeEvidence { ++ pub report: String, ++ pub ima_log: Option>, ++} ++ + #[derive(Debug, Serialize, Deserialize)] + pub struct Evidence { + pub tee: TeeType, +-- +2.43.0 + diff --git a/secGear.spec b/secGear.spec index 5f0ef974290b966331a4e6161c36bb83cefddbc4..47e4e96bff699a5572c4bb3cf863e242619da0c3 100644 --- a/secGear.spec +++ b/secGear.spec @@ -1,6 +1,6 @@ Name: secGear Version: 0.1.0 -Release: 63 +Release: 64 Summary: secGear is an SDK to develop confidential computing apps based on hardware enclave features @@ -115,6 +115,7 @@ Patch101: 0102-Add-support-for-UEFI-measured-boot-attestation.patch Patch102: 0103-fix-ima-attestation-log-and-add-pcr-check.patch Patch103: 0104-attestation-service-Do-not-hardcode-the-token-path.patch Patch104: 0105-attestation-service-reference-support-itrustee.patch +Patch105: 0106-support-attest-npu-firmware-file-combined-with-itrus.patch BuildRequires: gcc python automake autoconf libtool BUildRequires: glibc glibc-devel cmake ocaml-dune rpm gcc-c++ compat-openssl11-libs compat-openssl11-devel @@ -357,6 +358,9 @@ popd systemctl restart rsyslog %changelog +* Thu Aug 28 2025 houmingyong - 0.1.0-64 +- sync support attest npu firmware file combined with itrustee token + * Thu Aug 28 2025 houmingyong - 0.1.0-63 - sync attestation service reference support itrustee feature