diff --git a/util/src/keys/directory.rs b/util/src/keys/directory.rs index 641a5cae4..7656a938f 100644 --- a/util/src/keys/directory.rs +++ b/util/src/keys/directory.rs @@ -273,7 +273,7 @@ impl KeyFileCrypto { /// `c` - number of iterations for derived key. /// `salt` - cryptographic site, random 256-bit hash (ensure it's crypto-random). /// `iv` - initialisation vector. - pub fn new_pbkdf2(cipher_text: Bytes, iv: H128, salt: H256, c: u32, dk_len: u32) -> KeyFileCrypto { + pub fn new_pbkdf2(cipher_text: Bytes, iv: H128, salt: H256, mac: H256, c: u32, dk_len: u32) -> KeyFileCrypto { KeyFileCrypto { cipher_type: CryptoCipherType::Aes128Ctr(iv), cipher_text: cipher_text, @@ -283,7 +283,7 @@ impl KeyFileCrypto { c: c, prf: Pbkdf2CryptoFunction::HMacSha256 }), - mac: H256::random(), + mac: mac, } } } @@ -530,6 +530,22 @@ impl KeyDirectory { self.cache.borrow().len() } + /// Removes key file from key directory + pub fn delete(&mut self, id: &Uuid) -> Result<(), ::std::io::Error> { + let path = self.key_path(id); + + if !self.cache.borrow().contains_key(id) { + return match fs::remove_file(&path) { + Ok(_) => { + self.cache.borrow_mut().remove(&id); + Ok(()) + }, + Err(e) => Err(e) + }; + } + Ok(()) + } + fn key_path(&self, id: &Uuid) -> PathBuf { let mut path = PathBuf::new(); path.push(self.path.clone()); @@ -849,14 +865,14 @@ mod file_tests { #[test] fn can_create_key_with_new_id() { let cipher_text: Bytes = FromHex::from_hex("a0f05555").unwrap(); - let key = KeyFileContent::new(KeyFileCrypto::new_pbkdf2(cipher_text, H128::zero(), H256::random(), 32, 32)); + let key = KeyFileContent::new(KeyFileCrypto::new_pbkdf2(cipher_text, H128::zero(), H256::random(), H256::random(), 32, 32)); assert!(!uuid_to_string(&key.id).is_empty()); } #[test] fn can_load_json_from_itself() { let cipher_text: Bytes = FromHex::from_hex("aaaaaaaaaaaaaaaaaaaaaaaaaaa22222222222222222222222").unwrap(); - let key = KeyFileContent::new(KeyFileCrypto::new_pbkdf2(cipher_text, H128::zero(), H256::random(), 32, 32)); + let key = KeyFileContent::new(KeyFileCrypto::new_pbkdf2(cipher_text, H128::zero(), H256::random(), H256::random(), 32, 32)); let json = key.to_json(); let loaded_key = KeyFileContent::from_json(&json).unwrap(); @@ -1014,7 +1030,7 @@ mod directory_tests { let cipher_text: Bytes = FromHex::from_hex("a0f05555").unwrap(); let temp_path = RandomTempPath::create_dir(); let mut directory = KeyDirectory::new(&temp_path.as_path()); - let uuid = directory.save(KeyFileContent::new(KeyFileCrypto::new_pbkdf2(cipher_text, H128::zero(), H256::random(), 32, 32))).unwrap(); + let uuid = directory.save(KeyFileContent::new(KeyFileCrypto::new_pbkdf2(cipher_text, H128::zero(), H256::random(), H256::random(), 32, 32))).unwrap(); let path = directory.key_path(&uuid); let key = KeyDirectory::load_key(&path).unwrap(); @@ -1030,7 +1046,7 @@ mod directory_tests { let cipher_text: Bytes = FromHex::from_hex("a0f05555").unwrap(); let mut keys = Vec::new(); for _ in 0..1000 { - let key = KeyFileContent::new(KeyFileCrypto::new_pbkdf2(cipher_text.clone(), H128::zero(), H256::random(), 32, 32)); + let key = KeyFileContent::new(KeyFileCrypto::new_pbkdf2(cipher_text.clone(), H128::zero(), H256::random(), H256::random(), 32, 32)); keys.push(directory.save(key).unwrap()); } @@ -1050,7 +1066,7 @@ mod directory_tests { let cipher_text: Bytes = FromHex::from_hex("a0f05555").unwrap(); let mut keys = Vec::new(); for _ in 0..1000 { - let key = KeyFileContent::new(KeyFileCrypto::new_pbkdf2(cipher_text.clone(), H128::zero(), H256::random(), 32, 32)); + let key = KeyFileContent::new(KeyFileCrypto::new_pbkdf2(cipher_text.clone(), H128::zero(), H256::random(), H256::random(), 32, 32)); keys.push(directory.save(key).unwrap()); } @@ -1083,7 +1099,7 @@ mod specs { let temp_path = RandomTempPath::create_dir(); let mut directory = KeyDirectory::new(&temp_path.as_path()); - let uuid = directory.save(KeyFileContent::new(KeyFileCrypto::new_pbkdf2(cipher_text, H128::zero(), H256::random(), 32, 32))); + let uuid = directory.save(KeyFileContent::new(KeyFileCrypto::new_pbkdf2(cipher_text, H128::zero(), H256::random(), H256::random(), 32, 32))); assert!(uuid.is_ok()); } @@ -1093,7 +1109,7 @@ mod specs { let cipher_text: Bytes = FromHex::from_hex("a0f05555").unwrap(); let temp_path = RandomTempPath::create_dir(); let mut directory = KeyDirectory::new(&temp_path.as_path()); - let uuid = directory.save(KeyFileContent::new(KeyFileCrypto::new_pbkdf2(cipher_text.clone(), H128::zero(), H256::random(), 32, 32))).unwrap(); + let uuid = directory.save(KeyFileContent::new(KeyFileCrypto::new_pbkdf2(cipher_text.clone(), H128::zero(), H256::random(), H256::random(), 32, 32))).unwrap(); let key = directory.get(&uuid).unwrap(); @@ -1108,7 +1124,7 @@ mod specs { let cipher_text: Bytes = FromHex::from_hex("a0f05555").unwrap(); let mut keys = Vec::new(); for _ in 0..10 { - let key = KeyFileContent::new(KeyFileCrypto::new_pbkdf2(cipher_text.clone(), H128::zero(), H256::random(), 32, 32)); + let key = KeyFileContent::new(KeyFileCrypto::new_pbkdf2(cipher_text.clone(), H128::zero(), H256::random(), H256::random(), 32, 32)); keys.push(directory.save(key).unwrap()); } diff --git a/util/src/keys/mod.rs b/util/src/keys/mod.rs index abd029444..fd52136d7 100644 --- a/util/src/keys/mod.rs +++ b/util/src/keys/mod.rs @@ -17,5 +17,4 @@ //! Key management module pub mod directory; - -mod store; +pub mod store; diff --git a/util/src/keys/store.rs b/util/src/keys/store.rs index 9972b6e57..f473d0ea2 100644 --- a/util/src/keys/store.rs +++ b/util/src/keys/store.rs @@ -14,12 +14,11 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -//! Generic Encryptor +//! Secret Store use keys::directory::*; use common::*; use rcrypto::pbkdf2::*; -use rcrypto::aes; use rcrypto::hmac::*; use crypto; @@ -30,25 +29,37 @@ const KEY_LENGTH_AES: u32 = KEY_LENGTH/2; const KEY_LENGTH_USIZE: usize = KEY_LENGTH as usize; const KEY_LENGTH_AES_USIZE: usize = KEY_LENGTH_AES as usize; +/// Encrypted hash-map, each request should contain password pub trait EncryptedHashMap { - // Returns existing value for the key, if any - fn get(&self, key: &Key, password: &str) -> Option; - // Insert new encrypted key-value and returns previous if there was any + /// Returns existing value for the key, if any + fn get(&self, key: &Key, password: &str) -> Result; + /// Insert new encrypted key-value and returns previous if there was any fn insert(&mut self, key: Key, value: Value, password: &str) -> Option; - // Removes key-value by key and returns the removed one, if any exists and password was provided + /// Removes key-value by key and returns the removed one, if any exists and password was provided fn remove (&mut self, key: &Key, password: Option<&str>) -> Option; - // Deletes key-value by key and returns if the key-value existed + /// Deletes key-value by key and returns if the key-value existed fn delete(&mut self, key: &Key) -> bool { self.remove::<&[u8]>(key, None).is_some() } } +/// Error retrieving value from encrypted hashmap +#[derive(Debug)] +pub enum EncryptedHashMapError { + /// Encryption failed + InvalidPassword, + /// No key in the hashmap + UnknownIdentifier +} + +/// Represent service for storing encrypted arbitrary data pub struct SecretStore { directory: KeyDirectory } impl SecretStore { - fn new() -> SecretStore { + /// new instance of Secret Store + pub fn new() -> SecretStore { let mut path = ::std::env::home_dir().expect("Failed to get home dir"); path.push(".keys"); SecretStore { @@ -85,41 +96,75 @@ fn derive_mac(derived_left_bits: &[u8], cipher_text: &[u8]) -> Bytes { } impl EncryptedHashMap for SecretStore { - fn get(&self, key: &H128, password: &str) -> Option { + fn get(&self, key: &H128, password: &str) -> Result { match self.directory.get(key) { Some(key_file) => { - let mut instance = Value::default(); - instance.populate_raw(&key_file.crypto.cipher_text); - Some(instance) - }, - None => None - } + let decrypted_bytes = match key_file.crypto.kdf { + KeyFileKdf::Pbkdf2(ref params) => { + let (derived_left_bits, derived_right_bits) = derive_key(password, ¶ms.salt); + let expected_mac = derive_mac(&derived_right_bits, &key_file.crypto.cipher_text).sha3(); + if expected_mac != 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 mut instance = Value::default(); + instance.populate_raw(&decrypted_bytes); + Ok(instance) + }, + None => Err(EncryptedHashMapError::UnknownIdentifier) + } } fn insert(&mut self, key: H128, value: Value, password: &str) -> Option { - let previous = if let Some(_) = self.directory.get(&key) { self.get(&key, password) } else { None }; + let previous = if let Ok(previous_value) = self.get(&key, password) { Some(previous_value) } else { None }; + // crypto random initiators let salt = H256::random(); let iv = H128::random(); - let mut key_file = KeyFileContent::new(KeyFileCrypto::new_pbkdf2(vec![], iv.clone(), salt.clone(), KEY_ITERATIONS, KEY_LENGTH)); + // 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) = derive_key(password, &salt); let mut cipher_text = vec![0u8; value.as_slice().len()]; - crypto::aes::encrypt(&derived_left_bits, &iv.as_slice(), &value.as_slice(), &mut cipher_text); - key_file.crypto.cipher_text = cipher_text.clone(); + // aes-128-ctr with initial vector of iv + crypto::aes::encrypt(&derived_left_bits, &iv.clone(), &value.as_slice(), &mut cipher_text); - key_file.crypto.mac = derive_mac(&derived_right_bits, &cipher_text).sha3(); + // KECCAK(DK[16..31] ++ ), where DK[16..31] - derived_right_bits + let mac = derive_mac(&derived_right_bits, &cipher_text.clone()).sha3(); + let key_file = KeyFileContent::new( + KeyFileCrypto::new_pbkdf2( + cipher_text, + iv, + salt, + mac, + KEY_ITERATIONS, + KEY_LENGTH)); + if let Err(io_error) = self.directory.save(key_file) { + warn!("Error saving key file: {:?}", io_error); + } previous } fn remove(&mut self, key: &H128, password: Option<&str>) -> Option { - let previous = match (self.directory.get(&key), password) { - (Some(_), Some(pass)) => self.get(&key, pass), - (_, _) => None - }; + let previous = if let Some(pass) = password { + if let Ok(previous_value) = self.get(&key, pass) { Some(previous_value) } else { None } + } + else { None }; + + if let Err(io_error) = self.directory.delete(key) { + warn!("Error saving key file: {:?}", io_error); + } previous } @@ -127,7 +172,7 @@ impl EncryptedHashMap for SecretStore { #[cfg(test)] mod vector_tests { - use super::{derive_key,derive_mac,derive_key_iterations}; + use super::{derive_mac,derive_key_iterations}; use common::*; @@ -142,7 +187,7 @@ mod vector_tests { assert_eq!("f06d69cdc7da0faffb1008270bca38f5", derived_left_bits.to_hex()); assert_eq!("e31891a3a773950e6d0fea48a7188551", derived_right_bits.to_hex()); - let mut mac_body = derive_mac(&derived_right_bits, &cipher_text); + let mac_body = derive_mac(&derived_right_bits, &cipher_text); assert_eq!("e31891a3a773950e6d0fea48a71885515318b4d5bcd28de64ee5559e671353e16f075ecae9f99c7a79a38af5f869aa46", mac_body.to_hex()); let mac = mac_body.sha3(); @@ -165,6 +210,51 @@ mod tests { sstore.insert(H128::random(), "Cat".to_owned(), "pass"); } + #[test] + fn secret_store_get_fail() { + let temp = RandomTempPath::create_dir(); + { + use keys::directory::{KeyFileContent, KeyFileCrypto}; + let mut write_sstore = SecretStore::new_test(&temp); + write_sstore.directory.save( + KeyFileContent::new( + KeyFileCrypto::new_pbkdf2( + FromHex::from_hex("5318b4d5bcd28de64ee5559e671353e16f075ecae9f99c7a79a38af5f869aa46").unwrap(), + H128::from_str("6087dab2f9fdbbfaddc31a909735c1e6").unwrap(), + H256::from_str("ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd").unwrap(), + H256::from_str("517ead924a9d0dc3124507e3393d175ce3ff7c1e96529c6c555ce9e51205e9b2").unwrap(), + 262144, + 32))) + .unwrap(); + } + let sstore = SecretStore::new_test(&temp); + if let Ok(_) = sstore.get::(&H128::from_str("3198bc9c66725ab3d9954942343ae5b6").unwrap(), "testpassword") { + panic!("shoud be error loading key, we requested the wrong key"); + } + } + + #[test] + fn secret_store_get() { + let temp = RandomTempPath::create_dir(); + let key_id = { + use keys::directory::{KeyFileContent, KeyFileCrypto}; + let mut write_sstore = SecretStore::new_test(&temp); + write_sstore.directory.save( + KeyFileContent::new( + KeyFileCrypto::new_pbkdf2( + FromHex::from_hex("5318b4d5bcd28de64ee5559e671353e16f075ecae9f99c7a79a38af5f869aa46").unwrap(), + H128::from_str("6087dab2f9fdbbfaddc31a909735c1e6").unwrap(), + H256::from_str("ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd").unwrap(), + H256::from_str("517ead924a9d0dc3124507e3393d175ce3ff7c1e96529c6c555ce9e51205e9b2").unwrap(), + 262144, + 32))) + .unwrap() + }; + let sstore = SecretStore::new_test(&temp); + if let Err(e) = sstore.get::(&key_id, "testpassword") { + panic!("got no key: {:?}", e); + } + } }