diff --git a/s390-tools-01-opticsmon-Fix-runaway-loop-in-on_link_change.patch b/s390-tools-Additional-update-01.patch similarity index 100% rename from s390-tools-01-opticsmon-Fix-runaway-loop-in-on_link_change.patch rename to s390-tools-Additional-update-01.patch diff --git a/s390-tools-02-libzpci-opticsmon-Refactor-on_link_change-using-new.patch b/s390-tools-Additional-update-02.patch similarity index 100% rename from s390-tools-02-libzpci-opticsmon-Refactor-on_link_change-using-new.patch rename to s390-tools-Additional-update-02.patch diff --git a/s390-tools-General-update-01.patch b/s390-tools-General-update-01.patch new file mode 100644 index 0000000..fadf386 --- /dev/null +++ b/s390-tools-General-update-01.patch @@ -0,0 +1,147 @@ +From 1e44ace41de3cbd744b22a8f9835473b091186e0 Mon Sep 17 00:00:00 2001 +From: Steffen Eiden +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 +Reviewed-by: Christoph Schlameuss +Signed-off-by: Steffen Eiden +Signed-off-by: Jan Höppner +--- + 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>( ++ 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>( + 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(()) + } diff --git a/s390-tools-General-update-02.patch b/s390-tools-General-update-02.patch new file mode 100644 index 0000000..a1a1ac1 --- /dev/null +++ b/s390-tools-General-update-02.patch @@ -0,0 +1,467 @@ +From d1636168b26cc842bc0766235c8a4f2da9663f20 Mon Sep 17 00:00:00 2001 +From: Steffen Eiden +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 +Acked-by: Christoph Schlameuss +Signed-off-by: Steffen Eiden +Signed-off-by: Jan Höppner +--- + 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 { ++ 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 { ++ 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, 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, 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, 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 { ++ 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, ++ data: &'d [u8], ++} ++ ++impl<'d> InnerPem<'d> { ++ fn new(name: &str, header: Option, data: &'d [u8]) -> Result { ++ 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> { ++ 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 ----- ++///
++/// ++/// ++///-----END ----- ++ ++#[derive(Debug)] ++pub struct Pem { ++ pem: Confidential, ++} ++ ++#[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(name: &str, header: H, data: D) -> Result ++ where ++ D: AsRef<[u8]>, ++ H: Into>, ++ { ++ 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> { ++ 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); ++ } ++} diff --git a/s390-tools-General-update-03.patch b/s390-tools-General-update-03.patch new file mode 100644 index 0000000..70364cc --- /dev/null +++ b/s390-tools-General-update-03.patch @@ -0,0 +1,57 @@ +From 69eb06f39e5134565babfe96c66a3786c0a571cf Mon Sep 17 00:00:00 2001 +From: Steffen Eiden +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 +Reviewed-by: Christoph Schlameuss +Signed-off-by: Steffen Eiden +Signed-off-by: Jan Höppner +--- + 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 diff --git a/s390-tools-General-update-04.patch b/s390-tools-General-update-04.patch new file mode 100644 index 0000000..88e1007 --- /dev/null +++ b/s390-tools-General-update-04.patch @@ -0,0 +1,204 @@ +From 01cd81ecf5d1a7e1e504ae1b67692cf63cd4b51d Mon Sep 17 00:00:00 2001 +From: Steffen Eiden +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 +Acked-by: Marc Hartmayer +Signed-off-by: Steffen Eiden +Signed-off-by: Jan Höppner +--- + 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 = std::result::Result; + +@@ -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>, ++} ++ ++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 { ++ 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> { ++ 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 for RetrieveCmd { ++ type Error = Error; ++ ++ fn try_from(entry: SecretEntry) -> Result { ++ 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 { ++ &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 { diff --git a/s390-tools-General-update-05.patch b/s390-tools-General-update-05.patch new file mode 100644 index 0000000..4750656 --- /dev/null +++ b/s390-tools-General-update-05.patch @@ -0,0 +1,710 @@ +From 4af137f4fad8638169ccf0ddcb6dc4b0fe8fb1c1 Mon Sep 17 00:00:00 2001 +From: Steffen Eiden +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 +Reviewed-by: Christoph Schlameuss +Signed-off-by: Steffen Eiden +Signed-off-by: Jan Höppner +--- + 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 { ++ 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 { ++ 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 { ++ 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) -> Vec { ++ 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: (String name) ++impl Serialize for RetrievableSecret { ++ fn serialize(&self, serializer: S) -> Result ++ 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(de: D) -> Result ++ 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: ` (String name)` number in [3,10]|[17,21]", ++ ) ++ } ++ fn visit_str(self, s: &str) -> Result ++ 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(v: &U16, ser: S) -> Result + 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> for ListableSecretType { +- fn from(value: U16) -> Self { +- match value.get() { ++impl From> for ListableSecretType { ++ fn from(value: U16) -> Self { ++ value.get().into() ++ } ++} ++ ++impl From 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 for U16 { ++impl From for U16 { ++ fn from(value: ListableSecretType) -> Self { ++ Self::new(value.into()) ++ } ++} ++ ++impl From 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, ++ ], ++ ) ++ } + } diff --git a/s390-tools-General-update-06.patch b/s390-tools-General-update-06.patch new file mode 100644 index 0000000..a6a9708 --- /dev/null +++ b/s390-tools-General-update-06.patch @@ -0,0 +1,877 @@ +From fd024387d710887bd2016658c44d4762a08c791c Mon Sep 17 00:00:00 2001 +From: Steffen Eiden +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 +Reviewed-by: Christoph Schlameuss +Signed-off-by: Steffen Eiden +Signed-off-by: Jan Höppner +--- + 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 { +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>, ++ }, ++} ++ ++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 { ++ 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 { ++ 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>, + { +- 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>, 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>, 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>, 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>, 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, 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 { + 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>); ++ ++fn extend_to_multiple(mut key: Vec, multiple: usize) -> Confidential> { ++ 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 | payload (0-8190) bytes ++/// ``` ++fn plaintext(inp: Confidential>) -> Result { ++ 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 = (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>) -> Result { ++ 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>) -> Result { ++ 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>) -> Result { ++ 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) -> Result { ++ 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 = 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 { +- 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: ++/// ++/// ++///-----END IBM PROTECTED KEY----- ++/// ``` ++#[derive(Debug, PartialEq, Eq)] ++pub struct IbmProtectedKey { ++ kind: ListableSecretType, ++ key: Confidential>, ++} ++ ++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> { ++ 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::new( ++ "IBM PROTECTED KEY", ++ format!("kind: {}", self.kind), ++ self.key.value(), ++ ) ++ } ++ ++ fn new(kind: ListableSecretType, key: K) -> Self ++ where ++ K: Into>>, ++ { ++ Self { ++ kind, ++ key: key.into(), ++ } ++ } ++} ++ ++impl From 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::::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>), ++ /// 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> { ++ 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 { ++ 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); ++ } ++} diff --git a/s390-tools-General-update-07.patch b/s390-tools-General-update-07.patch new file mode 100644 index 0000000..d468f05 --- /dev/null +++ b/s390-tools-General-update-07.patch @@ -0,0 +1,95 @@ +From a14f9d4edcc5db0d54e4fbe3ec3d98c7c270bf8e Mon Sep 17 00:00:00 2001 +From: Steffen Eiden +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 +Reviewed-by: Christoph Schlameuss +Signed-off-by: Steffen Eiden +Signed-off-by: Jan Höppner +--- + 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, + +- /// 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, + }, + } +@@ -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), + +- /// 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). diff --git a/s390-tools-General-update-08.patch b/s390-tools-General-update-08.patch new file mode 100644 index 0000000..6fdd2d3 --- /dev/null +++ b/s390-tools-General-update-08.patch @@ -0,0 +1,423 @@ +From 93da795520ca2f0a73cfbfc951a9b16437a1b95b Mon Sep 17 00:00:00 2001 +From: Steffen Eiden +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 +Reviewed-by: Christoph Schlameuss +Signed-off-by: Steffen Eiden +Signed-off-by: Jan Höppner +--- + 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, + }, ++ ++ /// 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(path: &P, data: D, ctx: &str) -> pv::Result<()> + where + P: AsRef, +@@ -32,6 +33,23 @@ where + Ok(()) + } + ++fn retrievable(name: &str, secret: &str, kind: &RetrieveableSecretInpKind) -> Result { ++ 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 { + 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 { + .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>( + 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 { + 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 { ++ 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 { diff --git a/s390-tools-General-update-09.patch b/s390-tools-General-update-09.patch new file mode 100644 index 0000000..ee9844f --- /dev/null +++ b/s390-tools-General-update-09.patch @@ -0,0 +1,313 @@ +From 256289a30aa5d3f6a4d2631dea69d1dc47205150 Mon Sep 17 00:00:00 2001 +From: Steffen Eiden +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 +Reviewed-by: Christoph Schlameuss +Signed-off-by: Steffen Eiden +Signed-off-by: Jan Höppner +--- + 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, ++ #[serde(serialize_with = "ser_u16")] ++ total_num_secrets: U16, ++ #[serde(skip)] ++ next_secret_idx: U16, ++ #[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, + } + +@@ -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) -> 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) -> 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(&self, w: &mut T) -> Result<()> { +- let num_s = to_u16(self.secrets.len()).ok_or(Error::ManySecrets)?; +- w.write_u16::(num_s)?; +- w.write_u16::( +- 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: &mut R) -> std::io::Result { +- let num_s = r.read_u16::()?; +- let total_num_secrets = r.read_u16::()? as usize; +- let mut v: Vec = Vec::with_capacity(num_s as usize); +- r.seek(std::io::SeekFrom::Current(12))?; // skip reserved bytes ++ let mut buf = [0u8; size_of::()]; ++ 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 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, + ], + ) + } diff --git a/s390-tools-General-update-10.patch b/s390-tools-General-update-10.patch new file mode 100644 index 0000000..01b0dbc --- /dev/null +++ b/s390-tools-General-update-10.patch @@ -0,0 +1,111 @@ +From 93216d916c479ee1292aa1d598ac9c0e7f585bd8 Mon Sep 17 00:00:00 2001 +From: Steffen Eiden +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 +Reviewed-by: Christoph Schlameuss +Signed-off-by: Steffen Eiden +Signed-off-by: Jan Höppner +--- + 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 { +- 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) + } + diff --git a/s390-tools-General-update-11.patch b/s390-tools-General-update-11.patch new file mode 100644 index 0000000..8bb5da9 --- /dev/null +++ b/s390-tools-General-update-11.patch @@ -0,0 +1,387 @@ +From ff04f76257791593c8f92374f295a0c478e3b0f7 Mon Sep 17 00:00:00 2001 +From: Steffen Eiden +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 +Reviewed-by: Christoph Schlameuss +Signed-off-by: Steffen Eiden +Signed-off-by: Jan Höppner +--- + 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 { ++ /// Hashes the name with sha256 ++ pub fn name_to_id(name: &str) -> Result { + 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, 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, +@@ -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 { ++ 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(&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(self, s: &str) -> Result +@@ -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, ++ ++ /// 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> { + fn build_asrcb(opt: &CreateSecretOpt) -> Result { + 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 { + }; + 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 { + 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 = diff --git a/s390-tools-General-update-12.patch b/s390-tools-General-update-12.patch new file mode 100644 index 0000000..6e679a9 --- /dev/null +++ b/s390-tools-General-update-12.patch @@ -0,0 +1,1207 @@ +From a8a3e7d49cb0d3a069dacbe54c91a31b76876846 Mon Sep 17 00:00:00 2001 +From: Steffen Eiden +Date: Tue, 22 Oct 2024 17:53:17 +0200 +Subject: [PATCH] rust/pvsecret: Update manuals and README +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Acked-by: Marc Hartmayer +Reviewed-by: Christoph Schlameuss +Signed-off-by: Steffen Eiden +Signed-off-by: Jan Höppner +--- + rust/pvsecret/README.md | 222 ++++++++++++++---- + rust/pvsecret/man/pvsecret-add.1 | 9 +- + .../man/pvsecret-create-association.1 | 26 +- + rust/pvsecret/man/pvsecret-create-meta.1 | 7 +- + .../man/pvsecret-create-retrievable.1 | 74 ++++++ + rust/pvsecret/man/pvsecret-create.1 | 111 +++++---- + rust/pvsecret/man/pvsecret-list.1 | 17 +- + rust/pvsecret/man/pvsecret-lock.1 | 7 +- + rust/pvsecret/man/pvsecret-retrieve.1 | 77 ++++++ + rust/pvsecret/man/pvsecret-verify.1 | 17 +- + rust/pvsecret/man/pvsecret.1 | 34 ++- + 11 files changed, 449 insertions(+), 152 deletions(-) + create mode 100644 rust/pvsecret/man/pvsecret-create-retrievable.1 + create mode 100644 rust/pvsecret/man/pvsecret-retrieve.1 + +diff --git a/rust/pvsecret/README.md b/rust/pvsecret/README.md +index b31d3deb..711f81d7 100644 +--- a/rust/pvsecret/README.md ++++ b/rust/pvsecret/README.md +@@ -32,7 +32,7 @@ Create a new add-secret request + + - **add** +
    +-Perform an add-secret request (s390x only) ++Submit an add-secret request to the Ultravisor (s390x only) +
+ + - **lock** +@@ -50,23 +50,34 @@ List all ultravisor secrets (s390x only) + Verify that an add-secret request is sane + + ++- **retrieve** ++
    ++Retrieve a secret from the UV secret store (s390x only) ++
++ + ## Options + + `-v`, `--verbose` +
    +-Provide more detailed output ++Provide more detailed output. ++
++ ++ ++`-q`, `--quiet` ++
    ++Provide less output. +
+ + + `--version` +
    +-Print version information and exit ++Print version information and exit. +
+ + + `-h`, `--help` +
    +-Print help ++Print help (see a summary with '-h'). +
+ + +@@ -95,12 +106,17 @@ Create a meta secret + Create an association secret + + ++- **retrievable** ++
    ++Create a retrievable secret ++
++ + ### Options + + `-k`, `--host-key-document ` +
    + Use FILE as a host-key document. Can be specified multiple times and must be +-used at least once. ++specified at least once. +
+ + +@@ -114,7 +130,7 @@ the host-key document beforehand. + + `-C`, `--cert ` +
    +-Use FILE as a certificate to verify the host key or keys. The certificates are ++Use FILE as a certificate to verify the host-key or keys. The certificates are + used to establish a chain of trust for the verification of the host-key + documents. Specify this option twice to specify the IBM Z signing key and the + intermediate CA certificate (signed by the root CA). +@@ -123,15 +139,15 @@ intermediate CA certificate (signed by the root CA). + + `--crl ` +
      +-Use FILE as a certificate revocation list. The list is used to check whether a +-certificate of the chain of trust is revoked. Specify this option multiple times +-to use multiple CRLs. ++Use FILE as a certificate revocation list (CRL). The list is used to check ++whether a certificate of the chain of trust is revoked. Specify this option ++multiple times to use multiple CRLs. +
    + + + `--offline` +
      +-Make no attempt to download CRLs ++Make no attempt to download CRLs. +
    + + +@@ -146,8 +162,7 @@ specified certificate. + `--hdr ` +
      + 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. ++created by 'pvimg/genprotimg' or an extracted IBM Secure Execution header. +
    + + +@@ -162,7 +177,7 @@ behavior. + + `-o`, `--output ` +
      +-Write the generated request to FILE ++Write the generated request to FILE. +
    + + +@@ -209,15 +224,15 @@ the request. + + `--flags ` +
      +-Flags for the add-secret request ++Flags for the add-secret request. + Possible values: +- - **disable-dump**: Disables host-initiated dumping for the target guest instance ++ - **disable-dump**: Disables host-initiated dumping for the target guest instance. +
    + + + `--user-data ` +
      +-Use the content of FILE as user-data. Passes user data defined in through ++Use the content of FILE as user-data. Passes user data defined in FILE through + the add-secret request to the ultravisor. The user data can be up to 512 bytes + of arbitrary data, and the maximum size depends on the size of the user-signing + key: +@@ -236,19 +251,25 @@ Optional. No user-data by default. + `--user-sign-key ` +
        + Use the content of FILE as user signing key. Adds a signature calculated from +-the key in to the add-secret request. The file must be in DER or PEM +-format containing a private key. Supported are RSA 2048 & 3072-bit and +-EC(secp521r1) keys. The firmware ignores the content, but the request tag +-protects the signature. The user-signing key signs the request. The location of +-the signature is filled with zeros during the signature calculation. The request +-tag also secures the signature. See man pvsecret verify for more details. +-Optional. No signature by default. ++the key in FILE to the add-secret request. The file must be in DER or PEM format ++containing a private key. Supported are RSA 2048 & 3072-bit and EC(secp521r1) ++keys. The firmware ignores the content, but the request tag protects the ++signature. The user-signing key signs the request. The location of the signature ++is filled with zeros during the signature calculation. The request tag also ++secures the signature. See man pvsecret verify for more details. Optional. No ++signature by default. ++
      ++ ++ ++`--use-name` ++
        ++Do not hash the name, use it directly as secret ID. Ignored for meta-secrets. +
      + + + `-h`, `--help` +
        +-Print help ++Print help (see a summary with '-h'). +
      + + +@@ -265,14 +286,15 @@ of secrets. + `pvsecret create association [OPTIONS] ` + #### Description + Create an association secret. Use an association secret to connect a trusted I/O +-device to a guest. The `pvapconfig` tool provides more information about ++device to a guest. The 'pvapconfig' tool provides more information about + association secrets. + #### Arguments + + `` +
        +-String to identify the new secret. The actual secret is set with --input-secret. +-The name is saved in `NAME.yaml` with white-spaces mapped to `_`. ++String that identifies the new secret. The actual secret is set with ++'--input-secret'. The name is saved in `NAME.yaml` with white-spaces mapped to ++`_`. +
      + + +@@ -284,24 +306,76 @@ Print the hashed name to stdout. The hashed name is not written to `NAME.yaml` +
    + + +-`--input-secret ` ++`--input-secret ` +
      + Path from which to read the plaintext secret. Uses a random secret if not +-specified ++specified. ++
    ++ ++ ++`--output-secret ` ++
      ++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 anymore. ++
    ++ ++ ++`-h`, `--help` ++
      ++Print help (see a summary with '-h'). ++
    ++ ++ ++### pvsecret create retrievable ++#### Synopsis ++`pvsecret create retrievable [OPTIONS] --secret --type ` ++`pvsecret create retr [OPTIONS] --secret --type ` ++#### Description ++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. ++#### Arguments ++ ++`` ++
      ++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 `_`. ++
    ++ ++ ++#### Options ++ ++`--stdout` ++
      ++Print the hashed name to stdout. The hashed name is not written to `NAME.yaml` ++
    ++ ++ ++`--secret ` ++
      ++Use SECRET-FILE as retrievable secret. +
    + + +-`--output-secret ` ++`--type ` +
      +-Save the generated secret as plaintext in 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 anymore. ++Specify the secret type. Limitations to the input data apply depending on the ++secret type. ++ Possible values: ++ - **plain**: A plaintext secret. Can be any file up to 8190 bytes long. ++ - **aes**: An AES key. Must be a plain byte file 128, 192, or 256 bit long. ++ - **aes-xts**: An AES-XTS key. Must be a plain byte file 512, or 1024 bit long. ++ - **hmac-sha**: A HMAC-SHA key. Must be a plain byte file 512, or 1024 bit long. ++ - **ec**: An elliptic curve private key. Must be a PEM or DER file. +
    + + + `-h`, `--help` +
      +-Print help ++Print help (see a summary with '-h'). +
    + + +@@ -309,13 +383,14 @@ Print help + ### Synopsis + `pvsecret add ` + ### Description +-Perform an add-secret request (s390x only). Perform an add-secret request using +-a previously generated add-secret request. Only available on s390x. ++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. + ### Arguments + + `` +
      +-Specify the request to be sent ++Specify the request to be sent. +
    + + +@@ -325,8 +400,8 @@ Specify the request to be sent + `pvsecret lock` + ### Description + 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. ++this command executed successfully, all subsequent add-secret requests will ++fail. Only available on s390x. + + ## pvsecret list + ### Synopsis +@@ -339,7 +414,7 @@ Execution guest. Only available on s390x. + + `` +
      +-Store the result in FILE ++Store the result in FILE. + Default value: '-' +
    + +@@ -348,18 +423,18 @@ Store the result in FILE + + `--format ` +
      +-Define the output format of the list ++Define the output format of the list. + Default value: 'human' + Possible values: +- - **human**: Human-focused, non-parsable output format +- - **yaml**: Use yaml format +- - **bin**: Use the format the ultravisor uses to pass the list ++ - **human**: Human-focused, non-parsable output format. ++ - **yaml**: Use yaml format. ++ - **bin**: Use the format the ultravisor uses to pass the list. +
    + + + `-h`, `--help` +
      +-Print help ++Print help (see a summary with '-h'). +
    + + +@@ -407,7 +482,7 @@ The verification process works as follows: + + `` +
      +-Specify the request to be checked ++Specify the request to be checked. +
    + + +@@ -435,5 +510,58 @@ contains this user-data with padded zeros if available. + + `-h`, `--help` +
      +-Print help ++Print help (see a summary with '-h'). ++
    ++ ++ ++## pvsecret retrieve ++### Synopsis ++`pvsecret retrieve [OPTIONS] ` ++`pvsecret retr [OPTIONS] ` ++### Description ++Retrieve a secret from the UV secret store (s390x only) ++### Arguments ++ ++`` ++
      ++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. ++
    ++ ++ ++### Options ++ ++`-o`, `--output ` ++
      ++Specify the output path to place the secret value. ++ Default value: '-' ++
    ++ ++ ++`--inform ` ++
      ++Define input type for the Secret ID. ++ Default value: 'yaml' ++ Possible values: ++ - **yaml**: Use a yaml file. ++ - **hex**: Use a hex string. ++ - **name**: Use a name-string. Will hash it if no secret with the name found. ++
    ++ ++ ++`--outform ` ++
      ++Define the output format for the retrieved secret. ++ Default value: 'pem' ++ Possible values: ++ - **pem**: Write the secret as PEM. ++ - **bin**: Write the secret in binary. ++
    ++ ++ ++`-h`, `--help` ++
      ++Print help (see a summary with '-h'). +
    +diff --git a/rust/pvsecret/man/pvsecret-add.1 b/rust/pvsecret/man/pvsecret-add.1 +index a84702f5..5ac54a91 100644 +--- a/rust/pvsecret/man/pvsecret-add.1 ++++ b/rust/pvsecret/man/pvsecret-add.1 +@@ -3,12 +3,11 @@ + .\" it under the terms of the MIT license. See LICENSE for details. + .\" + +-.TH pvsecret-add 1 "2024-05-21" "s390-tools" "UV-Secret Manual" ++.TH "PVSECRET-ADD" "1" "2024-12-19" "s390-tools" "UV-Secret Manual" + .nh + .ad l + .SH NAME +-\fBpvsecret add\fP - Perform an add-secret request (s390x only) +-\fB ++pvsecret-add \- Submit an add-secret request to the Ultravisor (s390x only) + .SH SYNOPSIS + .nf + .fam C +@@ -16,7 +15,7 @@ pvsecret add + .fam C + .fi + .SH DESCRIPTION +-Perform an add-secret request using a previously generated add-secret request. ++Perform an add\-secret request using a previously generated add\-secret request. + Only available on s390x. + .SH OPTIONS + .PP +@@ -29,7 +28,7 @@ Specify the request to be sent. + .PP + \-h, \-\-help + .RS 4 +-Print help. ++Print help (see a summary with \fB\-h\fR). + .RE + .RE + +diff --git a/rust/pvsecret/man/pvsecret-create-association.1 b/rust/pvsecret/man/pvsecret-create-association.1 +index 5704d30c..87a411e5 100644 +--- a/rust/pvsecret/man/pvsecret-create-association.1 ++++ b/rust/pvsecret/man/pvsecret-create-association.1 +@@ -3,12 +3,11 @@ + .\" it under the terms of the MIT license. See LICENSE for details. + .\" + +-.TH pvsecret-create-association 1 "2024-05-21" "s390-tools" "UV-Secret Manual" ++.TH "PVSECRET-CREATE-ASSOCIATION" "1" "2024-12-19" "s390-tools" "UV-Secret Manual" + .nh + .ad l + .SH NAME +-\fBpvsecret create association\fP - Create an association secret +-\fB ++pvsecret-create-association \- Create an association secret + .SH SYNOPSIS + .nf + .fam C +@@ -17,14 +16,14 @@ pvsecret create association [OPTIONS] + .fi + .SH DESCRIPTION + Use an association secret to connect a trusted I/O device to a guest. The +-`pvapconfig` tool provides more information about association secrets. ++\fBpvapconfig\fR tool provides more information about association secrets. + .SH OPTIONS + .PP + + .RS 4 +-String to identify the new secret. The actual secret is set with +-\fB--input-secret\fR. The name is saved in `NAME.yaml` with white-spaces mapped +-to `_`. ++String that identifies the new secret. The actual secret is set with ++\fB\-\-input\-secret\fR. The name is saved in `NAME.yaml` with white\-spaces ++mapped to `_`. + .RE + .RE + +@@ -35,24 +34,25 @@ Print the hashed name to stdout. The hashed name is not written to `NAME.yaml` + .RE + .RE + .PP +-\-\-input-secret ++\-\-input\-secret + .RS 4 + Path from which to read the plaintext secret. Uses a random secret if not + specified. + .RE + .RE + .PP +-\-\-output-secret ++\-\-output\-secret + .RS 4 +-Save the generated secret as plaintext in FILE. The generated secret can be used +-to generate add-secret requests for a different guest with the same secret using +-\fB--input-secret\fR. Destroy the secret when it is not used anymore. ++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 \fB\-\-input\-secret\fR. Destroy the secret when it is not used ++anymore. + .RE + .RE + .PP + \-h, \-\-help + .RS 4 +-Print help. ++Print help (see a summary with \fB\-h\fR). + .RE + .RE + +diff --git a/rust/pvsecret/man/pvsecret-create-meta.1 b/rust/pvsecret/man/pvsecret-create-meta.1 +index c89cee77..78a57a22 100644 +--- a/rust/pvsecret/man/pvsecret-create-meta.1 ++++ b/rust/pvsecret/man/pvsecret-create-meta.1 +@@ -1,14 +1,13 @@ +-.\" Copyright 2023 IBM Corp. ++.\" Copyright 2023, 2024 IBM Corp. + .\" s390-tools is free software; you can redistribute it and/or modify + .\" it under the terms of the MIT license. See LICENSE for details. + .\" + +-.TH pvsecret-create-meta 1 "2024-01-30" "s390-tools" "UV-Secret Manual" ++.TH "PVSECRET-CREATE-META" "1" "2024-12-19" "s390-tools" "UV-Secret Manual" + .nh + .ad l + .SH NAME +-\fBpvsecret create meta\fP - Create a meta secret +-\fB ++pvsecret-create-meta \- Create a meta secret + .SH SYNOPSIS + .nf + .fam C +diff --git a/rust/pvsecret/man/pvsecret-create-retrievable.1 b/rust/pvsecret/man/pvsecret-create-retrievable.1 +new file mode 100644 +index 00000000..0d7575eb +--- /dev/null ++++ b/rust/pvsecret/man/pvsecret-create-retrievable.1 +@@ -0,0 +1,74 @@ ++.\" Copyright 2024 IBM Corp. ++.\" s390-tools is free software; you can redistribute it and/or modify ++.\" it under the terms of the MIT license. See LICENSE for details. ++.\" ++ ++.TH "PVSECRET-CREATE-RETRIEVABLE" "1" "2024-12-19" "s390-tools" "UV-Secret Manual" ++.nh ++.ad l ++.SH NAME ++pvsecret-create-retrievable \- Create a retrievable secret ++.SH SYNOPSIS ++.nf ++.fam C ++pvsecret create retrievable [OPTIONS] --secret --type ++pvsecret create retr [OPTIONS] --secret --type ++.fam C ++.fi ++.SH DESCRIPTION ++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. ++.SH OPTIONS ++.PP ++ ++.RS 4 ++String that identifies the new secret. The actual secret is set with ++\fB\-\-secret\fR. The name is saved in `NAME.yaml` with white\-spaces mapped to ++`_`. ++.RE ++.RE ++ ++.PP ++\-\-stdout ++.RS 4 ++Print the hashed name to stdout. The hashed name is not written to `NAME.yaml` ++.RE ++.RE ++.PP ++\-\-secret ++.RS 4 ++Use SECRET\-FILE as retrievable secret. ++.RE ++.RE ++.PP ++\-\-type ++.RS 4 ++Specify the secret type. Limitations to the input data apply depending on the ++secret type. ++ ++Possible values: ++.RS 4 ++\- \fBplain\fP: A plaintext secret. Can be any file up to 8190 bytes long. ++ ++\- \fBaes\fP: An AES key. Must be a plain byte file 128, 192, or 256 bit long. ++ ++\- \fBaes-xts\fP: An AES-XTS key. Must be a plain byte file 512, or 1024 bit long. ++ ++\- \fBhmac-sha\fP: A HMAC-SHA key. Must be a plain byte file 512, or 1024 bit long. ++ ++\- \fBec\fP: An elliptic curve private key. Must be a PEM or DER file. ++ ++.RE ++.RE ++.PP ++\-h, \-\-help ++.RS 4 ++Print help (see a summary with \fB\-h\fR). ++.RE ++.RE ++ ++.SH "SEE ALSO" ++.sp ++\fBpvsecret\fR(1) \fBpvsecret-create\fR(1) +diff --git a/rust/pvsecret/man/pvsecret-create.1 b/rust/pvsecret/man/pvsecret-create.1 +index 8237c06c..87c8d8bd 100644 +--- a/rust/pvsecret/man/pvsecret-create.1 ++++ b/rust/pvsecret/man/pvsecret-create.1 +@@ -3,12 +3,11 @@ + .\" it under the terms of the MIT license. See LICENSE for details. + .\" + +-.TH pvsecret-create 1 "2024-05-21" "s390-tools" "UV-Secret Manual" ++.TH "PVSECRET-CREATE" "1" "2024-12-19" "s390-tools" "UV-Secret Manual" + .nh + .ad l + .SH NAME +-\fBpvsecret create\fP - Create a new add-secret request +-\fB ++pvsecret-create \- Create a new add-secret request + .SH SYNOPSIS + .nf + .fam C +@@ -29,39 +28,46 @@ bound to the Configuration Unique ID from \fBpvattest\fR using \fB--cuid\fR + .SH "PVSECRET CREATE COMMANDS" + .PP + +-\fBmeta\fR ++\fBpvsecret create-meta(1)\fR + .RS 4 + Create a meta secret + .RE + + .PP + +-\fBassociation\fR ++\fBpvsecret create-association(1)\fR + .RS 4 + Create an association secret + .RE + ++.PP ++ ++\fBpvsecret create-retrievable(1)\fR ++.RS 4 ++Create a retrievable secret ++.RE ++ + .SH OPTIONS + .PP +-\-k, \-\-host-key-document ++\-k, \-\-host\-key\-document + .RS 4 +-Use FILE as a host-key document. Can be specified multiple times and must be +-used at least once. ++Use FILE as a host\-key document. Can be specified multiple times and must be ++specified at least once. + .RE + .RE + .PP +-\-\-no-verify ++\-\-no\-verify + .RS 4 +-Disable the host-key document verification. Does not require the host-key ++Disable the host\-key document verification. Does not require the host\-key + documents to be valid. Do not use for a production request unless you verified +-the host-key document beforehand. ++the host\-key document beforehand. + .RE + .RE + .PP + \-C, \-\-cert + .RS 4 +-Use FILE as a certificate to verify the host key or keys. The certificates are +-used to establish a chain of trust for the verification of the host-key ++Use FILE as a certificate to verify the host\-key or keys. The certificates are ++used to establish a chain of trust for the verification of the host\-key + documents. Specify this option twice to specify the IBM Z signing key and the + intermediate CA certificate (signed by the root CA). + .RE +@@ -69,9 +75,9 @@ intermediate CA certificate (signed by the root CA). + .PP + \-\-crl + .RS 4 +-Use FILE as a certificate revocation list. The list is used to check whether a +-certificate of the chain of trust is revoked. Specify this option multiple times +-to use multiple CRLs. ++Use FILE as a certificate revocation list (CRL). The list is used to check ++whether a certificate of the chain of trust is revoked. Specify this option ++multiple times to use multiple CRLs. + .RE + .RE + .PP +@@ -81,27 +87,26 @@ Make no attempt to download CRLs. + .RE + .RE + .PP +-\-\-root-ca ++\-\-root\-ca + .RS 4 +-Use FILE as the root-CA certificate for the verification. If omitted, the system +-wide-root CAs installed on the system are used. Use this only if you trust the +-specified certificate. ++Use FILE as the root\-CA certificate for the verification. If omitted, the ++system wide\-root CAs installed on the system are used. Use this only if you ++trust the specified certificate. + .RE + .RE + .PP + \-\-hdr + .RS 4 + 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. ++created by \fBpvimg/genprotimg\fR or an extracted IBM Secure Execution header. + .RE + .RE + .PP + \-f, \-\-force + .RS 4 +-Force the generation of add-secret requests on IBM Secure Execution guests. If ++Force the generation of add\-secret requests on IBM Secure Execution guests. If + the program detects that it is running on an IBM Secure Execution guest, it +-denies the generation of add-secret requests. The force flag overwrites this ++denies the generation of add\-secret requests. The force flag overwrites this + behavior. + .RE + .RE +@@ -112,7 +117,7 @@ Write the generated request to FILE. + .RE + .RE + .PP +-\-\-extension-secret ++\-\-extension\-secret + .RS 4 + Use the content of FILE as an extension secret. The file must be exactly 32 + bytes long. If this request is the first, all subsequent requests must have the +@@ -124,7 +129,7 @@ request. + .PP + \-\-cck + .RS 4 +-Use the content of FILE as the customer-communication key (CCK) to derive the ++Use the content of FILE as the customer\-communication key (CCK) to derive the + extension secret. The file must contain exactly 32 bytes of data. If the target + guest was started with bit 1 of the secret control flag set, the ultravisor also + derives the secret from the CCK. Otherwise, the ultravisor interprets the +@@ -133,13 +138,13 @@ all requests. + .RE + .RE + .PP +-\-\-cuid-hex ++\-\-cuid\-hex + .RS 4 +-Use HEXSTRING as the Configuration Unique ID. Must be a hex 128-bit unsigned big +-endian number string. Leading zeros must be provided. If specified, the value +-must match with the Config-UID from the attestation result of that guest. If not +-specified, the CUID will be ignored by the ultravisor during the verification of +-the request. ++Use HEXSTRING as the Configuration Unique ID. Must be a hex 128\-bit unsigned ++big endian number string. Leading zeros must be provided. If specified, the ++value must match with the Config\-UID from the attestation result of that guest. ++If not specified, the CUID will be ignored by the ultravisor during the ++verification of the request. + .RE + .RE + .PP +@@ -147,7 +152,7 @@ the request. + .RS 4 + Use the content of FILE as the Configuration Unique ID. The file must contain + exactly 128 bit of data or a yaml with a `cuid` entry. If specified, the value +-must match the Config-UID from the attestation result of that guest. If not ++must match the Config\-UID from the attestation result of that guest. If not + specified, the CUID will be ignored by the Ultravisor during the verification of + the request. + .RE +@@ -155,52 +160,58 @@ the request. + .PP + \-\-flags + .RS 4 +-Flags for the add-secret request. ++Flags for the add\-secret request. + + Possible values: + .RS 4 +-- \fBdisable-dump\fP: Disables host-initiated dumping for the target guest instance. ++\- \fBdisable-dump\fP: Disables host-initiated dumping for the target guest instance. + + .RE + .RE + .PP +-\-\-user-data ++\-\-user\-data + .RS 4 +-Use the content of FILE as user-data. Passes user data defined in through +-the add-secret request to the ultravisor. The user data can be up to 512 bytes +-of arbitrary data, and the maximum size depends on the size of the user-signing ++Use the content of FILE as user\-data. Passes user data defined in FILE through ++the add\-secret request to the ultravisor. The user data can be up to 512 bytes ++of arbitrary data, and the maximum size depends on the size of the user\-signing + key: + +- - No key: user data can be 512 bytes. ++ \- No key: user data can be 512 bytes. + +- - EC(secp521r1) or RSA 2048 keys: user data can be 256 bytes. ++ \- EC(secp521r1) or RSA 2048 keys: user data can be 256 bytes. + +- - RSA 3072 key: user data can be 128 bytes. ++ \- RSA 3072 key: user data can be 128 bytes. + +-The firmware ignores this data, but the request tag protects the user-data. +-Optional. No user-data by default. ++The firmware ignores this data, but the request tag protects the user\-data. ++Optional. No user\-data by default. + .RE + .RE + .PP +-\-\-user-sign-key ++\-\-user\-sign\-key + .RS 4 + Use the content of FILE as user signing key. Adds a signature calculated from +-the key in to the add-secret request. The file must be in DER or PEM +-format containing a private key. Supported are RSA 2048 & 3072-bit and ++the key in FILE to the add\-secret request. The file must be in DER or PEM ++format containing a private key. Supported are RSA 2048 & 3072\-bit and + EC(secp521r1) keys. The firmware ignores the content, but the request tag +-protects the signature. The user-signing key signs the request. The location of ++protects the signature. The user\-signing key signs the request. The location of + the signature is filled with zeros during the signature calculation. The request + tag also secures the signature. See man pvsecret verify for more details. + Optional. No signature by default. + .RE + .RE + .PP ++\-\-use\-name ++.RS 4 ++Do not hash the name, use it directly as secret ID. Ignored for meta\-secrets. ++.RE ++.RE ++.PP + \-h, \-\-help + .RS 4 +-Print help. ++Print help (see a summary with \fB\-h\fR). + .RE + .RE + + .SH "SEE ALSO" + .sp +-\fBpvsecret\fR(1) \fBpvsecret-create-meta\fR(1) \fBpvsecret-create-association\fR(1) ++\fBpvsecret\fR(1) \fBpvsecret-create-meta\fR(1) \fBpvsecret-create-association\fR(1) \fBpvsecret-create-retrievable\fR(1) +diff --git a/rust/pvsecret/man/pvsecret-list.1 b/rust/pvsecret/man/pvsecret-list.1 +index 2828179a..4dfc3033 100644 +--- a/rust/pvsecret/man/pvsecret-list.1 ++++ b/rust/pvsecret/man/pvsecret-list.1 +@@ -3,12 +3,11 @@ + .\" it under the terms of the MIT license. See LICENSE for details. + .\" + +-.TH pvsecret-list 1 "2024-05-21" "s390-tools" "UV-Secret Manual" ++.TH "PVSECRET-LIST" "1" "2024-12-19" "s390-tools" "UV-Secret Manual" + .nh + .ad l + .SH NAME +-\fBpvsecret list\fP - List all ultravisor secrets (s390x only) +-\fB ++pvsecret-list \- List all ultravisor secrets (s390x only) + .SH SYNOPSIS + .nf + .fam C +@@ -16,8 +15,8 @@ pvsecret list [OPTIONS] [FILE] + .fam C + .fi + .SH DESCRIPTION +-Lists the IDs of all non-null secrets currently stored in the ultravisor for the +-currently running IBM Secure Execution guest. Only available on s390x. ++Lists the IDs of all non\-null secrets currently stored in the ultravisor for ++the currently running IBM Secure Execution guest. Only available on s390x. + .SH OPTIONS + .PP + +@@ -35,18 +34,18 @@ Define the output format of the list. + + Possible values: + .RS 4 +-- \fBhuman\fP: Human-focused, non-parsable output format. ++\- \fBhuman\fP: Human-focused, non-parsable output format. + +-- \fByaml\fP: Use yaml format. ++\- \fByaml\fP: Use yaml format. + +-- \fBbin\fP: Use the format the ultravisor uses to pass the list. ++\- \fBbin\fP: Use the format the ultravisor uses to pass the list. + + .RE + .RE + .PP + \-h, \-\-help + .RS 4 +-Print help. ++Print help (see a summary with \fB\-h\fR). + .RE + .RE + +diff --git a/rust/pvsecret/man/pvsecret-lock.1 b/rust/pvsecret/man/pvsecret-lock.1 +index c59c34d8..d5b1ab25 100644 +--- a/rust/pvsecret/man/pvsecret-lock.1 ++++ b/rust/pvsecret/man/pvsecret-lock.1 +@@ -3,12 +3,11 @@ + .\" it under the terms of the MIT license. See LICENSE for details. + .\" + +-.TH pvsecret-lock 1 "2024-05-15" "s390-tools" "UV-Secret Manual" ++.TH "PVSECRET-LOCK" "1" "2024-12-19" "s390-tools" "UV-Secret Manual" + .nh + .ad l + .SH NAME +-\fBpvsecret lock\fP - Lock the secret-store (s390x only) +-\fB ++pvsecret-lock \- Lock the secret-store (s390x only) + .SH SYNOPSIS + .nf + .fam C +@@ -17,7 +16,7 @@ pvsecret lock + .fi + .SH DESCRIPTION + Lock the secret store (s390x only). After this command executed successfully, +-all add-secret requests will fail. Only available on s390x. ++all subsequent add\-secret requests will fail. Only available on s390x. + .SH "SEE ALSO" + .sp + \fBpvsecret\fR(1) +diff --git a/rust/pvsecret/man/pvsecret-retrieve.1 b/rust/pvsecret/man/pvsecret-retrieve.1 +new file mode 100644 +index 00000000..369037fa +--- /dev/null ++++ b/rust/pvsecret/man/pvsecret-retrieve.1 +@@ -0,0 +1,77 @@ ++.\" Copyright 2024 IBM Corp. ++.\" s390-tools is free software; you can redistribute it and/or modify ++.\" it under the terms of the MIT license. See LICENSE for details. ++.\" ++ ++.TH "PVSECRET-RETRIEVE" "1" "2024-12-19" "s390-tools" "UV-Secret Manual" ++.nh ++.ad l ++.SH NAME ++pvsecret-retrieve \- Retrieve a secret from the UV secret store (s390x only) ++.SH SYNOPSIS ++.nf ++.fam C ++pvsecret retrieve [OPTIONS] ++pvsecret retr [OPTIONS] ++.fam C ++.fi ++.SH DESCRIPTION ++Retrieve a secret from the UV secret store (s390x only) ++.SH OPTIONS ++.PP ++ ++.RS 4 ++Specify the secret ID to be retrieved. Input type depends on \fB\-\-inform\fR. ++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. ++.RE ++.RE ++ ++.PP ++\-o, \-\-output ++.RS 4 ++Specify the output path to place the secret value. ++[default: '-'] ++.RE ++.RE ++.PP ++\-\-inform ++.RS 4 ++Define input type for the Secret ID. ++[default: 'yaml'] ++ ++Possible values: ++.RS 4 ++\- \fByaml\fP: Use a yaml file. ++ ++\- \fBhex\fP: Use a hex string. ++ ++\- \fBname\fP: Use a name-string. Will hash it if no secret with the name found. ++ ++.RE ++.RE ++.PP ++\-\-outform ++.RS 4 ++Define the output format for the retrieved secret. ++[default: 'pem'] ++ ++Possible values: ++.RS 4 ++\- \fBpem\fP: Write the secret as PEM. ++ ++\- \fBbin\fP: Write the secret in binary. ++ ++.RE ++.RE ++.PP ++\-h, \-\-help ++.RS 4 ++Print help (see a summary with \fB\-h\fR). ++.RE ++.RE ++ ++.SH "SEE ALSO" ++.sp ++\fBpvsecret\fR(1) +diff --git a/rust/pvsecret/man/pvsecret-verify.1 b/rust/pvsecret/man/pvsecret-verify.1 +index a9d636fc..136ecadc 100644 +--- a/rust/pvsecret/man/pvsecret-verify.1 ++++ b/rust/pvsecret/man/pvsecret-verify.1 +@@ -3,12 +3,11 @@ + .\" it under the terms of the MIT license. See LICENSE for details. + .\" + +-.TH pvsecret-verify 1 "2024-05-21" "s390-tools" "UV-Secret Manual" ++.TH "PVSECRET-VERIFY" "1" "2024-12-19" "s390-tools" "UV-Secret Manual" + .nh + .ad l + .SH NAME +-\fBpvsecret verify\fP - Verify that an add-secret request is sane +-\fB ++pvsecret-verify \- Verify that an add-secret request is sane + .SH SYNOPSIS + .nf + .fam C +@@ -89,12 +88,12 @@ Specify the request to be checked. + .RE + + .PP +-\-\-user-cert ++\-\-user\-cert + .RS 4 + Certificate containing a public key used to verify the user data signature. +-Specifies a public key used to verify the user-data signature. The file must be ++Specifies a public key used to verify the user\-data signature. The file must be + a X509 certificate in DSA or PEM format. The certificate must hold the public +-EC, RSA 2048, or RSA 3072 key corresponding to the private user-key used during ++EC, RSA 2048, or RSA 3072 key corresponding to the private user\-key used during + `create`. No chain of trust is established. Ensuring that the certificate can be + trusted is the responsibility of the user. The EC key must use the NIST/SECG + curve over a 521 bit prime field (secp521r1). +@@ -103,15 +102,15 @@ curve over a 521 bit prime field (secp521r1). + .PP + \-o, \-\-output + .RS 4 +-Store the result in FILE If the request contained abirtary user-data the output +-contains this user-data with padded zeros if available. ++Store the result in FILE If the request contained abirtary user\-data the output ++contains this user\-data with padded zeros if available. + [default: '-'] + .RE + .RE + .PP + \-h, \-\-help + .RS 4 +-Print help. ++Print help (see a summary with \fB\-h\fR). + .RE + .RE + +diff --git a/rust/pvsecret/man/pvsecret.1 b/rust/pvsecret/man/pvsecret.1 +index b2a1d0f6..e8cb1327 100644 +--- a/rust/pvsecret/man/pvsecret.1 ++++ b/rust/pvsecret/man/pvsecret.1 +@@ -3,12 +3,11 @@ + .\" it under the terms of the MIT license. See LICENSE for details. + .\" + +-.TH pvsecret 1 "2024-05-21" "s390-tools" "UV-Secret Manual" ++.TH "PVSECRET" "1" "2024-12-19" "s390-tools" "UV-Secret Manual" + .nh + .ad l + .SH NAME +-\fBpvsecret\fP - Manage secrets for IBM Secure Execution guests +-\fB ++pvsecret \- Manage secrets for IBM Secure Execution guests + .SH SYNOPSIS + .nf + .fam C +@@ -36,39 +35,46 @@ both the PEM and DER input formats are supported. + .SH "PVSECRET COMMANDS" + .PP + +-\fBcreate\fR ++\fBpvsecret-create(1)\fR + .RS 4 + Create a new add-secret request + .RE + + .PP + +-\fBadd\fR ++\fBpvsecret-add(1)\fR + .RS 4 +-Perform an add-secret request (s390x only) ++Submit an add-secret request to the Ultravisor (s390x only) + .RE + + .PP + +-\fBlock\fR ++\fBpvsecret-lock(1)\fR + .RS 4 + Lock the secret-store (s390x only) + .RE + + .PP + +-\fBlist\fR ++\fBpvsecret-list(1)\fR + .RS 4 + List all ultravisor secrets (s390x only) + .RE + + .PP + +-\fBverify\fR ++\fBpvsecret-verify(1)\fR + .RS 4 + Verify that an add-secret request is sane + .RE + ++.PP ++ ++\fBpvsecret-retrieve(1)\fR ++.RS 4 ++Retrieve a secret from the UV secret store (s390x only) ++.RE ++ + .SH OPTIONS + .PP + \-v, \-\-verbose +@@ -77,6 +83,12 @@ Provide more detailed output. + .RE + .RE + .PP ++\-q, \-\-quiet ++.RS 4 ++Provide less output. ++.RE ++.RE ++.PP + \-\-version + .RS 4 + Print version information and exit. +@@ -85,7 +97,7 @@ Print version information and exit. + .PP + \-h, \-\-help + .RS 4 +-Print help. ++Print help (see a summary with \fB\-h\fR). + .RE + .RE + +@@ -138,4 +150,4 @@ On the SE-guest, \fIlock\fP the secret store. + .fi + .SH "SEE ALSO" + .sp +-\fBpvsecret-create\fR(1) \fBpvsecret-add\fR(1) \fBpvsecret-lock\fR(1) \fBpvsecret-list\fR(1) \fBpvsecret-verify\fR(1) ++\fBpvsecret-create\fR(1) \fBpvsecret-add\fR(1) \fBpvsecret-lock\fR(1) \fBpvsecret-list\fR(1) \fBpvsecret-verify\fR(1) \fBpvsecret-retrieve\fR(1) diff --git a/s390-tools-03-rust-pvimg-Add-enable-disable-image-encryption-flags-to-pvimg-create.patch b/s390-tools-Support-unencrypted-SE-images-01.patch similarity index 100% rename from s390-tools-03-rust-pvimg-Add-enable-disable-image-encryption-flags-to-pvimg-create.patch rename to s390-tools-Support-unencrypted-SE-images-01.patch diff --git a/s390-tools-pvimg-additional-01.patch b/s390-tools-pvimg-additional-01.patch new file mode 100644 index 0000000..cda88c4 --- /dev/null +++ b/s390-tools-pvimg-additional-01.patch @@ -0,0 +1,167 @@ +From 5b6d7a467dc342c9c25a0af72b2d5546798cdc94 Mon Sep 17 00:00:00 2001 +From: Marc Hartmayer +Date: Thu, 12 Dec 2024 20:19:56 +0100 +Subject: [PATCH] rust/pvimg: Add '--cck ' 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 ' 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 +Reviewed-by: Steffen Eiden +Signed-off-by: Marc Hartmayer +Signed-off-by: Jan Höppner +--- + 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 ++\-\-cck, \-\-comm\-key + .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 ++\-\-cck, \-\-comm\-key + .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, + + /// Disable Secure Execution guest dump support (default). +@@ -105,9 +105,9 @@ pub struct CreateBootImageLegacyFlags { + pub disable_dump: Option, + + /// 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, + + /// 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, ++ #[arg(long, value_name = "FILE", visible_alias = "comm-key")] ++ pub cck: Option, + + #[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) { diff --git a/s390-tools-pvimg-info-command-01.patch b/s390-tools-pvimg-info-command-01.patch new file mode 100644 index 0000000..09c8b18 --- /dev/null +++ b/s390-tools-pvimg-info-command-01.patch @@ -0,0 +1,58 @@ +From 560b276f7e9938475af921c8ebd4cd05910dbf31 Mon Sep 17 00:00:00 2001 +From: Marc Hartmayer +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 +Signed-off-by: Marc Hartmayer +Signed-off-by: Jan Höppner +--- + 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) ++ )); ++ } ++} diff --git a/s390-tools-pvimg-info-command-02.patch b/s390-tools-pvimg-info-command-02.patch new file mode 100644 index 0000000..6e0589b --- /dev/null +++ b/s390-tools-pvimg-info-command-02.patch @@ -0,0 +1,51 @@ +From 3f6572e901ddcc654021c4302cb2a99999acb87a Mon Sep 17 00:00:00 2001 +From: Marc Hartmayer +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 +Signed-off-by: Marc Hartmayer +Signed-off-by: Jan Höppner +--- + 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>(template: P) -> Result { + // 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); + diff --git a/s390-tools-pvimg-info-command-03.patch b/s390-tools-pvimg-info-command-03.patch new file mode 100644 index 0000000..7f19275 --- /dev/null +++ b/s390-tools-pvimg-info-command-03.patch @@ -0,0 +1,334 @@ +From 944581eaefe4c6887790f2b8ed39c9ee76146c55 Mon Sep 17 00:00:00 2001 +From: Marc Hartmayer +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 +Signed-off-by: Marc Hartmayer +Signed-off-by: Jan Höppner +--- + 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 { +- [serialize_to_bytes(&self.common).unwrap(), self.data.aad()].concat() ++ fn aad(&self) -> Result> { ++ Ok([serialize_to_bytes(&self.common)?, self.data.aad()?].concat()) + } + + fn data(&self) -> Vec { +@@ -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 { +- let data_aad = self.data.aad(); ++ fn aad(&self) -> Result> { ++ 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> { ++ fn data(&self) -> Result>> { + 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 { +- serialize_to_bytes(&self.aad).unwrap() ++ fn aad(&self) -> Result> { ++ serialize_to_bytes(&self.aad) + } + + fn data(&self) -> Vec { +@@ -508,12 +521,12 @@ impl AeadDataTrait for SeHdrBinV1 { + } + + impl AeadPlainDataTrait for SeHdrDataV1 { +- fn aad(&self) -> Vec { +- serialize_to_bytes(&self.aad).unwrap() ++ fn aad(&self) -> Result> { ++ serialize_to_bytes(&self.aad) + } + +- fn data(&self) -> Confidential> { +- serialize_to_bytes(self.data.value()).unwrap().into() ++ fn data(&self) -> Result>> { ++ Ok(serialize_to_bytes(self.data.value())?.into()) + } + + fn tag(&self) -> Vec { +@@ -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; ++ fn aad(&self) -> Result>; + + /// Returns the encrypted data. + fn data(&self) -> Vec; +@@ -47,10 +47,10 @@ pub trait AeadDataTrait { + #[enum_dispatch] + pub trait AeadPlainDataTrait { + /// Returns the authenticated associated data. +- fn aad(&self) -> Vec; ++ fn aad(&self) -> Result>; + + /// Returns the unencrypted data. +- fn data(&self) -> Confidential>; ++ fn data(&self) -> Result>>; + + /// Returns the tag data. + fn tag(&self) -> Vec; +@@ -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(); diff --git a/s390-tools-pvimg-info-command-04.patch b/s390-tools-pvimg-info-command-04.patch new file mode 100644 index 0000000..6a05f76 --- /dev/null +++ b/s390-tools-pvimg-info-command-04.patch @@ -0,0 +1,101 @@ +From 6e48c5ebaa26c6bd2a1bc33ccf36ed8bd6946358 Mon Sep 17 00:00:00 2001 +From: Marc Hartmayer +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 +Acked-by: Hendrik Brueckner +Reviewed-by: Steffen Eiden +Signed-off-by: Marc Hartmayer +Signed-off-by: Jan Höppner +--- + 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 ++\-\-hdr\-key + .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, ++ #[arg(long, value_name = "FILE", value_hint = ValueHint::FilePath, alias = "key")] ++ pub hdr_key: Option, + } + + #[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 + +- 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 diff --git a/s390-tools.spec b/s390-tools.spec index 77e096a..d739f4e 100644 --- a/s390-tools.spec +++ b/s390-tools.spec @@ -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 diff --git a/vendor.tar.gz b/vendor.tar.gz index 6fe4378..b20eaf3 100644 --- a/vendor.tar.gz +++ b/vendor.tar.gz @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f3de048bd3be1ca62aa8bb860fd4971e2f49d6fa4d1027f0b18ac4b11ebff28c -size 46338529 +oid sha256:c5844b1b0c04b5d882966d8a3335385f39a0bcca0693b87c018d8a7949b87739 +size 46334305