diff --git a/rust/pvimg/src/cli.rs b/rust/pvimg/src/cli.rs index a183dbce..9fecce1c 100644 --- a/rust/pvimg/src/cli.rs +++ b/rust/pvimg/src/cli.rs @@ -8,8 +8,8 @@ use std::string::ToString; use std::{env, ffi::OsStr, path::PathBuf}; use clap::{ - builder::PossibleValue, Arg, ArgGroup, Args, Command, CommandFactory, Parser, ValueEnum, - ValueHint, + builder::{PossibleValue, TypedValueParser}, + Arg, ArgGroup, Args, Command, CommandFactory, Parser, ValueEnum, ValueHint, }; use log::warn; use utils::{CertificateOptions, DeprecatedVerbosityOptions}; @@ -214,6 +214,8 @@ pub struct CreateBootImageLegacyFlags { #[non_exhaustive] #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug)] pub enum OutputFormatKind { + /// Human-readable, unstable text format + Text, /// JSON format. Json, } @@ -221,6 +223,7 @@ pub enum OutputFormatKind { impl Display for OutputFormatKind { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { + Self::Text => write!(f, "human-readable"), Self::Json => write!(f, "JSON"), } } @@ -231,6 +234,7 @@ impl FromStr for OutputFormatKind { fn from_str(s: &str) -> Result { match s { + "text" => Ok(Self::Text), "json" => Ok(Self::Json), _ => Err(format!("Invalid output format: {s}")), } @@ -241,6 +245,8 @@ impl FromStr for OutputFormatKind { pub enum OutputFormatVariant { /// Default Default, + /// Full + Full, /// Minified Minify, /// Pretty @@ -256,7 +262,7 @@ pub struct OutputFormatSpec { #[derive(Clone, Default)] struct OutputFormatSpecParser; -impl clap::builder::TypedValueParser for OutputFormatSpecParser { +impl TypedValueParser for OutputFormatSpecParser { type Value = OutputFormatSpec; fn parse_ref( @@ -288,14 +294,15 @@ impl clap::builder::TypedValueParser for OutputFormatSpecParser { (_, None) => OutputFormatVariant::Default, (_, Some("default")) => OutputFormatVariant::Default, + (OutputFormatKind::Text, Some("full")) => OutputFormatVariant::Full, (OutputFormatKind::Json, Some("pretty")) => OutputFormatVariant::Pretty, (OutputFormatKind::Json, Some("minify")) => OutputFormatVariant::Minify, - (OutputFormatKind::Json, Some(other)) => { + (_, Some(other)) => { let mut err = clap::error::Error::new(clap::error::ErrorKind::ValueValidation).with_cmd(cmd); err.insert( clap::error::ContextKind::InvalidArg, - clap::error::ContextValue::String(arg_name), + clap::error::ContextValue::String(arg_name.clone()), ); err.insert( clap::error::ContextKind::InvalidValue, diff --git a/rust/pvimg/src/cmd/info.rs b/rust/pvimg/src/cmd/info.rs index a1873318..f118bac7 100644 --- a/rust/pvimg/src/cmd/info.rs +++ b/rust/pvimg/src/cmd/info.rs @@ -51,6 +51,14 @@ pub fn info(opt: &InfoArgs) -> Result { }; match opt.format { + OutputFormatSpec { + kind: OutputFormatKind::Text, + variant: OutputFormatVariant::Default, + } => write!(output, "{se_hdr}").context("Cannot generate the human readable output")?, + OutputFormatSpec { + kind: OutputFormatKind::Text, + variant: OutputFormatVariant::Full, + } => write!(output, "{se_hdr:#}").context("Cannot generate the human readable output")?, OutputFormatSpec { kind: OutputFormatKind::Json, variant, @@ -63,10 +71,14 @@ pub fn info(opt: &InfoArgs) -> Result { OutputFormatVariant::Default | OutputFormatVariant::Pretty => { serde_json::to_writer_pretty(&mut output, &doc)?; } + OutputFormatVariant::Full => { + unreachable!("we already validate the variant in the outer match") + } } // Make sure the output ends with a new line writeln!(&mut output)? } + _ => unreachable!(), } output.flush()?; diff --git a/rust/pvimg/src/pv_utils/misc.rs b/rust/pvimg/src/pv_utils/misc.rs index 0328a312..322af1c1 100644 --- a/rust/pvimg/src/pv_utils/misc.rs +++ b/rust/pvimg/src/pv_utils/misc.rs @@ -2,10 +2,30 @@ // // Copyright IBM Corp. 2024 +use std::fmt::Display; + use pv::PvCoreError; use crate::error::{Error, Result}; +pub fn display_indented( + f: &mut std::fmt::Formatter<'_>, + s: &T, + width: usize, +) -> String { + let indentation = " ".repeat(width); + let value = if f.alternate() { + format!("{s:#}") + } else { + format!("{s}") + }; + value + .lines() + .map(|l| format!("{indentation}{l}")) + .collect::>() + .join("\n") +} + /// Rounds up the given `value` to a multiple of `multiple`. /// /// # Errors diff --git a/rust/pvimg/src/pv_utils/psw.rs b/rust/pvimg/src/pv_utils/psw.rs index 4b77edd3..a33d5f9e 100644 --- a/rust/pvimg/src/pv_utils/psw.rs +++ b/rust/pvimg/src/pv_utils/psw.rs @@ -2,6 +2,8 @@ // // Copyright IBM Corp. 2024 +use std::fmt::Display; + use deku::{ctx::Endian, DekuRead, DekuWrite}; use pv::request::Zeroize; use serde::{Deserialize, Serialize}; @@ -23,6 +25,13 @@ pub struct PSW { pub addr: u64, } +impl Display for PSW { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "address: {:#016x}", self.addr)?; + writeln!(f, "mask: {:#016x}", self.mask) + } +} + impl Zeroize for PSW { fn zeroize(&mut self) { self.mask.zeroize(); diff --git a/rust/pvimg/src/pv_utils/se_hdr/brb.rs b/rust/pvimg/src/pv_utils/se_hdr/brb.rs index 8756757e..811f01c7 100644 --- a/rust/pvimg/src/pv_utils/se_hdr/brb.rs +++ b/rust/pvimg/src/pv_utils/se_hdr/brb.rs @@ -2,6 +2,7 @@ // // Copyright IBM Corp. 2024 use std::{ + fmt::Display, io::{Read, Seek, SeekFrom}, mem::size_of, }; @@ -24,6 +25,7 @@ use crate::{ misc::PAGESIZE, pv_utils::{ error::{Error, Result}, + misc::display_indented, serializing::{serde_hex_array, serialize_to_bytes}, uvdata::{ AeadCipherTrait, AeadDataTrait, AeadPlainDataTrait, KeyExchangeTrait, UvDataPlainTrait, @@ -69,6 +71,18 @@ pub enum SeHdrVersion { V1 = 0x100, } +impl Display for SeHdrVersion { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + SeHdrVersion::V1 => "1", + } + ) + } +} + #[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] #[serde(tag = "kind")] #[serde(rename_all = "snake_case")] @@ -85,6 +99,37 @@ pub enum SeH { }, } +impl Display for SeH { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match &self { + SeH::DecryptedSeHdr { verified, se_hdr } => { + let verified_s = if *verified { + ", integrity and authenticity verified" + } else { + "" + }; + if f.alternate() { + write!(f, "decrypted{verified_s} {se_hdr:#}") + } else { + write!(f, "decrypted{verified_s} {se_hdr}") + } + } + SeH::SeHdr { verified, se_hdr } => { + let verified_s = if *verified { + "integrity and authenticity verified " + } else { + "" + }; + if f.alternate() { + write!(f, "{verified_s}{se_hdr:#}") + } else { + write!(f, "{verified_s}{se_hdr}") + } + } + } + } +} + #[repr(C)] #[derive(Debug, Clone, PartialEq, Eq, DekuRead, DekuWrite, Serialize, Deserialize)] #[deku(endian = "endian", ctx = "endian: Endian", ctx_default = "Endian::Big")] @@ -118,6 +163,12 @@ impl SeHdrCommon { } } +impl Display for SeHdrCommon { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "version: {}", self.version) + } +} + #[derive(Clone, PartialEq, Eq, Debug, DekuRead, DekuWrite, Serialize, Deserialize)] #[deku(endian = "Endian::Big")] /// Secure Execution header structure @@ -130,6 +181,17 @@ pub struct SeHdr { pub data: SeHdrVersioned, } +impl Display for SeHdr { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "Secure Execution header")?; + // Support verbose mode if the `alternate` (`{:#}`) flag is used. + let value = display_indented(f, &self.common, 2); + writeln!(f, "{value}")?; + let data = display_indented(f, &self.data, 2); + writeln!(f, "{data}") + } +} + #[derive(Clone, PartialEq, Eq, Debug, DekuRead, DekuWrite, Serialize, Deserialize)] #[deku(endian = "Endian::Big")] /// Plain data Secure Execution header structure @@ -141,6 +203,16 @@ pub struct SeHdrPlain { pub data: SeHdrData, } +impl Display for SeHdrPlain { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "Secure Execution header:")?; + let value = display_indented(f, &self.common, 2); + writeln!(f, "{value}")?; + let value = display_indented(f, &self.data, 2); + writeln!(f, "{value}") + } +} + #[enum_dispatch(AeadCipherTrait, AeadDataTrait, KeyExchangeTrait)] #[derive(Clone, PartialEq, Eq, Debug, DekuRead, DekuWrite, Serialize, Deserialize)] #[serde(untagged)] @@ -150,6 +222,20 @@ pub enum SeHdrVersioned { SeHdrBinV1(SeHdrBinV1), } +impl Display for SeHdrVersioned { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + SeHdrVersioned::SeHdrBinV1(se_hdr_bin_v1) => { + if f.alternate() { + write!(f, "{se_hdr_bin_v1:#}") + } else { + write!(f, "{se_hdr_bin_v1}") + } + } + } + } +} + #[enum_dispatch( AeadCipherTrait, AeadPlainDataTrait, @@ -164,6 +250,20 @@ pub enum SeHdrData { SeHdrDataV1(SeHdrDataV1), } +impl Display for SeHdrData { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::SeHdrDataV1(data_v1) => { + if f.alternate() { + write!(f, "{data_v1:#}") + } else { + write!(f, "{data_v1}") + } + } + } + } +} + impl AeadCipherBuilderTrait for SeHdrData { fn set_iv(&mut self, iv: &[u8]) -> Result<()> { match self { diff --git a/rust/pvimg/src/pv_utils/se_hdr/hdr_v1.rs b/rust/pvimg/src/pv_utils/se_hdr/hdr_v1.rs index cbdcc5bf..cf2a56fe 100644 --- a/rust/pvimg/src/pv_utils/se_hdr/hdr_v1.rs +++ b/rust/pvimg/src/pv_utils/se_hdr/hdr_v1.rs @@ -1,8 +1,12 @@ // SPDX-License-Identifier: MIT // // Copyright IBM Corp. 2024 -use std::mem::{size_of, size_of_val}; +use std::{ + fmt::Display, + mem::{size_of, size_of_val}, +}; +use base64::prelude::*; use deku::{ctx::Endian, prelude::*}; use openssl::{ nid::Nid, @@ -15,6 +19,7 @@ use pv::request::{ Zeroize, SHA_512_HASH_LEN, }; use serde::{Deserialize, Serialize}; +use utils::HexSlice; use super::keys::phkh_v1; use crate::{ @@ -22,6 +27,7 @@ use crate::{ misc::PAGESIZE, pv_utils::{ error::Result, + misc::display_indented, se_hdr::{ brb::{ ComponentMetadata, ComponentMetadataV1, SeHdrCommon, SeHdrConfBuilderTrait, @@ -81,6 +87,34 @@ impl SeHdrAadV1 { const KEY_TYPE: SymKeyType = SymKeyType::Aes256Gcm; } +impl Display for SeHdrAadV1 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + // Support verbose mode if the `alternate` (`{:#}`) flag is used. + if f.alternate() { + writeln!(f, "size: {} bytes", self.sehs)?; + writeln!(f, "number of key slots: {}", self.nks)?; + } + writeln!(f, "key slots:")?; + for s in &self.keyslots { + writeln!(f, " - {s}")?; + } + if f.alternate() { + let value = display_indented(f, &self.cust_pub_key, 2); + writeln!(f, "customer public key:\n{value}",)?; + writeln!(f, "number of component pages: {}", self.nep)?; + writeln!(f, "components content hash: {:}", HexSlice::from(&self.pld))?; + writeln!(f, "components address hash: {:}", HexSlice::from(&self.ald))?; + writeln!(f, "components tweak hash: {:}", HexSlice::from(&self.tld))?; + } + writeln!( + f, + "plaintext control flags:\n{}", + PlaintextControlFlagsV1::from(self.pcf) + )?; + Ok(()) + } +} + impl KeyExchangeTrait for SeHdrAadV1 { fn contains>>(&self, key: K) -> Result { let phkh = phkh_v1(key)?; @@ -146,6 +180,25 @@ impl Zeroize for SeHdrConfV1 { } } +impl Display for SeHdrConfV1 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!( + f, + "secret control flags:\n{}", + SecretControlFlagsV1::from(self.scf) + )?; + + // Support verbose mode if the `alternate` (`{:#}`) flag is used. + if f.alternate() { + writeln!(f, "CCK: {:}", HexSlice::from(self.cck.value()))?; + writeln!(f, "XTS key: {:}", HexSlice::from(self.xts.value()))?; + let psw = display_indented(f, &self.psw, 2); + writeln!(f, "PSW:\n{psw}")?; + } + Ok(()) + } +} + #[derive(Default, PartialEq, Eq, Debug, Clone, DekuRead, DekuWrite, Serialize, Deserialize)] #[deku(endian = "endian", ctx = "endian: Endian", ctx_default = "Endian::Big")] pub struct SeHdrTagV1 { @@ -153,6 +206,12 @@ pub struct SeHdrTagV1 { tag: [u8; SymKeyType::AES_256_GCM_TAG_LEN], } +impl Display for SeHdrTagV1 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:}", HexSlice::from(&self.tag)) + } +} + mod ser_confidential_confv1 { use pv::request::Confidential; use serde::{Deserialize, Deserializer, Serialize, Serializer}; @@ -190,6 +249,21 @@ pub struct SeHdrDataV1 { tag: SeHdrTagV1, } +impl Display for SeHdrDataV1 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + // Support verbose mode if the `alternate` (`{:#}`) flag is used. + if f.alternate() { + write!(f, "{:#}", self.aad)?; + write!(f, "{:#}", self.data.value())?; + writeln!(f, "GCM tag: {}", self.tag)?; + } else { + write!(f, "{}", self.aad)?; + write!(f, "{}", self.data.value())?; + } + Ok(()) + } +} + /// Reads from a `reader` and creates a confidential `SeHdrConfV1`. /// /// # Errors @@ -481,6 +555,24 @@ impl SeHdrBinV1 { } } +impl Display for SeHdrBinV1 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + // Support verbose mode if the `alternate` (`{:#}`) flag is used. + if f.alternate() { + write!(f, "{:#}", self.aad)?; + writeln!( + f, + "encrypted data: {:#}", + BASE64_STANDARD.encode(&self.cipher_data) + )?; + writeln!(f, "GCM tag: {:#}", self.tag)?; + } else { + write!(f, "{}", self.aad)?; + } + Ok(()) + } +} + impl UvDataTrait for SeHdrBinV1 { type P = SeHdrDataV1; } diff --git a/rust/pvimg/src/pv_utils/se_hdr/keys.rs b/rust/pvimg/src/pv_utils/se_hdr/keys.rs index 67ad4041..abca0cc0 100644 --- a/rust/pvimg/src/pv_utils/se_hdr/keys.rs +++ b/rust/pvimg/src/pv_utils/se_hdr/keys.rs @@ -2,8 +2,9 @@ // // Copyright IBM Corp. 2024 -use std::mem::size_of; +use std::{fmt::Display, mem::size_of}; +use base64::prelude::*; use deku::{ctx::Endian, DekuRead, DekuWrite}; use openssl::{ hash::{hash, MessageDigest}, @@ -11,6 +12,7 @@ use openssl::{ }; use pv::{request::EcPubKeyCoord, static_assert}; use serde::{Deserialize, Serialize}; +use utils::HexSlice; use crate::{ error::{Error, Result}, @@ -35,6 +37,12 @@ pub struct EcPubKeyCoordV1 { pub coord: [u8; 160], } +impl Display for EcPubKeyCoordV1 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "coordinate: {}", BASE64_STANDARD.encode(self.coord)) + } +} + #[allow(clippy::fallible_impl_from)] impl From for EcPubKeyCoordV1 { fn from(value: EcPubKeyCoord) -> Self { @@ -84,6 +92,12 @@ pub struct BinaryKeySlotV1 { } static_assert!(size_of::() == 80); +impl Display for BinaryKeySlotV1 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "target key hash: {:}", HexSlice::from(&self.phkh)) + } +} + impl TryFrom> for BinaryKeySlotV1 { type Error = Error; diff --git a/rust/pvimg/src/pv_utils/secured_comp.rs b/rust/pvimg/src/pv_utils/secured_comp.rs index 3eec28e7..2f25842f 100644 --- a/rust/pvimg/src/pv_utils/secured_comp.rs +++ b/rust/pvimg/src/pv_utils/secured_comp.rs @@ -27,7 +27,7 @@ use crate::pv_utils::{ /// Operation mode for component preparation. #[allow(unused)] -#[derive(Copy, Clone, Eq, PartialEq)] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum Mode { /// Encrypt the component data Encrypt,