mod page;
pub use corundum_framework_keys::{KeyId, KeyInfo, KeyStore, Token};
pub use page::KeysPage;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum KeysRequest {
List,
Add {
name: String,
description: Option<String>,
},
Retire(KeyId),
Restore(KeyId),
Rotate(KeyId),
}
#[derive(Clone, Debug)]
pub enum KeysAnswer {
List(Vec<KeyInfo>),
Changed(KeyInfo),
WithToken(KeyInfo, Token),
Failed(String),
}
pub trait KeysBackend {
fn send(&mut self, request: KeysRequest);
fn poll(&mut self) -> Vec<KeysAnswer>;
}
pub struct LocalKeys {
store: KeyStore,
answers: Vec<KeysAnswer>,
}
impl LocalKeys {
pub fn new(store: KeyStore) -> LocalKeys {
LocalKeys {
store,
answers: Vec::new(),
}
}
}
impl KeysBackend for LocalKeys {
fn send(&mut self, request: KeysRequest) {
let store = &mut self.store;
let answer = match request {
KeysRequest::List => {
match KeyStore::open(store.dir()) {
Ok(fresh) => Ok(KeysAnswer::List(
fresh.list().iter().map(|k| k.info()).collect(),
)),
Err(err) => Err(err),
}
}
KeysRequest::Add { name, description } => store
.add(&name, description, None)
.map(|(k, t)| KeysAnswer::WithToken(k.info(), t)),
KeysRequest::Retire(id) => store
.retire(id.as_str())
.map(|k| KeysAnswer::Changed(k.info())),
KeysRequest::Restore(id) => store
.restore(id.as_str())
.map(|k| KeysAnswer::Changed(k.info())),
KeysRequest::Rotate(id) => store
.rotate(id.as_str())
.map(|(k, t)| KeysAnswer::WithToken(k.info(), t)),
};
self.answers
.push(answer.unwrap_or_else(|err| KeysAnswer::Failed(err.to_string())));
}
fn poll(&mut self) -> Vec<KeysAnswer> {
std::mem::take(&mut self.answers)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn local_keys_round_trip() {
let dir = tempfile::tempdir().unwrap();
let mut b = LocalKeys::new(KeyStore::open(dir.path()).unwrap());
b.send(KeysRequest::Add {
name: "p".into(),
description: None,
});
let Ok::<[KeysAnswer; 1], _>([KeysAnswer::WithToken(info, token)]) = b.poll().try_into()
else {
panic!("not a token");
};
assert_eq!(info.name, "p");
let fresh = KeyStore::open(dir.path()).unwrap();
assert!(fresh.authenticate(token.expose()).is_some());
b.send(KeysRequest::Retire(info.id.clone()));
b.send(KeysRequest::List);
let answers = b.poll();
assert!(matches!(&answers[0], KeysAnswer::Changed(k) if k.is_retired()));
assert!(matches!(&answers[1], KeysAnswer::List(l) if l.len() == 1));
}
#[test]
fn local_keys_reports_failures() {
let dir = tempfile::tempdir().unwrap();
let mut b = LocalKeys::new(KeyStore::open(dir.path()).unwrap());
b.send(KeysRequest::Retire(KeyId::new("nope").unwrap()));
assert!(matches!(b.poll().as_slice(), [KeysAnswer::Failed(_)]));
}
}