use super::local_key::LocalKey;
use crate::{
crypto::{alg::AnyKey, alg::KeyAlg, buffer::SecretBytes, jwk::FromJwk},
entry::{Entry, EntryTag},
error::Error,
};
use std::str::FromStr;
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub enum KeyReference {
MobileSecureElement,
Any(String),
}
impl From<&str> for KeyReference {
fn from(value: &str) -> Self {
match value {
"mobile_secure_element" => Self::MobileSecureElement,
any => Self::Any(String::from(any)),
}
}
}
impl From<KeyReference> for String {
fn from(key_reference: KeyReference) -> Self {
match key_reference {
KeyReference::MobileSecureElement => String::from("mobile_secure_element"),
KeyReference::Any(s) => s,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub struct KeyParams {
#[serde(default, rename = "meta", skip_serializing_if = "Option::is_none")]
pub metadata: Option<String>,
#[serde(default, rename = "ref", skip_serializing_if = "Option::is_none")]
pub reference: Option<KeyReference>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub data: Option<SecretBytes>,
}
impl KeyParams {
pub(crate) fn to_bytes(&self) -> Result<SecretBytes, Error> {
let mut bytes = Vec::new();
ciborium::into_writer(self, &mut bytes)
.map_err(|e| err_msg!(Unexpected, "Error serializing key params: {}", e))?;
Ok(SecretBytes::from(bytes))
}
pub(crate) fn to_id(&self) -> Result<String, Error> {
self.data
.as_ref()
.and_then(|d| d.as_opt_str().map(ToOwned::to_owned))
.ok_or(err_msg!(
Input,
"Could not convert key data to string for id"
))
}
pub(crate) fn from_slice(params: &[u8]) -> Result<KeyParams, Error> {
ciborium::from_reader(params)
.map_err(|e| err_msg!(Unexpected, "Error deserializing key params: {}", e))
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct KeyEntry {
pub(crate) name: String,
pub(crate) params: KeyParams,
pub(crate) alg: Option<String>,
pub(crate) thumbprints: Vec<String>,
pub(crate) tags: Vec<EntryTag>,
}
impl KeyEntry {
pub fn algorithm(&self) -> Option<&str> {
self.alg.as_ref().map(String::as_ref)
}
pub fn metadata(&self) -> Option<&str> {
self.params.metadata.as_ref().map(String::as_ref)
}
pub fn name(&self) -> &str {
self.name.as_str()
}
pub fn tags_as_slice(&self) -> &[EntryTag] {
self.tags.as_slice()
}
pub fn is_local(&self) -> bool {
self.params.reference.is_none()
}
pub(crate) fn from_entry(entry: Entry) -> Result<Self, Error> {
let params = KeyParams::from_slice(&entry.value)?;
let mut alg = None;
let mut thumbprints = Vec::new();
let mut tags = entry.tags;
let mut idx = 0;
while idx < tags.len() {
let tag = &mut tags[idx];
let name = tag.name();
if name.starts_with("user:") {
tag.update_name(|tag| tag.replace_range(0..5, ""));
idx += 1;
} else if name == "alg" {
alg.replace(tags.remove(idx).into_value());
} else if name == "thumb" {
thumbprints.push(tags.remove(idx).into_value());
} else {
tags.remove(idx).into_value();
}
}
thumbprints.sort();
tags.sort();
Ok(Self {
name: entry.name,
params,
alg,
thumbprints,
tags,
})
}
pub fn load_local_key(&self) -> Result<LocalKey, Error> {
if let Some(key_data) = self.params.data.as_ref() {
match &self.params.reference {
Some(KeyReference::MobileSecureElement) => {
let id = self.params.to_id()?;
let alg = self
.alg
.as_ref()
.ok_or(err_msg!(Input, "Algorithm is required to get key by id"))?;
let alg = KeyAlg::from_str(alg)?;
Ok(LocalKey::from_id(alg, &id)?)
}
_ => Ok(LocalKey {
inner: Box::<AnyKey>::from_jwk_slice(key_data.as_ref())?,
ephemeral: false,
}),
}
} else {
Err(err_msg!("Missing key data"))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn key_params_roundtrip() {
let params = KeyParams {
metadata: Some("meta".to_string()),
reference: None,
data: Some(SecretBytes::from(vec![0, 0, 0, 0])),
};
let enc_params = params.to_bytes().unwrap();
let p2 = KeyParams::from_slice(&enc_params).unwrap();
assert_eq!(p2, params);
}
}