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

View File

@@ -45,6 +45,11 @@ Lock the secret-store (s390x only)
List all ultravisor secrets (s390x only)
</ul>
- **verify**
<ul>
Verify that an add-secret request is sane
</ul>
## Options
`-v`, `--verbose`
@@ -180,9 +185,9 @@ all requests.
<ul>
Use HEXSTRING as the Configuration Unique ID. Must be a hex 128-bit unsigned big
endian number string. Leading zeros must be provided. If specified, the value
must match with the Config-UID from the attestation result of that guest. If
not specified, the CUID will be ignored by the ultravisor during the
verification of the request.
must match with the Config-UID from the attestation result of that guest. If not
specified, the CUID will be ignored by the ultravisor during the verification of
the request.
</ul>
@@ -204,6 +209,37 @@ Flags for the add-secret request
</ul>
`--user-data <FILE>`
<ul>
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(secp521r1) 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.
</ul>
`--user-sign-key <FILE>`
<ul>
Use the content of FILE as user signing key. Adds a signature defined 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.
</ul>
### pvsecret create meta
#### Synopsis
`pvsecret create meta`
@@ -301,3 +337,73 @@ Define the output format of the list
- **yaml**: Use yaml format
- **bin**: Use the format the ultravisor uses to pass the list
</ul>
## pvsecret verify
### Synopsis
`pvsecret verify [OPTIONS] <FILE>`
### Description
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. All data in
the request is in big endian. `verify` checks the following:
- The first 6 bytes of the request are equal to: `B6173 7263 624d | asrcbM`
- The sizes in the request header are sane and do not point out of the file
- The request version is supported by the binary
- If user-data contains a signature, verify the signature using a public key
The content of bytes 6&7 of the request define which kind of user-data the
request contains.
- **0x0000** `no user-data (512 bytes zero)`
- **0x0001** `512 bytes user-data`
- **0x0002** `265 bytes user-data| 139 bytes ecdsa signature | 5 bytes reserved
| 2 bytes signature size | ...`
- **0x0003** `256 bytes user-data | 256 bytes rsa2048 signature`
- **0x0004** `128 bytes user-data | 384 bytes rsa3072 signature`
The actual user-data may be less than the capacity. If less data was provided
during `create` zeros are appended.
For type 2-4 The signature is calculated as follows:
1) The request is generated with the user-data in place and zeros for the
signature data.
2) The signature is calculated for the request. The signature signs the
authenticated data and the encrypted data, but not the request tag. I.e. the
signature signs the whole request but the last 16 bytes a,d with the signature
bytes set to zero.
3) The signature is inserted to its location in the request.
4) The request GCM tag is calculated.
The verification process works as follows:
1) copy the signature to a buffer
2) overwrite the signature with zeros
3) verify the signature of the request but the last 16 bytes
### Arguments
`<FILE>`
<ul>
Specify the request to be checked
</ul>
### Options
`--user-cert <FILE>`
<ul>
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).
</ul>
`-o`, `--output <FILE>`
<ul>
Store the result in FILE If the request contained abirtary user-data the output
contains this user-data with padded zeros if available.
Default value: '-'
</ul>

View File

@@ -3,7 +3,7 @@
.\" it under the terms of the MIT license. See LICENSE for details.
.\"
.TH pvsecret-add 1 "2023-10-09" "s390-tools" "UV-Secret Manual"
.TH pvsecret-add 1 "2024-01-30" "s390-tools" "UV-Secret Manual"
.nh
.ad l
.SH NAME

View File

@@ -3,7 +3,7 @@
.\" it under the terms of the MIT license. See LICENSE for details.
.\"
.TH pvsecret-create-association 1 "2023-10-09" "s390-tools" "UV-Secret Manual"
.TH pvsecret-create-association 1 "2024-01-30" "s390-tools" "UV-Secret Manual"
.nh
.ad l
.SH NAME

View File

@@ -3,7 +3,7 @@
.\" it under the terms of the MIT license. See LICENSE for details.
.\"
.TH pvsecret-create-meta 1 "2023-10-09" "s390-tools" "UV-Secret Manual"
.TH pvsecret-create-meta 1 "2024-01-30" "s390-tools" "UV-Secret Manual"
.nh
.ad l
.SH NAME

View File

@@ -3,7 +3,7 @@
.\" it under the terms of the MIT license. See LICENSE for details.
.\"
.TH pvsecret-create 1 "2023-10-09" "s390-tools" "UV-Secret Manual"
.TH pvsecret-create 1 "2024-01-30" "s390-tools" "UV-Secret Manual"
.nh
.ad l
.SH NAME
@@ -146,6 +146,37 @@ Possible values:
.RS 4
- \fBdisable-dump\fP: Disables host-initiated dumping for the target guest instance.
.RE
.RE
.PP
\-\-user-data <FILE>
.RS 4
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(secp521r1) 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.
.RE
.RE
.PP
\-\-user-sign-key <FILE>
.RS 4
Use the content of FILE as user signing key. Adds a signature defined 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.
.RE
.RE

View File

@@ -3,7 +3,7 @@
.\" it under the terms of the MIT license. See LICENSE for details.
.\"
.TH pvsecret-list 1 "2023-10-09" "s390-tools" "UV-Secret Manual"
.TH pvsecret-list 1 "2024-01-30" "s390-tools" "UV-Secret Manual"
.nh
.ad l
.SH NAME

View File

@@ -3,7 +3,7 @@
.\" it under the terms of the MIT license. See LICENSE for details.
.\"
.TH pvsecret-lock 1 "2023-10-09" "s390-tools" "UV-Secret Manual"
.TH pvsecret-lock 1 "2024-01-30" "s390-tools" "UV-Secret Manual"
.nh
.ad l
.SH NAME

View File

@@ -0,0 +1,139 @@
.\" Copyright 2023 IBM Corp.
.\" s390-tools is free software; you can redistribute it and/or modify
.\" it under the terms of the MIT license. See LICENSE for details.
.\"
.TH pvsecret-verify 1 "2024-01-30" "s390-tools" "UV-Secret Manual"
.nh
.ad l
.SH NAME
\fBpvsecret verify\fP - Verify that an add-secret request is sane
\fB
.SH SYNOPSIS
.nf
.fam C
pvsecret verify [OPTIONS] <FILE>
.fam C
.fi
.SH DESCRIPTION
.PP
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. All data in the
request is in big endian.
.PP
\fIverify\fP checks the following:
.RS
.IP \[bu] 2
The first 6 bytes of the request are equal to: \fB6173 7263 624d | asrcbM\fP
.IP \[bu] 2
The sizes in the request header are sane and do not point out of the
file
.IP \[bu] 2
The request version is supported by the binary
.IP \[bu] 2
If user-data contains a signature, verify the signature using a public
key
.RE
.PP
The content of bytes 6&7 of the request define which kind
of user-data the request contains.
.IP \fB0x0000\fP 8
no user-data (512 bytes zero)
.IP \fB0x0001\fP 8
512 bytes user-data
.IP \fB0x0002\fP 8
265 bytes user-data| 139 bytes ecdsa signature | 5 bytes reserved | 2 bytes
signature size | ...
.IP \fB0x0003\fP 8
256 bytes user-data | 256 bytes rsa2048 signature
.IP \fB0x0004\fP 8
128 bytes user-data | 384 bytes rsa3072 signature
.PP
The actual user-data may be less than the capacity. If less data was provided
during \fIcreate\fP zeros are appended.
.
For type 2-4 The signature is calculated as follows:
.RS
.IP "1." 3
The request is generated with the user-data in place and zeros for the
signature data.
.IP "2." 3
The signature is calculated for the request. The signature signs the
authenticated data and the encrypted data, but not the request tag. I.e. the
signature signs the whole request but the last 16 bytes and with the signature
bytes set to zero.
.IP "3." 3
The signature is inserted to its location in the request.
.IP "4." 3
The request GCM tag is calculated.
.PP
.RE
The verification process works as follows:
.RS
.IP "1." 3
copy the signature to a buffer
.IP "2." 3
overwrite the signature with zeros
.IP "3." 3
verify the signature of the request but the last 16 bytes
.RE
.SH OPTIONS
.PP
<FILE>
.RS 4
Specify the request to be checked.
.RE
.RE
.PP
\-\-user-cert <FILE>
.RS 4
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).
.RE
.RE
.PP
\-o, \-\-output <FILE>
.RS 4
Store the result in FILE If the request contained abirtary user-data the output
contains this user-data with padded zeros if available.
[default: '-']
.RE
.RE
.SH EXAMPLES
.PP
Create the add-secret request on a trusted system with signed user datai similar to the example for \fFpvsecret\fP. Let's assume there are three more files present .\fFuser_data\fP contains ascii "some example user-data", a private user-signing key e.g. rsa3072 \fFusr_sgn_key.priv.pem\fF, and a certificate containing the corresponding public key to the private rsa3072 key \fFuser_cert.pem\fP.
.PP
.RS
.IP trusted:~$ 12
pvsecret create -k hkd.crt --cert CA.crt --cert ibmsk.crt --hdr pvimage -o addsecreq.bin --user-data user_data --user-sign-key usr_sgn_key.priv.pem association EXAMPLE
.RE
.RS
Successfully generated the request
.br
Successfully wrote association info to 'EXAMPLE.yaml'
.RE
For example, on the SE-guest, perform \fIverify\fP on the request to verify the user-signature and the saneness of the request. On success, The user-data is printed to stdout (if \fI--output\fP was not specified) and \fFSuccesfully verified the request.\fP is printed to stderr.
.PP
.RS
.IP seguest:~$ 12
pvsecret verify --user-cert user_cert.pem -o addsecreq.bin
.RE
.RS
some example user-data
.br
Successfully verified the request
.RE
.SH "SEE ALSO"
.sp
\fBpvsecret\fR(1)

View File

@@ -3,7 +3,7 @@
.\" it under the terms of the MIT license. See LICENSE for details.
.\"
.TH pvsecret 1 "2023-10-09" "s390-tools" "UV-Secret Manual"
.TH pvsecret 1 "2024-01-30" "s390-tools" "UV-Secret Manual"
.nh
.ad l
.SH NAME
@@ -30,7 +30,7 @@ guest where you want to inject the secrets. This approach prevents the secrets
from being in cleartext on the guest. For extra safety, do an attestation with
\fBpvattest\fR of your guest beforehand, and include the configuration UID in
the secret request using \fB--cuid\fR. Refer to \fBpvsecret-add\fR(1) for more
information. For all certificates, revocation lists, and host-key documents,
information. For all certificates, revocation lists, and host-key documents,
both the PEM and DER input formats are supported.
.SH OPTIONS
@@ -96,4 +96,4 @@ On the SE-guest, \fIlock\fP the secret store.
.fi
.SH "SEE ALSO"
.sp
\fBpvsecret-create\fR(1) \fBpvsecret-add\fR(1) \fBpvsecret-lock\fR(1) \fBpvsecret-list\fR(1)
\fBpvsecret-create\fR(1) \fBpvsecret-add\fR(1) \fBpvsecret-lock\fR(1) \fBpvsecret-list\fR(1) \fBpvsecret-verify\fR(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,

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;

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

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(())
}

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 {