Unlocking with secrets.

This commit is contained in:
Tomasz Drwięga
2017-06-06 18:06:40 +02:00
parent 241de230bb
commit a5299bdb1a
10 changed files with 69 additions and 12 deletions

View File

@@ -26,7 +26,7 @@ use std::time::{Instant, Duration};
use util::{RwLock};
use ethstore::{
SimpleSecretStore, SecretStore, Error as SSError, EthStore, EthMultiStore,
random_string, SecretVaultRef, StoreAccountRef,
random_string, SecretVaultRef, StoreAccountRef, OpaqueSecret,
};
use ethstore::dir::MemoryDirectory;
use ethstore::ethkey::{Address, Message, Public, Secret, Random, Generator};
@@ -36,10 +36,10 @@ pub use ethstore::ethkey::Signature;
pub use ethstore::{Derivation, IndexDerivation, KeyFile};
/// Type of unlock.
#[derive(Clone)]
#[derive(Clone, PartialEq)]
enum Unlock {
/// If account is unlocked temporarily, it should be locked after first usage.
Temp,
OneTime,
/// Account unlocked permantently can always sign message.
/// Use with caution.
Perm,
@@ -116,8 +116,13 @@ type AccountToken = String;
/// Account management.
/// Responsible for unlocking accounts.
pub struct AccountProvider {
/// For performance reasons some methods can re-use unlocked secrets.
unlocked_secrets: RwLock<HashMap<StoreAccountRef, OpaqueSecret>>,
/// Unlocked account data.
unlocked: RwLock<HashMap<StoreAccountRef, AccountData>>,
/// Address book.
address_book: RwLock<AddressBook>,
/// Dapps settings.
dapps_settings: RwLock<DappsSettingsStore>,
/// Accounts on disk
sstore: Box<SecretStore>,
@@ -125,6 +130,8 @@ pub struct AccountProvider {
transient_sstore: EthMultiStore,
/// Accounts in hardware wallets.
hardware_store: Option<HardwareWalletManager>,
/// When unlocking permanently on for some time store a raw secret instead of password.
fast_unlock: bool,
}
/// Account management settings.
@@ -133,6 +140,8 @@ pub struct AccountProviderSettings {
pub enable_hardware_wallets: bool,
/// Use the classic chain key on the hardware wallet.
pub hardware_wallet_classic_key: bool,
/// Use fast, but unsafe unlock
pub fast_unlock: bool,
}
impl Default for AccountProviderSettings {
@@ -140,6 +149,7 @@ impl Default for AccountProviderSettings {
AccountProviderSettings {
enable_hardware_wallets: false,
hardware_wallet_classic_key: false,
fast_unlock: true,
}
}
}
@@ -158,24 +168,28 @@ impl AccountProvider {
}
}
AccountProvider {
unlocked_secrets: RwLock::new(HashMap::new()),
unlocked: RwLock::new(HashMap::new()),
address_book: RwLock::new(AddressBook::new(&sstore.local_path())),
dapps_settings: RwLock::new(DappsSettingsStore::new(&sstore.local_path())),
sstore: sstore,
transient_sstore: transient_sstore(),
hardware_store: hardware_store,
fast_unlock: settings.fast_unlock,
}
}
/// Creates not disk backed provider.
pub fn transient_provider() -> Self {
AccountProvider {
unlocked_secrets: RwLock::new(HashMap::new()),
unlocked: RwLock::new(HashMap::new()),
address_book: RwLock::new(AddressBook::transient()),
dapps_settings: RwLock::new(DappsSettingsStore::transient()),
sstore: Box::new(EthStore::open(Box::new(MemoryDirectory::default())).expect("MemoryDirectory load always succeeds; qed")),
transient_sstore: transient_sstore(),
hardware_store: None,
fast_unlock: false,
}
}
@@ -509,10 +523,7 @@ impl AccountProvider {
/// Helper method used for unlocking accounts.
fn unlock_account(&self, address: Address, password: String, unlock: Unlock) -> Result<(), Error> {
// verify password by signing dump message
// result may be discarded
let account = self.sstore.account_ref(&address)?;
let _ = self.sstore.sign(&account, &password, &Default::default())?;
// check if account is already unlocked pernamently, if it is, do nothing
let mut unlocked = self.unlocked.write();
@@ -522,6 +533,16 @@ impl AccountProvider {
}
}
if self.fast_unlock && unlock != Unlock::OneTime {
// verify password and get the secret
let secret = self.sstore.raw_secret(&account, &password)?;
self.unlocked_secrets.write().insert(account.clone(), secret);
} else {
// verify password by signing dump message
// result may be discarded
let _ = self.sstore.sign(&account, &password, &Default::default())?;
}
let data = AccountData {
unlock: unlock,
password: password,
@@ -534,7 +555,7 @@ impl AccountProvider {
fn password(&self, account: &StoreAccountRef) -> Result<String, SignError> {
let mut unlocked = self.unlocked.write();
let data = unlocked.get(account).ok_or(SignError::NotUnlocked)?.clone();
if let Unlock::Temp = data.unlock {
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 {
@@ -553,7 +574,7 @@ impl AccountProvider {
/// Unlocks account temporarily (for one signing).
pub fn unlock_account_temporarily(&self, account: Address, password: String) -> Result<(), Error> {
self.unlock_account(account, password, Unlock::Temp)
self.unlock_account(account, password, Unlock::OneTime)
}
/// Unlocks account temporarily with a timeout.
@@ -564,16 +585,24 @@ impl AccountProvider {
/// Checks if given account is unlocked
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())
.map(|r| unlocked.get(&r).is_some() || unlocked_secrets.get(&r).is_some())
.unwrap_or(false)
}
/// Signs the message. If password is not provided the account must be unlocked.
pub fn sign(&self, address: Address, password: Option<String>, 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(&account, &password, &message)?)
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(&account))?;
Ok(self.sstore.sign(&account, &password, &message)?)
}
}
}
/// Signs message using the derived secret. If password is not provided the account must be unlocked.