mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
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:
committed by
Jan Höppner
parent
93216d916c
commit
ff04f76257
@@ -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 {
|
||||
|
||||
@@ -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"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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 =
|
||||
|
||||
Reference in New Issue
Block a user