mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
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:
committed by
Jan Höppner
parent
4af137f4fa
commit
fd024387d7
@@ -29,7 +29,6 @@ pub type Aes256XtsKey = Confidential<[u8; SymKeyType::AES_256_XTS_KEY_LEN]>;
|
|||||||
|
|
||||||
/// SHA-512 digest length (in bytes)
|
/// SHA-512 digest length (in bytes)
|
||||||
pub const SHA_512_HASH_LEN: usize = 64;
|
pub const SHA_512_HASH_LEN: usize = 64;
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub(crate) const SHA_256_HASH_LEN: u32 = 32;
|
pub(crate) const SHA_256_HASH_LEN: u32 = 32;
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@@ -60,6 +59,8 @@ impl SymKeyType {
|
|||||||
pub const AES_256_XTS_KEY_LEN: usize = 64;
|
pub const AES_256_XTS_KEY_LEN: usize = 64;
|
||||||
/// AES256-XTS tweak length (in bytes)
|
/// AES256-XTS tweak length (in bytes)
|
||||||
pub const AES_256_XTS_TWEAK_LEN: usize = 16;
|
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
|
/// Returns the tag length of the [`SymKeyType`] if it is an AEAD key
|
||||||
pub const fn tag_len(&self) -> Option<usize> {
|
pub const fn tag_len(&self) -> Option<usize> {
|
||||||
|
|||||||
@@ -109,6 +109,14 @@ pub enum Error {
|
|||||||
#[error("An ASCII string was expected, but non-ASCII characters were received.")]
|
#[error("An ASCII string was expected, but non-ASCII characters were received.")]
|
||||||
NonAscii,
|
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
|
// errors from other crates
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
PvCore(#[from] pv_core::Error),
|
PvCore(#[from] pv_core::Error),
|
||||||
|
|||||||
@@ -104,7 +104,12 @@ pub mod request {
|
|||||||
|
|
||||||
/// Reexports some useful OpenSSL symbols
|
/// Reexports some useful OpenSSL symbols
|
||||||
pub mod openssl {
|
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::*;
|
pub use pv_core::request::*;
|
||||||
@@ -118,6 +123,7 @@ pub mod secret {
|
|||||||
asrcb::{AddSecretFlags, AddSecretRequest, AddSecretVersion},
|
asrcb::{AddSecretFlags, AddSecretRequest, AddSecretVersion},
|
||||||
ext_secret::ExtSecret,
|
ext_secret::ExtSecret,
|
||||||
guest_secret::GuestSecret,
|
guest_secret::GuestSecret,
|
||||||
|
retr_secret::{IbmProtectedKey, RetrievedSecret},
|
||||||
user_data::verify_asrcb_and_get_user_data,
|
user_data::verify_asrcb_and_get_user_data,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,4 +10,5 @@
|
|||||||
pub mod asrcb;
|
pub mod asrcb;
|
||||||
pub mod ext_secret;
|
pub mod ext_secret;
|
||||||
pub mod guest_secret;
|
pub mod guest_secret;
|
||||||
|
pub mod retr_secret;
|
||||||
pub mod user_data;
|
pub mod user_data;
|
||||||
|
|||||||
@@ -4,20 +4,34 @@
|
|||||||
|
|
||||||
#[allow(unused_imports)] // used for more convenient docstring
|
#[allow(unused_imports)] // used for more convenient docstring
|
||||||
use super::asrcb::AddSecretRequest;
|
use super::asrcb::AddSecretRequest;
|
||||||
use crate::assert_size;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
crypto::{hash, random_array},
|
assert_size,
|
||||||
request::Confidential,
|
crypto::{hash, random_array, SymKeyType},
|
||||||
Result,
|
request::{
|
||||||
|
openssl::{NID_ED25519, NID_ED448},
|
||||||
|
Confidential,
|
||||||
|
},
|
||||||
|
uv::{
|
||||||
|
AesSizes, AesXtsSizes, EcCurves, HmacShaSizes, ListableSecretType, RetrievableSecret,
|
||||||
|
RetrieveCmd, SecretId,
|
||||||
|
},
|
||||||
|
Error, Result,
|
||||||
};
|
};
|
||||||
use byteorder::BigEndian;
|
use byteorder::BigEndian;
|
||||||
use openssl::hash::MessageDigest;
|
use openssl::{
|
||||||
use pv_core::uv::{ListableSecretType, SecretId};
|
hash::MessageDigest,
|
||||||
|
nid::Nid,
|
||||||
|
pkey::{Id, PKey, Private},
|
||||||
|
};
|
||||||
|
use pv_core::static_assert;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{convert::TryInto, fmt::Display};
|
use std::fmt::Display;
|
||||||
use zerocopy::{AsBytes, U16, U32};
|
use zerocopy::{AsBytes, U16, U32};
|
||||||
|
|
||||||
const ASSOC_SECRET_SIZE: usize = 32;
|
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`]
|
/// A Secret to be added in [`AddSecretRequest`]
|
||||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
@@ -36,13 +50,60 @@ pub enum GuestSecret {
|
|||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
secret: Confidential<[u8; ASSOC_SECRET_SIZE]>,
|
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 {
|
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`].
|
/// Create a new [`GuestSecret::Association`].
|
||||||
///
|
///
|
||||||
/// * `name` - Name of the secret. Will be hashed into a 32 byte id
|
/// * `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
|
/// # Errors
|
||||||
///
|
///
|
||||||
@@ -51,10 +112,6 @@ impl GuestSecret {
|
|||||||
where
|
where
|
||||||
O: Into<Option<[u8; ASSOC_SECRET_SIZE]>>,
|
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() {
|
let secret = match secret.into() {
|
||||||
Some(s) => s,
|
Some(s) => s,
|
||||||
None => random_array()?,
|
None => random_array()?,
|
||||||
@@ -62,16 +119,28 @@ impl GuestSecret {
|
|||||||
|
|
||||||
Ok(Self::Association {
|
Ok(Self::Association {
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
id: id.into(),
|
id: Self::name_to_id(name)?,
|
||||||
secret: secret.into(),
|
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
|
/// Reference to the confidential data
|
||||||
pub fn confidential(&self) -> &[u8] {
|
pub fn confidential(&self) -> &[u8] {
|
||||||
match &self {
|
match &self {
|
||||||
Self::Null => &[],
|
Self::Null => &[],
|
||||||
Self::Association { secret, .. } => secret.value().as_slice(),
|
Self::Association { secret, .. } => secret.value().as_slice(),
|
||||||
|
Self::Retrievable { secret, .. } => secret.value(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,7 +148,7 @@ impl GuestSecret {
|
|||||||
pub(crate) fn auth(&self) -> SecretAuth {
|
pub(crate) fn auth(&self) -> SecretAuth {
|
||||||
match &self {
|
match &self {
|
||||||
Self::Null => SecretAuth::Null,
|
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 => {
|
listable => {
|
||||||
SecretAuth::Listable(ListableSecretHdr::from_guest_secret(listable).unwrap())
|
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)
|
// Null is not listable, but the ListableSecretType provides the type constant (1)
|
||||||
Self::Null => ListableSecretType::NULL,
|
Self::Null => ListableSecretType::NULL,
|
||||||
Self::Association { .. } => ListableSecretType::ASSOCIATION,
|
Self::Association { .. } => ListableSecretType::ASSOCIATION,
|
||||||
|
Self::Retrievable { kind, .. } => kind.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,6 +170,7 @@ impl GuestSecret {
|
|||||||
match self {
|
match self {
|
||||||
Self::Null => 0,
|
Self::Null => 0,
|
||||||
Self::Association { secret, .. } => secret.value().len() as u32,
|
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> {
|
fn id(&self) -> Option<SecretId> {
|
||||||
match self {
|
match self {
|
||||||
Self::Null => None,
|
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 {
|
impl Display for GuestSecret {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::Null => write!(f, "Meta"),
|
Self::Null => write!(f, "Meta"),
|
||||||
gs => {
|
gs => {
|
||||||
let kind: U16<BigEndian> = gs.kind().into();
|
let kind: U16<BigEndian> = gs.kind().into();
|
||||||
let st: ListableSecretType = kind.into();
|
let st: ListableSecretType = kind.get().into();
|
||||||
write!(f, "{st}")
|
write!(f, "{st}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -153,20 +363,24 @@ assert_size!(ListableSecretHdr, 0x30);
|
|||||||
|
|
||||||
impl ListableSecretHdr {
|
impl ListableSecretHdr {
|
||||||
fn from_guest_secret(gs: &GuestSecret) -> Option<Self> {
|
fn from_guest_secret(gs: &GuestSecret) -> Option<Self> {
|
||||||
let id = gs.id()?;
|
|
||||||
Some(Self {
|
Some(Self {
|
||||||
res0: 0,
|
res0: 0,
|
||||||
kind: gs.kind().into(),
|
kind: gs.kind().into(),
|
||||||
secret_len: gs.secret_len().into(),
|
secret_len: gs.secret_len().into(),
|
||||||
res8: 0,
|
res8: 0,
|
||||||
id,
|
id: gs.id()?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
|
||||||
|
use super::HmacShaSizes as HmacSizes;
|
||||||
|
use super::RetrievableSecret::*;
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use openssl::ec::{EcGroup, EcKey};
|
||||||
|
use pv_core::uv::AesSizes;
|
||||||
use serde_test::{assert_tokens, Token};
|
use serde_test::{assert_tokens, Token};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -187,8 +401,103 @@ mod test {
|
|||||||
assert_eq!(secret, exp);
|
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]
|
#[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 = [
|
let id = [
|
||||||
0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab,
|
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,
|
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]
|
#[test]
|
||||||
fn guest_secret_bin_null() {
|
fn guest_secret_bin_null() {
|
||||||
let gs = GuestSecret::Null;
|
let gs = GuestSecret::Null;
|
||||||
@@ -228,7 +570,7 @@ mod test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn guest_secret_bin_ap() {
|
fn guest_secret_bin_asoc() {
|
||||||
let gs = GuestSecret::Association {
|
let gs = GuestSecret::Association {
|
||||||
name: "test".to_string(),
|
name: "test".to_string(),
|
||||||
id: [1; 32].into(),
|
id: [1; 32].into(),
|
||||||
@@ -241,4 +583,21 @@ mod test {
|
|||||||
assert_eq!(exp, gs_bytes_auth.get());
|
assert_eq!(exp, gs_bytes_auth.get());
|
||||||
assert_eq!(&[2; 32], gs.confidential());
|
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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
234
rust/pv/src/uvsecret/retr_secret.rs
Normal file
234
rust/pv/src/uvsecret/retr_secret.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user