From e604c97a435dd763aec9fad8fbceecd65bfe4cf4 Mon Sep 17 00:00:00 2001 From: Nikolay Volf Date: Mon, 22 Feb 2016 23:12:13 +0300 Subject: [PATCH] more account tests --- ethtools/src/geth_keys.rs | 18 ++++++++++++ util/src/keys/directory.rs | 47 ++++++++++++++++++++++++++++-- util/src/keys/store.rs | 59 ++++++++++++++++++++++++++------------ 3 files changed, 103 insertions(+), 21 deletions(-) diff --git a/ethtools/src/geth_keys.rs b/ethtools/src/geth_keys.rs index c1a7782d7..7d6c7adef 100644 --- a/ethtools/src/geth_keys.rs +++ b/ethtools/src/geth_keys.rs @@ -49,9 +49,12 @@ pub fn enumerate_geth_keys(path: &Path) -> Result, io::Er Ok(entries) } +/// Geth import error #[derive(Debug)] pub enum ImportError { + /// Io error reading geth file IoError(io::Error), + /// format error FormatError, } @@ -81,6 +84,7 @@ pub fn import_geth_key(secret_store: &mut SecretStore, geth_keyfile_path: &Path) Ok(()) } +/// Imports all geth keys in the directory pub fn import_geth_keys(secret_store: &mut SecretStore, geth_keyfiles_directory: &Path) -> Result<(), ImportError> { let geth_files = try!(enumerate_geth_keys(geth_keyfiles_directory)); for &(ref address, ref file_path) in geth_files.iter() { @@ -129,4 +133,18 @@ mod tests { let key = secret_store.account(&Address::from_str("5ba4dcf897e97c2bdf8315b9ef26c13c085988cf").unwrap()); assert!(key.is_some()); } + + #[test] + fn can_decrypt_with_imported() { + use util::keys::store::EncryptedHashMap; + use util::bytes::*; + + let temp = ::devtools::RandomTempPath::create_dir(); + let mut secret_store = SecretStore::new_in(temp.as_path()); + import_geth_keys(&mut secret_store, Path::new("res/geth_keystore")).unwrap(); + + let val = secret_store.get::(&H128::from_str("62a0ad73556d496a8e1c0783d30d3ace").unwrap(), "123"); + assert!(val.is_ok()); + assert_eq!(vec![0u8, 10], val.unwrap()); + } } diff --git a/util/src/keys/directory.rs b/util/src/keys/directory.rs index e05ae4877..233b8b974 100644 --- a/util/src/keys/directory.rs +++ b/util/src/keys/directory.rs @@ -381,6 +381,7 @@ impl KeyFileContent { } } + /// Loads key from valid json, returns error and records warning if key is mallformed pub fn load(json: &Json) -> Result { match Self::from_json(json) { Ok(key_file) => Ok(key_file), @@ -444,6 +445,7 @@ impl KeyFileContent { map.insert("id".to_owned(), Json::String(uuid_to_string(&self.id))); map.insert("version".to_owned(), Json::U64(CURRENT_DECLARED_VERSION)); map.insert("crypto".to_owned(), self.crypto.to_json()); + if let Some(ref address) = self.account { map.insert("address".to_owned(), Json::String(format!("{:?}", address))); } Json::Object(map) } } @@ -670,7 +672,7 @@ mod file_tests { } #[test] - fn can_read_scrypt_krf() { + fn can_read_scrypt_kdf() { let json = Json::from_str( r#" { @@ -706,6 +708,47 @@ mod file_tests { } } + #[test] + fn can_read_scrypt_kdf_params() { + let json = Json::from_str( + r#" + { + "crypto" : { + "cipher" : "aes-128-ctr", + "cipherparams" : { + "iv" : "83dbcc02d8ccb40e466191a123791e0e" + }, + "ciphertext" : "d172bf743a674da9cdad04534d56926ef8358534d458fffccd4e6ad2fbde479c", + "kdf" : "scrypt", + "kdfparams" : { + "dklen" : 32, + "n" : 262144, + "r" : 1, + "p" : 8, + "salt" : "ab0c7876052600dd703518d6fc3fe8984592145b591fc8fb5c6d43190334ba19" + }, + "mac" : "2103ac29920d71da29f15d75b4a16dbe95cfd7ff8faea1056c33131d846e3097" + }, + "id" : "3198bc9c-6672-5ab3-d995-4942343ae5b6", + "version" : 3 + } + "#).unwrap(); + + match KeyFileContent::from_json(&json) { + Ok(key_file) => { + match key_file.crypto.kdf { + KeyFileKdf::Scrypt(scrypt_params) => { + assert_eq!(262144, scrypt_params.n); + assert_eq!(1, scrypt_params.r); + assert_eq!(8, scrypt_params.p); + }, + _ => { panic!("expected kdf params of crypto to be of scrypt type" ); } + } + }, + Err(e) => panic!("Error parsing valid file: {:?}", e) + } + } + #[test] fn can_return_error_no_id() { let json = Json::from_str( @@ -861,7 +904,7 @@ mod file_tests { panic!("Should be error of no identifier, got ok"); }, Err(KeyFileParseError::Crypto(CryptoParseError::Scrypt(_))) => { }, - Err(other_error) => { panic!("should be error of no identifier, got {:?}", other_error); } + Err(other_error) => { panic!("should be scrypt parse error, got {:?}", other_error); } } } diff --git a/util/src/keys/store.rs b/util/src/keys/store.rs index 7aa6d2e76..1fdb1e8a3 100644 --- a/util/src/keys/store.rs +++ b/util/src/keys/store.rs @@ -19,11 +19,12 @@ use keys::directory::*; use common::*; use rcrypto::pbkdf2::*; +use rcrypto::scrypt::*; use rcrypto::hmac::*; use crypto; const KEY_LENGTH: u32 = 32; -const KEY_ITERATIONS: u32 = 4096; +const KEY_ITERATIONS: u32 = 10240; const KEY_LENGTH_AES: u32 = KEY_LENGTH/2; const KEY_LENGTH_USIZE: usize = KEY_LENGTH as usize; @@ -60,13 +61,14 @@ pub struct SecretStore { } impl SecretStore { - /// new instance of Secret Store + /// new instance of Secret Store in default home directory pub fn new() -> SecretStore { let mut path = ::std::env::home_dir().expect("Failed to get home dir"); path.push("keystore"); Self::new_in(&path) } + /// new instance of Secret Store in specific directory pub fn new_in(path: &Path) -> SecretStore { SecretStore { directory: KeyDirectory::new(path) @@ -94,6 +96,7 @@ impl SecretStore { accounts.first().and_then(|&(_, ref key_id)| Some(key_id.clone())) } + /// Imports pregenerated key, returns error if not saved correctly pub fn import_key(&mut self, key_file: KeyFileContent) -> Result<(), ::std::io::Error> { try!(self.directory.save(key_file)); Ok(()) @@ -120,6 +123,15 @@ fn derive_key(password: &str, salt: &H256) -> (Bytes, Bytes) { derive_key_iterations(password, salt, KEY_ITERATIONS) } +fn derive_key_scrypt(password: &str, salt: &H256, n: u32, p: u32, r: u32) -> (Bytes, Bytes) { + let mut derived_key = vec![0u8; KEY_LENGTH_USIZE]; + let scrypt_params = ScryptParams::new(n as u8, r, p); + scrypt(password.as_bytes(), &salt.as_slice(), &scrypt_params, &mut derived_key); + let derived_right_bits = &derived_key[0..KEY_LENGTH_AES_USIZE]; + let derived_left_bits = &derived_key[KEY_LENGTH_AES_USIZE..KEY_LENGTH_USIZE]; + (derived_right_bits.to_vec(), derived_left_bits.to_vec()) +} + fn derive_mac(derived_left_bits: &[u8], cipher_text: &[u8]) -> Bytes { let mut mac = vec![0u8; KEY_LENGTH_AES_USIZE + cipher_text.len()]; mac[0..KEY_LENGTH_AES_USIZE].clone_from_slice(derived_left_bits); @@ -131,24 +143,22 @@ impl EncryptedHashMap for SecretStore { fn get(&self, key: &H128, password: &str) -> Result { match self.directory.get(key) { Some(key_file) => { - let decrypted_bytes = match key_file.crypto.kdf { - KeyFileKdf::Pbkdf2(ref params) => { - let (derived_left_bits, derived_right_bits) = derive_key_iterations(password, ¶ms.salt, params.c); - if derive_mac(&derived_right_bits, &key_file.crypto.cipher_text) - .sha3() != key_file.crypto.mac { return Err(EncryptedHashMapError::InvalidPassword); } - - let mut val = vec![0u8; key_file.crypto.cipher_text.len()]; - match key_file.crypto.cipher_type { - CryptoCipherType::Aes128Ctr(ref iv) => { - crypto::aes::decrypt(&derived_left_bits, &iv.as_slice(), &key_file.crypto.cipher_text, &mut val); - } - } - val - } - _ => { unimplemented!(); } + let (derived_left_bits, derived_right_bits) = match key_file.crypto.kdf { + KeyFileKdf::Pbkdf2(ref params) => derive_key_iterations(password, ¶ms.salt, params.c), + KeyFileKdf::Scrypt(ref params) => derive_key_scrypt(password, ¶ms.salt, params.n, params.p, params.r) }; - match Value::from_bytes(&decrypted_bytes) { + if derive_mac(&derived_right_bits, &key_file.crypto.cipher_text) + .sha3() != key_file.crypto.mac { return Err(EncryptedHashMapError::InvalidPassword); } + + let mut val = vec![0u8; key_file.crypto.cipher_text.len()]; + match key_file.crypto.cipher_type { + CryptoCipherType::Aes128Ctr(ref iv) => { + crypto::aes::decrypt(&derived_left_bits, &iv.as_slice(), &key_file.crypto.cipher_text, &mut val); + } + }; + + match Value::from_bytes(&val) { Ok(value) => Ok(value), Err(bytes_error) => Err(EncryptedHashMapError::InvalidValueFormat(bytes_error)) } @@ -304,6 +314,8 @@ mod tests { 262144, 32)); key_file.account = Some(x!(i as u64)); + result.push(key_file.id.clone()); + write_sstore.import_key(key_file).unwrap(); } result } @@ -359,9 +371,18 @@ mod tests { let mut sstore = SecretStore::new_test(&temp); - sstore.import_key(key_file); + sstore.import_key(key_file).unwrap(); assert_eq!(1, sstore.accounts().unwrap().len()); assert!(sstore.account(&Address::from_str("3f49624084b67849c7b4e805c5988c21a430f9d9").unwrap()).is_some()); } + + #[test] + fn can_list_accounts() { + let temp = RandomTempPath::create_dir(); + pregenerate_accounts(&temp, 30); + let sstore = SecretStore::new_test(&temp); + let accounts = sstore.accounts().unwrap(); + assert_eq!(30, accounts.len()); + } }