rust: Add toc support for ebc to pvsecret

Add two new command-line options to pvsecret create for Early Boot
Customization (EBC) Table of Contents (TOC) support:

 1. --policy FILE
    Links an Add-Secret-Request (ASR) to a policy file by embedding a
    PolicyReference in the ASR's user data field. The PolicyReference
    contains the relative file path and SHA512 hash of the policy file,
    enabling integrity verification of the policy. This option conflicts
    with --user-data as both use the same user data field in the ASR
    structure.

 2. --toc-policy FILE
    Appends the AES-GCM authentication tag (MAC tag - last 16 bytes of
    the encrypted ASR) to the specified TOC policy file. This enables
    the TOC policy to maintain a list of all ASR MAC tags for
    completeness verification during boot. The TOC can verify that all
    expected ASRs are present and unmodified by checking their MAC tags
    against this list. This option also conflicts with --user-data.

Both options support the EBC multi-party workflow where an ISV/CSP builds
a generic SEL image and customers customize it with their own secrets. The
TOC mechanism ensures the integrity and completeness of all EBC resources
during the boot process.

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-23 14:44:44 +01:00
committed by Jan Höppner
parent 1741ecff96
commit c803cb925e
5 changed files with 111 additions and 5 deletions

View File

@@ -13,6 +13,7 @@ anyhow = { version = "1.0.95", features = ["std"] }
clap = { version ="4.5", features = ["derive", "wrap_help"]}
log = { version = "0.4.25", features = ["std", "release_max_level_debug"] }
serde_yaml = "0.9"
zerocopy = "0.8"
pv = { path = "../pv" , package = "s390_pv" }
utils = { path = "../utils"}

View File

@@ -194,6 +194,28 @@ Optional. No user\-data by default.
.RE
.RE
.PP
\-\-policy <FILE>
.RS 4
Links an add\-secret request to a policy file.
This option embeds a reference to a policy in the add\-secret request user data field. The
reference includes the relative file path and the SHA-512 hash of the
policy file, enabling verification of the policy files integrity.
This option conflicts with \fB\-\-user\-data\fR, because both options use the
same user data field in the add\-secret request structure.
.RE
.RE
.PP
\-\-toc\-policy <FILE>
.RS 4
Adds the AES\-GCM authentication tag to a table-of-contents (TOC) policy file.
This option appends the AES\-GCM authentication tag to the specified TOC policy
file. This allows the TOC policy to maintain a list of all add\-secret request MAC tags for
completeness verification during boot. During verification, the TOC checks the
AES\-GCM tags against this list to ensure that all expected add\-secret request are present and
unmodified.
.RE
.RE
.PP
\-\-user\-sign\-key <FILE>
.RS 4
Use the content of FILE as user signing key. Adds a signature calculated from

View File

@@ -130,6 +130,30 @@ pub struct CreateSecretOpt {
#[arg(long, value_name = "FILE", value_hint = ValueHint::FilePath,)]
pub user_data: Option<String>,
/// Links an AddSecret-Request (ASR) to a policy file.
///
/// This option embeds a PolicyReference in the ASR user data field. The PolicyReference
/// includes the relative file path and the SHA512 hash of the policy file, allowing the
/// policys integrity to be verified.
///
/// This option conflicts with --user-data, because both options use the same user data field in
/// the ASR structure.
#[arg(long, value_name = "FILE", value_hint = ValueHint::FilePath, conflicts_with("user_data"))]
pub policy: Option<String>,
/// Adds the AESGCM authentication tag to a TOC policy file.
///
/// This option appends the AESGCM authentication tag to the specified TOC policy file. This
/// allows the TOC policy to maintain a list of all ASR MAC tags for completeness verification
/// during boot. During verification, the TOC checks the MAC tags against this list to ensure
/// that all expected ASRs are present and unmodified.
#[arg(
long = "toc-policy",
value_name = "FILE",
value_hint = ValueHint::FilePath,
)]
pub tocpolicy: Option<String>,
/// Use the content of FILE as user signing key.
///
/// Adds a signature calculated from the key in FILE to the add-secret request. The

View File

@@ -2,24 +2,30 @@
//
// Copyright IBM Corp. 2023, 2024
use std::path::Path;
use std::{
fs::OpenOptions,
io::{Read, Write},
path::Path,
};
use anyhow::{anyhow, bail, Context, Error, Result};
use log::{debug, info, trace, warn};
use pv::request::openssl;
use pv::{
misc::{
decode_hex, open_file, pv_guest_bit_set, read_exact_file, read_file, try_parse_u128,
try_parse_u64, write,
decode_hex, encode_hex, open_file, pv_guest_bit_set, read_exact_file, read_file,
try_parse_u128, try_parse_u64, write,
},
request::{
openssl::pkey::{PKey, Private},
BootHdrTags, ReqEncrCtx, Request, SymKeyType,
BootHdrTags, PolicyReference, ReqEncrCtx, Request, SymKeyType,
},
secret::{AddSecretFlags, AddSecretRequest, AddSecretVersion, ExtSecret, GuestSecret},
uv::ConfigUid,
};
use serde_yaml::Value;
use utils::get_writer_from_cli_file_arg;
use zerocopy::IntoBytes;
use crate::cli::{AddSecretType, CreateSecretFlags, CreateSecretOpt, RetrieveableSecretInpKind};
@@ -33,6 +39,38 @@ where
Ok(())
}
/// Computes the SHA-256 hash of data from a reader.
///
/// Reads data from the provided reader in 4096-byte chunks and computes
/// the SHA-256 hash of the entire content.
///
/// # Parameters
///
/// * `r` - A reader providing the data to hash
///
/// # Returns
///
/// Returns a `Vec<u8>` containing the 32-byte SHA-256 hash, or an error
/// if reading fails.
///
/// # Errors
///
/// Returns an error if reading from the reader fails.
pub fn sha256_hash<R: Read>(mut r: R) -> Result<Vec<u8>, pv::PvCoreError> {
let mut hasher = openssl::Sha256::new();
let mut buf: [u8; 4096] = [0; 4096];
loop {
let read = r.read(&mut buf)?;
if read == 0 {
break;
}
hasher.update(&buf[..read]);
}
Ok(hasher.finish().to_vec())
}
fn retrievable(name: &str, secret: &str, kind: &RetrieveableSecretInpKind) -> Result<GuestSecret> {
let secret_data = read_file(secret, &format!("retrievable {kind}"))?.into();
@@ -76,6 +114,14 @@ pub fn create(opt: &CreateSecretOpt) -> Result<()> {
let rq =
ReqEncrCtx::random(SymKeyType::Aes256Gcm).context("Failed to generate random input")?;
let ser_asrbc = asrcb.encrypt(&rq)?;
if let Some(path) = &opt.tocpolicy {
let mac_tag = encode_hex(&ser_asrbc[(ser_asrbc.len() - 16)..]);
let mut file = OpenOptions::new().create(true).append(true).open(path)?;
writeln!(file, "{mac_tag}")?;
}
warn!("Successfully generated the request");
write_out(&opt.output, ser_asrbc, "add-secret request")?;
info!("Successfully wrote the request to '{}'", &opt.output);
@@ -157,6 +203,17 @@ fn build_asrcb(opt: &CreateSecretOpt) -> Result<AddSecretRequest> {
warn!("Added empty user-data file.");
}
let supplied_ref = opt
.policy
.as_ref()
.map(|s| -> Result<PolicyReference> {
let p = Path::new(s);
let reference = PolicyReference::new(p, sha256_hash)?;
println!("{}", encode_hex(reference.hash));
Ok(reference)
})
.transpose()?;
let user_key = opt
.user_sign_key
.as_ref()
@@ -169,6 +226,8 @@ fn build_asrcb(opt: &CreateSecretOpt) -> Result<AddSecretRequest> {
if user_data.is_some() || user_key.is_some() {
asrcb.set_user_data(user_data.unwrap_or_default(), user_key)?;
} else if let Some(ref_val) = supplied_ref {
asrcb.set_user_data(ref_val.as_bytes(), None)?;
}
Ok(asrcb)
}

View File

@@ -14,7 +14,7 @@ use utils::{print_cli_error, print_error, print_version, PvLogger};
static LOGGER: PvLogger = PvLogger;
static EXIT_LOGGER: u8 = 3;
const FEATURES: &[&[&str]] = &[cmd::CMD_FN, cmd::UV_CMD_FN];
const FEATURES: &[&[&str]] = &[cmd::CMD_FN, cmd::UV_CMD_FN, &["+ebc"]];
fn main() -> ExitCode {
let cli: CliOptions = match CliOptions::try_parse() {