1use super::*;
2use crate::custody::block_store::BlockStore;
3use crate::custody::block_store_test_utils::MemoryBlockStore;
4use crate::custody::person_entries::restore_code_store;
5use crate::custody::KeychainError;
6use std::collections::BTreeMap;
7use std::path::Path;
8use zeroize::Zeroizing;
9
10const SAVED_DEVICE_ENTRIES_MAGIC: &[u8] = b"CVDE\x01";
13
14#[derive(Clone)]
19pub(super) struct SyncedEntries(Arc<Mutex<SyncedGroups>>);
20
21impl SyncedEntries {
22 fn groups(&self) -> std::sync::MutexGuard<'_, SyncedGroups> {
23 self.0.lock().expect("in-memory synced keychain entries lock is poisoned")
24 }
25}
26
27#[derive(Default)]
32pub(super) struct SyncedGroups {
33 default: BTreeMap<String, SecretBytes>,
34 another: BTreeMap<String, SecretBytes>,
35}
36
37impl SyncedGroups {
38 fn read(&self, account: &str) -> Option<SecretBytes> {
39 self.copies(account).into_iter().next()
40 }
41
42 fn write(&mut self, account: &str, bytes: &[u8]) {
43 if self.default.contains_key(account) {
44 if let Some(copy) = self.another.get_mut(account) {
45 *copy = SecretBytes::new(bytes.to_vec());
46 }
47 }
48 self.default.insert(account.to_owned(), SecretBytes::new(bytes.to_vec()));
49 }
50
51 fn delete(&mut self, account: &str) {
52 self.default.remove(account);
53 self.another.remove(account);
54 }
55
56 fn copies(&self, account: &str) -> Vec<SecretBytes> {
57 [&self.default, &self.another]
58 .into_iter()
59 .filter_map(|group| group.get(account))
60 .map(|bytes| SecretBytes::new(bytes.as_bytes().to_vec()))
61 .collect()
62 }
63
64 fn replace(&mut self, account: &str, bytes: &[u8]) {
65 let bytes = SecretBytes::new(bytes.to_vec());
66 if self.default.contains_key(account) || !self.another.contains_key(account) {
67 self.default.insert(account.to_owned(), bytes);
68 self.another.remove(account);
69 } else {
70 self.another.insert(account.to_owned(), bytes);
71 }
72 }
73
74 fn all(&self) -> Vec<(String, SecretBytes)> {
75 [&self.default, &self.another]
76 .into_iter()
77 .flatten()
78 .map(|(account, bytes)| (account.clone(), SecretBytes::new(bytes.as_bytes().to_vec())))
79 .collect()
80 }
81}
82
83pub(super) enum MemoryPerson {
85 ICloud(SyncedEntries),
87 BlockStore(BlockStore<MemoryBlockStore>),
89}
90
91pub(super) struct MemoryEntries {
92 device: BTreeMap<String, SecretBytes>,
94 person: MemoryPerson,
96 fail_after: std::collections::VecDeque<usize>,
97 refuse_person: bool,
99 panic_next: bool,
102}
103
104fn panic_if_asked(backend: &Mutex<MemoryEntries>) {
108 let asked = std::mem::take(
109 &mut backend.lock().expect("in-memory keychain entries lock is poisoned").panic_next,
110 );
111 if asked {
112 panic!("the in-memory keychain's operation panicked");
113 }
114}
115
116impl MemoryEntries {
117 fn check(&mut self, scope: EntryScope) -> Result<(), KeyError> {
118 let person = scope == EntryScope::Person && self.refuse_person;
119 if person || self.fail_after.front() == Some(&0) {
120 if !person {
121 self.fail_after.pop_front();
122 }
123 return Err(KeychainError::from(keyring_core::Error::NoStorageAccess(Box::new(
124 std::io::Error::new(
125 std::io::ErrorKind::PermissionDenied,
126 "in-memory keychain refused the operation",
127 ),
128 )))
129 .into());
130 }
131 if let Some(remaining) = self.fail_after.front_mut() {
132 *remaining -= 1;
133 }
134 Ok(())
135 }
136
137 fn change<T>(
139 &mut self,
140 scope: EntryScope,
141 device: impl FnOnce(&mut BTreeMap<String, SecretBytes>) -> T,
142 person: impl FnOnce(&mut MemoryPerson) -> Result<T, KeyError>,
143 ) -> Result<T, KeyError> {
144 self.check(scope)?;
145 match scope {
146 EntryScope::DeviceOnly => Ok(device(&mut self.device)),
147 EntryScope::Person => person(&mut self.person),
148 }
149 }
150
151 fn person<T>(
152 &mut self,
153 change: impl FnOnce(&mut MemoryPerson) -> Result<T, KeyError>,
154 ) -> Result<T, KeyError> {
155 self.check(EntryScope::Person)?;
156 change(&mut self.person)
157 }
158}
159
160impl KeychainBackend for Mutex<MemoryEntries> {
161 fn supports_person_entries(&self) -> bool {
162 true
163 }
164
165 fn person_backup_follows_writes(&self) -> bool {
166 matches!(
167 self.lock().expect("in-memory keychain entries lock is poisoned").person,
168 MemoryPerson::BlockStore(_)
169 )
170 }
171
172 fn person_value_limit(&self, service: &str, account: &str) -> Option<usize> {
173 match &self.lock().expect("in-memory keychain entries lock is poisoned").person {
174 MemoryPerson::ICloud(_) => None,
175 MemoryPerson::BlockStore(_) => {
176 Some(BlockStore::<MemoryBlockStore>::value_limit(service, account))
177 }
178 }
179 }
180
181 fn read(
182 &self,
183 scope: EntryScope,
184 service: &str,
185 account: &str,
186 ) -> Result<Option<SecretBytes>, KeyError> {
187 panic_if_asked(self);
188 self.lock().expect("in-memory keychain entries lock is poisoned").change(
189 scope,
190 |entries| entries.get(account).map(|bytes| SecretBytes::new(bytes.as_bytes().to_vec())),
191 |person| match person {
192 MemoryPerson::ICloud(entries) => Ok(entries.groups().read(account)),
193 MemoryPerson::BlockStore(store) => store.read(service, account),
194 },
195 )
196 }
197
198 fn write(
199 &self,
200 scope: EntryScope,
201 service: &str,
202 account: &str,
203 bytes: &[u8],
204 ) -> Result<(), KeyError> {
205 panic_if_asked(self);
206 self.lock().expect("in-memory keychain entries lock is poisoned").change(
207 scope,
208 |entries| {
209 entries.insert(account.to_owned(), SecretBytes::new(bytes.to_vec()));
210 },
211 |person| match person {
212 MemoryPerson::ICloud(entries) => {
213 entries.groups().write(account, bytes);
214 Ok(())
215 }
216 MemoryPerson::BlockStore(store) => store.write(service, account, bytes),
217 },
218 )
219 }
220
221 fn delete(&self, scope: EntryScope, service: &str, account: &str) -> Result<(), KeyError> {
222 panic_if_asked(self);
223 self.lock().expect("in-memory keychain entries lock is poisoned").change(
224 scope,
225 |entries| {
226 entries.remove(account);
227 },
228 |person| match person {
229 MemoryPerson::ICloud(entries) => {
230 entries.groups().delete(account);
231 Ok(())
232 }
233 MemoryPerson::BlockStore(store) => store.delete(service, account),
234 },
235 )
236 }
237
238 fn person_restore_codes(&self, service: &str) -> Result<Vec<(StoreId, SecretBytes)>, KeyError> {
239 panic_if_asked(self);
240 self.lock().expect("in-memory keychain entries lock is poisoned").person(|person| {
241 match person {
242 MemoryPerson::ICloud(entries) => {
243 let mut codes = Vec::new();
244 for (account, bytes) in entries.groups().all() {
245 if let Some(store) = restore_code_store(&account)? {
246 codes.push((store, bytes));
247 }
248 }
249 Ok(codes)
250 }
251 MemoryPerson::BlockStore(store) => store.restore_codes(service),
252 }
253 })
254 }
255
256 fn person_copies(&self, service: &str, account: &str) -> Result<Vec<SecretBytes>, KeyError> {
257 panic_if_asked(self);
258 self.lock().expect("in-memory keychain entries lock is poisoned").person(|person| {
259 match person {
260 MemoryPerson::ICloud(entries) => Ok(entries.groups().copies(account)),
261 MemoryPerson::BlockStore(store) => {
262 Ok(store.read(service, account)?.into_iter().collect())
263 }
264 }
265 })
266 }
267
268 fn replace_person_entry(
269 &self,
270 service: &str,
271 account: &str,
272 bytes: &[u8],
273 ) -> Result<(), KeyError> {
274 panic_if_asked(self);
275 self.lock().expect("in-memory keychain entries lock is poisoned").person(|person| {
276 match person {
277 MemoryPerson::ICloud(entries) => {
278 entries.groups().replace(account, bytes);
279 Ok(())
280 }
281 MemoryPerson::BlockStore(store) => store.write(service, account, bytes),
282 }
283 })
284 }
285}
286
287impl Keychain {
288 pub fn in_memory(name: impl Into<String>) -> Result<Arc<Self>, KeyError> {
292 let entries = SyncedEntries(Arc::new(Mutex::new(SyncedGroups::default())));
293 Self::memory(name.into(), MemoryPerson::ICloud(entries))
294 }
295
296 pub fn in_memory_block_store(name: impl Into<String>) -> Result<Arc<Self>, KeyError> {
300 Self::memory(
301 name.into(),
302 MemoryPerson::BlockStore(BlockStore::new(MemoryBlockStore::new())),
303 )
304 }
305
306 pub fn in_memory_with_saved_device_entries(
312 name: impl Into<String>,
313 path: &Path,
314 ) -> Result<Arc<Self>, KeyError> {
315 let bytes = Zeroizing::new(coven_foundation::files::read_existing(path)?);
316 let device = decode_device_entries(&bytes)?;
317 let keychain = Self::in_memory(name)?;
318 keychain.with_memory(|memory| memory.device = device);
319 Ok(keychain)
320 }
321
322 pub fn save_device_entries(&self, path: &Path) -> Result<(), KeyError> {
327 let bytes = self.with_memory(|memory| encode_device_entries(&memory.device));
328 Ok(coven_foundation::files::write_atomic(path, &bytes)?)
329 }
330
331 pub fn another_device(&self) -> Arc<Self> {
337 let person = match self.synced_entries() {
338 Some(entries) => MemoryPerson::ICloud(entries),
339 None => MemoryPerson::BlockStore(BlockStore::new(MemoryBlockStore::new())),
340 };
341 Self::memory(self.name.clone(), person).expect("registered service name is valid")
342 }
343
344 fn synced_entries(&self) -> Option<SyncedEntries> {
348 self.with_memory(|memory| match &memory.person {
349 MemoryPerson::ICloud(entries) => Some(entries.clone()),
350 MemoryPerson::BlockStore(_) => None,
351 })
352 }
353
354 pub fn set_up_new_device(&self) -> Arc<Self> {
358 let store = self.memory_block_store(|store| store.set_up_new_phone());
359 Self::memory(self.name.clone(), MemoryPerson::BlockStore(BlockStore::new(store)))
360 .expect("registered service name is valid")
361 }
362
363 pub fn restore_new_device_from_cloud(&self) -> Arc<Self> {
367 let store = self.memory_block_store(|store| store.restore_new_phone_from_cloud());
368 Self::memory(self.name.clone(), MemoryPerson::BlockStore(BlockStore::new(store)))
369 .expect("registered service name is valid")
370 }
371
372 pub fn back_up_to_cloud(&self) {
375 self.memory_block_store(MemoryBlockStore::back_up_to_cloud);
376 }
377
378 pub fn set_end_to_end_encryption(&self, available: bool) {
381 self.memory_block_store(|store| store.set_end_to_end_encryption(available));
382 }
383
384 fn memory_block_store<T>(&self, f: impl FnOnce(&MemoryBlockStore) -> T) -> T {
385 self.with_memory(|memory| match &memory.person {
386 MemoryPerson::BlockStore(store) => f(store.api()),
387 MemoryPerson::ICloud(_) => panic!("this in-memory keychain keeps no Block Store"),
388 })
389 }
390
391 fn memory(name: String, person: MemoryPerson) -> Result<Arc<Self>, KeyError> {
392 validate_service(&name)?;
393 Ok(Arc::new(Self {
394 name,
395 backend: Box::new(Mutex::new(MemoryEntries {
396 device: BTreeMap::new(),
397 person,
398 fail_after: std::collections::VecDeque::new(),
399 refuse_person: false,
400 panic_next: false,
401 })),
402 host_secrets: Mutex::new(()),
403 }))
404 }
405
406 fn with_memory<T>(&self, change: impl FnOnce(&mut MemoryEntries) -> T) -> T {
409 let backend: &dyn std::any::Any = self.backend.as_ref();
410 let memory: &Mutex<MemoryEntries> =
411 backend.downcast_ref().expect("failure injection requires an in-memory keychain");
412 change(&mut memory.lock().expect("in-memory keychain entries lock is poisoned"))
413 }
414
415 #[cfg(test)]
418 pub(super) fn with_device_entries<T>(
419 &self,
420 change: impl FnOnce(&mut BTreeMap<String, SecretBytes>) -> T,
421 ) -> T {
422 self.with_memory(|memory| change(&mut memory.device))
423 }
424
425 pub fn fail_next_operation(&self) {
428 self.fail_operations_after(&[0]);
429 }
430
431 pub fn panic_next_operation(&self) {
435 self.with_memory(|memory| memory.panic_next = true);
436 }
437
438 pub fn with_operations_held<T>(&self, run: impl FnOnce() -> T) -> T {
442 self.with_memory(|_entries| run())
443 }
444
445 pub fn fail_operations_after(&self, successes: &[usize]) {
449 self.with_memory(|memory| memory.fail_after = successes.iter().copied().collect());
450 }
451
452 pub(crate) fn write_in_another_group(&self, account: &str, bytes: &[u8]) {
456 self.with_memory(|memory| match &memory.person {
457 MemoryPerson::ICloud(entries) => {
458 entries
459 .groups()
460 .another
461 .insert(account.to_owned(), SecretBytes::new(bytes.to_vec()));
462 }
463 MemoryPerson::BlockStore(_) => panic!("Block Store has no keychain groups"),
464 });
465 }
466
467 pub fn refuse_person_entry_operations(&self, refuse: bool) {
471 self.with_memory(|memory| memory.refuse_person = refuse);
472 }
473}
474
475fn encode_device_entries(entries: &BTreeMap<String, SecretBytes>) -> Zeroizing<Vec<u8>> {
478 let fields = entries.iter().flat_map(|(account, bytes)| [account.as_bytes(), bytes.as_bytes()]);
479 let length = fields.clone().map(|field| 8 + field.len()).sum::<usize>();
481 let mut encoded =
482 Zeroizing::new(Vec::with_capacity(SAVED_DEVICE_ENTRIES_MAGIC.len() + 8 + length));
483 encoded.extend_from_slice(SAVED_DEVICE_ENTRIES_MAGIC);
484 encoded.extend_from_slice(&(entries.len() as u64).to_le_bytes());
485 for field in fields {
486 encoded.extend_from_slice(&(field.len() as u64).to_le_bytes());
487 encoded.extend_from_slice(field);
488 }
489 encoded
490}
491
492fn decode_device_entries(
494 mut encoded: &[u8],
495) -> Result<BTreeMap<String, SecretBytes>, MaterialError> {
496 fn field<'a>(encoded: &mut &'a [u8]) -> Result<&'a [u8], MaterialError> {
497 let length = usize::try_from(wire::number(encoded)?).or(Err(MaterialError::Encoding))?;
498 wire::take(encoded, length)
499 }
500 wire::prefix(&mut encoded, SAVED_DEVICE_ENTRIES_MAGIC)?;
501 let mut entries = BTreeMap::new();
502 for _ in 0..wire::number(&mut encoded)? {
503 let account =
504 String::from_utf8(field(&mut encoded)?.to_vec()).or(Err(MaterialError::Encoding))?;
505 let bytes = SecretBytes::new(field(&mut encoded)?.to_vec());
506 if entries.insert(account, bytes).is_some() {
507 return Err(MaterialError::Encoding);
508 }
509 }
510 wire::end(encoded)?;
511 Ok(entries)
512}