mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
rust: Add a new tool called 'pvimg'
Add a new tool called 'pvimg' that can be used to create and inspect
Secure Execution images. It has several subcommands:
+ create: create an IBM Secure Execution image (genprotimg compatible
sytnax) and C-'genprotimg' is going to be replaced by a
symlink to this subcommand.
+ test: test various aspects of an existing Secure Execution image
+ info: print information about an existing Secure Execution
image (experimental API!)
+ version: print version and exit
As mentioned above, the 'genprotimg' tool is now a symbolic link to the
'pvimg create' subcommand and the CLI is backward compatible with the
original genprotimg CLI, with the following exceptions:
- '-v' increases the verbosity instead of showing the version
- '-V' is now deprecated in favor of '-v'
- an existing output file is no longer silently overwritten, but there
is a new flag '--overwrite' to get the original behavior
- experimental options are no longer described in the help
- the commands '--cert ...' and '--root-ca' are now mutually exclusive
- to '--no-verify'
- there is now a component check, e.g. it checks if the specified
Linux kernel looks like a raw binary s390x kernel. These checks can be
disabled by using the new command line flag '--no-component-check'
Acked-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
This commit is contained in:
committed by
Jan Höppner
parent
f524b0b8dc
commit
f4cf4ae6eb
@@ -0,0 +1,85 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2024
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::Result;
|
||||
use log::info;
|
||||
use pv::{misc::read_file, request::Confidential};
|
||||
|
||||
use crate::cli::CreateBootImageExperimentalArgs;
|
||||
|
||||
#[macro_export]
|
||||
/// Makes it easier to
|
||||
macro_rules! log_println {
|
||||
($($arg:tt)+) => { warn!($($arg)+) };
|
||||
}
|
||||
|
||||
pub struct UserProvidedKeys {
|
||||
pub(crate) cck: Option<(PathBuf, Confidential<Vec<u8>>)>,
|
||||
pub(crate) components_key: Option<(PathBuf, Confidential<Vec<u8>>)>,
|
||||
pub(crate) aead_key: Option<(PathBuf, Confidential<Vec<u8>>)>,
|
||||
}
|
||||
|
||||
/// Reads all user provided keys.
|
||||
pub fn read_user_provided_keys(
|
||||
cck_path: Option<&Path>,
|
||||
experimental_args: &CreateBootImageExperimentalArgs,
|
||||
) -> Result<UserProvidedKeys> {
|
||||
let components_key = {
|
||||
match &experimental_args.x_comp_key {
|
||||
Some(key_path) => {
|
||||
info!(
|
||||
"Use file '{}' as the image components protection key",
|
||||
key_path.display()
|
||||
);
|
||||
Some((
|
||||
key_path.to_owned(),
|
||||
Confidential::new(read_file(key_path, "image components key")?),
|
||||
))
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
};
|
||||
let aead_key = {
|
||||
match &experimental_args.x_header_key {
|
||||
Some(key_path) => {
|
||||
info!(
|
||||
"Use file '{}' as the Secure Execution header protection",
|
||||
key_path.display()
|
||||
);
|
||||
Some((
|
||||
key_path.to_owned(),
|
||||
Confidential::new(read_file(
|
||||
key_path,
|
||||
"Secure Execution header protection key",
|
||||
)?),
|
||||
))
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
};
|
||||
|
||||
let cck = {
|
||||
match cck_path {
|
||||
Some(key_path) => {
|
||||
info!(
|
||||
"Use file '{}' as the customer communication key (CCK)",
|
||||
key_path.display()
|
||||
);
|
||||
Some((
|
||||
key_path.to_owned(),
|
||||
(Confidential::new(read_file(key_path, "customer communication key (CCK)")?)),
|
||||
))
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
};
|
||||
|
||||
Ok(UserProvidedKeys {
|
||||
cck,
|
||||
components_key,
|
||||
aead_key,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2024
|
||||
|
||||
use std::{fs::OpenOptions, io::BufReader};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use log::{debug, warn};
|
||||
use pv::misc::{open_file, try_parse_u64};
|
||||
use pvimg::{
|
||||
error::OwnExitCode,
|
||||
secured_comp::ComponentTrait,
|
||||
uvdata::{
|
||||
ControlFlagTrait, ControlFlagsTrait, FlagData, PcfV1, PlaintextControlFlagsV1, ScfV1,
|
||||
SeHdrDataV1, SecretControlFlagsV1,
|
||||
},
|
||||
};
|
||||
use utils::{AtomicFile, AtomicFileOperation};
|
||||
|
||||
use crate::{
|
||||
cli::{ComponentPaths, CreateBootImageArgs},
|
||||
cmd::common::read_user_provided_keys,
|
||||
se_img::{SeHdrArgs, SeImgBuilder},
|
||||
se_img_comps::{
|
||||
check_components, cmdline::Cmdline, kernel::S390Kernel, ramdisk::Ramdisk, Component,
|
||||
},
|
||||
};
|
||||
|
||||
/// The returned vector is sorted by the occurrence in the memory layout:
|
||||
/// First the kernel, then the ramdisk and then the kernel cmdline.
|
||||
///
|
||||
/// Keep this ordering in sync with the ordering of [`ComponentKind`]!
|
||||
fn components(component_args: &ComponentPaths) -> Result<Vec<Component>> {
|
||||
// IMPORTANT: Don't change the order of the components: kernel, ramdisk, and
|
||||
// then parmline! This is important since ALD, PLD and TLD is sorted by the
|
||||
// component address.
|
||||
let mut components: Vec<Component> =
|
||||
vec![S390Kernel::new(Box::new(BufReader::new(open_file(&component_args.kernel)?))).into()];
|
||||
if let Some(path) = &component_args.ramdisk {
|
||||
components.push(Ramdisk::new(Box::new(BufReader::new(open_file(path)?))).into());
|
||||
}
|
||||
if let Some(path) = &component_args.parmfile {
|
||||
components.push(Cmdline::new(Box::new(BufReader::new(open_file(path)?))).into());
|
||||
}
|
||||
Ok(components)
|
||||
}
|
||||
|
||||
fn parse_flags(
|
||||
args: &CreateBootImageArgs,
|
||||
) -> Result<(PlaintextControlFlagsV1, SecretControlFlagsV1)> {
|
||||
let lf = &args.legacy_flags;
|
||||
let plaintext_flags: Vec<FlagData<PcfV1>> = [
|
||||
lf.disable_dump
|
||||
.filter(|x| *x)
|
||||
.and(Some(PcfV1::all_disabled([PcfV1::AllowDumping]))),
|
||||
lf.enable_dump
|
||||
.filter(|x| *x)
|
||||
.and(Some(PcfV1::all_disabled([PcfV1::AllowDumping]))),
|
||||
lf.disable_pckmo
|
||||
.filter(|x| *x)
|
||||
.and(Some(PcfV1::all_disabled([
|
||||
PcfV1::PckmoAes,
|
||||
PcfV1::PckmoDeaTdea,
|
||||
PcfV1::PckmoEcc,
|
||||
]))),
|
||||
lf.enable_pckmo.filter(|x| *x).and(Some(PcfV1::all_enabled([
|
||||
PcfV1::PckmoAes,
|
||||
PcfV1::PckmoDeaTdea,
|
||||
PcfV1::PckmoEcc,
|
||||
]))),
|
||||
]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.flatten()
|
||||
.collect();
|
||||
// This is ensured by Clap's `conflicts_with`.
|
||||
assert!(PlaintextControlFlagsV1::no_duplicates(&plaintext_flags));
|
||||
|
||||
let secret_flags: Vec<FlagData<ScfV1>> = [
|
||||
lf.disable_cck_extension_secret
|
||||
.filter(|x| *x)
|
||||
.and(Some(ScfV1::all_disabled([
|
||||
ScfV1::CCKExtensionSecretEnforcment,
|
||||
]))),
|
||||
lf.enable_cck_extension_secret
|
||||
.filter(|x| *x)
|
||||
.and(Some(ScfV1::all_enabled([
|
||||
ScfV1::CCKExtensionSecretEnforcment,
|
||||
]))),
|
||||
]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.flatten()
|
||||
.collect();
|
||||
// This is ensured by Clap's `conflicts_with`.
|
||||
assert!(SecretControlFlagsV1::no_duplicates(&secret_flags));
|
||||
|
||||
let mut pcf: PlaintextControlFlagsV1 = match &args.experimental_args.x_pcf {
|
||||
Some(v) => try_parse_u64(v, "x-pcf")?.into(),
|
||||
None => PlaintextControlFlagsV1::default(),
|
||||
};
|
||||
pcf.parse_flags(&plaintext_flags);
|
||||
debug!("Using plaintext flags: {pcf}");
|
||||
|
||||
let mut scf: SecretControlFlagsV1 = match &args.experimental_args.x_scf {
|
||||
Some(v) => try_parse_u64(v, "x-scf")?.into(),
|
||||
None => SecretControlFlagsV1::default(),
|
||||
};
|
||||
scf.parse_flags(&secret_flags);
|
||||
debug!("Using secret flags: {scf}");
|
||||
|
||||
Ok((pcf, scf))
|
||||
}
|
||||
|
||||
/// Create a Secure Execution boot image
|
||||
pub fn create(opt: &CreateBootImageArgs) -> Result<OwnExitCode> {
|
||||
// Verify host key documents first, because if they are not valid there is
|
||||
// no reason to continue.
|
||||
let verified_host_keys = opt
|
||||
.certificate_args
|
||||
.get_verified_hkds("Secure Execution image")?;
|
||||
let user_provided_keys =
|
||||
read_user_provided_keys(opt.comm_key.as_deref(), &opt.experimental_args)?;
|
||||
let (plaintext_flags, secret_flags) = parse_flags(opt)?;
|
||||
|
||||
let mut components = components(&opt.component_paths)?;
|
||||
if opt.no_component_check {
|
||||
warn!("The component check is turned off!");
|
||||
} else {
|
||||
check_components(&mut components)?;
|
||||
}
|
||||
|
||||
// FIXME get rid of the legacy mode. But that's only possible as soon as all
|
||||
// available tools are updated.
|
||||
let expected_se_hdr_size = SeHdrDataV1::expected_size(verified_host_keys.len())?;
|
||||
let mut writer = AtomicFile::with_extension(&opt.output, "part", &mut OpenOptions::new())?;
|
||||
let mut seimg_ctx = SeImgBuilder::new_v1(
|
||||
&mut writer,
|
||||
plaintext_flags.is_unset(PcfV1::NoComponentEncryption),
|
||||
Some(expected_se_hdr_size),
|
||||
opt.experimental_args.x_bootloader_directory.as_ref(),
|
||||
)?;
|
||||
|
||||
// Enable expert mode
|
||||
seimg_ctx.i_know_what_i_am_doing();
|
||||
if let Some((path, key)) = user_provided_keys.components_key {
|
||||
seimg_ctx.set_components_key(key).with_context(|| {
|
||||
format!(
|
||||
"Failed to use '{}' as the image components key",
|
||||
path.display()
|
||||
)
|
||||
})?;
|
||||
}
|
||||
|
||||
let psw_addr: Option<u64> = match &opt.experimental_args.x_psw {
|
||||
Some(v) => try_parse_u64(v, "x-psw")?.into(),
|
||||
None => None,
|
||||
};
|
||||
|
||||
for mut component in components.into_iter() {
|
||||
seimg_ctx
|
||||
.prepare_and_append_as_secure_component(&mut component, None)
|
||||
.with_context(|| format!("Failed to prepare {} component", component.kind()))?;
|
||||
}
|
||||
|
||||
let img_comps = seimg_ctx.finish(SeHdrArgs {
|
||||
keys: verified_host_keys.as_slice(),
|
||||
pcf: &plaintext_flags,
|
||||
scf: &secret_flags,
|
||||
cck: &user_provided_keys.cck,
|
||||
hdr_aead_key: &user_provided_keys.aead_key,
|
||||
psw_addr: &psw_addr,
|
||||
})?;
|
||||
|
||||
debug!("");
|
||||
debug!("----------------------------------------------------------------");
|
||||
debug!("| {:^60} |", "Secure Execution image layout");
|
||||
debug!("|--------------------------------------------------------------|");
|
||||
debug!("| {:<23} | {:<34} |", "Component type", "Component address");
|
||||
debug!("|-------------------------|------------------------------------|");
|
||||
img_comps
|
||||
.iter()
|
||||
.for_each(|img_comp| debug!("{img_comp:<33}"));
|
||||
debug!("----------------------------------------------------------------");
|
||||
|
||||
// Rename the file `$OUTPUT.part` to `$OUTPUT` for achieving atomic file
|
||||
// creation.
|
||||
let op = match opt.overwrite {
|
||||
true => AtomicFileOperation::Replace,
|
||||
false => AtomicFileOperation::NoReplace,
|
||||
};
|
||||
writer.finish(op)?;
|
||||
|
||||
warn!("Successfully generated the Secure Execution image.");
|
||||
Ok(OwnExitCode::Success)
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2024
|
||||
|
||||
use std::io::Write;
|
||||
|
||||
use anyhow::Result;
|
||||
use log::info;
|
||||
use pv::{
|
||||
misc::{open_file, read_file},
|
||||
request::SymKey,
|
||||
};
|
||||
use pvimg::{
|
||||
error::OwnExitCode,
|
||||
uvdata::{KeyExchangeTrait, SeHdr, UvDataTrait},
|
||||
};
|
||||
|
||||
use crate::cli::InfoArgs;
|
||||
|
||||
pub fn info(opt: &InfoArgs) -> Result<OwnExitCode> {
|
||||
info!(
|
||||
"Reading Secure Execution header {}",
|
||||
opt.input.path.display()
|
||||
);
|
||||
let mut input = 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.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)?)?;
|
||||
} else {
|
||||
serde_json::to_writer_pretty(&mut output, &hdr)?;
|
||||
}
|
||||
writeln!(output)?;
|
||||
|
||||
Ok(OwnExitCode::Success)
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2024
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::Result;
|
||||
use log::{info, warn};
|
||||
use pv::{
|
||||
misc::{open_file, read_certs, read_file},
|
||||
FileAccessErrorType, PvCoreError,
|
||||
};
|
||||
use pvimg::{
|
||||
error::{Error, OwnExitCode, PvError},
|
||||
uvdata::{KeyExchangeTrait, SeHdr, UvKeyHashesV1},
|
||||
};
|
||||
use utils::HexSlice;
|
||||
|
||||
use crate::{cli::TestArgs, log_println};
|
||||
|
||||
/// Returns `Ok(true)` if at least one of the hashes is included.
|
||||
fn hdr_test_target_hashes(hdr: &SeHdr, key_hashes: &Path) -> Result<bool> {
|
||||
let file = open_file(key_hashes).map_err(|err| match err {
|
||||
PvCoreError::FileAccess {
|
||||
ref ty,
|
||||
ref path,
|
||||
ref source,
|
||||
} if matches!(ty, FileAccessErrorType::Open)
|
||||
&& source.kind() == std::io::ErrorKind::NotFound
|
||||
&& *path == PathBuf::from(UvKeyHashesV1::SYS_UV_KEYS_ALL) =>
|
||||
{
|
||||
Error::UnavailableQueryUvKeyHashesSupport { source: err }
|
||||
}
|
||||
err => Error::PvCore(err),
|
||||
})?;
|
||||
let hashes = UvKeyHashesV1::read_from_io(file)?;
|
||||
let mut contains = hdr.contains_hash(&hashes.pchkh);
|
||||
if contains {
|
||||
log_println!(
|
||||
" ✓ Host key hash {:#} is included",
|
||||
HexSlice::from(&hashes.pchkh)
|
||||
);
|
||||
}
|
||||
if hdr.contains_hash(&hashes.pbhkh) {
|
||||
log_println!(
|
||||
" ✓ Backup host key hash {:#} is included",
|
||||
HexSlice::from(&hashes.pbhkh)
|
||||
);
|
||||
contains = true;
|
||||
};
|
||||
|
||||
for hash in hashes.res {
|
||||
if hdr.contains_hash(&hash) {
|
||||
log_println!(" ✓ Key hash {:#} is included", HexSlice::from(&hash));
|
||||
contains = true;
|
||||
}
|
||||
}
|
||||
|
||||
if !contains {
|
||||
warn!(" ✘ None of the key hashes is included");
|
||||
}
|
||||
Ok(contains)
|
||||
}
|
||||
|
||||
/// Returns `Ok(true)` if at least one of the given public key of the host key
|
||||
/// documents was used for the image creation or if no host key document was
|
||||
/// specified.
|
||||
fn hdr_test_hkd<P>(hdr: &SeHdr, host_key_documents: &[P]) -> Result<bool>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
if host_key_documents.is_empty() {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
let mut result = false;
|
||||
for path in host_key_documents {
|
||||
let hkd_path = path.as_ref();
|
||||
let hkd_data = read_file(hkd_path, "host key document")?;
|
||||
let certs = read_certs(&hkd_data)?;
|
||||
if certs.is_empty() {
|
||||
return Err(PvError::NoHkdInFile(hkd_path.display().to_string()).into());
|
||||
}
|
||||
|
||||
if certs.len() != 1 {
|
||||
warn!("The host key document in '{}' contains more than one certificate! Only the first certificate will be used.",
|
||||
hkd_path.display());
|
||||
}
|
||||
|
||||
// Panic: len is == 1 -> unwrap will succeed/not panic
|
||||
let cert = certs.first().unwrap();
|
||||
if hdr.contains(cert.public_key()?)? {
|
||||
result = true;
|
||||
log_println!(" ✓ Host key document '{}' is included", hkd_path.display());
|
||||
} else {
|
||||
log_println!(
|
||||
" ✘ Host key document '{}' is not included",
|
||||
hkd_path.display()
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn test(opt: &TestArgs) -> Result<OwnExitCode> {
|
||||
info!("Testing a Secure Execution image");
|
||||
|
||||
let mut input = open_file(&opt.input.path)?;
|
||||
SeHdr::seek_sehdr(&mut input, None)?;
|
||||
let hdr = SeHdr::try_from_io(input)?;
|
||||
|
||||
let mut success = hdr_test_hkd(&hdr, &opt.host_key_documents)?;
|
||||
if let Some(path) = &opt.key_hashes {
|
||||
success = hdr_test_target_hashes(&hdr, path)? && success;
|
||||
}
|
||||
|
||||
Ok(if success {
|
||||
OwnExitCode::Success
|
||||
} else {
|
||||
OwnExitCode::GenericError
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright IBM Corp. 2024
|
||||
|
||||
use anyhow::Result;
|
||||
use log::LevelFilter;
|
||||
use pvimg::error::OwnExitCode;
|
||||
use utils::print_version;
|
||||
|
||||
use crate::cmd;
|
||||
|
||||
const FEATURES: &[&[&str]] = &[cmd::CMD_FN];
|
||||
|
||||
/// Print the version
|
||||
pub fn version(filter: LevelFilter) -> Result<OwnExitCode> {
|
||||
print_version!("2024", filter; FEATURES.concat());
|
||||
Ok(OwnExitCode::Success)
|
||||
}
|
||||
Reference in New Issue
Block a user