From 1741ecff96fcfbf3d2cc528562665bd5fc3861cb Mon Sep 17 00:00:00 2001 From: Finn Callies Date: Wed, 25 Mar 2026 17:12:08 +0100 Subject: [PATCH] rust: Add EBC support to pv_core library MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add EBC (Early Boot Customization) utility functions to pv_core library for parsing and verifying Add-Secret-Request structures. Introduce the core library functionality needed for EBC: - Add ebc_utils module to pv_core with ASR parsing and verification - Export ebc_utils in pv_core lib.rs - Re-export ebc_utils in pv lib.rs for downstream consumers - Update pvsecret Cargo.toml dependencies The library provides the foundation for tools that work with integrity-protected ASR structures used in SEL guest customization. Assisted-by: IBM Bob:1.0.1 Acked-by: Holger Dengler Reviewed-by: Steffen Eiden Signed-off-by: Finn Callies Signed-off-by: Jan Höppner --- rust/pv/src/lib.rs | 11 +++- rust/pv_core/Cargo.toml | 1 + rust/pv_core/src/lib.rs | 8 +++ rust/pv_core/src/policy.rs | 112 +++++++++++++++++++++++++++++++++++++ 4 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 rust/pv_core/src/policy.rs diff --git a/rust/pv/src/lib.rs b/rust/pv/src/lib.rs index 43375669..77d15e05 100644 --- a/rust/pv/src/lib.rs +++ b/rust/pv/src/lib.rs @@ -104,7 +104,14 @@ pub mod request { /// Reexports some useful OpenSSL symbols pub mod openssl { - pub use openssl::{error::ErrorStack, hash::DigestBytes, nid::Nid, pkey, x509}; + pub use openssl::{ + error::ErrorStack, + hash::DigestBytes, + nid::Nid, + pkey, + sha::{Sha256, Sha512}, + x509, + }; // rust-OpenSSL does not define these NIDs #[allow(missing_docs)] pub const NID_ED25519: Nid = Nid::from_raw(openssl_sys::NID_ED25519); @@ -113,6 +120,8 @@ pub mod request { } pub use pv_core::request::*; + + pub use pv_core::PolicyReference; } /// Functionalities for creating add-secret requests diff --git a/rust/pv_core/Cargo.toml b/rust/pv_core/Cargo.toml index 0a314b2f..23fcc90f 100644 --- a/rust/pv_core/Cargo.toml +++ b/rust/pv_core/Cargo.toml @@ -23,6 +23,7 @@ zerocopy = {version = "0.8", features = ["derive"]} serde = { version = "1.0.217", features = ["derive"]} byteorder = "1.5" regex = "1.10" +sha2 = "0.10.9" [dev-dependencies] serde_test = "1.0.177" diff --git a/rust/pv_core/src/lib.rs b/rust/pv_core/src/lib.rs index 03360fbc..2b272760 100644 --- a/rust/pv_core/src/lib.rs +++ b/rust/pv_core/src/lib.rs @@ -6,6 +6,7 @@ mod apdevice; mod confidential; mod error; mod macros; +mod policy; mod utils; mod uvattest; mod uvdevice; @@ -13,6 +14,13 @@ mod uvsecret; pub use error::{Error, FileAccessErrorType, FileIoErrorType, Result}; +/// Early Boot Customization (EBC) utilities. +/// +/// This module provides types and functions for working with Early Boot +/// Customization. The integrity and completeness of ASRs are ensured through +/// cryptographically protected table of contents files. +pub use policy::PolicyReference; + /// Functionalities for reading attestation requests pub mod attest { pub use crate::uvattest::{AttestationMagic, AttestationMeasAlg}; diff --git a/rust/pv_core/src/policy.rs b/rust/pv_core/src/policy.rs new file mode 100644 index 00000000..26640459 --- /dev/null +++ b/rust/pv_core/src/policy.rs @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. + +use crate::misc::encode_hex; +use crate::utils::open_file; +use crate::{Error, Result}; +use std::{ + fmt::{Display, Formatter, Result as Resfmt}, + fs::File, + os::unix::ffi::OsStrExt, + path::{Path, PathBuf}, + str::from_utf8, +}; +use zerocopy::{FromBytes, Immutable, IntoBytes}; + +const HASH_LEN: usize = 32; +// UserDataType::Unsigned.max() returns 512 +const USER_DATA_MAX_SIZE: usize = 512; + +/// A reference to a policy file containing its SHA-256 hash and file path. +/// +/// This structure is used in Early Boot Customization (EBC) to store +/// a reference to a policy file. It contains the SHA-256 hash of the policy +/// file content and the file path as a fixed-size byte array. +/// +/// The total size is constrained by `USER_DATA_MAX_SIZE` (512 bytes), with +/// 32 bytes allocated for the hash and the remaining bytes for the file path. +#[derive(Debug, FromBytes, IntoBytes, Immutable, Copy, Clone)] +#[repr(C)] +pub struct PolicyReference { + /// SHA-256 hash of the policy file content (32 bytes) + pub hash: [u8; HASH_LEN], + /// File path stored as a null-terminated byte array + pub name: [u8; USER_DATA_MAX_SIZE - HASH_LEN], +} + +impl PolicyReference { + /// Creates a new `PolicyReference` from a file path. + /// + /// Opens the file, reads its content, and computes the SHA-256 hash. + /// + /// # Parameters + /// + /// * `src` - The path to the policy file + /// * `sha256` - A function that computes the SHA-256 hash of the content + /// + /// # Returns + /// + /// Returns a `PolicyReference` containing the SHA-256 hash and the file path, + /// or an error if the file cannot be opened or the hash computation fails. + /// + /// # Note + /// + /// The file path is truncated if it exceeds the available space in the `name` field. + pub fn new(src: P, sha256: H) -> Result + where + P: AsRef, + H: Fn(File) -> Result>, + { + let mut ret = Self { + hash: [0; HASH_LEN], + name: [0; USER_DATA_MAX_SIZE - HASH_LEN], + }; + + let file = open_file(src.as_ref())?; + ret.hash.copy_from_slice(sha256(file)?.as_bytes()); + let strbytes = src.as_ref().as_os_str().as_bytes(); + let nbytes = strbytes.len().min(ret.name.len()); + ret.name[..nbytes].copy_from_slice(&strbytes[..nbytes]); + + Ok(ret) + } + + /// Converts the stored file path back to a `PathBuf`. + /// + /// # Returns + /// + /// Returns the file path as a `PathBuf`, or an error if the stored name + /// is not valid UTF-8. + /// + /// # Errors + /// + /// * `Error::ParseError` - If the name contains invalid UTF-8 + pub fn to_path(&self) -> Result { + // Extract bytes until the first null byte (null-terminated string) + let name_bytes: Vec = self + .name + .iter() + .copied() + .take_while(|&byte| byte != 0) + .collect(); + + let rust_string = String::from_utf8(name_bytes).map_err(|e| Error::ParseError { + subject: "PolicyReference name".to_string(), + content: format!("Invalid UTF-8 in name: {}", e), + })?; + + Ok(Path::new(&rust_string).to_owned()) + } +} + +impl Display for PolicyReference { + fn fmt(&self, f: &mut Formatter) -> Resfmt { + write!( + f, + "{} {}", + encode_hex(self.hash), + from_utf8(&self.name).expect("unable to convert name") + ) + } +}