eidetica/account/
key_manager.rs1use std::collections::HashMap;
4use zeroize::{Zeroize, ZeroizeOnDrop};
5
6use super::{AccountError, AccountKey, KeyStorage, UnlockedCredentials};
7use crate::{
8 Result,
9 auth::crypto::{PrivateKey, PublicKey},
10};
11
12pub struct AccountKeyManager {
15 credentials: UnlockedCredentials,
16 key_metadata: HashMap<PublicKey, AccountKey>,
17}
18
19impl AccountKeyManager {
20 pub(crate) fn new(credentials: UnlockedCredentials, metadata: Vec<AccountKey>) -> Result<Self> {
21 let mut manager = Self {
22 credentials,
23 key_metadata: HashMap::new(),
24 };
25 for key in metadata {
26 manager.add_key(key)?;
27 }
28 if manager.key_metadata.len() != manager.credentials.key_ids().len() {
30 return Err(AccountError::InvalidLocalState.into());
31 }
32 Ok(manager)
33 }
34
35 pub fn get_signing_key(&self, key_id: &PublicKey) -> Option<&PrivateKey> {
36 self.credentials.get_signer(key_id)
37 }
38
39 pub fn add_key(&mut self, metadata: AccountKey) -> Result<()> {
41 if self.get_signing_key(&metadata.key_id).is_none() {
42 return Err(AccountError::KeyNotFound {
43 key_id: metadata.key_id.to_string(),
44 }
45 .into());
46 }
47 self.key_metadata.insert(metadata.key_id.clone(), metadata);
48 Ok(())
49 }
50
51 pub(crate) fn import_key(&mut self, key: PrivateKey, metadata: AccountKey) -> Result<()> {
52 self.credentials.add_key(key)?;
53 self.add_key(metadata)
54 }
55
56 pub(crate) fn unlock_storage(&self, storage: &KeyStorage) -> Result<UnlockedCredentials> {
57 self.credentials.unlock_storage(storage)
58 }
59
60 pub fn clear(&mut self) {
61 self.zeroize();
62 }
63
64 pub fn list_key_ids(&self) -> Vec<PublicKey> {
66 let mut keys: Vec<_> = self.key_metadata.values().collect();
67 keys.sort_by_key(|key| (key.created_at, key.key_id.to_string()));
68 keys.into_iter().map(|key| key.key_id.clone()).collect()
69 }
70
71 pub fn get_key_metadata(&self, key_id: &PublicKey) -> Option<&AccountKey> {
72 self.key_metadata.get(key_id)
73 }
74
75 pub fn get_default_key_id(&self) -> Option<PublicKey> {
76 self.list_key_ids()
77 .into_iter()
78 .find(|id| self.key_metadata[id].is_default)
79 .or_else(|| self.list_key_ids().into_iter().next())
80 }
81}
82
83impl Zeroize for AccountKeyManager {
84 fn zeroize(&mut self) {
85 self.credentials.clear();
86 self.key_metadata.clear();
87 }
88}
89impl Drop for AccountKeyManager {
90 fn drop(&mut self) {
91 self.zeroize();
92 }
93}
94impl ZeroizeOnDrop for AccountKeyManager {}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99 use crate::{ID, account::CredentialBundle, auth::crypto::generate_keypair};
100
101 fn metadata(key_id: PublicKey, created_at: i64, is_default: bool) -> AccountKey {
102 AccountKey {
103 key_id,
104 created_at,
105 is_default,
106 display_name: None,
107 last_used: None,
108 database_sigkeys: HashMap::new(),
109 }
110 }
111
112 #[test]
113 fn key_manager_holds_exact_bundle_keys_and_metadata() {
114 let (first, a) = generate_keypair();
115 let (second, b) = generate_keypair();
116 let account = ID::from_bytes("account");
117 let bundle = CredentialBundle::seal(&account, &[first, second], "password").unwrap();
118 let mut manager = AccountKeyManager::new(
119 bundle.unlock("password").unwrap(),
120 vec![metadata(a.clone(), 2, true), metadata(b.clone(), 1, false)],
121 )
122 .unwrap();
123 assert_eq!(manager.list_key_ids(), [b.clone(), a.clone()]);
124 assert_eq!(manager.get_default_key_id(), Some(a.clone()));
125 let message = b"actual-key";
126 b.verify(message, &manager.get_signing_key(&b).unwrap().sign(message))
127 .unwrap();
128 assert!(
129 manager
130 .add_key(metadata(generate_keypair().1, 3, false))
131 .is_err()
132 );
133 let mut updated = manager.get_key_metadata(&b).unwrap().clone();
134 updated.display_name = Some("new label".to_owned());
135 manager.add_key(updated).unwrap();
136 assert_eq!(
137 manager
138 .get_key_metadata(&b)
139 .unwrap()
140 .display_name
141 .as_deref(),
142 Some("new label")
143 );
144 let storage = manager.credentials.storage().unwrap();
145 assert_eq!(
146 manager.unlock_storage(&storage).unwrap().key_ids(),
147 manager.credentials.key_ids()
148 );
149 manager.clear();
150 assert!(manager.get_signing_key(&a).is_none());
151 assert!(manager.list_key_ids().is_empty());
152 assert!(manager.get_default_key_id().is_none());
153 }
154
155 #[test]
156 fn key_manager_rejects_incomplete_or_foreign_metadata() {
157 let (key, id) = generate_keypair();
158 let bundle =
159 CredentialBundle::seal(&ID::from_bytes("account"), &[key], "password").unwrap();
160 assert!(AccountKeyManager::new(bundle.unlock("password").unwrap(), vec![]).is_err());
161 assert!(
162 AccountKeyManager::new(
163 bundle.unlock("password").unwrap(),
164 vec![metadata(generate_keypair().1, 0, true)]
165 )
166 .is_err()
167 );
168 let mut manager = AccountKeyManager::new(
169 bundle.unlock("password").unwrap(),
170 vec![metadata(id.clone(), 0, false)],
171 )
172 .unwrap();
173 assert_eq!(manager.get_default_key_id(), Some(id));
174 let rewrapped = bundle
175 .unlock("password")
176 .unwrap()
177 .rewrap("new-password")
178 .unwrap();
179 assert!(
180 manager
181 .unlock_storage(&KeyStorage::Encrypted { bundle: rewrapped })
182 .is_err()
183 );
184 manager.clear();
185 }
186}