rust: Add EBC support to pv_core library

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 <dengler@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Finn Callies <fcallies@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
This commit is contained in:
Finn Callies
2026-03-25 17:12:08 +01:00
committed by Jan Höppner
parent 20de1fce2a
commit 1741ecff96
4 changed files with 131 additions and 1 deletions

View File

@@ -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

View File

@@ -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"

View File

@@ -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};

112
rust/pv_core/src/policy.rs Normal file
View File

@@ -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<P, H>(src: P, sha256: H) -> Result<Self>
where
P: AsRef<Path>,
H: Fn(File) -> Result<Vec<u8>>,
{
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<PathBuf> {
// Extract bytes until the first null byte (null-terminated string)
let name_bytes: Vec<u8> = 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")
)
}
}