1use argon2::Argon2;
13use zeroize::{Zeroize, Zeroizing};
14
15use crate::crypto::KEY_LEN;
16use crate::error::CoreError;
17
18pub struct MasterKey([u8; KEY_LEN]);
24
25impl MasterKey {
26 pub fn new(bytes: [u8; KEY_LEN]) -> Self {
28 Self(bytes)
29 }
30
31 pub fn expose(&self) -> &[u8; KEY_LEN] {
33 &self.0
34 }
35}
36
37impl Drop for MasterKey {
38 fn drop(&mut self) {
39 self.0.zeroize();
40 }
41}
42
43impl core::fmt::Debug for MasterKey {
45 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
46 f.write_str("MasterKey(REDACTED)")
47 }
48}
49
50pub trait Keyring {
53 fn get_master_key(&self) -> Result<MasterKey, CoreError>;
56
57 fn set_master_key(&self, key: &MasterKey) -> Result<(), CoreError>;
61}
62
63#[derive(Default)]
65pub struct MockKeyring {
66 key: std::sync::Mutex<Option<[u8; KEY_LEN]>>,
67}
68
69impl MockKeyring {
70 pub fn empty() -> Self {
72 Self::default()
73 }
74
75 pub fn with_key(bytes: [u8; KEY_LEN]) -> Self {
77 Self {
78 key: std::sync::Mutex::new(Some(bytes)),
79 }
80 }
81}
82
83impl Keyring for MockKeyring {
84 fn get_master_key(&self) -> Result<MasterKey, CoreError> {
85 self.key
86 .lock()
87 .expect("mock keyring mutex poisoned")
88 .map(MasterKey::new)
89 .ok_or_else(|| CoreError::Keyring("no master key set".to_string()))
90 }
91
92 fn set_master_key(&self, key: &MasterKey) -> Result<(), CoreError> {
93 *self.key.lock().expect("mock keyring mutex poisoned") = Some(*key.expose());
94 Ok(())
95 }
96}
97
98pub struct OsKeyring {
103 service: String,
104 user: String,
105}
106
107impl OsKeyring {
108 pub fn new() -> Self {
111 Self {
112 service: "kovra".to_string(),
113 user: "master-key".to_string(),
114 }
115 }
116}
117
118impl Default for OsKeyring {
119 fn default() -> Self {
120 Self::new()
121 }
122}
123
124impl Keyring for OsKeyring {
125 fn get_master_key(&self) -> Result<MasterKey, CoreError> {
126 let entry = keyring::Entry::new(&self.service, &self.user)
127 .map_err(|e| CoreError::Keyring(e.to_string()))?;
128 let secret = entry
129 .get_secret()
130 .map_err(|e| CoreError::Keyring(e.to_string()))?;
131 let bytes: [u8; KEY_LEN] = secret
132 .as_slice()
133 .try_into()
134 .map_err(|_| CoreError::Keyring("stored key has wrong length".to_string()))?;
135 Ok(MasterKey::new(bytes))
136 }
137
138 fn set_master_key(&self, key: &MasterKey) -> Result<(), CoreError> {
139 let entry = keyring::Entry::new(&self.service, &self.user)
140 .map_err(|e| CoreError::Keyring(e.to_string()))?;
141 entry
142 .set_secret(key.expose())
143 .map_err(|e| CoreError::Keyring(e.to_string()))
144 }
145}
146
147pub struct Argon2Keyring {
152 passphrase: Zeroizing<Vec<u8>>,
153 salt: Vec<u8>,
154}
155
156pub const MIN_SALT_LEN: usize = 8;
158
159impl Argon2Keyring {
160 pub fn new(
164 passphrase: impl Into<Vec<u8>>,
165 salt: impl Into<Vec<u8>>,
166 ) -> Result<Self, CoreError> {
167 let salt = salt.into();
168 if salt.len() < MIN_SALT_LEN {
169 return Err(CoreError::Keyring(format!(
170 "salt must be at least {MIN_SALT_LEN} bytes"
171 )));
172 }
173 Ok(Self {
174 passphrase: Zeroizing::new(passphrase.into()),
175 salt,
176 })
177 }
178}
179
180impl Keyring for Argon2Keyring {
181 fn get_master_key(&self) -> Result<MasterKey, CoreError> {
182 let mut key = [0u8; KEY_LEN];
183 Argon2::default()
184 .hash_password_into(&self.passphrase, &self.salt, &mut key)
185 .map_err(|e| CoreError::Keyring(e.to_string()))?;
186 let master = MasterKey::new(key);
187 key.zeroize();
188 Ok(master)
189 }
190
191 fn set_master_key(&self, _key: &MasterKey) -> Result<(), CoreError> {
192 Err(CoreError::Keyring(
193 "passphrase-derived key cannot be stored; it is recomputed from the passphrase"
194 .to_string(),
195 ))
196 }
197}
198
199#[cfg(test)]
200mod tests {
201 use super::*;
202
203 #[test]
204 fn mock_keyring_round_trips() {
205 let kr = MockKeyring::empty();
206 assert!(kr.get_master_key().is_err());
207 kr.set_master_key(&MasterKey::new([5u8; KEY_LEN])).unwrap();
208 assert_eq!(kr.get_master_key().unwrap().expose(), &[5u8; KEY_LEN]);
209 }
210
211 #[test]
212 fn mock_with_key_seeds_value() {
213 let kr = MockKeyring::with_key([9u8; KEY_LEN]);
214 assert_eq!(kr.get_master_key().unwrap().expose(), &[9u8; KEY_LEN]);
215 }
216
217 #[test]
218 fn master_key_debug_is_redacted() {
219 let mk = MasterKey::new([1u8; KEY_LEN]);
220 assert_eq!(format!("{mk:?}"), "MasterKey(REDACTED)");
221 }
222
223 #[test]
224 fn argon2_is_deterministic_for_same_inputs() {
225 let a =
226 Argon2Keyring::new(b"correct horse".to_vec(), b"stable-salt-1234".to_vec()).unwrap();
227 let b =
228 Argon2Keyring::new(b"correct horse".to_vec(), b"stable-salt-1234".to_vec()).unwrap();
229 assert_eq!(
230 a.get_master_key().unwrap().expose(),
231 b.get_master_key().unwrap().expose()
232 );
233 }
234
235 #[test]
236 fn argon2_differs_for_different_passphrase() {
237 let salt = b"stable-salt-1234".to_vec();
238 let a = Argon2Keyring::new(b"passphrase-a".to_vec(), salt.clone()).unwrap();
239 let b = Argon2Keyring::new(b"passphrase-b".to_vec(), salt).unwrap();
240 assert_ne!(
241 a.get_master_key().unwrap().expose(),
242 b.get_master_key().unwrap().expose()
243 );
244 }
245
246 #[test]
247 fn argon2_rejects_short_salt() {
248 assert!(matches!(
249 Argon2Keyring::new(b"pw".to_vec(), b"short".to_vec()),
250 Err(CoreError::Keyring(_))
251 ));
252 }
253
254 #[test]
255 fn argon2_key_is_not_settable() {
256 let kr = Argon2Keyring::new(b"pw".to_vec(), b"stable-salt-1234".to_vec()).unwrap();
257 assert!(kr.set_master_key(&MasterKey::new([0u8; KEY_LEN])).is_err());
258 }
259}