keybase_keystore/
keystore.rs1use crate::error::Error;
2use crate::generation::Generation;
3use crate::types::*;
4use async_std::path::{Path, PathBuf};
5use async_std::prelude::*;
6use async_std::sync::RwLock;
7
8pub struct KeyStore {
9 path: PathBuf,
10 gen: RwLock<Generation>,
11}
12
13impl KeyStore {
14 pub async fn open<T: AsRef<Path>>(path: T) -> Result<Self, Error> {
16 let path = path.as_ref().to_path_buf();
17 async_std::fs::create_dir_all(&path).await?;
18 let mut gens = vec![];
19 let mut dir = async_std::fs::read_dir(&path).await?;
20 while let Some(entry) = dir.next().await {
21 let gen = entry?
22 .file_name()
23 .to_str()
24 .ok_or(Error::Corrupted)?
25 .parse()
26 .map_err(|_| Error::Corrupted)?;
27 gens.push(Generation::new(&path, gen));
28 }
29 let gen = match gens.len() {
30 0 => Generation::new(&path, 0),
31 1 => gens.pop().unwrap(),
32 _ => {
33 let mut ugen: Option<Generation> = None;
34 let mut rgens = vec![];
35 for gen in gens {
36 if gen.device_key().await.is_ok() {
37 if let Some(ugen2) = ugen {
38 if ugen2.gen() < gen.gen() {
39 rgens.push(ugen2);
40 ugen = Some(gen);
41 } else {
42 rgens.push(gen);
43 ugen = Some(ugen2);
44 }
45 } else {
46 ugen = Some(gen);
47 }
48 } else {
49 rgens.push(gen);
50 }
51 }
52 if let Some(ugen) = ugen {
53 for rgen in rgens {
54 rgen.remove().await?;
55 }
56 ugen
57 } else {
58 return Err(Error::Corrupted);
59 }
60 }
61 };
62 Ok(Self {
63 path,
64 gen: RwLock::new(gen),
65 })
66 }
67
68 pub async fn apply_mask(&self, mask: &Mask, next_gen: u16) -> Result<(), Error> {
70 let mut gen = self.gen.write().await;
71 if gen.gen() + 1 != next_gen {
72 return Err(Error::GenMissmatch);
73 }
74 let dk = gen.device_key().await?;
75 let pass = gen.password().await?.apply_mask(mask);
76 let next_gen = Generation::new(&self.path, next_gen);
77 next_gen.initialize(&dk, &pass, true).await?;
78 let old_gen = std::mem::replace(&mut *gen, next_gen);
79 old_gen.remove().await?;
80 Ok(())
81 }
82
83 pub async fn gen(&self) -> u16 {
85 self.gen.read().await.gen()
86 }
87
88 pub async fn is_initialized(&self) -> bool {
90 self.gen.read().await.is_initialized().await
91 }
92
93 pub async fn initialize(
95 &self,
96 dk: &DeviceKey,
97 pass: &Password,
98 force: bool,
99 ) -> Result<(), Error> {
100 self.gen.write().await.initialize(dk, pass, force).await
101 }
102
103 pub async fn unlock(&self, pass: &Password) -> Result<DeviceKey, Error> {
105 self.gen.write().await.unlock(pass).await
106 }
107
108 pub async fn lock(&self) -> Result<(), Error> {
111 self.gen.write().await.lock().await
112 }
113
114 pub async fn device_key(&self) -> Result<DeviceKey, Error> {
118 self.gen.read().await.device_key().await
119 }
120
121 pub async fn password(&self) -> Result<Password, Error> {
125 self.gen.read().await.password().await
126 }
127
128 pub async fn public(&self) -> Result<PublicDeviceKey, Error> {
130 self.gen.read().await.public().await
131 }
132
133 pub async fn change_password_mask(&self, password: &Password) -> Result<Mask, Error> {
135 self.gen.read().await.change_password_mask(password).await
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142 use crate::types::{DeviceKey, Password};
143 use fail::FailScenario;
144 use tempdir::TempDir;
145
146 #[async_std::test]
147 async fn test_keystore() {
148 fail::cfg("edk-write-fail", "off").unwrap();
149 fail::cfg("gen-rm-fail", "off").unwrap();
150 let tmp = TempDir::new("keystore-").unwrap();
151 let store = KeyStore::open(tmp.path()).await.unwrap();
152
153 let key = DeviceKey::generate().await;
155 let p1 = Password::from("password".to_string());
156 store.initialize(&key, &p1, false).await.unwrap();
157
158 let key2 = store.device_key().await.unwrap();
160 assert_eq!(key.expose_secret(), key2.expose_secret());
161
162 let rp1 = store.password().await.unwrap();
164 assert_eq!(p1.expose_secret(), rp1.expose_secret());
165
166 store.lock().await.unwrap();
168 store.unlock(&p1).await.unwrap();
169 let key2 = store.device_key().await.unwrap();
170 assert_eq!(key.expose_secret(), key2.expose_secret());
171
172 let p2 = Password::from("other password".to_string());
174 let mask = store.change_password_mask(&p2).await.unwrap();
175 store
176 .apply_mask(&mask, store.gen().await + 1)
177 .await
178 .unwrap();
179
180 store.lock().await.unwrap();
182
183 let store = KeyStore::open(tmp.path()).await.unwrap();
184 store.unlock(&p2).await.unwrap();
185 let key2 = store.device_key().await.unwrap();
186 assert_eq!(key.expose_secret(), key2.expose_secret());
187
188 let p3 = Password::from("wrong password".to_string());
190 store.lock().await.unwrap();
191 match store.unlock(&p3).await {
192 Err(Error::Locked) => {}
193 Ok(_) => panic!("should fail"),
194 r => {
195 r.unwrap();
196 }
197 }
198 match store.device_key().await {
199 Err(Error::Locked) => {}
200 Ok(_) => panic!("should fail"),
201 r => {
202 r.unwrap();
203 }
204 }
205 }
206
207 #[async_std::test]
208 #[ignore] async fn test_edk_write_fail_unlock() {
210 fail::cfg("edk-write-fail", "off").unwrap();
211 fail::cfg("gen-rm-fail", "off").unwrap();
212 let tmp = TempDir::new("keystore-").unwrap();
213 let store = KeyStore::open(tmp.path()).await.unwrap();
214 let key = DeviceKey::generate().await;
215 let pass = Password::generate().await;
216 store.initialize(&key, &pass, false).await.unwrap();
217
218 let scenario = FailScenario::setup();
219
220 fail::cfg("edk-write-fail", "return(())").unwrap();
221 let npass = Password::generate().await;
222 let mask = store.change_password_mask(&npass).await.unwrap();
223 store.apply_mask(&mask, store.gen().await + 1).await.ok();
224 store.lock().await.unwrap();
225 store.unlock(&pass).await.unwrap();
226
227 scenario.teardown();
228 }
229
230 #[async_std::test]
231 #[ignore] async fn test_edk_write_fail_recovery() {
233 fail::cfg("edk-write-fail", "off").unwrap();
234 fail::cfg("gen-rm-fail", "off").unwrap();
235 let tmp = TempDir::new("keystore-").unwrap();
236 let store = KeyStore::open(tmp.path()).await.unwrap();
237 let key = DeviceKey::generate().await;
238 let pass = Password::generate().await;
239 store.initialize(&key, &pass, false).await.unwrap();
240
241 let scenario = FailScenario::setup();
242
243 fail::cfg("edk-write-fail", "return(())").unwrap();
244 let npass = Password::generate().await;
245 let mask = store.change_password_mask(&npass).await.unwrap();
246 store.apply_mask(&mask, store.gen().await + 1).await.ok();
247
248 let key2 = store.device_key().await.unwrap();
249 assert_eq!(key.expose_secret(), key2.expose_secret());
250
251 let store = KeyStore::open(tmp.path()).await.unwrap();
252 let key2 = store.device_key().await.unwrap();
253 assert_eq!(key.expose_secret(), key2.expose_secret());
254
255 scenario.teardown();
256 }
257
258 #[async_std::test]
259 #[ignore] async fn test_gen_remove_fail_recovery() {
261 fail::cfg("edk-write-fail", "off").unwrap();
262 fail::cfg("gen-rm-fail", "off").unwrap();
263 let tmp = TempDir::new("keystore-").unwrap();
264 let store = KeyStore::open(tmp.path()).await.unwrap();
265 let key = DeviceKey::generate().await;
266 let pass = Password::generate().await;
267 store.initialize(&key, &pass, false).await.unwrap();
268
269 let scenario = FailScenario::setup();
270
271 fail::cfg("gen-rm-fail", "return(())").unwrap();
272 let npass = Password::generate().await;
273 let mask = store.change_password_mask(&npass).await.unwrap();
274 store.apply_mask(&mask, store.gen().await + 1).await.ok();
275
276 let key2 = store.device_key().await.unwrap();
277 assert_eq!(key.expose_secret(), key2.expose_secret());
278
279 let store = KeyStore::open(tmp.path()).await.unwrap();
280 let key2 = store.device_key().await.unwrap();
281 assert_eq!(key.expose_secret(), key2.expose_secret());
282
283 scenario.teardown();
284 }
285}