Ana Guerrero 2025-01-12 10:14:00 +00:00 committed by Git OBS Bridge
commit 201c253b02
23 changed files with 5777 additions and 7 deletions

View File

@ -0,0 +1,147 @@
From 1e44ace41de3cbd744b22a8f9835473b091186e0 Mon Sep 17 00:00:00 2001
From: Steffen Eiden <seiden@linux.ibm.com>
Date: Thu, 18 Jul 2024 10:55:45 +0200
Subject: [PATCH] rust/pvsecret: Refactor writing secret
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Refactor the writing of secret-type dependent output files to ease
extensions.
Reviewed-by: Marc Hartmayer <marc@linux.ibm.com>
Reviewed-by: Christoph Schlameuss <schlameuss@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
---
rust/pv/src/uvsecret/guest_secret.rs | 2 +-
rust/pvsecret/src/cmd/create.rs | 89 +++++++++++++++-------------
2 files changed, 48 insertions(+), 43 deletions(-)
diff --git a/rust/pv/src/uvsecret/guest_secret.rs b/rust/pv/src/uvsecret/guest_secret.rs
index 509691fa..4f1db31c 100644
--- a/rust/pv/src/uvsecret/guest_secret.rs
+++ b/rust/pv/src/uvsecret/guest_secret.rs
@@ -68,7 +68,7 @@ impl GuestSecret {
}
/// Reference to the confidential data
- pub(crate) fn confidential(&self) -> &[u8] {
+ pub fn confidential(&self) -> &[u8] {
match &self {
Self::Null => &[],
Self::Association { secret, .. } => secret.value().as_slice(),
diff --git a/rust/pvsecret/src/cmd/create.rs b/rust/pvsecret/src/cmd/create.rs
index 808b29e1..9251c38c 100644
--- a/rust/pvsecret/src/cmd/create.rs
+++ b/rust/pvsecret/src/cmd/create.rs
@@ -62,7 +62,7 @@ pub fn create(opt: &CreateSecretOpt) -> Result<()> {
write_out(&opt.output, ser_asrbc, "add-secret request")?;
info!("Successfully wrote the request to '{}'", &opt.output);
- write_secret(&opt.secret, &asrcb, &opt.output)
+ write_secret(&opt.secret, asrcb.guest_secret(), &opt.output)
}
/// Read+parse the first key from the buffer.
@@ -206,54 +206,59 @@ fn read_cuid(asrcb: &mut AddSecretRequest, opt: &CreateSecretOpt) -> Result<()>
Ok(())
}
+// Write non confidential data (=name+id) to a yaml stdout
+fn write_yaml<P: AsRef<Path>>(
+ name: &str,
+ guest_secret: &GuestSecret,
+ stdout: &bool,
+ outp_path: P,
+) -> Result<()> {
+ debug!("Non-confidential secret information: {guest_secret:x?}");
+
+ let secret_info = serde_yaml::to_string(guest_secret)?;
+ if stdout.to_owned() {
+ println!("{secret_info}");
+ return Ok(());
+ }
+
+ let gen_name: String = name
+ .chars()
+ .map(|c| if c.is_whitespace() { '_' } else { c })
+ .collect();
+ let mut yaml_path = outp_path
+ .as_ref()
+ .parent()
+ .with_context(|| format!("Cannot open directory of {:?}", outp_path.as_ref()))?
+ .to_owned();
+ yaml_path.push(gen_name);
+ yaml_path.set_extension("yaml");
+ write_out(&yaml_path, secret_info, "secret information")?;
+ warn!(
+ "Successfully wrote secret info to '{}'",
+ yaml_path.display().to_string()
+ );
+ Ok(())
+}
+
/// Write the generated secret (if any) to the specified output stream
fn write_secret<P: AsRef<Path>>(
secret: &AddSecretType,
- asrcb: &AddSecretRequest,
+ guest_secret: &GuestSecret,
outp_path: P,
) -> Result<()> {
- if let AddSecretType::Association {
- name,
- stdout,
- output_secret: secret_out,
- ..
- } = secret
- {
- let gen_name: String = name
- .chars()
- .map(|c| if c.is_whitespace() { '_' } else { c })
- .collect();
- let mut gen_path = outp_path
- .as_ref()
- .parent()
- .with_context(|| format!("Cannot open directory of {:?}", outp_path.as_ref()))?
- .to_owned();
- gen_path.push(format!("{gen_name}.yaml"));
-
- // write non confidential data (=name+id) to a yaml
- let secret_info = serde_yaml::to_string(asrcb.guest_secret())?;
- if stdout.to_owned() {
- println!("{secret_info}");
- } else {
- write_out(&gen_path, secret_info, "association secret info")?;
- debug!(
- "Non-confidential secret information: {:x?}",
- asrcb.guest_secret()
- );
- warn!(
- "Successfully wrote association info to '{}'",
- gen_path.display()
- );
- }
-
- if let Some(path) = secret_out {
- if let GuestSecret::Association { secret, .. } = asrcb.guest_secret() {
- write_out(path, secret.value(), "Association secret")?
- } else {
- unreachable!("The secret type has to be `association` at this point (bug)!")
+ match secret {
+ AddSecretType::Association {
+ name,
+ stdout,
+ output_secret,
+ ..
+ } => {
+ write_yaml(name, guest_secret, stdout, outp_path)?;
+ if let Some(path) = output_secret {
+ write_out(path, guest_secret.confidential(), "Association secret")?
}
- info!("Successfully wrote generated association secret to '{path}'");
}
+ _ => (),
};
Ok(())
}

View File

@ -0,0 +1,467 @@
From d1636168b26cc842bc0766235c8a4f2da9663f20 Mon Sep 17 00:00:00 2001
From: Steffen Eiden <seiden@linux.ibm.com>
Date: Tue, 5 Mar 2024 10:46:29 +0100
Subject: [PATCH] rust/pv: Support for writing data in PEM format
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Use existing OpenSSL functionalities to create PEM files containing
arbitrary data.
Acked-by: Marc Hartmayer <marc@linux.ibm.com>
Acked-by: Christoph Schlameuss <schlameuss@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
---
rust/pv/src/error.rs | 3 +
rust/pv/src/lib.rs | 6 +
rust/pv/src/openssl_extensions/bio.rs | 85 +++++++
rust/pv/src/openssl_extensions/mod.rs | 2 +
.../src/openssl_extensions/stackable_crl.rs | 41 +---
rust/pv/src/pem_utils.rs | 222 ++++++++++++++++++
6 files changed, 321 insertions(+), 38 deletions(-)
create mode 100644 rust/pv/src/openssl_extensions/bio.rs
create mode 100644 rust/pv/src/pem_utils.rs
diff --git a/rust/pv/src/error.rs b/rust/pv/src/error.rs
index af85e93e..3ba808f2 100644
--- a/rust/pv/src/error.rs
+++ b/rust/pv/src/error.rs
@@ -106,6 +106,9 @@ pub enum Error {
)]
AddDataMissing(&'static str),
+ #[error("An ASCII string was expected, but non-ASCII characters were received.")]
+ NonAscii,
+
// errors from other crates
#[error(transparent)]
PvCore(#[from] pv_core::Error),
diff --git a/rust/pv/src/lib.rs b/rust/pv/src/lib.rs
index 7a33210c..ec31b9a4 100644
--- a/rust/pv/src/lib.rs
+++ b/rust/pv/src/lib.rs
@@ -37,6 +37,7 @@ mod brcb;
mod crypto;
mod error;
mod openssl_extensions;
+mod pem_utils;
mod req;
mod utils;
mod uvattest;
@@ -71,6 +72,11 @@ pub mod attest {
};
}
+/// Definitions and functions to write objects in PEM format
+pub mod pem {
+ pub use crate::pem_utils::Pem;
+}
+
/// Miscellaneous functions and definitions
pub mod misc {
pub use pv_core::misc::*;
diff --git a/rust/pv/src/openssl_extensions/bio.rs b/rust/pv/src/openssl_extensions/bio.rs
new file mode 100644
index 00000000..73528eed
--- /dev/null
+++ b/rust/pv/src/openssl_extensions/bio.rs
@@ -0,0 +1,85 @@
+// SPDX-License-Identifier: MIT
+//
+// Copyright IBM Corp. 2024
+
+use core::slice;
+use openssl::error::ErrorStack;
+use openssl_sys::BIO_new_mem_buf;
+use std::ffi::c_int;
+use std::{marker::PhantomData, ptr};
+
+pub struct BioMem(*mut openssl_sys::BIO);
+
+impl Drop for BioMem {
+ fn drop(&mut self) {
+ // SAFETY: Pointer is valid. The pointer value is dropped after the free.
+ unsafe {
+ openssl_sys::BIO_free_all(self.0);
+ }
+ }
+}
+
+impl BioMem {
+ pub fn new() -> Result<Self, ErrorStack> {
+ openssl_sys::init();
+
+ // SAFETY: Returns a valid pointer or null. null-case is tested right after this.
+ let bio = unsafe { openssl_sys::BIO_new(openssl_sys::BIO_s_mem()) };
+ match bio.is_null() {
+ true => Err(ErrorStack::get()),
+ false => Ok(Self(bio)),
+ }
+ }
+
+ pub fn as_ptr(&self) -> *mut openssl_sys::BIO {
+ self.0
+ }
+
+ /// Copies the content of this slice into a Vec
+ pub fn to_vec(&self) -> Vec<u8> {
+ let buf;
+ // SAFTEY: BIO provides a continuous memory that can be used to build a slice.
+ unsafe {
+ let mut ptr = ptr::null_mut();
+ let len = openssl_sys::BIO_get_mem_data(self.0, &mut ptr);
+ buf = slice::from_raw_parts(ptr as *const _ as *const _, len as usize)
+ }
+ buf.to_vec()
+ }
+}
+
+pub struct BioMemSlice<'a>(*mut openssl_sys::BIO, PhantomData<&'a [u8]>);
+impl Drop for BioMemSlice<'_> {
+ fn drop(&mut self) {
+ // SAFETY: Pointer is valid. The pointer value is dropped after the free.
+ unsafe {
+ openssl_sys::BIO_free_all(self.0);
+ }
+ }
+}
+
+impl<'a> BioMemSlice<'a> {
+ pub fn new(buf: &'a [u8]) -> Result<BioMemSlice<'a>, ErrorStack> {
+ openssl_sys::init();
+
+ // SAFETY: `buf` is a slice (i.e. pointer+size) pointing to a valid memory region.
+ // So the resulting bio is valid. Lifetime of the slice is connected by this Rust
+ // structure.
+ assert!(buf.len() <= c_int::MAX as usize);
+ let bio = unsafe {
+ {
+ let r = BIO_new_mem_buf(buf.as_ptr() as *const _, buf.len() as c_int);
+ match r.is_null() {
+ true => Err(ErrorStack::get()),
+ false => Ok(r),
+ }
+ }?
+ };
+
+ Ok(BioMemSlice(bio, PhantomData))
+ }
+
+ pub fn as_ptr(&self) -> *mut openssl_sys::BIO {
+ self.0
+ }
+}
diff --git a/rust/pv/src/openssl_extensions/mod.rs b/rust/pv/src/openssl_extensions/mod.rs
index fab26638..f6234e5d 100644
--- a/rust/pv/src/openssl_extensions/mod.rs
+++ b/rust/pv/src/openssl_extensions/mod.rs
@@ -6,8 +6,10 @@
/// Extensions to the rust-openssl crate
mod akid;
+mod bio;
mod crl;
mod stackable_crl;
pub use akid::*;
+pub use bio::*;
pub use crl::*;
diff --git a/rust/pv/src/openssl_extensions/stackable_crl.rs b/rust/pv/src/openssl_extensions/stackable_crl.rs
index aef7cf86..12a9f9de 100644
--- a/rust/pv/src/openssl_extensions/stackable_crl.rs
+++ b/rust/pv/src/openssl_extensions/stackable_crl.rs
@@ -2,16 +2,14 @@
//
// Copyright IBM Corp. 2023
-use std::{marker::PhantomData, ptr};
-
+use crate::openssl_extensions::bio::BioMemSlice;
use foreign_types::{ForeignType, ForeignTypeRef};
use openssl::{
error::ErrorStack,
stack::Stackable,
x509::{X509Crl, X509CrlRef},
};
-use openssl_sys::BIO_new_mem_buf;
-use std::ffi::c_int;
+use std::ptr;
#[derive(Debug)]
pub struct StackableX509Crl(*mut openssl_sys::X509_CRL);
@@ -62,44 +60,11 @@ impl Stackable for StackableX509Crl {
type StackType = openssl_sys::stack_st_X509_CRL;
}
-pub struct MemBioSlice<'a>(*mut openssl_sys::BIO, PhantomData<&'a [u8]>);
-impl Drop for MemBioSlice<'_> {
- fn drop(&mut self) {
- unsafe {
- openssl_sys::BIO_free_all(self.0);
- }
- }
-}
-
-impl<'a> MemBioSlice<'a> {
- pub fn new(buf: &'a [u8]) -> Result<MemBioSlice<'a>, ErrorStack> {
- openssl_sys::init();
-
- assert!(buf.len() <= c_int::MAX as usize);
- let bio = unsafe {
- {
- let r = BIO_new_mem_buf(buf.as_ptr() as *const _, buf.len() as c_int);
- if r.is_null() {
- Err(ErrorStack::get())
- } else {
- Ok(r)
- }
- }?
- };
-
- Ok(MemBioSlice(bio, PhantomData))
- }
-
- pub fn as_ptr(&self) -> *mut openssl_sys::BIO {
- self.0
- }
-}
-
impl StackableX509Crl {
pub fn stack_from_pem(pem: &[u8]) -> Result<Vec<X509Crl>, ErrorStack> {
unsafe {
openssl_sys::init();
- let bio = MemBioSlice::new(pem)?;
+ let bio = BioMemSlice::new(pem)?;
let mut crls = vec![];
loop {
diff --git a/rust/pv/src/pem_utils.rs b/rust/pv/src/pem_utils.rs
new file mode 100644
index 00000000..e6462519
--- /dev/null
+++ b/rust/pv/src/pem_utils.rs
@@ -0,0 +1,222 @@
+// SPDX-License-Identifier: MIT
+//
+// Copyright IBM Corp. 2024
+
+use crate::Result;
+use crate::{openssl_extensions::BioMem, Error};
+use openssl::error::ErrorStack;
+use pv_core::request::Confidential;
+use std::{
+ ffi::{c_char, CString},
+ fmt::Display,
+};
+
+mod ffi {
+ use openssl_sys::BIO;
+ use std::ffi::{c_char, c_int, c_long, c_uchar};
+ extern "C" {
+ pub fn PEM_write_bio(
+ bio: *mut BIO,
+ name: *const c_char,
+ header: *const c_char,
+ data: *const c_uchar,
+ len: c_long,
+ ) -> c_int;
+ }
+}
+
+/// Thin wrapper around [`CString`] only containing ASCII chars.
+#[derive(Debug)]
+struct AsciiCString(CString);
+
+impl AsciiCString {
+ /// Convert from string
+ ///
+ /// # Returns
+ /// Error if string is not ASCII or contains null chars
+ pub(crate) fn from_str(s: &str) -> Result<Self> {
+ match s.is_ascii() {
+ true => Ok(Self(CString::new(s).map_err(|_| Error::NonAscii)?)),
+ false => Err(Error::NonAscii),
+ }
+ }
+
+ fn as_ptr(&self) -> *const c_char {
+ self.0.as_ptr()
+ }
+}
+
+/// Helper struct to construct the PEM format
+#[derive(Debug)]
+struct InnerPem<'d> {
+ name: AsciiCString,
+ header: Option<AsciiCString>,
+ data: &'d [u8],
+}
+
+impl<'d> InnerPem<'d> {
+ fn new(name: &str, header: Option<String>, data: &'d [u8]) -> Result<Self> {
+ Ok(Self {
+ name: AsciiCString::from_str(name)?,
+ header: match header {
+ Some(h) => Some(AsciiCString::from_str(&h)?),
+ None => None,
+ },
+ data,
+ })
+ }
+
+ /// Generate PEM representation of the data
+ fn to_pem(&self) -> Result<Vec<u8>> {
+ let bio = BioMem::new()?;
+ let hdr_ptr = match self.header {
+ // avoid moving variable -> use reference
+ Some(ref h) => h.as_ptr(),
+ None => std::ptr::null(),
+ };
+
+ // SAFETY:
+ // All pointers point to valid C strings or memory regions
+ let rc = unsafe {
+ ffi::PEM_write_bio(
+ bio.as_ptr(),
+ self.name.as_ptr(),
+ hdr_ptr,
+ self.data.as_ptr(),
+ self.data.len() as std::ffi::c_long,
+ )
+ };
+
+ match rc {
+ 1 => Err(Error::InternalSsl("Could not write PEM", ErrorStack::get())),
+ _ => Ok(bio.to_vec()),
+ }
+ }
+}
+
+/// Data in PEM format
+///
+/// Displays into a printable PEM structure.
+/// Must be constructed from another structure in this library.
+///
+/// ```rust,ignore
+/// let pem: Pem = ...;
+/// println!("PEM {pem}");
+/// ```
+/// ```PEM
+///-----BEGIN <name>-----
+///<header>
+///
+///<Base64 formatted binary data>
+///-----END <name>-----
+
+#[derive(Debug)]
+pub struct Pem {
+ pem: Confidential<String>,
+}
+
+#[allow(unused)]
+impl Pem {
+ /// Create a new PEM structure.
+ ///
+ /// # Errors
+ ///
+ /// This function will return an error if name or header contain non-ASCII chars, or OpenSSL
+ /// could not generate the PEM (very likely due to OOM).
+ pub(crate) fn new<D, H>(name: &str, header: H, data: D) -> Result<Self>
+ where
+ D: AsRef<[u8]>,
+ H: Into<Option<String>>,
+ {
+ let mut header = header.into();
+ let header = match header {
+ Some(h) if h.ends_with('\n') => Some(h),
+ Some(h) if h.is_empty() => None,
+ Some(mut h) => {
+ h.push('\n');
+ Some(h)
+ }
+ None => None,
+ };
+
+ let inner_pem = InnerPem::new(name, header, data.as_ref())?;
+
+ // Create the PEM format eagerly so that to_string/display cannot fail because of ASCII or OpenSSL Errors
+ // Both error should be very unlikely
+ // OpenSSL should be able to create PEM if there is enough memory and produce a non-null
+ // terminated ASCII-string
+ // Unwrap succeeds it's all ASCII
+ // Std lib implements all the conversations without a copy
+ let pem = CString::new(inner_pem.to_pem()?)
+ .map_err(|_| Error::NonAscii)?
+ .into_string()
+ .unwrap()
+ .into();
+
+ Ok(Self { pem })
+ }
+
+ /// Converts the PEM-data into a byte vector.
+ ///
+ /// This consumes the `PEM`.
+ #[inline]
+ #[must_use = "`self` will be dropped if the result is not used"]
+ pub fn into_bytes(self) -> Confidential<Vec<u8>> {
+ self.pem.into_inner().into_bytes().into()
+ }
+}
+
+impl Display for Pem {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ self.pem.value().fmt(f)
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn no_data() {
+ const EXP: &str =
+ "-----BEGIN PEM test-----\ntest hdr value: 17\n\n-----END PEM test-----\n";
+ let test_pem = Pem::new("PEM test", "test hdr value: 17".to_string(), []).unwrap();
+ let pem_str = test_pem.to_string();
+ assert_eq!(pem_str, EXP);
+ }
+
+ #[test]
+ fn no_hdr() {
+ const EXP: &str =
+ "-----BEGIN PEM test-----\ndmVyeSBzZWNyZXQga2V5\n-----END PEM test-----\n";
+ let test_pem = Pem::new("PEM test", None, "very secret key").unwrap();
+ let pem_str = test_pem.to_string();
+ assert_eq!(pem_str, EXP);
+ }
+
+ #[test]
+ fn some_data() {
+ const EXP: &str= "-----BEGIN PEM test-----\ntest hdr value: 17\n\ndmVyeSBzZWNyZXQga2V5\n-----END PEM test-----\n";
+ let test_pem = Pem::new(
+ "PEM test",
+ "test hdr value: 17".to_string(),
+ "very secret key",
+ )
+ .unwrap();
+ let pem_str = test_pem.to_string();
+ assert_eq!(pem_str, EXP);
+ }
+
+ #[test]
+ fn data_linebreak() {
+ const EXP: &str= "-----BEGIN PEM test-----\ntest hdr value: 17\n\ndmVyeSBzZWNyZXQga2V5\n-----END PEM test-----\n";
+ let test_pem = Pem::new(
+ "PEM test",
+ "test hdr value: 17\n".to_string(),
+ "very secret key",
+ )
+ .unwrap();
+ let pem_str = test_pem.to_string();
+ assert_eq!(pem_str, EXP);
+ }
+}

View File

@ -0,0 +1,57 @@
From 69eb06f39e5134565babfe96c66a3786c0a571cf Mon Sep 17 00:00:00 2001
From: Steffen Eiden <seiden@linux.ibm.com>
Date: Tue, 20 Feb 2024 14:50:47 +0100
Subject: [PATCH] rust/pv_core: Update ffi.rs to linux/uvdevice.h v6.13
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
While at it, add a file global #[allow(dead_code)].
The file is a rustified copy of linux/arch/s390/include/uapi/asm/uvdevice.h
and there might be things that are not needed here but are defined in that header.
Acked-by: Marc Hartmayer <marc@linux.ibm.com>
Reviewed-by: Christoph Schlameuss <schlameuss@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
---
rust/pv_core/src/uvdevice/ffi.rs | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/rust/pv_core/src/uvdevice/ffi.rs b/rust/pv_core/src/uvdevice/ffi.rs
index bbcc5867..3d9998db 100644
--- a/rust/pv_core/src/uvdevice/ffi.rs
+++ b/rust/pv_core/src/uvdevice/ffi.rs
@@ -2,6 +2,13 @@
//
// Copyright IBM Corp. 2023
+// This file is a rustified copy of linux/arch/s390/include/uapi/asm/uvdevice.h
+// There might be things that are not needed here but nontheless defined in that header.
+// Those two files should be in sync -> there might be unused/dead code.
+//
+// The `UVIO_IOCTL_*` and `UVIO_SUPP_*` macros
+#![allow(dead_code)]
+
use std::mem::size_of;
use crate::{assert_size, static_assert};
@@ -11,9 +18,8 @@ pub const UVIO_ATT_ARCB_MAX_LEN: usize = 0x100000;
pub const UVIO_ATT_MEASUREMENT_MAX_LEN: usize = 0x8000;
pub const UVIO_ATT_ADDITIONAL_MAX_LEN: usize = 0x8000;
pub const UVIO_ADD_SECRET_MAX_LEN: usize = 0x100000;
-#[allow(unused)]
-// here for completeness
pub const UVIO_LIST_SECRETS_LEN: usize = 0x1000;
+pub const UVIO_RETR_SECRET_MAX_LEN: usize = 0x2000;
// equal to ascii 'u'
pub const UVIO_TYPE_UVC: u8 = 117u8;
@@ -23,6 +29,7 @@ pub const UVIO_IOCTL_ATT_NR: u8 = 1;
pub const UVIO_IOCTL_ADD_SECRET_NR: u8 = 2;
pub const UVIO_IOCTL_LIST_SECRETS_NR: u8 = 3;
pub const UVIO_IOCTL_LOCK_SECRETS_NR: u8 = 4;
+pub const UVIO_IOCTL_RETR_SECRET_NR: u8 = 5;
/// Uvdevice IOCTL control block
/// Programs can use this struct to communicate with the uvdevice via IOCTLs

View File

@ -0,0 +1,204 @@
From 01cd81ecf5d1a7e1e504ae1b67692cf63cd4b51d Mon Sep 17 00:00:00 2001
From: Steffen Eiden <seiden@linux.ibm.com>
Date: Tue, 5 Mar 2024 11:56:57 +0100
Subject: [PATCH] rust/pv_core: Retrieve Secret UVC
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Create the uvdevice-IOCTL functionality for the new Retrieve Secret UVC.
Reviewed-by: Christoph Schlameuss <schlameuss@linux.ibm.com>
Acked-by: Marc Hartmayer <marc@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
---
rust/pv_core/src/error.rs | 7 ++
rust/pv_core/src/lib.rs | 2 +-
rust/pv_core/src/uvdevice/secret.rs | 97 +++++++++++++++++++++++-
rust/pv_core/src/uvdevice/secret_list.rs | 15 ++++
4 files changed, 118 insertions(+), 3 deletions(-)
diff --git a/rust/pv_core/src/error.rs b/rust/pv_core/src/error.rs
index 20fca24d..ba7b7e26 100644
--- a/rust/pv_core/src/error.rs
+++ b/rust/pv_core/src/error.rs
@@ -4,6 +4,8 @@
use std::path::PathBuf;
+use crate::uv::SecretId;
+
/// Result type for this crate
pub type Result<T, E = Error> = std::result::Result<T, E>;
@@ -70,6 +72,11 @@ pub enum Error {
#[error("The attestation request does not specify a measurement size or measurement data.")]
BinArcbNoMeasurement,
+ #[error(
+ "The secret with the ID {id} cannot be retrieved. The requested size is too large ({size})"
+ )]
+ InvalidRetrievableSecretType { id: SecretId, size: usize },
+
// errors from other crates
#[error(transparent)]
Io(#[from] std::io::Error),
diff --git a/rust/pv_core/src/lib.rs b/rust/pv_core/src/lib.rs
index 349c0b28..5922211f 100644
--- a/rust/pv_core/src/lib.rs
+++ b/rust/pv_core/src/lib.rs
@@ -32,7 +32,7 @@ pub mod misc {
/// [`crate::uv::UvCmd`]
pub mod uv {
pub use crate::uvdevice::attest::AttestationCmd;
- pub use crate::uvdevice::secret::{AddCmd, ListCmd, LockCmd};
+ pub use crate::uvdevice::secret::{AddCmd, ListCmd, LockCmd, RetrieveCmd};
pub use crate::uvdevice::secret_list::{ListableSecretType, SecretEntry, SecretId, SecretList};
pub use crate::uvdevice::{ConfigUid, UvCmd, UvDevice, UvDeviceInfo, UvFlags, UvcSuccess};
}
diff --git a/rust/pv_core/src/uvdevice/secret.rs b/rust/pv_core/src/uvdevice/secret.rs
index 6c22b6ed..263f17d5 100644
--- a/rust/pv_core/src/uvdevice/secret.rs
+++ b/rust/pv_core/src/uvdevice/secret.rs
@@ -3,8 +3,15 @@
// Copyright IBM Corp. 2023
use super::ffi;
-use crate::{request::MagicValue, uv::UvCmd, uvsecret::AddSecretMagic, Error, Result, PAGESIZE};
-use std::io::Read;
+use crate::{
+ request::{Confidential, MagicValue},
+ uv::{SecretEntry, UvCmd},
+ uvsecret::AddSecretMagic,
+ Error, Result, PAGESIZE,
+};
+use log::debug;
+use std::{io::Read, mem::size_of_val};
+use zerocopy::AsBytes;
/// _List Secrets_ Ultravisor command.
///
@@ -116,3 +123,89 @@ impl UvCmd for LockCmd {
}
}
}
+
+/// Retrieve a secret value from UV store
+#[derive(Debug)]
+pub struct RetrieveCmd {
+ entry: SecretEntry,
+ key: Confidential<Vec<u8>>,
+}
+
+impl RetrieveCmd {
+ /// Maximum size of a retrieved key (=2 pages)
+ pub const MAX_SIZE: usize = ffi::UVIO_RETR_SECRET_MAX_LEN;
+
+ /// Create a retrieve-secret UVC from a [`SecretEntry`].
+ ///
+ /// This uses the index of the secret entry for the UVC.
+ pub fn from_entry(entry: SecretEntry) -> Result<Self> {
+ entry.try_into()
+ }
+
+ /// Transform a [`RetrieveCmd`] into a key-vector.
+ ///
+ /// Only makes sense to call after a successful UVC execution.
+ pub fn into_key(self) -> Confidential<Vec<u8>> {
+ self.key
+ }
+
+ /// Get the secret entry
+ ///
+ /// Get the secret entry that is used as metadata to retrieve the secret
+ pub fn meta_data(&self) -> &SecretEntry {
+ &self.entry
+ }
+}
+
+impl TryFrom<SecretEntry> for RetrieveCmd {
+ type Error = Error;
+
+ fn try_from(entry: SecretEntry) -> Result<Self> {
+ let len = entry.secret_size() as usize;
+
+ // Next to impossible if the secret entry is a valid response from UV
+ if len > Self::MAX_SIZE {
+ return Err(Error::InvalidRetrievableSecretType {
+ id: entry.secret_id().to_owned(),
+ size: len,
+ });
+ }
+
+ // Ensure that an u16 fits into the buffer.
+ let size = std::cmp::max(size_of_val(&entry.index()), len);
+ debug!("Create a buf with {} elements", size);
+ let mut buf = vec![0; size];
+ // The IOCTL expects the secret index in the first two bytes of the buffer. They will be
+ // overwritten in the response
+ entry.index_be().write_to_prefix(&mut buf).unwrap();
+ Ok(Self {
+ entry,
+ key: buf.into(),
+ })
+ }
+}
+
+impl UvCmd for RetrieveCmd {
+ const UV_IOCTL_NR: u8 = ffi::UVIO_IOCTL_RETR_SECRET_NR;
+
+ fn rc_fmt(&self, rc: u16, _: u16) -> Option<&'static str> {
+ match rc {
+ // should not appear (TM), software creates request from a list item
+ 0x0009 => Some("the allocated buffer is to small to store the secret"),
+ // should not appear (TM), kernel allocates the memory
+ 0x0102 => {
+ Some("access exception recognized when accessing retrieved secret storage area")
+ }
+ // should not appear (TM), software creates request from a list item
+ 0x010f => Some("the Secret Store is empty"),
+ // should not appear (TM), software creates request from a list item
+ 0x0110 => Some("the Secret Store does not contain a secret with the specified index"),
+ 0x0111 => Some("the secret is not retrievable"),
+ _ => None,
+ }
+ }
+
+ fn data(&mut self) -> Option<&mut [u8]> {
+ Some(self.key.value_mut())
+ }
+}
diff --git a/rust/pv_core/src/uvdevice/secret_list.rs b/rust/pv_core/src/uvdevice/secret_list.rs
index d20928b5..0a8af504 100644
--- a/rust/pv_core/src/uvdevice/secret_list.rs
+++ b/rust/pv_core/src/uvdevice/secret_list.rs
@@ -110,6 +110,11 @@ impl SecretEntry {
self.index.get()
}
+ /// Returns the index of this [`SecretEntry`] in BE.
+ pub(crate) fn index_be(&self) -> &U16<BigEndian> {
+ &self.index
+ }
+
/// Returns the secret type of this [`SecretEntry`].
pub fn stype(&self) -> ListableSecretType {
self.stype.into()
@@ -127,6 +132,16 @@ impl SecretEntry {
pub fn id(&self) -> &[u8] {
self.id.as_ref()
}
+
+ /// Get the id as [`SecretId`] reference
+ pub(crate) fn secret_id(&self) -> &SecretId {
+ &self.id
+ }
+
+ /// Returns the secret size of this [`SecretEntry`].
+ pub fn secret_size(&self) -> u32 {
+ self.len.get()
+ }
}
impl Display for SecretEntry {

View File

@ -0,0 +1,710 @@
From 4af137f4fad8638169ccf0ddcb6dc4b0fe8fb1c1 Mon Sep 17 00:00:00 2001
From: Steffen Eiden <seiden@linux.ibm.com>
Date: Tue, 5 Mar 2024 12:16:44 +0100
Subject: [PATCH] rust/pv_core: Support for listing Retrievable Secrets
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Add support for listing retrievable secrets in the List Secrets UVC.
Acked-by: Marc Hartmayer <marc@linux.ibm.com>
Reviewed-by: Christoph Schlameuss <schlameuss@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
---
rust/pv_core/src/lib.rs | 2 +
rust/pv_core/src/uvdevice.rs | 1 +
rust/pv_core/src/uvdevice/retr_secret.rs | 399 +++++++++++++++++++++++
rust/pv_core/src/uvdevice/secret_list.rs | 157 +++++++--
4 files changed, 536 insertions(+), 23 deletions(-)
create mode 100644 rust/pv_core/src/uvdevice/retr_secret.rs
diff --git a/rust/pv_core/src/lib.rs b/rust/pv_core/src/lib.rs
index 5922211f..caebfcea 100644
--- a/rust/pv_core/src/lib.rs
+++ b/rust/pv_core/src/lib.rs
@@ -32,6 +32,8 @@ pub mod misc {
/// [`crate::uv::UvCmd`]
pub mod uv {
pub use crate::uvdevice::attest::AttestationCmd;
+ pub use crate::uvdevice::retr_secret::RetrievableSecret;
+ pub use crate::uvdevice::retr_secret::{AesSizes, AesXtsSizes, EcCurves, HmacShaSizes};
pub use crate::uvdevice::secret::{AddCmd, ListCmd, LockCmd, RetrieveCmd};
pub use crate::uvdevice::secret_list::{ListableSecretType, SecretEntry, SecretId, SecretList};
pub use crate::uvdevice::{ConfigUid, UvCmd, UvDevice, UvDeviceInfo, UvFlags, UvcSuccess};
diff --git a/rust/pv_core/src/uvdevice.rs b/rust/pv_core/src/uvdevice.rs
index d4176815..e9848243 100644
--- a/rust/pv_core/src/uvdevice.rs
+++ b/rust/pv_core/src/uvdevice.rs
@@ -25,6 +25,7 @@ mod info;
mod test;
pub(crate) use ffi::uv_ioctl;
pub mod attest;
+pub mod retr_secret;
pub mod secret;
pub mod secret_list;
diff --git a/rust/pv_core/src/uvdevice/retr_secret.rs b/rust/pv_core/src/uvdevice/retr_secret.rs
new file mode 100644
index 00000000..490152b4
--- /dev/null
+++ b/rust/pv_core/src/uvdevice/retr_secret.rs
@@ -0,0 +1,399 @@
+// SPDX-License-Identifier: MIT
+//
+// Copyright IBM Corp. 2024
+
+use crate::uv::{ListableSecretType, RetrieveCmd};
+use serde::{Deserialize, Serialize, Serializer};
+use std::fmt::Display;
+
+/// Allowed sizes for AES keys
+#[non_exhaustive]
+#[derive(PartialEq, Eq, Debug)]
+pub enum AesSizes {
+ /// 128 bit key
+ Bits128,
+ /// 192 bit key
+ Bits192,
+ /// 256 bit key
+ Bits256,
+}
+
+impl AesSizes {
+ /// Construct the key-size from the bit-size.
+ ///
+ /// Returns [`None`] if the bit-size is not supported.
+ pub fn from_bits(bits: u32) -> Option<Self> {
+ match bits {
+ 128 => Some(Self::Bits128),
+ 192 => Some(Self::Bits192),
+ 256 => Some(Self::Bits256),
+ _ => None,
+ }
+ }
+
+ /// Returns the bit-size for the key-type
+ const fn bit_size(&self) -> u32 {
+ match self {
+ Self::Bits128 => 128,
+ Self::Bits192 => 192,
+ Self::Bits256 => 256,
+ }
+ }
+}
+
+impl Display for AesSizes {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{}", self.bit_size())
+ }
+}
+
+/// Allowed sizes for AES-XTS keys
+#[non_exhaustive]
+#[derive(PartialEq, Eq, Debug)]
+pub enum AesXtsSizes {
+ /// Two AES 128 bit keys
+ Bits128,
+ /// Two AES 256 bit keys
+ Bits256,
+}
+
+impl AesXtsSizes {
+ /// Construct the key-size from the bit-size.
+ ///
+ /// It's a key containing two keys; bit-size is half the number of bits it has
+ /// Returns [`None`] if the bit-size is not supported.
+ pub fn from_bits(bits: u32) -> Option<Self> {
+ match bits {
+ 128 => Some(Self::Bits128),
+ 256 => Some(Self::Bits256),
+ _ => None,
+ }
+ }
+
+ /// Returns the bit-size for the key-type
+ ///
+ /// It's a key containing two keys: bit-size is half the number of bits it has
+ const fn bit_size(&self) -> u32 {
+ match self {
+ Self::Bits128 => 128,
+ Self::Bits256 => 256,
+ }
+ }
+}
+
+impl Display for AesXtsSizes {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{}", self.bit_size())
+ }
+}
+
+/// Allowed sizes for HMAC-SHA keys
+#[non_exhaustive]
+#[derive(PartialEq, Eq, Debug)]
+pub enum HmacShaSizes {
+ /// SHA 256 bit
+ Sha256,
+ /// SHA 512 bit
+ Sha512,
+}
+
+impl HmacShaSizes {
+ /// Construct the key-size from the sha-size.
+ ///
+ /// FW expects maximum resistance keys (double the SHA size).
+ /// The `sha_size` is half of the number of bits in the key
+ /// Returns [`None`] if the `sha_size` is not supported.
+ pub fn from_sha_size(sha_size: u32) -> Option<Self> {
+ match sha_size {
+ 256 => Some(Self::Sha256),
+ 512 => Some(Self::Sha512),
+ _ => None,
+ }
+ }
+
+ /// Returns the sha-size for the key-type
+ ///
+ /// FW expects maximum resistance keys (double the SHA size).
+ /// The `sha_size` is half of the number of bits in the key
+ const fn sha_size(&self) -> u32 {
+ match self {
+ Self::Sha256 => 256,
+ Self::Sha512 => 512,
+ }
+ }
+}
+
+impl Display for HmacShaSizes {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{}", self.sha_size())
+ }
+}
+
+/// Allowed curves for EC private keys
+#[non_exhaustive]
+#[derive(PartialEq, Eq, Debug)]
+pub enum EcCurves {
+ /// secp256r1 or prime256v1 curve
+ Secp256R1,
+ /// secp384p1 curve
+ Secp384R1,
+ /// secp521r1 curve
+ Secp521R1,
+ /// ed25519 curve
+ Ed25519,
+ /// ed448 curve
+ Ed448,
+}
+
+impl EcCurves {
+ const fn exp_size(&self) -> usize {
+ match self {
+ Self::Secp256R1 => 32,
+ Self::Secp384R1 => 48,
+ Self::Secp521R1 => 80,
+ Self::Ed25519 => 32,
+ Self::Ed448 => 64,
+ }
+ }
+
+ /// Resizes the raw key to the expected size.
+ ///
+ /// See [`Vec::resize`]
+ pub fn resize_raw_key(&self, mut raw: Vec<u8>) -> Vec<u8> {
+ raw.resize(self.exp_size(), 0);
+ raw
+ }
+}
+
+// The names have to stay constant, otherwise the PEM contains invalid types
+impl Display for EcCurves {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Self::Secp256R1 => write!(f, "SECP256R1"),
+ Self::Secp384R1 => write!(f, "SECP384R1"),
+ Self::Secp521R1 => write!(f, "SECP521R1"),
+ Self::Ed25519 => write!(f, "ED25519"),
+ Self::Ed448 => write!(f, "ED448"),
+ }
+ }
+}
+
+/// Retrievable Secret types
+#[non_exhaustive]
+#[derive(PartialEq, Eq, Debug)]
+pub enum RetrievableSecret {
+ /// Plain-text secret
+ PlainText,
+ /// Protected AES key
+ Aes(AesSizes),
+ /// Protected AES-XTS key
+ AesXts(AesXtsSizes),
+ /// Protected HMAC-SHA key
+ HmacSha(HmacShaSizes),
+ /// Protected EC-private key
+ Ec(EcCurves),
+}
+
+// The names have to stay constant, otherwise the PEM contains invalid/unknown types
+impl Display for RetrievableSecret {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ // Alternate representation: Omit sizes/curves
+ if f.alternate() {
+ match self {
+ Self::PlainText => write!(f, "PLAINTEXT"),
+ Self::Aes(_) => write!(f, "AES-KEY"),
+ Self::AesXts(_) => write!(f, "AES-XTS-KEY"),
+ Self::HmacSha(_) => write!(f, "HMAC-SHA-KEY"),
+ Self::Ec(_) => write!(f, "EC-PRIVATE-KEY"),
+ }
+ } else {
+ match self {
+ Self::PlainText => write!(f, "PLAINTEXT"),
+ Self::Aes(s) => write!(f, "AES-{s}-KEY"),
+ Self::AesXts(s) => write!(f, "AES-XTS-{s}-KEY"),
+ Self::HmacSha(s) => write!(f, "HMAC-SHA-{s}-KEY"),
+ Self::Ec(c) => write!(f, "EC-{c}-PRIVATE-KEY"),
+ }
+ }
+ }
+}
+
+impl RetrievableSecret {
+ /// Report expected input types
+ pub fn expected(&self) -> String {
+ match self {
+ Self::PlainText => format!("less than {}", RetrieveCmd::MAX_SIZE),
+ Self::Aes(_) => "128, 192, or 256".to_string(),
+ Self::AesXts(_) => "128 or 256".to_string(),
+ Self::HmacSha(_) => "256 or 512".to_string(),
+ Self::Ec(_) => "secp256r1, secp384r1, secp521r1, ed25519, or ed448".to_string(),
+ }
+ }
+}
+
+impl From<&RetrievableSecret> for u16 {
+ fn from(value: &RetrievableSecret) -> Self {
+ match value {
+ RetrievableSecret::PlainText => ListableSecretType::PLAINTEXT,
+ RetrievableSecret::Aes(AesSizes::Bits128) => ListableSecretType::AES_128_KEY,
+ RetrievableSecret::Aes(AesSizes::Bits192) => ListableSecretType::AES_192_KEY,
+ RetrievableSecret::Aes(AesSizes::Bits256) => ListableSecretType::AES_256_KEY,
+ RetrievableSecret::AesXts(AesXtsSizes::Bits128) => ListableSecretType::AES_128_XTS_KEY,
+ RetrievableSecret::AesXts(AesXtsSizes::Bits256) => ListableSecretType::AES_256_XTS_KEY,
+ RetrievableSecret::HmacSha(HmacShaSizes::Sha256) => {
+ ListableSecretType::HMAC_SHA_256_KEY
+ }
+ RetrievableSecret::HmacSha(HmacShaSizes::Sha512) => {
+ ListableSecretType::HMAC_SHA_512_KEY
+ }
+ RetrievableSecret::Ec(EcCurves::Secp256R1) => ListableSecretType::ECDSA_P256_KEY,
+ RetrievableSecret::Ec(EcCurves::Secp384R1) => ListableSecretType::ECDSA_P384_KEY,
+ RetrievableSecret::Ec(EcCurves::Secp521R1) => ListableSecretType::ECDSA_P521_KEY,
+ RetrievableSecret::Ec(EcCurves::Ed25519) => ListableSecretType::ECDSA_ED25519_KEY,
+ RetrievableSecret::Ec(EcCurves::Ed448) => ListableSecretType::ECDSA_ED448_KEY,
+ }
+ }
+}
+
+// serializes to: <secret type nb> (String name)
+impl Serialize for RetrievableSecret {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: Serializer,
+ {
+ let id: u16 = self.into();
+ serializer.serialize_str(&format!("{id} ({self})"))
+ }
+}
+
+/// deserializes from the secret type nb only
+impl<'de> Deserialize<'de> for RetrievableSecret {
+ fn deserialize<D>(de: D) -> Result<Self, D::Error>
+ where
+ D: serde::Deserializer<'de>,
+ {
+ struct RetrSecretVisitor;
+ impl<'de> serde::de::Visitor<'de> for RetrSecretVisitor {
+ type Value = RetrievableSecret;
+
+ fn expecting(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
+ fmt.write_str(
+ "a retrievable secret type: `<number> (String name)` number in [3,10]|[17,21]",
+ )
+ }
+ fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
+ where
+ E: serde::de::Error,
+ {
+ let (n, _) = s.split_once(' ').ok_or(serde::de::Error::invalid_value(
+ serde::de::Unexpected::Str(s),
+ &self,
+ ))?;
+ let id: u16 = n.parse().map_err(|_| {
+ serde::de::Error::invalid_value(serde::de::Unexpected::Str(n), &self)
+ })?;
+ let listable: ListableSecretType = id.into();
+ match listable {
+ ListableSecretType::Retrievable(r) => Ok(r),
+ _ => Err(serde::de::Error::invalid_value(
+ serde::de::Unexpected::Unsigned(id.into()),
+ &self,
+ )),
+ }
+ }
+ }
+ de.deserialize_str(RetrSecretVisitor)
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use serde_test::{assert_tokens, Token};
+
+ use super::*;
+
+ #[test]
+ fn retr_serde_plain() {
+ let retr = RetrievableSecret::PlainText;
+ assert_tokens(&retr, &[Token::Str("3 (PLAINTEXT)")]);
+ }
+
+ #[test]
+ fn retr_serde_aes() {
+ let retr = RetrievableSecret::Aes(AesSizes::Bits192);
+ assert_tokens(&retr, &[Token::Str("5 (AES-192-KEY)")]);
+ }
+
+ #[test]
+ fn retr_serde_aes_xts() {
+ let retr = RetrievableSecret::AesXts(AesXtsSizes::Bits128);
+ assert_tokens(&retr, &[Token::Str("7 (AES-XTS-128-KEY)")]);
+ }
+
+ #[test]
+ fn retr_serde_hmac() {
+ let retr = RetrievableSecret::HmacSha(HmacShaSizes::Sha256);
+ assert_tokens(&retr, &[Token::Str("9 (HMAC-SHA-256-KEY)")]);
+ }
+
+ #[test]
+ fn retr_serde_es() {
+ let retr = RetrievableSecret::Ec(EcCurves::Secp521R1);
+ assert_tokens(&retr, &[Token::Str("19 (EC-SECP521R1-PRIVATE-KEY)")]);
+ }
+
+ // Ensure that the string representation of the retrievable types stay constant, or PEM will have
+ // different, incompatible types
+ #[test]
+ fn stable_type_names() {
+ assert_eq!("PLAINTEXT", RetrievableSecret::PlainText.to_string());
+ assert_eq!(
+ "AES-128-KEY",
+ RetrievableSecret::Aes(AesSizes::Bits128).to_string()
+ );
+ assert_eq!(
+ "AES-192-KEY",
+ RetrievableSecret::Aes(AesSizes::Bits192).to_string()
+ );
+ assert_eq!(
+ "AES-256-KEY",
+ RetrievableSecret::Aes(AesSizes::Bits256).to_string()
+ );
+ assert_eq!(
+ "AES-XTS-128-KEY",
+ RetrievableSecret::AesXts(AesXtsSizes::Bits128).to_string()
+ );
+ assert_eq!(
+ "AES-XTS-256-KEY",
+ RetrievableSecret::AesXts(AesXtsSizes::Bits256).to_string()
+ );
+ assert_eq!(
+ "HMAC-SHA-256-KEY",
+ RetrievableSecret::HmacSha(HmacShaSizes::Sha256).to_string()
+ );
+ assert_eq!(
+ "HMAC-SHA-512-KEY",
+ RetrievableSecret::HmacSha(HmacShaSizes::Sha512).to_string()
+ );
+ assert_eq!(
+ "EC-SECP256R1-PRIVATE-KEY",
+ RetrievableSecret::Ec(EcCurves::Secp256R1).to_string()
+ );
+ assert_eq!(
+ "EC-SECP384R1-PRIVATE-KEY",
+ RetrievableSecret::Ec(EcCurves::Secp384R1).to_string()
+ );
+ assert_eq!(
+ "EC-SECP521R1-PRIVATE-KEY",
+ RetrievableSecret::Ec(EcCurves::Secp521R1).to_string()
+ );
+ assert_eq!(
+ "EC-ED25519-PRIVATE-KEY",
+ RetrievableSecret::Ec(EcCurves::Ed25519).to_string()
+ );
+ assert_eq!(
+ "EC-ED448-PRIVATE-KEY",
+ RetrievableSecret::Ec(EcCurves::Ed448).to_string()
+ );
+ }
+}
diff --git a/rust/pv_core/src/uvdevice/secret_list.rs b/rust/pv_core/src/uvdevice/secret_list.rs
index 0a8af504..4e955010 100644
--- a/rust/pv_core/src/uvdevice/secret_list.rs
+++ b/rust/pv_core/src/uvdevice/secret_list.rs
@@ -2,9 +2,14 @@
//
// Copyright IBM Corp. 2024
-use crate::assert_size;
-use crate::{misc::to_u16, uv::ListCmd, uvdevice::UvCmd, Error, Result};
-use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
+use crate::{
+ assert_size,
+ misc::to_u16,
+ uv::{AesSizes, AesXtsSizes, EcCurves, HmacShaSizes, ListCmd, RetrievableSecret},
+ uvdevice::UvCmd,
+ Error, Result,
+};
+use byteorder::{BigEndian, ByteOrder, ReadBytesExt, WriteBytesExt};
use serde::{Deserialize, Serialize, Serializer};
use std::{
fmt::Display,
@@ -18,7 +23,7 @@ use zerocopy::{AsBytes, FromBytes, FromZeroes, U16, U32};
///
/// (de)serializes itself in/from a hex-string
#[repr(C)]
-#[derive(PartialEq, Eq, AsBytes, FromZeroes, FromBytes, Debug, Clone)]
+#[derive(PartialEq, Eq, AsBytes, FromZeroes, FromBytes, Debug, Clone, Default)]
pub struct SecretId([u8; Self::ID_SIZE]);
assert_size!(SecretId, SecretId::ID_SIZE);
@@ -94,11 +99,11 @@ impl SecretEntry {
/// Create a new entry for a [`SecretList`].
///
/// The content of this entry will very likely not represent the status of the guest in the
- /// Ultravisor. Use of [`SecretList::decode`] in any non-test environments is encuraged.
+ /// Ultravisor. Use of [`SecretList::decode`] in any non-test environments is encouraged.
pub fn new(index: u16, stype: ListableSecretType, id: SecretId, secret_len: u32) -> Self {
Self {
index: index.into(),
- stype: stype.into(),
+ stype: U16::new(stype.into()),
len: secret_len.into(),
res_8: 0,
id,
@@ -117,7 +122,7 @@ impl SecretEntry {
/// Returns the secret type of this [`SecretEntry`].
pub fn stype(&self) -> ListableSecretType {
- self.stype.into()
+ self.stype.get().into()
}
/// Returns a reference to the id of this [`SecretEntry`].
@@ -146,7 +151,7 @@ impl SecretEntry {
impl Display for SecretEntry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- let stype: ListableSecretType = self.stype.into();
+ let stype: ListableSecretType = self.stype.get().into();
writeln!(f, "{} {}:", self.index, stype)?;
write!(f, " ")?;
for b in self.id.as_ref() {
@@ -298,51 +303,115 @@ fn ser_u16<S: Serializer>(v: &U16<BigEndian>, ser: S) -> Result<S::Ok, S::Error>
pub enum ListableSecretType {
/// Association Secret
Association,
+ /// Retrievable key
+ Retrievable(RetrievableSecret),
+
/// Invalid secret type, that should never appear in a list
///
/// 0 is reserved
- /// 1 is Null secret, with no id and not listable
+ /// 1 is Null secret, with no id and not list-able
Invalid(u16),
/// Unknown secret type
Unknown(u16),
}
impl ListableSecretType {
- /// UV type id for an association secret
- pub const ASSOCIATION: u16 = 0x0002;
- /// UV type id for a null secret
- pub const NULL: u16 = 0x0001;
const RESERVED_0: u16 = 0x0000;
+ /// UV secret-type id for a null secret
+ pub const NULL: u16 = 0x0001;
+ /// UV secret-type id for an association secret
+ pub const ASSOCIATION: u16 = 0x0002;
+ /// UV secret-type id for a plain text secret
+ pub const PLAINTEXT: u16 = 0x0003;
+ /// UV secret-type id for an aes-128-key secret
+ pub const AES_128_KEY: u16 = 0x0004;
+ /// UV secret-type id for an aes-192-key secret
+ pub const AES_192_KEY: u16 = 0x0005;
+ /// UV secret-type id for an aes-256-key secret
+ pub const AES_256_KEY: u16 = 0x0006;
+ /// UV secret-type id for an aes-xts-128-key secret
+ pub const AES_128_XTS_KEY: u16 = 0x0007;
+ /// UV secret-type id for an aes-xts-256-key secret
+ pub const AES_256_XTS_KEY: u16 = 0x0008;
+ /// UV secret-type id for an hmac-sha-256-key secret
+ pub const HMAC_SHA_256_KEY: u16 = 0x0009;
+ /// UV secret-type id for an hmac-sha-512-key secret
+ pub const HMAC_SHA_512_KEY: u16 = 0x000a;
+ // 0x000b - 0x0010 reserved
+ /// UV secret-type id for an ecdsa-p256-private-key secret
+ pub const ECDSA_P256_KEY: u16 = 0x0011;
+ /// UV secret-type id for an ecdsa-p384-private-key secret
+ pub const ECDSA_P384_KEY: u16 = 0x0012;
+ /// UV secret-type id for an ecdsa-p521-private-key secret
+ pub const ECDSA_P521_KEY: u16 = 0x0013;
+ /// UV secret-type id for an ed25519-private-key secret
+ pub const ECDSA_ED25519_KEY: u16 = 0x0014;
+ /// UV secret-type id for an ed448-private-key secret
+ pub const ECDSA_ED448_KEY: u16 = 0x0015;
}
impl Display for ListableSecretType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Association => write!(f, "Association"),
- Self::Invalid(n) => write!(f, "Invalid({n})"),
- Self::Unknown(n) => write!(f, "Unknown({n})"),
+ Self::Invalid(n) => write!(f, "Invalid(0x{n:04x})"),
+ Self::Unknown(n) => write!(f, "Unknown(0x{n:04x})"),
+ Self::Retrievable(r) => write!(f, "{r}"),
}
}
}
-impl From<U16<BigEndian>> for ListableSecretType {
- fn from(value: U16<BigEndian>) -> Self {
- match value.get() {
+impl<O: ByteOrder> From<U16<O>> for ListableSecretType {
+ fn from(value: U16<O>) -> Self {
+ value.get().into()
+ }
+}
+
+impl From<u16> for ListableSecretType {
+ fn from(value: u16) -> Self {
+ match value {
Self::RESERVED_0 => Self::Invalid(Self::RESERVED_0),
Self::NULL => Self::Invalid(Self::NULL),
Self::ASSOCIATION => Self::Association,
+ Self::PLAINTEXT => Self::Retrievable(RetrievableSecret::PlainText),
+ Self::AES_128_KEY => Self::Retrievable(RetrievableSecret::Aes(AesSizes::Bits128)),
+ Self::AES_192_KEY => Self::Retrievable(RetrievableSecret::Aes(AesSizes::Bits192)),
+ Self::AES_256_KEY => Self::Retrievable(RetrievableSecret::Aes(AesSizes::Bits256)),
+ Self::AES_128_XTS_KEY => {
+ Self::Retrievable(RetrievableSecret::AesXts(AesXtsSizes::Bits128))
+ }
+ Self::AES_256_XTS_KEY => {
+ Self::Retrievable(RetrievableSecret::AesXts(AesXtsSizes::Bits256))
+ }
+ Self::HMAC_SHA_256_KEY => {
+ Self::Retrievable(RetrievableSecret::HmacSha(HmacShaSizes::Sha256))
+ }
+ Self::HMAC_SHA_512_KEY => {
+ Self::Retrievable(RetrievableSecret::HmacSha(HmacShaSizes::Sha512))
+ }
+ Self::ECDSA_P256_KEY => Self::Retrievable(RetrievableSecret::Ec(EcCurves::Secp256R1)),
+ Self::ECDSA_P384_KEY => Self::Retrievable(RetrievableSecret::Ec(EcCurves::Secp384R1)),
+ Self::ECDSA_P521_KEY => Self::Retrievable(RetrievableSecret::Ec(EcCurves::Secp521R1)),
+ Self::ECDSA_ED25519_KEY => Self::Retrievable(RetrievableSecret::Ec(EcCurves::Ed25519)),
+ Self::ECDSA_ED448_KEY => Self::Retrievable(RetrievableSecret::Ec(EcCurves::Ed448)),
n => Self::Unknown(n),
}
}
}
-impl From<ListableSecretType> for U16<BigEndian> {
+impl<O: ByteOrder> From<ListableSecretType> for U16<O> {
+ fn from(value: ListableSecretType) -> Self {
+ Self::new(value.into())
+ }
+}
+
+impl From<ListableSecretType> for u16 {
fn from(value: ListableSecretType) -> Self {
match value {
ListableSecretType::Association => ListableSecretType::ASSOCIATION,
ListableSecretType::Invalid(n) | ListableSecretType::Unknown(n) => n,
+ ListableSecretType::Retrievable(r) => (&r).into(),
}
- .into()
}
}
@@ -363,8 +432,8 @@ where
where
E: serde::de::Error,
{
- if s.len() != SecretId::ID_SIZE * 2 + 2 {
- return Err(serde::de::Error::invalid_length(s.len(), &self));
+ if s.len() != SecretId::ID_SIZE * 2 + "0x".len() {
+ return Err(serde::de::Error::invalid_length(s.len() - 2, &self));
}
let nb = s.strip_prefix("0x").ok_or_else(|| {
serde::de::Error::invalid_value(serde::de::Unexpected::Str(s), &self)
@@ -385,7 +454,6 @@ mod test {
use super::*;
use std::io::{BufReader, BufWriter, Cursor};
-
#[test]
fn dump_secret_entry() {
const EXP: &[u8] = &[
@@ -516,4 +584,47 @@ mod test {
)],
)
}
+
+ #[test]
+ fn secret_list_ser() {
+ let list = SecretList {
+ total_num_secrets: 0x112,
+ secrets: vec![SecretEntry {
+ index: 1.into(),
+ stype: 2.into(),
+ len: 32.into(),
+ res_8: 0,
+ id: SecretId::from([0; 32]),
+ }],
+ };
+
+ assert_ser_tokens(
+ &list,
+ &[
+ Token::Struct {
+ name: "SecretList",
+ len: 2,
+ },
+ Token::String("total_num_secrets"),
+ Token::U64(0x112),
+ Token::String("secrets"),
+ Token::Seq { len: Some(1) },
+ Token::Struct {
+ name: "SecretEntry",
+ len: (4),
+ },
+ Token::String("index"),
+ Token::U16(1),
+ Token::String("stype"),
+ Token::U16(2),
+ Token::String("len"),
+ Token::U32(32),
+ Token::String("id"),
+ Token::String("0x0000000000000000000000000000000000000000000000000000000000000000"),
+ Token::StructEnd,
+ Token::SeqEnd,
+ Token::StructEnd,
+ ],
+ )
+ }
}

View File

@ -0,0 +1,877 @@
From fd024387d710887bd2016658c44d4762a08c791c Mon Sep 17 00:00:00 2001
From: Steffen Eiden <seiden@linux.ibm.com>
Date: Tue, 5 Mar 2024 12:19:22 +0100
Subject: [PATCH] rust/pv: Retrievable secrets support
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Support retrievable secret for Add-Secret requests.
Acked-by: Marc Hartmayer <marc@linux.ibm.com>
Reviewed-by: Christoph Schlameuss <schlameuss@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
---
rust/pv/src/crypto.rs | 3 +-
rust/pv/src/error.rs | 8 +
rust/pv/src/lib.rs | 8 +-
rust/pv/src/uvsecret.rs | 1 +
rust/pv/src/uvsecret/guest_secret.rs | 399 +++++++++++++++++++++++++--
rust/pv/src/uvsecret/retr_secret.rs | 234 ++++++++++++++++
6 files changed, 631 insertions(+), 22 deletions(-)
create mode 100644 rust/pv/src/uvsecret/retr_secret.rs
diff --git a/rust/pv/src/crypto.rs b/rust/pv/src/crypto.rs
index 8f11d2b4..ebc85f72 100644
--- a/rust/pv/src/crypto.rs
+++ b/rust/pv/src/crypto.rs
@@ -29,7 +29,6 @@ pub type Aes256XtsKey = Confidential<[u8; SymKeyType::AES_256_XTS_KEY_LEN]>;
/// SHA-512 digest length (in bytes)
pub const SHA_512_HASH_LEN: usize = 64;
-
#[allow(dead_code)]
pub(crate) const SHA_256_HASH_LEN: u32 = 32;
#[allow(dead_code)]
@@ -60,6 +59,8 @@ impl SymKeyType {
pub const AES_256_XTS_KEY_LEN: usize = 64;
/// AES256-XTS tweak length (in bytes)
pub const AES_256_XTS_TWEAK_LEN: usize = 16;
+ /// AES256 GCM Block length
+ pub const AES_256_GCM_BLOCK_LEN: usize = 16;
/// Returns the tag length of the [`SymKeyType`] if it is an AEAD key
pub const fn tag_len(&self) -> Option<usize> {
diff --git a/rust/pv/src/error.rs b/rust/pv/src/error.rs
index 3ba808f2..601b40f0 100644
--- a/rust/pv/src/error.rs
+++ b/rust/pv/src/error.rs
@@ -109,6 +109,14 @@ pub enum Error {
#[error("An ASCII string was expected, but non-ASCII characters were received.")]
NonAscii,
+ #[error("Incorrect {what} for a {kind}. Is: {value}; expected: {exp}")]
+ RetrInvKey {
+ what: &'static str,
+ kind: String,
+ value: String,
+ exp: String,
+ },
+
// errors from other crates
#[error(transparent)]
PvCore(#[from] pv_core::Error),
diff --git a/rust/pv/src/lib.rs b/rust/pv/src/lib.rs
index ec31b9a4..43375669 100644
--- a/rust/pv/src/lib.rs
+++ b/rust/pv/src/lib.rs
@@ -104,7 +104,12 @@ pub mod request {
/// Reexports some useful OpenSSL symbols
pub mod openssl {
- pub use openssl::{error::ErrorStack, hash::DigestBytes, pkey, x509};
+ pub use openssl::{error::ErrorStack, hash::DigestBytes, nid::Nid, pkey, x509};
+ // rust-OpenSSL does not define these NIDs
+ #[allow(missing_docs)]
+ pub const NID_ED25519: Nid = Nid::from_raw(openssl_sys::NID_ED25519);
+ #[allow(missing_docs)]
+ pub const NID_ED448: Nid = Nid::from_raw(openssl_sys::NID_ED448);
}
pub use pv_core::request::*;
@@ -118,6 +123,7 @@ pub mod secret {
asrcb::{AddSecretFlags, AddSecretRequest, AddSecretVersion},
ext_secret::ExtSecret,
guest_secret::GuestSecret,
+ retr_secret::{IbmProtectedKey, RetrievedSecret},
user_data::verify_asrcb_and_get_user_data,
};
}
diff --git a/rust/pv/src/uvsecret.rs b/rust/pv/src/uvsecret.rs
index 343e4b05..c3b43bba 100644
--- a/rust/pv/src/uvsecret.rs
+++ b/rust/pv/src/uvsecret.rs
@@ -10,4 +10,5 @@
pub mod asrcb;
pub mod ext_secret;
pub mod guest_secret;
+pub mod retr_secret;
pub mod user_data;
diff --git a/rust/pv/src/uvsecret/guest_secret.rs b/rust/pv/src/uvsecret/guest_secret.rs
index 4f1db31c..3bad6d3c 100644
--- a/rust/pv/src/uvsecret/guest_secret.rs
+++ b/rust/pv/src/uvsecret/guest_secret.rs
@@ -4,20 +4,34 @@
#[allow(unused_imports)] // used for more convenient docstring
use super::asrcb::AddSecretRequest;
-use crate::assert_size;
use crate::{
- crypto::{hash, random_array},
- request::Confidential,
- Result,
+ assert_size,
+ crypto::{hash, random_array, SymKeyType},
+ request::{
+ openssl::{NID_ED25519, NID_ED448},
+ Confidential,
+ },
+ uv::{
+ AesSizes, AesXtsSizes, EcCurves, HmacShaSizes, ListableSecretType, RetrievableSecret,
+ RetrieveCmd, SecretId,
+ },
+ Error, Result,
};
use byteorder::BigEndian;
-use openssl::hash::MessageDigest;
-use pv_core::uv::{ListableSecretType, SecretId};
+use openssl::{
+ hash::MessageDigest,
+ nid::Nid,
+ pkey::{Id, PKey, Private},
+};
+use pv_core::static_assert;
use serde::{Deserialize, Serialize};
-use std::{convert::TryInto, fmt::Display};
+use std::fmt::Display;
use zerocopy::{AsBytes, U16, U32};
const ASSOC_SECRET_SIZE: usize = 32;
+/// Maximum size of a plain-text secret payload (8190)
+pub(crate) const MAX_SIZE_PLAIN_PAYLOAD: usize = RetrieveCmd::MAX_SIZE - 2;
+static_assert!(MAX_SIZE_PLAIN_PAYLOAD == 8190);
/// A Secret to be added in [`AddSecretRequest`]
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
@@ -36,13 +50,60 @@ pub enum GuestSecret {
#[serde(skip)]
secret: Confidential<[u8; ASSOC_SECRET_SIZE]>,
},
+ /// Retrievable key
+ ///
+ /// Create Retrievables using [`GuestSecret::retrievable`]
+ /// Secret size is always valid for the type/kind
+ Retrievable {
+ /// Retrievable secret type
+ kind: RetrievableSecret,
+ /// Name of the secret
+ name: String,
+ /// SHA256 hash of [`GuestSecret::RetrievableKey::name`]
+ id: SecretId,
+ /// Confidential actual retrievable secret (32 bytes)
+ #[serde(skip)]
+ secret: Confidential<Vec<u8>>,
+ },
+}
+
+macro_rules! retr_constructor {
+ ($(#[$err:meta])* | $(#[$kind:meta])* => $type: ty, $func: ident) => {
+ /// Create a new
+ $(#[$kind])*
+ /// [`GuestSecret::Retrievable`] secret.
+ ///
+ /// * `name` - Name of the secret. Will be hashed into a 32 byte id
+ /// * `secret` - the secret value
+ ///
+ /// # Errors
+ ///
+ $(#[$err])*
+ pub fn $func(name: &str, secret: $type) -> Result<Self> {
+ let (kind, secret) = $func(secret)?;
+ Ok(Self::Retrievable {
+ kind,
+ name: name.to_string(),
+ id: Self::name_to_id(name)?,
+ secret,
+ })
+ }
+ };
}
impl GuestSecret {
+ fn name_to_id(name: &str) -> Result<SecretId> {
+ let id: [u8; SecretId::ID_SIZE] = hash(MessageDigest::sha256(), name.as_bytes())?
+ .to_vec()
+ .try_into()
+ .unwrap();
+ Ok(id.into())
+ }
+
/// Create a new [`GuestSecret::Association`].
///
/// * `name` - Name of the secret. Will be hashed into a 32 byte id
- /// * `secret` - Value of the secret. Ranom if [`Option::None`]
+ /// * `secret` - Value of the secret. Random if [`Option::None`]
///
/// # Errors
///
@@ -51,10 +112,6 @@ impl GuestSecret {
where
O: Into<Option<[u8; ASSOC_SECRET_SIZE]>>,
{
- let id: [u8; SecretId::ID_SIZE] = hash(MessageDigest::sha256(), name.as_bytes())?
- .to_vec()
- .try_into()
- .unwrap();
let secret = match secret.into() {
Some(s) => s,
None => random_array()?,
@@ -62,16 +119,28 @@ impl GuestSecret {
Ok(Self::Association {
name: name.to_string(),
- id: id.into(),
+ id: Self::name_to_id(name)?,
secret: secret.into(),
})
}
+ retr_constructor!(#[doc = r"This function will return an error if the secret is larger than 8 pages"]
+ | #[doc = r"plaintext"] => Confidential<Vec<u8>>, plaintext);
+ retr_constructor!(#[doc = r"This function will return an error if OpenSSL cannot create a hash or the secret size is invalid"]
+ | #[doc = r"AES Key"] => Confidential<Vec<u8>>, aes);
+ retr_constructor!(#[doc = r"This function will return an error if OpenSSL cannot create a hash or the secret size is invalid"]
+ | #[doc = r"AES-XTS Key"] => Confidential<Vec<u8>>, aes_xts);
+ retr_constructor!(#[doc = r"This function will return an error if OpenSSL cannot create a hash or the secret size is invalid"]
+ | #[doc = r"HMAC-SHA Key"] => Confidential<Vec<u8>>, hmac_sha);
+ retr_constructor!(#[doc = r"This function will return an error if OpenSSL cannot create a hash or the curve is invalid"]
+ | #[doc = r"EC PRIVATE Key"] => PKey<Private>, ec);
+
/// Reference to the confidential data
pub fn confidential(&self) -> &[u8] {
match &self {
Self::Null => &[],
Self::Association { secret, .. } => secret.value().as_slice(),
+ Self::Retrievable { secret, .. } => secret.value(),
}
}
@@ -79,7 +148,7 @@ impl GuestSecret {
pub(crate) fn auth(&self) -> SecretAuth {
match &self {
Self::Null => SecretAuth::Null,
- // Panic: every non null secret type is listable -> no panic
+ // Panic: every non null secret type is list-able -> no panic
listable => {
SecretAuth::Listable(ListableSecretHdr::from_guest_secret(listable).unwrap())
}
@@ -92,6 +161,7 @@ impl GuestSecret {
// Null is not listable, but the ListableSecretType provides the type constant (1)
Self::Null => ListableSecretType::NULL,
Self::Association { .. } => ListableSecretType::ASSOCIATION,
+ Self::Retrievable { kind, .. } => kind.into(),
}
}
@@ -100,6 +170,7 @@ impl GuestSecret {
match self {
Self::Null => 0,
Self::Association { secret, .. } => secret.value().len() as u32,
+ Self::Retrievable { secret, .. } => secret.value().len() as u32,
}
}
@@ -107,18 +178,157 @@ impl GuestSecret {
fn id(&self) -> Option<SecretId> {
match self {
Self::Null => None,
- Self::Association { id, .. } => Some(id.to_owned()),
+ Self::Association { id, .. } | Self::Retrievable { id, .. } => Some(id.to_owned()),
}
}
}
+type RetrKeyInfo = (RetrievableSecret, Confidential<Vec<u8>>);
+
+fn extend_to_multiple(mut key: Vec<u8>, multiple: usize) -> Confidential<Vec<u8>> {
+ match key.len().checked_rem(multiple) {
+ Some(0) | None => key,
+ Some(m) => {
+ key.resize(key.len() + multiple - m, 0);
+ key
+ }
+ }
+ .into()
+}
+
+/// Get a plain-text key
+///
+/// ```none
+/// size U16<BigEndian> | payload (0-8190) bytes
+/// ```
+fn plaintext(inp: Confidential<Vec<u8>>) -> Result<RetrKeyInfo> {
+ let key_len = inp.value().len();
+ if key_len > RetrieveCmd::MAX_SIZE {
+ return Err(Error::RetrInvKey {
+ what: "key size",
+ value: key_len.to_string(),
+ kind: RetrievableSecret::PlainText.to_string(),
+ exp: RetrievableSecret::PlainText.expected(),
+ });
+ }
+ let mut key = Vec::with_capacity(2 + inp.value().len());
+ let key_len: U16<BigEndian> = (key_len as u16).into();
+ key.extend_from_slice(key_len.as_bytes());
+ key.extend_from_slice(inp.value());
+ let key = extend_to_multiple(key, SymKeyType::AES_256_GCM_BLOCK_LEN);
+
+ Ok((RetrievableSecret::PlainText, key))
+}
+
+/// Get an AES-key
+fn aes(key: Confidential<Vec<u8>>) -> Result<RetrKeyInfo> {
+ let key_len = key.value().len() as u32;
+ let bit_size = bitsize(key_len);
+ match AesSizes::from_bits(bit_size) {
+ Some(size) => Ok((RetrievableSecret::Aes(size), key)),
+ None => {
+ // Use some AES type to get exp sizes and name
+ let kind = RetrievableSecret::Aes(AesSizes::Bits128);
+ Err(Error::RetrInvKey {
+ what: "key size",
+ value: bit_size.to_string(),
+ kind: format!("{kind:#}"),
+ exp: kind.expected(),
+ })
+ }
+ }
+}
+
+/// Get an AES-XTS-key
+fn aes_xts(key: Confidential<Vec<u8>>) -> Result<RetrKeyInfo> {
+ let key_len = key.value().len() as u32;
+ let bit_size = bitsize(key_len / 2);
+ match AesXtsSizes::from_bits(bit_size) {
+ Some(size) => Ok((RetrievableSecret::AesXts(size), key)),
+ None => {
+ // Use some AES-XTS type to get exp sizes and name
+ let kind = RetrievableSecret::AesXts(AesXtsSizes::Bits128);
+ Err(Error::RetrInvKey {
+ what: "key size",
+ value: bit_size.to_string(),
+ kind: format!("{kind:#}"),
+ exp: kind.expected(),
+ })
+ }
+ }
+}
+
+/// Get an HMAC-SHA-key
+fn hmac_sha(key: Confidential<Vec<u8>>) -> Result<RetrKeyInfo> {
+ let key_len = key.value().len() as u32;
+ let size = bitsize(key_len / 2);
+ match HmacShaSizes::from_sha_size(size) {
+ Some(size) => Ok((RetrievableSecret::HmacSha(size), key)),
+ None => {
+ // Use some HMAC type to get exp sizes and name
+ let kind = RetrievableSecret::HmacSha(HmacShaSizes::Sha256);
+ Err(Error::RetrInvKey {
+ what: "key size",
+ value: size.to_string(),
+ kind: format!("{kind:#}"),
+ exp: kind.expected(),
+ })
+ }
+ }
+}
+
+/// Get an EC-private-key
+fn ec(key: PKey<Private>) -> Result<RetrKeyInfo> {
+ let (key, nid) = match key.id() {
+ Id::EC => {
+ let ec_key = key.ec_key()?;
+ let key = ec_key.private_key().to_vec();
+ let nid = ec_key.group().curve_name().unwrap_or(Nid::UNDEF);
+ (key, nid)
+ }
+ // ED keys are not handled via the EC struct in OpenSSL.
+ id @ (Id::ED25519 | Id::ED448) => {
+ let key = key.raw_private_key()?;
+ let nid = Nid::from_raw(id.as_raw());
+ (key, nid)
+ }
+ _ => (vec![], Nid::UNDEF),
+ };
+
+ let kind = match nid {
+ Nid::X9_62_PRIME256V1 => EcCurves::Secp256R1,
+ Nid::SECP384R1 => EcCurves::Secp384R1,
+ Nid::SECP521R1 => EcCurves::Secp521R1,
+ NID_ED25519 => EcCurves::Ed25519,
+ NID_ED448 => EcCurves::Ed448,
+ nid => {
+ // Use some EC type to get exp sizes and name
+ let ec = RetrievableSecret::Ec(EcCurves::Secp521R1);
+ return Err(Error::RetrInvKey {
+ what: "curve or format",
+ kind: format!("{ec:#}"),
+ value: nid.long_name()?.to_string(),
+ exp: ec.expected(),
+ });
+ }
+ };
+
+ let key = kind.resize_raw_key(key);
+ Ok((RetrievableSecret::Ec(kind), key.into()))
+}
+
+#[inline(always)]
+const fn bitsize(bytesize: u32) -> u32 {
+ bytesize * 8
+}
+
impl Display for GuestSecret {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Null => write!(f, "Meta"),
gs => {
let kind: U16<BigEndian> = gs.kind().into();
- let st: ListableSecretType = kind.into();
+ let st: ListableSecretType = kind.get().into();
write!(f, "{st}")
}
}
@@ -153,20 +363,24 @@ assert_size!(ListableSecretHdr, 0x30);
impl ListableSecretHdr {
fn from_guest_secret(gs: &GuestSecret) -> Option<Self> {
- let id = gs.id()?;
Some(Self {
res0: 0,
kind: gs.kind().into(),
secret_len: gs.secret_len().into(),
res8: 0,
- id,
+ id: gs.id()?,
})
}
}
#[cfg(test)]
mod test {
+
+ use super::HmacShaSizes as HmacSizes;
+ use super::RetrievableSecret::*;
use super::*;
+ use openssl::ec::{EcGroup, EcKey};
+ use pv_core::uv::AesSizes;
use serde_test::{assert_tokens, Token};
#[test]
@@ -187,8 +401,103 @@ mod test {
assert_eq!(secret, exp);
}
+ macro_rules! retr_test {
+ ($name: ident, $func: ident, $size: expr, $exp_kind: expr) => {
+ #[test]
+ fn $name() {
+ let secret_value = vec![0x11; $size];
+ let name = "test retr secret".to_string();
+ let secret = GuestSecret::$func(&name, secret_value.clone().into()).unwrap();
+ let exp_id = [
+ 0x61, 0x2c, 0xd6, 0x3e, 0xa8, 0xf2, 0xc1, 0x15, 0xc1, 0xe, 0x15, 0xb8, 0x8a,
+ 0x90, 0x16, 0xc1, 0x55, 0xef, 0x9c, 0x7c, 0x2c, 0x8e, 0x56, 0xd0, 0x78, 0x4c,
+ 0x8a, 0x1d, 0xc9, 0x3a, 0x80, 0xba,
+ ];
+ let exp = GuestSecret::Retrievable {
+ kind: $exp_kind,
+ name,
+ id: exp_id.into(),
+ secret: secret_value.into(),
+ };
+ assert_eq!(exp, secret);
+ }
+ };
+ }
+
+ retr_test!(retr_aes_128, aes, 16, Aes(AesSizes::Bits128));
+ retr_test!(retr_aes_192, aes, 24, Aes(AesSizes::Bits192));
+ retr_test!(retr_aes_256, aes, 32, Aes(AesSizes::Bits256));
+ retr_test!(retr_aes_xts_128, aes_xts, 32, AesXts(AesXtsSizes::Bits128));
+ retr_test!(retr_aes_xts_256, aes_xts, 64, AesXts(AesXtsSizes::Bits256));
+ retr_test!(retr_aes_hmac_256, hmac_sha, 64, HmacSha(HmacSizes::Sha256));
+ retr_test!(retr_aes_hmac_512, hmac_sha, 128, HmacSha(HmacSizes::Sha512));
+
+ #[test]
+ fn plaintext_no_pad() {
+ let key = vec![0, 14, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7];
+ let name = "PLAINTEXT_PAD".to_string();
+ let secret = GuestSecret::plaintext(&name, key[2..].to_vec().into()).unwrap();
+ let exp_id = [
+ 15, 123, 176, 210, 135, 231, 220, 232, 148, 93, 198, 195, 165, 212, 214, 129, 45, 1,
+ 94, 11, 167, 18, 151, 15, 120, 254, 13, 109, 173, 186, 37, 74,
+ ];
+ let exp = GuestSecret::Retrievable {
+ kind: PlainText,
+ name,
+ id: exp_id.into(),
+ secret: key.into(),
+ };
+
+ assert_eq!(secret, exp);
+ }
+
#[test]
- fn ap_asc_parse() {
+ fn plaintext_pad() {
+ let key = vec![0, 10, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 0, 0, 0, 0];
+ let name = "PLAINTEXT_PAD".to_string();
+ let secret = GuestSecret::plaintext(&name, key[2..12].to_vec().into()).unwrap();
+ let exp_id = [
+ 15, 123, 176, 210, 135, 231, 220, 232, 148, 93, 198, 195, 165, 212, 214, 129, 45, 1,
+ 94, 11, 167, 18, 151, 15, 120, 254, 13, 109, 173, 186, 37, 74,
+ ];
+ let exp = GuestSecret::Retrievable {
+ kind: PlainText,
+ name,
+ id: exp_id.into(),
+ secret: key.into(),
+ };
+
+ assert_eq!(secret, exp);
+ }
+
+ #[track_caller]
+ fn test_ec(grp: Nid, exp_kind: EcCurves, exp_len: usize) {
+ let key = match grp {
+ NID_ED25519 => PKey::generate_ed25519().unwrap(),
+ NID_ED448 => PKey::generate_ed448().unwrap(),
+ nid => {
+ let group = EcGroup::from_curve_name(nid).unwrap();
+ let key = EcKey::generate(&group).unwrap();
+ PKey::from_ec_key(key).unwrap()
+ }
+ };
+ let (kind, key) = ec(key).unwrap();
+
+ assert_eq!(kind, Ec(exp_kind));
+ assert_eq!(key.value().len(), exp_len);
+ }
+
+ #[test]
+ fn retr_ec() {
+ test_ec(Nid::X9_62_PRIME256V1, EcCurves::Secp256R1, 32);
+ test_ec(Nid::SECP384R1, EcCurves::Secp384R1, 48);
+ test_ec(Nid::SECP521R1, EcCurves::Secp521R1, 80);
+ test_ec(NID_ED25519, EcCurves::Ed25519, 32);
+ test_ec(NID_ED448, EcCurves::Ed448, 64);
+ }
+
+ #[test]
+ fn asc_parse() {
let id = [
0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab,
0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67,
@@ -217,6 +526,39 @@ mod test {
);
}
+ #[test]
+ fn retrievable_parse() {
+ let id = [
+ 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab,
+ 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67,
+ 0x89, 0xab, 0xcd, 0xef,
+ ];
+ let asc = GuestSecret::Retrievable {
+ kind: PlainText,
+ name: "test123".to_string(),
+ id: id.into(),
+ secret: vec![].into(),
+ };
+
+ assert_tokens(
+ &asc,
+ &[
+ Token::StructVariant {
+ name: "GuestSecret",
+ variant: "Retrievable",
+ len: 3,
+ },
+ Token::String("kind"),
+ Token::String("3 (PLAINTEXT)"),
+ Token::String("name"),
+ Token::String("test123"),
+ Token::String("id"),
+ Token::String("0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"),
+ Token::StructVariantEnd,
+ ],
+ );
+ }
+
#[test]
fn guest_secret_bin_null() {
let gs = GuestSecret::Null;
@@ -228,7 +570,7 @@ mod test {
}
#[test]
- fn guest_secret_bin_ap() {
+ fn guest_secret_bin_asoc() {
let gs = GuestSecret::Association {
name: "test".to_string(),
id: [1; 32].into(),
@@ -241,4 +583,21 @@ mod test {
assert_eq!(exp, gs_bytes_auth.get());
assert_eq!(&[2; 32], gs.confidential());
}
+
+ #[test]
+ fn guest_secret_bin_retr() {
+ let gs = GuestSecret::Retrievable {
+ kind: PlainText,
+ name: "test".to_string(),
+ id: [1; 32].into(),
+ secret: vec![2; 32].into(),
+ };
+ let auth = gs.auth();
+ let gs_bytes_auth = auth.get();
+ let mut exp = vec![0u8, 0, 0, 3, 0, 0, 0, 0x20, 0, 0, 0, 0, 0, 0, 0, 0];
+ exp.extend([1; 32]);
+
+ assert_eq!(exp, gs_bytes_auth);
+ assert_eq!(&[2; 32], gs.confidential());
+ }
}
diff --git a/rust/pv/src/uvsecret/retr_secret.rs b/rust/pv/src/uvsecret/retr_secret.rs
new file mode 100644
index 00000000..5fad016f
--- /dev/null
+++ b/rust/pv/src/uvsecret/retr_secret.rs
@@ -0,0 +1,234 @@
+// SPDX-License-Identifier: MIT
+//
+// Copyright IBM Corp. 2024
+
+use crate::{pem::Pem, uvsecret::guest_secret::MAX_SIZE_PLAIN_PAYLOAD, Result};
+
+use byteorder::BigEndian;
+use log::warn;
+use pv_core::{
+ request::Confidential,
+ uv::{ListableSecretType, RetrievableSecret, RetrieveCmd},
+};
+use zerocopy::{FromBytes, U16};
+
+/// An IBM Protected Key
+///
+/// A protected key, writeable as pem.
+///
+/// Will convert into PEM as:
+/// ```PEM
+///-----BEGIN IBM PROTECTED KEY-----
+///kind: <name>
+///
+///<protected key in base64>
+///-----END IBM PROTECTED KEY-----
+/// ```
+#[derive(Debug, PartialEq, Eq)]
+pub struct IbmProtectedKey {
+ kind: ListableSecretType,
+ key: Confidential<Vec<u8>>,
+}
+
+impl IbmProtectedKey {
+ /// Get the binary representation of the key.
+ pub fn data(&self) -> &[u8] {
+ self.key.value()
+ }
+
+ /// Converts a [`IbmProtectedKey`] into a vector.
+ pub fn into_bytes(self) -> Confidential<Vec<u8>> {
+ self.key
+ }
+
+ /// Get the data in PEM format.
+ ///
+ /// # Errors
+ ///
+ /// This function will return an error if the PEM conversion failed (very unlikely).
+ pub fn to_pem(&self) -> Result<Pem> {
+ Pem::new(
+ "IBM PROTECTED KEY",
+ format!("kind: {}", self.kind),
+ self.key.value(),
+ )
+ }
+
+ fn new<K>(kind: ListableSecretType, key: K) -> Self
+ where
+ K: Into<Confidential<Vec<u8>>>,
+ {
+ Self {
+ kind,
+ key: key.into(),
+ }
+ }
+}
+
+impl From<RetrieveCmd> for RetrievedSecret {
+ fn from(value: RetrieveCmd) -> Self {
+ let kind = value.meta_data().stype();
+ let key = value.into_key();
+
+ match kind {
+ ListableSecretType::Retrievable(RetrievableSecret::PlainText) => {
+ // Will not run into default, retrieve has a granularity of 16 bytes and 16 bytes is the
+ // minimum size
+ let len = U16::<BigEndian>::read_from_prefix(key.value())
+ .unwrap_or_default()
+ .get() as usize;
+
+ // Test if the plain text secret has a size:
+ // 1. len <= 8190
+ // 2. first two bytes are max 15 less than buffer-size+2
+ // 3. bytes after len + 2 are zero
+ match len <= MAX_SIZE_PLAIN_PAYLOAD
+ && key.value().len() - (len + 2) < 15
+ && key.value()[len + 2..].iter().all(|c| *c == 0)
+ {
+ false => Self::Plaintext(key),
+ true => Self::Plaintext(key.value()[2..len + 2].to_vec().into()),
+ }
+ }
+ kind => {
+ match kind {
+ ListableSecretType::Retrievable(_) => (),
+ _ => warn!("Retrieved an unretrievable Secret! Will continue; interpreting it as a protected key."),
+ }
+ Self::ProtectedKey(IbmProtectedKey::new(kind, key))
+ }
+ }
+ }
+}
+
+/// A retrieved Secret.
+#[derive(Debug, PartialEq, Eq)]
+pub enum RetrievedSecret {
+ /// A plaintext secret
+ Plaintext(Confidential<Vec<u8>>),
+ /// An [`IbmProtectedKey`]
+ ProtectedKey(IbmProtectedKey),
+}
+
+impl RetrievedSecret {
+ /// Create a new IBM PROTECTED KEY object
+ pub fn from_cmd(cmd: RetrieveCmd) -> Self {
+ cmd.into()
+ }
+
+ /// Get the binary representation of the key.
+ pub fn data(&self) -> &[u8] {
+ match self {
+ RetrievedSecret::Plaintext(p) => p.value(),
+ RetrievedSecret::ProtectedKey(p) => p.data(),
+ }
+ }
+
+ /// Converts a [`IbmProtectedKey`] into a vector.
+ pub fn into_bytes(self) -> Confidential<Vec<u8>> {
+ match self {
+ RetrievedSecret::Plaintext(p) => p,
+ RetrievedSecret::ProtectedKey(p) => p.into_bytes(),
+ }
+ }
+ /// Get the data in PEM format.
+ ///
+ /// # Errors
+ ///
+ /// This function will return an error if the PEM conversion failed (very unlikely).
+ pub fn to_pem(&self) -> Result<Pem> {
+ match self {
+ RetrievedSecret::Plaintext(p) => Pem::new("PLAINTEXT SECRET", None, p.value()),
+ RetrievedSecret::ProtectedKey(p) => p.to_pem(),
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use pv_core::uv::*;
+
+ fn mk_retr(secret: &[u8]) -> RetrievedSecret {
+ let entry = SecretEntry::new(
+ 0,
+ ListableSecretType::Retrievable(RetrievableSecret::PlainText),
+ SecretId::default(),
+ secret.len() as u32,
+ );
+ let mut cmd = RetrieveCmd::from_entry(entry).unwrap();
+ cmd.data().unwrap().copy_from_slice(secret);
+ RetrievedSecret::from_cmd(cmd)
+ }
+
+ #[test]
+ fn from_retr_cmd() {
+ let secret = vec![0, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0, 0, 0, 0];
+ let prot_key = mk_retr(&secret);
+ let exp = RetrievedSecret::Plaintext(secret[2..12].to_vec().into());
+ assert_eq!(prot_key, exp);
+ }
+
+ #[test]
+ fn from_retr_inv_size() {
+ let secret = vec![0x20; 32];
+ let prot_key = mk_retr(&secret);
+ let exp = RetrievedSecret::Plaintext(secret.into());
+ assert_eq!(prot_key, exp);
+ }
+
+ #[test]
+ fn from_retr_inv_no_zero_after_end() {
+ let secret = vec![0, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 1, 0, 0, 0];
+ let prot_key = mk_retr(&secret);
+ let exp = RetrievedSecret::Plaintext(secret.into());
+ assert_eq!(prot_key, exp);
+ }
+
+ #[test]
+ fn from_retr_inv_to_much_padding() {
+ let secret = vec![
+ 0, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ ];
+ let prot_key = mk_retr(&secret);
+ let exp = RetrievedSecret::Plaintext(secret.into());
+ assert_eq!(prot_key, exp);
+ }
+
+ #[test]
+ fn from_retr_0_size() {
+ let secret = vec![0x00; 32];
+ let prot_key = mk_retr(&secret);
+ let exp = RetrievedSecret::Plaintext(secret.into());
+ assert_eq!(prot_key, exp);
+ }
+
+ #[test]
+ fn plain_text_pem() {
+ let exp = "\
+ -----BEGIN PLAINTEXT SECRET-----\n\
+ ERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERER\n\
+ -----END PLAINTEXT SECRET-----\n";
+ let prot = RetrievedSecret::Plaintext(vec![17; 48].into());
+ let pem = prot.to_pem().unwrap();
+ let pem_str = pem.to_string();
+ assert_eq!(pem_str, exp);
+ }
+
+ #[test]
+ fn prot_key_pem() {
+ let exp = "\
+ -----BEGIN IBM PROTECTED KEY-----\n\
+ kind: AES-128-KEY\n\n\
+ ERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERER\n\
+ -----END IBM PROTECTED KEY-----\n";
+ let prot = IbmProtectedKey::new(
+ ListableSecretType::Retrievable(RetrievableSecret::Aes(AesSizes::Bits128)),
+ vec![17; 48],
+ );
+ let pem = prot.to_pem().unwrap();
+ let pem_str = pem.to_string();
+ assert_eq!(pem_str, exp);
+ }
+}

View File

@ -0,0 +1,95 @@
From a14f9d4edcc5db0d54e4fbe3ec3d98c7c270bf8e Mon Sep 17 00:00:00 2001
From: Steffen Eiden <seiden@linux.ibm.com>
Date: Fri, 13 Dec 2024 15:04:02 +0100
Subject: [PATCH] rust/pvsecret: Improve CLI
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Improve the wording of the help/man text/
Acked-by: Marc Hartmayer <marc@linux.ibm.com>
Reviewed-by: Christoph Schlameuss <schlameuss@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
---
rust/pvsecret/src/cli.rs | 26 +++++++++++++-------------
1 file changed, 13 insertions(+), 13 deletions(-)
diff --git a/rust/pvsecret/src/cli.rs b/rust/pvsecret/src/cli.rs
index 6deaaebd..c4b9f2b3 100644
--- a/rust/pvsecret/src/cli.rs
+++ b/rust/pvsecret/src/cli.rs
@@ -37,8 +37,8 @@ pub struct CreateSecretOpt {
/// Specifies the header of the guest image.
///
- /// Can be an IBM Secure Execution image created by genprotimg or an extracted IBM Secure
- /// Execution header. The header must start at a page boundary.
+ /// Can be an IBM Secure Execution image created by 'pvimg/genprotimg' or an
+ /// extracted IBM Secure Execution header.
#[arg(long, value_name = "FILE", value_hint = ValueHint::FilePath)]
pub hdr: String,
@@ -150,12 +150,12 @@ pub enum AddSecretType {
/// Create an association secret.
///
- /// Use an association secret to connect a trusted I/O device to a guest. The `pvapconfig` tool
+ /// Use an association secret to connect a trusted I/O device to a guest. The 'pvapconfig' tool
/// provides more information about association secrets.
Association {
- /// String to identify the new secret.
+ /// String that identifies the new secret.
///
- /// The actual secret is set with --input-secret. The name is saved in `NAME.yaml` with
+ /// The actual secret is set with '--input-secret'. The name is saved in `NAME.yaml` with
/// white-spaces mapped to `_`.
name: String,
@@ -166,15 +166,15 @@ pub enum AddSecretType {
stdout: bool,
/// Path from which to read the plaintext secret. Uses a random secret if not specified.
- #[arg(long, value_name = "FILE", value_hint = ValueHint::FilePath, conflicts_with("output_secret"))]
+ #[arg(long, value_name = "SECRET-FILE", value_hint = ValueHint::FilePath, conflicts_with("output_secret"))]
input_secret: Option<String>,
- /// Save the generated secret as plaintext in FILE.
+ /// Save the generated secret as plaintext in SECRET-FILE.
///
/// The generated secret can be used to generate add-secret requests for a different guest
- /// with the same secret using --input-secret. Destroy the secret when it is not used
+ /// with the same secret using '--input-secret'. Destroy the secret when it is not used
/// anymore.
- #[arg(long, value_name = "FILE", value_hint = ValueHint::FilePath,)]
+ #[arg(long, value_name = "SECRET-FILE", value_hint = ValueHint::FilePath,)]
output_secret: Option<String>,
},
}
@@ -243,13 +243,13 @@ pub enum Command {
/// Create a new add-secret request.
///
/// Create add-secret requests for IBM Secure Execution guests. Only create these requests in a
- /// trusted environment, such as your workstation. The `pvattest create` command creates a
+ /// trusted environment, such as your workstation. The 'pvattest create' command creates a
/// randomly generated key to protect the request. The generated requests can then be added on
- /// an IBM Secure Execution guest using `pvsecret add`. The guest can then use the secrets with
+ /// an IBM Secure Execution guest using 'pvsecret add'. The guest can then use the secrets with
/// the use case depending on the secret type.
Create(Box<CreateSecretOpt>),
- /// Perform an add-secret request (s390x only).
+ /// Submit an add-secret request to the Ultravisor (s390x only).
///
/// Perform an add-secret request using a previously generated add-secret request. Only
/// available on s390x.
@@ -258,7 +258,7 @@ pub enum Command {
/// Lock the secret-store (s390x only).
///
/// Lock the secret store (s390x only). After this command executed successfully, all
- /// add-secret requests will fail. Only available on s390x.
+ /// subsequent add-secret requests will fail. Only available on s390x.
Lock,
/// List all ultravisor secrets (s390x only).

View File

@ -0,0 +1,423 @@
From 93da795520ca2f0a73cfbfc951a9b16437a1b95b Mon Sep 17 00:00:00 2001
From: Steffen Eiden <seiden@linux.ibm.com>
Date: Mon, 19 Feb 2024 15:15:16 +0100
Subject: [PATCH] rust/pvsecret: Add support for retrievable secrets
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Support for creating and retrieving retrievable secrets.
Acked-by: Marc Hartmayer <marc@linux.ibm.com>
Reviewed-by: Christoph Schlameuss <schlameuss@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
---
rust/pvsecret/src/cli.rs | 129 +++++++++++++++++++++++++++++++-
rust/pvsecret/src/cmd.rs | 6 +-
rust/pvsecret/src/cmd/create.rs | 30 +++++++-
rust/pvsecret/src/cmd/list.rs | 12 ++-
rust/pvsecret/src/cmd/retr.rs | 62 +++++++++++++++
rust/pvsecret/src/main.rs | 1 +
6 files changed, 230 insertions(+), 10 deletions(-)
create mode 100644 rust/pvsecret/src/cmd/retr.rs
diff --git a/rust/pvsecret/src/cli.rs b/rust/pvsecret/src/cli.rs
index c4b9f2b3..4e747682 100644
--- a/rust/pvsecret/src/cli.rs
+++ b/rust/pvsecret/src/cli.rs
@@ -1,7 +1,10 @@
// SPDX-License-Identifier: MIT
//
-// Copyright IBM Corp. 2023
+// Copyright IBM Corp. 2023, 2024
+use std::fmt::Display;
+
+use clap::error::ErrorKind::ValueValidation;
use clap::{ArgGroup, Args, CommandFactory, Parser, Subcommand, ValueEnum, ValueHint};
use utils::{CertificateOptions, DeprecatedVerbosityOptions, STDOUT};
@@ -177,6 +180,72 @@ pub enum AddSecretType {
#[arg(long, value_name = "SECRET-FILE", value_hint = ValueHint::FilePath,)]
output_secret: Option<String>,
},
+
+ /// Create a retrievable secret.
+ ///
+ /// A retrievable secret is stored in the per-guest storage of the Ultravisor. A SE-guest can
+ /// retrieve the secret at runtime and use it. All retrievable secrets, but the plaintext
+ /// secret, are retrieved as wrapped/protected key objects and only usable inside the current,
+ /// running SE-guest instance.
+ #[command(visible_alias = "retr")]
+ Retrievable {
+ /// String that identifies the new secret.
+ ///
+ /// The actual secret is set with '--secret'. The name is saved in `NAME.yaml` with
+ /// white-spaces mapped to `_`.
+ name: String,
+
+ /// Print the hashed name to stdout.
+ ///
+ /// The hashed name is not written to `NAME.yaml`
+ #[arg(long)]
+ stdout: bool,
+
+ /// Use SECRET-FILE as retrievable secret
+ #[arg(long, value_name = "SECRET-FILE", value_hint = ValueHint::FilePath)]
+ secret: String,
+
+ /// Specify the secret type.
+ ///
+ /// Limitations to the input data apply depending on the secret type.
+ #[arg(long = "type", value_name = "TYPE")]
+ kind: RetrieveableSecretInpKind,
+ },
+}
+
+#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug)]
+pub enum RetrieveableSecretInpKind {
+ /// A plaintext secret.
+ /// Can be any file up to 8190 bytes long
+ Plain,
+ /// An AES key.
+ /// Must be a plain byte file 128, 192, or 256 bit long.
+ Aes,
+ /// An AES-XTS key.
+ /// Must be a plain byte file 512, or 1024 bit long.
+ AesXts,
+ /// A HMAC-SHA key.
+ /// Must be a plain byte file 512, or 1024 bit long.
+ HmacSha,
+ /// An elliptic curve private key.
+ /// Must be a PEM or DER file.
+ Ec,
+}
+
+impl Display for RetrieveableSecretInpKind {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(
+ f,
+ "{}",
+ match self {
+ Self::Plain => "PLAINTEXT",
+ Self::Aes => "AES KEY",
+ Self::AesXts => "AES-XTS KEY",
+ Self::HmacSha => "HMAC-SHA KEY",
+ Self::Ec => "EC PRIVATE KEY",
+ }
+ )
+ }
}
// all members s390x only
@@ -238,6 +307,56 @@ pub struct VerifyOpt {
pub output: String,
}
+// all members s390x only
+#[derive(Args, Debug)]
+pub struct RetrSecretOptions {
+ /// Specify the secret ID to be retrieved.
+ ///
+ /// Input type depends on '--inform'. If `yaml` (default) is specified, it must be a yaml
+ /// created by the create subcommand of this tool. If `hex` is specified, it must be a hex
+ /// 32-byte unsigned big endian number string. Leading zeros are required.
+ #[cfg(target_arch = "s390x")]
+ #[arg(value_name = "ID", value_hint = ValueHint::FilePath)]
+ pub input: String,
+
+ /// Specify the output path to place the secret value
+ #[cfg(target_arch = "s390x")]
+ #[arg(short, long, value_name = "FILE", default_value = STDOUT, value_hint = ValueHint::FilePath)]
+ pub output: String,
+
+ /// Define input type for the Secret ID
+ #[cfg(target_arch = "s390x")]
+ #[arg(long, value_enum, default_value_t)]
+ pub inform: RetrInpFmt,
+
+ /// Define the output format for the retrieved secret
+ #[cfg(target_arch = "s390x")]
+ #[arg(long, value_enum, default_value_t)]
+ pub outform: RetrOutFmt,
+}
+
+#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug, Default)]
+pub enum RetrInpFmt {
+ /// Use a yaml file
+ #[default]
+ Yaml,
+ /// Use a hex string.
+ Hex,
+}
+
+#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug, Default)]
+pub enum RetrOutFmt {
+ /// Write the secret as PEM.
+ ///
+ /// File starts with `-----BEGIN IBM PROTECTED KEY----` and `-----BEGIN
+ /// PLAINTEXT SECRET-----` for plaintext secrets it contains one header
+ /// line with the type information and the base64 protected key
+ #[default]
+ Pem,
+ /// Write the secret in binary.
+ Bin,
+}
+
#[derive(Subcommand, Debug)]
pub enum Command {
/// Create a new add-secret request.
@@ -274,6 +393,10 @@ pub enum Command {
/// provided key. Outputs the arbitrary user-data.
Verify(VerifyOpt),
+ /// Retrieve a secret from the UV secret store (s390x only).
+ #[command(visible_alias = "retr")]
+ Retrieve(RetrSecretOptions),
+
/// Print version information and exit.
#[command(aliases(["--version"]), hide(true))]
Version,
@@ -294,13 +417,13 @@ pub fn validate_cli(cli: &CliOptions) -> Result<(), clap::Error> {
}
if secret_out == &Some(format!("{name}.yaml")) {
return Err(CliOptions::command().error(
- clap::error::ErrorKind::ValueValidation,
+ ValueValidation,
format!("Secret output file and the secret name '{name}.yaml' are the same."),
));
}
if format!("{name}.yaml") == opt.output {
return Err(CliOptions::command().error(
- clap::error::ErrorKind::ValueValidation,
+ ValueValidation,
format!(
"output file and the secret name '{}' are the same.",
&opt.output
diff --git a/rust/pvsecret/src/cmd.rs b/rust/pvsecret/src/cmd.rs
index a826fb31..10d99a5b 100644
--- a/rust/pvsecret/src/cmd.rs
+++ b/rust/pvsecret/src/cmd.rs
@@ -16,6 +16,8 @@ mod add;
mod list;
#[cfg(target_arch = "s390x")]
mod lock;
+#[cfg(target_arch = "s390x")]
+mod retr;
// Commands (directly) related to UVCs are only available on s389x
#[cfg(target_arch = "s390x")]
@@ -24,12 +26,13 @@ mod uv_cmd {
pub use add::add;
pub use list::list;
pub use lock::lock;
+ pub use retr::retr;
pub const UV_CMD_FN: &[&str] = &["+add", "+lock", "+list"];
}
#[cfg(not(target_arch = "s390x"))]
mod uv_cmd {
- use crate::cli::{AddSecretOpt, ListSecretOpt};
+ use crate::cli::{AddSecretOpt, ListSecretOpt, RetrSecretOptions};
use anyhow::{bail, Result};
macro_rules! not_supp {
($name: ident $( ,$opt: ty )?) => {
@@ -40,6 +43,7 @@ mod uv_cmd {
}
not_supp!(add, AddSecretOpt);
not_supp!(list, ListSecretOpt);
+ not_supp!(retr, RetrSecretOptions);
not_supp!(lock);
pub const UV_CMD_FN: &[&str] = &[];
}
diff --git a/rust/pvsecret/src/cmd/create.rs b/rust/pvsecret/src/cmd/create.rs
index 9251c38c..73089a12 100644
--- a/rust/pvsecret/src/cmd/create.rs
+++ b/rust/pvsecret/src/cmd/create.rs
@@ -4,7 +4,6 @@
use std::path::Path;
-use crate::cli::{AddSecretType, CreateSecretFlags, CreateSecretOpt};
use anyhow::{anyhow, bail, Context, Error, Result};
use log::{debug, info, trace, warn};
use pv::{
@@ -22,6 +21,8 @@ use pv::{
use serde_yaml::Value;
use utils::get_writer_from_cli_file_arg;
+use crate::cli::{AddSecretType, CreateSecretFlags, CreateSecretOpt, RetrieveableSecretInpKind};
+
fn write_out<P, D>(path: &P, data: D, ctx: &str) -> pv::Result<()>
where
P: AsRef<Path>,
@@ -32,6 +33,23 @@ where
Ok(())
}
+fn retrievable(name: &str, secret: &str, kind: &RetrieveableSecretInpKind) -> Result<GuestSecret> {
+ let secret_data = read_file(secret, &format!("retrievable {kind}"))?.into();
+
+ match kind {
+ RetrieveableSecretInpKind::Plain => GuestSecret::plaintext(name, secret_data),
+ RetrieveableSecretInpKind::Aes => GuestSecret::aes(name, secret_data),
+ RetrieveableSecretInpKind::AesXts => GuestSecret::aes_xts(name, secret_data),
+ RetrieveableSecretInpKind::HmacSha => GuestSecret::hmac_sha(name, secret_data),
+ RetrieveableSecretInpKind::Ec => GuestSecret::ec(
+ name,
+ read_private_key(secret_data.value())
+ .with_context(|| format!("Cannot read {secret} as {kind} from PEM or DER"))?,
+ ),
+ }
+ .map_err(Error::from)
+}
+
/// Prepare an add-secret request
pub fn create(opt: &CreateSecretOpt) -> Result<()> {
if pv_guest_bit_set() {
@@ -88,6 +106,9 @@ fn build_asrcb(opt: &CreateSecretOpt) -> Result<AddSecretRequest> {
input_secret: None,
..
} => GuestSecret::association(name, None)?,
+ AddSecretType::Retrievable {
+ name, secret, kind, ..
+ } => retrievable(name, secret, kind)?,
};
trace!("AddSecret: {secret:x?}");
@@ -136,7 +157,9 @@ fn build_asrcb(opt: &CreateSecretOpt) -> Result<AddSecretRequest> {
.as_ref()
.map(|p| read_file(p, "User-signing key"))
.transpose()?
- .map(|buf| read_private_key(&buf))
+ .map(|buf| {
+ read_private_key(&buf).context("Cannot read {secret} as private key from PEM or DER")
+ })
.transpose()?;
if user_data.is_some() || user_key.is_some() {
@@ -258,6 +281,9 @@ fn write_secret<P: AsRef<Path>>(
write_out(path, guest_secret.confidential(), "Association secret")?
}
}
+ AddSecretType::Retrievable { name, stdout, .. } => {
+ write_yaml(name, guest_secret, stdout, outp_path)?
+ }
_ => (),
};
Ok(())
diff --git a/rust/pvsecret/src/cmd/list.rs b/rust/pvsecret/src/cmd/list.rs
index f7e3a72b..0bd9eca4 100644
--- a/rust/pvsecret/src/cmd/list.rs
+++ b/rust/pvsecret/src/cmd/list.rs
@@ -3,21 +3,25 @@
// Copyright IBM Corp. 2023
use crate::cli::{ListSecretOpt, ListSecretOutputType};
-use anyhow::{Context, Result};
+use anyhow::{Context, Error, Result};
use log::warn;
use pv::uv::{ListCmd, SecretList, UvDevice, UvcSuccess};
use utils::{get_writer_from_cli_file_arg, STDOUT};
/// Do a List Secrets UVC
-pub fn list(opt: &ListSecretOpt) -> Result<()> {
- let uv = UvDevice::open()?;
+pub fn list_uvc(uv: &UvDevice) -> Result<SecretList> {
let mut cmd = ListCmd::default();
match uv.send_cmd(&mut cmd)? {
UvcSuccess::RC_SUCCESS => (),
UvcSuccess::RC_MORE_DATA => warn!("There is more data available than expected"),
};
+ cmd.try_into().map_err(Error::new)
+}
- let secret_list: SecretList = cmd.try_into()?;
+/// Do a List Secrets UVC and output the list in the requested format
+pub fn list(opt: &ListSecretOpt) -> Result<()> {
+ let uv = UvDevice::open()?;
+ let secret_list = list_uvc(&uv)?;
let mut wr_out = get_writer_from_cli_file_arg(&opt.output)?;
match &opt.format {
diff --git a/rust/pvsecret/src/cmd/retr.rs b/rust/pvsecret/src/cmd/retr.rs
new file mode 100644
index 00000000..7f7704cc
--- /dev/null
+++ b/rust/pvsecret/src/cmd/retr.rs
@@ -0,0 +1,62 @@
+// SPDX-License-Identifier: MIT
+//
+// Copyright IBM Corp. 2024
+
+use super::list::list_uvc;
+use crate::cli::{RetrInpFmt, RetrOutFmt, RetrSecretOptions};
+use anyhow::{anyhow, bail, Context, Result};
+use log::{debug, info};
+use pv::{
+ misc::open_file,
+ misc::write,
+ secret::{GuestSecret, RetrievedSecret},
+ uv::{RetrieveCmd, SecretId, UvDevice},
+};
+use utils::get_writer_from_cli_file_arg;
+
+fn retrieve(id: &SecretId) -> Result<RetrievedSecret> {
+ let uv = UvDevice::open()?;
+ let secrets = list_uvc(&uv)?;
+ let secret = secrets
+ .into_iter()
+ .find(|s| s.id() == id.as_ref())
+ .ok_or(anyhow!(
+ "The UV secret-store has no secret with the ID {id}"
+ ))?;
+
+ info!("Try to retrieve secret at index: {}", secret.index());
+ debug!("Try to retrieve: {secret:?}");
+
+ let mut uv_cmd = RetrieveCmd::from_entry(secret)?;
+ uv.send_cmd(&mut uv_cmd)?;
+
+ Ok(RetrievedSecret::from_cmd(uv_cmd))
+}
+
+pub fn retr(opt: &RetrSecretOptions) -> Result<()> {
+ let mut output = get_writer_from_cli_file_arg(&opt.output)?;
+ let id = match &opt.inform {
+ RetrInpFmt::Yaml => match serde_yaml::from_reader(&mut open_file(&opt.input)?)? {
+ GuestSecret::Retrievable { id, .. } => id,
+ gs => bail!("The file contains a {gs}-secret, which is not retrievable."),
+ },
+ RetrInpFmt::Hex => {
+ serde_yaml::from_str(&opt.input).context("Cannot parse SecretId information")?
+ }
+ };
+
+ let retr_secret =
+ retrieve(&id).context("Could not retrieve the secret from the UV secret store.")?;
+
+ let out_data = match opt.outform {
+ RetrOutFmt::Bin => retr_secret.into_bytes(),
+ RetrOutFmt::Pem => retr_secret.to_pem()?.into_bytes(),
+ };
+ write(
+ &mut output,
+ out_data.value(),
+ &opt.output,
+ "IBM Protected Key",
+ )?;
+ Ok(())
+}
diff --git a/rust/pvsecret/src/main.rs b/rust/pvsecret/src/main.rs
index 502a6ea0..883a3ee2 100644
--- a/rust/pvsecret/src/main.rs
+++ b/rust/pvsecret/src/main.rs
@@ -45,6 +45,7 @@ fn main() -> ExitCode {
Command::Create(opt) => cmd::create(opt),
Command::Version => Ok(print_version!("2024", log_level; FEATURES.concat())),
Command::Verify(opt) => cmd::verify(opt),
+ Command::Retrieve(opt) => cmd::retr(opt),
};
match res {

View File

@ -0,0 +1,313 @@
From 256289a30aa5d3f6a4d2631dea69d1dc47205150 Mon Sep 17 00:00:00 2001
From: Steffen Eiden <seiden@linux.ibm.com>
Date: Wed, 12 Jun 2024 16:23:31 +0200
Subject: [PATCH] rust/pv_core: Refactor secret list
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Improve the secret list implementation. Use structs+{As,From}Bytes
instead of arbitrary seeks and reads/writes to parse the secret list.
Acked-by: Marc Hartmayer <marc@linux.ibm.com>
Reviewed-by: Christoph Schlameuss <schlameuss@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
---
rust/pv_core/src/uvdevice.rs | 10 ++
rust/pv_core/src/uvdevice/secret_list.rs | 124 ++++++++++++++---------
2 files changed, 86 insertions(+), 48 deletions(-)
diff --git a/rust/pv_core/src/uvdevice.rs b/rust/pv_core/src/uvdevice.rs
index e9848243..e701366d 100644
--- a/rust/pv_core/src/uvdevice.rs
+++ b/rust/pv_core/src/uvdevice.rs
@@ -163,6 +163,16 @@ pub enum UvcSuccess {
RC_MORE_DATA = UvDevice::RC_MORE_DATA,
}
+impl UvcSuccess {
+ /// Returns true if there is more data available
+ pub fn more_data(&self) -> bool {
+ match self {
+ Self::RC_SUCCESS => false,
+ Self::RC_MORE_DATA => true,
+ }
+ }
+}
+
/// The `UvDevice` is a (virtual) device on s390 machines to send Ultravisor commands(UVCs) from
/// userspace.
///
diff --git a/rust/pv_core/src/uvdevice/secret_list.rs b/rust/pv_core/src/uvdevice/secret_list.rs
index 4e955010..d7c268c9 100644
--- a/rust/pv_core/src/uvdevice/secret_list.rs
+++ b/rust/pv_core/src/uvdevice/secret_list.rs
@@ -4,16 +4,16 @@
use crate::{
assert_size,
- misc::to_u16,
uv::{AesSizes, AesXtsSizes, EcCurves, HmacShaSizes, ListCmd, RetrievableSecret},
uvdevice::UvCmd,
Error, Result,
};
-use byteorder::{BigEndian, ByteOrder, ReadBytesExt, WriteBytesExt};
+use byteorder::{BigEndian, ByteOrder};
use serde::{Deserialize, Serialize, Serializer};
use std::{
fmt::Display,
io::{Cursor, Read, Seek, Write},
+ mem::size_of,
slice::Iter,
vec::IntoIter,
};
@@ -31,7 +31,7 @@ impl SecretId {
/// Size in bytes of the [`SecretId`]
pub const ID_SIZE: usize = 32;
- /// Create a [`SecretId`] forom a buffer.
+ /// Create a [`SecretId`] from a buffer.
pub fn from(buf: [u8; Self::ID_SIZE]) -> Self {
buf.into()
}
@@ -120,7 +120,7 @@ impl SecretEntry {
&self.index
}
- /// Returns the secret type of this [`SecretEntry`].
+ /// Returns the secret type of this [`SecretEntry`]
pub fn stype(&self) -> ListableSecretType {
self.stype.get().into()
}
@@ -161,12 +161,45 @@ impl Display for SecretEntry {
}
}
+#[repr(C)]
+#[derive(Debug, FromBytes, AsBytes, FromZeroes, Clone, PartialEq, Eq, Default, Serialize)]
+struct SecretListHdr {
+ #[serde(skip)]
+ num_secrets_stored: U16<BigEndian>,
+ #[serde(serialize_with = "ser_u16")]
+ total_num_secrets: U16<BigEndian>,
+ #[serde(skip)]
+ next_secret_idx: U16<BigEndian>,
+ #[serde(skip)]
+ reserved_06: u16,
+ #[serde(skip)]
+ reserved_08: u64,
+}
+
+impl SecretListHdr {
+ fn new(num_secrets_stored: u16, total_num_secrets: u16, next_secret_idx: u16) -> Self {
+ Self {
+ num_secrets_stored: num_secrets_stored.into(),
+ total_num_secrets: total_num_secrets.into(),
+ next_secret_idx: next_secret_idx.into(),
+ reserved_06: 0,
+ reserved_08: 0,
+ }
+ }
+}
+assert_size!(SecretListHdr, 16);
+
/// List of secrets used to parse the [`crate::uv::ListCmd`] result.
///
-/// The list should not hold more than 0xffffffff elements
-#[derive(Debug, PartialEq, Eq, Serialize)]
+/// The list should ONLY be created from an UV-Call result using either:
+/// - [`TryInto::try_into`] from [`ListCmd`]
+/// - [`SecretList::decode`]
+/// Any other ways can create invalid lists that do not represent the UV secret store.
+/// The list must not hold more than [`u32::MAX`] elements
+#[derive(Debug, PartialEq, Eq, Serialize, Default)]
pub struct SecretList {
- total_num_secrets: usize,
+ #[serde(flatten)]
+ hdr: SecretListHdr,
secrets: Vec<SecretEntry>,
}
@@ -202,10 +235,14 @@ impl SecretList {
/// The content of this list will very likely not represent the status of the guest in the
/// Ultravisor. Use of [`SecretList::decode`] in any non-test environments is encuraged.
pub fn new(total_num_secrets: u16, secrets: Vec<SecretEntry>) -> Self {
- Self {
- total_num_secrets: total_num_secrets as usize,
+ Self::new_with_hdr(
+ SecretListHdr::new(total_num_secrets, total_num_secrets, 0),
secrets,
- }
+ )
+ }
+
+ fn new_with_hdr(hdr: SecretListHdr, secrets: Vec<SecretEntry>) -> Self {
+ Self { hdr, secrets }
}
/// Returns an iterator over the slice.
@@ -229,19 +266,12 @@ impl SecretList {
///
/// This number may be not equal to the provided number of [`SecretEntry`]
pub fn total_num_secrets(&self) -> usize {
- self.total_num_secrets
+ self.hdr.total_num_secrets.get() as usize
}
/// Encodes the list in the same binary format the UV would do
pub fn encode<T: Write>(&self, w: &mut T) -> Result<()> {
- let num_s = to_u16(self.secrets.len()).ok_or(Error::ManySecrets)?;
- w.write_u16::<BigEndian>(num_s)?;
- w.write_u16::<BigEndian>(
- self.total_num_secrets
- .try_into()
- .map_err(|_| Error::ManySecrets)?,
- )?;
- w.write_all(&[0u8; 12])?;
+ w.write_all(self.hdr.as_bytes())?;
for secret in &self.secrets {
w.write_all(secret.as_bytes())?;
}
@@ -250,19 +280,20 @@ impl SecretList {
/// Decodes the list from the binary format of the UV into this internal representation
pub fn decode<R: Read + Seek>(r: &mut R) -> std::io::Result<Self> {
- let num_s = r.read_u16::<BigEndian>()?;
- let total_num_secrets = r.read_u16::<BigEndian>()? as usize;
- let mut v: Vec<SecretEntry> = Vec::with_capacity(num_s as usize);
- r.seek(std::io::SeekFrom::Current(12))?; // skip reserved bytes
+ let mut buf = [0u8; size_of::<SecretListHdr>()];
+ r.read_exact(&mut buf)?;
+ let hdr = SecretListHdr::ref_from(&buf).unwrap();
+
let mut buf = [0u8; SecretEntry::STRUCT_SIZE];
- for _ in 0..num_s {
+ let mut v = Vec::with_capacity(hdr.num_secrets_stored.get() as usize);
+ for _ in 0..hdr.num_secrets_stored.get() {
r.read_exact(&mut buf)?;
// cannot fail. buffer has the same size as the secret entry
let secr = SecretEntry::read_from(buf.as_slice()).unwrap();
v.push(secr);
}
Ok(Self {
- total_num_secrets,
+ hdr: hdr.clone(),
secrets: v,
})
}
@@ -278,7 +309,7 @@ impl TryFrom<ListCmd> for SecretList {
impl Display for SecretList {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- writeln!(f, "Total number of secrets: {}", self.total_num_secrets)?;
+ writeln!(f, "Total number of secrets: {}", self.total_num_secrets())?;
if !self.secrets.is_empty() {
writeln!(f)?;
}
@@ -481,8 +512,8 @@ mod test {
let buf = [
0x00u8, 0x01, // num secr stored
0x01, 0x12, // total num secrets
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, // reserved
+ 0x01, 0x01, // next valid idx
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
// secret
0x00, 0x01, 0x00, 0x02, // idx + type
0x00, 0x00, 0x00, 0x20, // len
@@ -493,16 +524,16 @@ mod test {
0x00, 0x00, 0x00, 0x00,
];
- let exp = SecretList {
- total_num_secrets: 0x112,
- secrets: vec![SecretEntry {
+ let exp = SecretList::new_with_hdr(
+ SecretListHdr::new(0x001, 0x112, 0x101),
+ vec![SecretEntry {
index: 1.into(),
stype: 2.into(),
len: 32.into(),
res_8: 0,
id: SecretId::from([0; 32]),
}],
- };
+ );
let mut br = BufReader::new(Cursor::new(buf));
let sl = SecretList::decode(&mut br).unwrap();
@@ -514,8 +545,8 @@ mod test {
const EXP: &[u8] = &[
0x00, 0x01, // num secr stored
0x01, 0x12, // total num secrets
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
- 0x00, // reserved
+ 0x01, 0x01, // next valid idx
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
// secret
0x00, 0x01, 0x00, 0x02, // idx + type
0x00, 0x00, 0x00, 0x20, // len
@@ -526,16 +557,16 @@ mod test {
0x00, 0x00, 0x00, 0x00,
];
- let sl = SecretList {
- total_num_secrets: 0x112,
- secrets: vec![SecretEntry {
+ let sl = SecretList::new_with_hdr(
+ SecretListHdr::new(0x001, 0x112, 0x101),
+ vec![SecretEntry {
index: 1.into(),
stype: 2.into(),
len: 32.into(),
res_8: 0,
id: SecretId::from([0; 32]),
}],
- };
+ );
let mut buf = [0u8; 0x40];
{
@@ -587,26 +618,23 @@ mod test {
#[test]
fn secret_list_ser() {
- let list = SecretList {
- total_num_secrets: 0x112,
- secrets: vec![SecretEntry {
+ let list = SecretList::new_with_hdr(
+ SecretListHdr::new(0x001, 0x112, 0x101),
+ vec![SecretEntry {
index: 1.into(),
stype: 2.into(),
len: 32.into(),
res_8: 0,
id: SecretId::from([0; 32]),
}],
- };
+ );
assert_ser_tokens(
&list,
&[
- Token::Struct {
- name: "SecretList",
- len: 2,
- },
+ Token::Map { len: None },
Token::String("total_num_secrets"),
- Token::U64(0x112),
+ Token::U16(0x112),
Token::String("secrets"),
Token::Seq { len: Some(1) },
Token::Struct {
@@ -623,7 +651,7 @@ mod test {
Token::String("0x0000000000000000000000000000000000000000000000000000000000000000"),
Token::StructEnd,
Token::SeqEnd,
- Token::StructEnd,
+ Token::MapEnd,
],
)
}

View File

@ -0,0 +1,111 @@
From 93216d916c479ee1292aa1d598ac9c0e7f585bd8 Mon Sep 17 00:00:00 2001
From: Steffen Eiden <seiden@linux.ibm.com>
Date: Wed, 12 Jun 2024 16:35:15 +0200
Subject: [PATCH] rust/pv*: Support longer secret lists
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Make use of the enhanced list secrets UAPI for the uvdevice in the latest kernel
version. This allows fetching secret lists with more than 85 entries via
reserving more userspace memory in the IOCTL argument.
While at it, move the errno readout next to the ioctl-syscall.
Acked-by: Marc Hartmayer <marc@linux.ibm.com>
Reviewed-by: Christoph Schlameuss <schlameuss@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
---
rust/pv_core/src/uvdevice.rs | 6 ++++--
rust/pv_core/src/uvdevice/secret.rs | 11 +++++++++++
rust/pvsecret/src/cmd/list.rs | 28 +++++++++++++++++++++-------
3 files changed, 36 insertions(+), 9 deletions(-)
diff --git a/rust/pv_core/src/uvdevice.rs b/rust/pv_core/src/uvdevice.rs
index e701366d..689748a1 100644
--- a/rust/pv_core/src/uvdevice.rs
+++ b/rust/pv_core/src/uvdevice.rs
@@ -59,11 +59,13 @@ fn ioctl_raw(raw_fd: RawFd, cmd: c_ulong, cb: &mut IoctlCb) -> Result<()> {
rc = ioctl(raw_fd, cmd, cb.as_ptr_mut());
}
+ // NOTE io::Error handles all errnos ioctl uses
+ let errno = std::io::Error::last_os_error();
+
debug!("ioctl resulted with {cb:?}");
match rc {
0 => Ok(()),
- // NOTE io::Error handles all errnos ioctl uses
- _ => Err(std::io::Error::last_os_error().into()),
+ _ => Err(errno.into()),
}
}
diff --git a/rust/pv_core/src/uvdevice/secret.rs b/rust/pv_core/src/uvdevice/secret.rs
index 263f17d5..cb5b7233 100644
--- a/rust/pv_core/src/uvdevice/secret.rs
+++ b/rust/pv_core/src/uvdevice/secret.rs
@@ -24,6 +24,17 @@ impl ListCmd {
Self(vec![0; size])
}
+ /// Create a new list secrets command with `pages` capacity.
+ ///
+ /// * `pages` - number pf pages to allocate for this IOCTL
+ ///
+ /// # Panic
+ /// This function will trigger a panic if the allocation size is larger than [`usize::MAX`].
+ /// Very likely an OOM situation occurs way before this!
+ pub fn with_pages(pages: usize) -> Self {
+ Self::with_size(pages * PAGESIZE)
+ }
+
/// Create a new list secrets command with a one page capacity
pub fn new() -> Self {
Self::with_size(PAGESIZE)
diff --git a/rust/pvsecret/src/cmd/list.rs b/rust/pvsecret/src/cmd/list.rs
index 0bd9eca4..56294cac 100644
--- a/rust/pvsecret/src/cmd/list.rs
+++ b/rust/pvsecret/src/cmd/list.rs
@@ -2,19 +2,33 @@
//
// Copyright IBM Corp. 2023
+use std::io::ErrorKind;
+
use crate::cli::{ListSecretOpt, ListSecretOutputType};
use anyhow::{Context, Error, Result};
-use log::warn;
-use pv::uv::{ListCmd, SecretList, UvDevice, UvcSuccess};
+use log::{info, warn};
+use pv::uv::{ListCmd, SecretList, UvDevice};
use utils::{get_writer_from_cli_file_arg, STDOUT};
+const SECRET_LIST_BUF_SIZE: usize = 4;
+
/// Do a List Secrets UVC
pub fn list_uvc(uv: &UvDevice) -> Result<SecretList> {
- let mut cmd = ListCmd::default();
- match uv.send_cmd(&mut cmd)? {
- UvcSuccess::RC_SUCCESS => (),
- UvcSuccess::RC_MORE_DATA => warn!("There is more data available than expected"),
- };
+ let mut cmd = ListCmd::with_pages(SECRET_LIST_BUF_SIZE);
+ let more_data = match uv.send_cmd(&mut cmd) {
+ Ok(v) => Ok(v),
+ Err(pv::PvCoreError::Io(e)) if e.kind() == ErrorKind::InvalidInput => {
+ info!("Uvdevice does not suport longer list. Fallback to one page list.");
+ cmd = ListCmd::default();
+ uv.send_cmd(&mut cmd)
+ }
+ Err(e) => Err(e),
+ }?
+ .more_data();
+ if more_data {
+ warn!("The secret list contains more data but the uvdevice cannot show all.");
+ }
+
cmd.try_into().map_err(Error::new)
}

View File

@ -0,0 +1,387 @@
From ff04f76257791593c8f92374f295a0c478e3b0f7 Mon Sep 17 00:00:00 2001
From: Steffen Eiden <seiden@linux.ibm.com>
Date: Mon, 5 Aug 2024 09:34:47 +0200
Subject: [PATCH] rust/pv*: Allow the use of non-hashes secret IDs
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Secret IDs identify a secret in the store. Tooling (pvsecret) calculates
them by hashing a user-defined string. With this patch it is now
possible to skip the hash step and directly use the input string as the
ID. Up to the first 31 bytes of the input ASCII-string are used. The last byte
is the NUL char. During list pvsecret tries to interpret the secret
as ASCII string and if possible displays the ASCII characters alongside
the hex number.
Also, use the Upper/Lower Hex formatters for the hexstring formatting of
SecretId. Display will, additionally show the ASCII representation if
applicable.
While at it, use Self wherever possible.
Acked-by: Marc Hartmayer <marc@linux.ibm.com>
Reviewed-by: Christoph Schlameuss <schlameuss@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
---
rust/pv/src/uvsecret/guest_secret.rs | 16 ++-
rust/pv_core/src/uvdevice/secret_list.rs | 163 ++++++++++++++++++++---
rust/pvsecret/src/cli.rs | 8 ++
rust/pvsecret/src/cmd/create.rs | 4 +-
rust/pvsecret/src/cmd/retr.rs | 14 +-
5 files changed, 184 insertions(+), 21 deletions(-)
diff --git a/rust/pv/src/uvsecret/guest_secret.rs b/rust/pv/src/uvsecret/guest_secret.rs
index 3bad6d3c..87b58bb8 100644
--- a/rust/pv/src/uvsecret/guest_secret.rs
+++ b/rust/pv/src/uvsecret/guest_secret.rs
@@ -92,7 +92,8 @@ macro_rules! retr_constructor {
}
impl GuestSecret {
- fn name_to_id(name: &str) -> Result<SecretId> {
+ /// Hashes the name with sha256
+ pub fn name_to_id(name: &str) -> Result<SecretId> {
let id: [u8; SecretId::ID_SIZE] = hash(MessageDigest::sha256(), name.as_bytes())?
.to_vec()
.try_into()
@@ -135,6 +136,19 @@ impl GuestSecret {
retr_constructor!(#[doc = r"This function will return an error if OpenSSL cannot create a hash or the curve is invalid"]
| #[doc = r"EC PRIVATE Key"] => PKey<Private>, ec);
+ /// Use the name as ID, do not hash it
+ pub fn no_hash_name(&mut self) {
+ match self {
+ Self::Null => (),
+ Self::Association {
+ name, ref mut id, ..
+ }
+ | Self::Retrievable {
+ name, ref mut id, ..
+ } => id.clone_from(&SecretId::from_string(name)),
+ }
+ }
+
/// Reference to the confidential data
pub fn confidential(&self) -> &[u8] {
match &self {
diff --git a/rust/pv_core/src/uvdevice/secret_list.rs b/rust/pv_core/src/uvdevice/secret_list.rs
index d7c268c9..7c7e63b5 100644
--- a/rust/pv_core/src/uvdevice/secret_list.rs
+++ b/rust/pv_core/src/uvdevice/secret_list.rs
@@ -11,7 +11,9 @@ use crate::{
use byteorder::{BigEndian, ByteOrder};
use serde::{Deserialize, Serialize, Serializer};
use std::{
- fmt::Display,
+ cmp::min,
+ ffi::CStr,
+ fmt::{Debug, Display, LowerHex, UpperHex},
io::{Cursor, Read, Seek, Write},
mem::size_of,
slice::Iter,
@@ -35,6 +37,33 @@ impl SecretId {
pub fn from(buf: [u8; Self::ID_SIZE]) -> Self {
buf.into()
}
+
+ /// Create a Id from a string
+ ///
+ /// Uses the first 31 bytes from `name` as id
+ /// Does not hash anything. Byte 32 is the NUL char
+ pub fn from_string(name: &str) -> Self {
+ let len = min(name.len(), Self::ID_SIZE - 1);
+ let mut res = Self::default();
+ res.0[0..len].copy_from_slice(&name.as_bytes()[0..len]);
+ res
+ }
+
+ /// Tries to represent the Id as printable-ASCII string
+ pub fn as_ascii(&self) -> Option<&str> {
+ if let Ok(t) = CStr::from_bytes_until_nul(&self.0) {
+ if let Ok(t) = t.to_str() {
+ if !t.is_empty()
+ && t.chars()
+ .all(|c| c.is_ascii_whitespace() | c.is_ascii_graphic())
+ && self.0[t.len()..].iter().all(|b| *b == 0)
+ {
+ return Some(t);
+ }
+ }
+ };
+ None
+ }
}
impl Serialize for SecretId {
@@ -42,8 +71,8 @@ impl Serialize for SecretId {
where
S: Serializer,
{
- // calls Display at one point
- ser.serialize_str(&self.to_string())
+ // calls LowerHex at one point
+ ser.serialize_str(&format!("{self:#x}"))
}
}
@@ -56,12 +85,36 @@ impl<'de> Deserialize<'de> for SecretId {
}
}
+impl UpperHex for SecretId {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ if f.alternate() {
+ write!(f, "0x")?;
+ }
+ for b in self.0 {
+ write!(f, "{b:02X}")?;
+ }
+ Ok(())
+ }
+}
+
+impl LowerHex for SecretId {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ if f.alternate() {
+ write!(f, "0x")?;
+ }
+ for b in self.0 {
+ write!(f, "{b:02x}")?;
+ }
+ Ok(())
+ }
+}
+
impl Display for SecretId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- let mut s = String::with_capacity(32 * 2 + 2);
- s.push_str("0x");
- let s = self.0.iter().fold(s, |acc, e| acc + &format!("{e:02x}"));
- write!(f, "{s}")
+ if let Some(s) = self.as_ascii() {
+ write!(f, "{s} | ")?;
+ }
+ write!(f, "{self:#x}")
}
}
@@ -79,7 +132,7 @@ impl AsRef<[u8]> for SecretId {
/// A secret in a [`SecretList`]
#[repr(C)]
-#[derive(Debug, PartialEq, Eq, AsBytes, FromZeroes, FromBytes, Serialize)]
+#[derive(Debug, Clone, PartialEq, Eq, AsBytes, FromZeroes, FromBytes, Serialize)]
pub struct SecretEntry {
#[serde(serialize_with = "ser_u16")]
index: U16<BigEndian>,
@@ -153,11 +206,7 @@ impl Display for SecretEntry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let stype: ListableSecretType = self.stype.get().into();
writeln!(f, "{} {}:", self.index, stype)?;
- write!(f, " ")?;
- for b in self.id.as_ref() {
- write!(f, "{b:02x}")?;
- }
- Ok(())
+ write!(f, " {}", self.id)
}
}
@@ -269,6 +318,11 @@ impl SecretList {
self.hdr.total_num_secrets.get() as usize
}
+ /// Find the first [`SecretEntry`] that has the provided [`SecretId`]
+ pub fn find(&self, id: &SecretId) -> Option<SecretEntry> {
+ self.iter().find(|e| e.id() == id.as_ref()).cloned()
+ }
+
/// Encodes the list in the same binary format the UV would do
pub fn encode<T: Write>(&self, w: &mut T) -> Result<()> {
w.write_all(self.hdr.as_bytes())?;
@@ -456,7 +510,7 @@ where
type Value = [u8; SecretId::ID_SIZE];
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
- formatter.write_str("a `32 bytes long hexstring` prepended with 0x")
+ formatter.write_str("a `32 bytes (=64 character) long hexstring` prepended with 0x")
}
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
@@ -464,7 +518,10 @@ where
E: serde::de::Error,
{
if s.len() != SecretId::ID_SIZE * 2 + "0x".len() {
- return Err(serde::de::Error::invalid_length(s.len() - 2, &self));
+ return Err(serde::de::Error::invalid_length(
+ s.len().saturating_sub("0x".len()),
+ &self,
+ ));
}
let nb = s.strip_prefix("0x").ok_or_else(|| {
serde::de::Error::invalid_value(serde::de::Unexpected::Str(s), &self)
@@ -655,4 +712,80 @@ mod test {
],
)
}
+
+ #[test]
+ fn secret_id_display() {
+ let text = "Fancy secret ID";
+ let id = SecretId::from_string(text);
+
+ let exp =
+ "Fancy secret ID | 0x46616e6379207365637265742049440000000000000000000000000000000000";
+ assert_eq!(id.to_string(), exp);
+ }
+
+ #[test]
+ fn secret_id_long_name() {
+ let text = "the most fanciest secret ID you ever seen in the time the universe exists";
+ let id = SecretId::from_string(text);
+ let exp =
+ "the most fanciest secret ID you | 0x746865206d6f73742066616e63696573742073656372657420494420796f7500";
+ assert_eq!(id.to_string(), exp);
+ }
+
+ #[test]
+ fn secret_id_no_ascii_name() {
+ let text = [0; 32];
+ let id = SecretId::from(text);
+
+ let exp = "0x0000000000000000000000000000000000000000000000000000000000000000";
+ assert_eq!(id.to_string(), exp);
+ }
+
+ #[test]
+ fn secret_id_no_ascii_name2() {
+ let text = [
+ 0x25, 0x55, 3, 4, 50, 0, 6, 0, 8, 0, 0, 0, 0, 0, 0, 0, 90, 0, 0xa, 0, 0, 0, 0, 0xf, 0,
+ 0, 0, 0, 0, 0, 0, 0,
+ ];
+ let id = SecretId::from(text);
+ assert_eq!(id.as_ascii(), None);
+ }
+
+ #[test]
+ fn secret_id_no_ascii_name3() {
+ let text = [
+ 0x25, 0x55, 0, 4, 50, 0, 6, 0, 8, 0, 0, 0, 0, 0, 0, 0, 90, 0, 0xa, 0, 0, 0, 0, 0xf, 0,
+ 0, 0, 0, 0, 0, 0, 0,
+ ];
+ let id = SecretId::from(text);
+ assert_eq!(id.as_ascii(), None);
+ }
+
+ #[test]
+ fn secret_id_hex() {
+ let id_str = "Nice Test 123";
+ let id = SecretId::from_string(id_str);
+
+ let s = format!("{id:#x}");
+ assert_eq!(
+ s,
+ "0x4e69636520546573742031323300000000000000000000000000000000000000"
+ );
+ let s = format!("{id:x}");
+ assert_eq!(
+ s,
+ "4e69636520546573742031323300000000000000000000000000000000000000"
+ );
+ let s = format!("{id:#X}");
+ assert_eq!(
+ s,
+ "0x4E69636520546573742031323300000000000000000000000000000000000000"
+ );
+
+ let s = format!("{id:X}");
+ assert_eq!(
+ s,
+ "4E69636520546573742031323300000000000000000000000000000000000000"
+ );
+ }
}
diff --git a/rust/pvsecret/src/cli.rs b/rust/pvsecret/src/cli.rs
index 4e747682..d858fc29 100644
--- a/rust/pvsecret/src/cli.rs
+++ b/rust/pvsecret/src/cli.rs
@@ -141,6 +141,12 @@ pub struct CreateSecretOpt {
/// by default.
#[arg(long, value_name = "FILE", value_hint = ValueHint::FilePath,)]
pub user_sign_key: Option<String>,
+
+ /// Do not hash the name, use it directly as secret ID.
+ ///
+ /// Ignored for meta-secrets.
+ #[arg(long)]
+ pub use_name: bool,
}
#[derive(Subcommand, Debug)]
@@ -342,6 +348,8 @@ pub enum RetrInpFmt {
Yaml,
/// Use a hex string.
Hex,
+ /// Use a name-string. Will hash it if no secret with the name found.
+ Name,
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug, Default)]
diff --git a/rust/pvsecret/src/cmd/create.rs b/rust/pvsecret/src/cmd/create.rs
index 73089a12..fab37e67 100644
--- a/rust/pvsecret/src/cmd/create.rs
+++ b/rust/pvsecret/src/cmd/create.rs
@@ -94,7 +94,7 @@ fn read_private_key(buf: &[u8]) -> Result<PKey<Private>> {
fn build_asrcb(opt: &CreateSecretOpt) -> Result<AddSecretRequest> {
debug!("Build add-secret request");
- let secret = match &opt.secret {
+ let mut secret = match &opt.secret {
AddSecretType::Meta => GuestSecret::Null,
AddSecretType::Association {
name,
@@ -112,6 +112,8 @@ fn build_asrcb(opt: &CreateSecretOpt) -> Result<AddSecretRequest> {
};
trace!("AddSecret: {secret:x?}");
+ opt.use_name.then(|| secret.no_hash_name());
+
let mut flags = match &opt.pcf {
Some(v) => (&try_parse_u64(v, "pcf")?).into(),
None => AddSecretFlags::default(),
diff --git a/rust/pvsecret/src/cmd/retr.rs b/rust/pvsecret/src/cmd/retr.rs
index 7f7704cc..ad3e91c3 100644
--- a/rust/pvsecret/src/cmd/retr.rs
+++ b/rust/pvsecret/src/cmd/retr.rs
@@ -17,12 +17,17 @@ use utils::get_writer_from_cli_file_arg;
fn retrieve(id: &SecretId) -> Result<RetrievedSecret> {
let uv = UvDevice::open()?;
let secrets = list_uvc(&uv)?;
- let secret = secrets
- .into_iter()
- .find(|s| s.id() == id.as_ref())
+ let secret = match secrets.find(id) {
+ Some(s) => s,
+ // hash it + try again if it is ASCII-representable
+ None => match id.as_ascii() {
+ Some(s) => secrets.find(&GuestSecret::name_to_id(s)?),
+ None => None,
+ }
.ok_or(anyhow!(
"The UV secret-store has no secret with the ID {id}"
- ))?;
+ ))?,
+ };
info!("Try to retrieve secret at index: {}", secret.index());
debug!("Try to retrieve: {secret:?}");
@@ -43,6 +48,7 @@ pub fn retr(opt: &RetrSecretOptions) -> Result<()> {
RetrInpFmt::Hex => {
serde_yaml::from_str(&opt.input).context("Cannot parse SecretId information")?
}
+ RetrInpFmt::Name => SecretId::from_string(&opt.input),
};
let retr_secret =

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,167 @@
From 5b6d7a467dc342c9c25a0af72b2d5546798cdc94 Mon Sep 17 00:00:00 2001
From: Marc Hartmayer <mhartmay@linux.ibm.com>
Date: Thu, 12 Dec 2024 20:19:56 +0100
Subject: [PATCH] rust/pvimg: Add '--cck <FILE>' command line option and make
'--comm-key' an alias
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Add '--cck <FILE>' as an command line option and make '--comm-key' an
alias of it. This makes the command line more similar to the other
Secure Execution related PV-tools (e.g. pvattest and pvsecret).
Suggested-by: Reinhard Bündgen <buendgen@de.ibm.com>
Reviewed-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>
---
rust/pvimg/man/genprotimg.1 | 11 +++++------
rust/pvimg/man/pvimg-create.1 | 11 +++++------
rust/pvimg/src/cli.rs | 14 ++++++++------
rust/pvimg/src/cmd/create.rs | 3 +--
4 files changed, 19 insertions(+), 20 deletions(-)
Index: s390-tools-2.36.0/rust/pvimg/man/genprotimg.1
===================================================================
--- s390-tools-2.36.0.orig/rust/pvimg/man/genprotimg.1
+++ s390-tools-2.36.0/rust/pvimg/man/genprotimg.1
@@ -123,7 +123,7 @@ Overwrite an existing Secure Execution b
.RE
.RE
.PP
-\-\-comm\-key <FILE>
+\-\-cck, \-\-comm\-key <FILE>
.RS 4
Use the content of FILE as the customer\-communication key (CCK). The file must
contain exactly 32 bytes of data.
@@ -133,7 +133,7 @@ contain exactly 32 bytes of data.
\-\-enable\-dump
.RS 4
Enable Secure Execution guest dump support. This option requires the
-\fB\-\-comm\-key\fR option.
+\fB\-\-cck\fR option.
.RE
.RE
.PP
@@ -146,8 +146,7 @@ Disable Secure Execution guest dump supp
\-\-enable\-cck\-extension\-secret
.RS 4
Add\-secret requests must provide an extension secret that matches the
-CCK\-derived extension secret. This option requires the \fB\-\-comm\-key\fR
-option.
+CCK\-derived extension secret. This option requires the \fB\-\-cck\fR option.
.RE
.RE
.PP
@@ -268,7 +267,7 @@ Generate an IBM Secure Execution image:
Generate an IBM Secure Execution image with Secure Execution guest dump support:
.PP
-.B genprotimg \-i \fI\,/boot/vmlinuz\/\fR \-r \fI\,/boot/initrd.img\/\fR \-p \fI\,parmfile\/\fR \-k \fI\,host_key.crt\/\fR \-C \fI\,ibm-z-host-key-signing.crt\/\fR \-C \fI\,DigiCertCA.crt\fR \-o \fI\,/boot/secure-linux\/\fR \-\-enable\-dump \-\-comm\-key \fI\,comm-key\fR
+.B genprotimg \-i \fI\,/boot/vmlinuz\/\fR \-r \fI\,/boot/initrd.img\/\fR \-p \fI\,parmfile\/\fR \-k \fI\,host_key.crt\/\fR \-C \fI\,ibm-z-host-key-signing.crt\/\fR \-C \fI\,DigiCertCA.crt\fR \-o \fI\,/boot/secure-linux\/\fR \-\-enable\-dump \-\-cck \fI\,comm-key\fR
.SH NOTES
.IP "1." 4
The \fBgenprotimg\fR(1) command is a symbolic link to the \fBpvimg-create\fR(1) command.
Index: s390-tools-2.36.0/rust/pvimg/man/pvimg-create.1
===================================================================
--- s390-tools-2.36.0.orig/rust/pvimg/man/pvimg-create.1
+++ s390-tools-2.36.0/rust/pvimg/man/pvimg-create.1
@@ -122,7 +122,7 @@ Overwrite an existing Secure Execution b
.RE
.RE
.PP
-\-\-comm\-key <FILE>
+\-\-cck, \-\-comm\-key <FILE>
.RS 4
Use the content of FILE as the customer\-communication key (CCK). The file must
contain exactly 32 bytes of data.
@@ -132,7 +132,7 @@ contain exactly 32 bytes of data.
\-\-enable\-dump
.RS 4
Enable Secure Execution guest dump support. This option requires the
-\fB\-\-comm\-key\fR option.
+\fB\-\-cck\fR option.
.RE
.RE
.PP
@@ -145,8 +145,7 @@ Disable Secure Execution guest dump supp
\-\-enable\-cck\-extension\-secret
.RS 4
Add\-secret requests must provide an extension secret that matches the
-CCK\-derived extension secret. This option requires the \fB\-\-comm\-key\fR
-option.
+CCK\-derived extension secret. This option requires the \fB\-\-cck\fR option.
.RE
.RE
.PP
@@ -249,7 +248,7 @@ Generate an IBM Secure Execution image:
Generate an IBM Secure Execution image with Secure Execution guest dump support:
.PP
-.B pvimg create \-i \fI\,/boot/vmlinuz\/\fR \-r \fI\,/boot/initrd.img\/\fR \-p \fI\,parmfile\/\fR \-k \fI\,host_key.crt\/\fR \-C \fI\,ibm-z-host-key-signing.crt\/\fR \-C \fI\,DigiCertCA.crt\fR \-o \fI\,/boot/secure-linux\/\fR \-\-enable\-dump \-\-comm\-key \fI\,comm-key\fR
+.B pvimg create \-i \fI\,/boot/vmlinuz\/\fR \-r \fI\,/boot/initrd.img\/\fR \-p \fI\,parmfile\/\fR \-k \fI\,host_key.crt\/\fR \-C \fI\,ibm-z-host-key-signing.crt\/\fR \-C \fI\,DigiCertCA.crt\fR \-o \fI\,/boot/secure-linux\/\fR \-\-enable\-dump \-\-cck \fI\,comm-key\fR
.SH NOTES
.IP "1." 4
The \fBgenprotimg\fR(1) command is a symbolic link to the \fBpvimg-create\fR(1) command.
Index: s390-tools-2.36.0/rust/pvimg/src/cli.rs
===================================================================
--- s390-tools-2.36.0.orig/rust/pvimg/src/cli.rs
+++ s390-tools-2.36.0/rust/pvimg/src/cli.rs
@@ -96,8 +96,8 @@ pub struct ComponentPaths {
#[command(group(ArgGroup::new("header-flags").multiple(true).conflicts_with_all(["x_pcf", "x_scf"])))]
pub struct CreateBootImageLegacyFlags {
/// Enable Secure Execution guest dump support. This option requires the
- /// '--comm-key' option.
- #[arg(long, action = clap::ArgAction::SetTrue, requires="comm_key", group="header-flags")]
+ /// '--cck' option.
+ #[arg(long, action = clap::ArgAction::SetTrue, requires="cck", group="header-flags")]
pub enable_dump: Option<bool>,
/// Disable Secure Execution guest dump support (default).
@@ -105,9 +105,9 @@ pub struct CreateBootImageLegacyFlags {
pub disable_dump: Option<bool>,
/// Add-secret requests must provide an extension secret that matches the
- /// CCK-derived extension secret. This option requires the '--comm-key'
+ /// CCK-derived extension secret. This option requires the '--cck'
/// option.
- #[arg(long, action = clap::ArgAction::SetTrue, requires="comm_key", group="header-flags")]
+ #[arg(long, action = clap::ArgAction::SetTrue, requires="cck", group="header-flags")]
pub enable_cck_extension_secret: Option<bool>,
/// Add-secret requests don't have to provide the CCK-derived extension
@@ -328,8 +328,8 @@ pub struct CreateBootImageArgs {
/// Use the content of FILE as the customer-communication key (CCK).
///
/// The file must contain exactly 32 bytes of data.
- #[arg(long, value_name = "FILE")]
- pub comm_key: Option<PathBuf>,
+ #[arg(long, value_name = "FILE", visible_alias = "comm-key")]
+ pub cck: Option<PathBuf>,
#[clap(flatten)]
pub legacy_flags: CreateBootImageLegacyFlags,
@@ -482,6 +482,8 @@ mod test {
flat_map_collect(insert(mvca.clone(), vec![CliOption::new("enable-dump", ["--enable-dump"]),
CliOption::new("comm-key", ["--comm-key", "/dev/null"])])),
flat_map_collect(insert(mvca.clone(), vec![CliOption::new("enable-dump", ["--enable-dump"]),
+ CliOption::new("comm-key", ["--cck", "/dev/null"])])),
+ flat_map_collect(insert(mvca.clone(), vec![CliOption::new("enable-dump", ["--enable-dump"]),
CliOption::new("comm-key", ["--comm-key", "/dev/null"])])),
flat_map_collect(insert(mvca.clone(), vec![CliOption::new("x-pcf", ["--x-pcf", "0x0"]),
CliOption::new("x-scf", ["--x-scf", "0x0"])])),
Index: s390-tools-2.36.0/rust/pvimg/src/cmd/create.rs
===================================================================
--- s390-tools-2.36.0.orig/rust/pvimg/src/cmd/create.rs
+++ s390-tools-2.36.0/rust/pvimg/src/cmd/create.rs
@@ -137,8 +137,7 @@ pub fn create(opt: &CreateBootImageArgs)
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 user_provided_keys = read_user_provided_keys(opt.cck.as_deref(), &opt.experimental_args)?;
let (plaintext_flags, secret_flags) = parse_flags(opt)?;
if plaintext_flags.is_set(PcfV1::NoComponentEncryption) {

View File

@ -0,0 +1,58 @@
From 560b276f7e9938475af921c8ebd4cd05910dbf31 Mon Sep 17 00:00:00 2001
From: Marc Hartmayer <mhartmay@linux.ibm.com>
Date: Fri, 6 Dec 2024 20:45:36 +0100
Subject: [PATCH] rust/pvimg: Fix possible 'range start index out of range for
slice' error
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Fix possible 'range start index 16 out of range for slice of length 0'
error by adding a check of the slice data length.
Fixes: f4cf4ae6ebb1 ("rust: Add a new tool called 'pvimg'")
Reviewed-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>
---
rust/pvimg/src/pv_utils/se_hdr/brb.rs | 23 +++++++++++++++++++++++
1 file changed, 23 insertions(+)
diff --git a/rust/pvimg/src/pv_utils/se_hdr/brb.rs b/rust/pvimg/src/pv_utils/se_hdr/brb.rs
index f7ae1bc9..ac3a2e6e 100644
--- a/rust/pvimg/src/pv_utils/se_hdr/brb.rs
+++ b/rust/pvimg/src/pv_utils/se_hdr/brb.rs
@@ -259,6 +259,10 @@ impl SeHdr {
return Err(Error::InvalidSeHdr);
}
+ if sehs <= common_size {
+ return Err(Error::InvalidSeHdr);
+ }
+
data.resize(sehs, 0);
reader.read_exact(&mut data[common_size..])?;
Self::try_from_data(&data)
@@ -366,3 +370,22 @@ impl AeadCipherTrait for SeHdrPlain {
self.data.aead_tag_size()
}
}
+
+#[cfg(test)]
+mod tests {
+ use std::io::Cursor;
+
+ use super::SeHdr;
+ use crate::error::Error;
+
+ #[test]
+ fn test_sehdr_try_from_io() {
+ // Invalid SeHdr as `sehs` is set to 0
+ assert!(matches!(
+ SeHdr::try_from_io(Cursor::new([
+ 73, 66, 77, 83, 101, 99, 69, 120, 0, 0, 1, 0, 0, 0, 0, 0, 2, 0, 8
+ ])),
+ Err(Error::InvalidSeHdr)
+ ));
+ }
+}

View File

@ -0,0 +1,51 @@
From 3f6572e901ddcc654021c4302cb2a99999acb87a Mon Sep 17 00:00:00 2001
From: Marc Hartmayer <mhartmay@linux.ibm.com>
Date: Wed, 18 Dec 2024 13:41:13 +0100
Subject: [PATCH] rust/utils: mkdtemp: fix memory leak
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Fix memory leak of @template_raw. The documentation of CString::into_raw
reads:
"Consumes the CString and transfers ownership of the string to a C
caller.
...
Failure to call CString::from_raw will lead to a memory leak." [1]
Let's fix the memory leak by always calling `CString::from_raw` and
therefore reclaim the ownership.
[1] https://doc.rust-lang.org/std/ffi/struct.CString.html#method.into_raw
Fixes: e56acf4f14b0 ("pv_core: add `TemporaryDirectory`")
Reviewed-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>
---
rust/utils/src/tmpfile.rs | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/rust/utils/src/tmpfile.rs b/rust/utils/src/tmpfile.rs
index 07acdba8..883d5586 100644
--- a/rust/utils/src/tmpfile.rs
+++ b/rust/utils/src/tmpfile.rs
@@ -16,13 +16,14 @@ fn mkdtemp<P: AsRef<Path>>(template: P) -> Result<PathBuf, std::io::Error> {
// SAFETY: template_raw is a valid CString because it was generated by
// the `CString::new`.
let ret = libc::mkdtemp(template_raw);
+ // SAFETY: `template_raw` is still a valid CString because it was
+ // generated by `CString::new` and modified by `libc::mkdtemp`.
+ let path_cstr = std::ffi::CString::from_raw(template_raw);
if ret.is_null() {
+ drop(path_cstr);
Err(std::io::Error::last_os_error())
} else {
- // SAFETY: `template_raw` is still a valid CString because it was
- // generated by `CString::new` and modified by `libc::mkdtemp`.
- let path_cstr = std::ffi::CString::from_raw(template_raw);
let path = OsStr::from_bytes(path_cstr.as_bytes());
let path = std::path::PathBuf::from(path);

View File

@ -0,0 +1,334 @@
From 944581eaefe4c6887790f2b8ed39c9ee76146c55 Mon Sep 17 00:00:00 2001
From: Marc Hartmayer <mhartmay@linux.ibm.com>
Date: Tue, 17 Dec 2024 11:58:01 +0100
Subject: [PATCH] rust/pvimg: Add upper estimates for the Secure Execution
header
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
A Secure Execution header V1 can be at maximum two pages large, optional
items are not supported, and the size of the encrypted part cannot be
larger than the total size of the Secure Execution header add this as
Deku assertions and additional conditions to the code. In addition, add
a check for the number of key slots.
Fixes: f4cf4ae6ebb1 ("rust: Add a new tool called 'pvimg'")
Reviewed-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>
---
rust/pvimg/src/pv_utils/error.rs | 3 +
rust/pvimg/src/pv_utils/se_hdr/brb.rs | 50 +++++++++++++---
rust/pvimg/src/pv_utils/se_hdr/builder.rs | 10 +++-
rust/pvimg/src/pv_utils/se_hdr/hdr_v1.rs | 71 ++++++++++++++++++++---
rust/pvimg/src/pv_utils/uvdata.rs | 18 ++++--
5 files changed, 130 insertions(+), 22 deletions(-)
diff --git a/rust/pvimg/src/pv_utils/error.rs b/rust/pvimg/src/pv_utils/error.rs
index 2a176276..a12c4a22 100644
--- a/rust/pvimg/src/pv_utils/error.rs
+++ b/rust/pvimg/src/pv_utils/error.rs
@@ -30,6 +30,9 @@ pub enum Error {
#[error("Invalid Secure Execution header")]
InvalidSeHdr,
+ #[error("Secure Execution header size {given} is larger than the maximum of {maximum} bytes")]
+ InvalidSeHdrTooLarge { given: usize, maximum: usize },
+
#[error("Invalid component metadata.")]
InvalidComponentMetadata,
diff --git a/rust/pvimg/src/pv_utils/se_hdr/brb.rs b/rust/pvimg/src/pv_utils/se_hdr/brb.rs
index ac3a2e6e..b8dadba1 100644
--- a/rust/pvimg/src/pv_utils/se_hdr/brb.rs
+++ b/rust/pvimg/src/pv_utils/se_hdr/brb.rs
@@ -171,8 +171,8 @@ impl AeadCipherTrait for SeHdr {
}
impl AeadDataTrait for SeHdr {
- fn aad(&self) -> Vec<u8> {
- [serialize_to_bytes(&self.common).unwrap(), self.data.aad()].concat()
+ fn aad(&self) -> Result<Vec<u8>> {
+ Ok([serialize_to_bytes(&self.common)?, self.data.aad()?].concat())
}
fn data(&self) -> Vec<u8> {
@@ -265,7 +265,7 @@ impl SeHdr {
data.resize(sehs, 0);
reader.read_exact(&mut data[common_size..])?;
- Self::try_from_data(&data)
+ Self::try_from_data(&data).map_err(|_| Error::InvalidSeHdr)
}
}
@@ -342,13 +342,13 @@ impl UvDataPlainTrait for SeHdrPlain {
}
impl AeadPlainDataTrait for SeHdrPlain {
- fn aad(&self) -> Vec<u8> {
- let data_aad = self.data.aad();
+ fn aad(&self) -> Result<Vec<u8>> {
+ let data_aad = self.data.aad()?;
- [serialize_to_bytes(&self.common).unwrap(), data_aad].concat()
+ Ok([serialize_to_bytes(&self.common)?, data_aad].concat())
}
- fn data(&self) -> Confidential<Vec<u8>> {
+ fn data(&self) -> Result<Confidential<Vec<u8>>> {
self.data.data()
}
@@ -387,5 +387,41 @@ mod tests {
])),
Err(Error::InvalidSeHdr)
));
+
+ // Invalid SeHdr as the `sehs` is too large.
+ assert!(matches!(
+ SeHdr::try_from_io(Cursor::new([
+ 73, 66, 77, 83, 101, 99, 69, 120, 0, 0, 1, 0, 0, 0, 1, 255, 65, 65, 65, 65, 67, 0,
+ 65, 17, 65, 0, 65, 65, 65, 65, 65, 65, 91, 91, 180, 91, 91, 91, 91, 91, 91, 91, 91,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 241, 241,
+ 241, 241, 241, 91, 91, 91, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112,
+ 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 80,
+ 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112,
+ 112, 112, 112, 112, 91, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112,
+ 112, 112, 112, 112, 112, 112, 112, 0, 0, 0, 0, 101, 99, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 65, 65, 65, 65, 67, 0, 65, 17, 65, 0, 65, 65, 65, 65,
+ 65, 65, 91, 91, 180, 91, 91, 91, 91, 91, 91, 91, 91, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 241, 241, 241, 241, 241, 91, 91, 91, 112,
+ 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112,
+ 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 80, 112, 112, 112, 112, 112, 112,
+ 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 91, 112, 112,
+ 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 73, 66, 77, 83, 101, 99, 69, 120,
+ 0, 112, 112, 0, 1, 0, 0, 0, 0, 101, 99, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 65, 65, 65, 65, 67, 0, 65, 17, 65, 0, 65, 65, 65, 65, 65, 65, 91,
+ 91, 180, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91, 91,
+ 91, 91, 112, 112, 112, 112, 112, 73, 66, 77, 83, 101, 99, 69, 120, 0, 0, 1, 0, 0,
+ 0, 0, 48, 53, 53, 53, 53, 53, 53, 53, 91, 91, 91, 241, 241, 46, 49, 49, 0, 49, 49,
+ 0, 0, 112, 112, 112, 91, 0, 0, 0, 0, 9, 0, 49, 50, 22, 241, 241, 241, 241, 241,
+ 241, 241, 241, 241, 241, 241, 91, 91, 91, 91, 91, 255, 251, 0, 0, 91, 91, 91, 91,
+ 91, 91, 91, 91, 91, 91, 91, 0, 0, 91, 0, 0, 10, 91, 91, 91, 65, 65, 65, 65
+ ])),
+ Err(Error::InvalidSeHdr)
+ ));
}
}
diff --git a/rust/pvimg/src/pv_utils/se_hdr/builder.rs b/rust/pvimg/src/pv_utils/se_hdr/builder.rs
index ba6de898..93bcc7af 100644
--- a/rust/pvimg/src/pv_utils/se_hdr/builder.rs
+++ b/rust/pvimg/src/pv_utils/se_hdr/builder.rs
@@ -230,8 +230,14 @@ mod tests {
let decrypted = bin.decrypt(&prot_key).expect("BUG");
assert_eq!(bin.common, decrypted.common);
- assert_eq!(bin.aad(), decrypted.aad());
- assert_ne!(&bin.data(), decrypted.data().value());
+ assert_eq!(
+ bin.aad().expect("should not fail"),
+ decrypted.aad().expect("should not fail")
+ );
+ assert_ne!(
+ &bin.data(),
+ decrypted.data().expect("should not fail").value()
+ );
let _decrypted_hdrv1: SeHdrDataV1 = decrypted.data.try_into().expect("BUG");
}
diff --git a/rust/pvimg/src/pv_utils/se_hdr/hdr_v1.rs b/rust/pvimg/src/pv_utils/se_hdr/hdr_v1.rs
index a7f2f609..b179d50d 100644
--- a/rust/pvimg/src/pv_utils/se_hdr/hdr_v1.rs
+++ b/rust/pvimg/src/pv_utils/se_hdr/hdr_v1.rs
@@ -19,6 +19,7 @@ use serde::{Serialize, Serializer};
use super::keys::phkh_v1;
use crate::{
error::Error,
+ misc::PAGESIZE,
pv_utils::{
error::Result,
se_hdr::{
@@ -51,11 +52,14 @@ struct HdrSizesV1 {
#[derive(Debug, Clone, PartialEq, Eq, DekuRead, DekuWrite, Serialize)]
#[deku(endian = "endian", ctx = "endian: Endian", ctx_default = "Endian::Big")]
struct SeHdrAadV1 {
+ #[deku(assert = "*sehs <= SeHdrDataV1::MAX_SIZE.try_into().unwrap()")]
sehs: u32,
#[serde(serialize_with = "ser_hex")]
iv: [u8; SymKeyType::AES_256_GCM_IV_LEN],
res1: u32,
+ #[deku(assert = "*nks <= (*sehs).into()", update = "self.keyslots.len()")]
nks: u64,
+ #[deku(assert = "*sea <= (*sehs).into()")]
sea: u64,
nep: u64,
#[serde(serialize_with = "ser_lower_hex")]
@@ -118,6 +122,7 @@ pub struct SeHdrConfV1 {
psw: PSW,
#[serde(serialize_with = "ser_lower_hex")]
scf: u64,
+ #[deku(assert_eq = "0")]
noi: u32,
res2: u32,
#[deku(count = "noi")]
@@ -200,6 +205,7 @@ where
}
impl SeHdrDataV1 {
+ const MAX_SIZE: usize = 2 * PAGESIZE;
const PCF_DEFAULT: u64 = 0x0;
const SCF_DEFAULT: u64 = 0x0;
@@ -241,7 +247,14 @@ impl SeHdrDataV1 {
tag: SeHdrTagV1::default(),
};
let hdr_size = ret.size()?;
- ret.aad.sehs = hdr_size.phs.try_into()?;
+ let phs = hdr_size.phs.try_into()?;
+ if phs > Self::MAX_SIZE {
+ return Err(Error::InvalidSeHdrTooLarge {
+ given: phs,
+ maximum: Self::MAX_SIZE,
+ });
+ }
+ ret.aad.sehs = phs.try_into()?;
ret.aad.sea = hdr_size.sea;
Ok(ret)
}
@@ -494,8 +507,8 @@ impl KeyExchangeTrait for SeHdrBinV1 {
}
impl AeadDataTrait for SeHdrBinV1 {
- fn aad(&self) -> Vec<u8> {
- serialize_to_bytes(&self.aad).unwrap()
+ fn aad(&self) -> Result<Vec<u8>> {
+ serialize_to_bytes(&self.aad)
}
fn data(&self) -> Vec<u8> {
@@ -508,12 +521,12 @@ impl AeadDataTrait for SeHdrBinV1 {
}
impl AeadPlainDataTrait for SeHdrDataV1 {
- fn aad(&self) -> Vec<u8> {
- serialize_to_bytes(&self.aad).unwrap()
+ fn aad(&self) -> Result<Vec<u8>> {
+ serialize_to_bytes(&self.aad)
}
- fn data(&self) -> Confidential<Vec<u8>> {
- serialize_to_bytes(self.data.value()).unwrap().into()
+ fn data(&self) -> Result<Confidential<Vec<u8>>> {
+ Ok(serialize_to_bytes(self.data.value())?.into())
}
fn tag(&self) -> Vec<u8> {
@@ -610,4 +623,48 @@ mod tests {
assert_eq!(psw, hdr_data_v1.data.value().psw);
assert_eq!(cck.value(), hdr_data_v1.data.value().cck.value());
}
+
+ #[test]
+ fn max_size_sehdr_test() {
+ const MAX_HOST_KEYS: usize = 95;
+
+ let (_, host_key) = get_test_key_and_cert();
+ let pub_key = host_key.public_key().unwrap();
+ let host_keys_max: Vec<_> = (0..MAX_HOST_KEYS).map(|_| pub_key.clone()).collect();
+ let too_many_host_keys: Vec<_> = (0..MAX_HOST_KEYS + 1).map(|_| pub_key.clone()).collect();
+ let xts_key = Confidential::new([0x3; SymKeyType::AES_256_XTS_KEY_LEN]);
+ let meta = ComponentMetadataV1 {
+ ald: [0x1; SHA_512_HASH_LEN],
+ pld: [0x2; SHA_512_HASH_LEN],
+ tld: [0x3; SHA_512_HASH_LEN],
+ nep: 3,
+ key: xts_key,
+ };
+ let psw = PSW {
+ addr: 1234,
+ mask: 5678,
+ };
+
+ let mut builder = SeHdrBuilder::new(SeHdrVersion::V1, psw.clone(), meta.clone())
+ .expect("should not fail");
+ builder
+ .add_hostkeys(&host_keys_max)
+ .expect("should not fail")
+ .with_components(meta.clone())
+ .expect("should not fail");
+ let bin = builder.build().expect("should not fail");
+ assert_eq!(bin.common.version, SeHdrVersion::V1);
+ let hdr_v1: SeHdrBinV1 = bin.data.try_into().expect("should not fail");
+ assert_eq!(hdr_v1.aad.sehs, 8160);
+
+ let mut builder = SeHdrBuilder::new(SeHdrVersion::V1, psw.clone(), meta.clone())
+ .expect("should not fail");
+
+ builder
+ .add_hostkeys(&too_many_host_keys)
+ .expect("should not fail")
+ .with_components(meta)
+ .expect("should not fail");
+ assert!(matches!(builder.build(), Err(Error::InvalidSeHdr)));
+ }
}
diff --git a/rust/pvimg/src/pv_utils/uvdata.rs b/rust/pvimg/src/pv_utils/uvdata.rs
index b0ec355a..c6ed9567 100644
--- a/rust/pvimg/src/pv_utils/uvdata.rs
+++ b/rust/pvimg/src/pv_utils/uvdata.rs
@@ -34,7 +34,7 @@ pub trait AeadCipherTrait {
#[enum_dispatch]
pub trait AeadDataTrait {
/// Returns the authenticated associated data.
- fn aad(&self) -> Vec<u8>;
+ fn aad(&self) -> Result<Vec<u8>>;
/// Returns the encrypted data.
fn data(&self) -> Vec<u8>;
@@ -47,10 +47,10 @@ pub trait AeadDataTrait {
#[enum_dispatch]
pub trait AeadPlainDataTrait {
/// Returns the authenticated associated data.
- fn aad(&self) -> Vec<u8>;
+ fn aad(&self) -> Result<Vec<u8>>;
/// Returns the unencrypted data.
- fn data(&self) -> Confidential<Vec<u8>>;
+ fn data(&self) -> Result<Confidential<Vec<u8>>>;
/// Returns the tag data.
fn tag(&self) -> Vec<u8>;
@@ -124,8 +124,14 @@ pub trait UvDataPlainTrait:
expected: self.aead_key_type().to_string(),
});
}
- let aad = self.aad();
- let unecrypted_data = self.data();
+ let aad = self.aad().map_err(|err| match err {
+ Error::Deku(_) => Error::InvalidSeHdr,
+ err => err,
+ })?;
+ let unecrypted_data = self.data().map_err(|err| match err {
+ Error::Deku(_) => Error::InvalidSeHdr,
+ err => err,
+ })?;
let iv = self.iv();
let result = encrypt_aead(key, iv, &aad, unecrypted_data.value())?;
Self::C::try_from_data(&result.into_buf())
@@ -169,7 +175,7 @@ pub trait UvDataTrait: AeadDataTrait + AeadCipherTrait + KeyExchangeTrait + Clon
}
let tag_size = self.aead_tag_size();
- let aad = self.aad();
+ let aad = self.aad()?;
let unecrypted_data = self.data();
let iv = self.iv();
let tag = self.tag();

View File

@ -0,0 +1,101 @@
From 6e48c5ebaa26c6bd2a1bc33ccf36ed8bd6946358 Mon Sep 17 00:00:00 2001
From: Marc Hartmayer <mhartmay@linux.ibm.com>
Date: Tue, 17 Dec 2024 18:13:31 +0100
Subject: [PATCH] pvimg: info: Rename '--key' into '--hdr-key' and use '--key'
as an alias
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Rename '--key' into '--hdr-key' and use '--key' as an (non-visible)
alias for '--hdr-key' in order to keep the command line backwards
compatible. The chances of someone using '--key' are very low, as this
version has not yet been released by any OS distribution.
This change makes the command line options for the different subcommands
more consistent and therefore easier to use.
Suggested-by: Reinhard Bündgen <buendgen@de.ibm.com>
Acked-by: Hendrik Brueckner <brueckner@linux.ibm.com>
Reviewed-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>
---
rust/pvimg/man/pvimg-info.1 | 2 +-
rust/pvimg/src/cli.rs | 22 +++++++++++++++++++---
rust/pvimg/src/cmd/info.rs | 2 +-
3 files changed, 21 insertions(+), 5 deletions(-)
Index: s390-tools-2.36.0/rust/pvimg/man/pvimg-info.1
===================================================================
--- s390-tools-2.36.0.orig/rust/pvimg/man/pvimg-info.1
+++ s390-tools-2.36.0/rust/pvimg/man/pvimg-info.1
@@ -37,7 +37,7 @@ Possible values:
.RE
.RE
.PP
-\-\-key <FILE>
+\-\-hdr\-key <FILE>
.RS 4
Use the key in FILE to decrypt the Secure Execution header.
.RE
Index: s390-tools-2.36.0/rust/pvimg/src/cli.rs
===================================================================
--- s390-tools-2.36.0.orig/rust/pvimg/src/cli.rs
+++ s390-tools-2.36.0/rust/pvimg/src/cli.rs
@@ -192,8 +192,8 @@ pub struct InfoArgs {
pub format: OutputFormat,
/// Use the key in FILE to decrypt the Secure Execution header.
- #[arg(long, value_name = "FILE", value_hint = ValueHint::FilePath,)]
- pub key: Option<PathBuf>,
+ #[arg(long, value_name = "FILE", value_hint = ValueHint::FilePath, alias = "key")]
+ pub hdr_key: Option<PathBuf>,
}
#[derive(Args, Debug)]
@@ -710,6 +710,22 @@ mod test {
CliOption::new("image", ["/dev/null"]),
],
)),
+ flat_map_collect(insert(
+ args.clone(),
+ vec![
+ CliOption::new("hdr-key", ["--hdr-key", "/dev/null"]),
+ CliOption::new("format", ["--format=json"]),
+ CliOption::new("image", ["/dev/null"]),
+ ],
+ )),
+ flat_map_collect(insert(
+ args.clone(),
+ vec![
+ CliOption::new("hdr-key", ["--key", "/dev/null"]),
+ CliOption::new("format", ["--format=json"]),
+ CliOption::new("image", ["/dev/null"]),
+ ],
+ )),
// separation between keyword and positional args works
flat_map_collect(insert(
args.clone(),
@@ -750,7 +766,7 @@ mod test {
// Test for invalid combinations
// Input is missing
- let mut pvimg_invalid_args = vec![vec!["pvimg", "test"]];
+ let mut pvimg_invalid_args = vec![vec!["pvimg", "info"]];
for create_args in &valid_test_args {
pvimg_valid_args.push(
Index: s390-tools-2.36.0/rust/pvimg/src/cmd/info.rs
===================================================================
--- s390-tools-2.36.0.orig/rust/pvimg/src/cmd/info.rs
+++ s390-tools-2.36.0/rust/pvimg/src/cmd/info.rs
@@ -27,7 +27,7 @@ pub fn info(opt: &InfoArgs) -> Result<Ow
SeHdr::seek_sehdr(&mut input, None)?;
let hdr = SeHdr::try_from_io(input)?;
- if let Some(key_path) = &opt.key {
+ 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)?)?;

View File

@ -1,3 +1,43 @@
-------------------------------------------------------------------
Thu Jan 9 07:05:53 UTC 2025 - Nikolay Gueorguiev <nikolay.gueorguiev@suse.com>
- Applied backport patches from s390-tools 2.37 to 2.36 ( jsc#PED-11870 )
( jsc#IBM-1447, jsc#IBM-1062 )
* s390-tools-General-update-01.patch
* s390-tools-General-update-02.patch
* s390-tools-General-update-03.patch
* s390-tools-General-update-04.patch
* s390-tools-General-update-05.patch
* s390-tools-General-update-06.patch
* s390-tools-General-update-07.patch
* s390-tools-General-update-08.patch
* s390-tools-General-update-09.patch
* s390-tools-General-update-10.patch
* s390-tools-General-update-11.patch
* s390-tools-General-update-12.patch
* s390-tools-Additional-update-01.patch
* s390-tools-Additional-update-02.patch
( jsc#IBM-1570, jsc#IBM-1571 )
* s390-tools-Support-unencrypted-SE-images-01.patch
( jsc#IBM-1572, jsc#IBM-1573 )
* s390-tools-pvimg-info-command-01.patch
* s390-tools-pvimg-info-command-02.patch
* s390-tools-pvimg-info-command-03.patch
* s390-tools-pvimg-info-command-04.patch
( jsc#IBM-1576, jsc#IBM-1577 )
* s390-tools-pvimg-additional-01.patch
- Renamed patches from - to
* s390-tools-01-opticsmon-Fix-runaway-loop-in-on_link_change.patch
to
s390-tools-Additional-update-01.patch
* s390-tools-02-libzpci-opticsmon-Refactor-on_link_change-using-new.patch
to
s390-tools-Additional-update-02.patch
* s390-tools-03-rust-pvimg-Add-enable-disable-image-encryption-flags-to-pvimg-create.patch
to
s390-tools-Support-unencrypted-SE-images-01.patch
- Revendored vendor.tar.gz
-------------------------------------------------------------------
Tue Jan 7 08:59:16 UTC 2025 - Nikolay Gueorguiev <nikolay.gueorguiev@suse.com>

View File

@ -153,14 +153,35 @@ Patch910: s390-tools-sles15sp1-11-zdev-Do-not-call-zipl-on-initrd-update.p
Patch911: s390-tools-sles15sp5-remove-no-pie-link-arguments.patch
Patch912: s390-tools-ALP-zdev-live.patch
Patch913: s390-tools-sles15sp6-kdump-initrd-59-zfcp-compat-rules.patch
###
Patch914: s390-tools-01-zipl_helper.device-mapper-add-missed-step-in-logical.patch
Patch915: s390-tools-02-zipl-src-fix-imprecise-check-that-file-is-on-specifi.patch
###
Patch916: s390-tools-01-opticsmon-Fix-runaway-loop-in-on_link_change.patch
Patch917: s390-tools-02-libzpci-opticsmon-Refactor-on_link_change-using-new.patch
Patch918: s390-tools-03-rust-pvimg-Add-enable-disable-image-encryption-flags-to-pvimg-create.patch
Patch920: s390-tools-General-update-01.patch
Patch921: s390-tools-General-update-02.patch
Patch922: s390-tools-General-update-03.patch
Patch923: s390-tools-General-update-04.patch
Patch924: s390-tools-General-update-05.patch
Patch925: s390-tools-General-update-06.patch
Patch926: s390-tools-General-update-07.patch
Patch927: s390-tools-General-update-08.patch
Patch928: s390-tools-General-update-09.patch
Patch929: s390-tools-General-update-10.patch
Patch930: s390-tools-General-update-11.patch
Patch931: s390-tools-General-update-12.patch
###
Patch920: s390-tools-slfo-01-parse-ipl-device-for-activation.patch
Patch935: s390-tools-Additional-update-01.patch
Patch936: s390-tools-Additional-update-02.patch
###
Patch950: s390-tools-pvimg-info-command-01.patch
Patch951: s390-tools-pvimg-info-command-02.patch
Patch952: s390-tools-pvimg-info-command-03.patch
###
Patch960: s390-tools-Support-unencrypted-SE-images-01.patch
Patch961: s390-tools-pvimg-info-command-04.patch
Patch962: s390-tools-pvimg-additional-01.patch
###
Patch990: s390-tools-slfo-01-parse-ipl-device-for-activation.patch
###
BuildRequires: curl-devel
@ -809,7 +830,7 @@ done
%prep
%autosetup -p1
install -D -m 0644 %{SOURCE200} .cargo/config
install -D -m 0644 %{SOURCE200} .cargo/config.toml
tar -xzf %{SOURCE201}
%build

BIN
vendor.tar.gz (Stored with Git LFS)

Binary file not shown.