Skip to content
This repository has been archived by the owner on Nov 6, 2020. It is now read-only.

Commit

Permalink
Merge pull request #3691 from ethcore/rotating-key
Browse files Browse the repository at this point in the history
Signing transactions with rotating token
  • Loading branch information
gavofyork authored Dec 15, 2016
2 parents 6d41168 + 7eb9112 commit c4406c9
Show file tree
Hide file tree
Showing 21 changed files with 765 additions and 203 deletions.
120 changes: 82 additions & 38 deletions ethcore/src/account_provider/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ use self::stores::{AddressBook, DappsSettingsStore};
use std::fmt;
use std::collections::HashMap;
use std::time::{Instant, Duration};
use util::{Mutex, RwLock};
use ethstore::{SecretStore, Error as SSError, SafeAccount, EthStore};
use ethstore::dir::{KeyDirectory};
use util::RwLock;
use ethstore::{SimpleSecretStore, SecretStore, Error as SSError, EthStore, EthMultiStore, random_string};
use ethstore::dir::MemoryDirectory;
use ethstore::ethkey::{Address, Message, Public, Secret, Random, Generator};
use ethjson::misc::AccountMeta;
pub use ethstore::ethkey::Signature;
Expand Down Expand Up @@ -73,58 +73,47 @@ impl From<SSError> for Error {
}
}

#[derive(Default)]
struct NullDir {
accounts: RwLock<HashMap<Address, SafeAccount>>,
}

impl KeyDirectory for NullDir {
fn load(&self) -> Result<Vec<SafeAccount>, SSError> {
Ok(self.accounts.read().values().cloned().collect())
}

fn insert(&self, account: SafeAccount) -> Result<SafeAccount, SSError> {
self.accounts.write().insert(account.address.clone(), account.clone());
Ok(account)
}
/// Dapp identifier
pub type DappId = String;

fn remove(&self, address: &Address) -> Result<(), SSError> {
self.accounts.write().remove(address);
Ok(())
}
fn transient_sstore() -> EthMultiStore {
EthMultiStore::open(Box::new(MemoryDirectory::default())).expect("MemoryDirectory load always succeeds; qed")
}

/// Dapp identifier
pub type DappId = String;
type AccountToken = String;

/// Account management.
/// Responsible for unlocking accounts.
pub struct AccountProvider {
unlocked: Mutex<HashMap<Address, AccountData>>,
sstore: Box<SecretStore>,
unlocked: RwLock<HashMap<Address, AccountData>>,
address_book: RwLock<AddressBook>,
dapps_settings: RwLock<DappsSettingsStore>,
/// Accounts on disk
sstore: Box<SecretStore>,
/// Accounts unlocked with rolling tokens
transient_sstore: EthMultiStore,
}

impl AccountProvider {
/// Creates new account provider.
pub fn new(sstore: Box<SecretStore>) -> Self {
AccountProvider {
unlocked: Mutex::new(HashMap::new()),
unlocked: RwLock::new(HashMap::new()),
address_book: RwLock::new(AddressBook::new(sstore.local_path().into())),
dapps_settings: RwLock::new(DappsSettingsStore::new(sstore.local_path().into())),
sstore: sstore,
transient_sstore: transient_sstore(),
}
}

/// Creates not disk backed provider.
pub fn transient_provider() -> Self {
AccountProvider {
unlocked: Mutex::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(NullDir::default()))
.expect("NullDir load always succeeds; qed"))
sstore: Box::new(EthStore::open(Box::new(MemoryDirectory::default())).expect("MemoryDirectory load always succeeds; qed")),
transient_sstore: transient_sstore(),
}
}

Expand Down Expand Up @@ -231,11 +220,8 @@ impl AccountProvider {

/// Returns `true` if the password for `account` is `password`. `false` if not.
pub fn test_password(&self, account: &Address, password: &str) -> Result<bool, Error> {
match self.sstore.sign(account, password, &Default::default()) {
Ok(_) => Ok(true),
Err(SSError::InvalidPassword) => Ok(false),
Err(e) => Err(Error::SStore(e)),
}
self.sstore.test_password(account, password)
.map_err(Into::into)
}

/// Permanently removes an account.
Expand All @@ -256,7 +242,7 @@ impl AccountProvider {
let _ = try!(self.sstore.sign(&account, &password, &Default::default()));

// check if account is already unlocked pernamently, if it is, do nothing
let mut unlocked = self.unlocked.lock();
let mut unlocked = self.unlocked.write();
if let Some(data) = unlocked.get(&account) {
if let Unlock::Perm = data.unlock {
return Ok(())
Expand All @@ -273,7 +259,7 @@ impl AccountProvider {
}

fn password(&self, account: &Address) -> Result<String, Error> {
let mut unlocked = self.unlocked.lock();
let mut unlocked = self.unlocked.write();
let data = try!(unlocked.get(account).ok_or(Error::NotUnlocked)).clone();
if let Unlock::Temp = data.unlock {
unlocked.remove(account).expect("data exists: so key must exist: qed");
Expand Down Expand Up @@ -304,7 +290,7 @@ impl AccountProvider {

/// Checks if given account is unlocked
pub fn is_unlocked(&self, account: Address) -> bool {
let unlocked = self.unlocked.lock();
let unlocked = self.unlocked.read();
unlocked.get(&account).is_some()
}

Expand All @@ -314,6 +300,48 @@ impl AccountProvider {
Ok(try!(self.sstore.sign(&account, &password, &message)))
}

/// Signs given message with supplied token. Returns a token to use in next signing within this session.
pub fn sign_with_token(&self, account: Address, token: AccountToken, message: Message) -> Result<(Signature, AccountToken), Error> {
let is_std_password = try!(self.sstore.test_password(&account, &token));

let new_token = random_string(16);
let signature = if is_std_password {
// Insert to transient store
try!(self.sstore.copy_account(&self.transient_sstore, &account, &token, &new_token));
// sign
try!(self.sstore.sign(&account, &token, &message))
} else {
// check transient store
try!(self.transient_sstore.change_password(&account, &token, &new_token));
// and sign
try!(self.transient_sstore.sign(&account, &new_token, &message))
};

Ok((signature, new_token))
}

/// Decrypts a message with given token. Returns a token to use in next operation for this account.
pub fn decrypt_with_token(&self, account: Address, token: AccountToken, shared_mac: &[u8], message: &[u8])
-> Result<(Vec<u8>, AccountToken), Error>
{
let is_std_password = try!(self.sstore.test_password(&account, &token));

let new_token = random_string(16);
let message = if is_std_password {
// Insert to transient store
try!(self.sstore.copy_account(&self.transient_sstore, &account, &token, &new_token));
// decrypt
try!(self.sstore.decrypt(&account, &token, shared_mac, message))
} else {
// check transient store
try!(self.transient_sstore.change_password(&account, &token, &new_token));
// and decrypt
try!(self.transient_sstore.decrypt(&account, &token, shared_mac, message))
};

Ok((message, new_token))
}

/// Decrypts a message. If password is not provided the account must be unlocked.
pub fn decrypt(&self, account: Address, password: Option<String>, shared_mac: &[u8], message: &[u8]) -> Result<Vec<u8>, Error> {
let password = try!(password.map(Ok).unwrap_or_else(|| self.password(&account)));
Expand Down Expand Up @@ -370,10 +398,26 @@ mod tests {
assert!(ap.unlock_account_timed(kp.address(), "test1".into(), 60000).is_err());
assert!(ap.unlock_account_timed(kp.address(), "test".into(), 60000).is_ok());
assert!(ap.sign(kp.address(), None, Default::default()).is_ok());
ap.unlocked.lock().get_mut(&kp.address()).unwrap().unlock = Unlock::Timed(Instant::now());
ap.unlocked.write().get_mut(&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() {
// given
let kp = Random.generate().unwrap();
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".into(), Default::default()).unwrap();

// then
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_set_dapps_addresses() {
// given
Expand Down
10 changes: 7 additions & 3 deletions ethstore/src/dir/disk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ use std::{fs, io};
use std::path::{PathBuf, Path};
use std::collections::HashMap;
use time;
use ethkey::Address;
use {json, SafeAccount, Error};
use json::Uuid;
use super::KeyDirectory;
Expand Down Expand Up @@ -106,6 +105,11 @@ impl KeyDirectory for DiskDirectory {
Ok(accounts)
}

fn update(&self, account: SafeAccount) -> Result<SafeAccount, Error> {
// Disk store handles updates correctly iff filename is the same
self.insert(account)
}

fn insert(&self, account: SafeAccount) -> Result<SafeAccount, Error> {
// transform account into key file
let keyfile: json::KeyFile = account.clone().into();
Expand Down Expand Up @@ -138,12 +142,12 @@ impl KeyDirectory for DiskDirectory {
Ok(account)
}

fn remove(&self, address: &Address) -> Result<(), Error> {
fn remove(&self, account: &SafeAccount) -> Result<(), Error> {
// enumerate all entries in keystore
// and find entry with given address
let to_remove = try!(self.files())
.into_iter()
.find(|&(_, ref account)| &account.address == address);
.find(|&(_, ref acc)| acc == account);

// remove it
match to_remove {
Expand Down
9 changes: 6 additions & 3 deletions ethstore/src/dir/geth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

use std::env;
use std::path::PathBuf;
use ethkey::Address;
use {SafeAccount, Error};
use super::{KeyDirectory, DiskDirectory, DirectoryType};

Expand Down Expand Up @@ -89,7 +88,11 @@ impl KeyDirectory for GethDirectory {
self.dir.insert(account)
}

fn remove(&self, address: &Address) -> Result<(), Error> {
self.dir.remove(address)
fn update(&self, account: SafeAccount) -> Result<SafeAccount, Error> {
self.dir.update(account)
}

fn remove(&self, account: &SafeAccount) -> Result<(), Error> {
self.dir.remove(account)
}
}
67 changes: 67 additions & 0 deletions ethstore/src/dir/memory.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.

// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.

use std::collections::HashMap;
use parking_lot::RwLock;
use itertools::Itertools;
use ethkey::Address;

use {SafeAccount, Error};
use super::KeyDirectory;

#[derive(Default)]
pub struct MemoryDirectory {
accounts: RwLock<HashMap<Address, Vec<SafeAccount>>>,
}

impl KeyDirectory for MemoryDirectory {
fn load(&self) -> Result<Vec<SafeAccount>, Error> {
Ok(self.accounts.read().values().cloned().flatten().collect())
}

fn update(&self, account: SafeAccount) -> Result<SafeAccount, Error> {
let mut lock = self.accounts.write();
let mut accounts = lock.entry(account.address.clone()).or_insert_with(Vec::new);
// If the filename is the same we just need to replace the entry
accounts.retain(|acc| acc.filename != account.filename);
accounts.push(account.clone());
Ok(account)
}

fn insert(&self, account: SafeAccount) -> Result<SafeAccount, Error> {
let mut lock = self.accounts.write();
let mut accounts = lock.entry(account.address.clone()).or_insert_with(Vec::new);
accounts.push(account.clone());
Ok(account)
}

fn remove(&self, account: &SafeAccount) -> Result<(), Error> {
let mut accounts = self.accounts.write();
let is_empty = if let Some(mut accounts) = accounts.get_mut(&account.address) {
if let Some(position) = accounts.iter().position(|acc| acc == account) {
accounts.remove(position);
}
accounts.is_empty()
} else {
false
};
if is_empty {
accounts.remove(&account.address);
}
Ok(())
}
}

6 changes: 4 additions & 2 deletions ethstore/src/dir/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.

use ethkey::Address;
use std::path::{PathBuf};
use {SafeAccount, Error};

mod disk;
mod geth;
mod memory;
mod parity;

pub enum DirectoryType {
Expand All @@ -30,10 +30,12 @@ pub enum DirectoryType {
pub trait KeyDirectory: Send + Sync {
fn load(&self) -> Result<Vec<SafeAccount>, Error>;
fn insert(&self, account: SafeAccount) -> Result<SafeAccount, Error>;
fn remove(&self, address: &Address) -> Result<(), Error>;
fn update(&self, account: SafeAccount) -> Result<SafeAccount, Error>;
fn remove(&self, account: &SafeAccount) -> Result<(), Error>;
fn path(&self) -> Option<&PathBuf> { None }
}

pub use self::disk::DiskDirectory;
pub use self::geth::GethDirectory;
pub use self::memory::MemoryDirectory;
pub use self::parity::ParityDirectory;
9 changes: 6 additions & 3 deletions ethstore/src/dir/parity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

use std::env;
use std::path::PathBuf;
use ethkey::Address;
use {SafeAccount, Error};
use super::{KeyDirectory, DiskDirectory, DirectoryType};

Expand Down Expand Up @@ -68,7 +67,11 @@ impl KeyDirectory for ParityDirectory {
self.dir.insert(account)
}

fn remove(&self, address: &Address) -> Result<(), Error> {
self.dir.remove(address)
fn update(&self, account: SafeAccount) -> Result<SafeAccount, Error> {
self.dir.update(account)
}

fn remove(&self, account: &SafeAccount) -> Result<(), Error> {
self.dir.remove(account)
}
}
Loading

0 comments on commit c4406c9

Please sign in to comment.