rust/pvsecret: User defined signatures and verifications

Introduces the ability to `pvsecret` to add a signature (ecdsa or rsa)
to the program-reserved space (user-data) of an add-secret request
during the request creation. Additionally, some arbitrary data may be
inserted.

The new command `verify` checks if add-secret requests are sane (e.g.
start with the correct magic value). If the request contains a
user-signature `verify` will also verify this signature.

Acked-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
This commit is contained in:
Steffen Eiden
2024-01-30 12:56:13 +01:00
committed by Jan Höppner
parent 551f66282e
commit 98f7a0569c
14 changed files with 418 additions and 15 deletions
+56 -1
View File
@@ -4,7 +4,6 @@
use clap::{ArgGroup, Args, CommandFactory, Parser, Subcommand, ValueEnum, ValueHint};
use pv::misc::CertificateOptions;
#[cfg(target_arch = "s390x")]
use pv::misc::STDOUT;
/// Manage secrets for IBM Secure Execution guests.
@@ -113,6 +112,31 @@ pub struct CreateSecretOpt {
value_delimiter = ','
)]
pub flags: Vec<CreateSecretFlags>,
/// Use the content of FILE as user-data.
///
/// Passes user data defined in <FILE> through the add-secret request to the ultravisor. The
/// user data can be up to 512 bytes of arbitrary data, and the maximum size depends on the
/// size of the user-signing key:
/// - No key: user data can be 512 bytes.
/// - EC or RSA 2048 keys: user data can be 256 bytes.
/// - RSA 3072 key: user data can be 128 bytes.
///
/// The firmware ignores this data, but the request tag protects the user-data. Optional. No
/// user-data by default.
#[arg(long, value_name = "FILE", value_hint = ValueHint::FilePath,)]
pub user_data: Option<String>,
/// Use the content of FILE as user signing key.
///
/// Adds a signature calculated from the key in <FILE> to the add-secret request. The
/// file must be in DER or PEM format containing a private key. Supported are RSA 2048 &
/// 3072-bit and EC(secp521r1) keys. The firmware ignores the content, but the request tag protects the
/// signature. The user-signing key signs the request. The location of the signature is filled
/// with zeros during the signature calculation. The request tag also secures the signature.
/// See man pvsecret verify for more details. Optional. No signature by default.
#[arg(long, value_name = "FILE", value_hint = ValueHint::FilePath,)]
pub user_sign_key: Option<String>,
}
#[derive(Subcommand, Debug)]
@@ -189,6 +213,30 @@ pub struct ListSecretOpt {
pub format: ListSecretOutputType,
}
#[derive(Args, Debug)]
pub struct VerifyOpt {
/// Specify the request to be checked.
#[arg(value_name = "FILE", value_hint = ValueHint::FilePath,)]
pub input: String,
/// Certificate containing a public key used to verify the user data signature.
///
/// Specifies a public key used to verify the user-data signature. The file must be a X509
/// certificate in DSA or PEM format. The certificate must hold the public EC, RSA 2048, or RSA
/// 3072 key corresponding to the private user-key used during `create`. No chain of trust is
/// established. Ensuring that the certificate can be trusted is the responsibility of the
/// user. The EC key must use the NIST/SECG curve over a 521 bit prime field (secp521r1).
#[arg(long, value_name = "FILE", value_hint = ValueHint::FilePath,)]
pub user_cert: Option<String>,
/// Store the result in FILE
///
/// If the request contained abirtary user-data the output contains this user-data with padded
/// zeros if available.
#[arg(short, long, value_name = "FILE", default_value = STDOUT, value_hint = ValueHint::FilePath,)]
pub output: String,
}
#[derive(Subcommand, Debug)]
pub enum Command {
/// Create a new add-secret request.
@@ -218,6 +266,13 @@ pub enum Command {
/// running IBM Secure Execution guest. Only available on s390x.
List(ListSecretOpt),
/// Verify that an add-secret request is sane.
///
/// Verifies that the given request is an add-secret request by testing for some values to be
/// present. If the request contains signed user-data, the signature is verified with the
/// provided key. Outputs the arbitrary user-data.
Verify(VerifyOpt),
/// Print version information and exit.
#[command(aliases(["--version"]), hide(true))]
Version,
+3
View File
@@ -5,6 +5,9 @@
mod create;
pub use create::create;
mod verify;
pub use verify::verify;
// Commands (directly) related to UVCs are only available on s389x
#[cfg(target_arch = "s390x")]
mod add;
+23 -2
View File
@@ -8,7 +8,7 @@ use log::{debug, info, trace, warn};
use pv::{
misc::{
get_writer_from_cli_file_arg, open_file, parse_hex, pv_guest_bit_set, read_certs,
read_exact_file, read_file, try_parse_u128, try_parse_u64, write,
read_exact_file, read_file, read_private_key, try_parse_u128, try_parse_u64, write,
},
request::{
openssl::pkey::{PKey, Public},
@@ -106,6 +106,27 @@ fn build_asrcb(opt: &CreateSecretOpt) -> Result<AddSecretRequest> {
asrcb.set_ext_secret(ExtSecret::Derived(read_exact_file(path, "CCK")?.into()))?;
}
// add user data
let user_data = opt
.user_data
.as_ref()
.map(|p| read_file(p, "user-data"))
.transpose()?;
if user_data.as_ref().is_some_and(|data| data.is_empty()) {
warn!("Added empty user-data file.");
}
let user_key = opt
.user_sign_key
.as_ref()
.map(|p| read_file(p, "User-signing key"))
.transpose()?
.map(|buf| read_private_key(&buf))
.transpose()?;
if user_data.is_some() || user_key.is_some() {
asrcb.set_user_data(user_data.unwrap_or_default(), user_key)?;
}
Ok(asrcb)
}
@@ -140,7 +161,7 @@ fn try_from_val(val: Value) -> anyhow::Result<ConfigUid> {
.ok_or(anyhow!("No 'cuid' entry found"))?;
let cuid = cuid
.strip_prefix("0x")
.ok_or(anyhow!("Value starts not with 0x".to_string()))?
.ok_or(anyhow!("CUID value starts not with 0x".to_string()))?
.to_owned();
if cuid.len() != ::std::mem::size_of::<ConfigUid>() * 2 {
return Err(anyhow!(format!("len invalid ({})", cuid.len())));
+46
View File
@@ -0,0 +1,46 @@
use anyhow::{anyhow, Context, Result};
use log::warn;
use pv::{
misc::{get_reader_from_cli_file_arg, get_writer_from_cli_file_arg, read_certs, read_file},
request::{
openssl::pkey::{PKey, Public},
uvsecret::verify_asrcb_and_get_user_data,
},
};
use crate::cli::VerifyOpt;
/// read the content of a DER or PEM x509 and return the public key
fn read_sgn_key(path: &str) -> Result<PKey<Public>> {
read_certs(&read_file(path, "user-signing key")?)?
.get(0)
.ok_or(anyhow!("File does not contain a X509 certificate"))?
.public_key()
.map_err(anyhow::Error::new)
}
pub fn verify(opt: &VerifyOpt) -> Result<()> {
let mut rd_in = get_reader_from_cli_file_arg(&opt.input)?;
let mut data_in = Vec::with_capacity(0x1000);
rd_in
.read_to_end(&mut data_in)
.with_context(|| format!("Cannot read input file {}", opt.input))?;
let verify_cert = opt
.user_cert
.as_ref()
.map(|p| read_sgn_key(p))
.transpose()
.context("Cannot read user-verification certificate.")?;
let user_data = verify_asrcb_and_get_user_data(data_in, verify_cert)
.context("Could not verify the the Add-secret request")?;
if let Some(user_data) = user_data {
get_writer_from_cli_file_arg(&opt.output)?
.write_all(&user_data)
.with_context(|| format!("Cannot write user data to {}", opt.output))?;
}
warn!("Successfully verified the request.");
Ok(())
}
+2
View File
@@ -25,6 +25,7 @@ const FEATURES: &[&str] = &[
"+lock",
#[cfg(target_arch = "s390x")]
"+list",
"+verify",
];
fn print_error(e: anyhow::Error, verbosity: u8) -> ExitCode {
@@ -116,6 +117,7 @@ fn main() -> ExitCode {
Command::Lock => not_supported(),
Command::Create(opt) => cmd::create(opt),
Command::Version => print_version(cli.verbose),
Command::Verify(opt) => cmd::verify(opt),
};
match res {