pvimg: info: Add '--format text' support

Add human-readable output format to 'pvimg info' command. The format
'text:normal' shows only basic information about the Secure Execution
header, but skips the keys and other binary data; the format 'text:full'
shows everything.

Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Marc Hartmayer <marc@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
This commit is contained in:
Marc Hartmayer
2026-01-30 12:25:46 +01:00
committed by Jan Höppner
parent 1d2a89b387
commit 126ba7e336
8 changed files with 262 additions and 8 deletions

View File

@@ -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<Self, Self::Err> {
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,

View File

@@ -51,6 +51,14 @@ pub fn info(opt: &InfoArgs) -> Result<OwnExitCode> {
};
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<OwnExitCode> {
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()?;

View File

@@ -2,10 +2,30 @@
//
// Copyright IBM Corp. 2024
use std::fmt::Display;
use pv::PvCoreError;
use crate::error::{Error, Result};
pub fn display_indented<T: Display>(
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::<Vec<_>>()
.join("\n")
}
/// Rounds up the given `value` to a multiple of `multiple`.
///
/// # Errors

View File

@@ -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();

View File

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

View File

@@ -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<K: AsRef<PKeyRef<Public>>>(&self, key: K) -> Result<bool> {
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;
}

View File

@@ -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<EcPubKeyCoord> for EcPubKeyCoordV1 {
fn from(value: EcPubKeyCoord) -> Self {
@@ -84,6 +92,12 @@ pub struct BinaryKeySlotV1 {
}
static_assert!(size_of::<BinaryKeySlotV1>() == 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<Vec<u8>> for BinaryKeySlotV1 {
type Error = Error;

View File

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