#![warn(missing_docs)]
mod account_data;
mod error;
mod stores;
use self::{
account_data::{AccountData, Unlock},
stores::AddressBook,
};
use std::{
collections::HashMap,
time::{Duration, Instant},
};
use cfxkey::{Address, Generator, Message, Password, Public, Random, Secret};
use cfxstore::{
accounts_dir::MemoryDirectory, random_string, CfxMultiStore, CfxStore,
OpaqueSecret, SecretStore, SecretVaultRef, SimpleSecretStore,
StoreAccountRef,
};
use log::warn;
use parking_lot::RwLock;
pub use cfxkey::Signature;
pub use cfxstore::{Derivation, Error, IndexDerivation, KeyFile};
pub use self::{account_data::AccountMeta, error::SignError};
type AccountToken = Password;
#[derive(Debug, Default)]
pub struct AccountProviderSettings {
pub unlock_keep_secret: bool,
pub blacklisted_accounts: Vec<Address>,
}
pub struct AccountProvider {
unlocked_secrets: RwLock<HashMap<StoreAccountRef, OpaqueSecret>>,
unlocked: RwLock<HashMap<StoreAccountRef, AccountData>>,
address_book: RwLock<AddressBook>,
sstore: Box<dyn SecretStore>,
transient_sstore: CfxMultiStore,
unlock_keep_secret: bool,
blacklisted_accounts: Vec<Address>,
}
fn transient_sstore() -> CfxMultiStore {
CfxMultiStore::open(Box::new(MemoryDirectory::default()))
.expect("MemoryDirectory load always succeeds; qed")
}
impl AccountProvider {
pub fn new(
sstore: Box<dyn SecretStore>, settings: AccountProviderSettings,
) -> Self {
if let Ok(accounts) = sstore.accounts() {
for account in accounts
.into_iter()
.filter(|a| settings.blacklisted_accounts.contains(&a.address))
{
warn!("Local Account {} has a blacklisted (known to be weak) address and will be ignored",
account.address);
}
}
let mut address_book = AddressBook::new(&sstore.local_path());
for addr in &settings.blacklisted_accounts {
address_book.remove(*addr);
}
AccountProvider {
unlocked_secrets: RwLock::new(HashMap::new()),
unlocked: RwLock::new(HashMap::new()),
address_book: RwLock::new(address_book),
sstore,
transient_sstore: transient_sstore(),
unlock_keep_secret: settings.unlock_keep_secret,
blacklisted_accounts: settings.blacklisted_accounts,
}
}
pub fn transient_provider() -> Self {
AccountProvider {
unlocked_secrets: RwLock::new(HashMap::new()),
unlocked: RwLock::new(HashMap::new()),
address_book: RwLock::new(AddressBook::transient()),
sstore: Box::new(
CfxStore::open(Box::new(MemoryDirectory::default()))
.expect("MemoryDirectory load always succeeds; qed"),
),
transient_sstore: transient_sstore(),
unlock_keep_secret: false,
blacklisted_accounts: vec![],
}
}
pub fn new_account(&self, password: &Password) -> Result<Address, Error> {
self.new_account_and_public(password).map(|d| d.0)
}
pub fn new_account_and_public(
&self, password: &Password,
) -> Result<(Address, Public), Error> {
let acc = Random
.generate()
.expect("secp context has generation capabilities; qed");
let public = acc.public().clone();
let secret = acc.secret().clone();
let account = self.sstore.insert_account(
SecretVaultRef::Root,
secret,
password,
)?;
Ok((account.address, public))
}
pub fn insert_account(
&self, secret: Secret, password: &Password,
) -> Result<Address, Error> {
let account = self.sstore.insert_account(
SecretVaultRef::Root,
secret,
password,
)?;
if self.blacklisted_accounts.contains(&account.address) {
self.sstore.remove_account(&account, password)?;
return Err(Error::InvalidAccount);
}
Ok(account.address)
}
pub fn derive_account(
&self, address: &Address, password: Option<Password>,
derivation: Derivation, save: bool,
) -> Result<Address, SignError> {
let account = self.sstore.account_ref(&address)?;
let password = password
.map(Ok)
.unwrap_or_else(|| self.password(&account))?;
Ok(if save {
self.sstore
.insert_derived(
SecretVaultRef::Root,
&account,
&password,
derivation,
)?
.address
} else {
self.sstore
.generate_derived(&account, &password, derivation)?
})
}
pub fn import_wallet(
&self, json: &[u8], password: &Password, gen_id: bool,
) -> Result<Address, Error> {
let account = self.sstore.import_wallet(
SecretVaultRef::Root,
json,
password,
gen_id,
)?;
if self.blacklisted_accounts.contains(&account.address) {
self.sstore.remove_account(&account, password)?;
return Err(Error::InvalidAccount);
}
Ok(account.address)
}
pub fn has_account(&self, address: Address) -> bool {
self.sstore.account_ref(&address).is_ok()
&& !self.blacklisted_accounts.contains(&address)
}
pub fn accounts(&self) -> Result<Vec<Address>, Error> {
let accounts = self.sstore.accounts()?;
Ok(accounts
.into_iter()
.map(|a| a.address)
.filter(|address| !self.blacklisted_accounts.contains(address))
.collect())
}
pub fn default_account(&self) -> Result<Address, Error> {
Ok(self.accounts()?.first().cloned().unwrap_or_default())
}
pub fn addresses_info(&self) -> HashMap<Address, AccountMeta> {
self.address_book.read().get()
}
pub fn set_address_name(&self, account: Address, name: String) {
self.address_book.write().set_name(account, name)
}
pub fn set_address_meta(&self, account: Address, meta: String) {
self.address_book.write().set_meta(account, meta)
}
pub fn remove_address(&self, addr: Address) {
self.address_book.write().remove(addr)
}
pub fn accounts_info(
&self,
) -> Result<HashMap<Address, AccountMeta>, Error> {
let r = self
.sstore
.accounts()?
.into_iter()
.filter(|a| !self.blacklisted_accounts.contains(&a.address))
.map(|a| {
(
a.address.clone(),
self.account_meta(a.address).ok().unwrap_or_default(),
)
})
.collect();
Ok(r)
}
pub fn account_meta(&self, address: Address) -> Result<AccountMeta, Error> {
let account = self.sstore.account_ref(&address)?;
Ok(AccountMeta {
name: self.sstore.name(&account)?,
meta: self.sstore.meta(&account)?,
uuid: self.sstore.uuid(&account).ok().map(Into::into), })
}
pub fn account_public(
&self, address: Address, password: &Password,
) -> Result<Public, Error> {
self.sstore
.public(&self.sstore.account_ref(&address)?, password)
}
pub fn set_account_name(
&self, address: Address, name: String,
) -> Result<(), Error> {
self.sstore
.set_name(&self.sstore.account_ref(&address)?, name)?;
Ok(())
}
pub fn set_account_meta(
&self, address: Address, meta: String,
) -> Result<(), Error> {
self.sstore
.set_meta(&self.sstore.account_ref(&address)?, meta)?;
Ok(())
}
pub fn test_password(
&self, address: &Address, password: &Password,
) -> Result<bool, Error> {
self.sstore
.test_password(&self.sstore.account_ref(&address)?, password)
.map_err(Into::into)
}
pub fn kill_account(
&self, address: &Address, password: &Password,
) -> Result<(), Error> {
self.sstore
.remove_account(&self.sstore.account_ref(&address)?, &password)?;
Ok(())
}
pub fn change_password(
&self, address: &Address, password: Password, new_password: Password,
) -> Result<(), Error> {
self.sstore.change_password(
&self.sstore.account_ref(address)?,
&password,
&new_password,
)
}
pub fn export_account(
&self, address: &Address, password: Password,
) -> Result<KeyFile, Error> {
self.sstore
.export_account(&self.sstore.account_ref(address)?, &password)
}
fn unlock_account(
&self, address: Address, password: Password, unlock: Unlock,
) -> Result<(), Error> {
let account = self.sstore.account_ref(&address)?;
let mut unlocked = self.unlocked.write();
if let Some(data) = unlocked.get(&account) {
if let Unlock::Perm = data.unlock {
return Ok(());
}
}
if self.unlock_keep_secret && unlock == Unlock::Perm {
let secret = self.sstore.raw_secret(&account, &password)?;
self.unlocked_secrets
.write()
.insert(account.clone(), secret);
} else {
let _ =
self.sstore.sign(&account, &password, &Default::default())?;
}
let data = AccountData { unlock, password };
unlocked.insert(account, data);
Ok(())
}
pub fn lock_account(&self, address: Address) -> Result<(), Error> {
let account = self.sstore.account_ref(&address)?;
let mut unlocked = self.unlocked.write();
if let Some(data) = unlocked.get(&account) {
if let Unlock::Perm = data.unlock {
self.unlocked_secrets.write().remove(&account);
}
unlocked.remove(&account);
}
Ok(())
}
fn password(
&self, account: &StoreAccountRef,
) -> Result<Password, SignError> {
let mut unlocked = self.unlocked.write();
Self::password_with_unlocked(&mut unlocked, account)
}
fn password_with_unlocked(
unlocked: &mut HashMap<StoreAccountRef, AccountData>,
account: &StoreAccountRef,
) -> Result<Password, SignError> {
let data = unlocked.get(account).ok_or(SignError::NotUnlocked)?.clone();
if let Unlock::OneTime = data.unlock {
unlocked
.remove(account)
.expect("data exists: so key must exist: qed");
}
if let Unlock::Timed(ref end) = data.unlock {
if Instant::now() > *end {
unlocked
.remove(account)
.expect("data exists: so key must exist: qed");
return Err(SignError::NotUnlocked);
}
}
Ok(data.password)
}
pub fn unlock_account_permanently(
&self, account: Address, password: Password,
) -> Result<(), Error> {
self.unlock_account(account, password, Unlock::Perm)
}
pub fn unlock_account_temporarily(
&self, account: Address, password: Password,
) -> Result<(), Error> {
self.unlock_account(account, password, Unlock::OneTime)
}
pub fn unlock_account_timed(
&self, account: Address, password: Password, duration: Duration,
) -> Result<(), Error> {
self.unlock_account(
account,
password,
Unlock::Timed(Instant::now() + duration),
)
}
pub fn is_unlocked(&self, address: &Address) -> bool {
let unlocked = self.unlocked.read();
let unlocked_secrets = self.unlocked_secrets.read();
self.sstore
.account_ref(address)
.map(|r| {
unlocked.get(&r).is_some() || unlocked_secrets.get(&r).is_some()
})
.unwrap_or(false)
}
pub fn is_unlocked_permanently(&self, address: &Address) -> bool {
let unlocked = self.unlocked.read();
self.sstore
.account_ref(address)
.map(|r| {
unlocked
.get(&r)
.map_or(false, |account| account.unlock == Unlock::Perm)
})
.unwrap_or(false)
}
pub fn sign(
&self, address: Address, password: Option<Password>, message: Message,
) -> Result<Signature, SignError> {
let account = self.sstore.account_ref(&address)?;
let mut unlocked = self.unlocked.write();
match self.unlocked_secrets.read().get(&account) {
Some(secret) => {
Ok(self.sstore.sign_with_secret(&secret, &message)?)
}
None => {
let password = password.map(Ok).unwrap_or_else(|| {
Self::password_with_unlocked(&mut unlocked, &account)
})?;
Ok(self.sstore.sign(&account, &password, &message)?)
}
}
}
pub fn sign_derived(
&self, address: &Address, password: Option<Password>,
derivation: Derivation, message: Message,
) -> Result<Signature, SignError> {
let account = self.sstore.account_ref(address)?;
let password = password
.map(Ok)
.unwrap_or_else(|| self.password(&account))?;
Ok(self
.sstore
.sign_derived(&account, &password, derivation, &message)?)
}
pub fn sign_with_token(
&self, address: Address, token: AccountToken, message: Message,
) -> Result<(Signature, AccountToken), SignError> {
let account = self.sstore.account_ref(&address)?;
let is_std_password = self.sstore.test_password(&account, &token)?;
let new_token = Password::from(random_string(16));
let signature = if is_std_password {
self.sstore.copy_account(
&self.transient_sstore,
SecretVaultRef::Root,
&account,
&token,
&new_token,
)?;
self.sstore.sign(&account, &token, &message)?
} else {
self.transient_sstore
.change_password(&account, &token, &new_token)?;
self.transient_sstore.sign(&account, &new_token, &message)?
};
Ok((signature, new_token))
}
pub fn decrypt_with_token(
&self, address: Address, token: AccountToken, shared_mac: &[u8],
message: &[u8],
) -> Result<(Vec<u8>, AccountToken), SignError> {
let account = self.sstore.account_ref(&address)?;
let is_std_password = self.sstore.test_password(&account, &token)?;
let new_token = Password::from(random_string(16));
let message = if is_std_password {
self.sstore.copy_account(
&self.transient_sstore,
SecretVaultRef::Root,
&account,
&token,
&new_token,
)?;
self.sstore.decrypt(&account, &token, shared_mac, message)?
} else {
self.transient_sstore
.change_password(&account, &token, &new_token)?;
self.transient_sstore
.decrypt(&account, &token, shared_mac, message)?
};
Ok((message, new_token))
}
pub fn decrypt(
&self, address: Address, password: Option<Password>, shared_mac: &[u8],
message: &[u8],
) -> Result<Vec<u8>, SignError> {
let account = self.sstore.account_ref(&address)?;
let password = password
.map(Ok)
.unwrap_or_else(|| self.password(&account))?;
Ok(self
.sstore
.decrypt(&account, &password, shared_mac, message)?)
}
pub fn agree(
&self, address: Address, password: Option<Password>,
other_public: &Public,
) -> Result<Secret, SignError> {
let account = self.sstore.account_ref(&address)?;
let password = password
.map(Ok)
.unwrap_or_else(|| self.password(&account))?;
Ok(self.sstore.agree(&account, &password, other_public)?)
}
pub fn list_geth_accounts(&self, testnet: bool) -> Vec<Address> {
self.sstore
.list_geth_accounts(testnet)
.into_iter()
.map(|a| a)
.collect()
}
pub fn import_geth_accounts(
&self, desired: Vec<Address>, testnet: bool,
) -> Result<Vec<Address>, Error> {
self.sstore
.import_geth_accounts(SecretVaultRef::Root, desired, testnet)
.map(|a| a.into_iter().map(|a| a.address).collect())
.map_err(Into::into)
}
pub fn create_vault(
&self, name: &str, password: &Password,
) -> Result<(), Error> {
self.sstore.create_vault(name, password).map_err(Into::into)
}
pub fn open_vault(
&self, name: &str, password: &Password,
) -> Result<(), Error> {
self.sstore.open_vault(name, password).map_err(Into::into)
}
pub fn close_vault(&self, name: &str) -> Result<(), Error> {
self.sstore.close_vault(name).map_err(Into::into)
}
pub fn list_vaults(&self) -> Result<Vec<String>, Error> {
self.sstore.list_vaults().map_err(Into::into)
}
pub fn list_opened_vaults(&self) -> Result<Vec<String>, Error> {
self.sstore.list_opened_vaults().map_err(Into::into)
}
pub fn change_vault_password(
&self, name: &str, new_password: &Password,
) -> Result<(), Error> {
self.sstore
.change_vault_password(name, new_password)
.map_err(Into::into)
}
pub fn change_vault(
&self, address: Address, new_vault: &str,
) -> Result<(), Error> {
let new_vault_ref = if new_vault.is_empty() {
SecretVaultRef::Root
} else {
SecretVaultRef::Vault(new_vault.to_owned())
};
let old_account_ref = self.sstore.account_ref(&address)?;
self.sstore
.change_account_vault(new_vault_ref, old_account_ref)
.map_err(Into::into)
.map(|_| ())
}
pub fn get_vault_meta(&self, name: &str) -> Result<String, Error> {
self.sstore.get_vault_meta(name).map_err(Into::into)
}
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)]
mod tests {
use super::{AccountProvider, Unlock};
use cfx_types::H256;
use cfxkey::{Address, Generator, Random};
use cfxstore::{Derivation, StoreAccountRef};
use std::time::{Duration, Instant};
#[test]
fn unlock_account_temp() {
let kp = Random.generate().unwrap();
let ap = AccountProvider::transient_provider();
assert!(ap
.insert_account(kp.secret().clone(), &"test".into())
.is_ok());
assert!(ap
.unlock_account_temporarily(kp.address(), "test1".into())
.is_err());
assert!(ap
.unlock_account_temporarily(kp.address(), "test".into())
.is_ok());
assert!(ap.sign(kp.address(), None, Default::default()).is_ok());
assert!(ap.sign(kp.address(), None, Default::default()).is_err());
}
#[test]
fn derived_account_nosave() {
let kp = Random.generate().unwrap();
let ap = AccountProvider::transient_provider();
assert!(ap
.insert_account(kp.secret().clone(), &"base".into())
.is_ok());
assert!(ap
.unlock_account_permanently(kp.address(), "base".into())
.is_ok());
let derived_addr = ap
.derive_account(
&kp.address(),
None,
Derivation::SoftHash(H256::from_low_u64_be(999)),
false,
)
.expect("Derivation should not fail");
assert!(ap.unlock_account_permanently(derived_addr, "base".into()).is_err(),
"There should be an error because account is not supposed to be saved");
}
#[test]
fn derived_account_save() {
let kp = Random.generate().unwrap();
let ap = AccountProvider::transient_provider();
assert!(ap
.insert_account(kp.secret().clone(), &"base".into())
.is_ok());
assert!(ap
.unlock_account_permanently(kp.address(), "base".into())
.is_ok());
let derived_addr = ap
.derive_account(
&kp.address(),
None,
Derivation::SoftHash(H256::from_low_u64_be(999)),
true,
)
.expect("Derivation should not fail");
assert!(
ap.unlock_account_permanently(derived_addr, "base_wrong".into())
.is_err(),
"There should be an error because password is invalid"
);
assert!(
ap.unlock_account_permanently(derived_addr, "base".into())
.is_ok(),
"Should be ok because account is saved and password is valid"
);
}
#[test]
fn derived_account_sign() {
let kp = Random.generate().unwrap();
let ap = AccountProvider::transient_provider();
assert!(ap
.insert_account(kp.secret().clone(), &"base".into())
.is_ok());
assert!(ap
.unlock_account_permanently(kp.address(), "base".into())
.is_ok());
let derived_addr = ap
.derive_account(
&kp.address(),
None,
Derivation::SoftHash(H256::from_low_u64_be(1999)),
true,
)
.expect("Derivation should not fail");
ap.unlock_account_permanently(derived_addr, "base".into())
.expect(
"Should be ok because account is saved and password is valid",
);
let msg = Default::default();
let signed_msg1 = ap
.sign(derived_addr, None, msg)
.expect("Signing with existing unlocked account should not fail");
let signed_msg2 = ap.sign_derived(
&kp.address(),
None,
Derivation::SoftHash(H256::from_low_u64_be(1999)),
msg,
).expect("Derived signing with existing unlocked account should not fail");
assert_eq!(signed_msg1, signed_msg2, "Signed messages should match");
}
#[test]
fn unlock_account_perm() {
let kp = Random.generate().unwrap();
let ap = AccountProvider::transient_provider();
assert!(ap
.insert_account(kp.secret().clone(), &"test".into())
.is_ok());
assert!(ap
.unlock_account_permanently(kp.address(), "test1".into())
.is_err());
assert!(ap
.unlock_account_permanently(kp.address(), "test".into())
.is_ok());
assert!(ap.sign(kp.address(), None, Default::default()).is_ok());
assert!(ap.sign(kp.address(), None, Default::default()).is_ok());
assert!(ap
.unlock_account_temporarily(kp.address(), "test".into())
.is_ok());
assert!(ap.sign(kp.address(), None, Default::default()).is_ok());
assert!(ap.sign(kp.address(), None, Default::default()).is_ok());
}
#[test]
fn unlock_account_timer() {
let kp = Random.generate().unwrap();
let ap = AccountProvider::transient_provider();
assert!(ap
.insert_account(kp.secret().clone(), &"test".into())
.is_ok());
assert!(ap
.unlock_account_timed(
kp.address(),
"test1".into(),
Duration::from_secs(60)
)
.is_err());
assert!(ap
.unlock_account_timed(
kp.address(),
"test".into(),
Duration::from_secs(60)
)
.is_ok());
assert!(ap.sign(kp.address(), None, Default::default()).is_ok());
ap.unlocked
.write()
.get_mut(&StoreAccountRef::root(kp.address()))
.unwrap()
.unlock = Unlock::Timed(Instant::now());
assert!(ap.sign(kp.address(), None, Default::default()).is_err());
}
#[test]
fn should_sign_and_return_token() {
let kp = Random.generate().unwrap();
let ap = AccountProvider::transient_provider();
assert!(ap
.insert_account(kp.secret().clone(), &"test".into())
.is_ok());
let (_signature, token) = ap
.sign_with_token(kp.address(), "test".into(), Default::default())
.unwrap();
ap.sign_with_token(kp.address(), token.clone(), Default::default())
.expect("First usage of token should be correct.");
assert!(
ap.sign_with_token(kp.address(), token, Default::default())
.is_err(),
"Second usage of the same token should fail."
);
}
#[test]
fn should_not_return_blacklisted_account() {
let mut ap = AccountProvider::transient_provider();
let acc = ap.new_account(&"test".into()).unwrap();
ap.blacklisted_accounts = vec![acc];
assert_eq!(
ap.accounts_info()
.unwrap()
.keys()
.cloned()
.collect::<Vec<Address>>(),
vec![]
);
assert_eq!(ap.accounts().unwrap(), vec![]);
}
}