1use std::convert::TryFrom;
2use std::fs;
3use std::path::{PathBuf};
4use protobuf::Message;
5use uuid::Uuid;
6use crate::error::VaultError;
7use crate::storage::vault::{safe_update, VaultStorage};
8use crate::structs::crypto::{Encrypted, GlobalKey};
9use crate::proto::crypto::{GlobalKey as proto_GlobalKey};
10use crate::structs::types::UsesOddKey;
11
12pub struct VaultGlobalKey {
15 pub(crate) vault: PathBuf,
16}
17
18pub(crate) const KEY_FILE: &str = "global.key";
19
20impl VaultGlobalKey {
21 fn get_path(&self) -> PathBuf {
22 self.vault.join(KEY_FILE)
23 }
24
25 pub fn is_set(&self) -> bool {
28 self.get_path().is_file()
29 }
30
31 pub fn create(&self, password: &str) -> Result<(), VaultError> {
37 if self.is_set() {
38 return Err(VaultError::FilesystemError("Global key already set".to_string()))
39 }
40 let global = GlobalKey::generate(password.as_bytes())?;
41 let encoded: Vec<u8> = proto_GlobalKey::try_from(&global).unwrap().write_to_bytes()?;
42 let file = self.get_path();
43 let write_result = fs::write(&file, encoded);
44 if write_result.is_err() {
45 if file.exists() && fs::remove_file(&file).is_err() {
48 println!("Failed to remove {:?}", file)
49 }
50 return Err(VaultError::FilesystemError(
51 format!("Failed to write global key: {:?}", write_result.err().unwrap()))
52 );
53 }
54 Ok(())
55 }
56
57 pub fn get(&self) -> Result<GlobalKey, VaultError> {
60 let file = self.get_path();
61 if file.exists() && file.is_file() {
62 let proto = fs::read(file)?;
63 let global = GlobalKey::try_from(proto.as_slice())?;
64 return Ok(global)
65 }
66 Err(VaultError::FilesystemError("Global key doesn't exist".to_string()))
67 }
68
69 pub fn get_if_exists(&self) -> Result<Option<GlobalKey>, VaultError> {
72 if !self.is_set() {
73 return Ok(None)
74 }
75 let global = self.get()?;
77 Ok(Some(global))
78 }
79
80 pub fn change_password(self, current_password: &str, new_password: &str) -> Result<(), VaultError> {
84 if !self.is_set() {
85 return Err(VaultError::GlobalKeyRequired)
86 }
87
88 let mut g = self.get()?;
89 let key_value = g.key.decrypt(current_password.as_bytes(), None)?;
90 g.key = Encrypted::encrypt(key_value, new_password.as_bytes(), None)?;
91
92 let encoded: Vec<u8> = proto_GlobalKey::try_from(&g).unwrap().write_to_bytes()?;
93 safe_update(self.get_path(), encoded, None)
96 }
97
98 pub fn verify_password(self, password: &str) -> Result<bool, VaultError> {
102 if !self.is_set() {
103 return Err(VaultError::GlobalKeyRequired)
104 }
105
106 let key = self.get()?;
107 key.verify_password(password).map_err(VaultError::from)
108 }
109}
110
111#[derive(Clone, PartialEq, Eq, Debug)]
112pub enum LegacyEntryRef {
113 Seed(Uuid),
114 PrivateKey(Uuid)
115}
116
117impl VaultStorage {
118 pub fn get_global_key_missing(&self) -> Result<Vec<LegacyEntryRef>, VaultError> {
121 let seeds: Vec<LegacyEntryRef> = self.seeds()
122 .list_entries()?
123 .iter()
124 .filter(|seed| seed.is_odd_key())
125 .map(|seed| LegacyEntryRef::Seed(seed.id))
126 .collect();
127 let keys: Vec<LegacyEntryRef> = self.keys()
128 .list_entries()?
129 .iter()
130 .filter(|key| key.is_odd_key())
131 .map(|key| LegacyEntryRef::PrivateKey(key.id))
132 .collect();
133
134 let mut result = Vec::with_capacity(seeds.len() + keys.len());
135 result.extend(seeds);
136 result.extend(keys);
137 Ok(result)
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use std::str::FromStr;
144 use hdpath::StandardHDPath;
145 use tempdir::TempDir;
146 use crate::chains::Blockchain;
147 use crate::EthereumAddress;
148 use crate::storage::vault::VaultStorage;
149 use crate::structs::pk::PrivateKeyHolder;
150 use crate::structs::seed::{Seed, SeedSource, LedgerSource};
151 use crate::storage::global_key::LegacyEntryRef;
152
153 #[test]
154 fn create_when_unset() {
155 let tmp_dir = TempDir::new("emerald-global-key-test").unwrap();
156 let vault = VaultStorage::create(tmp_dir.path()).unwrap();
157
158 let global = vault.global_key();
159 assert!(!global.is_set());
160 global.create("test").expect("create global key");
161 assert!(global.is_set());
162 }
163
164 #[test]
165 fn cannot_get_when_unset() {
166 let tmp_dir = TempDir::new("emerald-global-key-test").unwrap();
167 let vault = VaultStorage::create(tmp_dir.path()).unwrap();
168
169 let global = vault.global_key();
170 assert!(!global.is_set());
171 let value_result = global.get();
172 assert!(value_result.is_err());
173 }
174
175 #[test]
176 fn cannot_create_when_set() {
177 let tmp_dir = TempDir::new("emerald-global-key-test").unwrap();
178 let vault = VaultStorage::create(tmp_dir.path()).unwrap();
179
180 let global = vault.global_key();
181 assert!(!global.is_set());
182 global.create("test-1").expect("create global key");
183 assert!(global.is_set());
184 let global_value_1 = global.get().unwrap();
185
186 let create2 = global.create("test-2");
187 assert!(create2.is_err());
188
189 let global_value_2 = global.get().unwrap();
190 assert_eq!(global_value_1, global_value_2);
191 }
192
193 #[test]
194 fn returns_when_exists() {
195 let tmp_dir = TempDir::new("emerald-global-key-test").unwrap();
196 let vault = VaultStorage::create(tmp_dir.path()).unwrap();
197
198 let global = vault.global_key();
199 global.create("test-1").unwrap();
200
201 let value = global.get_if_exists();
202
203 assert!(value.is_ok());
204 assert!(value.unwrap().is_some());
205 }
206
207 #[test]
208 fn verify_correct() {
209 let tmp_dir = TempDir::new("emerald-global-key-test").unwrap();
210 let vault = VaultStorage::create(tmp_dir.path()).unwrap();
211
212 let global = vault.global_key();
213 global.create("test-1").unwrap();
214
215 let value = global.verify_password("test-1");
216
217 assert!(value.is_ok());
218 assert!(value.unwrap());
219 }
220
221 #[test]
222 fn verify_wrong() {
223 let tmp_dir = TempDir::new("emerald-global-key-test").unwrap();
224 let vault = VaultStorage::create(tmp_dir.path()).unwrap();
225
226 let global = vault.global_key();
227 global.create("test-1").unwrap();
228
229 let value = global.verify_password("test-2");
230
231 assert!(value.is_ok());
232 assert!(!value.unwrap());
233 }
234
235 #[test]
236 fn none_when_doesnt_exist() {
237 let tmp_dir = TempDir::new("emerald-global-key-test").unwrap();
238 let vault = VaultStorage::create(tmp_dir.path()).unwrap();
239
240 let global = vault.global_key();
241
242 let value = global.get_if_exists();
243
244 assert!(value.is_ok());
245 assert!(value.unwrap().is_none());
246 }
247
248 #[test]
249 fn is_used_in_encryption() {
250 let tmp_dir = TempDir::new("emerald-global-key-test").unwrap();
251 let vault = VaultStorage::create(tmp_dir.path()).unwrap();
252
253 let global_store = vault.global_key();
254 global_store.create("test-1").unwrap();
255 let global = Some(global_store.get().unwrap());
256
257 let seed_id = vault.seeds().add(
258 Seed::test_generate(None, "test-1".as_bytes(), global.clone()).unwrap()
259 ).unwrap();
260
261 let seed_source = vault.seeds().get(seed_id).unwrap().source;
262
263 let get_no_global = seed_source.get_addresses::<EthereumAddress>(
264 Some("test-1".to_string()),
265 None,
266 &vec![StandardHDPath::from_str("m/44'/60'/0'/0/0").unwrap()],
267 Blockchain::Ethereum,
268 );
269
270 assert!(get_no_global.is_err());
271
272 let get_w_global = seed_source.get_addresses::<EthereumAddress>(
273 Some("test-1".to_string()),
274 global,
275 &vec![StandardHDPath::from_str("m/44'/60'/0'/0/0").unwrap()],
276 Blockchain::Ethereum,
277 );
278
279 println!("Result: {:?}", get_w_global);
280
281 assert!(get_w_global.is_ok());
282 }
283
284 #[test]
285 fn can_change_password() {
286 let tmp_dir = TempDir::new("emerald-global-key-test").unwrap();
287 let vault = VaultStorage::create(tmp_dir.path()).unwrap();
288
289 let global_store = vault.global_key();
290 global_store.create("test-1").unwrap();
291 let global = Some(global_store.get().unwrap());
292
293 let seed_id = vault.seeds().add(
294 Seed::test_generate(None, "test-1".as_bytes(), global.clone()).unwrap()
295 ).unwrap();
296
297 let changed = global_store.change_password("test-1", "test-2");
298 assert!(changed.is_ok());
299
300 let global = Some(vault.global_key().get().unwrap());
302
303 let seed_source = vault.seeds().get(seed_id).unwrap().source;
304
305 let get_old_password = seed_source.get_addresses::<EthereumAddress>(
306 Some("test-1".to_string()),
307 global.clone(),
308 &vec![StandardHDPath::from_str("m/44'/60'/0'/0/0").unwrap()],
309 Blockchain::Ethereum,
310 );
311 assert!(get_old_password.is_err());
312
313 let get_new_password = seed_source.get_addresses::<EthereumAddress>(
314 Some("test-2".to_string()),
315 global,
316 &vec![StandardHDPath::from_str("m/44'/60'/0'/0/0").unwrap()],
317 Blockchain::Ethereum,
318 );
319
320 println!("Result: {:?}", get_new_password);
321
322 assert!(get_new_password.is_ok());
323 }
324
325 #[test]
326 fn doesnt_change_wrong_password() {
327 let tmp_dir = TempDir::new("emerald-global-key-test").unwrap();
328 let vault = VaultStorage::create(tmp_dir.path()).unwrap();
329
330 let global_store = vault.global_key();
331 global_store.create("test-1").unwrap();
332 let global = Some(global_store.get().unwrap());
333
334 let seed_id = vault.seeds().add(
335 Seed::test_generate(None, "test-1".as_bytes(), global.clone()).unwrap()
336 ).unwrap();
337
338 let changed = global_store.change_password("test-1-wrong", "test-2");
339 assert!(changed.is_err());
340
341 let global = Some(vault.global_key().get().unwrap());
343
344 let seed_source = vault.seeds().get(seed_id).unwrap().source;
345
346 let get_old_password = seed_source.get_addresses::<EthereumAddress>(
347 Some("test-1".to_string()),
348 global.clone(),
349 &vec![StandardHDPath::from_str("m/44'/60'/0'/0/0").unwrap()],
350 Blockchain::Ethereum,
351 );
352 assert!(get_old_password.is_ok());
353
354 let get_new_password = seed_source.get_addresses::<EthereumAddress>(
355 Some("test-2".to_string()),
356 global,
357 &vec![StandardHDPath::from_str("m/44'/60'/0'/0/0").unwrap()],
358 Blockchain::Ethereum,
359 );
360
361 assert!(get_new_password.is_err());
362 }
363
364 #[test]
365 fn ignored_for_legacy_seed() {
366 let tmp_dir = TempDir::new("emerald-global-key-test").unwrap();
367 let vault = VaultStorage::create(tmp_dir.path()).unwrap();
368
369 let seed_id = vault.seeds().add(
370 Seed::test_generate(None, "test-1".as_bytes(), None).unwrap()
371 ).unwrap();
372
373 let global_store = vault.global_key();
374 global_store.create("test-1").unwrap();
375 let global = Some(global_store.get().unwrap());
376
377 let seed_source = vault.seeds().get(seed_id).unwrap().source;
378
379 let get_no_global = seed_source.get_addresses::<EthereumAddress>(
382 Some("test-1".to_string()),
383 None,
384 &vec![StandardHDPath::from_str("m/44'/60'/0'/0/0").unwrap()],
385 Blockchain::Ethereum,
386 );
387
388 assert!(get_no_global.is_ok());
389
390 let get_w_global = seed_source.get_addresses::<EthereumAddress>(
391 Some("test-1".to_string()),
392 global,
393 &vec![StandardHDPath::from_str("m/44'/60'/0'/0/0").unwrap()],
394 Blockchain::Ethereum,
395 );
396
397 println!("Result: {:?}", get_w_global);
398
399 assert!(get_w_global.is_ok());
400 }
401
402 #[test]
403 fn reports_nokey_items() {
404 let tmp_dir = TempDir::new("emerald-global-key-test").unwrap();
405 let vault = VaultStorage::create(tmp_dir.path()).unwrap();
406
407 let seed_id_1 = vault.seeds().add(
408 Seed::test_generate(None, "test-1".as_bytes(), None).unwrap()
409 ).unwrap();
410
411 let key_id_1 = vault.keys().add(
412 PrivateKeyHolder::generate_ethereum_raw("test-2").unwrap()
413 ).unwrap();
414
415 println!("init: {:?}, {:?}", seed_id_1, key_id_1);
416
417 let global_store = vault.global_key();
418 global_store.create("test-g").unwrap();
419 let global = Some(global_store.get().unwrap());
420
421 let seed_id_2 = vault.seeds().add(
422 Seed::test_generate(None, "test-g".as_bytes(), global.clone()).unwrap()
423 ).unwrap();
424
425 let key_id_2 = vault.keys().add(
426 PrivateKeyHolder::create_ethereum_raw(hex::decode("15cc67bb2a7f75a682198264728b951c461bd4a92692ab3bb00f01e9dbe2fbe4").unwrap(), "test-g", global.clone()).unwrap()
427 ).unwrap();
428
429 let key_id_3 = vault.keys().add(
430 PrivateKeyHolder::generate_ethereum_raw("test-3").unwrap()
431 ).unwrap();
432
433 println!("post: {:?}, {:?}, {:?}", seed_id_2, key_id_2, key_id_3);
434
435 let unused = vault.get_global_key_missing().unwrap();
436
437 println!("{:?}", unused);
438
439 assert_eq!(unused.len(), 3);
440
441 assert!(unused.contains(&LegacyEntryRef::Seed(seed_id_1)));
442 assert!(unused.contains(&LegacyEntryRef::PrivateKey(key_id_1)));
443 assert!(unused.contains(&LegacyEntryRef::PrivateKey(key_id_3)));
444 }
445
446 #[test]
447 fn doesnt_report_ledger() {
448 let tmp_dir = TempDir::new("emerald-global-key-test").unwrap();
449 let vault = VaultStorage::create(tmp_dir.path()).unwrap();
450
451 let seed_id_1 = vault.seeds().add(
452 Seed::test_generate(None, "test-1".as_bytes(), None).unwrap()
453 ).unwrap();
454
455 let seed_id_2 = vault.seeds().add(
456 Seed {
457 source: SeedSource::Ledger(LedgerSource::default()),
458 ..Seed::default()
459 }
460 ).unwrap();
461
462 println!("init: {:?}, {:?}", seed_id_1, seed_id_2);
463
464 let global_store = vault.global_key();
465 global_store.create("test-g").unwrap();
466 let _global = Some(global_store.get().unwrap());
467
468 let unused = vault.get_global_key_missing().unwrap();
469
470 println!("{:?}", unused);
471
472 assert_eq!(unused.len(), 1);
473
474 assert!(unused.contains(&LegacyEntryRef::Seed(seed_id_1)));
475 }
476}