Skip to main content

coven_crypto/custody/
mod.rs

1//! Where this device keeps its opened keys and its member's two key pairs (E1).
2
3use crate::{MaterialError, MemberIdentity, SecretBytes, StoreKeyring};
4use coven_foundation::files::StoreFile;
5use std::sync::Arc;
6
7#[cfg(any(target_os = "android", test, feature = "test-utils"))]
8mod block_store;
9#[cfg(target_os = "android")]
10mod block_store_jni;
11#[cfg(any(test, feature = "test-utils"))]
12mod block_store_test_utils;
13mod error;
14mod key_session;
15mod keychain;
16mod memory;
17mod passphrase;
18mod person_entries;
19mod platform;
20mod store_custody;
21
22pub use error::{KeyError, KeychainError};
23pub use key_session::KeySession;
24pub use keychain::{Keychain, KeyringCustody, StoreKeychain};
25pub use memory::InMemoryCustody;
26pub use passphrase::{Passphrase, PassphraseCustody};
27pub use person_entries::{PersonEntries, PersonRestoreCode, RestoreCodeChange};
28pub use store_custody::{IdentityError, StoreCustody};
29
30mod sealed {
31    pub trait Sealed {}
32}
33
34/// What custody keeps: the store keys or the member's identity. Sealed: coven
35/// alone says how each is named and encoded.
36pub trait KeyMaterial: sealed::Sealed + Clone + Send + Sync + 'static {
37    /// The keychain entry and passphrase file entry it is kept under.
38    const ENTRY: &'static str;
39    /// The store file a passphrase custody seals it in.
40    const FILE: StoreFile;
41    /// Encode it for custody.
42    fn to_secret_bytes(&self) -> SecretBytes;
43    /// Decode what `to_secret_bytes` encoded.
44    fn from_secret_bytes(bytes: &[u8]) -> Result<Self, MaterialError>;
45}
46
47impl sealed::Sealed for StoreKeyring {}
48impl sealed::Sealed for MemberIdentity {}
49
50impl KeyMaterial for StoreKeyring {
51    const ENTRY: &'static str = "store-keys";
52    const FILE: StoreFile = StoreFile::StoreKeys;
53    fn to_secret_bytes(&self) -> SecretBytes {
54        Self::to_secret_bytes(self)
55    }
56    fn from_secret_bytes(bytes: &[u8]) -> Result<Self, MaterialError> {
57        Self::from_secret_bytes(bytes)
58    }
59}
60
61impl KeyMaterial for MemberIdentity {
62    const ENTRY: &'static str = "member-keys";
63    const FILE: StoreFile = StoreFile::MemberKeys;
64    fn to_secret_bytes(&self) -> SecretBytes {
65        Self::to_secret_bytes(self)
66    }
67    fn from_secret_bytes(bytes: &[u8]) -> Result<Self, MaterialError> {
68        Self::from_secret_bytes(bytes)
69    }
70}
71
72/// The app's own store for key material this device holds (E1).
73pub trait Custody<T: KeyMaterial>: Send + Sync {
74    /// Called once at open; `None` when this device has never held `T`.
75    /// Retain the unlocked persistence capability until close.
76    fn unlock(&self) -> Result<Option<T>, KeyError>;
77    /// Keeps `material`, replacing what was kept.
78    fn persist(&self, material: &T) -> Result<(), KeyError>;
79    /// Removes what was kept; succeeds if nothing was kept.
80    fn forget(&self) -> Result<(), KeyError>;
81    /// Erase held memory after users finish; performs no persistence or IO.
82    fn close(&self);
83}
84
85/// Where this device keeps `T` (E1).
86pub enum CustodyChoice<T: KeyMaterial> {
87    /// The OS keychain under the service registered at startup.
88    Keyring,
89    /// A file sealed with a key derived from this passphrase.
90    Passphrase(Passphrase),
91    /// Starts empty; holds only this handle's session: what opening, creation,
92    /// restore or join puts there.
93    InMemory,
94    /// The app's own store for `T`.
95    Custom(Arc<dyn Custody<T>>),
96}
97
98impl<T: KeyMaterial> std::fmt::Debug for CustodyChoice<T> {
99    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
100        f.write_str(match self {
101            Self::Keyring => "CustodyChoice::Keyring",
102            Self::Passphrase(_) => "CustodyChoice::Passphrase([REDACTED])",
103            Self::InMemory => "CustodyChoice::InMemory",
104            Self::Custom(_) => "CustodyChoice::Custom(..)",
105        })
106    }
107}
108
109impl<T: KeyMaterial> Custody<T> for InMemoryCustody<T> {
110    fn unlock(&self) -> Result<Option<T>, KeyError> {
111        Ok(self.read())
112    }
113    fn persist(&self, material: &T) -> Result<(), KeyError> {
114        self.write(material);
115        Ok(())
116    }
117    fn forget(&self) -> Result<(), KeyError> {
118        self.remove();
119        Ok(())
120    }
121    fn close(&self) {
122        self.remove();
123    }
124}
125
126impl<T: KeyMaterial> Custody<T> for PassphraseCustody<T> {
127    fn unlock(&self) -> Result<Option<T>, KeyError> {
128        self.read(T::ENTRY)?
129            .map(|bytes| T::from_secret_bytes(bytes.as_bytes()).map_err(KeyError::from))
130            .transpose()
131    }
132    fn persist(&self, material: &T) -> Result<(), KeyError> {
133        self.write(T::ENTRY, material.to_secret_bytes().as_bytes())
134    }
135    fn forget(&self) -> Result<(), KeyError> {
136        self.remove()
137    }
138    fn close(&self) {
139        self.release();
140    }
141}
142
143impl<T: KeyMaterial> Custody<T> for KeyringCustody<T> {
144    fn unlock(&self) -> Result<Option<T>, KeyError> {
145        self.read(T::ENTRY)?
146            .map(|bytes| T::from_secret_bytes(bytes.as_bytes()).map_err(KeyError::from))
147            .transpose()
148    }
149    fn persist(&self, material: &T) -> Result<(), KeyError> {
150        self.write(T::ENTRY, material.to_secret_bytes().as_bytes())
151    }
152    fn forget(&self) -> Result<(), KeyError> {
153        self.remove(T::ENTRY)
154    }
155    fn close(&self) {}
156}
157
158#[cfg(test)]
159#[path = "mod_tests.rs"]
160mod tests;