1use super::{
18 super::account::Crypto,
19 disk::{self, DiskDirectory, KeyFileManager},
20 KeyDirectory, SetKeyError, VaultKey, VaultKeyDirectory,
21};
22use crate::{json, Error, SafeAccount};
23use cfx_crypto::crypto::keccak::Keccak256;
24use parking_lot::Mutex;
25use std::{
26 fs, io,
27 path::{Path, PathBuf},
28};
29
30pub const VAULT_FILE_NAME: &str = "vault.json";
32pub const VAULT_TEMP_FILE_NAME: &str = "vault_temp.json";
34
35pub type VaultDiskDirectory = DiskDirectory<VaultKeyFileManager>;
37
38pub struct VaultKeyFileManager {
40 name: String,
41 key: VaultKey,
42 meta: Mutex<String>,
43}
44
45impl VaultDiskDirectory {
46 pub fn create<P>(
48 root: P, name: &str, key: VaultKey,
49 ) -> Result<Self, Error>
50 where P: AsRef<Path> {
51 let vault_dir_path = make_vault_dir_path(root, name, true)?;
53 if vault_dir_path.exists() {
54 return Err(Error::CreationFailed);
55 }
56
57 let vault_meta = "{}";
59 fs::create_dir_all(&vault_dir_path)?;
60 if let Err(err) = create_vault_file(&vault_dir_path, &key, vault_meta) {
61 let _ = fs::remove_dir_all(&vault_dir_path); return Err(err);
63 }
64
65 Ok(DiskDirectory::new(
66 vault_dir_path,
67 VaultKeyFileManager::new(name, key, vault_meta),
68 ))
69 }
70
71 pub fn at<P>(root: P, name: &str, key: VaultKey) -> Result<Self, Error>
73 where P: AsRef<Path> {
74 let vault_dir_path = make_vault_dir_path(root, name, true)?;
76 if !vault_dir_path.is_dir() {
77 return Err(Error::CreationFailed);
78 }
79
80 let meta = read_vault_file(&vault_dir_path, Some(&key))?;
82
83 Ok(DiskDirectory::new(
84 vault_dir_path,
85 VaultKeyFileManager::new(name, key, &meta),
86 ))
87 }
88
89 pub fn meta_at<P>(root: P, name: &str) -> Result<String, Error>
91 where P: AsRef<Path> {
92 let vault_dir_path = make_vault_dir_path(root, name, true)?;
94 if !vault_dir_path.is_dir() {
95 return Err(Error::VaultNotFound);
96 }
97
98 read_vault_file(&vault_dir_path, None)
100 }
101
102 fn create_temp_vault(
103 &self, key: VaultKey,
104 ) -> Result<VaultDiskDirectory, Error> {
105 let original_path = self.path().expect("self is instance of DiskDirectory; DiskDirectory always returns path; qed");
106 let mut path: PathBuf = original_path.clone();
107 let name = self.name();
108
109 path.push(name); let mut index = 0;
112 loop {
113 let name = format!("{}_temp_{}", name, index);
114 path.set_file_name(&name);
115 if !path.exists() {
116 return VaultDiskDirectory::create(original_path, &name, key);
117 }
118
119 index += 1;
120 }
121 }
122
123 fn copy_to_vault(&self, vault: &VaultDiskDirectory) -> Result<(), Error> {
124 for account in self.load()? {
125 let filename = account.filename.clone().expect("self is instance of DiskDirectory; DiskDirectory fills filename in load; qed");
126 vault.insert_with_filename(account, filename, true)?;
127 }
128
129 Ok(())
130 }
131
132 fn delete(&self) -> Result<(), Error> {
133 let path = self.path().expect("self is instance of DiskDirectory; DiskDirectory always returns path; qed");
134 fs::remove_dir_all(path).map_err(Into::into)
135 }
136}
137
138impl VaultKeyDirectory for VaultDiskDirectory {
139 fn as_key_directory(&self) -> &dyn KeyDirectory { self }
140
141 fn name(&self) -> &str { &self.key_manager().name }
142
143 fn key(&self) -> VaultKey { self.key_manager().key.clone() }
144
145 fn set_key(&self, new_key: VaultKey) -> Result<(), SetKeyError> {
146 let temp_vault = VaultDiskDirectory::create_temp_vault(self, new_key)
147 .map_err(SetKeyError::NonFatalOld)?;
148 let mut source_path = temp_vault.path().expect("temp_vault is instance of DiskDirectory; DiskDirectory always returns path; qed").clone();
149 let mut target_path = self.path().expect("self is instance of DiskDirectory; DiskDirectory always returns path; qed").clone();
150
151 temp_vault
153 .set_meta(&self.meta())
154 .map_err(SetKeyError::NonFatalOld)?;
155
156 source_path.push("next");
158 target_path.push("next");
159
160 let temp_accounts = self
161 .copy_to_vault(&temp_vault)
162 .and_then(|_| temp_vault.load())
163 .map_err(|err| {
164 let _ = temp_vault.delete();
166 SetKeyError::NonFatalOld(err)
167 })?;
168
169 for temp_account in temp_accounts {
173 let filename = temp_account.filename.expect("self is instance of DiskDirectory; DiskDirectory fills filename in load; qed");
174 source_path.set_file_name(&filename);
175 target_path.set_file_name(&filename);
176 fs::rename(&source_path, &target_path)
177 .map_err(|err| SetKeyError::Fatal(err.into()))?;
178 }
179 source_path.set_file_name(VAULT_FILE_NAME);
180 target_path.set_file_name(VAULT_FILE_NAME);
181 fs::rename(source_path, target_path)
182 .map_err(|err| SetKeyError::Fatal(err.into()))?;
183
184 temp_vault.delete().map_err(SetKeyError::NonFatalNew)
185 }
186
187 fn meta(&self) -> String { self.key_manager().meta.lock().clone() }
188
189 fn set_meta(&self, meta: &str) -> Result<(), Error> {
190 let key_manager = self.key_manager();
191 let vault_path = self.path().expect("self is instance of DiskDirectory; DiskDirectory always returns path; qed");
192 create_vault_file(vault_path, &key_manager.key, meta)?;
193 *key_manager.meta.lock() = meta.to_owned();
194 Ok(())
195 }
196}
197
198impl VaultKeyFileManager {
199 pub fn new(name: &str, key: VaultKey, meta: &str) -> Self {
200 VaultKeyFileManager {
201 name: name.into(),
202 key,
203 meta: Mutex::new(meta.to_owned()),
204 }
205 }
206}
207
208impl KeyFileManager for VaultKeyFileManager {
209 fn read<T>(
210 &self, filename: Option<String>, reader: T,
211 ) -> Result<SafeAccount, Error>
212 where T: io::Read {
213 let vault_file = json::VaultKeyFile::load(reader)
214 .map_err(|e| Error::Custom(format!("{:?}", e)))?;
215 let mut safe_account = SafeAccount::from_vault_file(
216 &self.key.password,
217 vault_file,
218 filename,
219 )?;
220
221 safe_account.meta = json::insert_vault_name_to_json_meta(
222 &safe_account.meta,
223 &self.name,
224 )
225 .map_err(|err| Error::Custom(format!("{:?}", err)))?;
226 Ok(safe_account)
227 }
228
229 fn write<T>(
230 &self, mut account: SafeAccount, writer: &mut T,
231 ) -> Result<(), Error>
232 where T: io::Write {
233 account.meta = json::remove_vault_name_from_json_meta(&account.meta)
234 .map_err(|err| Error::Custom(format!("{:?}", err)))?;
235
236 let vault_file: json::VaultKeyFile =
237 account.into_vault_file(self.key.iterations, &self.key.password)?;
238 vault_file
239 .write(writer)
240 .map_err(|e| Error::Custom(format!("{:?}", e)))
241 }
242}
243
244fn make_vault_dir_path<P>(
246 root: P, name: &str, check_name: bool,
247) -> Result<PathBuf, Error>
248where P: AsRef<Path> {
249 if check_name && !check_vault_name(name) {
251 return Err(Error::InvalidVaultName);
252 }
253
254 let mut vault_dir_path: PathBuf = root.as_ref().into();
255 vault_dir_path.push(name);
256 Ok(vault_dir_path)
257}
258
259fn check_vault_name(name: &str) -> bool {
264 !name.is_empty()
265 && name.chars().all(|c| {
266 c.is_alphanumeric() || c.is_whitespace() || c == '-' || c == '_'
267 })
268}
269
270fn create_vault_file<P>(
273 vault_dir_path: P, key: &VaultKey, meta: &str,
274) -> Result<(), Error>
275where P: AsRef<Path> {
276 let password_hash = key.password.as_bytes().keccak256();
277 let crypto =
278 Crypto::with_plain(&password_hash, &key.password, key.iterations)?;
279
280 let vault_file_path = vault_dir_path.as_ref().join(VAULT_FILE_NAME);
281 let temp_vault_file_name = disk::find_unique_filename_using_random_suffix(
282 vault_dir_path.as_ref(),
283 &VAULT_TEMP_FILE_NAME,
284 )?;
285 let temp_vault_file_path =
286 vault_dir_path.as_ref().join(&temp_vault_file_name);
287
288 let mut vault_file =
292 disk::create_new_file_with_permissions_to_owner(&temp_vault_file_path)?;
293 let vault_file_contents = json::VaultFile {
294 crypto: crypto.into(),
295 meta: Some(meta.to_owned()),
296 };
297 vault_file_contents
298 .write(&mut vault_file)
299 .map_err(|e| Error::Custom(format!("{:?}", e)))?;
300 drop(vault_file);
301 fs::rename(&temp_vault_file_path, &vault_file_path)?;
302
303 Ok(())
304}
305
306fn read_vault_file<P>(
308 vault_dir_path: P, key: Option<&VaultKey>,
309) -> Result<String, Error>
310where P: AsRef<Path> {
311 let mut vault_file_path: PathBuf = vault_dir_path.as_ref().into();
312 vault_file_path.push(VAULT_FILE_NAME);
313
314 let vault_file = fs::File::open(vault_file_path)?;
315 let vault_file_contents = json::VaultFile::load(vault_file)
316 .map_err(|e| Error::Custom(format!("{:?}", e)))?;
317 let vault_file_meta = vault_file_contents.meta.unwrap_or("{}".to_owned());
318 let vault_file_crypto: Crypto = vault_file_contents.crypto.into();
319
320 if let Some(key) = key {
321 let password_bytes = vault_file_crypto.decrypt(&key.password)?;
322 let password_hash = key.password.as_bytes().keccak256();
323 if password_hash != password_bytes.as_slice() {
324 return Err(Error::InvalidPassword);
325 }
326 }
327
328 Ok(vault_file_meta)
329}
330
331#[cfg(test)]
332mod test {
333 use super::{
334 check_vault_name, create_vault_file, make_vault_dir_path,
335 read_vault_file, VaultDiskDirectory, VaultKey, VAULT_FILE_NAME,
336 };
337 use std::{fs, io::Write, path::PathBuf};
338 use tempfile::tempdir;
339
340 #[test]
341 fn check_vault_name_succeeds() {
342 assert!(check_vault_name("vault"));
343 assert!(check_vault_name("vault with spaces"));
344 assert!(check_vault_name("vault with tabs"));
345 assert!(check_vault_name("vault_with_underscores"));
346 assert!(check_vault_name("vault-with-dashes"));
347 assert!(check_vault_name("vault-with-digits-123"));
348 assert!(check_vault_name("vault中文名字"));
349 }
350
351 #[test]
352 fn check_vault_name_fails() {
353 assert!(!check_vault_name(""));
354 assert!(!check_vault_name("."));
355 assert!(!check_vault_name("*"));
356 assert!(!check_vault_name("../.bash_history"));
357 assert!(!check_vault_name("/etc/passwd"));
358 assert!(!check_vault_name("c:\\windows"));
359 }
360
361 #[test]
362 fn make_vault_dir_path_succeeds() {
363 use std::path::Path;
364
365 assert_eq!(
366 &make_vault_dir_path("/home/user/parity", "vault", true).unwrap(),
367 &Path::new("/home/user/parity/vault")
368 );
369 assert_eq!(
370 &make_vault_dir_path("/home/user/parity", "*bad-name*", false)
371 .unwrap(),
372 &Path::new("/home/user/parity/*bad-name*")
373 );
374 }
375
376 #[test]
377 fn make_vault_dir_path_fails() {
378 assert!(make_vault_dir_path("/home/user/parity", "*bad-name*", true)
379 .is_err());
380 }
381
382 #[test]
383 fn create_vault_file_succeeds() {
384 let temp_path = tempdir().unwrap();
386 let key = VaultKey::new(&"password".into(), 1024);
387 let mut vault_dir: PathBuf = temp_path.path().into();
388 vault_dir.push("vault");
389 fs::create_dir_all(&vault_dir).unwrap();
390
391 let result = create_vault_file(&vault_dir, &key, "{}");
393
394 assert!(result.is_ok());
396 let mut vault_file_path = vault_dir;
397 vault_file_path.push(VAULT_FILE_NAME);
398 assert!(vault_file_path.exists() && vault_file_path.is_file());
399 }
400
401 #[test]
402 fn read_vault_file_succeeds() {
403 let temp_path = tempdir().unwrap();
405 let key = VaultKey::new(&"password".into(), 1024);
406 let vault_file_contents = r#"{"crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"758696c8dc6378ab9b25bb42790da2f5"},"ciphertext":"54eb50683717d41caaeb12ea969f2c159daada5907383f26f327606a37dc7168","kdf":"pbkdf2","kdfparams":{"c":1024,"dklen":32,"prf":"hmac-sha256","salt":"3c320fa566a1a7963ac8df68a19548d27c8f40bf92ef87c84594dcd5bbc402b6"},"mac":"9e5c2314c2a0781962db85611417c614bd6756666b6b1e93840f5b6ed895f003"}}"#;
407 let dir: PathBuf = temp_path.path().into();
408 let mut vault_file_path: PathBuf = dir.clone();
409 vault_file_path.push(VAULT_FILE_NAME);
410 {
411 let mut vault_file = fs::File::create(vault_file_path).unwrap();
412 vault_file
413 .write_all(vault_file_contents.as_bytes())
414 .unwrap();
415 }
416
417 let result = read_vault_file(&dir, Some(&key));
419
420 assert!(result.is_ok());
422 }
423
424 #[test]
425 fn read_vault_file_fails() {
426 let temp_path = tempdir().unwrap();
428 let key = VaultKey::new(&"password1".into(), 1024);
429 let dir: PathBuf = temp_path.path().into();
430 let mut vault_file_path: PathBuf = dir.clone();
431 vault_file_path.push(VAULT_FILE_NAME);
432
433 let result = read_vault_file(&dir, Some(&key));
435
436 assert!(result.is_err());
438
439 let vault_file_contents = r#"{"crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"0155e3690be19fbfbecabcd440aa284b"},"ciphertext":"4d6938a1f49b7782","kdf":"pbkdf2","kdfparams":{"c":1024,"dklen":32,"prf":"hmac-sha256","salt":"b6a9338a7ccd39288a86dba73bfecd9101b4f3db9c9830e7c76afdbd4f6872e5"},"mac":"16381463ea11c6eb2239a9f339c2e780516d29d234ce30ac5f166f9080b5a262"}}"#;
441 {
442 let mut vault_file = fs::File::create(vault_file_path).unwrap();
443 vault_file
444 .write_all(vault_file_contents.as_bytes())
445 .unwrap();
446 }
447
448 let result = read_vault_file(&dir, Some(&key));
450
451 assert!(result.is_err());
453 }
454
455 #[test]
456 fn vault_directory_can_be_created() {
457 let temp_path = tempdir().unwrap();
459 let key = VaultKey::new(&"password".into(), 1024);
460 let dir: PathBuf = temp_path.path().into();
461
462 let vault = VaultDiskDirectory::create(&dir, "vault", key.clone());
464
465 assert!(vault.is_ok());
467
468 let vault = VaultDiskDirectory::at(&dir, "vault", key);
470
471 assert!(vault.is_ok());
473 }
474
475 #[test]
476 fn vault_directory_cannot_be_created_if_already_exists() {
477 let temp_path = tempdir().unwrap();
479 let key = VaultKey::new(&"password".into(), 1024);
480 let dir: PathBuf = temp_path.path().into();
481 let mut vault_dir = dir.clone();
482 vault_dir.push("vault");
483 fs::create_dir_all(&vault_dir).unwrap();
484
485 let vault = VaultDiskDirectory::create(&dir, "vault", key);
487
488 assert!(vault.is_err());
490 }
491
492 #[test]
493 fn vault_directory_cannot_be_opened_if_not_exists() {
494 let temp_path = tempdir().unwrap();
496 let key = VaultKey::new(&"password".into(), 1024);
497 let dir: PathBuf = temp_path.path().into();
498
499 let vault = VaultDiskDirectory::at(&dir, "vault", key);
501
502 assert!(vault.is_err());
504 }
505}