Merge pull request #449 from ethcore/secret-store-enc
Secret store (part2 - encrypted key/value svc)
This commit is contained in:
		
						commit
						8747fe2dc9
					
				@ -141,7 +141,7 @@ impl<'a> Deref for BytesRef<'a> {
 | 
				
			|||||||
	fn deref(&self) -> &[u8] {
 | 
						fn deref(&self) -> &[u8] {
 | 
				
			||||||
		match *self {
 | 
							match *self {
 | 
				
			||||||
			BytesRef::Flexible(ref bytes) => bytes,
 | 
								BytesRef::Flexible(ref bytes) => bytes,
 | 
				
			||||||
			BytesRef::Fixed(ref bytes) => bytes
 | 
								BytesRef::Fixed(ref bytes) => bytes,
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -150,7 +150,7 @@ impl <'a> DerefMut for BytesRef<'a> {
 | 
				
			|||||||
	fn deref_mut(&mut self) -> &mut [u8] {
 | 
						fn deref_mut(&mut self) -> &mut [u8] {
 | 
				
			||||||
		match *self {
 | 
							match *self {
 | 
				
			||||||
			BytesRef::Flexible(ref mut bytes) => bytes,
 | 
								BytesRef::Flexible(ref mut bytes) => bytes,
 | 
				
			||||||
			BytesRef::Fixed(ref mut bytes) => bytes
 | 
								BytesRef::Fixed(ref mut bytes) => bytes,
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -178,6 +178,10 @@ impl BytesConvertable for Vec<u8> {
 | 
				
			|||||||
	fn bytes(&self) -> &[u8] { self }
 | 
						fn bytes(&self) -> &[u8] { self }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl BytesConvertable for String {
 | 
				
			||||||
 | 
						fn bytes(&self) -> &[u8] { &self.as_bytes() }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
macro_rules! impl_bytes_convertable_for_array {
 | 
					macro_rules! impl_bytes_convertable_for_array {
 | 
				
			||||||
	($zero: expr) => ();
 | 
						($zero: expr) => ();
 | 
				
			||||||
	($len: expr, $($idx: expr),*) => {
 | 
						($len: expr, $($idx: expr),*) => {
 | 
				
			||||||
@ -252,6 +256,49 @@ impl<T> Populatable for [T] where T: Sized {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug)]
 | 
				
			||||||
 | 
					/// Bytes array deserialization error
 | 
				
			||||||
 | 
					pub enum FromBytesError {
 | 
				
			||||||
 | 
						/// Not enough bytes for the requested type
 | 
				
			||||||
 | 
						NotLongEnough,
 | 
				
			||||||
 | 
						/// Too many bytes for the requested type
 | 
				
			||||||
 | 
						TooLong,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Value that can be serialized from bytes array
 | 
				
			||||||
 | 
					pub trait FromRawBytes : Sized {
 | 
				
			||||||
 | 
						/// function that will instantiate and initialize object from slice
 | 
				
			||||||
 | 
						fn from_bytes(d: &[u8]) -> Result<Self, FromBytesError>;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<T> FromRawBytes for T where T: Sized + FixedHash {
 | 
				
			||||||
 | 
						fn from_bytes(bytes: &[u8]) -> Result<Self, FromBytesError> {
 | 
				
			||||||
 | 
							use std::mem;
 | 
				
			||||||
 | 
							use std::cmp::Ordering;
 | 
				
			||||||
 | 
							match bytes.len().cmp(&mem::size_of::<T>()) {
 | 
				
			||||||
 | 
								Ordering::Less => return Err(FromBytesError::NotLongEnough),
 | 
				
			||||||
 | 
								Ordering::Greater => return Err(FromBytesError::TooLong),
 | 
				
			||||||
 | 
								Ordering::Equal => ()
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							let mut res: Self = unsafe { mem::uninitialized() };
 | 
				
			||||||
 | 
							res.copy_raw(bytes);
 | 
				
			||||||
 | 
							Ok(res)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl FromRawBytes for String {
 | 
				
			||||||
 | 
						fn from_bytes(bytes: &[u8]) -> Result<String, FromBytesError> {
 | 
				
			||||||
 | 
							Ok(::std::str::from_utf8(bytes).unwrap().to_owned())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl FromRawBytes for Vec<u8> {
 | 
				
			||||||
 | 
						fn from_bytes(bytes: &[u8]) -> Result<Vec<u8>, FromBytesError> {
 | 
				
			||||||
 | 
							Ok(bytes.clone().to_vec())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[test]
 | 
					#[test]
 | 
				
			||||||
fn fax_raw() {
 | 
					fn fax_raw() {
 | 
				
			||||||
	let mut x = [255u8; 4];
 | 
						let mut x = [255u8; 4];
 | 
				
			||||||
 | 
				
			|||||||
@ -27,7 +27,7 @@ const MAX_CACHE_USAGE_TRACK: usize = 128;
 | 
				
			|||||||
#[derive(PartialEq, Debug, Clone)]
 | 
					#[derive(PartialEq, Debug, Clone)]
 | 
				
			||||||
pub enum CryptoCipherType {
 | 
					pub enum CryptoCipherType {
 | 
				
			||||||
	/// aes-128-ctr with 128-bit initialisation vector(iv)
 | 
						/// aes-128-ctr with 128-bit initialisation vector(iv)
 | 
				
			||||||
	Aes128Ctr(U128)
 | 
						Aes128Ctr(H128)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(PartialEq, Debug, Clone)]
 | 
					#[derive(PartialEq, Debug, Clone)]
 | 
				
			||||||
@ -168,6 +168,8 @@ pub struct KeyFileCrypto {
 | 
				
			|||||||
	pub cipher_text: Bytes,
 | 
						pub cipher_text: Bytes,
 | 
				
			||||||
	/// Password derived key generator function settings.
 | 
						/// Password derived key generator function settings.
 | 
				
			||||||
	pub kdf: KeyFileKdf,
 | 
						pub kdf: KeyFileKdf,
 | 
				
			||||||
 | 
						/// Mac
 | 
				
			||||||
 | 
						pub mac: H256
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl KeyFileCrypto {
 | 
					impl KeyFileCrypto {
 | 
				
			||||||
@ -182,7 +184,7 @@ impl KeyFileCrypto {
 | 
				
			|||||||
			Some("aes-128-ctr") => CryptoCipherType::Aes128Ctr(
 | 
								Some("aes-128-ctr") => CryptoCipherType::Aes128Ctr(
 | 
				
			||||||
				match try!(as_object.get("cipherparams").ok_or(CryptoParseError::NoCipherParameters)).as_object() {
 | 
									match try!(as_object.get("cipherparams").ok_or(CryptoParseError::NoCipherParameters)).as_object() {
 | 
				
			||||||
					None => { return Err(CryptoParseError::NoCipherParameters); },
 | 
										None => { return Err(CryptoParseError::NoCipherParameters); },
 | 
				
			||||||
					Some(cipher_param) => match U128::from_str(match cipher_param["iv"].as_string() {
 | 
										Some(cipher_param) => match H128::from_str(match cipher_param["iv"].as_string() {
 | 
				
			||||||
							None => { return Err(CryptoParseError::NoInitialVector); },
 | 
												None => { return Err(CryptoParseError::NoInitialVector); },
 | 
				
			||||||
							Some(iv_hex_string) => iv_hex_string
 | 
												Some(iv_hex_string) => iv_hex_string
 | 
				
			||||||
						})
 | 
											})
 | 
				
			||||||
@ -216,22 +218,31 @@ impl KeyFileCrypto {
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		let cipher_text = match as_object["ciphertext"].as_string() {
 | 
							let cipher_text = match try!(as_object.get("ciphertext").ok_or(CryptoParseError::NoCipherText)).as_string() {
 | 
				
			||||||
			None => { return Err(CryptoParseError::NoCipherText); }
 | 
								None => { return Err(CryptoParseError::InvalidCipherText); }
 | 
				
			||||||
			Some(text) => text
 | 
								Some(text) => text
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							let mac: H256 = match try!(as_object.get("mac").ok_or(CryptoParseError::NoMac)).as_string() {
 | 
				
			||||||
 | 
								None => { return Err(CryptoParseError::InvalidMacFormat(None)) },
 | 
				
			||||||
 | 
								Some(salt_value) => match H256::from_str(salt_value) {
 | 
				
			||||||
 | 
									Ok(salt_hex_value) => salt_hex_value,
 | 
				
			||||||
 | 
									Err(from_hex_error) => { return Err(CryptoParseError::InvalidMacFormat(Some(from_hex_error))); },
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		Ok(KeyFileCrypto {
 | 
							Ok(KeyFileCrypto {
 | 
				
			||||||
			cipher_text: Bytes::from(cipher_text),
 | 
								cipher_text: match FromHex::from_hex(cipher_text) { Ok(bytes) => bytes, Err(_) => { return Err(CryptoParseError::InvalidCipherText); } },
 | 
				
			||||||
			cipher_type: cipher_type,
 | 
								cipher_type: cipher_type,
 | 
				
			||||||
			kdf: kdf,
 | 
								kdf: kdf,
 | 
				
			||||||
 | 
								mac: mac,
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	fn to_json(&self) -> Json {
 | 
						fn to_json(&self) -> Json {
 | 
				
			||||||
		let mut map = BTreeMap::new();
 | 
							let mut map = BTreeMap::new();
 | 
				
			||||||
		match self.cipher_type {
 | 
							match self.cipher_type {
 | 
				
			||||||
			CryptoCipherType::Aes128Ctr(iv) => {
 | 
								CryptoCipherType::Aes128Ctr(ref iv) => {
 | 
				
			||||||
				map.insert("cipher".to_owned(), Json::String("aes-128-ctr".to_owned()));
 | 
									map.insert("cipher".to_owned(), Json::String("aes-128-ctr".to_owned()));
 | 
				
			||||||
				let mut cipher_params = BTreeMap::new();
 | 
									let mut cipher_params = BTreeMap::new();
 | 
				
			||||||
				cipher_params.insert("iv".to_owned(), Json::String(format!("{:?}", iv)));
 | 
									cipher_params.insert("iv".to_owned(), Json::String(format!("{:?}", iv)));
 | 
				
			||||||
@ -251,6 +262,8 @@ impl KeyFileCrypto {
 | 
				
			|||||||
			KeyFileKdf::Scrypt(ref scrypt_params) => scrypt_params.to_json()
 | 
								KeyFileKdf::Scrypt(ref scrypt_params) => scrypt_params.to_json()
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							map.insert("mac".to_owned(), Json::String(format!("{:?}", self.mac)));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		Json::Object(map)
 | 
							Json::Object(map)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -260,7 +273,7 @@ impl KeyFileCrypto {
 | 
				
			|||||||
	/// `c` - number of iterations for derived key.
 | 
						/// `c` - number of iterations for derived key.
 | 
				
			||||||
	/// `salt` - cryptographic site, random 256-bit hash (ensure it's crypto-random).
 | 
						/// `salt` - cryptographic site, random 256-bit hash (ensure it's crypto-random).
 | 
				
			||||||
	/// `iv` - initialisation vector.
 | 
						/// `iv` - initialisation vector.
 | 
				
			||||||
	pub fn new_pbkdf2(cipher_text: Bytes, iv: U128, 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 {
 | 
							KeyFileCrypto {
 | 
				
			||||||
			cipher_type: CryptoCipherType::Aes128Ctr(iv),
 | 
								cipher_type: CryptoCipherType::Aes128Ctr(iv),
 | 
				
			||||||
			cipher_text: cipher_text,
 | 
								cipher_text: cipher_text,
 | 
				
			||||||
@ -270,6 +283,7 @@ impl KeyFileCrypto {
 | 
				
			|||||||
				c: c,
 | 
									c: c,
 | 
				
			||||||
				prf: Pbkdf2CryptoFunction::HMacSha256
 | 
									prf: Pbkdf2CryptoFunction::HMacSha256
 | 
				
			||||||
			}),
 | 
								}),
 | 
				
			||||||
 | 
								mac: mac,
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -324,14 +338,17 @@ pub struct KeyFileContent {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#[derive(Debug)]
 | 
					#[derive(Debug)]
 | 
				
			||||||
enum CryptoParseError {
 | 
					enum CryptoParseError {
 | 
				
			||||||
 | 
						InvalidMacFormat(Option<UtilError>),
 | 
				
			||||||
 | 
						NoMac,
 | 
				
			||||||
	NoCipherText,
 | 
						NoCipherText,
 | 
				
			||||||
 | 
						InvalidCipherText,
 | 
				
			||||||
	NoCipherType,
 | 
						NoCipherType,
 | 
				
			||||||
	InvalidJsonFormat,
 | 
						InvalidJsonFormat,
 | 
				
			||||||
	InvalidKdfType(Mismatch<String>),
 | 
						InvalidKdfType(Mismatch<String>),
 | 
				
			||||||
	InvalidCipherType(Mismatch<String>),
 | 
						InvalidCipherType(Mismatch<String>),
 | 
				
			||||||
	NoInitialVector,
 | 
						NoInitialVector,
 | 
				
			||||||
	NoCipherParameters,
 | 
						NoCipherParameters,
 | 
				
			||||||
	InvalidInitialVector(FromHexError),
 | 
						InvalidInitialVector(UtilError),
 | 
				
			||||||
	NoKdf,
 | 
						NoKdf,
 | 
				
			||||||
	NoKdfType,
 | 
						NoKdfType,
 | 
				
			||||||
	Scrypt(ScryptParseError),
 | 
						Scrypt(ScryptParseError),
 | 
				
			||||||
@ -425,17 +442,17 @@ enum KeyFileLoadError {
 | 
				
			|||||||
pub struct KeyDirectory {
 | 
					pub struct KeyDirectory {
 | 
				
			||||||
	/// Directory path for key management.
 | 
						/// Directory path for key management.
 | 
				
			||||||
	path: String,
 | 
						path: String,
 | 
				
			||||||
	cache: HashMap<Uuid, KeyFileContent>,
 | 
						cache: RefCell<HashMap<Uuid, KeyFileContent>>,
 | 
				
			||||||
	cache_usage: VecDeque<Uuid>,
 | 
						cache_usage: RefCell<VecDeque<Uuid>>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl KeyDirectory {
 | 
					impl KeyDirectory {
 | 
				
			||||||
	/// Initializes new cache directory context with a given `path`
 | 
						/// Initializes new cache directory context with a given `path`
 | 
				
			||||||
	pub fn new(path: &Path) -> KeyDirectory {
 | 
						pub fn new(path: &Path) -> KeyDirectory {
 | 
				
			||||||
		KeyDirectory {
 | 
							KeyDirectory {
 | 
				
			||||||
			cache: HashMap::new(),
 | 
								cache: RefCell::new(HashMap::new()),
 | 
				
			||||||
			path: path.to_str().expect("Initialized key directory with empty path").to_owned(),
 | 
								path: path.to_str().expect("Initialized key directory with empty path").to_owned(),
 | 
				
			||||||
			cache_usage: VecDeque::new(),
 | 
								cache_usage: RefCell::new(VecDeque::new()),
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -448,25 +465,37 @@ impl KeyDirectory {
 | 
				
			|||||||
			let json_bytes = json_text.into_bytes();
 | 
								let json_bytes = json_text.into_bytes();
 | 
				
			||||||
			try!(file.write(&json_bytes));
 | 
								try!(file.write(&json_bytes));
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							let mut cache = self.cache.borrow_mut();
 | 
				
			||||||
		let id = key_file.id.clone();
 | 
							let id = key_file.id.clone();
 | 
				
			||||||
		self.cache.insert(id.clone(), key_file);
 | 
							cache.insert(id.clone(), key_file);
 | 
				
			||||||
		Ok(id.clone())
 | 
							Ok(id.clone())
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/// Returns key given by id if corresponding file exists and no load error occured.
 | 
						/// Returns key given by id if corresponding file exists and no load error occured.
 | 
				
			||||||
	/// Warns if any error occured during the key loading
 | 
						/// Warns if any error occured during the key loading
 | 
				
			||||||
	pub fn get(&mut self, id: &Uuid) -> Option<&KeyFileContent> {
 | 
						pub fn get(&self, id: &Uuid) -> Option<KeyFileContent> {
 | 
				
			||||||
		let path = self.key_path(id);
 | 
							let path = self.key_path(id);
 | 
				
			||||||
		self.cache_usage.push_back(id.clone());
 | 
							{
 | 
				
			||||||
		Some(self.cache.entry(id.to_owned()).or_insert(
 | 
								let mut usage = self.cache_usage.borrow_mut();
 | 
				
			||||||
 | 
								usage.push_back(id.clone());
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if !self.cache.borrow().contains_key(id) {
 | 
				
			||||||
			match KeyDirectory::load_key(&path) {
 | 
								match KeyDirectory::load_key(&path) {
 | 
				
			||||||
				Ok(loaded_key) => loaded_key,
 | 
									Ok(loaded_key) => {
 | 
				
			||||||
 | 
										self.cache.borrow_mut().insert(id.to_owned(), loaded_key);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
				Err(error) => {
 | 
									Err(error) => {
 | 
				
			||||||
					warn!(target: "sstore", "error loading key {:?}: {:?}", id, error);
 | 
										warn!(target: "sstore", "error loading key {:?}: {:?}", id, error);
 | 
				
			||||||
					return None;
 | 
										return None;
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		))
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// todo: replace with Ref::map when it stabilized to avoid copies
 | 
				
			||||||
 | 
							Some(self.cache.borrow().get(id)
 | 
				
			||||||
 | 
								.expect("Key should be there, we have just inserted or checked it.")
 | 
				
			||||||
 | 
								.clone())
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/// Returns current path to the directory with keys
 | 
						/// Returns current path to the directory with keys
 | 
				
			||||||
@ -476,29 +505,63 @@ impl KeyDirectory {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	/// Removes keys that never been requested during last `MAX_USAGE_TRACK` times
 | 
						/// Removes keys that never been requested during last `MAX_USAGE_TRACK` times
 | 
				
			||||||
	pub fn collect_garbage(&mut self) {
 | 
						pub fn collect_garbage(&mut self) {
 | 
				
			||||||
		let total_usages = self.cache_usage.len();
 | 
							let mut cache_usage = self.cache_usage.borrow_mut();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							let total_usages = cache_usage.len();
 | 
				
			||||||
		let untracked_usages = max(total_usages as i64 - MAX_CACHE_USAGE_TRACK as i64, 0) as usize;
 | 
							let untracked_usages = max(total_usages as i64 - MAX_CACHE_USAGE_TRACK as i64, 0) as usize;
 | 
				
			||||||
		if untracked_usages > 0 {
 | 
							if untracked_usages > 0 {
 | 
				
			||||||
			self.cache_usage.drain(..untracked_usages);
 | 
								cache_usage.drain(..untracked_usages);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if self.cache.len() <= MAX_CACHE_USAGE_TRACK { return; }
 | 
							if self.cache.borrow().len() <= MAX_CACHE_USAGE_TRACK { return; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		let uniqs: HashSet<&Uuid> = self.cache_usage.iter().collect();
 | 
							let uniqs: HashSet<&Uuid> = cache_usage.iter().collect();
 | 
				
			||||||
		let mut removes = HashSet::new();
 | 
							let removes:Vec<Uuid> = {
 | 
				
			||||||
 | 
								let cache = self.cache.borrow();
 | 
				
			||||||
		for key in self.cache.keys() {
 | 
								cache.keys().cloned().filter(|key| !uniqs.contains(key)).collect()
 | 
				
			||||||
			if !uniqs.contains(key) {
 | 
							};
 | 
				
			||||||
				removes.insert(key.clone());
 | 
							if removes.is_empty() { return; }
 | 
				
			||||||
			}
 | 
							let mut cache = self.cache.borrow_mut();
 | 
				
			||||||
		}
 | 
							for key in removes { cache.remove(&key); }
 | 
				
			||||||
 | 
					 | 
				
			||||||
		for removed_key in removes { self.cache.remove(&removed_key); }
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/// Reports how many keys are currently cached.
 | 
						/// Reports how many keys are currently cached.
 | 
				
			||||||
	pub fn cache_size(&self) -> usize {
 | 
						pub fn cache_size(&self) -> usize {
 | 
				
			||||||
		self.cache.len()
 | 
							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(())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/// Enumerates all keys in the directory
 | 
				
			||||||
 | 
						pub fn list(&self) -> Result<Vec<Uuid>, ::std::io::Error> {
 | 
				
			||||||
 | 
							let mut result = Vec::new();
 | 
				
			||||||
 | 
							for entry in try!(fs::read_dir(&self.path)) {
 | 
				
			||||||
 | 
								let entry = try!(entry);
 | 
				
			||||||
 | 
								if !try!(fs::metadata(entry.path())).is_dir() {
 | 
				
			||||||
 | 
									match entry.file_name().to_str() {
 | 
				
			||||||
 | 
										Some(ref name) => {
 | 
				
			||||||
 | 
											if let Ok(uuid) = uuid_from_string(name) { result.push(uuid); }
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										None => { continue; }
 | 
				
			||||||
 | 
									};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							Ok(result)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	fn key_path(&self, id: &Uuid) -> PathBuf {
 | 
						fn key_path(&self, id: &Uuid) -> PathBuf {
 | 
				
			||||||
@ -820,14 +883,14 @@ mod file_tests {
 | 
				
			|||||||
	#[test]
 | 
						#[test]
 | 
				
			||||||
	fn can_create_key_with_new_id() {
 | 
						fn can_create_key_with_new_id() {
 | 
				
			||||||
		let cipher_text: Bytes = FromHex::from_hex("a0f05555").unwrap();
 | 
							let cipher_text: Bytes = FromHex::from_hex("a0f05555").unwrap();
 | 
				
			||||||
		let key = KeyFileContent::new(KeyFileCrypto::new_pbkdf2(cipher_text, U128::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());
 | 
							assert!(!uuid_to_string(&key.id).is_empty());
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	#[test]
 | 
						#[test]
 | 
				
			||||||
	fn can_load_json_from_itself() {
 | 
						fn can_load_json_from_itself() {
 | 
				
			||||||
		let cipher_text: Bytes = FromHex::from_hex("aaaaaaaaaaaaaaaaaaaaaaaaaaa22222222222222222222222").unwrap();
 | 
							let cipher_text: Bytes = FromHex::from_hex("aaaaaaaaaaaaaaaaaaaaaaaaaaa22222222222222222222222").unwrap();
 | 
				
			||||||
		let key = KeyFileContent::new(KeyFileCrypto::new_pbkdf2(cipher_text, U128::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 json = key.to_json();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		let loaded_key = KeyFileContent::from_json(&json).unwrap();
 | 
							let loaded_key = KeyFileContent::from_json(&json).unwrap();
 | 
				
			||||||
@ -985,7 +1048,7 @@ mod directory_tests {
 | 
				
			|||||||
		let cipher_text: Bytes = FromHex::from_hex("a0f05555").unwrap();
 | 
							let cipher_text: Bytes = FromHex::from_hex("a0f05555").unwrap();
 | 
				
			||||||
		let temp_path = RandomTempPath::create_dir();
 | 
							let temp_path = RandomTempPath::create_dir();
 | 
				
			||||||
		let mut directory = KeyDirectory::new(&temp_path.as_path());
 | 
							let mut directory = KeyDirectory::new(&temp_path.as_path());
 | 
				
			||||||
		let uuid = directory.save(KeyFileContent::new(KeyFileCrypto::new_pbkdf2(cipher_text, U128::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 path = directory.key_path(&uuid);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		let key = KeyDirectory::load_key(&path).unwrap();
 | 
							let key = KeyDirectory::load_key(&path).unwrap();
 | 
				
			||||||
@ -1001,7 +1064,7 @@ mod directory_tests {
 | 
				
			|||||||
		let cipher_text: Bytes = FromHex::from_hex("a0f05555").unwrap();
 | 
							let cipher_text: Bytes = FromHex::from_hex("a0f05555").unwrap();
 | 
				
			||||||
		let mut keys = Vec::new();
 | 
							let mut keys = Vec::new();
 | 
				
			||||||
		for _ in 0..1000 {
 | 
							for _ in 0..1000 {
 | 
				
			||||||
			let key = KeyFileContent::new(KeyFileCrypto::new_pbkdf2(cipher_text.clone(), U128::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());
 | 
								keys.push(directory.save(key).unwrap());
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -1021,7 +1084,7 @@ mod directory_tests {
 | 
				
			|||||||
		let cipher_text: Bytes = FromHex::from_hex("a0f05555").unwrap();
 | 
							let cipher_text: Bytes = FromHex::from_hex("a0f05555").unwrap();
 | 
				
			||||||
		let mut keys = Vec::new();
 | 
							let mut keys = Vec::new();
 | 
				
			||||||
		for _ in 0..1000 {
 | 
							for _ in 0..1000 {
 | 
				
			||||||
			let key = KeyFileContent::new(KeyFileCrypto::new_pbkdf2(cipher_text.clone(), U128::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());
 | 
								keys.push(directory.save(key).unwrap());
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -1033,6 +1096,14 @@ mod directory_tests {
 | 
				
			|||||||
		// since all keys are different, should be exactly MAX_CACHE_USAGE_TRACK
 | 
							// since all keys are different, should be exactly MAX_CACHE_USAGE_TRACK
 | 
				
			||||||
		assert_eq!(MAX_CACHE_USAGE_TRACK, directory.cache_size())
 | 
							assert_eq!(MAX_CACHE_USAGE_TRACK, directory.cache_size())
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						#[test]
 | 
				
			||||||
 | 
						fn collects_garbage_on_empty() {
 | 
				
			||||||
 | 
							let temp_path = RandomTempPath::create_dir();
 | 
				
			||||||
 | 
							let mut directory = KeyDirectory::new(&temp_path.as_path());
 | 
				
			||||||
 | 
							directory.collect_garbage();
 | 
				
			||||||
 | 
							assert_eq!(0, directory.cache_size())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[cfg(test)]
 | 
					#[cfg(test)]
 | 
				
			||||||
@ -1054,7 +1125,7 @@ mod specs {
 | 
				
			|||||||
		let temp_path = RandomTempPath::create_dir();
 | 
							let temp_path = RandomTempPath::create_dir();
 | 
				
			||||||
		let mut directory = KeyDirectory::new(&temp_path.as_path());
 | 
							let mut directory = KeyDirectory::new(&temp_path.as_path());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		let uuid = directory.save(KeyFileContent::new(KeyFileCrypto::new_pbkdf2(cipher_text, U128::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());
 | 
							assert!(uuid.is_ok());
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -1064,7 +1135,7 @@ mod specs {
 | 
				
			|||||||
		let cipher_text: Bytes = FromHex::from_hex("a0f05555").unwrap();
 | 
							let cipher_text: Bytes = FromHex::from_hex("a0f05555").unwrap();
 | 
				
			||||||
		let temp_path = RandomTempPath::create_dir();
 | 
							let temp_path = RandomTempPath::create_dir();
 | 
				
			||||||
		let mut directory = KeyDirectory::new(&temp_path.as_path());
 | 
							let mut directory = KeyDirectory::new(&temp_path.as_path());
 | 
				
			||||||
		let uuid = directory.save(KeyFileContent::new(KeyFileCrypto::new_pbkdf2(cipher_text.clone(), U128::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();
 | 
							let key = directory.get(&uuid).unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -1079,10 +1150,25 @@ mod specs {
 | 
				
			|||||||
		let cipher_text: Bytes = FromHex::from_hex("a0f05555").unwrap();
 | 
							let cipher_text: Bytes = FromHex::from_hex("a0f05555").unwrap();
 | 
				
			||||||
		let mut keys = Vec::new();
 | 
							let mut keys = Vec::new();
 | 
				
			||||||
		for _ in 0..10 {
 | 
							for _ in 0..10 {
 | 
				
			||||||
			let key = KeyFileContent::new(KeyFileCrypto::new_pbkdf2(cipher_text.clone(), U128::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());
 | 
								keys.push(directory.save(key).unwrap());
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		assert_eq!(10, keys.len())
 | 
							assert_eq!(10, keys.len())
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						#[test]
 | 
				
			||||||
 | 
						fn can_list_keys() {
 | 
				
			||||||
 | 
							let temp_path = RandomTempPath::create_dir();
 | 
				
			||||||
 | 
							let mut directory = KeyDirectory::new(&temp_path.as_path());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							let cipher_text: Bytes = FromHex::from_hex("a0f05555").unwrap();
 | 
				
			||||||
 | 
							let mut keys = Vec::new();
 | 
				
			||||||
 | 
							for _ in 0..33 {
 | 
				
			||||||
 | 
								let key = KeyFileContent::new(KeyFileCrypto::new_pbkdf2(cipher_text.clone(), H128::zero(), H256::random(), H256::random(), 32, 32));
 | 
				
			||||||
 | 
								keys.push(directory.save(key).unwrap());
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							assert_eq!(33, directory.list().unwrap().len());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -17,3 +17,4 @@
 | 
				
			|||||||
//! Key management module
 | 
					//! Key management module
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub mod directory;
 | 
					pub mod directory;
 | 
				
			||||||
 | 
					pub mod store;
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										297
									
								
								util/src/keys/store.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										297
									
								
								util/src/keys/store.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,297 @@
 | 
				
			|||||||
 | 
					// Copyright 2015, 2016 Ethcore (UK) Ltd.
 | 
				
			||||||
 | 
					// This file is part of Parity.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Parity is free software: you can redistribute it and/or modify
 | 
				
			||||||
 | 
					// it under the terms of the GNU General Public License as published by
 | 
				
			||||||
 | 
					// the Free Software Foundation, either version 3 of the License, or
 | 
				
			||||||
 | 
					// (at your option) any later version.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Parity is distributed in the hope that it will be useful,
 | 
				
			||||||
 | 
					// but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
				
			||||||
 | 
					// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
				
			||||||
 | 
					// GNU General Public License for more details.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// You should have received a copy of the GNU General Public License
 | 
				
			||||||
 | 
					// along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//! Secret Store
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use keys::directory::*;
 | 
				
			||||||
 | 
					use common::*;
 | 
				
			||||||
 | 
					use rcrypto::pbkdf2::*;
 | 
				
			||||||
 | 
					use rcrypto::hmac::*;
 | 
				
			||||||
 | 
					use crypto;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const KEY_LENGTH: u32 = 32;
 | 
				
			||||||
 | 
					const KEY_ITERATIONS: u32 = 4096;
 | 
				
			||||||
 | 
					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: FromRawBytes + BytesConvertable>(&self, key: &Key, password: &str) ->  Result<Value, EncryptedHashMapError>;
 | 
				
			||||||
 | 
						/// Insert new encrypted key-value and returns previous if there was any
 | 
				
			||||||
 | 
						fn insert<Value: FromRawBytes + 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
 | 
				
			||||||
 | 
						fn remove<Value: FromRawBytes + BytesConvertable> (&mut self, key: &Key, password: Option<&str>) -> Option<Value>;
 | 
				
			||||||
 | 
						/// Deletes key-value by key and returns if the key-value existed
 | 
				
			||||||
 | 
						fn delete(&mut self, key: &Key) -> bool {
 | 
				
			||||||
 | 
							self.remove::<Bytes>(key, None).is_some()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Error retrieving value from encrypted hashmap
 | 
				
			||||||
 | 
					#[derive(Debug)]
 | 
				
			||||||
 | 
					pub enum EncryptedHashMapError {
 | 
				
			||||||
 | 
						/// Encryption failed
 | 
				
			||||||
 | 
						InvalidPassword,
 | 
				
			||||||
 | 
						/// No key in the hashmap
 | 
				
			||||||
 | 
						UnknownIdentifier,
 | 
				
			||||||
 | 
						/// Stored value is not well formed for the requested type
 | 
				
			||||||
 | 
						InvalidValueFormat(FromBytesError),
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Represent service for storing encrypted arbitrary data
 | 
				
			||||||
 | 
					pub struct SecretStore {
 | 
				
			||||||
 | 
						directory: KeyDirectory
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl 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 {
 | 
				
			||||||
 | 
								directory: KeyDirectory::new(&path)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						#[cfg(test)]
 | 
				
			||||||
 | 
						fn new_test(path: &::tests::helpers::RandomTempPath) -> SecretStore {
 | 
				
			||||||
 | 
							SecretStore {
 | 
				
			||||||
 | 
								directory: KeyDirectory::new(path.as_path())
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn derive_key_iterations(password: &str, salt: &H256, c: u32) -> (Bytes, Bytes) {
 | 
				
			||||||
 | 
						let mut h_mac = Hmac::new(::rcrypto::sha2::Sha256::new(), password.as_bytes());
 | 
				
			||||||
 | 
						let mut derived_key = vec![0u8; KEY_LENGTH_USIZE];
 | 
				
			||||||
 | 
						pbkdf2(&mut h_mac, &salt.as_slice(), c, &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_key(password: &str, salt: &H256) -> (Bytes, Bytes) {
 | 
				
			||||||
 | 
						derive_key_iterations(password, salt, KEY_ITERATIONS)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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);
 | 
				
			||||||
 | 
						mac[KEY_LENGTH_AES_USIZE..cipher_text.len()+KEY_LENGTH_AES_USIZE].clone_from_slice(cipher_text);
 | 
				
			||||||
 | 
						mac
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl EncryptedHashMap<H128> for SecretStore {
 | 
				
			||||||
 | 
						fn get<Value: FromRawBytes + BytesConvertable>(&self, key: &H128, password: &str) -> Result<Value, EncryptedHashMapError> {
 | 
				
			||||||
 | 
							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!(); }
 | 
				
			||||||
 | 
									};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									match Value::from_bytes(&decrypted_bytes) {
 | 
				
			||||||
 | 
										Ok(value) => Ok(value),
 | 
				
			||||||
 | 
										Err(bytes_error) => Err(EncryptedHashMapError::InvalidValueFormat(bytes_error))
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								None => Err(EncryptedHashMapError::UnknownIdentifier)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						fn insert<Value: FromRawBytes + BytesConvertable>(&mut self, key: H128, value: Value, password: &str) -> Option<Value> {
 | 
				
			||||||
 | 
							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();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 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()];
 | 
				
			||||||
 | 
							// aes-128-ctr with initial vector of iv
 | 
				
			||||||
 | 
							crypto::aes::encrypt(&derived_left_bits, &iv.clone(), &value.as_slice(), &mut cipher_text);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// KECCAK(DK[16..31] ++ <ciphertext>), where DK[16..31] - derived_right_bits
 | 
				
			||||||
 | 
							let mac = derive_mac(&derived_right_bits, &cipher_text.clone()).sha3();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							let mut key_file = KeyFileContent::new(
 | 
				
			||||||
 | 
								KeyFileCrypto::new_pbkdf2(
 | 
				
			||||||
 | 
									cipher_text,
 | 
				
			||||||
 | 
									iv,
 | 
				
			||||||
 | 
									salt,
 | 
				
			||||||
 | 
									mac,
 | 
				
			||||||
 | 
									KEY_ITERATIONS,
 | 
				
			||||||
 | 
									KEY_LENGTH));
 | 
				
			||||||
 | 
							key_file.id = key;
 | 
				
			||||||
 | 
							if let Err(io_error) = self.directory.save(key_file) {
 | 
				
			||||||
 | 
								warn!("Error saving key file: {:?}", io_error);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							previous
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						fn remove<Value: FromRawBytes + BytesConvertable>(&mut self, key: &H128, password: Option<&str>) -> Option<Value> {
 | 
				
			||||||
 | 
							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
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[cfg(test)]
 | 
				
			||||||
 | 
					mod vector_tests {
 | 
				
			||||||
 | 
						use super::{derive_mac,derive_key_iterations};
 | 
				
			||||||
 | 
						use common::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						#[test]
 | 
				
			||||||
 | 
						fn mac_vector() {
 | 
				
			||||||
 | 
							let password = "testpassword";
 | 
				
			||||||
 | 
							let salt = H256::from_str("ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd").unwrap();
 | 
				
			||||||
 | 
							let cipher_text = FromHex::from_hex("5318b4d5bcd28de64ee5559e671353e16f075ecae9f99c7a79a38af5f869aa46").unwrap();
 | 
				
			||||||
 | 
							let iterations = 262144u32;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							let (derived_left_bits, derived_right_bits) = derive_key_iterations(password, &salt, iterations);
 | 
				
			||||||
 | 
							assert_eq!("f06d69cdc7da0faffb1008270bca38f5", derived_left_bits.to_hex());
 | 
				
			||||||
 | 
							assert_eq!("e31891a3a773950e6d0fea48a7188551", derived_right_bits.to_hex());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							let mac_body = derive_mac(&derived_right_bits, &cipher_text);
 | 
				
			||||||
 | 
							assert_eq!("e31891a3a773950e6d0fea48a71885515318b4d5bcd28de64ee5559e671353e16f075ecae9f99c7a79a38af5f869aa46", mac_body.to_hex());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							let mac = mac_body.sha3();
 | 
				
			||||||
 | 
							assert_eq!("517ead924a9d0dc3124507e3393d175ce3ff7c1e96529c6c555ce9e51205e9b2", format!("{:?}", mac));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[cfg(test)]
 | 
				
			||||||
 | 
					mod tests {
 | 
				
			||||||
 | 
						use super::*;
 | 
				
			||||||
 | 
						use tests::helpers::*;
 | 
				
			||||||
 | 
						use common::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						#[test]
 | 
				
			||||||
 | 
						fn can_insert() {
 | 
				
			||||||
 | 
							let temp = RandomTempPath::create_dir();
 | 
				
			||||||
 | 
							let mut sstore = SecretStore::new_test(&temp);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							let id = H128::random();
 | 
				
			||||||
 | 
							sstore.insert(id.clone(), "Cat".to_owned(), "pass");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							assert!(sstore.get::<String>(&id, "pass").is_ok());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						#[test]
 | 
				
			||||||
 | 
						fn can_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!("should be error loading key,  we requested the wrong key");
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						fn pregenerate_keys(temp: &RandomTempPath, count: usize) -> Vec<H128> {
 | 
				
			||||||
 | 
							use keys::directory::{KeyFileContent, KeyFileCrypto};
 | 
				
			||||||
 | 
							let mut write_sstore = SecretStore::new_test(&temp);
 | 
				
			||||||
 | 
							let mut result = Vec::new();
 | 
				
			||||||
 | 
							for _ in 0..count {
 | 
				
			||||||
 | 
								result.push(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());
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							result
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						#[test]
 | 
				
			||||||
 | 
						fn can_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);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						#[test]
 | 
				
			||||||
 | 
						fn can_delete() {
 | 
				
			||||||
 | 
							let temp = RandomTempPath::create_dir();
 | 
				
			||||||
 | 
							let keys = pregenerate_keys(&temp, 5);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							let mut sstore = SecretStore::new_test(&temp);
 | 
				
			||||||
 | 
							sstore.delete(&keys[2]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							assert_eq!(4, sstore.directory.list().unwrap().len())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -16,7 +16,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
//! Semantic version formatting and comparing.
 | 
					//! Semantic version formatting and comparing.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// A version value with strict meaning. Use `to_u32` to convert to a simple integer.
 | 
					/// A version value with strict meaning. Use `as_u32` to convert to a simple integer.
 | 
				
			||||||
///
 | 
					///
 | 
				
			||||||
/// # Example
 | 
					/// # Example
 | 
				
			||||||
/// ```
 | 
					/// ```
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user