diff --git a/ethcore/src/secret_store.rs b/ethcore/src/keys_directory.rs
similarity index 70%
rename from ethcore/src/secret_store.rs
rename to ethcore/src/keys_directory.rs
index 2626b9b9c..cd722fa27 100644
--- a/ethcore/src/secret_store.rs
+++ b/ethcore/src/keys_directory.rs
@@ -14,19 +14,18 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see .
-//! SecretStore
-//! module for managing key files, decrypting and encrypting arbitrary data
+//! Keys Directory
use common::*;
use std::path::{PathBuf};
const CURRENT_DECLARED_VERSION: u64 = 3;
-
const MAX_KEY_FILE_LEN: u64 = 1024 * 80;
+/// Cipher type (currently only aes-128-ctr)
#[derive(PartialEq, Debug)]
-enum CryptoCipherType {
- // aes-128-ctr with 128-bit initialisation vector(iv)
+pub enum CryptoCipherType {
+ /// aes-128-ctr with 128-bit initialisation vector(iv)
Aes128Ctr(U128)
}
@@ -35,23 +34,25 @@ enum KeyFileVersion {
V3(u64)
}
+/// key generator function
#[derive(PartialEq, Debug)]
-enum Pbkdf2CryptoFunction {
+pub enum Pbkdf2CryptoFunction {
+ /// keyed-hash generator (HMAC-256)
HMacSha256
}
#[allow(non_snake_case)]
-// Kdf of type `Pbkdf2`
-// https://en.wikipedia.org/wiki/PBKDF2
-struct KdfPbkdf2Params {
- // desired length of the derived key, in octets
- dkLen: u32,
- // cryptographic salt
- salt: H256,
- // number of iterations for derived key
- c: u32,
- // pseudo-random 2-parameters function
- prf: Pbkdf2CryptoFunction
+/// Kdf of type `Pbkdf2`
+/// https://en.wikipedia.org/wiki/PBKDF2
+pub struct KdfPbkdf2Params {
+ /// desired length of the derived key, in octets
+ pub dkLen: u32,
+ /// cryptographic salt
+ pub salt: H256,
+ /// number of iterations for derived key
+ pub c: u32,
+ /// pseudo-random 2-parameters function
+ pub prf: Pbkdf2CryptoFunction
}
#[derive(Debug)]
@@ -94,25 +95,24 @@ impl KdfPbkdf2Params {
}
#[allow(non_snake_case)]
-// Kdf of type `Scrypt`
-// https://en.wikipedia.org/wiki/Scrypt
-struct KdfScryptParams {
- // desired length of the derived key, in octets
- dkLen: u32,
- // parallelization
- p: u32,
- // cpu cost
- n: u32,
- // TODO: comment
- r: u32,
- // cryptographic salt
- salt: H256,
+/// Kdf of type `Scrypt`
+/// https://en.wikipedia.org/wiki/Scrypt
+pub struct KdfScryptParams {
+ /// desired length of the derived key, in octets
+ pub dkLen: u32,
+ /// parallelization
+ pub p: u32,
+ /// cpu cost
+ pub n: u32,
+ /// TODO: comment
+ pub r: u32,
+ /// cryptographic salt
+ pub salt: H256,
}
#[derive(Debug)]
enum ScryptParseError {
InvalidParameter(&'static str),
- InvalidPrf(Mismatch),
InvalidSaltFormat(UtilError),
MissingParameter(&'static str),
}
@@ -148,15 +148,22 @@ impl KdfScryptParams {
}
}
-enum KeyFileKdf {
+/// Settings for password derived key geberator function
+pub enum KeyFileKdf {
+ /// Password-Based Key Derivation Function 2 (PBKDF2) type
+ /// https://en.wikipedia.org/wiki/PBKDF2
Pbkdf2(KdfPbkdf2Params),
+ /// Scrypt password-based key derivation function
+ /// https://en.wikipedia.org/wiki/Scrypt
Scrypt(KdfScryptParams)
}
-struct KeyFileCrypto {
- cipher_type: CryptoCipherType,
- cipher_text: Bytes,
- kdf: KeyFileKdf,
+/// Encrypted password or other arbitrary message
+/// with settings for password derived key generator for decrypting content
+pub struct KeyFileCrypto {
+ pub cipher_type: CryptoCipherType,
+ pub cipher_text: Bytes,
+ pub kdf: KeyFileKdf,
}
impl KeyFileCrypto {
@@ -229,38 +236,185 @@ impl KeyFileCrypto {
Json::Object(map)
}
+
+ /// New pbkdf2-type secret
+ /// `cipher-text` - encrypted cipher text
+ /// `dk-len` - desired length of the derived key, in octets
+ /// `c` - number of iterations for derived key
+ /// `salt` - cryptographic site, random 256-bit hash (ensure it's crypto-random)
+ /// `iv` - ini
+ pub fn new_pbkdf2(cipher_text: Bytes, iv: U128, salt: H256, c: u32, dk_len: u32) -> KeyFileCrypto {
+ KeyFileCrypto {
+ cipher_type: CryptoCipherType::Aes128Ctr(iv),
+ cipher_text: cipher_text,
+ kdf: KeyFileKdf::Pbkdf2(KdfPbkdf2Params {
+ dkLen: dk_len,
+ salt: salt,
+ c: c,
+ prf: Pbkdf2CryptoFunction::HMacSha256
+ }),
+ }
+ }
}
-type Uuid = String;
+/// Universally unique identifier
+pub type Uuid = H128;
-struct KeyFileContent {
+fn new_uuid() -> Uuid {
+ H128::random()
+}
+
+fn uuid_to_string(uuid: &Uuid) -> String {
+ let d1 = &uuid.as_slice()[0..4];
+ let d2 = &uuid.as_slice()[4..6];
+ let d3 = &uuid.as_slice()[6..8];
+ let d4 = &uuid.as_slice()[8..10];
+ let d5 = &uuid.as_slice()[10..16];
+ format!("{}-{}-{}-{}-{}", d1.to_hex(), d2.to_hex(), d3.to_hex(), d4.to_hex(), d5.to_hex())
+}
+
+fn uuid_from_string(s: &str) -> Result {
+ let parts: Vec<&str> = s.split("-").collect();
+ if parts.len() != 5 { return Err(UtilError::BadSize); }
+
+ let mut uuid = H128::zero();
+
+ if parts[0].len() != 8 { return Err(UtilError::BadSize); }
+ uuid[0..4].clone_from_slice(&try!(FromHex::from_hex(parts[0])));
+ if parts[1].len() != 4 { return Err(UtilError::BadSize); }
+ uuid[4..6].clone_from_slice(&try!(FromHex::from_hex(parts[1])));
+ if parts[2].len() != 4 { return Err(UtilError::BadSize); }
+ uuid[6..8].clone_from_slice(&try!(FromHex::from_hex(parts[2])));
+ if parts[3].len() != 4 { return Err(UtilError::BadSize); }
+ uuid[8..10].clone_from_slice(&try!(FromHex::from_hex(parts[3])));
+ if parts[4].len() != 12 { return Err(UtilError::BadSize); }
+ uuid[10..16].clone_from_slice(&try!(FromHex::from_hex(parts[4])));
+
+ Ok(uuid)
+}
+
+/// Stored key file struct with encrypted message (cipher_text)
+/// also contains password derivation function settings (PBKDF2/Scrypt)
+pub struct KeyFileContent {
version: KeyFileVersion,
- crypto: KeyFileCrypto,
- id: Uuid
+ /// holds cypher and decrypt function settings
+ pub crypto: KeyFileCrypto,
+ /// identifier
+ pub id: Uuid
}
-struct KeyDirectory {
- cache: HashMap,
- path: Path,
+#[derive(Debug)]
+enum CryptoParseError {
+ NoCipherText,
+ NoCipherType,
+ InvalidJsonFormat,
+ InvalidKdfType(Mismatch),
+ InvalidCipherType(Mismatch),
+ NoInitialVector,
+ NoCipherParameters,
+ InvalidInitialVector(FromHexError),
+ NoKdfType,
+ Scrypt(ScryptParseError),
+ KdfPbkdf2(Pbkdf2ParseError)
+}
+
+#[derive(Debug)]
+enum KeyFileParseError {
+ InvalidVersion,
+ UnsupportedVersion(OutOfBounds),
+ InvalidJsonFormat,
+ InvalidIdentifier,
+ NoCryptoSection,
+ Crypto(CryptoParseError),
+}
+
+impl KeyFileContent {
+ /// new stored key file struct with encrypted message (cipher_text)
+ /// also contains password derivation function settings (PBKDF2/Scrypt)
+ /// to decrypt cipher_text given the password is provided
+ pub fn new(crypto: KeyFileCrypto) -> KeyFileContent {
+ KeyFileContent {
+ id: new_uuid(),
+ version: KeyFileVersion::V3(3),
+ crypto: crypto
+ }
+ }
+
+ fn from_json(json: &Json) -> Result {
+ let as_object = match json.as_object() {
+ None => { return Err(KeyFileParseError::InvalidJsonFormat); },
+ Some(obj) => obj
+ };
+
+ let version = match as_object["version"].as_u64() {
+ None => { return Err(KeyFileParseError::InvalidVersion); },
+ Some(json_version) => {
+ if json_version <= 2 {
+ return Err(KeyFileParseError::UnsupportedVersion(OutOfBounds { min: Some(3), max: None, found: json_version }))
+ };
+ KeyFileVersion::V3(json_version)
+ }
+ };
+
+ let id_text = try!(as_object.get("id").and_then(|json| json.as_string()).ok_or(KeyFileParseError::InvalidIdentifier));
+ let id = match uuid_from_string(&id_text) {
+ Err(_) => { return Err(KeyFileParseError::InvalidIdentifier); },
+ Ok(id) => id
+ };
+
+ let crypto = match as_object.get("crypto") {
+ None => { return Err(KeyFileParseError::NoCryptoSection); }
+ Some(crypto_json) => match KeyFileCrypto::from_json(crypto_json) {
+ Ok(crypto) => crypto,
+ Err(crypto_error) => { return Err(KeyFileParseError::Crypto(crypto_error)); }
+ }
+ };
+
+ Ok(KeyFileContent {
+ version: version,
+ id: id.clone(),
+ crypto: crypto
+ })
+ }
+
+ fn to_json(&self) -> Json {
+ let mut map = BTreeMap::new();
+ 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());
+
+ Json::Object(map)
+ }
}
#[derive(Debug)]
enum KeyLoadError {
- NotFound,
InvalidEncoding,
FileTooLarge(OutOfBounds),
FileParseError(KeyFileParseError),
- FileReadError(::std::io::Error)
+ FileReadError(::std::io::Error),
+}
+
+/// represents directory for saving/loading key files
+pub struct KeyDirectory {
+ /// directory path for key management
+ path: String,
+ cache: HashMap,
+ cache_usage: VecDeque,
}
impl KeyDirectory {
- fn key_path(&self, id: &Uuid) -> PathBuf {
- let mut path = self.path.to_path_buf();
- path.push(&id);
- path
+ /// Initializes new cache directory context with a given `path`
+ pub fn new(path: &Path) -> KeyDirectory {
+ KeyDirectory {
+ cache: HashMap::new(),
+ path: path.to_str().expect("Initialized key directory with empty path").to_owned(),
+ cache_usage: VecDeque::new(),
+ }
}
- fn save(&mut self, key_file: KeyFileContent) -> Result<(), ::std::io::Error> {
+ /// saves (inserts or updates) given key
+ pub fn save(&mut self, key_file: KeyFileContent) -> Result<(), ::std::io::Error> {
{
let mut file = try!(fs::File::create(self.key_path(&key_file.id)));
let json = key_file.to_json();
@@ -272,28 +426,40 @@ impl KeyDirectory {
Ok(())
}
- fn get(&mut self, id: &Uuid) -> Option<&KeyFileContent> {
- let path = {
- let mut path = self.path.to_path_buf();
- path.push(&id);
- path
- };
-
+ /// returns key given by id if corresponding file exists and no load error occured
+ /// warns if any error occured during the key loading
+ pub fn get(&mut self, id: &Uuid) -> Option<&KeyFileContent> {
+ let path = self.key_path(id);
Some(self.cache.entry(id.to_owned()).or_insert(
- match KeyDirectory::load_key(&path, id) {
+ match KeyDirectory::load_key(&path) {
Ok(loaded_key) => loaded_key,
- Err(error) => { return None; }
+ Err(error) => {
+ warn!(target: "sstore", "error loading key {:?}: {:?}", id, error);
+ return None;
+ }
}
))
}
- fn load_key(path: &PathBuf, id: &Uuid) -> Result {
+ /// returns current path to the directory with keys
+ pub fn path(&self) -> &str {
+ &self.path
+ }
+
+ fn key_path(&self, id: &Uuid) -> PathBuf {
+ let mut path = PathBuf::new();
+ path.push(self.path.clone());
+ path.push(uuid_to_string(&id));
+ path
+ }
+
+ fn load_key(path: &PathBuf) -> Result {
match fs::File::open(path.clone()) {
Ok(mut open_file) => {
match open_file.metadata() {
Ok(metadata) =>
- if metadata.len() > MAX_KEY_FILE_LEN { Err(KeyLoadError::FileTooLarge(OutOfBounds { min: Some(2), max: Some(MAX_KEY_FILE_LEN), found: metadata.len() })) }
- else { KeyDirectory::load_from_file(&mut open_file, metadata.len()) },
+ if metadata.len() > MAX_KEY_FILE_LEN { Err(KeyLoadError::FileTooLarge(OutOfBounds { min: Some(2), max: Some(MAX_KEY_FILE_LEN), found: metadata.len() })) }
+ else { KeyDirectory::load_from_file(&mut open_file, metadata.len()) },
Err(read_error) => Err(KeyLoadError::FileReadError(read_error))
}
},
@@ -315,90 +481,31 @@ impl KeyDirectory {
Ok(key_file_content) => Ok(key_file_content),
Err(parse_error) => Err(KeyLoadError::FileParseError(parse_error))
},
- Err(json_error) => Err(KeyLoadError::FileParseError(KeyFileParseError::InvalidJsonFormat))
+ Err(_) => Err(KeyLoadError::FileParseError(KeyFileParseError::InvalidJsonFormat))
},
- Err(error) => Err(KeyLoadError::InvalidEncoding)
+ Err(_) => Err(KeyLoadError::InvalidEncoding)
}
}
}
-#[derive(Debug)]
-enum CryptoParseError {
- NoCryptoVersion,
- NoCipherText,
- NoCipherType,
- InvalidJsonFormat,
- InvalidCryptoVersion,
- InvalidKdfType(Mismatch),
- InvalidCipherType(Mismatch),
- NoInitialVector,
- NoCipherParameters,
- InvalidInitialVector(FromHexError),
- NoKdfType,
- NoKdfParams,
- Scrypt(ScryptParseError),
- KdfPbkdf2(Pbkdf2ParseError)
-}
-
-#[derive(Debug)]
-enum KeyFileParseError {
- InvalidVersion,
- UnsupportedVersion(OutOfBounds),
- InvalidJsonFormat,
- InvalidIdentifier,
- NoCryptoSection,
- Crypto(CryptoParseError),
-}
-
-impl KeyFileContent {
- fn from_json(json: &Json) -> Result {
- let as_object = match json.as_object() {
- None => { return Err(KeyFileParseError::InvalidJsonFormat); },
- Some(obj) => obj
- };
-
- let version = match as_object["version"].as_u64() {
- None => { return Err(KeyFileParseError::InvalidVersion); },
- Some(json_version) => {
- if json_version <= 2 {
- return Err(KeyFileParseError::UnsupportedVersion(OutOfBounds { min: Some(3), max: None, found: json_version }))
- };
- KeyFileVersion::V3(json_version)
- }
- };
-
- let id = try!(as_object.get("id").and_then(|json| json.as_string()).ok_or(KeyFileParseError::InvalidIdentifier));
-
- let crypto = match as_object.get("crypto") {
- None => { return Err(KeyFileParseError::NoCryptoSection); }
- Some(crypto_json) => match KeyFileCrypto::from_json(crypto_json) {
- Ok(crypto) => crypto,
- Err(crypto_error) => { return Err(KeyFileParseError::Crypto(crypto_error)); }
- }
- };
-
- Ok(KeyFileContent {
- version: version,
- id: id.to_owned(),
- crypto: crypto
- })
- }
-
- fn to_json(&self) -> Json {
- let mut map = BTreeMap::new();
- map.insert("id".to_owned(), Json::String(self.id.to_owned()));
- map.insert("version".to_owned(), Json::U64(CURRENT_DECLARED_VERSION));
- map.insert("crypto".to_owned(), self.crypto.to_json());
-
- Json::Object(map)
- }
-}
#[cfg(test)]
mod tests {
- use super::{KeyFileContent, KeyFileVersion, KeyFileKdf, KeyFileParseError, CryptoParseError};
+ use super::{KeyFileContent, KeyFileVersion, KeyFileKdf, KeyFileParseError, CryptoParseError, uuid_from_string, uuid_to_string};
use common::*;
+ #[test]
+ fn uuid_parses() {
+ let uuid = uuid_from_string("3198bc9c-6672-5ab3-d995-4942343ae5b6").unwrap();
+ assert!(uuid > H128::zero());
+ }
+
+ #[test]
+ fn uuid_serializes() {
+ let uuid = uuid_from_string("3198bc9c-6fff-5ab3-d995-4942343ae5b6").unwrap();
+ assert_eq!(uuid_to_string(&uuid), "3198bc9c-6fff-5ab3-d995-4942343ae5b6");
+ }
+
#[test]
fn can_read_keyfile() {
let json = Json::from_str(
@@ -461,7 +568,7 @@ mod tests {
match KeyFileContent::from_json(&json) {
Ok(key_file) => {
match key_file.crypto.kdf {
- KeyFileKdf::Scrypt(scrypt_params) => {},
+ KeyFileKdf::Scrypt(_) => {},
_ => { panic!("expected kdf params of crypto to be of scrypt type" ); }
}
},
@@ -593,3 +700,19 @@ mod tests {
}
}
}
+
+#[cfg(test)]
+mod specs {
+ use super::*;
+ use common::*;
+ use tests::helpers::*;
+
+ #[test]
+ fn can_initiate_key_directory() {
+ let temp_path = RandomTempPath::create_dir();
+
+ let directory = KeyDirectory::new(&temp_path.as_path());
+
+ assert!(directory.path().len() > 0);
+ }
+}
diff --git a/ethcore/src/lib.rs b/ethcore/src/lib.rs
index c026d1b28..f3e0a5486 100644
--- a/ethcore/src/lib.rs
+++ b/ethcore/src/lib.rs
@@ -101,6 +101,7 @@ pub mod spec;
pub mod transaction;
pub mod views;
pub mod receipt;
+pub mod keys_directory;
mod common;
mod basic_types;
@@ -123,7 +124,6 @@ mod substate;
mod executive;
mod externalities;
mod verification;
-mod secret_store;
#[cfg(test)]
mod tests;
diff --git a/ethcore/src/tests/helpers.rs b/ethcore/src/tests/helpers.rs
index 93e3e0a0d..77ef57b12 100644
--- a/ethcore/src/tests/helpers.rs
+++ b/ethcore/src/tests/helpers.rs
@@ -46,6 +46,15 @@ impl RandomTempPath {
}
}
+ pub fn create_dir() -> RandomTempPath {
+ let mut dir = env::temp_dir();
+ dir.push(H32::random().hex());
+ fs::create_dir_all(dir.as_path()).unwrap();
+ RandomTempPath {
+ path: dir.clone()
+ }
+ }
+
pub fn as_path(&self) -> &PathBuf {
&self.path
}