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

Fix wallet import #7873

Merged
merged 5 commits into from
Feb 14, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is dedup optional and not a default behaviour?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Afair this function is also used to update the key file (for instance after a name change or meta change).

// 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