diff --git a/rust/pvimg/src/cli.rs b/rust/pvimg/src/cli.rs index de6ce6ed..a183dbce 100644 --- a/rust/pvimg/src/cli.rs +++ b/rust/pvimg/src/cli.rs @@ -2,9 +2,15 @@ // // Copyright IBM Corp. 2024 -use std::{env, fmt::Display, path::PathBuf}; +use std::fmt::Display; +use std::str::FromStr; +use std::string::ToString; +use std::{env, ffi::OsStr, path::PathBuf}; -use clap::{ArgGroup, Args, Command, CommandFactory, Parser, ValueEnum, ValueHint}; +use clap::{ + builder::PossibleValue, Arg, ArgGroup, Args, Command, CommandFactory, Parser, ValueEnum, + ValueHint, +}; use log::warn; use utils::{CertificateOptions, DeprecatedVerbosityOptions}; @@ -206,21 +212,112 @@ pub struct CreateBootImageLegacyFlags { } #[non_exhaustive] -#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug)] -pub enum OutputFormat { +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug)] +pub enum OutputFormatKind { /// JSON format. Json, } -impl Display for OutputFormat { +impl Display for OutputFormatKind { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{}", - match self { - Self::Json => "JSON", + match self { + Self::Json => write!(f, "JSON"), + } + } +} + +impl FromStr for OutputFormatKind { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "json" => Ok(Self::Json), + _ => Err(format!("Invalid output format: {s}")), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum OutputFormatVariant { + /// Default + Default, + /// Minified + Minify, + /// Pretty + Pretty, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct OutputFormatSpec { + pub kind: OutputFormatKind, + pub variant: OutputFormatVariant, +} + +#[derive(Clone, Default)] +struct OutputFormatSpecParser; + +impl clap::builder::TypedValueParser for OutputFormatSpecParser { + type Value = OutputFormatSpec; + + fn parse_ref( + &self, + cmd: &Command, + arg: Option<&Arg>, + value: &OsStr, + ) -> Result { + let s = value.to_string_lossy(); + let mut parts = s.splitn(2, ':'); + let arg_name = arg.unwrap().get_id().to_string(); + let kind_s = parts.next().unwrap().to_ascii_lowercase(); + let variant_s = parts.next().map(|x| x.to_ascii_lowercase()); + let kind = kind_s.as_str().parse().map_err(|_| { + 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.clone()), + ); + err.insert( + clap::error::ContextKind::InvalidValue, + clap::error::ContextValue::String(kind_s.to_string()), + ); + err + })?; + + let variant = match (kind, variant_s.as_deref()) { + (_, None) => OutputFormatVariant::Default, + (_, Some("default")) => OutputFormatVariant::Default, + + (OutputFormatKind::Json, Some("pretty")) => OutputFormatVariant::Pretty, + (OutputFormatKind::Json, Some("minify")) => OutputFormatVariant::Minify, + (OutputFormatKind::Json, 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), + ); + err.insert( + clap::error::ContextKind::InvalidValue, + clap::error::ContextValue::String(format!("{kind_s}:{other}")), + ); + Err(err)? } - ) + }; + + Ok(OutputFormatSpec { kind, variant }) + } + + // This is used for shell completion suggestions for `--format` + fn possible_values(&self) -> Option + '_>> { + use clap::builder::PossibleValue as PV; + + let vals: Vec = vec![ + PV::new("json").help("Pretty-printed machine-readable JSON"), + PV::new("json:pretty").help("Pretty-printed machine-readable JSON"), + PV::new("json:minify").help("Minified machine-readable JSON"), + ]; + Some(Box::new(vals.into_iter())) } } @@ -236,9 +333,9 @@ pub struct InfoArgs { #[clap(flatten)] pub input: SeImgInputArgs, - /// The output format - #[arg(long, value_enum)] - pub format: OutputFormat, + /// Output format + #[arg(long, value_parser=OutputFormatSpecParser::default())] + pub format: OutputFormatSpec, /// Use the key in FILE to decrypt the Secure Execution header. /// @@ -793,6 +890,30 @@ mod test { CliOption::new("image", ["/dev/null"]), ], )), + flat_map_collect(insert( + args.clone(), + vec![ + CliOption::new("hdr-key", ["--key", "/dev/null"]), + CliOption::new("format", ["--format=json:default"]), + CliOption::new("image", ["/dev/null"]), + ], + )), + flat_map_collect(insert( + args.clone(), + vec![ + CliOption::new("hdr-key", ["--key", "/dev/null"]), + CliOption::new("format", ["--format=json:minify"]), + CliOption::new("image", ["/dev/null"]), + ], + )), + flat_map_collect(insert( + args.clone(), + vec![ + CliOption::new("hdr-key", ["--key", "/dev/null"]), + CliOption::new("format", ["--format=json:pretty"]), + CliOption::new("image", ["/dev/null"]), + ], + )), // separation between keyword and positional args works flat_map_collect(insert( args.clone(), diff --git a/rust/pvimg/src/cmd/info.rs b/rust/pvimg/src/cmd/info.rs index 2abd2a09..4452f7f4 100644 --- a/rust/pvimg/src/cmd/info.rs +++ b/rust/pvimg/src/cmd/info.rs @@ -12,30 +12,49 @@ use pv::{ }; use pvimg::{ error::OwnExitCode, - uvdata::{KeyExchangeTrait, SeHdr, UvDataTrait}, + uvdata::{KeyExchangeTrait, SeH, SeHdr, UvDataTrait}, }; -use crate::cli::InfoArgs; +use crate::cli::{InfoArgs, OutputFormatKind, OutputFormatSpec, OutputFormatVariant}; pub fn info(opt: &InfoArgs) -> Result { info!( "Reading Secure Execution header {}", opt.input.path.display() ); - let mut input = open_file(&opt.input.path)?; + let mut img = open_file(&opt.input.path)?; let mut output = std::io::stdout(); - SeHdr::seek_sehdr(&mut input, None)?; - let hdr = SeHdr::try_from_io(input)?; - if let Some(key_path) = &opt.hdr_key { + SeHdr::seek_sehdr(&mut img, None)?; + let hdr = SeHdr::try_from_io(&mut img)?; + let se_hdr = if let Some(key_path) = &opt.hdr_key { let key = SymKey::try_from_data(hdr.key_type(), read_file(key_path, "Reading key")?.into())?; - serde_json::to_writer_pretty(&mut output, &hdr.decrypt(&key)?)?; + let decrypted_hdr = hdr.decrypt(&key)?; + SeH::Decrypted(decrypted_hdr) } else { warn!("WARNING: The Secure Execution header integrity and authenticity was not verified. Specify '--hdr-key' to authenticate it. Do not trust the data without verification."); - serde_json::to_writer_pretty(&mut output, &hdr)?; + SeH::Encrypted(hdr) + }; + + match opt.format { + OutputFormatSpec { + kind: OutputFormatKind::Json, + variant, + } => { + match variant { + OutputFormatVariant::Minify => { + serde_json::to_writer(&mut output, &se_hdr)?; + } + OutputFormatVariant::Default | OutputFormatVariant::Pretty => { + serde_json::to_writer_pretty(&mut output, &se_hdr)? + } + } + // Make sure the output ends with a new line + writeln!(&mut output)? + } } - writeln!(output)?; + output.flush()?; Ok(OwnExitCode::Success) } diff --git a/rust/pvimg/src/lib.rs b/rust/pvimg/src/lib.rs index 22f68ac2..d521ecb1 100644 --- a/rust/pvimg/src/lib.rs +++ b/rust/pvimg/src/lib.rs @@ -37,7 +37,7 @@ pub mod misc { pub mod uvdata { pub use crate::pv_utils::{ AeadPlainDataTrait, BuilderTrait, ComponentMetadataV1, ControlFlagTrait, ControlFlagsTrait, - FlagData, KeyExchangeTrait, PcfV1, PlaintextControlFlagsV1, ScfV1, SeHdr, SeHdrAadV1, + FlagData, KeyExchangeTrait, PcfV1, PlaintextControlFlagsV1, ScfV1, SeH, SeHdr, SeHdrAadV1, SeHdrBinV1, SeHdrBuilder, SeHdrData, SeHdrDataV1, SeHdrPlain, SeHdrVersion, SeHdrVersioned, SecretControlFlagsV1, UvDataPlainTrait, UvDataTrait, UvKeyHashesV1, }; diff --git a/rust/pvimg/src/pv_utils.rs b/rust/pvimg/src/pv_utils.rs index 06ce3ef6..3b58cace 100644 --- a/rust/pvimg/src/pv_utils.rs +++ b/rust/pvimg/src/pv_utils.rs @@ -19,7 +19,7 @@ pub use misc::{round_up, try_copy_slice_to_array}; pub use psw::{ShortPsw, PSW, PSW_MASK_BA, PSW_MASK_EA}; pub use se_hdr::{ ComponentMetadataV1, ControlFlagTrait, ControlFlagsTrait, FlagData, PcfV1, - PlaintextControlFlagsV1, ScfV1, SeHdr, SeHdrAadV1, SeHdrBinV1, SeHdrBuilder, SeHdrData, + PlaintextControlFlagsV1, ScfV1, SeH, SeHdr, SeHdrAadV1, SeHdrBinV1, SeHdrBuilder, SeHdrData, SeHdrDataV1, SeHdrPlain, SeHdrVersion, SeHdrVersioned, SecretControlFlagsV1, }; pub use secured_comp::{ComponentTrait, SecuredComponent, SecuredComponentBuilder}; diff --git a/rust/pvimg/src/pv_utils/se_hdr.rs b/rust/pvimg/src/pv_utils/se_hdr.rs index 6d7c3400..66272622 100644 --- a/rust/pvimg/src/pv_utils/se_hdr.rs +++ b/rust/pvimg/src/pv_utils/se_hdr.rs @@ -9,9 +9,9 @@ mod hdr_v1; mod keys; pub use brb::{ - ComponentMetadata, ComponentMetadataV1, SeHdr, SeHdrDataV1, SeHdrPlain, SeHdrVersion, + ComponentMetadata, ComponentMetadataV1, SeH, SeHdr, SeHdrBinV1, SeHdrData, SeHdrDataV1, + SeHdrPlain, SeHdrVersion, SeHdrVersioned, }; -pub use brb::{SeHdrBinV1, SeHdrData, SeHdrVersioned}; pub use builder::SeHdrBuilder; pub use flags::{ ControlFlagTrait, ControlFlagsTrait, FlagData, PcfV1, PlaintextControlFlagsV1, ScfV1, diff --git a/rust/pvimg/src/pv_utils/se_hdr/brb.rs b/rust/pvimg/src/pv_utils/se_hdr/brb.rs index 9401c953..ade71e95 100644 --- a/rust/pvimg/src/pv_utils/se_hdr/brb.rs +++ b/rust/pvimg/src/pv_utils/se_hdr/brb.rs @@ -47,6 +47,12 @@ pub enum SeHdrVersion { V1 = 0x100, } +#[derive(Clone, PartialEq, Eq, Debug, Serialize)] +pub enum SeH { + Decrypted(SeHdrPlain), + Encrypted(SeHdr), +} + #[repr(C)] #[derive(Debug, Clone, PartialEq, Eq, DekuRead, DekuWrite, Serialize)] #[deku(endian = "endian", ctx = "endian: Endian", ctx_default = "Endian::Big")]