diff --git a/Cargo.lock b/Cargo.lock index bf7e727c7..5dfa0acdc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -708,7 +708,7 @@ dependencies = [ "ethereum-types 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "ethjson 0.1.0", "ethkey 0.3.0", - "ethstore 0.2.0", + "ethstore 0.2.1", "evm 0.1.0", "fake-hardware-wallet 0.0.1", "hardware-wallet 1.12.0", @@ -1143,7 +1143,7 @@ dependencies = [ [[package]] name = "ethstore" -version = "0.2.0" +version = "0.2.1" dependencies = [ "dir 0.1.2", "ethereum-types 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1168,11 +1168,12 @@ dependencies = [ [[package]] name = "ethstore-cli" -version = "0.1.0" +version = "0.1.1" dependencies = [ "dir 0.1.2", "docopt 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "ethstore 0.2.0", + "env_logger 0.5.13 (registry+https://github.com/rust-lang/crates.io-index)", + "ethstore 0.2.1", "num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "panic_hook 0.1.0", "parking_lot 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2526,7 +2527,7 @@ dependencies = [ "ethereum-types 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "ethjson 0.1.0", "ethkey 0.3.0", - "ethstore 0.2.0", + "ethstore 0.2.1", "fake-fetch 0.0.1", "fake-hardware-wallet 0.0.1", "fastmap 0.1.0", diff --git a/accounts/ethkey/cli/src/main.rs b/accounts/ethkey/cli/src/main.rs index e908baec3..71ea5ca6c 100644 --- a/accounts/ethkey/cli/src/main.rs +++ b/accounts/ethkey/cli/src/main.rs @@ -168,7 +168,7 @@ fn main() { Ok(ok) => println!("{}", ok), Err(Error::Docopt(ref e)) => e.exit(), Err(err) => { - println!("{}", err); + eprintln!("{}", err); process::exit(1); } } diff --git a/accounts/ethstore/Cargo.toml b/accounts/ethstore/Cargo.toml index 384a06acb..96d24d8c3 100644 --- a/accounts/ethstore/Cargo.toml +++ b/accounts/ethstore/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ethstore" -version = "0.2.0" +version = "0.2.1" authors = ["Parity Technologies "] [dependencies] diff --git a/accounts/ethstore/cli/Cargo.toml b/accounts/ethstore/cli/Cargo.toml index c1a2ee6d1..82858eaa7 100644 --- a/accounts/ethstore/cli/Cargo.toml +++ b/accounts/ethstore/cli/Cargo.toml @@ -1,10 +1,11 @@ [package] name = "ethstore-cli" -version = "0.1.0" +version = "0.1.1" authors = ["Parity Technologies "] [dependencies] docopt = "1.0" +env_logger = "0.5" num_cpus = "1.6" rustc-hex = "1.0" serde = "1.0" diff --git a/accounts/ethstore/cli/src/main.rs b/accounts/ethstore/cli/src/main.rs index 6751f713c..7e607973c 100644 --- a/accounts/ethstore/cli/src/main.rs +++ b/accounts/ethstore/cli/src/main.rs @@ -23,6 +23,8 @@ extern crate parking_lot; extern crate rustc_hex; extern crate serde; +extern crate env_logger; + #[macro_use] extern crate serde_derive; @@ -45,7 +47,7 @@ Usage: ethstore insert [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD] ethstore change-pwd
[--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD] ethstore list [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD] - ethstore import [--src DIR] [--dir DIR] + ethstore import [] [--src DIR] [--dir DIR] ethstore import-wallet [--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD] ethstore find-wallet-pass ethstore remove
[--dir DIR] [--vault VAULT] [--vault-pwd VAULTPWD] @@ -146,30 +148,34 @@ impl fmt::Display for Error { fn main() { panic_hook::set_abort(); + if env::var("RUST_LOG").is_err() { + env::set_var("RUST_LOG", "warn") + } + env_logger::try_init().expect("Logger initialized only once."); match execute(env::args()) { Ok(result) => println!("{}", result), Err(Error::Docopt(ref e)) => e.exit(), Err(err) => { - println!("{}", err); + eprintln!("{}", err); process::exit(1); } } } -fn key_dir(location: &str) -> Result, Error> { - let dir: Box = match location { - "geth" => Box::new(RootDiskDirectory::create(dir::geth(false))?), - "geth-test" => Box::new(RootDiskDirectory::create(dir::geth(true))?), +fn key_dir(location: &str, password: Option) -> Result, Error> { + let dir: RootDiskDirectory = match location { + "geth" => RootDiskDirectory::create(dir::geth(false))?, + "geth-test" => RootDiskDirectory::create(dir::geth(true))?, path if path.starts_with("parity") => { let chain = path.split('-').nth(1).unwrap_or("ethereum"); let path = dir::parity(chain); - Box::new(RootDiskDirectory::create(path)?) + RootDiskDirectory::create(path)? }, - path => Box::new(RootDiskDirectory::create(path)?), + path => RootDiskDirectory::create(path)?, }; - Ok(dir) + Ok(Box::new(dir.with_password(password))) } fn open_args_vault(store: &EthStore, args: &Args) -> Result { @@ -202,9 +208,9 @@ fn format_vaults(vaults: &[String]) -> String { } fn load_password(path: &str) -> Result { - let mut file = fs::File::open(path).map_err(|e| ethstore::Error::Custom(format!("Error opening password file {}: {}", path, e)))?; + let mut file = fs::File::open(path).map_err(|e| ethstore::Error::Custom(format!("Error opening password file '{}': {}", path, e)))?; let mut password = String::new(); - file.read_to_string(&mut password).map_err(|e| ethstore::Error::Custom(format!("Error reading password file {}: {}", path, e)))?; + file.read_to_string(&mut password).map_err(|e| ethstore::Error::Custom(format!("Error reading password file '{}': {}", path, e)))?; // drop EOF let _ = password.pop(); Ok(password.into()) @@ -214,7 +220,7 @@ fn execute(command: I) -> Result where I: IntoIterator(command: I) -> Result where I: IntoIterator None, + _ => Some(load_password(&args.arg_password)?) + }; + let src = key_dir(&args.flag_src, password)?; + let dst = key_dir(&args.flag_dir, None)?; + let accounts = import_accounts(&*src, &*dst)?; Ok(format_accounts(&accounts)) } else if args.cmd_import_wallet { diff --git a/accounts/ethstore/src/account/crypto.rs b/accounts/ethstore/src/account/crypto.rs index 07f84af7f..4ab01dae8 100644 --- a/accounts/ethstore/src/account/crypto.rs +++ b/accounts/ethstore/src/account/crypto.rs @@ -84,7 +84,8 @@ impl Crypto { // two parts of derived key // DK = [ DK[0..15] DK[16..31] ] = [derived_left_bits, derived_right_bits] - let (derived_left_bits, derived_right_bits) = crypto::derive_key_iterations(password.as_bytes(), &salt, iterations); + let (derived_left_bits, derived_right_bits) = + crypto::derive_key_iterations(password.as_bytes(), &salt, iterations); // preallocated (on-stack in case of `Secret`) buffer to hold cipher // length = length(plain) as we are using CTR-approach @@ -104,7 +105,7 @@ impl Crypto { ciphertext: ciphertext.into_vec(), kdf: Kdf::Pbkdf2(Pbkdf2 { dklen: crypto::KEY_LENGTH as u32, - salt: salt, + salt: salt.to_vec(), c: iterations, prf: Prf::HmacSha256, }), diff --git a/accounts/ethstore/src/account/kdf.rs b/accounts/ethstore/src/account/kdf.rs index 4d6d7cd95..e8d001917 100644 --- a/accounts/ethstore/src/account/kdf.rs +++ b/accounts/ethstore/src/account/kdf.rs @@ -26,7 +26,7 @@ pub struct Pbkdf2 { pub c: u32, pub dklen: u32, pub prf: Prf, - pub salt: [u8; 32], + pub salt: Vec, } #[derive(Debug, PartialEq, Clone)] @@ -35,7 +35,7 @@ pub struct Scrypt { pub p: u32, pub n: u32, pub r: u32, - pub salt: [u8; 32], + pub salt: Vec, } #[derive(Debug, PartialEq, Clone)] diff --git a/accounts/ethstore/src/account/safe_account.rs b/accounts/ethstore/src/account/safe_account.rs index 849dafab1..4492c6014 100644 --- a/accounts/ethstore/src/account/safe_account.rs +++ b/accounts/ethstore/src/account/safe_account.rs @@ -45,7 +45,7 @@ impl Into for SafeAccount { json::KeyFile { id: From::from(self.id), version: self.version.into(), - address: self.address.into(), + address: Some(self.address.into()), crypto: self.crypto.into(), name: Some(self.name.into()), meta: Some(self.meta.into()), @@ -77,16 +77,43 @@ impl SafeAccount { /// Create a new `SafeAccount` from the given `json`; if it was read from a /// file, the `filename` should be `Some` name. If it is as yet anonymous, then it /// can be left `None`. - pub fn from_file(json: json::KeyFile, filename: Option) -> Self { - SafeAccount { + /// In case `password` is provided, we will attempt to read the secret from the keyfile + /// and derive the address from it instead of reading it directly. + /// Providing password is required for `json::KeyFile`s with no address. + pub fn from_file(json: json::KeyFile, filename: Option, password: &Option) -> Result { + let crypto = Crypto::from(json.crypto); + let address = match (password, &json.address) { + (None, Some(json_address)) => json_address.into(), + (None, None) => Err(Error::Custom( + "This keystore does not contain address. You need to provide password to import it".into()))?, + (Some(password), json_address) => { + let derived_address = KeyPair::from_secret( + crypto.secret(&password).map_err(|_| Error::InvalidPassword)? + )?.address(); + + match json_address { + Some(json_address) => { + let json_address = json_address.into(); + if derived_address != json_address { + warn!("Detected address mismatch when opening an account. Derived: {:?}, in json got: {:?}", + derived_address, json_address); + } + }, + _ => {}, + } + derived_address + } + }; + + Ok(SafeAccount { id: json.id.into(), version: json.version.into(), - address: json.address.into(), - crypto: json.crypto.into(), - filename: filename, + address, + crypto, + filename, name: json.name.unwrap_or(String::new()), meta: json.meta.unwrap_or("{}".to_owned()), - } + }) } /// Create a new `SafeAccount` from the given vault `json`; if it was read from a @@ -97,14 +124,14 @@ impl SafeAccount { let meta_plain = meta_crypto.decrypt(password)?; let meta_plain = json::VaultKeyMeta::load(&meta_plain).map_err(|e| Error::Custom(format!("{:?}", e)))?; - Ok(SafeAccount::from_file(json::KeyFile { + SafeAccount::from_file(json::KeyFile { id: json.id, version: json.version, crypto: json.crypto, - address: meta_plain.address, + address: Some(meta_plain.address), name: meta_plain.name, meta: meta_plain.meta, - }, filename)) + }, filename, &None) } /// Create a new `VaultKeyFile` from the given `self` diff --git a/accounts/ethstore/src/accounts_dir/disk.rs b/accounts/ethstore/src/accounts_dir/disk.rs index cf4841e74..344c643c5 100644 --- a/accounts/ethstore/src/accounts_dir/disk.rs +++ b/accounts/ethstore/src/accounts_dir/disk.rs @@ -23,6 +23,7 @@ use {json, SafeAccount, Error}; use json::Uuid; use super::{KeyDirectory, VaultKeyDirectory, VaultKeyDirectoryProvider, VaultKey}; use super::vault::{VAULT_FILE_NAME, VaultDiskDirectory}; +use ethkey::Password; const IGNORED_FILES: &'static [&'static str] = &[ "thumbs.db", @@ -106,6 +107,7 @@ pub type RootDiskDirectory = DiskDirectory; pub trait KeyFileManager: Send + Sync { /// Read `SafeAccount` from given key file stream fn read(&self, filename: Option, reader: T) -> Result where T: io::Read; + /// Write `SafeAccount` to given key file stream fn write(&self, account: SafeAccount, writer: &mut T) -> Result<(), Error> where T: io::Write; } @@ -117,7 +119,10 @@ pub struct DiskDirectory where T: KeyFileManager { } /// Keys file manager for root keys directory -pub struct DiskKeyFileManager; +#[derive(Default)] +pub struct DiskKeyFileManager { + password: Option, +} impl RootDiskDirectory { pub fn create

(path: P) -> Result where P: AsRef { @@ -125,8 +130,14 @@ impl RootDiskDirectory { Ok(Self::at(path)) } + /// allows to read keyfiles with given password (needed for keyfiles w/o address) + pub fn with_password(&self, password: Option) -> Self { + DiskDirectory::new(&self.path, DiskKeyFileManager { password }) + } + + pub fn at

(path: P) -> Self where P: AsRef { - DiskDirectory::new(path, DiskKeyFileManager) + DiskDirectory::new(path, DiskKeyFileManager::default()) } } @@ -319,7 +330,7 @@ impl VaultKeyDirectoryProvider for DiskDirectory where T: KeyFileManager { impl KeyFileManager for DiskKeyFileManager { fn read(&self, filename: Option, reader: T) -> Result where T: io::Read { let key_file = json::KeyFile::load(reader).map_err(|e| Error::Custom(format!("{:?}", e)))?; - Ok(SafeAccount::from_file(key_file, filename)) + SafeAccount::from_file(key_file, filename, &self.password) } fn write(&self, mut account: SafeAccount, writer: &mut T) -> Result<(), Error> where T: io::Write { diff --git a/accounts/ethstore/src/ethstore.rs b/accounts/ethstore/src/ethstore.rs index 2deafbad6..574ac8c3d 100644 --- a/accounts/ethstore/src/ethstore.rs +++ b/accounts/ethstore/src/ethstore.rs @@ -168,7 +168,7 @@ impl SecretStore for EthStore { fn import_wallet(&self, vault: SecretVaultRef, json: &[u8], password: &Password, gen_id: bool) -> Result { 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); + let mut safe_account = SafeAccount::from_file(json_keyfile, None, &None)?; if gen_id { safe_account.id = Random::random(); diff --git a/accounts/ethstore/src/import.rs b/accounts/ethstore/src/import.rs index 876119fd5..e1b73c567 100644 --- a/accounts/ethstore/src/import.rs +++ b/accounts/ethstore/src/import.rs @@ -25,7 +25,7 @@ use Error; /// Import an account from a file. pub fn import_account(path: &Path, dst: &KeyDirectory) -> Result { - let key_manager = DiskKeyFileManager; + let key_manager = DiskKeyFileManager::default(); let existing_accounts = dst.load()?.into_iter().map(|a| a.address).collect::>(); let filename = path.file_name().and_then(|n| n.to_str()).map(|f| f.to_owned()); let account = fs::File::open(&path) @@ -42,7 +42,9 @@ pub fn import_account(path: &Path, dst: &KeyDirectory) -> Result /// Import all accounts from one directory to the other. pub fn import_accounts(src: &KeyDirectory, dst: &KeyDirectory) -> Result, Error> { let accounts = src.load()?; - let existing_accounts = dst.load()?.into_iter().map(|a| a.address).collect::>(); + let existing_accounts = dst.load()?.into_iter() + .map(|a| a.address) + .collect::>(); accounts.into_iter() .filter(|a| !existing_accounts.contains(&a.address)) diff --git a/accounts/ethstore/src/json/crypto.rs b/accounts/ethstore/src/json/crypto.rs index 0a926cc83..ff4728a96 100644 --- a/accounts/ethstore/src/json/crypto.rs +++ b/accounts/ethstore/src/json/crypto.rs @@ -52,6 +52,7 @@ enum CryptoField { Kdf, KdfParams, Mac, + Version, } impl<'a> Deserialize<'a> for CryptoField { @@ -81,6 +82,7 @@ impl<'a> Visitor<'a> for CryptoFieldVisitor { "kdf" => Ok(CryptoField::Kdf), "kdfparams" => Ok(CryptoField::KdfParams), "mac" => Ok(CryptoField::Mac), + "version" => Ok(CryptoField::Version), _ => Err(Error::custom(format!("Unknown field: '{}'", value))), } } @@ -122,6 +124,8 @@ impl<'a> Visitor<'a> for CryptoVisitor { Some(CryptoField::Kdf) => { kdf = Some(visitor.next_value()?); } Some(CryptoField::KdfParams) => { kdfparams = Some(visitor.next_value()?); } Some(CryptoField::Mac) => { mac = Some(visitor.next_value()?); } + // skip not required version field (it appears in pyethereum generated keystores) + Some(CryptoField::Version) => { visitor.next_value().unwrap_or(()) } None => { break; } } } diff --git a/accounts/ethstore/src/json/kdf.rs b/accounts/ethstore/src/json/kdf.rs index f8df3c228..d7b3f08f5 100644 --- a/accounts/ethstore/src/json/kdf.rs +++ b/accounts/ethstore/src/json/kdf.rs @@ -17,7 +17,7 @@ use std::fmt; use serde::{Serialize, Serializer, Deserialize, Deserializer}; use serde::de::{Visitor, Error as SerdeError}; -use super::{Error, H256}; +use super::{Error, Bytes}; #[derive(Debug, PartialEq)] pub enum KdfSer { @@ -111,7 +111,7 @@ pub struct Pbkdf2 { pub c: u32, pub dklen: u32, pub prf: Prf, - pub salt: H256, + pub salt: Bytes, } #[derive(Debug, PartialEq, Serialize, Deserialize)] @@ -120,7 +120,7 @@ pub struct Scrypt { pub p: u32, pub n: u32, pub r: u32, - pub salt: H256, + pub salt: Bytes, } #[derive(Debug, PartialEq)] diff --git a/accounts/ethstore/src/json/key_file.rs b/accounts/ethstore/src/json/key_file.rs index 2c3cf3fdd..d5dc13605 100644 --- a/accounts/ethstore/src/json/key_file.rs +++ b/accounts/ethstore/src/json/key_file.rs @@ -46,7 +46,7 @@ pub struct KeyFile { pub id: Uuid, pub version: Version, pub crypto: Crypto, - pub address: H160, + pub address: Option, pub name: Option, pub meta: Option, } @@ -158,11 +158,6 @@ impl<'a> Visitor<'a> for KeyFileVisitor { None => return Err(V::Error::missing_field("crypto")), }; - let address = match address { - Some(address) => address, - None => return Err(V::Error::missing_field("address")), - }; - let result = KeyFile { id: id, version: version, @@ -222,7 +217,7 @@ mod tests { let expected = KeyFile { id: Uuid::from_str("8777d9f6-7860-4b9b-88b7-0b57ee6b3a73").unwrap(), version: Version::V3, - address: "6edddfc6349aff20bc6467ccf276c5b52487f7a8".into(), + address: Some("6edddfc6349aff20bc6467ccf276c5b52487f7a8".into()), crypto: Crypto { cipher: Cipher::Aes128Ctr(Aes128Ctr { iv: "b5a7ec855ec9e2c405371356855fec83".into(), @@ -273,7 +268,7 @@ mod tests { let expected = KeyFile { id: "8777d9f6-7860-4b9b-88b7-0b57ee6b3a73".into(), version: Version::V3, - address: "6edddfc6349aff20bc6467ccf276c5b52487f7a8".into(), + address: Some("6edddfc6349aff20bc6467ccf276c5b52487f7a8".into()), crypto: Crypto { cipher: Cipher::Aes128Ctr(Aes128Ctr { iv: "b5a7ec855ec9e2c405371356855fec83".into(), @@ -301,7 +296,7 @@ mod tests { let file = KeyFile { id: "8777d9f6-7860-4b9b-88b7-0b57ee6b3a73".into(), version: Version::V3, - address: "6edddfc6349aff20bc6467ccf276c5b52487f7a8".into(), + address: Some("6edddfc6349aff20bc6467ccf276c5b52487f7a8".into()), crypto: Crypto { cipher: Cipher::Aes128Ctr(Aes128Ctr { iv: "b5a7ec855ec9e2c405371356855fec83".into(),