Initial commit for vaults (#4312)

* initial commit for vaults

* fixed TODO

* public docs

* vault_file.json now contains enc(pwd hash)

* removed doc
This commit is contained in:
Svyatoslav Nikolsky
2017-01-30 13:44:09 +03:00
committed by Nikolay Volf
parent bf1e7ecfcb
commit 9ac4d83ca3
23 changed files with 1926 additions and 356 deletions

188
ethstore/src/dir/disk.rs Normal file → Executable file
View File

@@ -20,7 +20,8 @@ use std::collections::HashMap;
use time;
use {json, SafeAccount, Error};
use json::Uuid;
use super::KeyDirectory;
use super::{KeyDirectory, VaultKeyDirectory, VaultKeyDirectoryProvider, VaultKey};
use super::vault::VaultDiskDirectory;
const IGNORED_FILES: &'static [&'static str] = &[
"thumbs.db",
@@ -28,6 +29,7 @@ const IGNORED_FILES: &'static [&'static str] = &[
"dapps_policy.json",
"dapps_accounts.json",
"dapps_history.json",
"vault.json",
];
#[cfg(not(windows))]
@@ -48,19 +50,43 @@ fn restrict_permissions_to_owner(_file_path: &Path) -> Result<(), i32> {
Ok(())
}
pub struct DiskDirectory {
path: PathBuf,
/// Root keys directory implementation
pub type RootDiskDirectory = DiskDirectory<DiskKeyFileManager>;
/// Disk directory key file manager
pub trait KeyFileManager: Send + Sync {
/// Read `SafeAccount` from given key file stream
fn read<T>(&self, filename: Option<String>, reader: T) -> Result<SafeAccount, Error> where T: io::Read;
/// Write `SafeAccount` to given key file stream
fn write<T>(&self, account: SafeAccount, writer: &mut T) -> Result<(), Error> where T: io::Write;
}
impl DiskDirectory {
/// Disk-based keys directory implementation
pub struct DiskDirectory<T> where T: KeyFileManager {
path: PathBuf,
key_manager: T,
}
/// Keys file manager for root keys directory
pub struct DiskKeyFileManager;
impl RootDiskDirectory {
pub fn create<P>(path: P) -> Result<Self, Error> where P: AsRef<Path> {
fs::create_dir_all(&path)?;
Ok(Self::at(path))
}
pub fn at<P>(path: P) -> Self where P: AsRef<Path> {
DiskDirectory::new(path, DiskKeyFileManager)
}
}
impl<T> DiskDirectory<T> where T: KeyFileManager {
/// Create new disk directory instance
pub fn new<P>(path: P, key_manager: T) -> Self where P: AsRef<Path> {
DiskDirectory {
path: path.as_ref().to_path_buf(),
key_manager: key_manager,
}
}
@@ -85,28 +111,60 @@ impl DiskDirectory {
.collect::<Vec<PathBuf>>();
Ok(paths
.iter()
.map(|p| (
fs::File::open(p)
.map_err(Error::from)
.and_then(|r| json::KeyFile::load(r).map_err(|e| Error::Custom(format!("{:?}", e)))),
p
))
.filter_map(|(file, path)| match file {
Ok(file) => Some((path.clone(), SafeAccount::from_file(
file, Some(path.file_name().and_then(|n| n.to_str()).expect("Keys have valid UTF8 names only.").to_owned())
))),
Err(err) => {
warn!("Invalid key file: {:?} ({})", path, err);
None
},
.into_iter()
.filter_map(|path| {
let filename = Some(path.file_name().and_then(|n| n.to_str()).expect("Keys have valid UTF8 names only.").to_owned());
fs::File::open(path.clone())
.map_err(Into::into)
.and_then(|file| self.key_manager.read(filename, file))
.map_err(|err| {
warn!("Invalid key file: {:?} ({})", path, err);
err
})
.map(|account| (path, account))
.ok()
})
.collect()
)
}
/// insert account with given file name
pub fn insert_with_filename(&self, account: SafeAccount, filename: String) -> Result<SafeAccount, Error> {
// update account filename
let original_account = account.clone();
let mut account = account;
account.filename = Some(filename.clone());
{
// Path to keyfile
let mut keyfile_path = self.path.clone();
keyfile_path.push(filename.as_str());
// save the file
let mut file = fs::File::create(&keyfile_path)?;
if let Err(err) = self.key_manager.write(original_account, &mut file).map_err(|e| Error::Custom(format!("{:?}", e))) {
drop(file);
fs::remove_file(keyfile_path).expect("Expected to remove recently created file");
return Err(err);
}
if let Err(_) = restrict_permissions_to_owner(keyfile_path.as_path()) {
drop(file);
fs::remove_file(keyfile_path).expect("Expected to remove recently created file");
return Err(Error::Io(io::Error::last_os_error()));
}
}
Ok(account)
}
/// Get key file manager
pub fn key_manager(&self) -> &T {
&self.key_manager
}
}
impl KeyDirectory for DiskDirectory {
impl<T> KeyDirectory for DiskDirectory<T> where T: KeyFileManager {
fn load(&self) -> Result<Vec<SafeAccount>, Error> {
let accounts = self.files()?
.into_iter()
@@ -121,35 +179,13 @@ impl KeyDirectory for DiskDirectory {
}
fn insert(&self, account: SafeAccount) -> Result<SafeAccount, Error> {
// transform account into key file
let keyfile: json::KeyFile = account.clone().into();
// build file path
let filename = account.filename.as_ref().cloned().unwrap_or_else(|| {
let timestamp = time::strftime("%Y-%m-%dT%H-%M-%S", &time::now_utc()).expect("Time-format string is valid.");
format!("UTC--{}Z--{}", timestamp, Uuid::from(account.id))
});
// update account filename
let mut account = account;
account.filename = Some(filename.clone());
{
// Path to keyfile
let mut keyfile_path = self.path.clone();
keyfile_path.push(filename.as_str());
// save the file
let mut file = fs::File::create(&keyfile_path)?;
keyfile.write(&mut file).map_err(|e| Error::Custom(format!("{:?}", e)))?;
if let Err(_) = restrict_permissions_to_owner(keyfile_path.as_path()) {
fs::remove_file(keyfile_path).expect("Expected to remove recently created file");
return Err(Error::Io(io::Error::last_os_error()));
}
}
Ok(account)
self.insert_with_filename(account, filename)
}
fn remove(&self, account: &SafeAccount) -> Result<(), Error> {
@@ -167,14 +203,41 @@ impl KeyDirectory for DiskDirectory {
}
fn path(&self) -> Option<&PathBuf> { Some(&self.path) }
fn as_vault_provider(&self) -> Option<&VaultKeyDirectoryProvider> {
Some(self)
}
}
impl<T> VaultKeyDirectoryProvider for DiskDirectory<T> where T: KeyFileManager {
fn create(&self, name: &str, key: VaultKey) -> Result<Box<VaultKeyDirectory>, Error> {
let vault_dir = VaultDiskDirectory::create(&self.path, name, key)?;
Ok(Box::new(vault_dir))
}
fn open(&self, name: &str, key: VaultKey) -> Result<Box<VaultKeyDirectory>, Error> {
let vault_dir = VaultDiskDirectory::at(&self.path, name, key)?;
Ok(Box::new(vault_dir))
}
}
impl KeyFileManager for DiskKeyFileManager {
fn read<T>(&self, filename: Option<String>, reader: T) -> Result<SafeAccount, Error> where T: io::Read {
let key_file = json::KeyFile::load(reader).map_err(|e| Error::Custom(format!("{:?}", e)))?;
Ok(SafeAccount::from_file(key_file, filename))
}
fn write<T>(&self, account: SafeAccount, writer: &mut T) -> Result<(), Error> where T: io::Write {
let key_file: json::KeyFile = account.into();
key_file.write(writer).map_err(|e| Error::Custom(format!("{:?}", e)))
}
}
#[cfg(test)]
mod test {
use std::{env, fs};
use super::DiskDirectory;
use dir::KeyDirectory;
use super::RootDiskDirectory;
use dir::{KeyDirectory, VaultKey};
use account::SafeAccount;
use ethkey::{Random, Generator};
@@ -185,7 +248,7 @@ mod test {
dir.push("ethstore_should_create_new_account");
let keypair = Random.generate().unwrap();
let password = "hello world";
let directory = DiskDirectory::create(dir.clone()).unwrap();
let directory = RootDiskDirectory::create(dir.clone()).unwrap();
// when
let account = SafeAccount::create(&keypair, [0u8; 16], password, 1024, "Test".to_owned(), "{}".to_owned());
@@ -199,4 +262,37 @@ mod test {
// cleanup
let _ = fs::remove_dir_all(dir);
}
#[test]
fn should_manage_vaults() {
// given
let mut dir = env::temp_dir();
dir.push("should_create_new_vault");
let directory = RootDiskDirectory::create(dir.clone()).unwrap();
let vault_name = "vault";
let password = "password";
// then
assert!(directory.as_vault_provider().is_some());
// and when
let before_root_items_count = fs::read_dir(&dir).unwrap().count();
let vault = directory.as_vault_provider().unwrap().create(vault_name, VaultKey::new(password, 1024));
// then
assert!(vault.is_ok());
let after_root_items_count = fs::read_dir(&dir).unwrap().count();
assert!(after_root_items_count > before_root_items_count);
// and when
let vault = directory.as_vault_provider().unwrap().open(vault_name, VaultKey::new(password, 1024));
// then
assert!(vault.is_ok());
let after_root_items_count2 = fs::read_dir(&dir).unwrap().count();
assert!(after_root_items_count == after_root_items_count2);
// cleanup
let _ = fs::remove_dir_all(dir);
}
}