EthMultiStore
This commit is contained in:
parent
79e79473bf
commit
ad440a12bd
@ -163,12 +163,19 @@ impl AddressBook {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn transient_sstore() -> Box<SecretStore> {
|
||||||
|
Box::new(EthStore::open(Box::new(NullDir::default())).expect("NullDir load always succeeds; qed")))
|
||||||
|
}
|
||||||
|
|
||||||
/// Account management.
|
/// Account management.
|
||||||
/// Responsible for unlocking accounts.
|
/// Responsible for unlocking accounts.
|
||||||
pub struct AccountProvider {
|
pub struct AccountProvider {
|
||||||
unlocked: Mutex<HashMap<Address, AccountData>>,
|
|
||||||
sstore: Box<SecretStore>,
|
|
||||||
address_book: Mutex<AddressBook>,
|
address_book: Mutex<AddressBook>,
|
||||||
|
unlocked: Mutex<HashMap<Address, AccountData>>,
|
||||||
|
/// Accounts on disk
|
||||||
|
sstore: Box<SecretStore>,
|
||||||
|
/// Accounts unlocked with rolling tokens
|
||||||
|
transient_sstore: Box<SecretStore>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AccountProvider {
|
impl AccountProvider {
|
||||||
@ -178,6 +185,7 @@ impl AccountProvider {
|
|||||||
unlocked: Mutex::new(HashMap::new()),
|
unlocked: Mutex::new(HashMap::new()),
|
||||||
address_book: Mutex::new(AddressBook::new(sstore.local_path().into())),
|
address_book: Mutex::new(AddressBook::new(sstore.local_path().into())),
|
||||||
sstore: sstore,
|
sstore: sstore,
|
||||||
|
transient_sstore: transient_sstore(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,8 +194,8 @@ impl AccountProvider {
|
|||||||
AccountProvider {
|
AccountProvider {
|
||||||
unlocked: Mutex::new(HashMap::new()),
|
unlocked: Mutex::new(HashMap::new()),
|
||||||
address_book: Mutex::new(AddressBook::transient()),
|
address_book: Mutex::new(AddressBook::transient()),
|
||||||
sstore: Box::new(EthStore::open(Box::new(NullDir::default()))
|
sstore: transient_sstore(),
|
||||||
.expect("NullDir load always succeeds; qed"))
|
transient_sstore: transient_sstore(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -433,4 +441,15 @@ mod tests {
|
|||||||
ap.unlocked.lock().get_mut(&kp.address()).unwrap().unlock = Unlock::Timed(Instant::now());
|
ap.unlocked.lock().get_mut(&kp.address()).unwrap().unlock = Unlock::Timed(Instant::now());
|
||||||
assert!(ap.sign(kp.address(), None, Default::default()).is_err());
|
assert!(ap.sign(kp.address(), None, Default::default()).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_sign_and_return_token() {
|
||||||
|
let kp = Random.generate().unwrap();
|
||||||
|
// given
|
||||||
|
let ap = AccountProvider::transient_provider();
|
||||||
|
assert!(ap.insert_account(kp.secret().clone(), "test").is_ok());
|
||||||
|
|
||||||
|
// when
|
||||||
|
let (_signature, token) = ap.sign_with_token(kp.address(), "test", Default::default()).unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,6 @@ use std::{fs, io};
|
|||||||
use std::path::{PathBuf, Path};
|
use std::path::{PathBuf, Path};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use time;
|
use time;
|
||||||
use ethkey::Address;
|
|
||||||
use {json, SafeAccount, Error};
|
use {json, SafeAccount, Error};
|
||||||
use json::UUID;
|
use json::UUID;
|
||||||
use super::KeyDirectory;
|
use super::KeyDirectory;
|
||||||
@ -138,12 +137,12 @@ impl KeyDirectory for DiskDirectory {
|
|||||||
Ok(account)
|
Ok(account)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove(&self, address: &Address) -> Result<(), Error> {
|
fn remove(&self, account: &SafeAccount) -> Result<(), Error> {
|
||||||
// enumerate all entries in keystore
|
// enumerate all entries in keystore
|
||||||
// and find entry with given address
|
// and find entry with given address
|
||||||
let to_remove = try!(self.files())
|
let to_remove = try!(self.files())
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.find(|&(_, ref account)| &account.address == address);
|
.find(|&(_, ref acc)| acc == account);
|
||||||
|
|
||||||
// remove it
|
// remove it
|
||||||
match to_remove {
|
match to_remove {
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use ethkey::Address;
|
|
||||||
use {SafeAccount, Error};
|
use {SafeAccount, Error};
|
||||||
use super::{KeyDirectory, DiskDirectory, DirectoryType};
|
use super::{KeyDirectory, DiskDirectory, DirectoryType};
|
||||||
|
|
||||||
@ -89,7 +88,7 @@ impl KeyDirectory for GethDirectory {
|
|||||||
self.dir.insert(account)
|
self.dir.insert(account)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove(&self, address: &Address) -> Result<(), Error> {
|
fn remove(&self, account: &SafeAccount) -> Result<(), Error> {
|
||||||
self.dir.remove(address)
|
self.dir.remove(account)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,6 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use ethkey::Address;
|
|
||||||
use std::path::{PathBuf};
|
use std::path::{PathBuf};
|
||||||
use {SafeAccount, Error};
|
use {SafeAccount, Error};
|
||||||
|
|
||||||
@ -30,7 +29,7 @@ pub enum DirectoryType {
|
|||||||
pub trait KeyDirectory: Send + Sync {
|
pub trait KeyDirectory: Send + Sync {
|
||||||
fn load(&self) -> Result<Vec<SafeAccount>, Error>;
|
fn load(&self) -> Result<Vec<SafeAccount>, Error>;
|
||||||
fn insert(&self, account: SafeAccount) -> Result<SafeAccount, Error>;
|
fn insert(&self, account: SafeAccount) -> Result<SafeAccount, Error>;
|
||||||
fn remove(&self, address: &Address) -> Result<(), Error>;
|
fn remove(&self, account: &SafeAccount) -> Result<(), Error>;
|
||||||
fn path(&self) -> Option<&PathBuf> { None }
|
fn path(&self) -> Option<&PathBuf> { None }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use ethkey::Address;
|
|
||||||
use {SafeAccount, Error};
|
use {SafeAccount, Error};
|
||||||
use super::{KeyDirectory, DiskDirectory, DirectoryType};
|
use super::{KeyDirectory, DiskDirectory, DirectoryType};
|
||||||
|
|
||||||
@ -68,7 +67,7 @@ impl KeyDirectory for ParityDirectory {
|
|||||||
self.dir.insert(account)
|
self.dir.insert(account)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove(&self, address: &Address) -> Result<(), Error> {
|
fn remove(&self, account: &SafeAccount) -> Result<(), Error> {
|
||||||
self.dir.remove(address)
|
self.dir.remove(account)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,7 @@ impl EthStore {
|
|||||||
|
|
||||||
fn save(&self, account: SafeAccount) -> Result<(), Error> {
|
fn save(&self, account: SafeAccount) -> Result<(), Error> {
|
||||||
// save to file
|
// save to file
|
||||||
let account = try!(self.dir.insert(account.clone()));
|
let account = try!(self.dir.insert(account));
|
||||||
|
|
||||||
// update cache
|
// update cache
|
||||||
let mut cache = self.cache.write();
|
let mut cache = self.cache.write();
|
||||||
@ -124,13 +124,11 @@ impl SecretStore for EthStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn remove_account(&self, address: &Address, password: &str) -> Result<(), Error> {
|
fn remove_account(&self, address: &Address, password: &str) -> Result<(), Error> {
|
||||||
let can_remove = {
|
|
||||||
let account = try!(self.get(address));
|
let account = try!(self.get(address));
|
||||||
account.check_password(password)
|
let can_remove = account.check_password(password);
|
||||||
};
|
|
||||||
|
|
||||||
if can_remove {
|
if can_remove {
|
||||||
try!(self.dir.remove(address));
|
try!(self.dir.remove(&account));
|
||||||
let mut cache = self.cache.write();
|
let mut cache = self.cache.write();
|
||||||
cache.remove(address);
|
cache.remove(address);
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -197,3 +195,136 @@ impl SecretStore for EthStore {
|
|||||||
import::import_geth_accounts(&*self.dir, desired.into_iter().collect(), testnet)
|
import::import_geth_accounts(&*self.dir, desired.into_iter().collect(), testnet)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Similar to `EthStore` but may store many accounts (with different passwords) for the same `Address`
|
||||||
|
pub struct EthMultiStore {
|
||||||
|
dir: Box<KeyDirectory>,
|
||||||
|
iterations: u32,
|
||||||
|
cache: RwLock<BTreeMap<Address, Vec<SafeAccount>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EthMultiStore {
|
||||||
|
|
||||||
|
pub fn open(directory: Box<KeyDirectory>) -> Result<Self, Error> {
|
||||||
|
Self::open_with_iterations(directory, KEY_ITERATIONS as u32)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn open_with_iterations(directory: Box<KeyDirectory>, iterations: u32) -> Result<Self, Error> {
|
||||||
|
let mut store = EthMultiStore {
|
||||||
|
dir: directory,
|
||||||
|
iterations: iterations,
|
||||||
|
cache: Default::default(),
|
||||||
|
};
|
||||||
|
try!(store.reload_accounts());
|
||||||
|
Ok(store)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reload_accounts(&self) -> Result<(), Error> {
|
||||||
|
let mut cache = self.cache.write();
|
||||||
|
let accounts = try!(self.dir.load());
|
||||||
|
|
||||||
|
let mut new_accounts = BTreeMap::new();
|
||||||
|
for account in accounts {
|
||||||
|
let mut entry = new_accounts.entry(account.address.clone()).or_insert_with(Vec::new);
|
||||||
|
entry.push(account);
|
||||||
|
}
|
||||||
|
mem::replace(&mut *cache, new_accounts);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get(&self, address: &Address) -> Result<Vec<SafeAccount>, Error> {
|
||||||
|
{
|
||||||
|
let cache = self.cache.read();
|
||||||
|
if let Some(accounts) = cache.get(address) {
|
||||||
|
if !accounts.is_empty() {
|
||||||
|
return Ok(accounts.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try!(self.reload_accounts());
|
||||||
|
let cache = self.cache.read();
|
||||||
|
let accounts = try!(cache.get(address).cloned().ok_or(Error::InvalidAccount));
|
||||||
|
if accounts.is_empty() {
|
||||||
|
Err(Error::InvalidAccount)
|
||||||
|
} else {
|
||||||
|
Ok(accounts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert_account(&self, account: SafeAccount) -> Result<(), Error> {
|
||||||
|
//save to file
|
||||||
|
let account = try!(self.dir.insert(account));
|
||||||
|
|
||||||
|
// update cache
|
||||||
|
let mut cache = self.cache.write();
|
||||||
|
let mut accounts = cache.entry(account.address.clone()).or_insert_with(Vec::new);
|
||||||
|
accounts.push(account);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_account(&self, address: &Address, password: &str) -> Result<(), Error> {
|
||||||
|
let accounts = try!(self.get(address));
|
||||||
|
|
||||||
|
for account in accounts {
|
||||||
|
// Skip if password is invalid
|
||||||
|
if !account.check_password(password) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove from dir
|
||||||
|
try!(self.dir.remove(&account));
|
||||||
|
|
||||||
|
// Remove from cache
|
||||||
|
let mut cache = self.cache.write();
|
||||||
|
let is_empty = {
|
||||||
|
let mut accounts = cache.get_mut(address).expect("Entry exists, because it was returned by `get`; qed");
|
||||||
|
if let Some(position) = accounts.iter().position(|acc| acc == &account) {
|
||||||
|
accounts.remove(position);
|
||||||
|
}
|
||||||
|
accounts.is_empty()
|
||||||
|
};
|
||||||
|
|
||||||
|
if is_empty {
|
||||||
|
cache.remove(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
Err(Error::InvalidPassword)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn change_password(&self, address: &Address, old_password: &str, new_password: &str) -> Result<(), Error> {
|
||||||
|
let accounts = try!(self.get(address));
|
||||||
|
for account in accounts {
|
||||||
|
// First remove
|
||||||
|
try!(self.remove_account(&address, old_password));
|
||||||
|
// Then insert back with new password
|
||||||
|
let new_account = try!(account.change_password(old_password, new_password, self.iterations));
|
||||||
|
try!(self.insert_account(new_account));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sign(&self, address: &Address, password: &str, message: &Message) -> Result<Signature, Error> {
|
||||||
|
let accounts = try!(self.get(address));
|
||||||
|
for account in accounts {
|
||||||
|
if account.check_password(password) {
|
||||||
|
return account.sign(password, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(Error::InvalidPassword)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decrypt(&self, account: &Address, password: &str, shared_mac: &[u8], message: &[u8]) -> Result<Vec<u8>, Error> {
|
||||||
|
let accounts = try!(self.get(account));
|
||||||
|
for account in accounts {
|
||||||
|
if account.check_password(password) {
|
||||||
|
return account.decrypt(password, shared_mac, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(Error::InvalidPassword)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -18,7 +18,6 @@ use std::path::PathBuf;
|
|||||||
use std::{env, fs};
|
use std::{env, fs};
|
||||||
use rand::{Rng, OsRng};
|
use rand::{Rng, OsRng};
|
||||||
use ethstore::dir::{KeyDirectory, DiskDirectory};
|
use ethstore::dir::{KeyDirectory, DiskDirectory};
|
||||||
use ethstore::ethkey::Address;
|
|
||||||
use ethstore::{Error, SafeAccount};
|
use ethstore::{Error, SafeAccount};
|
||||||
|
|
||||||
pub fn random_dir() -> PathBuf {
|
pub fn random_dir() -> PathBuf {
|
||||||
@ -68,7 +67,7 @@ impl KeyDirectory for TransientDir {
|
|||||||
self.dir.insert(account)
|
self.dir.insert(account)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove(&self, address: &Address) -> Result<(), Error> {
|
fn remove(&self, account: &SafeAccount) -> Result<(), Error> {
|
||||||
self.dir.remove(address)
|
self.dir.remove(account)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user