rust/pv*: Allow the use of non-hashes secret IDs

Secret IDs identify a secret in the store. Tooling (pvsecret) calculates
them by hashing a user-defined string. With this patch it is now
possible to skip the hash step and directly use the input string as the
ID. Up to the first 31 bytes of the input ASCII-string are used. The last byte
is the NUL char. During list pvsecret tries to interpret the secret
as ASCII string and if possible displays the ASCII characters alongside
the hex number.

Also, use the Upper/Lower Hex formatters for the hexstring formatting of
SecretId. Display will, additionally show the ASCII representation if
applicable.

While at it, use Self wherever possible.

Acked-by: Marc Hartmayer <marc@linux.ibm.com>
Reviewed-by: Christoph Schlameuss <schlameuss@linux.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
This commit is contained in:
Steffen Eiden
2024-08-05 09:34:47 +02:00
committed by Jan Höppner
parent 93216d916c
commit ff04f76257
5 changed files with 184 additions and 21 deletions
+15 -1
View File
@@ -92,7 +92,8 @@ macro_rules! retr_constructor {
}
impl GuestSecret {
fn name_to_id(name: &str) -> Result<SecretId> {
/// Hashes the name with sha256
pub fn name_to_id(name: &str) -> Result<SecretId> {
let id: [u8; SecretId::ID_SIZE] = hash(MessageDigest::sha256(), name.as_bytes())?
.to_vec()
.try_into()
@@ -135,6 +136,19 @@ impl GuestSecret {
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);
/// Use the name as ID, do not hash it
pub fn no_hash_name(&mut self) {
match self {
Self::Null => (),
Self::Association {
name, ref mut id, ..
}
| Self::Retrievable {
name, ref mut id, ..
} => id.clone_from(&SecretId::from_string(name)),
}
}
/// Reference to the confidential data
pub fn confidential(&self) -> &[u8] {
match &self {
+148 -15
View File
@@ -11,7 +11,9 @@ use crate::{
use byteorder::{BigEndian, ByteOrder};
use serde::{Deserialize, Serialize, Serializer};
use std::{
fmt::Display,
cmp::min,
ffi::CStr,
fmt::{Debug, Display, LowerHex, UpperHex},
io::{Cursor, Read, Seek, Write},
mem::size_of,
slice::Iter,
@@ -35,6 +37,33 @@ impl SecretId {
pub fn from(buf: [u8; Self::ID_SIZE]) -> Self {
buf.into()
}
/// Create a Id from a string
///
/// Uses the first 31 bytes from `name` as id
/// Does not hash anything. Byte 32 is the NUL char
pub fn from_string(name: &str) -> Self {
let len = min(name.len(), Self::ID_SIZE - 1);
let mut res = Self::default();
res.0[0..len].copy_from_slice(&name.as_bytes()[0..len]);
res
}
/// Tries to represent the Id as printable-ASCII string
pub fn as_ascii(&self) -> Option<&str> {
if let Ok(t) = CStr::from_bytes_until_nul(&self.0) {
if let Ok(t) = t.to_str() {
if !t.is_empty()
&& t.chars()
.all(|c| c.is_ascii_whitespace() | c.is_ascii_graphic())
&& self.0[t.len()..].iter().all(|b| *b == 0)
{
return Some(t);
}
}
};
None
}
}
impl Serialize for SecretId {
@@ -42,8 +71,8 @@ impl Serialize for SecretId {
where
S: Serializer,
{
// calls Display at one point
ser.serialize_str(&self.to_string())
// calls LowerHex at one point
ser.serialize_str(&format!("{self:#x}"))
}
}
@@ -56,12 +85,36 @@ impl<'de> Deserialize<'de> for SecretId {
}
}
impl UpperHex for SecretId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if f.alternate() {
write!(f, "0x")?;
}
for b in self.0 {
write!(f, "{b:02X}")?;
}
Ok(())
}
}
impl LowerHex for SecretId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if f.alternate() {
write!(f, "0x")?;
}
for b in self.0 {
write!(f, "{b:02x}")?;
}
Ok(())
}
}
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}")
if let Some(s) = self.as_ascii() {
write!(f, "{s} | ")?;
}
write!(f, "{self:#x}")
}
}
@@ -79,7 +132,7 @@ impl AsRef<[u8]> for SecretId {
/// A secret in a [`SecretList`]
#[repr(C)]
#[derive(Debug, PartialEq, Eq, AsBytes, FromZeroes, FromBytes, Serialize)]
#[derive(Debug, Clone, PartialEq, Eq, AsBytes, FromZeroes, FromBytes, Serialize)]
pub struct SecretEntry {
#[serde(serialize_with = "ser_u16")]
index: U16<BigEndian>,
@@ -153,11 +206,7 @@ impl Display for SecretEntry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let stype: ListableSecretType = self.stype.get().into();
writeln!(f, "{} {}:", self.index, stype)?;
write!(f, " ")?;
for b in self.id.as_ref() {
write!(f, "{b:02x}")?;
}
Ok(())
write!(f, " {}", self.id)
}
}
@@ -269,6 +318,11 @@ impl SecretList {
self.hdr.total_num_secrets.get() as usize
}
/// Find the first [`SecretEntry`] that has the provided [`SecretId`]
pub fn find(&self, id: &SecretId) -> Option<SecretEntry> {
self.iter().find(|e| e.id() == id.as_ref()).cloned()
}
/// Encodes the list in the same binary format the UV would do
pub fn encode<T: Write>(&self, w: &mut T) -> Result<()> {
w.write_all(self.hdr.as_bytes())?;
@@ -456,7 +510,7 @@ where
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")
formatter.write_str("a `32 bytes (=64 character) long hexstring` prepended with 0x")
}
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
@@ -464,7 +518,10 @@ where
E: serde::de::Error,
{
if s.len() != SecretId::ID_SIZE * 2 + "0x".len() {
return Err(serde::de::Error::invalid_length(s.len() - 2, &self));
return Err(serde::de::Error::invalid_length(
s.len().saturating_sub("0x".len()),
&self,
));
}
let nb = s.strip_prefix("0x").ok_or_else(|| {
serde::de::Error::invalid_value(serde::de::Unexpected::Str(s), &self)
@@ -655,4 +712,80 @@ mod test {
],
)
}
#[test]
fn secret_id_display() {
let text = "Fancy secret ID";
let id = SecretId::from_string(text);
let exp =
"Fancy secret ID | 0x46616e6379207365637265742049440000000000000000000000000000000000";
assert_eq!(id.to_string(), exp);
}
#[test]
fn secret_id_long_name() {
let text = "the most fanciest secret ID you ever seen in the time the universe exists";
let id = SecretId::from_string(text);
let exp =
"the most fanciest secret ID you | 0x746865206d6f73742066616e63696573742073656372657420494420796f7500";
assert_eq!(id.to_string(), exp);
}
#[test]
fn secret_id_no_ascii_name() {
let text = [0; 32];
let id = SecretId::from(text);
let exp = "0x0000000000000000000000000000000000000000000000000000000000000000";
assert_eq!(id.to_string(), exp);
}
#[test]
fn secret_id_no_ascii_name2() {
let text = [
0x25, 0x55, 3, 4, 50, 0, 6, 0, 8, 0, 0, 0, 0, 0, 0, 0, 90, 0, 0xa, 0, 0, 0, 0, 0xf, 0,
0, 0, 0, 0, 0, 0, 0,
];
let id = SecretId::from(text);
assert_eq!(id.as_ascii(), None);
}
#[test]
fn secret_id_no_ascii_name3() {
let text = [
0x25, 0x55, 0, 4, 50, 0, 6, 0, 8, 0, 0, 0, 0, 0, 0, 0, 90, 0, 0xa, 0, 0, 0, 0, 0xf, 0,
0, 0, 0, 0, 0, 0, 0,
];
let id = SecretId::from(text);
assert_eq!(id.as_ascii(), None);
}
#[test]
fn secret_id_hex() {
let id_str = "Nice Test 123";
let id = SecretId::from_string(id_str);
let s = format!("{id:#x}");
assert_eq!(
s,
"0x4e69636520546573742031323300000000000000000000000000000000000000"
);
let s = format!("{id:x}");
assert_eq!(
s,
"4e69636520546573742031323300000000000000000000000000000000000000"
);
let s = format!("{id:#X}");
assert_eq!(
s,
"0x4E69636520546573742031323300000000000000000000000000000000000000"
);
let s = format!("{id:X}");
assert_eq!(
s,
"4E69636520546573742031323300000000000000000000000000000000000000"
);
}
}
+8
View File
@@ -141,6 +141,12 @@ pub struct CreateSecretOpt {
/// by default.
#[arg(long, value_name = "FILE", value_hint = ValueHint::FilePath,)]
pub user_sign_key: Option<String>,
/// Do not hash the name, use it directly as secret ID.
///
/// Ignored for meta-secrets.
#[arg(long)]
pub use_name: bool,
}
#[derive(Subcommand, Debug)]
@@ -342,6 +348,8 @@ pub enum RetrInpFmt {
Yaml,
/// Use a hex string.
Hex,
/// Use a name-string. Will hash it if no secret with the name found.
Name,
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug, Default)]
+3 -1
View File
@@ -94,7 +94,7 @@ fn read_private_key(buf: &[u8]) -> Result<PKey<Private>> {
fn build_asrcb(opt: &CreateSecretOpt) -> Result<AddSecretRequest> {
debug!("Build add-secret request");
let secret = match &opt.secret {
let mut secret = match &opt.secret {
AddSecretType::Meta => GuestSecret::Null,
AddSecretType::Association {
name,
@@ -112,6 +112,8 @@ fn build_asrcb(opt: &CreateSecretOpt) -> Result<AddSecretRequest> {
};
trace!("AddSecret: {secret:x?}");
opt.use_name.then(|| secret.no_hash_name());
let mut flags = match &opt.pcf {
Some(v) => (&try_parse_u64(v, "pcf")?).into(),
None => AddSecretFlags::default(),
+10 -4
View File
@@ -17,12 +17,17 @@ use utils::get_writer_from_cli_file_arg;
fn retrieve(id: &SecretId) -> Result<RetrievedSecret> {
let uv = UvDevice::open()?;
let secrets = list_uvc(&uv)?;
let secret = secrets
.into_iter()
.find(|s| s.id() == id.as_ref())
let secret = match secrets.find(id) {
Some(s) => s,
// hash it + try again if it is ASCII-representable
None => match id.as_ascii() {
Some(s) => secrets.find(&GuestSecret::name_to_id(s)?),
None => None,
}
.ok_or(anyhow!(
"The UV secret-store has no secret with the ID {id}"
))?;
))?,
};
info!("Try to retrieve secret at index: {}", secret.index());
debug!("Try to retrieve: {secret:?}");
@@ -43,6 +48,7 @@ pub fn retr(opt: &RetrSecretOptions) -> Result<()> {
RetrInpFmt::Hex => {
serde_yaml::from_str(&opt.input).context("Cannot parse SecretId information")?
}
RetrInpFmt::Name => SecretId::from_string(&opt.input),
};
let retr_secret =