Skip to main content

hatchery_keystore/
keyring.rs

1//! Many humans, one keystore host: one sealed branch per account, each unlocked only by that
2//! account's passkey-derived secret ([`tesserax_keytree::MultiRoot`]).
3
4use std::collections::BTreeSet;
5use std::path::PathBuf;
6
7use tesserax_keytree as kt;
8
9use crate::{HumanKey, Provisioned, Signature, Signer, SignerError};
10
11pub struct Keyring {
12    dir: PathBuf,
13    accounts: kt::MultiRoot<Signer>,
14}
15
16impl Keyring {
17    pub fn new(dir: impl Into<PathBuf>) -> Self {
18        Self { dir: dir.into(), accounts: kt::MultiRoot::new() }
19    }
20
21    fn path(&self, account: &str) -> Result<PathBuf, SignerError> {
22        let id = kt::branch_id(account).map_err(|_| SignerError::Denied("bad account id".into()))?;
23        Ok(self.dir.join(format!("{id}.sealed")))
24    }
25
26    pub fn enroll(&mut self, account: &str, human: &HumanKey) -> Result<(), SignerError> {
27        let mut s = Signer::init(self.path(account)?, human)?;
28        s.set_account(account)?;
29        self.accounts.insert(account, s).map_err(|_| SignerError::Denied("bad account id".into()))
30    }
31
32    pub fn unlock(&mut self, account: &str, human: &HumanKey) -> Result<(), SignerError> {
33        let mut s = Signer::open(self.path(account)?);
34        s.unlock(human)?;
35        if s.account()? != account {
36            return Err(SignerError::WrongHumanKey);
37        }
38        self.accounts.insert(account, s).map_err(|_| SignerError::Denied("bad account id".into()))
39    }
40
41    /// Whether `account` already has a sealed branch on disk.
42    pub fn exists(&self, account: &str) -> bool {
43        self.path(account).map(|p| p.exists()).unwrap_or(false)
44    }
45
46    /// Lock every other unlocked account that holds one of `account`'s nodes, so that node's
47    /// sessions get their identities from `account` alone. Returns the accounts locked.
48    pub fn take_nodes(&mut self, account: &str, nodes: &[String]) -> Vec<String> {
49        let others: Vec<String> = self
50            .accounts
51            .iter()
52            .filter(|(a, s)| a.as_str() != account && nodes.iter().any(|n| s.has_node(n)))
53            .map(|(a, _)| a.clone())
54            .collect();
55        for a in &others {
56            self.accounts.remove(a);
57        }
58        others
59    }
60
61    pub fn lock(&mut self, account: &str) {
62        self.accounts.remove(account);
63    }
64
65    pub fn is_unlocked(&self, account: &str) -> bool {
66        self.accounts.get(account).is_some_and(Signer::is_unlocked)
67    }
68
69    pub fn branch(&mut self, account: &str) -> Result<&mut Signer, SignerError> {
70        self.accounts.get_mut(account).ok_or(SignerError::Locked)
71    }
72
73    /// Account named inside a token (unverified, routing only).
74    pub fn account_of(token: &str) -> Result<String, SignerError> {
75        kt::unverified_fact(token, "account").map_err(|_| SignerError::BadToken)
76    }
77
78    pub fn account_for_node(&self, node: &str) -> Option<String> {
79        self.accounts.iter().find(|(_, s)| s.has_node(node)).map(|(a, _)| a.clone())
80    }
81
82    pub fn sign_from_node(&self, node: &str, token: &str, op: &str, payload: &[u8]) -> Result<Signature, SignerError> {
83        let account = Self::account_of(token)?;
84        self.accounts.get(&account).ok_or(SignerError::Locked)?.sign_from_node(node, token, op, payload)
85    }
86
87    pub fn sign(&self, token: &str, op: &str, payload: &[u8]) -> Result<Signature, SignerError> {
88        let account = Self::account_of(token)?;
89        self.accounts.get(&account).ok_or(SignerError::Locked)?.sign(token, op, payload)
90    }
91
92    pub fn m4a_vault_key(&self, node: &str, token: &str) -> Result<zeroize::Zeroizing<[u8; 32]>, SignerError> {
93        let account = Self::account_of(token)?;
94        self.accounts.get(&account).ok_or(SignerError::Locked)?.m4a_vault_key(node, token)
95    }
96
97    pub fn sign_m4a_keyproof(&self, node: &str, token: &str, message: &[u8]) -> Result<Signature, SignerError> {
98        let account = Self::account_of(token)?;
99        self.accounts.get(&account).ok_or(SignerError::Locked)?.sign_m4a_keyproof(node, token, message)
100    }
101
102    pub fn delegate(&self, token: &str, transport: &str) -> Result<String, SignerError> {
103        let account = Self::account_of(token)?;
104        self.accounts.get(&account).ok_or(SignerError::Locked)?.delegate(token, transport)
105    }
106
107    pub fn provision(&mut self, account: &str, node: &str, provider: &str, session: &str, rights: BTreeSet<String>, human: Option<&HumanKey>) -> Result<Provisioned, SignerError> {
108        self.branch(account)?.provision(node, provider, session, rights, human)
109    }
110
111    pub fn public_key_of(&self, account: &str, session: &str) -> Result<[u8; 32], SignerError> {
112        self.accounts.get(account).ok_or(SignerError::Locked)?.public_key_of(session)
113    }
114}