File s390-tools-General-update-06.patch of Package s390-tools

From fd024387d710887bd2016658c44d4762a08c791c Mon Sep 17 00:00:00 2001
From: Steffen Eiden <seiden@linux.ibm.com>
Date: Tue, 5 Mar 2024 12:19:22 +0100
Subject: [PATCH] rust/pv: Retrievable secrets support
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Support retrievable secret for Add-Secret requests.

Acked-by: Marc Hartmayer <marc@linux.ibm.com>
Reviewed-by: Christoph Schlameuss <schlameuss@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
---
 rust/pv/src/crypto.rs                |   3 +-
 rust/pv/src/error.rs                 |   8 +
 rust/pv/src/lib.rs                   |   8 +-
 rust/pv/src/uvsecret.rs              |   1 +
 rust/pv/src/uvsecret/guest_secret.rs | 399 +++++++++++++++++++++++++--
 rust/pv/src/uvsecret/retr_secret.rs  | 234 ++++++++++++++++
 6 files changed, 631 insertions(+), 22 deletions(-)
 create mode 100644 rust/pv/src/uvsecret/retr_secret.rs

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