diff --git a/rust/pvsecret/Cargo.toml b/rust/pvsecret/Cargo.toml index d64c95e1..7be46d7d 100644 --- a/rust/pvsecret/Cargo.toml +++ b/rust/pvsecret/Cargo.toml @@ -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"} diff --git a/rust/pvsecret/man/pvsecret-create.1 b/rust/pvsecret/man/pvsecret-create.1 index 2372c92c..aa137519 100644 --- a/rust/pvsecret/man/pvsecret-create.1 +++ b/rust/pvsecret/man/pvsecret-create.1 @@ -194,6 +194,28 @@ Optional. No user\-data by default. .RE .RE .PP +\-\-policy +.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 file’s 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 +.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 .RS 4 Use the content of FILE as user signing key. Adds a signature calculated from diff --git a/rust/pvsecret/src/cli.rs b/rust/pvsecret/src/cli.rs index 9ddda198..8ceb32b7 100644 --- a/rust/pvsecret/src/cli.rs +++ b/rust/pvsecret/src/cli.rs @@ -130,6 +130,30 @@ pub struct CreateSecretOpt { #[arg(long, value_name = "FILE", value_hint = ValueHint::FilePath,)] pub user_data: Option, + /// Links an Add‑Secret-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 SHA‑512 hash of the policy file, allowing the + /// policy’s 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, + + /// Adds the AES‑GCM authentication tag to a 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 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, + /// Use the content of FILE as user signing key. /// /// Adds a signature calculated from the key in FILE to the add-secret request. The diff --git a/rust/pvsecret/src/cmd/create.rs b/rust/pvsecret/src/cmd/create.rs index a6f76213..4b8e2d4d 100644 --- a/rust/pvsecret/src/cmd/create.rs +++ b/rust/pvsecret/src/cmd/create.rs @@ -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` 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(mut r: R) -> Result, 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 { 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 { warn!("Added empty user-data file."); } + let supplied_ref = opt + .policy + .as_ref() + .map(|s| -> Result { + 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 { 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) } diff --git a/rust/pvsecret/src/main.rs b/rust/pvsecret/src/main.rs index 2338fb7e..4072f479 100644 --- a/rust/pvsecret/src/main.rs +++ b/rust/pvsecret/src/main.rs @@ -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() {