diff --git a/README.md b/README.md index c6d8b022..5e75558e 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,8 @@ Package contents Manage secrets for IBM Secure Execution guests - pvimg: Create and inspect IBM Secure Execution images + - pvebc: + Verify a secret structure for IBM Secure Execution for Linux. * dasdfmt: Low-level format ECKD DASDs with the classical Linux disk layout or the new diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 0471df0a..a551a6c7 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -62,9 +62,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.95" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "autocfg" @@ -111,6 +111,15 @@ dependencies = [ "wyz", ] +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "byteorder" version = "1.5.0" @@ -212,6 +221,25 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "curl" version = "0.4.49" @@ -302,6 +330,16 @@ dependencies = [ "syn", ] +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + [[package]] name = "enum_dispatch" version = "0.3.13" @@ -363,6 +401,16 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.2.16" @@ -404,6 +452,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "ident_case" version = "1.0.1" @@ -641,6 +695,20 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "pvebc" +version = "0.12.0" +dependencies = [ + "anyhow", + "clap", + "clap_complete", + "hex", + "s390_pv_core", + "sha2", + "utils", + "zerocopy", +] + [[package]] name = "pvimg" version = "0.12.0" @@ -687,6 +755,7 @@ dependencies = [ "s390_pv", "serde_yaml", "utils", + "zerocopy", ] [[package]] @@ -891,6 +960,7 @@ dependencies = [ "regex", "serde", "serde_test", + "sha2", "thiserror", "zerocopy", ] @@ -969,6 +1039,17 @@ dependencies = [ "unsafe-libyaml", ] +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "shlex" version = "1.3.0" @@ -1069,6 +1150,12 @@ dependencies = [ "winnow", ] +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + [[package]] name = "unarray" version = "0.1.4" @@ -1112,6 +1199,12 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "wait-timeout" version = "0.2.0" @@ -1238,18 +1331,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.25" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.25" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 0e96b677..1c6b2cde 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -5,6 +5,7 @@ members = [ "pv_core", "pvapconfig", "pvattest", + "pvebc", "pvimg", "pvinfo", "pvsecret", diff --git a/rust/Makefile b/rust/Makefile index 0043229f..a399df4f 100644 --- a/rust/Makefile +++ b/rust/Makefile @@ -11,7 +11,7 @@ PV_TARGETS := CARGO_TEST_TARGETS := ifneq (${HAVE_CARGO},0) - CARGO_TARGETS := + CARGO_TARGETS := pvebc BUILD_TARGETS = $(CARGO_TARGETS) INSTALL_TARGETS := install-rust-tools install-man install-shell-completions diff --git a/rust/pvebc/Cargo.toml b/rust/pvebc/Cargo.toml new file mode 100644 index 00000000..8066e3c8 --- /dev/null +++ b/rust/pvebc/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "pvebc" +version = "0.12.0" +edition.workspace = true +license.workspace = true +rust-version.workspace = true + +[dependencies] +sha2 = "0.10" +hex = "0.4" +clap = { version = "4.5", features = ["derive"] } +pv_core = { path = "../pv_core" , package = "s390_pv_core" } +utils = { path = "../utils" } +anyhow = { version = "1.0.95", features = ["std"] } +zerocopy = { version = "0.8.27", features = ["derive"] } + +[build-dependencies] +clap = { version ="4.5", features = ["derive"]} +clap_complete = "4.5" + +[lints] +workspace = true diff --git a/rust/pvebc/man/pvebc.1 b/rust/pvebc/man/pvebc.1 new file mode 100644 index 00000000..219176bf --- /dev/null +++ b/rust/pvebc/man/pvebc.1 @@ -0,0 +1,165 @@ +.\" Copyright IBM Corp. +.\" s390-tools is free software; you can redistribute it and/or modify +.\" it under the terms of the MIT license. See LICENSE for details. +.\" + +.TH "PVEBC" "1" "2026-03-24" "s390-tools" "EBC Management Manual" +.nh +.ad l +.SH NAME +pvebc \- Protected Virtualization Early Boot Customization Tool +.SH SYNOPSIS +.nf +.fam C +pvebc [OPTIONS] \-\-toc +.fam C +.fi +.SH DESCRIPTION +Use the \fBpvebc\fR tool to process add-secret request files (\fBtoc.asr\fR) +that define the root of early boot customization (EBC) resources for +IBM Secure Execution for Linux (SEL) guests. The tool validates the +integrity and completeness of add-secret request (ASR) files using +cryptographic verification to prevent tampering during transmission +over unsecured channels. + +The tool operates on a structure consisting of three types of files: +.RS 4 +\- \fBtoc.asr\fP: The root add\-secret request file that references the table of contents (TOC policy). + +\- \fBtoc.pol\fP: The table of contents policy file that contains a list of AES\-GCM authentication tags (MAC tags) + +\- \fBASR files\fP: Individual add-secret request files to be added to the guest + +.RE +The integrity protection mechanism works as follows: +.RS 4 +.IP "1." 3 +Each add-secret request file includes an AES\-GCM authentication tag (last 16 bytes), which provides integrity +protection for the file. +.IP "2." 3 +The toc.pol file lists the expected message authentication code (MAC) tags of all add-secret request files. +.IP "3." 3 +The toc.asr file contains a cryptographically protected reference to toc.pol, consisting +of the relative file path and a SHA\-512 hash of the policy file. +.IP "4." 3 +The AES\-GCM authentication tag of toc.asr protects the integrity of this reference. +.RE + +This structure prevents attackers from: +.RS 4 +\- Removing add-secret request files from the set + +\- Inserting unauthorized add-secret request files + +\- Modifying existing add-secret request files + +\- Altering the policy file + +.RE +\fBpvebc\fR validates the entire chain of trust by: +.RS 4 +\- Verifying that the SHA\-512 hash of toc.pol matches the reference stored in toc.asr + +\- Checking that all MAC tags listed in toc.pol correspond to existing add-secret request files + +\- Ensuring that all add-secret request files are present and unmodified + +.RE +The tool can run in one of two modes: +.RS 4 +\- \fBNormal mode\fP: Validates add\-secret requests and adds the defined secrets to the SEL guest (z/Architecture only) + +\- \fBDry\-run mode\fP: Validates the request structure without adding secrets; useful for verification on non\-SEL systems + +.RE +.SH OPTIONS +.PP +\-t, \-\-toc +.RS 4 +Specify the table\-of\-contents add\-secret request file toc.asr, which serves as the root of the EBC +resources. This file must contain a reference to toc.pol in its user data field, +as generated by \fBpvsecret\fR with the \fB\-\-policy\fR option. The toc.asr +file cryptographically links to the policy file by using a relative path and +SHA\-512 hash, ensuring the integrity of the complete EBC structure. +.RE +.RE +.PP +\-\-dry\-run +.RS 4 +Validate the EBC structure without adding the add\-secret requests to the +ultravisor. Use this option to verify the integrity of the generated +policy file and the links between add\-secret request files and the policy. +This mode can be run on non\-SEL guests to validate the +structure before deployment. When \fB\-\-dry\-run\fR is specified, the tool +performs all cryptographic verifications but skips the actual addition of +secrets to the ultravisor secret store. +.RE +.RE +.PP +\-\-version +.RS 4 +Print version information and exit. +.RE +.RE +.PP +\-h, \-\-help +.RS 4 +Print help information. +.RE +.RE + +.SH FILES +.PP +The \fBpvebc\fR tool operates on the following file types (actual names may +differ): +.PP +\fBtoc.asr\fR +.RS 4 +The root add\-secret request file that contains a reference to the table\-of\-content \fBtoc.pol\fR in its +user data field. This file is generated by using \fBpvsecret create\fR with the +\fB\-\-policy\fR option. +.RE +.PP +\fBtoc.pol\fR +.RS 4 +The policy file containing a newline\-separated list of AES\-GCM authentication +tags (MAC tags) in hexadecimal format. Each entry corresponds to one +add\-secret request file. +This file is generated by using \fBpvsecret create\fR with the +\fB\-\-toc\-policy\fR option. +.RE +.PP +\fB*.asr\fR +.RS 4 +Individual add\-secret request files that contain the actual secrets to be added to +the SEL guest. Each add\-secret request file includes an AES\-GCM authentication tag (the last 16 bytes), which +provides integrity protection. +.RE + +.SH EXIT STATUS +.PP +\fBpvebc\fR returns the following exit codes: +.PP +\fB0\fR +.RS 4 +Success. All validations passed and secrets were added (if not in dry\-run mode). +.RE +.PP +\fB1\fR +.RS 4 +Failure. An error occurred during validation or secret addition. Error details +are printed to stderr. +.RE + +.SH NOTES +.PP +\- All file paths in a policy reference must be relative to the directory +that contains the referencing file. +.PP +\- On z/Architecture systems, the tool requires access to the ultravisor device +(\fB/dev/uv\fR) to add secrets. +.PP + +.SH "SEE ALSO" +.sp +\fBpvsecret\fR(1), \fBpvsecret\-create\fR(1), \fBpvsecret\-add\fR(1) diff --git a/rust/pvebc/src/cli.rs b/rust/pvebc/src/cli.rs new file mode 100644 index 00000000..62c4eac5 --- /dev/null +++ b/rust/pvebc/src/cli.rs @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. + +use std::{path::PathBuf, sync::OnceLock}; + +use clap::{ArgAction, Parser, ValueHint}; + +static VERSION: OnceLock = OnceLock::new(); + +/// The pvebc command processes an AddSecretRequest file (toc.asr) that defines the root of EBC +/// resources. It validates references to the associated toc.pol policy and manages their addition, +/// with options for verification and dry-run execution. +#[derive(Parser)] +#[command(long_version=ver(), disable_version_flag(true))] +pub struct Cli { + /// Print version information and exit. + #[arg(long, action=ArgAction::Version)] + version: (), + + /// Specifies the toc.asr which is the root of the EBC resources + /// + /// Specify the add-secret request file toc.asr that serves as the root of the EBC resources. + /// Its user data must contain a reference to toc.pol as generated by pvsecret with the --policy + /// option. + #[arg(short, long, value_name = "FILE", value_hint = ValueHint::FilePath)] + pub toc: PathBuf, + + /// Prevents adding of the AddSecretRequest + /// + /// Do not add the AddSecretRequest. Use this option to validate a generated policy. It can be + /// run on a non-SEL guest to verify ASR-to-policy links and the associated policy toc.pol. + #[arg(long)] + pub dry_run: bool, +} + +fn ver() -> &'static str { + VERSION.get_or_init(|| utils::tools_version_fmt!(2026)) +} diff --git a/rust/pvebc/src/ebc_utils.rs b/rust/pvebc/src/ebc_utils.rs new file mode 100644 index 00000000..1912c6ba --- /dev/null +++ b/rust/pvebc/src/ebc_utils.rs @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. + +use anyhow::{bail, Context, Result}; +use pv_core::misc::open_file; +use std::{io::Read, path::Path}; + +/// Length of the MAC tag in bytes (last 16 bytes of AddSecretRequest files) +pub const MAC_TAG_LEN: usize = 16; + +/// Opens a file and returns a boxed reader +pub fn get_reader_from_filepath>(filepath: P) -> Result> { + Ok(Box::new(open_file(filepath)?)) +} + +/// Get reader from &Path with additional context on error +pub fn get_reader(filepath: &Path) -> Result> { + get_reader_from_filepath(filepath) + .with_context(|| format!("unable to get reader from {:?}", filepath)) +} + +/// Read all data from a reader into a Vec +pub fn get_data(rd_in: &mut Box) -> Result> { + let mut data_in = Vec::new(); + rd_in + .read_to_end(&mut data_in) + .context("Cannot read input file")?; + + Ok(data_in) +} + +/// Extract the MAC tag (last 16 bytes) from an AddSecretRequest file +/// +/// # Errors +/// +/// Returns an error if: +/// - The file cannot be read +/// - The file is smaller than MAC_TAG_LEN bytes +pub fn get_mac_tag(filepath: &Path) -> Result> { + let mut rd_in = get_reader(filepath)?; + let data_in = get_data(&mut rd_in)?; + + if data_in.len() < MAC_TAG_LEN { + bail!( + "File {:?} too small to contain MAC tag (expected at least {} bytes, got {})", + filepath, + MAC_TAG_LEN, + data_in.len() + ); + } + + Ok(data_in[data_in.len() - MAC_TAG_LEN..].to_vec()) +} diff --git a/rust/pvebc/src/main.rs b/rust/pvebc/src/main.rs new file mode 100644 index 00000000..0de6c1c6 --- /dev/null +++ b/rust/pvebc/src/main.rs @@ -0,0 +1,369 @@ +// SPDX-License-Identifier: MIT +// +// Copyright IBM Corp. + +//! # pvebc - Protected Virtualization Early Boot Customization Tool +//! +//! This tool processes AddSecretRequest files (toc.asr) that define the root of EBC +//! (Early Boot Customization) resources. It validates references to associated +//! toc.pol policies and manages their addition to the system. +//! +//! ## Features +//! +//! - Validates AddSecretRequest files and their associated policies +//! - Verifies cryptographic hashes of policy files using SHA-256 +//! - Supports dry-run mode for validation without system changes +//! - MAC tag validation for AddSecretRequest files + +#![allow(missing_docs)] + +mod cli; +mod ebc_utils; + +#[cfg(target_arch = "s390x")] +use pv_core::uv::{AddCmd, UvDevice}; +#[cfg(target_arch = "s390x")] +use utils::get_reader_from_cli_file_arg; + +use anyhow::{bail, Context, Error, Result}; +use clap::Parser; +use pv_core::{ + misc::{decode_hex, encode_hex}, + PolicyReference, +}; +use std::{ + fs::{self, File}, + io::{BufRead, Read}, + path::{Path, PathBuf}, + process::ExitCode, + str::from_utf8, +}; +// Don't use openssl here because this tool is intended to run in the initramfs +// phase of the boot and there we don't want to dynamically link against a C lib +use sha2::{self, Digest}; +use zerocopy::TryFromBytes; + +use crate::cli::Cli; +use crate::ebc_utils::{get_data, get_mac_tag, get_reader}; + +/// Offset in bytes where user data starts in an ASRCB v1 structure +const V1_USER_DATA_OFFS: usize = 536; +/// Size in bytes of the user data field in an ASRCB +const USER_DATA_SIZE: usize = 512; + +/// Validates and resolves a policy name relative to a base directory +/// +/// # Errors +/// Returns an error if path traversal is detected or the resolved path +/// is outside the base directory +fn validate_and_resolve_policy_path(base: &Path, name: &str) -> Result { + // Validate path to prevent directory traversal + if name.contains("..") || name.starts_with('/') { + bail!("Invalid policy name: path traversal detected in '{}'", name); + } + + let resolved = base.join(name); + + // Ensure the resulting path is within the expected directory + if !resolved.starts_with(base) { + bail!( + "Policy path '{}' is outside the expected directory", + resolved.display() + ); + } + + Ok(resolved) +} + +/// Extracts and validates the policy name from a PolicyReference +/// +/// # Errors +/// Returns an error if the policy name contains invalid UTF-8 or is empty +fn extract_policy_name(policy_ref: &PolicyReference) -> Result { + let name = from_utf8(&policy_ref.name) + .context("Policy name contains invalid UTF-8")? + .trim_matches('\0') + .to_string(); + + if name.is_empty() { + bail!("Policy name is empty"); + } + + Ok(name) +} + +/// 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, Error> { + let mut hasher = sha2::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.finalize().to_vec()) +} + +/// Extract the user data from an ASRCB +/// +/// # Errors +/// +/// Returns an error if the ASRCB is too small to contain user data +fn get_user_data(asrcb: &[u8]) -> Result>> { + if asrcb.len() < V1_USER_DATA_OFFS + USER_DATA_SIZE { + bail!( + "ASRCB too small (expected at least {} bytes, got {})", + V1_USER_DATA_OFFS + USER_DATA_SIZE, + asrcb.len() + ); + } + + let user_data = &asrcb[V1_USER_DATA_OFFS..V1_USER_DATA_OFFS + USER_DATA_SIZE]; + Ok(Some(user_data.to_vec())) +} + +/// Get user data from AddSecretRequest +fn verify_user_data(filepath: &Path) -> Result>> { + let mut rd_in = get_reader(filepath)?; + let data_in = get_data(&mut rd_in)?; + + get_user_data(&data_in).context("Could not verify the Add-secret request") +} + +/// Adds the given AddSecretRequest (if dryrun == false) and parses the contained user data +/// +/// # Returns +/// +/// On Success returns the PolicyReference parsed from the given AddSecretRequest +/// +/// # Errors +/// +/// returns an error if +/// - unable to open UvDevice +/// - reader from path fails +/// - adding of ASR fails +/// - verify_user_data fails +/// - unable to convert user data to PolicyReference +fn asr_to_pol_ref(filepath: &Path, dryrun: bool) -> Result> { + print!("Add-Secret-Request: \"{}\"", filepath.display()); + if !dryrun { + #[cfg(target_arch = "s390x")] + { + let uv = UvDevice::open()?; + let mut rd_in = get_reader_from_cli_file_arg(filepath)?; + let mut cmd = AddCmd::new(&mut rd_in) + .context(format!("Processing input file {:?}", filepath.to_str()))?; + uv.send_cmd(&mut cmd)?; + println!(); + } + #[cfg(not(target_arch = "s390x"))] + { + println!(" (skip adding: not running on s390x architecture)"); + } + } else { + println!(" (skip adding: dry-run mode)"); + } + + Ok(match verify_user_data(filepath)? { + Some(ud) => { + let ret = PolicyReference::try_read_from_bytes(&ud).map_err(|e| { + anyhow::anyhow!("Failed to parse PolicyReference from user data: {:?}", e) + })?; + println!(" Reference: {}", ret); + Some(ret) + } + None => None, + }) +} + +/// Read the mac tag list of the toc policy, parse and verify every tag +fn execute_toc_pol(path: &Path, toc_pol_ref: PolicyReference) -> Result> { + let name = extract_policy_name(&toc_pol_ref)?; + let toc_path = validate_and_resolve_policy_path(path, &name)?; + let mut ret: Vec = Vec::new(); + let mut rd_in = get_reader(&toc_path)?; + let data_in = get_data(&mut rd_in)?; + + let macs = data_in.lines(); + let base = get_base_dir(&toc_path); + + println!("Mac tags in {:?}:", toc_path); + for mac in macs { + let mut mac_tag = Vec::new(); + let mac_tag_ref = match mac { + Ok(s) => s, + _ => continue, + }; + print!(" {}", mac_tag_ref); + + let entries = fs::read_dir(base)?; + for entry in entries { + let path = entry?; + let filepath = path.path(); + let ext = match filepath.extension() { + Some(e) => e, + None => continue, + }; + if ext == "asr" { + mac_tag = get_mac_tag(filepath.as_path())?; + + if mac_tag == decode_hex(&mac_tag_ref)? { + println!(" -> {}", filepath.display()); + ret.push(filepath.clone()); + + break; + } + } + } + + if mac_tag != decode_hex(&mac_tag_ref)? { + bail!( + "No ASR with mac tag \"{}\" found in {:?}", + mac_tag_ref, + base + ); + } + } + + println!(); + + Ok(ret) +} + +/// Checks whether the given hash in polref matches the actual hash of the referenced file +fn verify_policy(basename: &Path, polref: PolicyReference) -> Result<()> { + let name = extract_policy_name(&polref)?; + let filepath = validate_and_resolve_policy_path(basename, &name)?; + println!("Verify \"{}\"", filepath.display()); + + println!(" Referenced: {}", encode_hex(polref.hash)); + let f = File::open(filepath.as_path())?; + + let check_hash = sha256_hash(f)?; + println!(" Calculated: {}", encode_hex(&check_hash)); + + if check_hash != polref.hash { + bail!( + "{:?} ({}) does not match expected hash ({})", + filepath, + encode_hex(&check_hash), + encode_hex(polref.hash) + ); + } + + Ok(()) +} + +/// For every AddSecretRequest find the corresponding policy +fn execute_asrs(asrs: Vec, dryrun: bool) -> Result<()> { + // loop over ASRs + for asr in asrs { + // get the referenced policy + let pol_ref = match asr_to_pol_ref(asr.as_path(), dryrun)? { + Some(pr) => pr, + None => continue, + }; + + if pol_ref.hash.iter().all(|&b| b == 0) { + continue; + } + + let base = get_base_dir(asr.as_path()); + let name = extract_policy_name(&pol_ref)?; + let _pol_path = validate_and_resolve_policy_path(base, &name)?; + + // verify the integrity of the referenced policy + verify_policy(base, pol_ref)?; + } + + Ok(()) +} + +/// Get the parent directory of the given file +fn get_base_dir(filepath: &Path) -> &Path { + match filepath.parent() { + Some(p) => p, + None => Path::new(""), + } +} + +/// Print a given error and return the failure exit code +fn error_to_exit_code(err: Error) -> ExitCode { + eprintln!("Error: {}", err); + + ExitCode::FAILURE +} + +/// main function +pub fn main() -> ExitCode { + let opt: Cli = Cli::parse(); + + let dryrun = opt.dry_run; + + if dryrun { + println!("Dry-run mode detected, skipping secret addition"); + println!(); + } + + // get toc asr filepath - wrapper script has already copied files to tmpfs + let toc_asr = &opt.toc; + let basename = match toc_asr.parent() { + Some(p) => p, + None => { + return error_to_exit_code(Error::msg("Unable to get directory from specified path")) + } + }; + + // get the PolicyReference from toc.asr to toc.pol + let toc_pol = match asr_to_pol_ref(toc_asr, dryrun) { + Ok(o) => match o { + Some(r) => r, + None => { + return error_to_exit_code(Error::msg( + "There is no linked policy in the supplied ASR", + )) + } + }, + Err(e) => return error_to_exit_code(e), + }; + + // verify the integrity on the referenced toc.pol + if let Err(e) = verify_policy(basename, toc_pol) { + return error_to_exit_code(e); + } + + println!(); + + // get and verify ASRs from mac list of toc policy + let asr_list = match execute_toc_pol(basename, toc_pol) { + Ok(v) => v, + Err(e) => return error_to_exit_code(e), + }; + + // execute ASRs + if let Err(e) = execute_asrs(asr_list, dryrun) { + return error_to_exit_code(e); + } + + ExitCode::SUCCESS +}