use std::collections::BTreeMap;
use std::fmt;
use std::sync::Mutex;
use zeroize::Zeroizing;
use super::{MAX_KEY_LEN, StoreError};
mod os;
pub(super) use os::OsKeyStore;
pub const SERVICE: &str = "io.qso-graph.trust";
pub const TEST_SERVICE: &str = "io.qso-graph.trust.test";
pub const MAX_KEYS: usize = 8192;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum OrphanDetection {
#[default]
Available,
Unavailable,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum KeyStoreError {
Unavailable,
AccessDenied,
AlreadyExists,
InvalidInput,
EnumerationUnavailable,
}
impl fmt::Display for KeyStoreError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::Unavailable => "unavailable",
Self::AccessDenied => "access denied",
Self::AlreadyExists => "entry already exists",
Self::InvalidInput => "invalid entry or too many entries",
Self::EnumerationUnavailable => "credential enumeration is unavailable",
})
}
}
impl std::error::Error for KeyStoreError {}
pub(crate) trait KeyStore: Send + Sync {
fn orphan_detection(&self) -> OrphanDetection {
OrphanDetection::Available
}
fn put(&self, name: &str, der: &[u8]) -> Result<(), KeyStoreError>;
fn get(&self, name: &str) -> Result<Option<Zeroizing<Vec<u8>>>, KeyStoreError>;
fn delete(&self, name: &str) -> Result<(), KeyStoreError>;
fn exists(&self, name: &str) -> Result<bool, KeyStoreError>;
fn names(&self) -> Result<Vec<String>, KeyStoreError>;
}
#[derive(Default)]
pub(super) struct MemoryKeyStore {
secrets: Mutex<BTreeMap<String, Zeroizing<Vec<u8>>>>,
}
impl MemoryKeyStore {
pub(super) fn new() -> Self {
Self::default()
}
}
impl KeyStore for MemoryKeyStore {
fn put(&self, name: &str, der: &[u8]) -> Result<(), KeyStoreError> {
if !valid_name(name, "key:") || der.is_empty() || der.len() > MAX_KEY_LEN {
return Err(KeyStoreError::InvalidInput);
}
let mut secrets = self
.secrets
.lock()
.map_err(|_| KeyStoreError::Unavailable)?;
if secrets.contains_key(name) {
return Err(KeyStoreError::AlreadyExists);
}
if secrets.len() >= MAX_KEYS {
return Err(KeyStoreError::InvalidInput);
}
secrets.insert(name.into(), Zeroizing::new(der.to_vec()));
Ok(())
}
fn get(&self, name: &str) -> Result<Option<Zeroizing<Vec<u8>>>, KeyStoreError> {
Ok(self
.secrets
.lock()
.map_err(|_| KeyStoreError::Unavailable)?
.get(name)
.cloned())
}
fn delete(&self, name: &str) -> Result<(), KeyStoreError> {
self.secrets
.lock()
.map_err(|_| KeyStoreError::Unavailable)?
.remove(name);
Ok(())
}
fn exists(&self, name: &str) -> Result<bool, KeyStoreError> {
Ok(self
.secrets
.lock()
.map_err(|_| KeyStoreError::Unavailable)?
.contains_key(name))
}
fn names(&self) -> Result<Vec<String>, KeyStoreError> {
Ok(self
.secrets
.lock()
.map_err(|_| KeyStoreError::Unavailable)?
.keys()
.cloned()
.collect())
}
}
pub(super) fn new_name(prefix: &str) -> Result<String, StoreError> {
let mut bytes = [0u8; 16];
qso_graph_trust_awslc::random(&mut bytes).map_err(|_| StoreError::Random)?;
bytes[6] = (bytes[6] & 0x0f) | 0x40;
bytes[8] = (bytes[8] & 0x3f) | 0x80;
let mut name = String::from(prefix);
const HEX: &[u8; 16] = b"0123456789abcdef";
for (i, byte) in bytes.into_iter().enumerate() {
if matches!(i, 4 | 6 | 8 | 10) {
name.push('-');
}
name.push(char::from(HEX[usize::from(byte >> 4)]));
name.push(char::from(HEX[usize::from(byte & 0x0f)]));
}
Ok(name)
}
pub(super) fn valid_name(name: &str, prefix: &str) -> bool {
let Some(uuid) = name.strip_prefix(prefix) else {
return false;
};
let b = uuid.as_bytes();
b.len() == 36
&& b[14] == b'4'
&& matches!(b[19], b'8' | b'9' | b'a' | b'b')
&& b.iter().enumerate().all(|(i, c)| {
if matches!(i, 8 | 13 | 18 | 23) {
*c == b'-'
} else {
c.is_ascii_digit() || (b'a'..=b'f').contains(c)
}
})
}