use parking_lot::{Mutex, RwLock};
use std::{
collections::{BTreeMap, HashMap},
path::PathBuf,
time::{Duration, Instant},
};
use crate::{
account::SafeAccount,
accounts_dir::{KeyDirectory, SetKeyError, VaultKey, VaultKeyDirectory},
crypto::KEY_ITERATIONS,
import,
json::{self, OpaqueKeyFile, Uuid},
random::Random,
Derivation, Error, OpaqueSecret, SecretStore, SecretVaultRef,
SimpleSecretStore, StoreAccountRef,
};
use cfxkey::{
self, Address, ExtendedKeyPair, KeyPair, Message, Password, Public, Secret,
Signature,
};
pub struct CfxStore {
store: CfxMultiStore,
}
impl CfxStore {
pub fn open(directory: Box<dyn KeyDirectory>) -> Result<Self, Error> {
Self::open_with_iterations(directory, KEY_ITERATIONS as u32)
}
pub fn open_with_iterations(
directory: Box<dyn KeyDirectory>, iterations: u32,
) -> Result<Self, Error> {
Ok(CfxStore {
store: CfxMultiStore::open_with_iterations(directory, iterations)?,
})
}
pub fn set_refresh_time(&self, time: Duration) {
self.store.set_refresh_time(time)
}
fn get(&self, account: &StoreAccountRef) -> Result<SafeAccount, Error> {
let mut accounts = self.store.get_accounts(account)?.into_iter();
accounts.next().ok_or(Error::InvalidAccount)
}
}
impl SimpleSecretStore for CfxStore {
fn insert_account(
&self, vault: SecretVaultRef, secret: Secret, password: &Password,
) -> Result<StoreAccountRef, Error> {
self.store.insert_account(vault, secret, password)
}
fn insert_derived(
&self, vault: SecretVaultRef, account_ref: &StoreAccountRef,
password: &Password, derivation: Derivation,
) -> Result<StoreAccountRef, Error> {
self.store
.insert_derived(vault, account_ref, password, derivation)
}
fn generate_derived(
&self, account_ref: &StoreAccountRef, password: &Password,
derivation: Derivation,
) -> Result<Address, Error> {
self.store
.generate_derived(account_ref, password, derivation)
}
fn account_ref(&self, address: &Address) -> Result<StoreAccountRef, Error> {
self.store.account_ref(address)
}
fn accounts(&self) -> Result<Vec<StoreAccountRef>, Error> {
self.store.accounts()
}
fn change_password(
&self, account: &StoreAccountRef, old_password: &Password,
new_password: &Password,
) -> Result<(), Error> {
self.store
.change_password(account, old_password, new_password)
}
fn export_account(
&self, account: &StoreAccountRef, password: &Password,
) -> Result<OpaqueKeyFile, Error> {
self.store.export_account(account, password)
}
fn remove_account(
&self, account: &StoreAccountRef, password: &Password,
) -> Result<(), Error> {
self.store.remove_account(account, password)
}
fn sign(
&self, account: &StoreAccountRef, password: &Password,
message: &Message,
) -> Result<Signature, Error> {
self.get(account)?.sign(password, message)
}
fn sign_derived(
&self, account_ref: &StoreAccountRef, password: &Password,
derivation: Derivation, message: &Message,
) -> Result<Signature, Error> {
self.store
.sign_derived(account_ref, password, derivation, message)
}
fn agree(
&self, account: &StoreAccountRef, password: &Password, other: &Public,
) -> Result<Secret, Error> {
self.store.agree(account, password, other)
}
fn decrypt(
&self, account: &StoreAccountRef, password: &Password,
shared_mac: &[u8], message: &[u8],
) -> Result<Vec<u8>, Error> {
let account = self.get(account)?;
account.decrypt(password, shared_mac, message)
}
fn create_vault(
&self, name: &str, password: &Password,
) -> Result<(), Error> {
self.store.create_vault(name, password)
}
fn open_vault(&self, name: &str, password: &Password) -> Result<(), Error> {
self.store.open_vault(name, password)
}
fn close_vault(&self, name: &str) -> Result<(), Error> {
self.store.close_vault(name)
}
fn list_vaults(&self) -> Result<Vec<String>, Error> {
self.store.list_vaults()
}
fn list_opened_vaults(&self) -> Result<Vec<String>, Error> {
self.store.list_opened_vaults()
}
fn change_vault_password(
&self, name: &str, new_password: &Password,
) -> Result<(), Error> {
self.store.change_vault_password(name, new_password)
}
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 CfxStore {
fn raw_secret(
&self, account: &StoreAccountRef, password: &Password,
) -> Result<OpaqueSecret, Error> {
Ok(OpaqueSecret(self.get(account)?.crypto.secret(password)?))
}
fn import_wallet(
&self, vault: SecretVaultRef, json: &[u8], password: &Password,
gen_id: bool,
) -> Result<StoreAccountRef, Error> {
let json_keyfile = json::KeyFile::load(json).map_err(|_| {
Error::InvalidKeyFile("Invalid JSON format".to_owned())
})?;
let mut safe_account =
SafeAccount::from_file(json_keyfile, None, &None)?;
if gen_id {
safe_account.id = Random::random();
}
let secret = safe_account
.crypto
.secret(password)
.map_err(|_| Error::InvalidPassword)?;
safe_account.address = KeyPair::from_secret(secret)?.address();
self.store.import(vault, safe_account)
}
fn test_password(
&self, account: &StoreAccountRef, password: &Password,
) -> Result<bool, Error> {
let account = self.get(account)?;
Ok(account.check_password(password))
}
fn copy_account(
&self, new_store: &dyn SimpleSecretStore, new_vault: SecretVaultRef,
account: &StoreAccountRef, password: &Password,
new_password: &Password,
) -> Result<(), Error> {
let account = self.get(account)?;
let secret = account.crypto.secret(password)?;
new_store.insert_account(new_vault, secret, new_password)?;
Ok(())
}
fn public(
&self, account: &StoreAccountRef, password: &Password,
) -> Result<Public, Error> {
let account = self.get(account)?;
account.public(password)
}
fn uuid(&self, account: &StoreAccountRef) -> Result<Uuid, Error> {
let account = self.get(account)?;
Ok(account.id.into())
}
fn name(&self, account: &StoreAccountRef) -> Result<String, Error> {
let account = self.get(account)?;
Ok(account.name)
}
fn meta(&self, account: &StoreAccountRef) -> Result<String, Error> {
let account = self.get(account)?;
Ok(account.meta)
}
fn set_name(
&self, account_ref: &StoreAccountRef, name: String,
) -> Result<(), Error> {
let old = self.get(account_ref)?;
let mut safe_account = old.clone();
safe_account.name = name;
self.store.update(account_ref, old, safe_account)
}
fn set_meta(
&self, account_ref: &StoreAccountRef, meta: String,
) -> Result<(), Error> {
let old = self.get(account_ref)?;
let mut safe_account = old.clone();
safe_account.meta = meta;
self.store.update(account_ref, old, safe_account)
}
fn local_path(&self) -> PathBuf {
self.store.dir.path().cloned().unwrap_or_else(PathBuf::new)
}
fn list_geth_accounts(&self, testnet: bool) -> Vec<Address> {
import::read_geth_accounts(testnet)
}
fn import_geth_accounts(
&self, vault: SecretVaultRef, desired: Vec<Address>, testnet: bool,
) -> Result<Vec<StoreAccountRef>, Error> {
let imported_addresses = match vault {
SecretVaultRef::Root => import::import_geth_accounts(
&*self.store.dir,
desired.into_iter().collect(),
testnet,
),
SecretVaultRef::Vault(vault_name) => {
if let Some(vault) = self.store.vaults.lock().get(&vault_name) {
import::import_geth_accounts(
vault.as_key_directory(),
desired.into_iter().collect(),
testnet,
)
} else {
Err(Error::VaultNotFound)
}
}
};
imported_addresses
.map(|a| a.into_iter().map(StoreAccountRef::root).collect())
}
}
pub struct CfxMultiStore {
dir: Box<dyn KeyDirectory>,
iterations: u32,
cache: RwLock<BTreeMap<StoreAccountRef, Vec<SafeAccount>>>,
vaults: Mutex<HashMap<String, Box<dyn VaultKeyDirectory>>>,
timestamp: Mutex<Timestamp>,
}
struct Timestamp {
dir_hash: Option<u64>,
last_checked: Instant,
refresh_time: Duration,
}
impl CfxMultiStore {
pub fn open(directory: Box<dyn KeyDirectory>) -> Result<Self, Error> {
Self::open_with_iterations(directory, KEY_ITERATIONS as u32)
}
pub fn open_with_iterations(
directory: Box<dyn KeyDirectory>, iterations: u32,
) -> Result<Self, Error> {
let store = CfxMultiStore {
dir: directory,
vaults: Mutex::new(HashMap::new()),
iterations,
cache: Default::default(),
timestamp: Mutex::new(Timestamp {
dir_hash: None,
last_checked: Instant::now(),
refresh_time: Duration::from_secs(u64::max_value()),
}),
};
store.reload_accounts()?;
Ok(store)
}
pub fn set_refresh_time(&self, time: Duration) {
self.timestamp.lock().refresh_time = time;
}
fn reload_if_changed(&self) -> Result<(), Error> {
let mut last_timestamp = self.timestamp.lock();
let now = Instant::now();
if now - last_timestamp.last_checked > last_timestamp.refresh_time {
let dir_hash = Some(self.dir.unique_repr()?);
last_timestamp.last_checked = now;
if last_timestamp.dir_hash == dir_hash {
return Ok(());
}
self.reload_accounts()?;
last_timestamp.dir_hash = dir_hash;
}
Ok(())
}
fn reload_accounts(&self) -> Result<(), Error> {
let mut cache = self.cache.write();
let mut new_accounts = BTreeMap::new();
for account in self.dir.load()? {
let account_ref = StoreAccountRef::root(account.address);
new_accounts
.entry(account_ref)
.or_insert_with(Vec::new)
.push(account);
}
for (vault_name, vault) in &*self.vaults.lock() {
for account in vault.load()? {
let account_ref =
StoreAccountRef::vault(vault_name, account.address);
new_accounts
.entry(account_ref)
.or_insert_with(Vec::new)
.push(account);
}
}
*cache = new_accounts;
Ok(())
}
fn get_accounts(
&self, account: &StoreAccountRef,
) -> Result<Vec<SafeAccount>, Error> {
let from_cache = |account| {
let cache = self.cache.read();
if let Some(accounts) = cache.get(account) {
if !accounts.is_empty() {
return Some(accounts.clone());
}
}
None
};
match from_cache(account) {
Some(accounts) => Ok(accounts),
None => {
self.reload_if_changed()?;
from_cache(account).ok_or(Error::InvalidAccount)
}
}
}
fn get_matching(
&self, account: &StoreAccountRef, password: &Password,
) -> Result<Vec<SafeAccount>, Error> {
let accounts = self.get_accounts(account)?;
Ok(accounts
.into_iter()
.filter(|acc| acc.check_password(password))
.collect())
}
fn import(
&self, vault: SecretVaultRef, account: SafeAccount,
) -> Result<StoreAccountRef, Error> {
let account = match vault {
SecretVaultRef::Root => self.dir.insert(account)?,
SecretVaultRef::Vault(ref vault_name) => self
.vaults
.lock()
.get_mut(vault_name)
.ok_or(Error::VaultNotFound)?
.insert(account)?,
};
let account_ref = StoreAccountRef::new(vault, account.address.clone());
let mut cache = self.cache.write();
cache
.entry(account_ref.clone())
.or_insert_with(Vec::new)
.push(account);
Ok(account_ref)
}
fn update(
&self, account_ref: &StoreAccountRef, old: SafeAccount,
new: SafeAccount,
) -> Result<(), Error> {
let account = match account_ref.vault {
SecretVaultRef::Root => self.dir.update(new)?,
SecretVaultRef::Vault(ref vault_name) => self
.vaults
.lock()
.get_mut(vault_name)
.ok_or(Error::VaultNotFound)?
.update(new)?,
};
let mut cache = self.cache.write();
let accounts =
cache.entry(account_ref.clone()).or_insert_with(Vec::new);
accounts.retain(|acc| acc != &old);
accounts.push(account);
Ok(())
}
fn remove_safe_account(
&self, account_ref: &StoreAccountRef, account: &SafeAccount,
) -> Result<(), Error> {
match account_ref.vault {
SecretVaultRef::Root => self.dir.remove(&account)?,
SecretVaultRef::Vault(ref vault_name) => self
.vaults
.lock()
.get(vault_name)
.ok_or(Error::VaultNotFound)?
.remove(&account)?,
};
let mut cache = self.cache.write();
let is_empty = {
if let Some(accounts) = cache.get_mut(account_ref) {
if let Some(position) =
accounts.iter().position(|acc| acc == account)
{
accounts.remove(position);
}
accounts.is_empty()
} else {
false
}
};
if is_empty {
cache.remove(account_ref);
}
Ok(())
}
fn generate(
&self, secret: Secret, derivation: Derivation,
) -> Result<ExtendedKeyPair, Error> {
let mut extended = ExtendedKeyPair::new(secret);
match derivation {
Derivation::Hierarchical(path) => {
for path_item in path {
extended = extended.derive(
if path_item.soft {
cfxkey::Derivation::Soft(path_item.index)
} else {
cfxkey::Derivation::Hard(path_item.index)
},
)?;
}
}
Derivation::SoftHash(h256) => {
extended = extended.derive(cfxkey::Derivation::Soft(h256))?;
}
Derivation::HardHash(h256) => {
extended = extended.derive(cfxkey::Derivation::Hard(h256))?;
}
}
Ok(extended)
}
}
impl SimpleSecretStore for CfxMultiStore {
fn insert_account(
&self, vault: SecretVaultRef, secret: Secret, password: &Password,
) -> Result<StoreAccountRef, Error> {
let keypair =
KeyPair::from_secret(secret).map_err(|_| Error::CreationFailed)?;
let id: [u8; 16] = Random::random();
let account = SafeAccount::create(
&keypair,
id,
password,
self.iterations,
"".to_owned(),
"{}".to_owned(),
)?;
self.import(vault, account)
}
fn insert_derived(
&self, vault: SecretVaultRef, account_ref: &StoreAccountRef,
password: &Password, derivation: Derivation,
) -> Result<StoreAccountRef, Error> {
let accounts = self.get_matching(account_ref, password)?;
if let Some(account) = accounts.first() {
let extended =
self.generate(account.crypto.secret(password)?, derivation)?;
self.insert_account(
vault,
extended.secret().as_raw().clone(),
password,
)
} else {
Err(Error::InvalidPassword)
}
}
fn generate_derived(
&self, account_ref: &StoreAccountRef, password: &Password,
derivation: Derivation,
) -> Result<Address, Error> {
let accounts = self.get_matching(&account_ref, password)?;
if let Some(account) = accounts.first() {
let extended =
self.generate(account.crypto.secret(password)?, derivation)?;
Ok(cfxkey::public_to_address(extended.public().public(), true))
} else {
Err(Error::InvalidPassword)
}
}
fn sign_derived(
&self, account_ref: &StoreAccountRef, password: &Password,
derivation: Derivation, message: &Message,
) -> Result<Signature, Error> {
let accounts = self.get_matching(&account_ref, password)?;
if let Some(account) = accounts.first() {
let extended =
self.generate(account.crypto.secret(password)?, derivation)?;
let secret = extended.secret().as_raw();
Ok(cfxkey::sign(&secret, message)?)
} else {
Err(Error::InvalidPassword)
}
}
fn account_ref(&self, address: &Address) -> Result<StoreAccountRef, Error> {
let read_from_cache = |address: &Address| {
use std::collections::Bound;
let cache = self.cache.read();
let mut r = cache
.range((Bound::Included(*address), Bound::Included(*address)));
r.next().map(|(k, _)| k.clone())
};
match read_from_cache(address) {
Some(account) => Ok(account),
None => {
self.reload_if_changed()?;
read_from_cache(address).ok_or(Error::InvalidAccount)
}
}
}
fn accounts(&self) -> Result<Vec<StoreAccountRef>, Error> {
self.reload_if_changed()?;
Ok(self.cache.read().keys().cloned().collect())
}
fn remove_account(
&self, account_ref: &StoreAccountRef, password: &Password,
) -> Result<(), Error> {
let accounts = self.get_matching(account_ref, password)?;
if let Some(account) = accounts.first() {
self.remove_safe_account(account_ref, &account)
} else {
Err(Error::InvalidPassword)
}
}
fn change_password(
&self, account_ref: &StoreAccountRef, old_password: &Password,
new_password: &Password,
) -> Result<(), Error> {
let accounts = self.get_matching(account_ref, old_password)?;
if accounts.is_empty() {
return Err(Error::InvalidPassword);
}
for account in accounts {
let new_account = account.change_password(
old_password,
new_password,
self.iterations,
)?;
self.update(account_ref, account, new_account)?;
}
Ok(())
}
fn export_account(
&self, account_ref: &StoreAccountRef, password: &Password,
) -> Result<OpaqueKeyFile, Error> {
self.get_matching(account_ref, password)?
.into_iter()
.nth(0)
.map(Into::into)
.ok_or(Error::InvalidPassword)
}
fn sign(
&self, account: &StoreAccountRef, password: &Password,
message: &Message,
) -> Result<Signature, Error> {
let accounts = self.get_matching(account, password)?;
match accounts.first() {
Some(ref account) => account.sign(password, message),
None => Err(Error::InvalidPassword),
}
}
fn decrypt(
&self, account: &StoreAccountRef, password: &Password,
shared_mac: &[u8], message: &[u8],
) -> Result<Vec<u8>, Error> {
let accounts = self.get_matching(account, password)?;
match accounts.first() {
Some(ref account) => account.decrypt(password, shared_mac, message),
None => Err(Error::InvalidPassword),
}
}
fn agree(
&self, account: &StoreAccountRef, password: &Password, other: &Public,
) -> Result<Secret, Error> {
let accounts = self.get_matching(account, password)?;
match accounts.first() {
Some(ref account) => account.agree(password, other),
None => Err(Error::InvalidPassword),
}
}
fn create_vault(
&self, name: &str, password: &Password,
) -> Result<(), Error> {
let is_vault_created = {
let mut vaults = self.vaults.lock();
if !vaults.contains_key(&name.to_owned()) {
let vault_provider = self
.dir
.as_vault_provider()
.ok_or(Error::VaultsAreNotSupported)?;
let vault = vault_provider
.create(name, VaultKey::new(password, self.iterations))?;
vaults.insert(name.to_owned(), vault);
true
} else {
false
}
};
if is_vault_created {
self.reload_accounts()?;
}
Ok(())
}
fn open_vault(&self, name: &str, password: &Password) -> Result<(), Error> {
let is_vault_opened = {
let mut vaults = self.vaults.lock();
if !vaults.contains_key(&name.to_owned()) {
let vault_provider = self
.dir
.as_vault_provider()
.ok_or(Error::VaultsAreNotSupported)?;
let vault = vault_provider
.open(name, VaultKey::new(password, self.iterations))?;
vaults.insert(name.to_owned(), vault);
true
} else {
false
}
};
if is_vault_opened {
self.reload_accounts()?;
}
Ok(())
}
fn close_vault(&self, name: &str) -> Result<(), Error> {
let is_vault_removed =
self.vaults.lock().remove(&name.to_owned()).is_some();
if is_vault_removed {
self.reload_accounts()?;
}
Ok(())
}
fn list_vaults(&self) -> Result<Vec<String>, Error> {
let vault_provider = self
.dir
.as_vault_provider()
.ok_or(Error::VaultsAreNotSupported)?;
vault_provider.list_vaults()
}
fn list_opened_vaults(&self) -> Result<Vec<String>, Error> {
Ok(self.vaults.lock().keys().cloned().collect())
}
fn change_vault_password(
&self, name: &str, new_password: &Password,
) -> Result<(), Error> {
let old_key = self
.vaults
.lock()
.get(name)
.map(|v| v.key())
.ok_or(Error::VaultNotFound)?;
let vault_provider = self
.dir
.as_vault_provider()
.ok_or(Error::VaultsAreNotSupported)?;
let vault = vault_provider.open(name, old_key)?;
match vault.set_key(VaultKey::new(new_password, self.iterations)) {
Ok(_) => self
.close_vault(name)
.and_then(|_| self.open_vault(name, new_password)),
Err(SetKeyError::Fatal(err)) => {
let _ = self.close_vault(name);
Err(err)
}
Err(SetKeyError::NonFatalNew(err)) => {
let _ = self
.close_vault(name)
.and_then(|_| self.open_vault(name, new_password));
Err(err)
}
Err(SetKeyError::NonFatalOld(err)) => Err(err),
}
}
fn change_account_vault(
&self, vault: SecretVaultRef, account_ref: StoreAccountRef,
) -> Result<StoreAccountRef, Error> {
if account_ref.vault == vault {
return Ok(account_ref);
}
let account = self
.get_accounts(&account_ref)?
.into_iter()
.nth(0)
.ok_or(Error::InvalidAccount)?;
let new_account_ref = self.import(vault, account.clone())?;
self.remove_safe_account(&account_ref, &account)?;
self.reload_accounts()?;
Ok(new_account_ref)
}
fn get_vault_meta(&self, name: &str) -> Result<String, Error> {
self.vaults
.lock()
.get(name)
.map(|v| v.meta())
.ok_or(Error::VaultNotFound)
.or_else(|_| {
let vault_provider = self
.dir
.as_vault_provider()
.ok_or(Error::VaultsAreNotSupported)?;
vault_provider.vault_meta(name)
})
}
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)]
mod tests {
use super::{CfxMultiStore, CfxStore};
use crate::{
accounts_dir::{KeyDirectory, MemoryDirectory, RootDiskDirectory},
secret_store::{
Derivation, SecretStore, SecretVaultRef, SimpleSecretStore,
StoreAccountRef,
},
};
use cfx_types::H256;
use cfxkey::{Generator, KeyPair, Random};
use tempdir::TempDir;
fn keypair() -> KeyPair { Random.generate().unwrap() }
fn store() -> CfxStore {
CfxStore::open(Box::new(MemoryDirectory::default()))
.expect("MemoryDirectory always load successfuly; qed")
}
fn multi_store() -> CfxMultiStore {
CfxMultiStore::open(Box::new(MemoryDirectory::default()))
.expect("MemoryDirectory always load successfuly; qed")
}
struct RootDiskDirectoryGuard {
pub key_dir: Option<Box<dyn KeyDirectory>>,
_path: TempDir,
}
impl RootDiskDirectoryGuard {
pub fn new() -> Self {
let temp_path = TempDir::new("").unwrap();
let disk_dir =
Box::new(RootDiskDirectory::create(temp_path.path()).unwrap());
RootDiskDirectoryGuard {
key_dir: Some(disk_dir),
_path: temp_path,
}
}
}
#[test]
fn should_insert_account_successfully() {
let store = store();
let keypair = keypair();
let passwd = "test".into();
let address = store
.insert_account(
SecretVaultRef::Root,
keypair.secret().clone(),
&passwd,
)
.unwrap();
assert_eq!(address, StoreAccountRef::root(keypair.address()));
assert!(store.get(&address).is_ok(), "Should contain account.");
assert_eq!(
store.accounts().unwrap().len(),
1,
"Should have one account."
);
}
#[test]
fn should_update_meta_and_name() {
let store = store();
let keypair = keypair();
let passwd = "test".into();
let address = store
.insert_account(
SecretVaultRef::Root,
keypair.secret().clone(),
&passwd,
)
.unwrap();
assert_eq!(&store.meta(&address).unwrap(), "{}");
assert_eq!(&store.name(&address).unwrap(), "");
store.set_meta(&address, "meta".into()).unwrap();
store.set_name(&address, "name".into()).unwrap();
assert_eq!(&store.meta(&address).unwrap(), "meta");
assert_eq!(&store.name(&address).unwrap(), "name");
assert_eq!(store.accounts().unwrap().len(), 1);
}
#[test]
fn should_remove_account() {
let store = store();
let passwd = "test".into();
let keypair = keypair();
let address = store
.insert_account(
SecretVaultRef::Root,
keypair.secret().clone(),
&passwd,
)
.unwrap();
store.remove_account(&address, &passwd).unwrap();
assert_eq!(
store.accounts().unwrap().len(),
0,
"Should remove account."
);
}
#[test]
fn should_return_true_if_password_is_correct() {
let store = store();
let passwd = "test".into();
let keypair = keypair();
let address = store
.insert_account(
SecretVaultRef::Root,
keypair.secret().clone(),
&passwd,
)
.unwrap();
let res1 = store.test_password(&address, &"x".into()).unwrap();
let res2 = store.test_password(&address, &passwd).unwrap();
assert!(!res1, "First password should be invalid.");
assert!(res2, "Second password should be correct.");
}
#[test]
fn multistore_should_be_able_to_have_the_same_account_twice() {
let store = multi_store();
let passwd1 = "test".into();
let passwd2 = "xyz".into();
let keypair = keypair();
let address = store
.insert_account(
SecretVaultRef::Root,
keypair.secret().clone(),
&passwd1,
)
.unwrap();
let address2 = store
.insert_account(
SecretVaultRef::Root,
keypair.secret().clone(),
&passwd2,
)
.unwrap();
assert_eq!(address, address2);
assert!(
store.remove_account(&address, &passwd1).is_ok(),
"First password should work."
);
assert_eq!(store.accounts().unwrap().len(), 1);
assert!(
store.remove_account(&address, &passwd2).is_ok(),
"Second password should work too."
);
assert_eq!(store.accounts().unwrap().len(), 0);
}
#[test]
fn should_copy_account() {
let store = store();
let passwd1 = "test".into();
let passwd2 = "xzy".into();
let multi_store = multi_store();
let keypair = keypair();
let address = store
.insert_account(
SecretVaultRef::Root,
keypair.secret().clone(),
&passwd1,
)
.unwrap();
assert_eq!(multi_store.accounts().unwrap().len(), 0);
store
.copy_account(
&multi_store,
SecretVaultRef::Root,
&address,
&passwd1,
&passwd2,
)
.unwrap();
assert!(
store.test_password(&address, &passwd1).unwrap(),
"First password should work for store."
);
assert!(
multi_store
.sign(&address, &passwd2, &Default::default())
.is_ok(),
"Second password should work for second store."
);
assert_eq!(multi_store.accounts().unwrap().len(), 1);
}
#[test]
fn should_create_and_open_vaults() {
let mut dir = RootDiskDirectoryGuard::new();
let store = CfxStore::open(dir.key_dir.take().unwrap()).unwrap();
let name1 = "vault1";
let password1 = "password1".into();
let name2 = "vault2";
let password2 = "password2".into();
let keypair1 = keypair();
let keypair2 = keypair();
let keypair3 = keypair();
let password3 = "password3".into();
store.create_vault(name1, &password1).unwrap();
store.create_vault(name2, &password2).unwrap();
store
.insert_account(
SecretVaultRef::Vault(name1.to_owned()),
keypair1.secret().clone(),
&password1,
)
.unwrap();
store
.insert_account(
SecretVaultRef::Vault(name2.to_owned()),
keypair2.secret().clone(),
&password2,
)
.unwrap();
store
.insert_account(
SecretVaultRef::Root,
keypair3.secret().clone(),
&password3,
)
.unwrap();
store
.insert_account(
SecretVaultRef::Vault("vault3".to_owned()),
keypair1.secret().clone(),
&password3,
)
.unwrap_err();
let accounts = store.accounts().unwrap();
assert_eq!(accounts.len(), 3);
assert!(accounts.iter().any(|a| a.vault == SecretVaultRef::Root));
assert!(accounts
.iter()
.any(|a| a.vault == SecretVaultRef::Vault(name1.to_owned())));
assert!(accounts
.iter()
.any(|a| a.vault == SecretVaultRef::Vault(name2.to_owned())));
store.close_vault(name1).unwrap();
store.close_vault(name2).unwrap();
store.close_vault("vault3").unwrap();
let accounts = store.accounts().unwrap();
assert_eq!(accounts.len(), 1);
assert!(accounts.iter().any(|a| a.vault == SecretVaultRef::Root));
store.open_vault(name1, &password2).unwrap_err();
store.open_vault(name2, &password1).unwrap_err();
store.open_vault(name1, &password1).unwrap();
store.open_vault(name2, &password2).unwrap();
let accounts = store.accounts().unwrap();
assert_eq!(accounts.len(), 3);
assert!(accounts.iter().any(|a| a.vault == SecretVaultRef::Root));
assert!(accounts
.iter()
.any(|a| a.vault == SecretVaultRef::Vault(name1.to_owned())));
assert!(accounts
.iter()
.any(|a| a.vault == SecretVaultRef::Vault(name2.to_owned())));
}
#[test]
fn should_move_vault_acounts() {
let mut dir = RootDiskDirectoryGuard::new();
let store = CfxStore::open(dir.key_dir.take().unwrap()).unwrap();
let name1 = "vault1";
let password1 = "password1".into();
let name2 = "vault2";
let password2 = "password2".into();
let password3 = "password3".into();
let keypair1 = keypair();
let keypair2 = keypair();
let keypair3 = keypair();
store.create_vault(name1, &password1).unwrap();
store.create_vault(name2, &password2).unwrap();
let account1 = store
.insert_account(
SecretVaultRef::Vault(name1.to_owned()),
keypair1.secret().clone(),
&password1,
)
.unwrap();
let account2 = store
.insert_account(
SecretVaultRef::Vault(name1.to_owned()),
keypair2.secret().clone(),
&password1,
)
.unwrap();
let account3 = store
.insert_account(
SecretVaultRef::Root,
keypair3.secret().clone(),
&password3,
)
.unwrap();
let account1 = store
.change_account_vault(SecretVaultRef::Root, account1)
.unwrap();
let account2 = store
.change_account_vault(
SecretVaultRef::Vault(name2.to_owned()),
account2,
)
.unwrap();
let account3 = store
.change_account_vault(
SecretVaultRef::Vault(name2.to_owned()),
account3,
)
.unwrap();
let accounts = store.accounts().unwrap();
assert_eq!(accounts.len(), 3);
assert!(accounts
.iter()
.any(|a| a == &StoreAccountRef::root(account1.address.clone())));
assert!(accounts
.iter()
.any(|a| a
== &StoreAccountRef::vault(name2, account2.address.clone())));
assert!(accounts
.iter()
.any(|a| a
== &StoreAccountRef::vault(name2, account3.address.clone())));
assert_eq!(
store
.meta(&StoreAccountRef::root(account1.address))
.unwrap(),
r#"{}"#
);
assert_eq!(
store
.meta(&StoreAccountRef::vault("vault2", account2.address))
.unwrap(),
r#"{"vault":"vault2"}"#
);
assert_eq!(
store
.meta(&StoreAccountRef::vault("vault2", account3.address))
.unwrap(),
r#"{"vault":"vault2"}"#
);
}
#[test]
fn should_not_remove_account_when_moving_to_self() {
let mut dir = RootDiskDirectoryGuard::new();
let store = CfxStore::open(dir.key_dir.take().unwrap()).unwrap();
let password1 = "password1".into();
let keypair1 = keypair();
let account1 = store
.insert_account(
SecretVaultRef::Root,
keypair1.secret().clone(),
&password1,
)
.unwrap();
store
.change_account_vault(SecretVaultRef::Root, account1)
.unwrap();
let accounts = store.accounts().unwrap();
assert_eq!(accounts.len(), 1);
}
#[test]
fn should_remove_account_from_vault() {
let mut dir = RootDiskDirectoryGuard::new();
let store = CfxStore::open(dir.key_dir.take().unwrap()).unwrap();
let name1 = "vault1";
let password1 = "password1".into();
let keypair1 = keypair();
store.create_vault(name1, &password1).unwrap();
let account1 = store
.insert_account(
SecretVaultRef::Vault(name1.to_owned()),
keypair1.secret().clone(),
&password1,
)
.unwrap();
assert_eq!(store.accounts().unwrap().len(), 1);
store.remove_account(&account1, &password1).unwrap();
assert_eq!(store.accounts().unwrap().len(), 0);
}
#[test]
fn should_not_remove_account_from_vault_when_password_is_incorrect() {
let mut dir = RootDiskDirectoryGuard::new();
let store = CfxStore::open(dir.key_dir.take().unwrap()).unwrap();
let name1 = "vault1";
let password1 = "password1".into();
let password2 = "password2".into();
let keypair1 = keypair();
store.create_vault(name1, &password1).unwrap();
let account1 = store
.insert_account(
SecretVaultRef::Vault(name1.to_owned()),
keypair1.secret().clone(),
&password1,
)
.unwrap();
assert_eq!(store.accounts().unwrap().len(), 1);
store.remove_account(&account1, &password2).unwrap_err();
assert_eq!(store.accounts().unwrap().len(), 1);
}
#[test]
fn should_change_vault_password() {
let mut dir = RootDiskDirectoryGuard::new();
let store = CfxStore::open(dir.key_dir.take().unwrap()).unwrap();
let name = "vault";
let password = "password".into();
let keypair = keypair();
store.create_vault(name, &password).unwrap();
store
.insert_account(
SecretVaultRef::Vault(name.to_owned()),
keypair.secret().clone(),
&password,
)
.unwrap();
assert_eq!(store.accounts().unwrap().len(), 1);
let new_password = "new_password".into();
store.change_vault_password(name, &new_password).unwrap();
assert_eq!(store.accounts().unwrap().len(), 1);
store.close_vault(name).unwrap();
store.open_vault(name, &new_password).unwrap();
assert_eq!(store.accounts().unwrap().len(), 1);
}
#[test]
fn should_have_different_passwords_for_vault_secret_and_meta() {
let mut dir = RootDiskDirectoryGuard::new();
let store = CfxStore::open(dir.key_dir.take().unwrap()).unwrap();
let name = "vault";
let password = "password".into();
let secret_password = "sec_password".into();
let keypair = keypair();
store.create_vault(name, &password).unwrap();
let account_ref = store
.insert_account(
SecretVaultRef::Vault(name.to_owned()),
keypair.secret().clone(),
&secret_password,
)
.unwrap();
assert_eq!(store.accounts().unwrap().len(), 1);
let new_secret_password = "new_sec_password".into();
store
.change_password(
&account_ref,
&secret_password,
&new_secret_password,
)
.unwrap();
assert_eq!(store.accounts().unwrap().len(), 1);
}
#[test]
fn should_list_opened_vaults() {
let mut dir = RootDiskDirectoryGuard::new();
let store = CfxStore::open(dir.key_dir.take().unwrap()).unwrap();
let name1 = "vault1";
let password1 = "password1".into();
let name2 = "vault2";
let password2 = "password2".into();
let name3 = "vault3";
let password3 = "password3".into();
store.create_vault(name1, &password1).unwrap();
store.create_vault(name2, &password2).unwrap();
store.create_vault(name3, &password3).unwrap();
store.close_vault(name2).unwrap();
let opened_vaults = store.list_opened_vaults().unwrap();
assert_eq!(opened_vaults.len(), 2);
assert!(opened_vaults.iter().any(|v| *v == name1));
assert!(opened_vaults.iter().any(|v| *v == name3));
}
#[test]
fn should_manage_vaults_meta() {
let mut dir = RootDiskDirectoryGuard::new();
let store = CfxStore::open(dir.key_dir.take().unwrap()).unwrap();
let name1 = "vault1";
let password1 = "password1".into();
store.create_vault(name1, &password1).unwrap();
assert_eq!(store.get_vault_meta(name1).unwrap(), "{}".to_owned());
assert!(store.set_vault_meta(name1, "Hello, world!!!").is_ok());
assert_eq!(
store.get_vault_meta(name1).unwrap(),
"Hello, world!!!".to_owned()
);
store.close_vault(name1).unwrap();
store.open_vault(name1, &password1).unwrap();
assert_eq!(
store.get_vault_meta(name1).unwrap(),
"Hello, world!!!".to_owned()
);
store.close_vault(name1).unwrap();
assert_eq!(
store.get_vault_meta(name1).unwrap(),
"Hello, world!!!".to_owned()
);
assert!(store.get_vault_meta("vault2").is_err());
}
#[test]
fn should_store_derived_keys() {
let store = store();
let keypair = keypair();
let address = store
.insert_account(
SecretVaultRef::Root,
keypair.secret().clone(),
&"test".into(),
)
.unwrap();
let derived = store
.insert_derived(
SecretVaultRef::Root,
&address,
&"test".into(),
Derivation::HardHash(H256::zero()),
)
.unwrap();
let accounts = store.accounts().unwrap();
assert_eq!(accounts.len(), 2);
assert!(
store
.sign(&derived, &"test".into(), &Default::default())
.is_ok(),
"Second password should work for second store."
);
}
#[test]
fn should_save_meta_when_setting_before_password() {
let mut dir = RootDiskDirectoryGuard::new();
let store = CfxStore::open(dir.key_dir.take().unwrap()).unwrap();
let name = "vault";
let password = "password1".into();
let new_password = "password2".into();
store.create_vault(name, &password).unwrap();
store.set_vault_meta(name, "OldMeta").unwrap();
store.change_vault_password(name, &new_password).unwrap();
assert_eq!(store.get_vault_meta(name).unwrap(), "OldMeta".to_owned());
}
#[test]
fn should_export_account() {
let store = store();
let keypair = keypair();
let address = store
.insert_account(
SecretVaultRef::Root,
keypair.secret().clone(),
&"test".into(),
)
.unwrap();
let exported = store.export_account(&address, &"test".into());
assert!(
exported.is_ok(),
"Should export single account: {:?}",
exported
);
}
}