diff --git a/rpc/src/v1/impls/personal.rs b/rpc/src/v1/impls/personal.rs index 5bb0d3eeebe..9fec1af6bdc 100644 --- a/rpc/src/v1/impls/personal.rs +++ b/rpc/src/v1/impls/personal.rs @@ -60,7 +60,7 @@ impl Personal for PersonalClient where A: AccountProvider + 'static { from_params::<(Address, String, u64)>(params).and_then( |(account, account_pass, _)|{ let store = take_weak!(self.accounts); - match store.unlock_account(&account, &account_pass) { + match store.unlock_account_temp(&account, &account_pass) { Ok(_) => Ok(Value::Bool(true)), Err(_) => Ok(Value::Bool(false)), } diff --git a/util/src/keys/store.rs b/util/src/keys/store.rs index b8fd839be35..13e53bdb520 100644 --- a/util/src/keys/store.rs +++ b/util/src/keys/store.rs @@ -80,6 +80,8 @@ struct AccountUnlock { secret: H256, /// expiration datetime (None - never) expires: Option>, + /// Sccount should be relocked after first use. + relock_on_use: bool, } /// Basic account management trait @@ -88,6 +90,8 @@ pub trait AccountProvider : Send + Sync { fn accounts(&self) -> Result, ::std::io::Error>; /// Unlocks account with the password provided fn unlock_account(&self, account: &Address, pass: &str) -> Result<(), EncryptedHashMapError>; + /// Unlocks account with the password provided; relocks it on the next call to `account_secret` or `sign`. + fn unlock_account_temp(&self, account: &Address, pass: &str) -> Result<(), EncryptedHashMapError>; /// Creates account fn new_account(&self, pass: &str) -> Result; /// Returns secret for unlocked `account`. @@ -112,6 +116,9 @@ impl AccountProvider for AccountService { fn unlock_account(&self, account: &Address, pass: &str) -> Result<(), EncryptedHashMapError> { self.secret_store.read().unwrap().unlock_account(account, pass) } + fn unlock_account_temp(&self, account: &Address, pass: &str) -> Result<(), EncryptedHashMapError> { + self.secret_store.read().unwrap().unlock_account_temp(account, pass) + } /// Creates account fn new_account(&self, pass: &str) -> Result { self.secret_store.write().unwrap().new_account(pass) @@ -165,7 +172,7 @@ impl AccountService { /// Unlocks account for use (no expiration of unlock) pub fn unlock_account_no_expire(&self, account: &Address, pass: &str) -> Result<(), EncryptedHashMapError> { - self.secret_store.write().unwrap().unlock_account_with_expiration(account, pass, None) + self.secret_store.write().unwrap().unlock_account_with_expiration(account, pass, None, false) } } @@ -233,21 +240,26 @@ impl SecretStore { /// Unlocks account for use pub fn unlock_account(&self, account: &Address, pass: &str) -> Result<(), EncryptedHashMapError> { - self.unlock_account_with_expiration(account, pass, Some(UTC::now() + Duration::minutes(20))) + self.unlock_account_with_expiration(account, pass, Some(UTC::now() + Duration::minutes(20)), false) } /// Unlocks account for use (no expiration of unlock) pub fn unlock_account_no_expire(&self, account: &Address, pass: &str) -> Result<(), EncryptedHashMapError> { - self.unlock_account_with_expiration(account, pass, None) + self.unlock_account_with_expiration(account, pass, None, false) + } + + /// Unlocks account for use (no expiration of unlock) + pub fn unlock_account_temp(&self, account: &Address, pass: &str) -> Result<(), EncryptedHashMapError> { + self.unlock_account_with_expiration(account, pass, None, true) } - fn unlock_account_with_expiration(&self, account: &Address, pass: &str, expiration: Option>) -> Result<(), EncryptedHashMapError> { + fn unlock_account_with_expiration(&self, account: &Address, pass: &str, expiration: Option>, relock_on_use: bool) -> Result<(), EncryptedHashMapError> { let secret_id = try!(self.account(&account).ok_or(EncryptedHashMapError::UnknownIdentifier)); let secret = try!(self.get(&secret_id, pass)); { let mut write_lock = self.unlocks.write().unwrap(); let mut unlock = write_lock.entry(*account) - .or_insert_with(|| AccountUnlock { secret: secret, expires: Some(UTC::now()) }); + .or_insert_with(|| AccountUnlock { secret: secret, expires: Some(UTC::now()), relock_on_use: relock_on_use }); unlock.secret = secret; unlock.expires = expiration; } @@ -267,24 +279,42 @@ impl SecretStore { Ok(address) } - /// Signs message with unlocked account + /// Signs message with unlocked account. pub fn sign(&self, account: &Address, message: &H256) -> Result { - let read_lock = self.unlocks.read().unwrap(); - let unlock = try!(read_lock.get(account).ok_or(SigningError::AccountNotUnlocked)); - match crypto::KeyPair::from_secret(unlock.secret) { - Ok(pair) => match pair.sign(message) { - Ok(signature) => Ok(signature), + let (relock, ret) = { + let read_lock = self.unlocks.read().unwrap(); + if let Some(unlock) = read_lock.get(account) { + (unlock.relock_on_use, match crypto::KeyPair::from_secret(unlock.secret) { + Ok(pair) => match pair.sign(message) { + Ok(signature) => Ok(signature), + Err(_) => Err(SigningError::InvalidSecret) + }, Err(_) => Err(SigningError::InvalidSecret) - }, - Err(_) => Err(SigningError::InvalidSecret) + }) + } else { + (false, Err(SigningError::AccountNotUnlocked)) + } + }; + if relock { + self.unlocks.write().unwrap().remove(account); } + ret } - /// Returns secret for unlocked account + /// Returns secret for unlocked account. pub fn account_secret(&self, account: &Address) -> Result { - let read_lock = self.unlocks.read().unwrap(); - let unlock = try!(read_lock.get(account).ok_or(SigningError::AccountNotUnlocked)); - Ok(unlock.secret as crypto::Secret) + let (relock, ret) = { + let read_lock = self.unlocks.read().unwrap(); + if let Some(unlock) = read_lock.get(account) { + (unlock.relock_on_use, Ok(unlock.secret as crypto::Secret)) + } else { + (false, Err(SigningError::AccountNotUnlocked)) + } + }; + if relock { + self.unlocks.write().unwrap().remove(account); + } + ret } /// Makes account unlocks expire and removes unused key files from memory @@ -582,6 +612,30 @@ mod tests { assert!(signature != x!(0)); } + #[test] + fn can_relock_temp_account() { + let temp = RandomTempPath::create_dir(); + let address = { + let mut sstore = SecretStore::new_test(&temp); + sstore.new_account("334").unwrap() + }; + let signature = { + let sstore = SecretStore::new_test(&temp); + sstore.unlock_account_temp(&address, "334").unwrap(); + sstore.sign(&address, &H256::random()).unwrap(); + sstore.sign(&address, &H256::random()) + }; + assert!(signature.is_err()); + + let secret = { + let sstore = SecretStore::new_test(&temp); + sstore.unlock_account_temp(&address, "334").unwrap(); + sstore.account_secret(&address).unwrap(); + sstore.account_secret(&address) + }; + assert!(secret.is_err()); + } + #[test] fn can_import_account() { use keys::directory::{KeyFileContent, KeyFileCrypto}; diff --git a/util/src/keys/test_account_provider.rs b/util/src/keys/test_account_provider.rs index 4ce28ab6772..b8aed85ce38 100644 --- a/util/src/keys/test_account_provider.rs +++ b/util/src/keys/test_account_provider.rs @@ -82,6 +82,11 @@ impl AccountProvider for TestAccountProvider { } } + fn unlock_account_temp(&self, account: &Address, pass: &str) -> Result<(), EncryptedHashMapError> { + // TODO; actually make it relock on use + self.unlock_account(account, pass) + } + fn new_account(&self, pass: &str) -> Result { let account = TestAccount::new(pass); let address = KeyPair::from_secret(account.secret.clone()).unwrap().address();