pvimg: Add '--print-schema <FORMAT>' option to 'pvimg info'

Add a new '--print-schema <FORMAT>' option to the 'pvimg info' command
that prints the schema, if available, describing the given output
format. For example, 'pvimg info --print-schema json' prints the JSON
schema for the 'pvimg info' command output.

Acked-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-02-10 15:28:39 +01:00
committed by Jan Höppner
parent 8d40b5c97a
commit 3b0fc8cfda
3 changed files with 396 additions and 10 deletions

View File

@@ -0,0 +1,321 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "'pvimg info' JSON output schema",
"description": "JSON schema for the 'pvimg info' command output. This schema describes the structure of IBM Secure Execution (SE) image header information in three states: decrypted (with secrets), encrypted (verified), and encrypted (not verified).",
"type": "object",
"required": [
"meta",
"data"
],
"properties": {
"meta": {
"type": "object",
"description": "Metadata about the s390-tools version and execution environment.",
"required": [
"api_level",
"version",
"host",
"time_epoch",
"time"
],
"properties": {
"api_level": {
"type": "integer",
"const": 1,
"description": "API level version, currently always 1."
},
"version": {
"type": "string",
"description": "Version of s390-tools."
},
"host": {
"type": "string",
"description": "Hostname where the command was executed."
},
"time_epoch": {
"type": "integer",
"description": "Unix timestamp (seconds since epoch) when the command was executed."
},
"time": {
"type": "string",
"description": "Human-readable timestamp in format 'YYYY-MM-DD HH:MM:SS+ZZZZ'."
}
},
"additionalProperties": false
},
"data": {
"type": "object",
"description": "Contains the Secure Execution header information.",
"required": [
"kind",
"verified",
"magic_hex",
"version",
"sehs",
"iv_hex",
"nks",
"sea",
"nep",
"pcf_hex",
"cust_pub_key",
"pld_hex",
"ald_hex",
"tld_hex",
"keyslots",
"tag_hex"
],
"properties": {
"kind": {
"type": "string",
"enum": [
"decrypted_se_hdr",
"se_hdr"
],
"description": "Type of SE header."
},
"verified": {
"type": "boolean",
"description": "Indicates whether the SE header authenticity was verified."
},
"magic_hex": {
"$ref": "#/definitions/hexString",
"description": "Magic value identifying the SE header."
},
"version": {
"type": "string",
"description": "SE header version string."
},
"sehs": {
"type": "integer",
"description": "SE header size in bytes."
},
"iv_hex": {
"$ref": "#/definitions/hexString",
"description": "Initialization vector for SE header encryption."
},
"nks": {
"type": "integer",
"description": "Number of key slots."
},
"sea": {
"type": "integer",
"description": "Size of encrypted area of the SE header in bytes."
},
"nep": {
"type": "integer",
"description": "Number of (encrypted) component pages."
},
"pcf_hex": {
"$ref": "#/definitions/hexString",
"description": "Plaintext control flags."
},
"cust_pub_key": {
"$ref": "#/definitions/custPubKey",
"description": "Customer public key."
},
"pld_hex": {
"$ref": "#/definitions/hexString",
"description": "PLD (Page content List Digest) hash."
},
"ald_hex": {
"$ref": "#/definitions/hexString",
"description": "ALD (Address List Digest) hash."
},
"tld_hex": {
"$ref": "#/definitions/hexString",
"description": "TLD (Tweak List Digest) hash."
},
"keyslots": {
"type": "array",
"minItems": 1,
"description": "Array of key slot entries.",
"items": {
"$ref": "#/definitions/keyslot"
}
},
"cipher_data_b64": {
"$ref": "#/definitions/base64String",
"description": "Encrypted SE header data (only for encrypted headers)."
},
"psw": {
"$ref": "#/definitions/psw",
"description": "Program Status Word (only for decrypted headers)."
},
"scf_hex": {
"$ref": "#/definitions/hexString",
"description": "Secret control flags (only for decrypted headers)."
},
"cck_hex": {
"$ref": "#/definitions/hexString",
"description": "Customer communication key (CCK) (only for decrypted headers)."
},
"xts_hex": {
"$ref": "#/definitions/hexString",
"description": "Components encryption key (only for decrypted headers)."
},
"tag_hex": {
"$ref": "#/definitions/hexString",
"description": "SE Header authentication tag."
}
},
"oneOf": [
{
"title": "Decrypted SE Header",
"description": "SE header with decrypted secrets.",
"properties": {
"kind": {
"const": "decrypted_se_hdr"
},
"verified": {
"const": true
}
},
"required": [
"kind",
"verified",
"psw",
"scf_hex",
"cck_hex",
"xts_hex"
],
"not": {
"anyOf": [
{
"required": [
"cipher_data_b64"
]
}
]
}
},
{
"title": "Encrypted SE Header (Verified)",
"description": "Encrypted SE header integrity and authenticity checked with --hdr-key.",
"properties": {
"kind": {
"const": "se_hdr"
},
"verified": {
"const": true
}
},
"required": [
"kind",
"verified",
"cipher_data_b64"
]
},
{
"title": "Encrypted SE Header (Not Verified)",
"description": "Encrypted SE header without integrity and authenticity verification.",
"properties": {
"kind": {
"const": "se_hdr"
},
"verified": {
"const": false
}
},
"required": [
"kind",
"verified"
],
"not": {
"anyOf": [
{
"required": [
"psw"
]
},
{
"required": [
"scf_hex"
]
},
{
"required": [
"cck_hex"
]
},
{
"required": [
"xts_hex"
]
}
]
}
}
],
"additionalProperties": false
}
},
"definitions": {
"hexString": {
"type": "string",
"pattern": "^[0-9a-fA-F]+$",
"description": "Hexadecimal string without 0x prefix."
},
"base64String": {
"type": "string",
"pattern": "^[A-Za-z0-9+/]+={0,2}$",
"description": "Standard Base64-encoded string."
},
"custPubKey": {
"type": "object",
"description": "Customer public key.",
"required": [
"coord_hex"
],
"properties": {
"coord_hex": {
"$ref": "#/definitions/hexString",
"description": "EC public key coordinates in hex."
}
},
"additionalProperties": false
},
"keyslot": {
"type": "object",
"description": "Wrapped SE header protection key for a specific target key.",
"required": [
"phkh_hex",
"wrpk_hex",
"kst_hex"
],
"properties": {
"phkh_hex": {
"$ref": "#/definitions/hexString",
"description": "Hash of the public target key."
},
"wrpk_hex": {
"$ref": "#/definitions/hexString",
"description": "Wrapped SE header protection key."
},
"kst_hex": {
"$ref": "#/definitions/hexString",
"description": "Key slot authentication tag."
}
},
"additionalProperties": false
},
"psw": {
"type": "object",
"required": [
"mask_hex",
"addr_hex"
],
"description": "Program Status Word (PSW) defining the initial CPU state in the protected mode.",
"properties": {
"mask_hex": {
"$ref": "#/definitions/hexString",
"description": "Initial PSW mask."
},
"addr_hex": {
"$ref": "#/definitions/hexString",
"description": "Initial PSW address."
}
},
"additionalProperties": false
}
}
}

View File

@@ -354,15 +354,16 @@ pub struct SeImgInputArgs {
}
#[derive(Args, Debug)]
#[command(group(ArgGroup::new("info-mode").required(true).args(["path", "print_schema"])))]
pub struct InfoArgs {
#[clap(flatten)]
pub input: SeImgInputArgs,
pub input: Option<SeImgInputArgs>,
/// Output format for the Secure Execution image information.
///
/// If not specified, the format is automatically determined based on
/// whether stdout is connected to a terminal.
#[arg(long, value_parser=OutputFormatSpecParser::default())]
#[arg(long, value_parser=OutputFormatSpecParser::default(), conflicts_with = "print_schema")]
pub format: Option<OutputFormatSpec>,
/// Use the key in FILE to verify the Secure Execution header and optionally
@@ -376,7 +377,7 @@ pub struct InfoArgs {
/// Without this option, the information is displayed, but NOT verified, and
/// a warning is printed. The displayed data should not be trusted without
/// verification.
#[arg(long, value_name = "FILE", value_hint = ValueHint::FilePath, alias = "key", verbatim_doc_comment)]
#[arg(long, value_name = "FILE", value_hint = ValueHint::FilePath, alias = "key", verbatim_doc_comment, conflicts_with = "print_schema")]
pub hdr_key: Option<PathBuf>,
/// This option reveals sensitive information that is normally encrypted in
@@ -389,8 +390,22 @@ pub struct InfoArgs {
/// The decrypted secrets should never be exposed in untrusted systems.
///
/// This option requires '--hdr-key' to decrypt the header.
#[arg(long, requires = "hdr_key", verbatim_doc_comment)]
#[arg(
long,
requires = "hdr_key",
verbatim_doc_comment,
conflicts_with = "print_schema"
)]
pub show_secrets: bool,
/// Print the schema for the 'info' subcommand and exit.
///
/// This outputs the schema that describes the structure of the given output FORMAT
/// produced by the 'info' subcommand. The schema can be used for:
/// - Validating output
/// - Building tools that parse the output
#[arg(value_name = "FORMAT", long, verbatim_doc_comment)]
pub print_schema: Option<OutputFormatKind>,
}
#[derive(Args, Debug)]
@@ -984,17 +999,57 @@ mod test {
CliOption::new("verbose", ["-VVV"]),
],
)),
// --print-schema json works standalone
flat_map_collect(insert(
args.clone(),
vec![CliOption::new(
"print-json-schema",
["--print-schema", "json"],
)],
)),
];
let invalid_test_args = [
// No default defined for --format
flat_map_collect(insert(
args,
args.clone(),
vec![
CliOption::new("format", ["--format"]),
CliOption::new("image", ["--", "/dev/null"]),
],
)),
// --print-json-schema conflicts with input
flat_map_collect(insert(
args.clone(),
vec![
CliOption::new("print-json-schema", ["--print-schema", "json"]),
CliOption::new("image", ["/dev/null"]),
],
)),
// --print-json-schema conflicts with --format
flat_map_collect(insert(
args.clone(),
vec![
CliOption::new("print-json-schema", ["--print-schema", "json"]),
CliOption::new("format", ["--format=json"]),
],
)),
// --print-json-schema conflicts with --hdr-key
flat_map_collect(insert(
args.clone(),
vec![
CliOption::new("print-json-schema", ["--print-schema", "json"]),
CliOption::new("hdr-key", ["--hdr-key", "/dev/null"]),
],
)),
// --print-json-schema conflicts with --show-secrets
flat_map_collect(insert(
args,
vec![
CliOption::new("print-json-schema", ["--print-schema", "json"]),
CliOption::new("show-secrets", ["--show-secrets"]),
],
)),
];
let mut pvimg_valid_args = vec![];

View File

@@ -17,13 +17,23 @@ use pvimg::{
use crate::cli::{InfoArgs, OutputFormatKind, OutputFormatSpec, OutputFormatVariant};
pub const INFO_V1_JSON_SCHEMA: &str = include_str!("../../schema/info-v1.schema.json");
pub fn info(opt: &InfoArgs) -> Result<OwnExitCode> {
info!(
"Reading Secure Execution header {}",
opt.input.path.display()
);
let mut img = open_file(&opt.input.path)?;
let mut output = std::io::stdout();
if let Some(format_kind) = opt.print_schema {
match format_kind {
OutputFormatKind::Text => {
writeln!(output, "No schema for {format_kind} format available!")?
}
OutputFormatKind::Json => write!(output, "{INFO_V1_JSON_SCHEMA}")?,
}
return Ok(OwnExitCode::Success);
}
let input_path = opt.input.as_ref().unwrap().path.clone();
info!("Reading Secure Execution header {}", input_path.display(),);
let mut img = open_file(&input_path)?;
SeHdr::seek_sehdr(&mut img, None)?;
let hdr = SeHdr::try_from_io(&mut img)?;