rust/pv: Use a SecretId struct instead of an array

This streamlines and unifies the use and (de)serialization of structs
using a secret id. As a bonus, the hidden `for_pv` module is not longer
needed.

Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Reviewed-by: Julian Ruess <julianr@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
This commit is contained in:
Steffen Eiden
2024-02-15 17:06:04 +01:00
committed by Jan Höppner
parent 81a1e13f3b
commit c53dfa9754
6 changed files with 161 additions and 76 deletions

View File

@@ -35,11 +35,7 @@ const PAGESIZE: usize = 0x1000;
/// Definitions and functions for interacting with the Ultravisor
pub mod uv {
pub use pv_core::uv::{
uv_ioctl, ConfigUid, UvCmd, UvDevice, UvDeviceInfo, UvFlags, UvcSuccess,
};
pub use pv_core::uv::{AddCmd, ListCmd, LockCmd};
pub use pv_core::uv::{ListableSecretType, SecretEntry, SecretList};
pub use pv_core::uv::*;
}
/// Miscellaneous functions and definitions

View File

@@ -23,7 +23,7 @@ use pv_core::request::RequestVersion;
use zerocopy::AsBytes;
/// Internal wrapper for Guest Secret, so that we can dump it in the form the UV wants it to be
#[derive(Debug, Clone)]
#[derive(Debug)]
struct BinGuestSecret(GuestSecret);
impl BinGuestSecret {
/// Reference to the confidential data
@@ -40,7 +40,7 @@ impl BinGuestSecret {
let mut buf = vec![0; 48];
buf[3] = 2;
buf[7] = 0x20;
buf[16..48].copy_from_slice(id.as_slice());
buf[16..48].copy_from_slice(id.as_ref());
buf
}
}
@@ -75,7 +75,7 @@ impl ReqAuthData {
}
}
#[derive(Debug, Clone)]
#[derive(Debug)]
struct ReqConfData {
secret: BinGuestSecret,
extension_secret: Secret<[u8; 32]>,
@@ -164,7 +164,7 @@ impl From<AddSecretVersion> for RequestVersion {
/// | AES GCM Tag (16) |
/// |_____________________________________________________________|
///```
#[derive(Clone, Debug)]
#[derive(Debug)]
pub struct AddSecretRequest {
version: AddSecretVersion,
aad: ReqAuthData,
@@ -348,7 +348,7 @@ mod test {
fn guest_secret_bin_ap() {
let gs: BinGuestSecret = GuestSecret::Association {
name: "test".to_string(),
id: [1; 32],
id: [1; 32].into(),
secret: [2; 32].into(),
}
.into();

View File

@@ -8,13 +8,14 @@ use crate::{
request::{hash, openssl::MessageDigest, random_array, Secret},
Result,
};
use pv_core::for_pv::{ser_gsid, SECRET_ID_SIZE};
use pv_core::uv::SecretId;
use serde::{Deserialize, Serialize};
use std::convert::TryInto;
const SECRET_SIZE: usize = 32;
/// A Secret to be added in [`AddSecretRequest`]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum GuestSecret {
/// No guest secret
Null,
@@ -24,10 +25,9 @@ pub enum GuestSecret {
Association {
/// Name of the secret
name: String,
#[serde(serialize_with = "ser_gsid", deserialize_with = "de_gsid")]
/// SHA256 hash of [`GuestSecret::Association::name`]
id: [u8; SECRET_ID_SIZE],
/// Confidential actual assocuiation secret (32 bytes)
id: SecretId,
/// Confidential actual association secret (32 bytes)
#[serde(skip)]
secret: Secret<[u8; SECRET_SIZE]>,
},
@@ -46,7 +46,10 @@ impl GuestSecret {
where
O: Into<Option<[u8; SECRET_SIZE]>>,
{
let id = hash(MessageDigest::sha256(), name.as_bytes())?.to_vec();
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()?,
@@ -54,39 +57,11 @@ impl GuestSecret {
Ok(GuestSecret::Association {
name: name.to_string(),
id: id.try_into().unwrap(),
id: id.into(),
secret: secret.into(),
})
}
}
fn de_gsid<'de, D>(de: D) -> Result<[u8; 32], D::Error>
where
D: serde::Deserializer<'de>,
{
struct FieldVisitor;
impl<'de> serde::de::Visitor<'de> for FieldVisitor {
type Value = [u8; SECRET_ID_SIZE];
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a `32 bytes long hexstring` prepended with 0x")
}
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
if s.len() != SECRET_ID_SIZE * 2 + 2 {
return Err(serde::de::Error::invalid_length(s.len(), &self));
}
let nb = s.strip_prefix("0x").ok_or_else(|| {
serde::de::Error::invalid_value(serde::de::Unexpected::Str(s), &self)
})?;
crate::misc::parse_hex(nb)
.try_into()
.map_err(|_| serde::de::Error::invalid_value(serde::de::Unexpected::Str(s), &self))
}
}
de.deserialize_identifier(FieldVisitor)
}
#[cfg(test)]
mod test {
@@ -106,7 +81,7 @@ mod test {
let secret = GuestSecret::association("association secret", secret_value).unwrap();
let exp = GuestSecret::Association {
name,
id: exp_id,
id: exp_id.into(),
secret: secret_value.into(),
};
assert_eq!(secret, exp);
@@ -121,7 +96,7 @@ mod test {
];
let asc = GuestSecret::Association {
name: "test123".to_string(),
id,
id: id.into(),
secret: [0; 32].into(),
};

View File

@@ -37,7 +37,7 @@ pub mod misc {
/// Definitions and functions for interacting with the Ultravisor
pub mod uv {
pub use crate::uvdevice::secret::{AddCmd, ListCmd, LockCmd};
pub use crate::uvdevice::secret_list::{ListableSecretType, SecretEntry, SecretList};
pub use crate::uvdevice::secret_list::{ListableSecretType, SecretEntry, SecretId, SecretList};
pub use crate::uvdevice::{
uv_ioctl, ConfigUid, UvCmd, UvDevice, UvDeviceInfo, UvFlags, UvcSuccess,
};
@@ -84,10 +84,3 @@ pub const fn crate_info() -> &'static str {
const PAGESIZE: usize = 0x1000;
use ::utils::assert_size;
use ::utils::static_assert;
#[doc(hidden)]
/// stuff pv_core and pv share. Not intended for other users
pub mod for_pv {
pub use crate::uvdevice::secret_list::ser_gsid;
pub use crate::uvdevice::secret_list::SECRET_ID_SIZE;
}

View File

@@ -3,7 +3,7 @@
// Copyright IBM Corp. 2024
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use serde::{Serialize, Serializer};
use serde::{Deserialize, Serialize, Serializer};
use std::{
fmt::Display,
io::{Cursor, Read, Seek, Write},
@@ -15,6 +15,64 @@ use zerocopy::{AsBytes, FromBytes, FromZeroes, U16, U32};
use crate::{misc::to_u16, uv::ListCmd, uvdevice::UvCmd, Error, Result};
/// The 32 byte long ID of an UV secret
///
/// (de)serializes itself in/from a hex-string
#[repr(C)]
#[derive(PartialEq, Eq, AsBytes, FromZeroes, FromBytes, Debug, Clone)]
pub struct SecretId([u8; Self::ID_SIZE]);
assert_size!(SecretId, SecretId::ID_SIZE);
impl SecretId {
/// Size in bytes of the [`SecretId`]
pub const ID_SIZE: usize = 32;
/// Create a [`SecretId`] forom a buffer.
pub fn from(buf: [u8; Self::ID_SIZE]) -> Self {
buf.into()
}
}
impl Serialize for SecretId {
fn serialize<S>(&self, ser: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
//calls Display at one point
ser.serialize_str(&self.to_string())
}
}
impl<'de> Deserialize<'de> for SecretId {
fn deserialize<D>(de: D) -> std::result::Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
de_gsid(de).map(|id| id.into())
}
}
impl Display for SecretId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut s = String::with_capacity(32 * 2 + 2);
s.push_str("0x");
let s = self.0.iter().fold(s, |acc, e| acc + &format!("{e:02x}"));
write!(f, "{s}")
}
}
impl From<[u8; SecretId::ID_SIZE]> for SecretId {
fn from(value: [u8; SecretId::ID_SIZE]) -> Self {
Self(value)
}
}
impl AsRef<[u8]> for SecretId {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
/// A secret in a [`SecretList`]
#[repr(C)]
#[derive(Debug, PartialEq, Eq, AsBytes, FromZeroes, FromBytes, Serialize)]
@@ -27,8 +85,7 @@ pub struct SecretEntry {
len: U32<BigEndian>,
#[serde(skip)]
res_8: u64,
#[serde(serialize_with = "ser_gsid")]
id: [u8; SECRET_ID_SIZE],
id: SecretId,
}
assert_size!(SecretEntry, SecretEntry::STRUCT_SIZE);
@@ -39,7 +96,7 @@ impl SecretEntry {
///
/// The content of this entry will very liekly not represent the status of the guest in the
/// Ultravisor. Use of [`SecretList::decode`] in any non-test environments is encuraged.
pub fn new(index: u16, stype: ListableSecretType, id: [u8; 32], secret_len: u32) -> Self {
pub fn new(index: u16, stype: ListableSecretType, id: SecretId, secret_len: u32) -> Self {
Self {
index: index.into(),
stype: stype.into(),
@@ -60,8 +117,16 @@ impl SecretEntry {
}
/// Returns a reference to the id of this [`SecretEntry`].
///
/// The slice is guaranteed to be 32 bytes long.
/// ```rust
/// # use pv_core::uv::SecretEntry;
/// # use zerocopy::FromZeroes;
/// # let secr = SecretEntry::new_zeroed();
/// # assert_eq!(secr.id().len(), 32);
/// ```
pub fn id(&self) -> &[u8] {
&self.id
self.id.as_ref()
}
}
@@ -70,7 +135,7 @@ impl Display for SecretEntry {
let stype: ListableSecretType = self.stype.into();
writeln!(f, "{} {}:", self.index, stype)?;
write!(f, " ")?;
for b in self.id {
for b in self.id.as_ref() {
write!(f, "{b:02x}")?;
}
Ok(())
@@ -263,23 +328,40 @@ impl From<ListableSecretType> for U16<BigEndian> {
}
}
#[doc(hidden)]
pub const SECRET_ID_SIZE: usize = 32;
#[doc(hidden)]
pub fn ser_gsid<S>(id: &[u8; SECRET_ID_SIZE], ser: S) -> Result<S::Ok, S::Error>
fn de_gsid<'de, D>(de: D) -> Result<[u8; 32], D::Error>
where
S: serde::Serializer,
D: serde::Deserializer<'de>,
{
let mut s = String::with_capacity(32 * 2 + 2);
s.push_str("0x");
let s = id.iter().fold(s, |acc, e| acc + &format!("{e:02x}"));
ser.serialize_str(&s)
struct FieldVisitor;
impl<'de> serde::de::Visitor<'de> for FieldVisitor {
type Value = [u8; SecretId::ID_SIZE];
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a `32 bytes long hexstring` prepended with 0x")
}
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
if s.len() != SecretId::ID_SIZE * 2 + 2 {
return Err(serde::de::Error::invalid_length(s.len(), &self));
}
let nb = s.strip_prefix("0x").ok_or_else(|| {
serde::de::Error::invalid_value(serde::de::Unexpected::Str(s), &self)
})?;
crate::misc::parse_hex(nb)
.try_into()
.map_err(|_| serde::de::Error::invalid_value(serde::de::Unexpected::Str(s), &self))
}
}
de.deserialize_identifier(FieldVisitor)
}
#[cfg(test)]
mod test {
use serde_test::{assert_ser_tokens, assert_tokens, Token};
use super::*;
use std::io::{BufReader, BufWriter, Cursor};
@@ -299,7 +381,7 @@ mod test {
stype: 2.into(),
len: 32.into(),
res_8: 0,
id: [0; 32],
id: SecretId::from([0; 32]),
};
assert_eq!(s.as_bytes(), EXP);
@@ -328,7 +410,7 @@ mod test {
stype: 2.into(),
len: 32.into(),
res_8: 0,
id: [0; 32],
id: SecretId::from([0; 32]),
}],
};
@@ -360,7 +442,7 @@ mod test {
stype: 2.into(),
len: 32.into(),
res_8: 0,
id: [0; 32],
id: SecretId::from([0; 32]),
}],
};
@@ -372,4 +454,43 @@ mod test {
println!("list: {sl:?}");
assert_eq!(buf, EXP);
}
#[test]
fn secret_entry_ser() {
let entry = SecretEntry::new_zeroed();
assert_ser_tokens(
&entry,
&[
Token::Struct {
name: "SecretEntry",
len: (4),
},
Token::String("index"),
Token::U16(0),
Token::String("stype"),
Token::U16(0),
Token::String("len"),
Token::U32(0),
Token::String("id"),
Token::String("0x0000000000000000000000000000000000000000000000000000000000000000"),
Token::StructEnd,
],
)
}
#[test]
fn secret_id_serde() {
let id = SecretId::from([
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,
]);
assert_tokens(
&id,
&[Token::String(
"0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
)],
)
}
}

View File

@@ -528,8 +528,8 @@ mod tests {
fn make_assoc_secretentry(idx: u16, hexidstr: &str) -> SecretEntry {
let id = hexstring_to_u8(hexidstr);
let idlen: u32 = id.len().try_into().unwrap();
let idarray = <&[u8; 32]>::try_from(id.as_slice()).unwrap();
SecretEntry::new(idx, ListableSecretType::Association, *idarray, idlen)
let idarray: [u8; 32] = id.try_into().unwrap();
SecretEntry::new(idx, ListableSecretType::Association, idarray.into(), idlen)
}
fn make_test_secrets() -> Vec<SecretEntry> {