878 lines
29 KiB
Diff
878 lines
29 KiB
Diff
|
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);
|
||
|
+ }
|
||
|
+}
|