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)
|
||||
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> {
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -10,4 +10,5 @@
|
||||
pub mod asrcb;
|
||||
pub mod ext_secret;
|
||||
pub mod guest_secret;
|
||||
pub mod retr_secret;
|
||||
pub mod user_data;
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
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