rust/pv: Retrievable secrets support

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>
This commit is contained in:
Steffen Eiden
2024-03-05 12:19:22 +01:00
committed by Jan Höppner
parent 4af137f4fa
commit fd024387d7
6 changed files with 631 additions and 22 deletions

View File

@@ -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> {

View File

@@ -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),

View File

@@ -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,
};
}

View File

@@ -10,4 +10,5 @@
pub mod asrcb;
pub mod ext_secret;
pub mod guest_secret;
pub mod retr_secret;
pub mod user_data;

View File

@@ -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 ap_asc_parse() {
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 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());
}
}

View File

@@ -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);
}
}