warnings, docs, and finding bugs
This commit is contained in:
parent
a649d6f131
commit
9895f00e5e
@ -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());
|
||||
}
|
||||
|
||||
|
@ -17,5 +17,4 @@
|
||||
//! Key management module
|
||||
|
||||
pub mod directory;
|
||||
|
||||
mod store;
|
||||
pub mod store;
|
||||
|
@ -14,12 +14,11 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! 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<Key: Hash + Eq> {
|
||||
// Returns existing value for the key, if any
|
||||
fn get<Value: Populatable + Default + BytesConvertable>(&self, key: &Key, password: &str) -> Option<Value>;
|
||||
// Insert new encrypted key-value and returns previous if there was any
|
||||
/// Returns existing value for the key, if any
|
||||
fn get<Value: Populatable + Default + BytesConvertable>(&self, key: &Key, password: &str) -> Result<Value, EncryptedHashMapError>;
|
||||
/// Insert new encrypted key-value and returns previous if there was any
|
||||
fn insert<Value: Populatable + Default + BytesConvertable>(&mut self, key: Key, value: Value, password: &str) -> Option<Value>;
|
||||
// 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<Value: Populatable + Default + BytesConvertable> (&mut self, key: &Key, password: Option<&str>) -> Option<Value>;
|
||||
// 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<H128> for SecretStore {
|
||||
fn get<Value: Populatable + Default + BytesConvertable>(&self, key: &H128, password: &str) -> Option<Value> {
|
||||
fn get<Value: Populatable + Default + BytesConvertable>(&self, key: &H128, password: &str) -> Result<Value, EncryptedHashMapError> {
|
||||
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<Value: Populatable + Default + BytesConvertable>(&mut self, key: H128, value: Value, password: &str) -> Option<Value> {
|
||||
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] ++ <ciphertext>), 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<Value: Populatable + Default + BytesConvertable>(&mut self, key: &H128, password: Option<&str>) -> Option<Value> {
|
||||
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<H128> 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::<Bytes>(&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::<Bytes>(&key_id, "testpassword") {
|
||||
panic!("got no key: {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user