From a5299bdb1a19f5eb6cf0dc8745b5786165dfe52c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Tue, 6 Jun 2017 18:06:40 +0200 Subject: [PATCH] Unlocking with secrets. --- ethcore/src/account_provider/mod.rs | 51 ++++++++++++++++++++++------- ethstore/src/account/mod.rs | 1 + ethstore/src/ethstore.rs | 6 +++- ethstore/src/lib.rs | 3 ++ ethstore/src/secret_store.rs | 10 ++++++ parity/cli/mod.rs | 4 +++ parity/cli/usage.txt | 2 ++ parity/configuration.rs | 1 + parity/params.rs | 2 ++ parity/run.rs | 1 + 10 files changed, 69 insertions(+), 12 deletions(-) diff --git a/ethcore/src/account_provider/mod.rs b/ethcore/src/account_provider/mod.rs index 0ecbf3b17..5bb5f823e 100755 --- a/ethcore/src/account_provider/mod.rs +++ b/ethcore/src/account_provider/mod.rs @@ -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>, + /// Unlocked account data. unlocked: RwLock>, + /// Address book. address_book: RwLock, + /// Dapps settings. dapps_settings: RwLock, /// Accounts on disk sstore: Box, @@ -125,6 +130,8 @@ pub struct AccountProvider { transient_sstore: EthMultiStore, /// Accounts in hardware wallets. hardware_store: Option, + /// 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 { 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, message: Message) -> Result { 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. diff --git a/ethstore/src/account/mod.rs b/ethstore/src/account/mod.rs index 1ea19a265..c352ffe78 100755 --- a/ethstore/src/account/mod.rs +++ b/ethstore/src/account/mod.rs @@ -25,3 +25,4 @@ pub use self::crypto::Crypto; pub use self::kdf::{Kdf, Pbkdf2, Scrypt, Prf}; pub use self::safe_account::SafeAccount; pub use self::version::Version; + diff --git a/ethstore/src/ethstore.rs b/ethstore/src/ethstore.rs index 7c4ebfd95..246671990 100755 --- a/ethstore/src/ethstore.rs +++ b/ethstore/src/ethstore.rs @@ -26,7 +26,7 @@ use dir::{KeyDirectory, VaultKeyDirectory, VaultKey, SetKeyError}; use account::SafeAccount; use presale::PresaleWallet; use json::{self, Uuid, OpaqueKeyFile}; -use {import, Error, SimpleSecretStore, SecretStore, SecretVaultRef, StoreAccountRef, Derivation}; +use {import, Error, SimpleSecretStore, SecretStore, SecretVaultRef, StoreAccountRef, Derivation, OpaqueSecret}; /// Accounts store. pub struct EthStore { @@ -140,6 +140,10 @@ impl SimpleSecretStore for EthStore { } impl SecretStore for EthStore { + fn raw_secret(&self, account: &StoreAccountRef, password: &str) -> Result { + Ok(OpaqueSecret(self.get(account)?.crypto.secret(password)?)) + } + fn import_presale(&self, vault: SecretVaultRef, json: &[u8], password: &str) -> Result { let json_wallet = json::PresaleWallet::load(json).map_err(|_| Error::InvalidKeyFile("Invalid JSON format".to_owned()))?; let wallet = PresaleWallet::from(json_wallet); diff --git a/ethstore/src/lib.rs b/ethstore/src/lib.rs index 00a06a3e5..13ad4e013 100755 --- a/ethstore/src/lib.rs +++ b/ethstore/src/lib.rs @@ -66,3 +66,6 @@ pub use self::secret_store::{ }; pub use self::random::random_string; pub use self::parity_wordlist::random_phrase; + +/// A opaque wrapper for secret. +pub struct OpaqueSecret(::ethkey::Secret); diff --git a/ethstore/src/secret_store.rs b/ethstore/src/secret_store.rs index b292f0ef4..2deae023e 100755 --- a/ethstore/src/secret_store.rs +++ b/ethstore/src/secret_store.rs @@ -20,6 +20,7 @@ use ethkey::{Address, Message, Signature, Secret, Public}; use Error; use json::{Uuid, OpaqueKeyFile}; use bigint::hash::H256; +use OpaqueSecret; /// Key directory reference #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] @@ -88,6 +89,15 @@ pub trait SimpleSecretStore: Send + Sync { /// Secret Store API pub trait SecretStore: SimpleSecretStore { + + /// Returns a raw opaque Secret that can be later used to sign a message. + fn raw_secret(&self, account: &StoreAccountRef, password: &str) -> Result; + + /// Signs a message with raw secret. + fn sign_with_secret(&self, secret: &OpaqueSecret, message: &Message) -> Result { + Ok(::ethkey::sign(&secret.0, message)?) + } + /// Imports presale wallet fn import_presale(&self, vault: SecretVaultRef, json: &[u8], password: &str) -> Result; /// Imports existing JSON wallet diff --git a/parity/cli/mod.rs b/parity/cli/mod.rs index cd8f14add..59d3c9fc7 100644 --- a/parity/cli/mod.rs +++ b/parity/cli/mod.rs @@ -114,6 +114,8 @@ usage! { or |c: &Config| otry!(c.account).keys_iterations.clone(), flag_no_hardware_wallets: bool = false, or |c: &Config| otry!(c.account).disable_hardware.clone(), + flag_fast_unlock: bool = false, + or |c: &Config| otry!(c.account).fast_unlock.clone(), flag_force_ui: bool = false, @@ -424,6 +426,7 @@ struct Account { password: Option>, keys_iterations: Option, disable_hardware: Option, + fast_unlock: Option, } #[derive(Default, Debug, PartialEq, RustcDecodable)] @@ -927,6 +930,7 @@ mod tests { password: Some(vec!["passwdfile path".into()]), keys_iterations: None, disable_hardware: None, + fast_unlock: None, }), ui: Some(Ui { force: None, diff --git a/parity/cli/usage.txt b/parity/cli/usage.txt index 99f6c7304..5dc2f8404 100644 --- a/parity/cli/usage.txt +++ b/parity/cli/usage.txt @@ -99,6 +99,8 @@ Account Options: deriving key from the password (bigger is more secure) (default: {flag_keys_iterations}). --no-hardware-wallets Disables hardware wallet support. (default: {flag_no_hardware_wallets}) + --fast-unlock Use way faster unlocking mode. This setting causes raw secrets to be stored + unprotected in memory. (default: {flag_fast_unlock}) UI Options: --force-ui Enable Trusted UI WebSocket endpoint, diff --git a/parity/configuration.rs b/parity/configuration.rs index c211b29d5..214c00cbb 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -497,6 +497,7 @@ impl Configuration { password_files: self.args.flag_password.clone(), unlocked_accounts: to_addresses(&self.args.flag_unlock)?, enable_hardware_wallets: !self.args.flag_no_hardware_wallets, + enable_fast_unlock: self.args.flag_fast_unlock, }; Ok(cfg) diff --git a/parity/params.rs b/parity/params.rs index 85019b3e7..20a24ee1c 100644 --- a/parity/params.rs +++ b/parity/params.rs @@ -180,6 +180,7 @@ pub struct AccountsConfig { pub password_files: Vec, pub unlocked_accounts: Vec
, pub enable_hardware_wallets: bool, + pub enable_fast_unlock: bool, } impl Default for AccountsConfig { @@ -190,6 +191,7 @@ impl Default for AccountsConfig { password_files: Vec::new(), unlocked_accounts: Vec::new(), enable_hardware_wallets: true, + enable_fast_unlock: false, } } } diff --git a/parity/run.rs b/parity/run.rs index 352f11c5d..b543ab7f4 100644 --- a/parity/run.rs +++ b/parity/run.rs @@ -777,6 +777,7 @@ fn prepare_account_provider(spec: &SpecType, dirs: &Directories, data_dir: &str, let account_settings = AccountProviderSettings { enable_hardware_wallets: cfg.enable_hardware_wallets, hardware_wallet_classic_key: spec == &SpecType::Classic, + fast_unlock: cfg.enable_fast_unlock, }; let account_provider = AccountProvider::new( Box::new(EthStore::open_with_iterations(dir, cfg.iterations).map_err(|e| format!("Could not open keys directory: {}", e))?),