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

Commit

Permalink
Fix wallet import (#7873)
Browse files Browse the repository at this point in the history
* rpc: generate new account id for imported wallets

* ethstore: handle duplicate wallet filenames

* ethstore: simplify deduplication of wallet file names

* ethstore: do not dedup wallet filenames on update

* ethstore: fix minor grumbles
  • Loading branch information
andresilva authored and tomusdrw committed Feb 14, 2018
1 parent b8872e3 commit c5ce19b
Show file tree
Hide file tree
Showing 7 changed files with 93 additions and 24 deletions.
6 changes: 3 additions & 3 deletions ethcore/src/account_provider/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,9 +264,9 @@ impl AccountProvider {
Ok(Address::from(account.address).into())
}

/// Import a new presale wallet.
pub fn import_wallet(&self, json: &[u8], password: &str) -> Result<Address, Error> {
let account = self.sstore.import_wallet(SecretVaultRef::Root, json, password)?;
/// Import a new wallet.
pub fn import_wallet(&self, json: &[u8], password: &str, gen_id: bool) -> Result<Address, Error> {
let account = self.sstore.import_wallet(SecretVaultRef::Root, json, password, gen_id)?;
if self.blacklisted_accounts.contains(&account.address) {
self.sstore.remove_account(&account, password)?;
return Err(SSError::InvalidAccount.into());
Expand Down
76 changes: 60 additions & 16 deletions ethstore/src/accounts_dir/disk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,22 +153,30 @@ impl<T> DiskDirectory<T> where T: KeyFileManager {
)
}

/// insert account with given file name
pub fn insert_with_filename(&self, account: SafeAccount, filename: String) -> Result<SafeAccount, Error> {

/// insert account with given filename. if the filename is a duplicate of any stored account and dedup is set to
/// true, a random suffix is appended to the filename.
pub fn insert_with_filename(&self, account: SafeAccount, mut filename: String, dedup: bool) -> Result<SafeAccount, Error> {
// path to keyfile
let mut keyfile_path = self.path.join(filename.as_str());

// check for duplicate filename and append random suffix
if dedup && keyfile_path.exists() {
let suffix = ::random::random_string(4);
filename.push_str(&format!("-{}", suffix));
keyfile_path.set_file_name(&filename);
}

// update account filename
let original_account = account.clone();
let mut account = account;
account.filename = Some(filename.clone());
account.filename = Some(filename);

{
// Path to keyfile
let mut keyfile_path = self.path.clone();
keyfile_path.push(filename.as_str());

// save the file
let mut file = fs::File::create(&keyfile_path)?;

// Write key content
// write key content
self.key_manager.write(original_account, &mut file).map_err(|e| Error::Custom(format!("{:?}", e)))?;

file.flush()?;
Expand Down Expand Up @@ -200,17 +208,13 @@ impl<T> KeyDirectory for DiskDirectory<T> where T: KeyFileManager {

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

fn insert(&self, account: SafeAccount) -> Result<SafeAccount, Error> {
// build file path
let filename = account.filename.as_ref().cloned().unwrap_or_else(|| {
let timestamp = time::strftime("%Y-%m-%dT%H-%M-%S", &time::now_utc()).expect("Time-format string is valid.");
format!("UTC--{}Z--{}", timestamp, Uuid::from(account.id))
});

self.insert_with_filename(account, filename)
let filename = account_filename(&account);
self.insert_with_filename(account, filename, true)
}

fn remove(&self, account: &SafeAccount) -> Result<(), Error> {
Expand Down Expand Up @@ -286,6 +290,14 @@ impl KeyFileManager for DiskKeyFileManager {
}
}

fn account_filename(account: &SafeAccount) -> String {
// build file path
account.filename.clone().unwrap_or_else(|| {
let timestamp = time::strftime("%Y-%m-%dT%H-%M-%S", &time::now_utc()).expect("Time-format string is valid.");
format!("UTC--{}Z--{}", timestamp, Uuid::from(account.id))
})
}

#[cfg(test)]
mod test {
extern crate tempdir;
Expand Down Expand Up @@ -317,6 +329,38 @@ mod test {
let _ = fs::remove_dir_all(dir);
}

#[test]
fn should_handle_duplicate_filenames() {
// given
let mut dir = env::temp_dir();
dir.push("ethstore_should_handle_duplicate_filenames");
let keypair = Random.generate().unwrap();
let password = "hello world";
let directory = RootDiskDirectory::create(dir.clone()).unwrap();

// when
let account = SafeAccount::create(&keypair, [0u8; 16], password, 1024, "Test".to_owned(), "{}".to_owned());
let filename = "test".to_string();
let dedup = true;

directory.insert_with_filename(account.clone(), "foo".to_string(), dedup).unwrap();
let file1 = directory.insert_with_filename(account.clone(), filename.clone(), dedup).unwrap().filename.unwrap();
let file2 = directory.insert_with_filename(account.clone(), filename.clone(), dedup).unwrap().filename.unwrap();
let file3 = directory.insert_with_filename(account.clone(), filename.clone(), dedup).unwrap().filename.unwrap();

// then
// the first file should have the original names
assert_eq!(file1, filename);

// the following duplicate files should have a suffix appended
assert!(file2 != file3);
assert_eq!(file2.len(), filename.len() + 5);
assert_eq!(file3.len(), filename.len() + 5);

// cleanup
let _ = fs::remove_dir_all(dir);
}

#[test]
fn should_manage_vaults() {
// given
Expand Down
2 changes: 1 addition & 1 deletion ethstore/src/accounts_dir/vault.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ impl VaultDiskDirectory {
fn copy_to_vault(&self, vault: &VaultDiskDirectory) -> Result<(), Error> {
for account in self.load()? {
let filename = account.filename.clone().expect("self is instance of DiskDirectory; DiskDirectory fills filename in load; qed");
vault.insert_with_filename(account, filename)?;
vault.insert_with_filename(account, filename, true)?;
}

Ok(())
Expand Down
7 changes: 6 additions & 1 deletion ethstore/src/ethstore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,9 +166,14 @@ impl SecretStore for EthStore {
self.insert_account(vault, keypair.secret().clone(), password)
}

fn import_wallet(&self, vault: SecretVaultRef, json: &[u8], password: &str) -> Result<StoreAccountRef, Error> {
fn import_wallet(&self, vault: SecretVaultRef, json: &[u8], password: &str, gen_id: bool) -> Result<StoreAccountRef, Error> {
let json_keyfile = json::KeyFile::load(json).map_err(|_| Error::InvalidKeyFile("Invalid JSON format".to_owned()))?;
let mut safe_account = SafeAccount::from_file(json_keyfile, None);

if gen_id {
safe_account.id = Random::random();
}

let secret = safe_account.crypto.secret(password).map_err(|_| Error::InvalidPassword)?;
safe_account.address = KeyPair::from_secret(secret)?.address();
self.store.import(vault, safe_account)
Expand Down
2 changes: 1 addition & 1 deletion ethstore/src/secret_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ pub trait SecretStore: SimpleSecretStore {
/// Imports presale wallet
fn import_presale(&self, vault: SecretVaultRef, json: &[u8], password: &str) -> Result<StoreAccountRef, Error>;
/// Imports existing JSON wallet
fn import_wallet(&self, vault: SecretVaultRef, json: &[u8], password: &str) -> Result<StoreAccountRef, Error>;
fn import_wallet(&self, vault: SecretVaultRef, json: &[u8], password: &str, gen_id: bool) -> Result<StoreAccountRef, Error>;
/// Copies account between stores and vaults.
fn copy_account(&self, new_store: &SimpleSecretStore, new_vault: SecretVaultRef, account: &StoreAccountRef, password: &str, new_password: &str) -> Result<(), Error>;
/// Checks if password matches given account.
Expand Down
2 changes: 1 addition & 1 deletion rpc/src/v1/impls/parity_accounts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ impl ParityAccounts for ParityAccountsClient {
let store = self.account_provider()?;

store.import_presale(json.as_bytes(), &pass)
.or_else(|_| store.import_wallet(json.as_bytes(), &pass))
.or_else(|_| store.import_wallet(json.as_bytes(), &pass, true))
.map(Into::into)
.map_err(|e| errors::account("Could not create account.", e))
}
Expand Down
22 changes: 21 additions & 1 deletion rpc/src/v1/tests/mocked/parity_accounts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -480,7 +480,7 @@ fn should_export_account() {
// given
let tester = setup();
let wallet = r#"{"id":"6a186c80-7797-cff2-bc2e-7c1d6a6cc76e","version":3,"crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"a1c6ff99070f8032ca1c4e8add006373"},"ciphertext":"df27e3db64aa18d984b6439443f73660643c2d119a6f0fa2fa9a6456fc802d75","kdf":"pbkdf2","kdfparams":{"c":10240,"dklen":32,"prf":"hmac-sha256","salt":"ddc325335cda5567a1719313e73b4842511f3e4a837c9658eeb78e51ebe8c815"},"mac":"3dc888ae79cbb226ff9c455669f6cf2d79be72120f2298f6cb0d444fddc0aa3d"},"address":"0042e5d2a662eeaca8a7e828c174f98f35d8925b","name":"parity-export-test","meta":"{\"passwordHint\":\"parity-export-test\",\"timestamp\":1490017814987}"}"#;
tester.accounts.import_wallet(wallet.as_bytes(), "parity-export-test").unwrap();
tester.accounts.import_wallet(wallet.as_bytes(), "parity-export-test", false).unwrap();
let accounts = tester.accounts.accounts().unwrap();
assert_eq!(accounts.len(), 1);

Expand All @@ -501,6 +501,26 @@ fn should_export_account() {
assert_eq!(result, Some(response.into()));
}

#[test]
fn should_import_wallet() {
let tester = setup();

let id = "6a186c80-7797-cff2-bc2e-7c1d6a6cc76e";
let request = r#"{"jsonrpc":"2.0","method":"parity_newAccountFromWallet","params":["{\"id\":\"<ID>\",\"version\":3,\"crypto\":{\"cipher\":\"aes-128-ctr\",\"cipherparams\":{\"iv\":\"478736fb55872c1baf01b27b1998c90b\"},\"ciphertext\":\"fe5a63cc0055d7b0b3b57886f930ad9b63f48950d1348145d95996c41e05f4e0\",\"kdf\":\"pbkdf2\",\"kdfparams\":{\"c\":10240,\"dklen\":32,\"prf\":\"hmac-sha256\",\"salt\":\"658436d6738a19731149a98744e5cf02c8d5aa1f8e80c1a43cc9351c70a984e4\"},\"mac\":\"c7384b26ecf25539d942030230062af9b69de5766cbcc4690bffce1536644631\"},\"address\":\"00bac56a8a27232baa044c03f43bf3648c961735\",\"name\":\"hello world\",\"meta\":\"{}\"}", "himom"],"id":1}"#;
let request = request.replace("<ID>", id);
let response = r#"{"jsonrpc":"2.0","result":"0x00bac56a8a27232baa044c03f43bf3648c961735","id":1}"#;

let res = tester.io.handle_request_sync(&request).unwrap();

assert_eq!(res, response);

let account_meta = tester.accounts.account_meta("0x00bac56a8a27232baa044c03f43bf3648c961735".into()).unwrap();
let account_uuid: String = account_meta.uuid.unwrap().into();

// the RPC should import the account with a new id
assert!(account_uuid != id);
}

#[test]
fn should_sign_message() {
let tester = setup();
Expand Down

0 comments on commit c5ce19b

Please sign in to comment.