parity_getVaultMeta && parity_setVaultMeta (#4475)

This commit is contained in:
Svyatoslav Nikolsky 2017-02-08 15:53:39 +03:00 committed by Gav Wood
parent b0248cad0b
commit a92bf65181
10 changed files with 162 additions and 23 deletions

View File

@ -493,6 +493,18 @@ impl AccountProvider {
.map_err(Into::into)
.map(|_| ())
}
/// Get vault metadata string.
pub fn get_vault_meta(&self, name: &str) -> Result<String, Error> {
self.sstore.get_vault_meta(name)
.map_err(Into::into)
}
/// Set vault metadata string.
pub fn set_vault_meta(&self, name: &str, meta: &str) -> Result<(), Error> {
self.sstore.set_vault_meta(name, meta)
.map_err(Into::into)
}
}
#[cfg(test)]

View File

@ -158,7 +158,7 @@ impl<T> DiskDirectory<T> where T: KeyFileManager {
Ok(account)
}
/// Get key file manager
/// Get key file manager referece
pub fn key_manager(&self) -> &T {
&self.key_manager
}

View File

@ -84,6 +84,10 @@ pub trait VaultKeyDirectory: KeyDirectory {
fn key(&self) -> VaultKey;
/// Set new key for vault
fn set_key(&self, key: VaultKey) -> Result<(), SetKeyError>;
/// Get vault meta
fn meta(&self) -> String;
/// Set vault meta
fn set_meta(&self, meta: &str) -> Result<(), Error>;
}
pub use self::disk::RootDiskDirectory;

View File

@ -16,6 +16,7 @@
use std::{fs, io};
use std::path::{PathBuf, Path};
use parking_lot::Mutex;
use {json, SafeAccount, Error};
use util::sha3::Hashable;
use super::super::account::Crypto;
@ -24,6 +25,8 @@ use super::disk::{DiskDirectory, KeyFileManager};
/// Name of vault metadata file
pub const VAULT_FILE_NAME: &'static str = "vault.json";
/// Name of temporary vault metadata file
pub const VAULT_TEMP_FILE_NAME: &'static str = "vault_temp.json";
/// Vault directory implementation
pub type VaultDiskDirectory = DiskDirectory<VaultKeyFileManager>;
@ -32,6 +35,7 @@ pub type VaultDiskDirectory = DiskDirectory<VaultKeyFileManager>;
pub struct VaultKeyFileManager {
name: String,
key: VaultKey,
meta: Mutex<String>,
}
impl VaultDiskDirectory {
@ -44,13 +48,14 @@ impl VaultDiskDirectory {
}
// create vault && vault file
let vault_meta = "{}";
fs::create_dir_all(&vault_dir_path)?;
if let Err(err) = create_vault_file(&vault_dir_path, &key) {
if let Err(err) = create_vault_file(&vault_dir_path, &key, vault_meta) {
let _ = fs::remove_dir_all(&vault_dir_path); // can't do anything with this
return Err(err);
}
Ok(DiskDirectory::new(vault_dir_path, VaultKeyFileManager::new(name, key)))
Ok(DiskDirectory::new(vault_dir_path, VaultKeyFileManager::new(name, key, vault_meta)))
}
/// Open existing vault directory with given key
@ -62,9 +67,9 @@ impl VaultDiskDirectory {
}
// check that passed key matches vault file
check_vault_file(&vault_dir_path, &key)?;
let meta = read_vault_file(&vault_dir_path, &key)?;
Ok(DiskDirectory::new(vault_dir_path, VaultKeyFileManager::new(name, key)))
Ok(DiskDirectory::new(vault_dir_path, VaultKeyFileManager::new(name, key, &meta)))
}
fn create_temp_vault(&self, key: VaultKey) -> Result<VaultDiskDirectory, Error> {
@ -145,13 +150,26 @@ impl VaultKeyDirectory for VaultDiskDirectory {
temp_vault.delete().map_err(|err| SetKeyError::NonFatalNew(err))
}
fn meta(&self) -> String {
self.key_manager().meta.lock().clone()
}
fn set_meta(&self, meta: &str) -> Result<(), Error> {
let key_manager = self.key_manager();
let vault_path = self.path().expect("self is instance of DiskDirectory; DiskDirectory always returns path; qed");
create_vault_file(vault_path, &key_manager.key, meta)?;
*key_manager.meta.lock() = meta.to_owned();
Ok(())
}
}
impl VaultKeyFileManager {
pub fn new(name: &str, key: VaultKey) -> Self {
pub fn new(name: &str, key: VaultKey, meta: &str) -> Self {
VaultKeyFileManager {
name: name.into(),
key: key,
meta: Mutex::new(meta.to_owned()),
}
}
}
@ -199,29 +217,37 @@ fn check_vault_name(name: &str) -> bool {
}
/// Vault can be empty, but still must be pluggable => we store vault password in separate file
fn create_vault_file<P>(vault_dir_path: P, key: &VaultKey) -> Result<(), Error> where P: AsRef<Path> {
fn create_vault_file<P>(vault_dir_path: P, key: &VaultKey, meta: &str) -> Result<(), Error> where P: AsRef<Path> {
let password_hash = key.password.sha3();
let crypto = Crypto::with_plain(&password_hash, &key.password, key.iterations);
let mut vault_file_path: PathBuf = vault_dir_path.as_ref().into();
vault_file_path.push(VAULT_FILE_NAME);
let mut temp_vault_file_path: PathBuf = vault_dir_path.as_ref().into();
temp_vault_file_path.push(VAULT_TEMP_FILE_NAME);
let mut vault_file = fs::File::create(vault_file_path)?;
// this method is used to rewrite existing vault file
// => write to temporary file first, then rename temporary file to vault file
let mut vault_file = fs::File::create(&temp_vault_file_path)?;
let vault_file_contents = json::VaultFile {
crypto: crypto.into(),
meta: Some(meta.to_owned()),
};
vault_file_contents.write(&mut vault_file).map_err(|e| Error::Custom(format!("{:?}", e)))?;
drop(vault_file);
fs::rename(&temp_vault_file_path, &vault_file_path)?;
Ok(())
}
/// When vault is opened => we must check that password matches
fn check_vault_file<P>(vault_dir_path: P, key: &VaultKey) -> Result<(), Error> where P: AsRef<Path> {
/// When vault is opened => we must check that password matches && read metadata
fn read_vault_file<P>(vault_dir_path: P, key: &VaultKey) -> Result<String, Error> where P: AsRef<Path> {
let mut vault_file_path: PathBuf = vault_dir_path.as_ref().into();
vault_file_path.push(VAULT_FILE_NAME);
let vault_file = fs::File::open(vault_file_path)?;
let vault_file_contents = json::VaultFile::load(vault_file).map_err(|e| Error::Custom(format!("{:?}", e)))?;
let vault_file_meta = vault_file_contents.meta.unwrap_or("{}".to_owned());
let vault_file_crypto: Crypto = vault_file_contents.crypto.into();
let password_bytes = vault_file_crypto.decrypt(&key.password)?;
@ -230,7 +256,7 @@ fn check_vault_file<P>(vault_dir_path: P, key: &VaultKey) -> Result<(), Error> w
return Err(Error::InvalidPassword);
}
Ok(())
Ok(vault_file_meta)
}
#[cfg(test)]
@ -238,8 +264,8 @@ mod test {
use std::fs;
use std::io::Write;
use std::path::PathBuf;
use dir::VaultKey;
use super::{VAULT_FILE_NAME, check_vault_name, make_vault_dir_path, create_vault_file, check_vault_file, VaultDiskDirectory};
use dir::{VaultKey, VaultKeyDirectory};
use super::{VAULT_FILE_NAME, check_vault_name, make_vault_dir_path, create_vault_file, read_vault_file, VaultDiskDirectory};
use devtools::RandomTempPath;
#[test]
@ -283,7 +309,7 @@ mod test {
fs::create_dir_all(&vault_dir).unwrap();
// when
let result = create_vault_file(&vault_dir, &key);
let result = create_vault_file(&vault_dir, &key, "{}");
// then
assert!(result.is_ok());
@ -293,7 +319,7 @@ mod test {
}
#[test]
fn check_vault_file_succeeds() {
fn read_vault_file_succeeds() {
// given
let temp_path = RandomTempPath::create_dir();
let key = VaultKey::new("password", 1024);
@ -307,14 +333,14 @@ mod test {
}
// when
let result = check_vault_file(&dir, &key);
let result = read_vault_file(&dir, &key);
// then
assert!(result.is_ok());
}
#[test]
fn check_vault_file_fails() {
fn read_vault_file_fails() {
// given
let temp_path = RandomTempPath::create_dir();
let key = VaultKey::new("password1", 1024);
@ -323,7 +349,7 @@ mod test {
vault_file_path.push(VAULT_FILE_NAME);
// when
let result = check_vault_file(&dir, &key);
let result = read_vault_file(&dir, &key);
// then
assert!(result.is_err());
@ -336,7 +362,7 @@ mod test {
}
// when
let result = check_vault_file(&dir, &key);
let result = read_vault_file(&dir, &key);
// then
assert!(result.is_err());
@ -392,4 +418,22 @@ mod test {
// then
assert!(vault.is_err());
}
#[test]
fn vault_directory_can_preserve_meta() {
// given
let temp_path = RandomTempPath::new();
let key = VaultKey::new("password", 1024);
let dir: PathBuf = temp_path.as_path().into();
let vault = VaultDiskDirectory::create(&dir, "vault", key.clone()).unwrap();
// then
assert_eq!(vault.meta(), "{}".to_owned());
assert!(vault.set_meta("Hello, world!!!").is_ok());
assert_eq!(vault.meta(), "Hello, world!!!".to_owned());
// and when
let vault = VaultDiskDirectory::at(&dir, "vault", key.clone()).unwrap();
assert_eq!(vault.meta(), "Hello, world!!!".to_owned());
}
}

View File

@ -107,6 +107,14 @@ impl SimpleSecretStore for EthStore {
fn change_account_vault(&self, vault: SecretVaultRef, account: StoreAccountRef) -> Result<StoreAccountRef, Error> {
self.store.change_account_vault(vault, account)
}
fn get_vault_meta(&self, name: &str) -> Result<String, Error> {
self.store.get_vault_meta(name)
}
fn set_vault_meta(&self, name: &str, meta: &str) -> Result<(), Error> {
self.store.set_vault_meta(name, meta)
}
}
impl SecretStore for EthStore {
@ -491,6 +499,20 @@ impl SimpleSecretStore for EthMultiStore {
self.reload_accounts()?;
Ok(new_account_ref)
}
fn get_vault_meta(&self, name: &str) -> Result<String, Error> {
self.vaults.lock()
.get(name)
.ok_or(Error::VaultNotFound)
.and_then(|v| Ok(v.meta()))
}
fn set_vault_meta(&self, name: &str, meta: &str) -> Result<(), Error> {
self.vaults.lock()
.get(name)
.ok_or(Error::VaultNotFound)
.and_then(|v| v.set_meta(meta))
}
}
#[cfg(test)]

View File

@ -25,10 +25,13 @@ use super::Crypto;
pub struct VaultFile {
/// Vault password, encrypted with vault password
pub crypto: Crypto,
/// Vault metadata string
pub meta: Option<String>,
}
enum VaultFileField {
Crypto,
Meta,
}
impl Deserialize for VaultFileField {
@ -49,6 +52,7 @@ impl Visitor for VaultFileFieldVisitor {
{
match value {
"crypto" => Ok(VaultFileField::Crypto),
"meta" => Ok(VaultFileField::Meta),
_ => Err(Error::custom(format!("Unknown field: '{}'", value))),
}
}
@ -58,7 +62,7 @@ impl Deserialize for VaultFile {
fn deserialize<D>(deserializer: &mut D) -> Result<VaultFile, D::Error>
where D: Deserializer
{
static FIELDS: &'static [&'static str] = &["crypto"];
static FIELDS: &'static [&'static str] = &["crypto", "meta"];
deserializer.deserialize_struct("VaultFile", FIELDS, VaultFileVisitor)
}
}
@ -72,11 +76,13 @@ impl Visitor for VaultFileVisitor {
where V: MapVisitor
{
let mut crypto = None;
let mut meta = None;
loop {
match visitor.visit_key()? {
Some(VaultFileField::Crypto) => { crypto = Some(visitor.visit_value()?); }
None => { break; }
Some(VaultFileField::Crypto) => { crypto = Some(visitor.visit_value()?); },
Some(VaultFileField::Meta) => { meta = Some(visitor.visit_value()?); }
None => { break; },
}
}
@ -89,6 +95,7 @@ impl Visitor for VaultFileVisitor {
let result = VaultFile {
crypto: crypto,
meta: meta,
};
Ok(result)
@ -125,7 +132,8 @@ mod test {
salt: "b6a9338a7ccd39288a86dba73bfecd9101b4f3db9c9830e7c76afdbd4f6872e5".into(),
}),
mac: "16381463ea11c6eb2239a9f339c2e780516d29d234ce30ac5f166f9080b5a262".into(),
}
},
meta: Some("{}".into()),
};
let serialized = serde_json::to_string(&file).unwrap();

View File

@ -65,6 +65,10 @@ pub trait SimpleSecretStore: Send + Sync {
fn change_vault_password(&self, name: &str, new_password: &str) -> Result<(), Error>;
/// Cnage account' vault
fn change_account_vault(&self, vault: SecretVaultRef, account: StoreAccountRef) -> Result<StoreAccountRef, Error>;
/// Get vault metadata string.
fn get_vault_meta(&self, name: &str) -> Result<String, Error>;
/// Set vault metadata string.
fn set_vault_meta(&self, name: &str, meta: &str) -> Result<(), Error>;
}
pub trait SecretStore: SimpleSecretStore {

View File

@ -248,6 +248,19 @@ impl ParityAccounts for ParityAccountsClient {
.map_err(|e| errors::account("Could not change vault.", e))
.map(|_| true)
}
fn get_vault_meta(&self, name: String) -> Result<String, Error> {
take_weak!(self.accounts)
.get_vault_meta(&name)
.map_err(|e| errors::account("Could not get vault metadata.", e))
}
fn set_vault_meta(&self, name: String, meta: String) -> Result<bool, Error> {
take_weak!(self.accounts)
.set_vault_meta(&name, &meta)
.map_err(|e| errors::account("Could not update vault metadata.", e))
.map(|_| true)
}
}
fn into_vec<A, B>(a: Vec<A>) -> Vec<B> where

View File

@ -351,3 +351,27 @@ fn rpc_parity_list_opened_vaults() {
assert!(actual_response == Some(response1.to_owned())
|| actual_response == Some(response2.to_owned()));
}
#[test]
fn rpc_parity_get_set_vault_meta() {
let temp_path = RandomTempPath::new();
let tester = setup_with_vaults_support(temp_path.as_str());
assert!(tester.accounts.create_vault("vault1", "password1").is_ok());
assert!(tester.accounts.set_vault_meta("vault1", "vault1_meta").is_ok());
let request = r#"{"jsonrpc": "2.0", "method": "parity_getVaultMeta", "params":["vault1"], "id": 1}"#;
let response = r#"{"jsonrpc":"2.0","result":"vault1_meta","id":1}"#;
assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned()));
let request = r#"{"jsonrpc": "2.0", "method": "parity_setVaultMeta", "params":["vault1", "updated_vault1_meta"], "id": 1}"#;
let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#;
assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned()));
let request = r#"{"jsonrpc": "2.0", "method": "parity_getVaultMeta", "params":["vault1"], "id": 1}"#;
let response = r#"{"jsonrpc":"2.0","result":"updated_vault1_meta","id":1}"#;
assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned()));
}

View File

@ -133,5 +133,13 @@ build_rpc_trait! {
/// Change vault of the given address.
#[rpc(name = "parity_changeVault")]
fn change_vault(&self, H160, String) -> Result<bool, Error>;
/// Get vault metadata string.
#[rpc(name = "parity_getVaultMeta")]
fn get_vault_meta(&self, String) -> Result<String, Error>;
/// Set vault metadata string.
#[rpc(name = "parity_setVaultMeta")]
fn set_vault_meta(&self, String, String) -> Result<bool, Error>;
}
}