From 7bfb832312f4b6221a42e25a3f02a5950520b8c4 Mon Sep 17 00:00:00 2001 From: Nikolay Volf Date: Thu, 11 Feb 2016 01:06:35 +0300 Subject: [PATCH 01/17] type metadata for key files --- ethcore/src/lib.rs | 7 ++-- ethcore/src/secret_store.rs | 84 +++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 3 deletions(-) create mode 100644 ethcore/src/secret_store.rs diff --git a/ethcore/src/lib.rs b/ethcore/src/lib.rs index 6c4535339..c026d1b28 100644 --- a/ethcore/src/lib.rs +++ b/ethcore/src/lib.rs @@ -54,7 +54,7 @@ //! cd parity //! cargo build --release //! ``` -//! +//! //! - OSX: //! //! ```bash @@ -123,9 +123,10 @@ mod substate; mod executive; mod externalities; mod verification; +mod secret_store; -#[cfg(test)] +#[cfg(test)] mod tests; #[cfg(test)] -#[cfg(feature="json-tests")] +#[cfg(feature="json-tests")] mod json_tests; diff --git a/ethcore/src/secret_store.rs b/ethcore/src/secret_store.rs new file mode 100644 index 000000000..e610fa65b --- /dev/null +++ b/ethcore/src/secret_store.rs @@ -0,0 +1,84 @@ +// 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 . + +//! SecretStore +//! module for managing key files, decrypting and encrypting arbitrary data + +use common::*; + +enum CryptoCipherType { + // aes-128-ctr with 128-bit initialisation vector(iv) + Aes128Ctr(U128) +} + +enum KeyFileKdf { + Pbkdf2(KdfPbkdf2Params), + Scrypt(KdfScryptParams) +} + +struct KeyFileCrypto { + cipher: CryptoCipherType, + Kdf: KeyFileKdf, +} + +enum KeyFileVersion { + V1, V2, V3 +} + +enum Pbkdf2CryptoFunction { + 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 +} + +#[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, +} + +type Uuid = String; + +enum Kdf { + Pbkdf2(KdfPbkdf2Params), + Scrypt(KdfScryptParams) +} + +struct KeyFileContent { + version: KeyFileVersion, + crypto: KeyFileCrypto, + id: Uuid +} From e19b89be2c757264808c68885fe6641d10bffeda Mon Sep 17 00:00:00 2001 From: Nikolay Volf Date: Thu, 11 Feb 2016 03:32:44 +0300 Subject: [PATCH 02/17] flush --- ethcore/src/secret_store.rs | 172 +++++++++++++++++++++++++++++++++--- 1 file changed, 160 insertions(+), 12 deletions(-) diff --git a/ethcore/src/secret_store.rs b/ethcore/src/secret_store.rs index e610fa65b..a7289f4e4 100644 --- a/ethcore/src/secret_store.rs +++ b/ethcore/src/secret_store.rs @@ -24,18 +24,8 @@ enum CryptoCipherType { Aes128Ctr(U128) } -enum KeyFileKdf { - Pbkdf2(KdfPbkdf2Params), - Scrypt(KdfScryptParams) -} - -struct KeyFileCrypto { - cipher: CryptoCipherType, - Kdf: KeyFileKdf, -} - enum KeyFileVersion { - V1, V2, V3 + V3(u64) } enum Pbkdf2CryptoFunction { @@ -56,6 +46,22 @@ struct KdfPbkdf2Params { prf: Pbkdf2CryptoFunction } +#[derive(Debug)] +enum KdfPbkdf2ParseError { + InvalidParameter(String) +} + +impl KdfPbkdf2Params { + fn new(_json: &Json) -> Result { + KdfPbkdf2Params{ + dkLen: 0, + salt: H256::zero(), + c: 0, + prf: Pbkdf2CryptoFunction::HMacSha256 + } + } +} + #[allow(non_snake_case)] // Kdf of type `Scrypt` // https://en.wikipedia.org/wiki/Scrypt @@ -70,15 +76,157 @@ struct KdfScryptParams { r: u32, } -type Uuid = String; +#[derive(Debug)] +enum ScryptParseError { + InvalidParameter(String) +} + +impl KdfScryptParams { + fn new(_json: &Json) -> Result { + Ok(KdfScryptParams{ + dkLen: 0, + p: 0, + n: 0, + r: 0 + }) + } +} enum Kdf { Pbkdf2(KdfPbkdf2Params), Scrypt(KdfScryptParams) } +enum KeyFileKdf { + Pbkdf2(KdfPbkdf2Params), + Scrypt(KdfScryptParams) +} + +struct KeyFileCrypto { + cipher_type: CryptoCipherType, + cipher_text: Bytes, + kdf: KeyFileKdf, +} + +impl KeyFileCrypto { + fn new(json: &Json) -> Result { + let as_object = match json.as_object() { + None => { return Err(CryptoParseError::InvalidJsonFormat); } + Some(obj) => obj + }; + + let cipher_type = match as_object["cipher"].as_string() { + None => { return Err(CryptoParseError::NoCipherType); } + Some("aes-128-ctr") => CryptoCipherType::Aes128Ctr( + match as_object["cipherparams"].as_string() { + None => { return Err(CryptoParseError::NoCipherParameters); }, + Some(cipher_param) => H128::from(cipher_param) + } + ), + Some(oter_cipher_type) => { + return Err(CryptoParseError::InvalidCipherType( + Mismatch { expected: "aes-128-ctr".to_owned(), found: other_cipher_type.to_owned() })); + } + }; + + let kdf = match (as_object["kdf"].as_string(), as_object["kdfparams"]) { + (None, _) => { return Err(CryptoParseError::NoKdfType); }, + (_, None) => { return Err(CryptoParseError::NoKdfParams); }, + (Some("scrypt"), Some(kdf_params)) => + match KdfScryptParams::new(kdf_params) { + Err(scrypt_params_error) => return Err(CryptoParseError::Scrypt(scrypt_params_error)), + Ok(scrypt_params) => scrypt_params + }, + (Some("pbkdf2"), Some(kdf_params)) => + match KdfPbkdf2Params::new(kdf_params) { + Err(kdfPbkdf2_params_error) => return Err(CryptoParseError::Scrypt(scrypt_params_error)), + Ok(kdfPbkdf2_params) => kdfPbkdf2_params + }, + (Some(other_kdf), _) => { + return Err(CryptoParseError::InvalidKdfType( + Mismatch { expected: "pbkdf2/scrypt".to_owned(), found: other_kdf.to_ownded()})); + } + }; + + let cipher_text = match as_object["ciphertext"].as_string() { + None => { return Err(CryptoParseError::NoCipherText); } + Some(text) => text + }; + + Ok(KeyFileCrypto { + cipher_text: Bytes::from(cipher_text), + cipher_type: cipher_type, + kdf: kdf, + }) + } +} + +type Uuid = String; + struct KeyFileContent { version: KeyFileVersion, crypto: KeyFileCrypto, id: Uuid } + +#[derive(Debug)] +enum CryptoParseError { + InvalidJsonFormat, + InvalidCryptoVersion, + NoCryptoVersion, + InvalidKdfType(Mismatch), + InvalidCipherType(Mismatch), + NoCipherText, + NoKdfType, + NoKdfParams, + Scrypt(ScryptParseError), + KdfPbkdf2(KdfPbkdf2ParseError) +} + +#[derive(Debug)] +enum KeyFileParseError { + InvalidVersion, + UnsupportedVersion(OutOfBounds), + InvalidJsonFormat, + NoIdentifier, + NoCryptoSection, + Crypto(CryptoParseError), +} + +impl KeyFileContent { + fn new(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 = match as_object["id"].as_string() { + None => { return Err(KeyFileParseError::NoIdentifier); }, + Some(id) => id + }; + + let crypto = match as_object.get("crypto") { + None => { return Err(KeyFileParseError::NoCryptoSection); } + Some(crypto_json) => match KeyFileCrypto::new(crypto_json) { + Ok(crypto) => crypto, + Err(crypto_error) => { return Err(KeyFileParseError::Crypto(crypto_error)); } + } + }; + + Ok(KeyFileContent { + version: version, + id: id.to_owned(), + crypto: crypto + }) + } +} From f0431218d65fa8a74ed3b02c954c804bc913164e Mon Sep 17 00:00:00 2001 From: Nikolay Volf Date: Thu, 11 Feb 2016 04:22:59 +0300 Subject: [PATCH 03/17] basic parsing --- ethcore/src/secret_store.rs | 46 ++++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/ethcore/src/secret_store.rs b/ethcore/src/secret_store.rs index a7289f4e4..48e05f668 100644 --- a/ethcore/src/secret_store.rs +++ b/ethcore/src/secret_store.rs @@ -47,18 +47,18 @@ struct KdfPbkdf2Params { } #[derive(Debug)] -enum KdfPbkdf2ParseError { +enum Pbkdf2ParseError { InvalidParameter(String) } impl KdfPbkdf2Params { - fn new(_json: &Json) -> Result { - KdfPbkdf2Params{ + fn new(_json: &BTreeMap) -> Result { + Ok(KdfPbkdf2Params{ dkLen: 0, salt: H256::zero(), c: 0, prf: Pbkdf2CryptoFunction::HMacSha256 - } + }) } } @@ -82,7 +82,7 @@ enum ScryptParseError { } impl KdfScryptParams { - fn new(_json: &Json) -> Result { + fn new(_json: &BTreeMap) -> Result { Ok(KdfScryptParams{ dkLen: 0, p: 0, @@ -118,33 +118,39 @@ impl KeyFileCrypto { let cipher_type = match as_object["cipher"].as_string() { None => { return Err(CryptoParseError::NoCipherType); } Some("aes-128-ctr") => CryptoCipherType::Aes128Ctr( - match as_object["cipherparams"].as_string() { + match as_object["cipherparams"].as_object() { None => { return Err(CryptoParseError::NoCipherParameters); }, - Some(cipher_param) => H128::from(cipher_param) + Some(cipher_param) => match U128::from_str(match cipher_param["iv"].as_string() { + None => { return Err(CryptoParseError::NoInitialVector); }, + Some(iv_hex_string) => iv_hex_string + }) + { + Ok(iv_value) => iv_value, + Err(hex_error) => { return Err(CryptoParseError::InvalidInitialVector(hex_error)); } + } } ), - Some(oter_cipher_type) => { + Some(other_cipher_type) => { return Err(CryptoParseError::InvalidCipherType( Mismatch { expected: "aes-128-ctr".to_owned(), found: other_cipher_type.to_owned() })); } }; - let kdf = match (as_object["kdf"].as_string(), as_object["kdfparams"]) { + let kdf = match (as_object["kdf"].as_string(), as_object["kdfparams"].as_object()) { (None, _) => { return Err(CryptoParseError::NoKdfType); }, - (_, None) => { return Err(CryptoParseError::NoKdfParams); }, (Some("scrypt"), Some(kdf_params)) => match KdfScryptParams::new(kdf_params) { - Err(scrypt_params_error) => return Err(CryptoParseError::Scrypt(scrypt_params_error)), - Ok(scrypt_params) => scrypt_params + Err(scrypt_params_error) => { return Err(CryptoParseError::Scrypt(scrypt_params_error)); }, + Ok(scrypt_params) => KeyFileKdf::Scrypt(scrypt_params) }, (Some("pbkdf2"), Some(kdf_params)) => match KdfPbkdf2Params::new(kdf_params) { - Err(kdfPbkdf2_params_error) => return Err(CryptoParseError::Scrypt(scrypt_params_error)), - Ok(kdfPbkdf2_params) => kdfPbkdf2_params + Err(kdfPbkdf2_params_error) => { return Err(CryptoParseError::KdfPbkdf2(kdfPbkdf2_params_error)); }, + Ok(kdfPbkdf2_params) => KeyFileKdf::Pbkdf2(kdfPbkdf2_params) }, (Some(other_kdf), _) => { return Err(CryptoParseError::InvalidKdfType( - Mismatch { expected: "pbkdf2/scrypt".to_owned(), found: other_kdf.to_ownded()})); + Mismatch { expected: "pbkdf2/scrypt".to_owned(), found: other_kdf.to_owned()})); } }; @@ -171,16 +177,20 @@ struct KeyFileContent { #[derive(Debug)] enum CryptoParseError { + NoCryptoVersion, + NoCipherText, + NoCipherType, InvalidJsonFormat, InvalidCryptoVersion, - NoCryptoVersion, InvalidKdfType(Mismatch), InvalidCipherType(Mismatch), - NoCipherText, + NoInitialVector, + NoCipherParameters, + InvalidInitialVector(FromHexError), NoKdfType, NoKdfParams, Scrypt(ScryptParseError), - KdfPbkdf2(KdfPbkdf2ParseError) + KdfPbkdf2(Pbkdf2ParseError) } #[derive(Debug)] From e61376565e8d8d9ecb0140c0d7efab007758f9b2 Mon Sep 17 00:00:00 2001 From: Nikolay Volf Date: Thu, 11 Feb 2016 16:17:38 +0300 Subject: [PATCH 04/17] parsing tests --- ethcore/src/secret_store.rs | 227 ++++++++++++++++++++++++++++++++---- 1 file changed, 205 insertions(+), 22 deletions(-) diff --git a/ethcore/src/secret_store.rs b/ethcore/src/secret_store.rs index 48e05f668..0675524f6 100644 --- a/ethcore/src/secret_store.rs +++ b/ethcore/src/secret_store.rs @@ -19,15 +19,18 @@ use common::*; +#[derive(PartialEq, Debug)] enum CryptoCipherType { // aes-128-ctr with 128-bit initialisation vector(iv) Aes128Ctr(U128) } +#[derive(PartialEq, Debug)] enum KeyFileVersion { V3(u64) } +#[derive(PartialEq, Debug)] enum Pbkdf2CryptoFunction { HMacSha256 } @@ -48,16 +51,29 @@ struct KdfPbkdf2Params { #[derive(Debug)] enum Pbkdf2ParseError { - InvalidParameter(String) + InvalidParameter(&'static str), + InvalidPrf(Mismatch), + InvalidSaltFormat(UtilError), + MissingParameter(&'static str), } impl KdfPbkdf2Params { - fn new(_json: &BTreeMap) -> Result { + fn new(json: &BTreeMap) -> Result { Ok(KdfPbkdf2Params{ - dkLen: 0, - salt: H256::zero(), - c: 0, - prf: Pbkdf2CryptoFunction::HMacSha256 + salt: match try!(json.get("salt").ok_or(Pbkdf2ParseError::MissingParameter("salt"))).as_string() { + None => { return Err(Pbkdf2ParseError::InvalidParameter("salt")) }, + Some(salt_value) => match H256::from_str(salt_value) { + Ok(salt_hex_value) => salt_hex_value, + Err(from_hex_error) => { return Err(Pbkdf2ParseError::InvalidSaltFormat(from_hex_error)); }, + } + }, + prf: match try!(json.get("prf").ok_or(Pbkdf2ParseError::MissingParameter("prf"))).as_string() { + Some("hmac-sha256") => Pbkdf2CryptoFunction::HMacSha256, + Some(unexpected_prf) => { return Err(Pbkdf2ParseError::InvalidPrf(Mismatch { expected: "hmac-sha256".to_owned(), found: unexpected_prf.to_owned() })); }, + None => { return Err(Pbkdf2ParseError::InvalidParameter("prf")); }, + }, + dkLen: try!(try!(json.get("dklen").ok_or(Pbkdf2ParseError::MissingParameter("dklen"))).as_u64().ok_or(Pbkdf2ParseError::InvalidParameter("dkLen"))) as u32, + c: try!(try!(json.get("c").ok_or(Pbkdf2ParseError::MissingParameter("c"))).as_u64().ok_or(Pbkdf2ParseError::InvalidParameter("c"))) as u32, }) } } @@ -74,29 +90,36 @@ struct KdfScryptParams { n: u32, // TODO: comment r: u32, + // cryptographic salt + salt: H256, } #[derive(Debug)] enum ScryptParseError { - InvalidParameter(String) + InvalidParameter(&'static str), + InvalidPrf(Mismatch), + InvalidSaltFormat(UtilError), + MissingParameter(&'static str), } impl KdfScryptParams { - fn new(_json: &BTreeMap) -> Result { + fn new(json: &BTreeMap) -> Result { Ok(KdfScryptParams{ - dkLen: 0, - p: 0, - n: 0, - r: 0 + salt: match try!(json.get("salt").ok_or(ScryptParseError::MissingParameter("salt"))).as_string() { + None => { return Err(ScryptParseError::InvalidParameter("salt")) }, + Some(salt_value) => match H256::from_str(salt_value) { + Ok(salt_hex_value) => salt_hex_value, + Err(from_hex_error) => { return Err(ScryptParseError::InvalidSaltFormat(from_hex_error)); }, + } + }, + dkLen: try!(try!(json.get("dklen").ok_or(ScryptParseError::MissingParameter("dklen"))).as_u64().ok_or(ScryptParseError::InvalidParameter("dkLen"))) as u32, + p: try!(try!(json.get("p").ok_or(ScryptParseError::MissingParameter("p"))).as_u64().ok_or(ScryptParseError::InvalidParameter("p"))) as u32, + n: try!(try!(json.get("n").ok_or(ScryptParseError::MissingParameter("n"))).as_u64().ok_or(ScryptParseError::InvalidParameter("n"))) as u32, + r: try!(try!(json.get("r").ok_or(ScryptParseError::MissingParameter("r"))).as_u64().ok_or(ScryptParseError::InvalidParameter("r"))) as u32, }) } } -enum Kdf { - Pbkdf2(KdfPbkdf2Params), - Scrypt(KdfScryptParams) -} - enum KeyFileKdf { Pbkdf2(KdfPbkdf2Params), Scrypt(KdfScryptParams) @@ -198,7 +221,7 @@ enum KeyFileParseError { InvalidVersion, UnsupportedVersion(OutOfBounds), InvalidJsonFormat, - NoIdentifier, + InvalidIdentifier, NoCryptoSection, Crypto(CryptoParseError), } @@ -220,10 +243,7 @@ impl KeyFileContent { } }; - let id = match as_object["id"].as_string() { - None => { return Err(KeyFileParseError::NoIdentifier); }, - Some(id) => id - }; + 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); } @@ -240,3 +260,166 @@ impl KeyFileContent { }) } } + +#[cfg(test)] +mod tests { + use super::{KeyFileContent, KeyFileVersion, KeyFileKdf, KeyFileParseError}; + use common::*; + + #[test] + fn can_read_keyfile() { + let json = Json::from_str( + r#" + { + "crypto" : { + "cipher" : "aes-128-ctr", + "cipherparams" : { + "iv" : "6087dab2f9fdbbfaddc31a909735c1e6" + }, + "ciphertext" : "5318b4d5bcd28de64ee5559e671353e16f075ecae9f99c7a79a38af5f869aa46", + "kdf" : "pbkdf2", + "kdfparams" : { + "c" : 262144, + "dklen" : 32, + "prf" : "hmac-sha256", + "salt" : "ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd" + }, + "mac" : "517ead924a9d0dc3124507e3393d175ce3ff7c1e96529c6c555ce9e51205e9b2" + }, + "id" : "3198bc9c-6672-5ab3-d995-4942343ae5b6", + "version" : 3 + } + "#).unwrap(); + + match KeyFileContent::new(&json) { + Ok(key_file) => { + assert_eq!(KeyFileVersion::V3(3), key_file.version) + }, + Err(e) => panic!("Error parsing valid file: {:?}", e) + } + } + + #[test] + fn can_read_scrypt_krf() { + let json = Json::from_str( + r#" + { + "crypto" : { + "cipher" : "aes-128-ctr", + "cipherparams" : { + "iv" : "83dbcc02d8ccb40e466191a123791e0e" + }, + "ciphertext" : "d172bf743a674da9cdad04534d56926ef8358534d458fffccd4e6ad2fbde479c", + "kdf" : "scrypt", + "kdfparams" : { + "dklen" : 32, + "n" : 262144, + "r" : 1, + "p" : 8, + "salt" : "ab0c7876052600dd703518d6fc3fe8984592145b591fc8fb5c6d43190334ba19" + }, + "mac" : "2103ac29920d71da29f15d75b4a16dbe95cfd7ff8faea1056c33131d846e3097" + }, + "id" : "3198bc9c-6672-5ab3-d995-4942343ae5b6", + "version" : 3 + } + "#).unwrap(); + + match KeyFileContent::new(&json) { + Ok(key_file) => { + match key_file.crypto.kdf { + KeyFileKdf::Scrypt(scrypt_params) => {}, + _ => { panic!("expected kdf params of crypto to be of scrypt type" ); } + } + }, + Err(e) => panic!("Error parsing valid file: {:?}", e) + } + } + + #[test] + fn can_return_error_no_id() { + let json = Json::from_str( + r#" + { + "crypto" : { + "cipher" : "aes-128-ctr", + "cipherparams" : { + "iv" : "83dbcc02d8ccb40e466191a123791e0e" + }, + "ciphertext" : "d172bf743a674da9cdad04534d56926ef8358534d458fffccd4e6ad2fbde479c", + "kdf" : "scrypt", + "kdfparams" : { + "dklen" : 32, + "n" : 262144, + "r" : 1, + "p" : 8, + "salt" : "ab0c7876052600dd703518d6fc3fe8984592145b591fc8fb5c6d43190334ba19" + }, + "mac" : "2103ac29920d71da29f15d75b4a16dbe95cfd7ff8faea1056c33131d846e3097" + }, + "version" : 3 + } + "#).unwrap(); + + match KeyFileContent::new(&json) { + Ok(key_file) => { + panic!("Should be error of no crypto section, got ok"); + }, + Err(KeyFileParseError::InvalidIdentifier) => { }, + Err(other_error) => { panic!("should be error of no crypto section, got {:?}", other_error); } + } + } + + #[test] + fn can_return_error_no_crypto() { + let json = Json::from_str( + r#" + { + "id" : "3198bc9c-6672-5ab3-d995-4942343ae5b6", + "version" : 3 + } + "#).unwrap(); + + match KeyFileContent::new(&json) { + Ok(key_file) => { + panic!("Should be error of no identifier, got ok"); + }, + Err(KeyFileParseError::NoCryptoSection) => { }, + Err(other_error) => { panic!("should be error of no identifier, got {:?}", other_error); } + } + } + + #[test] + fn can_return_error_unsupported_version() { + let json = Json::from_str( + r#" + { + "crypto" : { + "cipher" : "aes-128-ctr", + "cipherparams" : { + "iv" : "83dbcc02d8ccb40e466191a123791e0e" + }, + "ciphertext" : "d172bf743a674da9cdad04534d56926ef8358534d458fffccd4e6ad2fbde479c", + "kdf" : "scrypt", + "kdfparams" : { + "dklen" : 32, + "n" : 262144, + "r" : 1, + "p" : 8, + "salt" : "ab0c7876052600dd703518d6fc3fe8984592145b591fc8fb5c6d43190334ba19" + }, + "mac" : "2103ac29920d71da29f15d75b4a16dbe95cfd7ff8faea1056c33131d846e3097" + }, + "version" : 1 + } + "#).unwrap(); + + match KeyFileContent::new(&json) { + Ok(key_file) => { + panic!("should be error of unsupported version, got ok"); + }, + Err(KeyFileParseError::UnsupportedVersion(_)) => { }, + Err(other_error) => { panic!("should be error of unsupported version, got {:?}", other_error); } + } + } +} From 02990290c68a0d721ba5672c61f8a74cf9a1f92f Mon Sep 17 00:00:00 2001 From: Nikolay Volf Date: Thu, 11 Feb 2016 18:25:00 +0300 Subject: [PATCH 05/17] json generation --- ethcore/src/secret_store.rs | 98 ++++++++++++++++++++++++++++++++++--- 1 file changed, 92 insertions(+), 6 deletions(-) diff --git a/ethcore/src/secret_store.rs b/ethcore/src/secret_store.rs index 0675524f6..65c43fa50 100644 --- a/ethcore/src/secret_store.rs +++ b/ethcore/src/secret_store.rs @@ -19,6 +19,8 @@ use common::*; +const CURRENT_DECLARED_VERSION: u64 = 3; + #[derive(PartialEq, Debug)] enum CryptoCipherType { // aes-128-ctr with 128-bit initialisation vector(iv) @@ -76,6 +78,16 @@ impl KdfPbkdf2Params { c: try!(try!(json.get("c").ok_or(Pbkdf2ParseError::MissingParameter("c"))).as_u64().ok_or(Pbkdf2ParseError::InvalidParameter("c"))) as u32, }) } + + fn to_json(&self) -> Json { + let mut map = BTreeMap::new(); + map.insert("dklen".to_owned(), json_from_u32(self.dkLen)); + map.insert("salt".to_owned(), Json::String(format!("{:?}", self.salt))); + map.insert("prf".to_owned(), Json::String("hmac-sha256".to_owned())); + map.insert("c".to_owned(), json_from_u32(self.c)); + + Json::Object(map) + } } #[allow(non_snake_case)] @@ -102,6 +114,8 @@ enum ScryptParseError { MissingParameter(&'static str), } +fn json_from_u32(number: u32) -> Json { Json::U64(number as u64) } + impl KdfScryptParams { fn new(json: &BTreeMap) -> Result { Ok(KdfScryptParams{ @@ -118,6 +132,17 @@ impl KdfScryptParams { r: try!(try!(json.get("r").ok_or(ScryptParseError::MissingParameter("r"))).as_u64().ok_or(ScryptParseError::InvalidParameter("r"))) as u32, }) } + + fn to_json(&self) -> Json { + let mut map = BTreeMap::new(); + map.insert("dklen".to_owned(), json_from_u32(self.dkLen)); + map.insert("salt".to_owned(), Json::String(format!("{:?}", self.salt))); + map.insert("p".to_owned(), json_from_u32(self.p)); + map.insert("n".to_owned(), json_from_u32(self.n)); + map.insert("r".to_owned(), json_from_u32(self.r)); + + Json::Object(map) + } } enum KeyFileKdf { @@ -168,8 +193,8 @@ impl KeyFileCrypto { }, (Some("pbkdf2"), Some(kdf_params)) => match KdfPbkdf2Params::new(kdf_params) { - Err(kdfPbkdf2_params_error) => { return Err(CryptoParseError::KdfPbkdf2(kdfPbkdf2_params_error)); }, - Ok(kdfPbkdf2_params) => KeyFileKdf::Pbkdf2(kdfPbkdf2_params) + Err(pbkdf2_params_error) => { return Err(CryptoParseError::KdfPbkdf2(pbkdf2_params_error)); }, + Ok(pbkdf2_params) => KeyFileKdf::Pbkdf2(pbkdf2_params) }, (Some(other_kdf), _) => { return Err(CryptoParseError::InvalidKdfType( @@ -188,6 +213,19 @@ impl KeyFileCrypto { kdf: kdf, }) } + + fn to_json(&self) -> Json { + let mut map = BTreeMap::new(); + map.insert("cipher_type".to_owned(), Json::String("aes-128-ctr".to_owned())); + map.insert("cipher_text".to_owned(), Json::String( + self.cipher_text.iter().map(|b| format!("{:02x}", b)).collect::>().join(""))); + map.insert("kdf".to_owned(), match self.kdf { + KeyFileKdf::Pbkdf2(ref pbkdf2_params) => pbkdf2_params.to_json(), + KeyFileKdf::Scrypt(ref scrypt_params) => scrypt_params.to_json() + }); + + Json::Object(map) + } } type Uuid = String; @@ -259,11 +297,20 @@ impl KeyFileContent { 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}; + use super::{KeyFileContent, KeyFileVersion, KeyFileKdf, KeyFileParseError, CryptoParseError}; use common::*; #[test] @@ -362,7 +409,7 @@ mod tests { "#).unwrap(); match KeyFileContent::new(&json) { - Ok(key_file) => { + Ok(_) => { panic!("Should be error of no crypto section, got ok"); }, Err(KeyFileParseError::InvalidIdentifier) => { }, @@ -381,7 +428,7 @@ mod tests { "#).unwrap(); match KeyFileContent::new(&json) { - Ok(key_file) => { + Ok(_) => { panic!("Should be error of no identifier, got ok"); }, Err(KeyFileParseError::NoCryptoSection) => { }, @@ -410,16 +457,55 @@ mod tests { }, "mac" : "2103ac29920d71da29f15d75b4a16dbe95cfd7ff8faea1056c33131d846e3097" }, + "id" : "3198bc9c-6672-5ab3-d995-4942343ae5b6", "version" : 1 } "#).unwrap(); match KeyFileContent::new(&json) { - Ok(key_file) => { + Ok(_) => { panic!("should be error of unsupported version, got ok"); }, Err(KeyFileParseError::UnsupportedVersion(_)) => { }, Err(other_error) => { panic!("should be error of unsupported version, got {:?}", other_error); } } } + + + #[test] + fn can_return_error_initial_vector() { + let json = Json::from_str( + r#" + { + "crypto" : { + "cipher" : "aes-128-ctr", + "cipherparams" : { + "iv" : "83dbcc02d8ccb40e4______66191a123791e0e" + }, + "ciphertext" : "d172bf743a674da9cdad04534d56926ef8358534d458fffccd4e6ad2fbde479c", + "kdf" : "scrypt", + "kdfparams" : { + "dklen" : 32, + "n" : 262144, + "r" : 1, + "p" : 8, + "salt" : "ab0c7876052600dd703518d6fc3fe8984592145b591fc8fb5c6d43190334ba19" + }, + "mac" : "2103ac29920d71da29f15d75b4a16dbe95cfd7ff8faea1056c33131d846e3097" + }, + "id" : "3198bc9c-6672-5ab3-d995-4942343ae5b6", + "version" : 3 + } + "#).unwrap(); + + match KeyFileContent::new(&json) { + Ok(_) => { + panic!("should be error of invalid initial vector, got ok"); + }, + Err(KeyFileParseError::Crypto(CryptoParseError::InvalidInitialVector(_))) => { }, + Err(other_error) => { panic!("should be error of invalid initial vector, got {:?}", other_error); } + } + } + + } From d9b6ab112889181000d1a3773684b1c7ba4bbe4d Mon Sep 17 00:00:00 2001 From: Nikolay Volf Date: Thu, 11 Feb 2016 19:48:47 +0300 Subject: [PATCH 06/17] flush --- ethcore/src/secret_store.rs | 51 +++++++++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/ethcore/src/secret_store.rs b/ethcore/src/secret_store.rs index 65c43fa50..3f8c1156f 100644 --- a/ethcore/src/secret_store.rs +++ b/ethcore/src/secret_store.rs @@ -21,6 +21,8 @@ use common::*; const CURRENT_DECLARED_VERSION: u64 = 3; +const MAX_KEY_FILE_LEN: u64 = 1024 * 80; + #[derive(PartialEq, Debug)] enum CryptoCipherType { // aes-128-ctr with 128-bit initialisation vector(iv) @@ -236,6 +238,53 @@ struct KeyFileContent { id: Uuid } +struct KeyDirectory { + cache: HashMap, + path: Path +} + +#[derive(Debug)] +enum KeyLoadError { + NotFound, + FileTooBig(OutOfBounds), + FileParseError(KeyFileParseError) +} + +use std::fs; + +impl KeyDirectory { + fn get(&mut self, id: Uuid) -> &KeyFileContent { + match cache.get(id) { + Ok(content) => content, + None => { + match self.load(id) { + + } + cache.insert(loaded_key); + loaded_key + } + } + } + + fn load(&mut self, id: Uuid) -> Result { + let mut path = self.path.clone(); + path.push(id); + match ::std::fs::File::open(path.clone()) { + Ok(open_file) => { + match open_file.metadata().len() { + 0...MAX_KEY_FILE_LEN => + } + } + } + } + + fn load_from_file(file: fs::File) -> Result { + match Json::from_str(::std::str::from_utf8(json_data)) { + + } + } +} + #[derive(Debug)] enum CryptoParseError { NoCryptoVersion, @@ -506,6 +555,4 @@ mod tests { Err(other_error) => { panic!("should be error of invalid initial vector, got {:?}", other_error); } } } - - } From 6cdc2204062760bd14a0abc5155d9b8dae263941 Mon Sep 17 00:00:00 2001 From: Nikolay Volf Date: Fri, 12 Feb 2016 01:43:37 +0300 Subject: [PATCH 07/17] key directory --- ethcore/src/secret_store.rs | 89 ++++++++++++++++++++++++++----------- 1 file changed, 63 insertions(+), 26 deletions(-) diff --git a/ethcore/src/secret_store.rs b/ethcore/src/secret_store.rs index 3f8c1156f..fde946ecf 100644 --- a/ethcore/src/secret_store.rs +++ b/ethcore/src/secret_store.rs @@ -18,6 +18,7 @@ //! module for managing key files, decrypting and encrypting arbitrary data use common::*; +use std::path::{PathBuf}; const CURRENT_DECLARED_VERSION: u64 = 3; @@ -240,47 +241,83 @@ struct KeyFileContent { struct KeyDirectory { cache: HashMap, - path: Path + path: Path, } #[derive(Debug)] enum KeyLoadError { NotFound, - FileTooBig(OutOfBounds), - FileParseError(KeyFileParseError) + InvalidEncoding, + FileTooLarge(OutOfBounds), + FileParseError(KeyFileParseError), + FileReadError(::std::io::Error) } -use std::fs; - impl KeyDirectory { - fn get(&mut self, id: Uuid) -> &KeyFileContent { - match cache.get(id) { - Ok(content) => content, - None => { - match self.load(id) { + fn key_path(&self, id: &Uuid) -> PathBuf { + let mut path = self.path.to_path_buf(); + path.push(&id); + path + } - } - cache.insert(loaded_key); - loaded_key + 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(); + let json_text = format!("{}", json.pretty()); + let json_bytes = json_text.into_bytes(); + try!(file.write(&json_bytes)); + } + self.cache.insert(key_file.id.clone(), key_file); + Ok(()) + } + + fn get(&mut self, id: &Uuid) -> Option<&KeyFileContent> { + let path = { + let mut path = self.path.to_path_buf(); + path.push(&id); + path + }; + + Some(self.cache.entry(id.to_owned()).or_insert( + match KeyDirectory::load_key(&path, id) { + Ok(loaded_key) => loaded_key, + Err(error) => { return None; } } + )) + } + + fn load_key(path: &PathBuf, id: &Uuid) -> 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()) }, + Err(read_error) => Err(KeyLoadError::FileReadError(read_error)) + } + }, + Err(read_error) => Err(KeyLoadError::FileReadError(read_error)) } } - fn load(&mut self, id: Uuid) -> Result { - let mut path = self.path.clone(); - path.push(id); - match ::std::fs::File::open(path.clone()) { - Ok(open_file) => { - match open_file.metadata().len() { - 0...MAX_KEY_FILE_LEN => - } - } + fn load_from_file(file: &mut fs::File, size: u64) -> Result { + let mut json_data = vec![0u8; size as usize]; + + match file.read_to_end(&mut json_data) { + Ok(_) => {}, + Err(read_error) => { return Err(KeyLoadError::FileReadError(read_error)); } } - } - - fn load_from_file(file: fs::File) -> Result { - match Json::from_str(::std::str::from_utf8(json_data)) { + match ::std::str::from_utf8(&json_data) { + Ok(ut8_string) => match Json::from_str(ut8_string) { + Ok(json) => match KeyFileContent::new(&json) { + 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(error) => Err(KeyLoadError::InvalidEncoding) } } } From 1c572147864abb14305a8c9caf4d5243027e5bdc Mon Sep 17 00:00:00 2001 From: Nikolay Volf Date: Fri, 12 Feb 2016 12:52:42 +0300 Subject: [PATCH 08/17] constructor rename --- ethcore/src/secret_store.rs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/ethcore/src/secret_store.rs b/ethcore/src/secret_store.rs index fde946ecf..2626b9b9c 100644 --- a/ethcore/src/secret_store.rs +++ b/ethcore/src/secret_store.rs @@ -63,7 +63,7 @@ enum Pbkdf2ParseError { } impl KdfPbkdf2Params { - fn new(json: &BTreeMap) -> Result { + fn from_json(json: &BTreeMap) -> Result { Ok(KdfPbkdf2Params{ salt: match try!(json.get("salt").ok_or(Pbkdf2ParseError::MissingParameter("salt"))).as_string() { None => { return Err(Pbkdf2ParseError::InvalidParameter("salt")) }, @@ -120,7 +120,7 @@ enum ScryptParseError { fn json_from_u32(number: u32) -> Json { Json::U64(number as u64) } impl KdfScryptParams { - fn new(json: &BTreeMap) -> Result { + fn from_json(json: &BTreeMap) -> Result { Ok(KdfScryptParams{ salt: match try!(json.get("salt").ok_or(ScryptParseError::MissingParameter("salt"))).as_string() { None => { return Err(ScryptParseError::InvalidParameter("salt")) }, @@ -160,7 +160,7 @@ struct KeyFileCrypto { } impl KeyFileCrypto { - fn new(json: &Json) -> Result { + fn from_json(json: &Json) -> Result { let as_object = match json.as_object() { None => { return Err(CryptoParseError::InvalidJsonFormat); } Some(obj) => obj @@ -190,12 +190,12 @@ impl KeyFileCrypto { let kdf = match (as_object["kdf"].as_string(), as_object["kdfparams"].as_object()) { (None, _) => { return Err(CryptoParseError::NoKdfType); }, (Some("scrypt"), Some(kdf_params)) => - match KdfScryptParams::new(kdf_params) { + match KdfScryptParams::from_json(kdf_params) { Err(scrypt_params_error) => { return Err(CryptoParseError::Scrypt(scrypt_params_error)); }, Ok(scrypt_params) => KeyFileKdf::Scrypt(scrypt_params) }, (Some("pbkdf2"), Some(kdf_params)) => - match KdfPbkdf2Params::new(kdf_params) { + match KdfPbkdf2Params::from_json(kdf_params) { Err(pbkdf2_params_error) => { return Err(CryptoParseError::KdfPbkdf2(pbkdf2_params_error)); }, Ok(pbkdf2_params) => KeyFileKdf::Pbkdf2(pbkdf2_params) }, @@ -311,7 +311,7 @@ impl KeyDirectory { match ::std::str::from_utf8(&json_data) { Ok(ut8_string) => match Json::from_str(ut8_string) { - Ok(json) => match KeyFileContent::new(&json) { + Ok(json) => match KeyFileContent::from_json(&json) { Ok(key_file_content) => Ok(key_file_content), Err(parse_error) => Err(KeyLoadError::FileParseError(parse_error)) }, @@ -351,7 +351,7 @@ enum KeyFileParseError { } impl KeyFileContent { - fn new(json: &Json) -> Result { + fn from_json(json: &Json) -> Result { let as_object = match json.as_object() { None => { return Err(KeyFileParseError::InvalidJsonFormat); }, Some(obj) => obj @@ -371,7 +371,7 @@ impl KeyFileContent { let crypto = match as_object.get("crypto") { None => { return Err(KeyFileParseError::NoCryptoSection); } - Some(crypto_json) => match KeyFileCrypto::new(crypto_json) { + Some(crypto_json) => match KeyFileCrypto::from_json(crypto_json) { Ok(crypto) => crypto, Err(crypto_error) => { return Err(KeyFileParseError::Crypto(crypto_error)); } } @@ -424,7 +424,7 @@ mod tests { } "#).unwrap(); - match KeyFileContent::new(&json) { + match KeyFileContent::from_json(&json) { Ok(key_file) => { assert_eq!(KeyFileVersion::V3(3), key_file.version) }, @@ -458,7 +458,7 @@ mod tests { } "#).unwrap(); - match KeyFileContent::new(&json) { + match KeyFileContent::from_json(&json) { Ok(key_file) => { match key_file.crypto.kdf { KeyFileKdf::Scrypt(scrypt_params) => {}, @@ -494,7 +494,7 @@ mod tests { } "#).unwrap(); - match KeyFileContent::new(&json) { + match KeyFileContent::from_json(&json) { Ok(_) => { panic!("Should be error of no crypto section, got ok"); }, @@ -513,7 +513,7 @@ mod tests { } "#).unwrap(); - match KeyFileContent::new(&json) { + match KeyFileContent::from_json(&json) { Ok(_) => { panic!("Should be error of no identifier, got ok"); }, @@ -548,7 +548,7 @@ mod tests { } "#).unwrap(); - match KeyFileContent::new(&json) { + match KeyFileContent::from_json(&json) { Ok(_) => { panic!("should be error of unsupported version, got ok"); }, @@ -584,7 +584,7 @@ mod tests { } "#).unwrap(); - match KeyFileContent::new(&json) { + match KeyFileContent::from_json(&json) { Ok(_) => { panic!("should be error of invalid initial vector, got ok"); }, From f198e5389120fb3165a06457bbd4c9dd284780eb Mon Sep 17 00:00:00 2001 From: Nikolay Volf Date: Fri, 12 Feb 2016 18:39:47 +0300 Subject: [PATCH 09/17] documentation effort --- .../{secret_store.rs => keys_directory.rs} | 397 ++++++++++++------ ethcore/src/lib.rs | 2 +- ethcore/src/tests/helpers.rs | 9 + 3 files changed, 270 insertions(+), 138 deletions(-) rename ethcore/src/{secret_store.rs => keys_directory.rs} (70%) 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 } From 89c5d9f6f6a42debc92cd163ac59ca4101557dba Mon Sep 17 00:00:00 2001 From: Nikolay Volf Date: Fri, 12 Feb 2016 20:09:24 +0300 Subject: [PATCH 10/17] tests and serialization fixes --- ethcore/src/keys_directory.rs | 78 +++++++++++++++++++++++++++++------ 1 file changed, 65 insertions(+), 13 deletions(-) diff --git a/ethcore/src/keys_directory.rs b/ethcore/src/keys_directory.rs index cd722fa27..5801cb491 100644 --- a/ethcore/src/keys_directory.rs +++ b/ethcore/src/keys_directory.rs @@ -161,8 +161,11 @@ pub enum KeyFileKdf { /// Encrypted password or other arbitrary message /// with settings for password derived key generator for decrypting content pub struct KeyFileCrypto { + /// Cipher type pub cipher_type: CryptoCipherType, + /// Cipher text (encrypted message) pub cipher_text: Bytes, + /// password derived key geberator function settings pub kdf: KeyFileKdf, } @@ -173,10 +176,10 @@ impl KeyFileCrypto { Some(obj) => obj }; - let cipher_type = match as_object["cipher"].as_string() { - None => { return Err(CryptoParseError::NoCipherType); } + let cipher_type = match try!(as_object.get("cipher").ok_or(CryptoParseError::NoCipherType)).as_string() { + None => { return Err(CryptoParseError::InvalidCipherType(Mismatch { expected: "aes-128-ctr".to_owned(), found: "not a json string".to_owned() })); } Some("aes-128-ctr") => CryptoCipherType::Aes128Ctr( - match as_object["cipherparams"].as_object() { + match try!(as_object.get("cipherparams").ok_or(CryptoParseError::NoCipherParameters)).as_object() { None => { return Err(CryptoParseError::NoCipherParameters); }, Some(cipher_param) => match U128::from_str(match cipher_param["iv"].as_string() { None => { return Err(CryptoParseError::NoInitialVector); }, @@ -194,7 +197,7 @@ impl KeyFileCrypto { } }; - let kdf = match (as_object["kdf"].as_string(), as_object["kdfparams"].as_object()) { + let kdf = match (try!(as_object.get("kdf").ok_or(CryptoParseError::NoKdf)).as_string(), try!(as_object.get("kdfparams").ok_or(CryptoParseError::NoKdfType)).as_object()) { (None, _) => { return Err(CryptoParseError::NoKdfType); }, (Some("scrypt"), Some(kdf_params)) => match KdfScryptParams::from_json(kdf_params) { @@ -226,10 +229,23 @@ impl KeyFileCrypto { fn to_json(&self) -> Json { let mut map = BTreeMap::new(); - map.insert("cipher_type".to_owned(), Json::String("aes-128-ctr".to_owned())); - map.insert("cipher_text".to_owned(), Json::String( + match self.cipher_type { + CryptoCipherType::Aes128Ctr(iv) => { + map.insert("cipher".to_owned(), Json::String("aes-128-ctr".to_owned())); + let mut cipher_params = BTreeMap::new(); + cipher_params.insert("iv".to_owned(), Json::String(format!("{:?}", iv))); + map.insert("cipherparams".to_owned(), Json::Object(cipher_params)); + } + } + map.insert("ciphertext".to_owned(), Json::String( self.cipher_text.iter().map(|b| format!("{:02x}", b)).collect::>().join(""))); - map.insert("kdf".to_owned(), match self.kdf { + + map.insert("kdf".to_owned(), Json::String(match self.kdf { + KeyFileKdf::Pbkdf2(_) => "pbkdf2".to_owned(), + KeyFileKdf::Scrypt(_) => "scrypt".to_owned() + })); + + map.insert("kdfparams".to_owned(), match self.kdf { KeyFileKdf::Pbkdf2(ref pbkdf2_params) => pbkdf2_params.to_json(), KeyFileKdf::Scrypt(ref scrypt_params) => scrypt_params.to_json() }); @@ -313,6 +329,7 @@ enum CryptoParseError { NoInitialVector, NoCipherParameters, InvalidInitialVector(FromHexError), + NoKdf, NoKdfType, Scrypt(ScryptParseError), KdfPbkdf2(Pbkdf2ParseError) @@ -340,6 +357,13 @@ impl KeyFileContent { } } + /// returns key file version if it is known + pub fn version(&self) -> Option { + match self.version { + KeyFileVersion::V3(declared) => Some(declared) + } + } + fn from_json(json: &Json) -> Result { let as_object = match json.as_object() { None => { return Err(KeyFileParseError::InvalidJsonFormat); }, @@ -414,7 +438,7 @@ impl KeyDirectory { } /// saves (inserts or updates) given key - pub fn save(&mut self, key_file: KeyFileContent) -> Result<(), ::std::io::Error> { + pub fn save(&mut self, key_file: KeyFileContent) -> Result<(Uuid), ::std::io::Error> { { let mut file = try!(fs::File::create(self.key_path(&key_file.id))); let json = key_file.to_json(); @@ -422,8 +446,9 @@ impl KeyDirectory { let json_bytes = json_text.into_bytes(); try!(file.write(&json_bytes)); } - self.cache.insert(key_file.id.clone(), key_file); - Ok(()) + let id = key_file.id.clone(); + self.cache.insert(id.clone(), key_file); + Ok(id.clone()) } /// returns key given by id if corresponding file exists and no load error occured @@ -491,7 +516,7 @@ impl KeyDirectory { #[cfg(test)] mod tests { - use super::{KeyFileContent, KeyFileVersion, KeyFileKdf, KeyFileParseError, CryptoParseError, uuid_from_string, uuid_to_string}; + use super::{KeyFileContent, KeyFileVersion, KeyFileKdf, KeyFileParseError, CryptoParseError, uuid_from_string, uuid_to_string, KeyFileCrypto}; use common::*; #[test] @@ -699,6 +724,24 @@ mod tests { Err(other_error) => { panic!("should be error of invalid initial vector, got {:?}", other_error); } } } + + #[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, U128::zero(), 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, U128::zero(), H256::random(), 32, 32)); + let json = key.to_json(); + + let loaded_key = KeyFileContent::from_json(&json).unwrap(); + + assert_eq!(loaded_key.id, key.id); + } } #[cfg(test)] @@ -710,9 +753,18 @@ mod specs { #[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); } + + #[test] + fn can_save_key() { + 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, U128::zero(), H256::random(), 32, 32))); + + assert!(uuid.is_ok()); + } } From 19e1f6390916d7e2ec588684f036e17a49ef9f6a Mon Sep 17 00:00:00 2001 From: Nikolay Volf Date: Fri, 12 Feb 2016 23:27:09 +0300 Subject: [PATCH 11/17] issues with loading and more tests --- ethcore/src/keys_directory.rs | 100 +++++++++++++++++++++++++++------- 1 file changed, 80 insertions(+), 20 deletions(-) diff --git a/ethcore/src/keys_directory.rs b/ethcore/src/keys_directory.rs index 5801cb491..9f26a8270 100644 --- a/ethcore/src/keys_directory.rs +++ b/ethcore/src/keys_directory.rs @@ -23,24 +23,25 @@ const CURRENT_DECLARED_VERSION: u64 = 3; const MAX_KEY_FILE_LEN: u64 = 1024 * 80; /// Cipher type (currently only aes-128-ctr) -#[derive(PartialEq, Debug)] +#[derive(PartialEq, Debug, Clone)] pub enum CryptoCipherType { /// aes-128-ctr with 128-bit initialisation vector(iv) Aes128Ctr(U128) } -#[derive(PartialEq, Debug)] +#[derive(PartialEq, Debug, Clone)] enum KeyFileVersion { V3(u64) } /// key generator function -#[derive(PartialEq, Debug)] +#[derive(PartialEq, Debug, Clone)] pub enum Pbkdf2CryptoFunction { /// keyed-hash generator (HMAC-256) HMacSha256 } +#[derive(Clone)] #[allow(non_snake_case)] /// Kdf of type `Pbkdf2` /// https://en.wikipedia.org/wiki/PBKDF2 @@ -94,6 +95,7 @@ impl KdfPbkdf2Params { } } +#[derive(Clone)] #[allow(non_snake_case)] /// Kdf of type `Scrypt` /// https://en.wikipedia.org/wiki/Scrypt @@ -148,6 +150,7 @@ impl KdfScryptParams { } } +#[derive(Clone)] /// Settings for password derived key geberator function pub enum KeyFileKdf { /// Password-Based Key Derivation Function 2 (PBKDF2) type @@ -158,6 +161,7 @@ pub enum KeyFileKdf { Scrypt(KdfScryptParams) } +#[derive(Clone)] /// Encrypted password or other arbitrary message /// with settings for password derived key generator for decrypting content pub struct KeyFileCrypto { @@ -309,6 +313,8 @@ fn uuid_from_string(s: &str) -> Result { Ok(uuid) } + +#[derive(Clone)] /// Stored key file struct with encrypted message (cipher_text) /// also contains password derivation function settings (PBKDF2/Scrypt) pub struct KeyFileContent { @@ -340,6 +346,7 @@ enum KeyFileParseError { InvalidVersion, UnsupportedVersion(OutOfBounds), InvalidJsonFormat, + InvalidJson, InvalidIdentifier, NoCryptoSection, Crypto(CryptoParseError), @@ -413,7 +420,6 @@ impl KeyFileContent { #[derive(Debug)] enum KeyLoadError { - InvalidEncoding, FileTooLarge(OutOfBounds), FileParseError(KeyFileParseError), FileReadError(::std::io::Error), @@ -483,8 +489,8 @@ impl KeyDirectory { 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) }, Err(read_error) => Err(KeyLoadError::FileReadError(read_error)) } }, @@ -492,30 +498,25 @@ impl KeyDirectory { } } - fn load_from_file(file: &mut fs::File, size: u64) -> Result { - let mut json_data = vec![0u8; size as usize]; - - match file.read_to_end(&mut json_data) { + fn load_from_file(file: &mut fs::File) -> Result { + let mut buf = String::new(); + match file.read_to_string(&mut buf) { Ok(_) => {}, Err(read_error) => { return Err(KeyLoadError::FileReadError(read_error)); } } - - match ::std::str::from_utf8(&json_data) { - Ok(ut8_string) => match Json::from_str(ut8_string) { - Ok(json) => match KeyFileContent::from_json(&json) { - Ok(key_file_content) => Ok(key_file_content), - Err(parse_error) => Err(KeyLoadError::FileParseError(parse_error)) - }, - Err(_) => Err(KeyLoadError::FileParseError(KeyFileParseError::InvalidJsonFormat)) + match Json::from_str(&buf) { + Ok(json) => match KeyFileContent::from_json(&json) { + Ok(key_file_content) => Ok(key_file_content), + Err(parse_error) => Err(KeyLoadError::FileParseError(parse_error)) }, - Err(_) => Err(KeyLoadError::InvalidEncoding) + Err(_) => Err(KeyLoadError::FileParseError(KeyFileParseError::InvalidJson)) } } } #[cfg(test)] -mod tests { +mod file_tests { use super::{KeyFileContent, KeyFileVersion, KeyFileKdf, KeyFileParseError, CryptoParseError, uuid_from_string, uuid_to_string, KeyFileCrypto}; use common::*; @@ -742,6 +743,38 @@ mod tests { assert_eq!(loaded_key.id, key.id); } + +} + +#[cfg(test)] +mod directory_tests { + use super::{KeyDirectory, new_uuid, uuid_to_string, KeyFileContent, KeyFileCrypto}; + use common::*; + use tests::helpers::*; + + #[test] + fn key_directory_locates_keys() { + let temp_path = RandomTempPath::create_dir(); + let directory = KeyDirectory::new(temp_path.as_path()); + let uuid = new_uuid(); + + let path = directory.key_path(&uuid); + + assert!(path.to_str().unwrap().contains(&uuid_to_string(&uuid))); + } + + #[test] + fn loads_key() { + 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, U128::zero(), H256::random(), 32, 32))).unwrap(); + let path = directory.key_path(&uuid); + + let key = KeyDirectory::load_key(&path).unwrap(); + + assert_eq!(key.id, uuid); + } } #[cfg(test)] @@ -767,4 +800,31 @@ mod specs { assert!(uuid.is_ok()); } + + #[test] + fn can_load_key() { + 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(), U128::zero(), H256::random(), 32, 32))).unwrap(); + + let key = directory.get(&uuid).unwrap(); + + assert_eq!(key.crypto.cipher_text, cipher_text); + } + + #[test] + fn csn_store_10_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..10 { + let key = KeyFileContent::new(KeyFileCrypto::new_pbkdf2(cipher_text.clone(), U128::zero(), H256::random(), 32, 32)); + keys.push(directory.save(key).unwrap()); + } + + assert_eq!(10, keys.len()) + } } From 7fa0fd2440a196c2d1379e6725704d1287264d8d Mon Sep 17 00:00:00 2001 From: Nikolay Volf Date: Sat, 13 Feb 2016 01:12:32 +0300 Subject: [PATCH 12/17] garbage collection --- ethcore/src/keys_directory.rs | 72 ++++++++++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/ethcore/src/keys_directory.rs b/ethcore/src/keys_directory.rs index 9f26a8270..8600d6dad 100644 --- a/ethcore/src/keys_directory.rs +++ b/ethcore/src/keys_directory.rs @@ -21,6 +21,7 @@ use std::path::{PathBuf}; const CURRENT_DECLARED_VERSION: u64 = 3; const MAX_KEY_FILE_LEN: u64 = 1024 * 80; +const MAX_CACHE_USAGE_TRACK: usize = 128; /// Cipher type (currently only aes-128-ctr) #[derive(PartialEq, Debug, Clone)] @@ -461,6 +462,7 @@ impl KeyDirectory { /// warns if any error occured during the key loading pub fn get(&mut self, id: &Uuid) -> Option<&KeyFileContent> { let path = self.key_path(id); + self.cache_usage.push_back(id.clone()); Some(self.cache.entry(id.to_owned()).or_insert( match KeyDirectory::load_key(&path) { Ok(loaded_key) => loaded_key, @@ -477,6 +479,33 @@ impl KeyDirectory { &self.path } + /// removes keys that never been requested during last `MAX_USAGE_TRACK` times + pub fn collect_garbage(&mut self) { + let total_usages = self.cache_usage.len(); + let untracked_usages = max(total_usages as i64 - MAX_CACHE_USAGE_TRACK as i64, 0) as usize; + if untracked_usages > 0 { + self.cache_usage.drain(..untracked_usages); + } + + if self.cache.len() <= MAX_CACHE_USAGE_TRACK { return; } + + let uniqs: HashSet<&Uuid> = self.cache_usage.iter().collect(); + let mut removes = HashSet::new(); + + for key in self.cache.keys() { + if !uniqs.contains(key) { + removes.insert(key.clone()); + } + } + + for removed_key in removes { self.cache.remove(&removed_key); } + } + + /// reports how much keys is currently cached + pub fn cache_size(&self) -> usize { + self.cache.len() + } + fn key_path(&self, id: &Uuid) -> PathBuf { let mut path = PathBuf::new(); path.push(self.path.clone()); @@ -748,7 +777,7 @@ mod file_tests { #[cfg(test)] mod directory_tests { - use super::{KeyDirectory, new_uuid, uuid_to_string, KeyFileContent, KeyFileCrypto}; + use super::{KeyDirectory, new_uuid, uuid_to_string, KeyFileContent, KeyFileCrypto, MAX_CACHE_USAGE_TRACK}; use common::*; use tests::helpers::*; @@ -775,6 +804,47 @@ mod directory_tests { assert_eq!(key.id, uuid); } + + #[test] + fn caches_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..1000 { + let key = KeyFileContent::new(KeyFileCrypto::new_pbkdf2(cipher_text.clone(), U128::zero(), H256::random(), 32, 32)); + keys.push(directory.save(key).unwrap()); + } + + for key_id in keys { + directory.get(&key_id).unwrap(); + } + + assert_eq!(1000, directory.cache_size()) + + } + + #[test] + fn collects_garbage() { + 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..1000 { + let key = KeyFileContent::new(KeyFileCrypto::new_pbkdf2(cipher_text.clone(), U128::zero(), H256::random(), 32, 32)); + keys.push(directory.save(key).unwrap()); + } + + for key_id in keys { + directory.get(&key_id).unwrap(); + } + + directory.collect_garbage(); + // since all keys are different, should be exactly MAX_CACHE_USAGE_TRACK + assert_eq!(MAX_CACHE_USAGE_TRACK, directory.cache_size()) + } } #[cfg(test)] From 91c6b6e2c1a9ec91cecfbfa1cc4b22ccad3fe50d Mon Sep 17 00:00:00 2001 From: Nikolay Volf Date: Sat, 13 Feb 2016 01:29:28 +0300 Subject: [PATCH 13/17] coverage fix --- ethcore/src/keys_directory.rs | 67 +++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/ethcore/src/keys_directory.rs b/ethcore/src/keys_directory.rs index 8600d6dad..27f193a32 100644 --- a/ethcore/src/keys_directory.rs +++ b/ethcore/src/keys_directory.rs @@ -755,6 +755,73 @@ mod file_tests { } } + #[test] + fn can_return_error_for_invalid_scrypt_kdf() { + let json = Json::from_str( + r#" + { + "crypto" : { + "cipher" : "aes-128-ctr", + "cipherparams" : { + "iv" : "83dbcc02d8ccb40e466191a123791e0e" + }, + "ciphertext" : "d172bf743a674da9cdad04534d56926ef8358534d458fffccd4e6ad2fbde479c", + "kdf" : "scrypt", + "kdfparams" : { + "dklen2" : 32, + "n5" : "xx", + "r" : 1, + "p" : 8, + "salt" : "ab0c7876052600dd703518d6fc3fe8984592145b591fc8fb5c6d43190334ba19" + }, + "mac" : "2103ac29920d71da29f15d75b4a16dbe95cfd7ff8faea1056c33131d846e3097" + }, + "id" : "3198bc9c-6672-5ab3-d995-4942343ae5b6", + "version" : 3 + } + "#).unwrap(); + + match KeyFileContent::from_json(&json) { + Ok(_) => { + panic!("Should be error of no identifier, got ok"); + }, + Err(KeyFileParseError::Crypto(CryptoParseError::Scrypt(_))) => { }, + Err(other_error) => { panic!("should be error of no identifier, got {:?}", other_error); } + } + } + + #[test] + fn can_serialize_scrypt_back() { + let json = Json::from_str( + r#" + { + "crypto" : { + "cipher" : "aes-128-ctr", + "cipherparams" : { + "iv" : "83dbcc02d8ccb40e466191a123791e0e" + }, + "ciphertext" : "d172bf743a674da9cdad04534d56926ef8358534d458fffccd4e6ad2fbde479c", + "kdf" : "scrypt", + "kdfparams" : { + "dklen" : 32, + "n" : 262144, + "r" : 1, + "p" : 8, + "salt" : "ab0c7876052600dd703518d6fc3fe8984592145b591fc8fb5c6d43190334ba19" + }, + "mac" : "2103ac29920d71da29f15d75b4a16dbe95cfd7ff8faea1056c33131d846e3097" + }, + "id" : "3198bc9c-6672-5ab3-d995-4942343ae5b6", + "version" : 3 + } + "#).unwrap(); + + let key = KeyFileContent::from_json(&json).unwrap(); + let serialized = key.to_json(); + + assert!(serialized.as_object().is_some()); + } + #[test] fn can_create_key_with_new_id() { let cipher_text: Bytes = FromHex::from_hex("a0f05555").unwrap(); From 75383199d62e2825b76739ebbf065158d8e2cca1 Mon Sep 17 00:00:00 2001 From: Nikolay Volf Date: Sun, 14 Feb 2016 18:22:42 +0300 Subject: [PATCH 14/17] kdf params error checking --- ethcore/src/keys_directory.rs | 84 ++++++++++++++++++++++++++++++++++- 1 file changed, 83 insertions(+), 1 deletion(-) diff --git a/ethcore/src/keys_directory.rs b/ethcore/src/keys_directory.rs index 27f193a32..24885524e 100644 --- a/ethcore/src/keys_directory.rs +++ b/ethcore/src/keys_directory.rs @@ -546,7 +546,7 @@ impl KeyDirectory { #[cfg(test)] mod file_tests { - use super::{KeyFileContent, KeyFileVersion, KeyFileKdf, KeyFileParseError, CryptoParseError, uuid_from_string, uuid_to_string, KeyFileCrypto}; + use super::{KeyFileContent, KeyFileVersion, KeyFileKdf, KeyFileParseError, CryptoParseError, uuid_from_string, uuid_to_string, KeyFileCrypto, KdfPbkdf2Params}; use common::*; #[test] @@ -840,6 +840,88 @@ mod file_tests { assert_eq!(loaded_key.id, key.id); } + #[test] + fn can_parse_kdf_params_fail() { + let json = Json::from_str( + r#" + { + "dklen" : 32, + "n" : 262144, + "r" : 1, + "p" : 8, + "salt" : "ab0c7876052600dd703518d6fc3fe8984592145b591fc8fb5c6d43190334ba19" + } + "#).unwrap(); + { + let mut invalid_json = json.as_object().unwrap().clone(); + invalid_json.remove("dklen"); + let kdf = KdfPbkdf2Params::from_json(&invalid_json); + assert!(!kdf.is_ok()); + } + { + let mut invalid_json = json.as_object().unwrap().clone(); + invalid_json.remove("n"); + let kdf = KdfPbkdf2Params::from_json(&invalid_json); + assert!(!kdf.is_ok()); + } + { + let mut invalid_json = json.as_object().unwrap().clone(); + invalid_json.remove("r"); + let kdf = KdfPbkdf2Params::from_json(&invalid_json); + assert!(!kdf.is_ok()); + } + { + let mut invalid_json = json.as_object().unwrap().clone(); + invalid_json.remove("p"); + let kdf = KdfPbkdf2Params::from_json(&invalid_json); + assert!(!kdf.is_ok()); + } + { + let mut invalid_json = json.as_object().unwrap().clone(); + invalid_json.remove("salt"); + let kdf = KdfPbkdf2Params::from_json(&invalid_json); + assert!(!kdf.is_ok()); + } + + } + + #[test] + fn can_parse_kdf_params_scrypt_fail() { + let json = Json::from_str( + r#" + { + "dklen" : 32, + "r" : 1, + "p" : 8, + "salt" : "ab0c7876052600dd703518d6fc3fe8984592145b591fc8fb5c6d43190334ba19" + } + "#).unwrap(); + { + let mut invalid_json = json.as_object().unwrap().clone(); + invalid_json.remove("dklen"); + let kdf = KdfPbkdf2Params::from_json(&invalid_json); + assert!(!kdf.is_ok()); + } + { + let mut invalid_json = json.as_object().unwrap().clone(); + invalid_json.remove("r"); + let kdf = KdfPbkdf2Params::from_json(&invalid_json); + assert!(!kdf.is_ok()); + } + { + let mut invalid_json = json.as_object().unwrap().clone(); + invalid_json.remove("p"); + let kdf = KdfPbkdf2Params::from_json(&invalid_json); + assert!(!kdf.is_ok()); + } + { + let mut invalid_json = json.as_object().unwrap().clone(); + invalid_json.remove("salt"); + let kdf = KdfPbkdf2Params::from_json(&invalid_json); + assert!(!kdf.is_ok()); + } + } + } #[cfg(test)] From 3389606c7b65aedd817c66dd7ae04e6cfb41148b Mon Sep 17 00:00:00 2001 From: Nikolay Volf Date: Sun, 14 Feb 2016 19:09:36 +0300 Subject: [PATCH 15/17] crypto section fails checks --- ethcore/src/keys_directory.rs | 44 +++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/ethcore/src/keys_directory.rs b/ethcore/src/keys_directory.rs index 24885524e..6becff0c7 100644 --- a/ethcore/src/keys_directory.rs +++ b/ethcore/src/keys_directory.rs @@ -922,6 +922,50 @@ mod file_tests { } } + #[test] + fn can_parse_crypto_fails() { + let json = Json::from_str( + r#" + { + "cipher" : "aes-128-ctr", + "cipherparams" : { + "iv" : "83dbcc02d8ccb40e466191a123791e0e" + }, + "ciphertext" : "d172bf743a674da9cdad04534d56926ef8358534d458fffccd4e6ad2fbde479c", + "kdf" : "scrypt", + "kdfparams" : { + "dklen" : 32, + "n" : 262144, + "r" : 1, + "p" : 8, + "salt" : "ab0c7876052600dd703518d6fc3fe8984592145b591fc8fb5c6d43190334ba19" + }, + "mac" : "2103ac29920d71da29f15d75b4a16dbe95cfd7ff8faea1056c33131d846e3097" + }"#).unwrap(); + + { + let mut invalid_json = json.as_object().unwrap().clone(); + invalid_json.insert("cipher".to_owned(), Json::String("unknown".to_owned())); + let crypto = KeyFileCrypto::from_json(&Json::Object(invalid_json)); + assert!(!crypto.is_ok()); + } + + { + let mut invalid_json = json.as_object().unwrap().clone(); + invalid_json.insert("kdfparams".to_owned(), Json::String("122".to_owned())); + let crypto = KeyFileCrypto::from_json(&Json::Object(invalid_json)); + assert!(!crypto.is_ok()); + } + + { + let mut invalid_json = json.as_object().unwrap().clone(); + invalid_json.insert("kdf".to_owned(), Json::String("15522".to_owned())); + let crypto = KeyFileCrypto::from_json(&Json::Object(invalid_json)); + assert!(!crypto.is_ok()); + } + + } + } #[cfg(test)] From 75197f45866128224ed63fc0dba738e0abcd77f0 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 15 Feb 2016 14:21:45 +0100 Subject: [PATCH 16/17] Update keys_directory.rs --- ethcore/src/keys_directory.rs | 63 +++++++++++++++++------------------ 1 file changed, 30 insertions(+), 33 deletions(-) diff --git a/ethcore/src/keys_directory.rs b/ethcore/src/keys_directory.rs index 6becff0c7..1646877b9 100644 --- a/ethcore/src/keys_directory.rs +++ b/ethcore/src/keys_directory.rs @@ -91,25 +91,24 @@ impl KdfPbkdf2Params { map.insert("salt".to_owned(), Json::String(format!("{:?}", self.salt))); map.insert("prf".to_owned(), Json::String("hmac-sha256".to_owned())); map.insert("c".to_owned(), json_from_u32(self.c)); - Json::Object(map) } } #[derive(Clone)] #[allow(non_snake_case)] -/// Kdf of type `Scrypt` +/// Kdf of type `Scrypt`. /// https://en.wikipedia.org/wiki/Scrypt pub struct KdfScryptParams { - /// desired length of the derived key, in octets + /// Desired length of the derived key, in octets. pub dkLen: u32, - /// parallelization + /// Parallelization parameter. pub p: u32, - /// cpu cost + /// CPU/memory cost parameter. pub n: u32, /// TODO: comment pub r: u32, - /// cryptographic salt + /// Cryptographic salt. pub salt: H256, } @@ -146,31 +145,30 @@ impl KdfScryptParams { map.insert("p".to_owned(), json_from_u32(self.p)); map.insert("n".to_owned(), json_from_u32(self.n)); map.insert("r".to_owned(), json_from_u32(self.r)); - Json::Object(map) } } #[derive(Clone)] -/// Settings for password derived key geberator function +/// Settings for password derived key geberator function. pub enum KeyFileKdf { - /// Password-Based Key Derivation Function 2 (PBKDF2) type + /// Password-Based Key Derivation Function 2 (PBKDF2) type. /// https://en.wikipedia.org/wiki/PBKDF2 Pbkdf2(KdfPbkdf2Params), - /// Scrypt password-based key derivation function + /// Scrypt password-based key derivation function. /// https://en.wikipedia.org/wiki/Scrypt Scrypt(KdfScryptParams) } #[derive(Clone)] /// Encrypted password or other arbitrary message -/// with settings for password derived key generator for decrypting content +/// with settings for password derived key generator for decrypting content. pub struct KeyFileCrypto { - /// Cipher type + /// Cipher type. pub cipher_type: CryptoCipherType, - /// Cipher text (encrypted message) + /// Cipher text (encrypted message). pub cipher_text: Bytes, - /// password derived key geberator function settings + /// Password derived key generator function settings. pub kdf: KeyFileKdf, } @@ -258,12 +256,12 @@ 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 + /// 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` - initialisation vector. pub fn new_pbkdf2(cipher_text: Bytes, iv: U128, salt: H256, c: u32, dk_len: u32) -> KeyFileCrypto { KeyFileCrypto { cipher_type: CryptoCipherType::Aes128Ctr(iv), @@ -320,9 +318,9 @@ fn uuid_from_string(s: &str) -> Result { /// also contains password derivation function settings (PBKDF2/Scrypt) pub struct KeyFileContent { version: KeyFileVersion, - /// holds cypher and decrypt function settings + /// Holds cypher and decrypt function settings. pub crypto: KeyFileCrypto, - /// identifier + /// The identifier. pub id: Uuid } @@ -354,9 +352,9 @@ enum KeyFileParseError { } impl KeyFileContent { - /// new stored key file struct with encrypted message (cipher_text) + /// 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 + /// to decrypt cipher_text given the password is provided. pub fn new(crypto: KeyFileCrypto) -> KeyFileContent { KeyFileContent { id: new_uuid(), @@ -365,7 +363,7 @@ impl KeyFileContent { } } - /// returns key file version if it is known + /// Returns key file version if it is known. pub fn version(&self) -> Option { match self.version { KeyFileVersion::V3(declared) => Some(declared) @@ -414,7 +412,6 @@ impl KeyFileContent { map.insert("id".to_owned(), Json::String(uuid_to_string(&self.id))); map.insert("version".to_owned(), Json::U64(CURRENT_DECLARED_VERSION)); map.insert("crypto".to_owned(), self.crypto.to_json()); - Json::Object(map) } } @@ -426,9 +423,9 @@ enum KeyLoadError { FileReadError(::std::io::Error), } -/// represents directory for saving/loading key files +/// Represents directory for saving/loading key files. pub struct KeyDirectory { - /// directory path for key management + /// Directory path for key management. path: String, cache: HashMap, cache_usage: VecDeque, @@ -458,8 +455,8 @@ impl KeyDirectory { Ok(id.clone()) } - /// returns key given by id if corresponding file exists and no load error occured - /// warns if any error occured during the key loading + /// 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); self.cache_usage.push_back(id.clone()); @@ -474,12 +471,12 @@ impl KeyDirectory { )) } - /// returns current path to the directory with keys + /// Returns current path to the directory with keys pub fn path(&self) -> &str { &self.path } - /// 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) { let total_usages = self.cache_usage.len(); let untracked_usages = max(total_usages as i64 - MAX_CACHE_USAGE_TRACK as i64, 0) as usize; @@ -501,7 +498,7 @@ impl KeyDirectory { for removed_key in removes { self.cache.remove(&removed_key); } } - /// reports how much keys is currently cached + /// Reports how many keys are currently cached. pub fn cache_size(&self) -> usize { self.cache.len() } From 017a1adb242981185186fe9ce471efc6017923c4 Mon Sep 17 00:00:00 2001 From: Nikolay Volf Date: Mon, 15 Feb 2016 18:01:52 +0300 Subject: [PATCH 17/17] fixing issues and moving --- ethcore/src/error.rs | 36 ++++------------ ethcore/src/lib.rs | 1 - ethcore/src/tests/helpers.rs | 9 ---- util/src/error.rs | 22 ++++++++++ .../src/keys/directory.rs | 42 +++++++++---------- util/src/keys/mod.rs | 19 +++++++++ util/src/lib.rs | 4 ++ util/src/tests/helpers.rs | 31 ++++++++++++++ util/src/tests/mod.rs | 1 + 9 files changed, 105 insertions(+), 60 deletions(-) rename ethcore/src/keys_directory.rs => util/src/keys/directory.rs (96%) create mode 100644 util/src/keys/mod.rs create mode 100644 util/src/tests/helpers.rs create mode 100644 util/src/tests/mod.rs diff --git a/ethcore/src/error.rs b/ethcore/src/error.rs index d441929c5..f75f338bf 100644 --- a/ethcore/src/error.rs +++ b/ethcore/src/error.rs @@ -20,59 +20,39 @@ use util::*; use header::BlockNumber; use basic_types::LogBloom; -#[derive(Debug, PartialEq, Eq)] -/// Error indicating an expected value was not found. -pub struct Mismatch { - /// Value expected. - pub expected: T, - /// Value found. - pub found: T, -} - -#[derive(Debug, PartialEq, Eq)] -/// Error indicating value found is outside of a valid range. -pub struct OutOfBounds { - /// Minimum allowed value. - pub min: Option, - /// Maximum allowed value. - pub max: Option, - /// Value found. - pub found: T, -} - /// Result of executing the transaction. #[derive(PartialEq, Debug)] pub enum ExecutionError { /// Returned when there gas paid for transaction execution is /// lower than base gas required. - NotEnoughBaseGas { + NotEnoughBaseGas { /// Absolute minimum gas required. - required: U256, + required: U256, /// Gas provided. got: U256 }, /// Returned when block (gas_used + gas) > gas_limit. - /// + /// /// If gas =< gas_limit, upstream may try to execute the transaction /// in next block. - BlockGasLimitReached { + BlockGasLimitReached { /// Gas limit of block for transaction. gas_limit: U256, /// Gas used in block prior to transaction. gas_used: U256, /// Amount of gas in block. - gas: U256 + gas: U256 }, /// Returned when transaction nonce does not match state nonce. - InvalidNonce { + InvalidNonce { /// Nonce expected. expected: U256, /// Nonce found. got: U256 }, - /// Returned when cost of transaction (value + gas_price * gas) exceeds + /// Returned when cost of transaction (value + gas_price * gas) exceeds /// current sender balance. - NotEnoughCash { + NotEnoughCash { /// Minimum required balance. required: U512, /// Actual balance. diff --git a/ethcore/src/lib.rs b/ethcore/src/lib.rs index 445cebec0..4cca74319 100644 --- a/ethcore/src/lib.rs +++ b/ethcore/src/lib.rs @@ -108,7 +108,6 @@ pub mod spec; pub mod transaction; pub mod views; pub mod receipt; -pub mod keys_directory; mod common; mod basic_types; diff --git a/ethcore/src/tests/helpers.rs b/ethcore/src/tests/helpers.rs index 77ef57b12..93e3e0a0d 100644 --- a/ethcore/src/tests/helpers.rs +++ b/ethcore/src/tests/helpers.rs @@ -46,15 +46,6 @@ 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 } diff --git a/util/src/error.rs b/util/src/error.rs index 465174b4e..68aa3e648 100644 --- a/util/src/error.rs +++ b/util/src/error.rs @@ -20,6 +20,7 @@ use rustc_serialize::hex::FromHexError; use network::NetworkError; use rlp::DecoderError; use io; +use std::fmt; #[derive(Debug)] /// Error in database subsystem. @@ -55,6 +56,27 @@ pub enum UtilError { BadSize, } + +#[derive(Debug, PartialEq, Eq)] +/// Error indicating an expected value was not found. +pub struct Mismatch { + /// Value expected. + pub expected: T, + /// Value found. + pub found: T, +} + +#[derive(Debug, PartialEq, Eq)] +/// Error indicating value found is outside of a valid range. +pub struct OutOfBounds { + /// Minimum allowed value. + pub min: Option, + /// Maximum allowed value. + pub max: Option, + /// Value found. + pub found: T, +} + impl From for UtilError { fn from(err: FromHexError) -> UtilError { UtilError::FromHex(err) diff --git a/ethcore/src/keys_directory.rs b/util/src/keys/directory.rs similarity index 96% rename from ethcore/src/keys_directory.rs rename to util/src/keys/directory.rs index 1646877b9..c5e17100f 100644 --- a/ethcore/src/keys_directory.rs +++ b/util/src/keys/directory.rs @@ -43,12 +43,11 @@ pub enum Pbkdf2CryptoFunction { } #[derive(Clone)] -#[allow(non_snake_case)] /// Kdf of type `Pbkdf2` /// https://en.wikipedia.org/wiki/PBKDF2 pub struct KdfPbkdf2Params { /// desired length of the derived key, in octets - pub dkLen: u32, + pub dk_len: u32, /// cryptographic salt pub salt: H256, /// number of iterations for derived key @@ -80,14 +79,14 @@ impl KdfPbkdf2Params { Some(unexpected_prf) => { return Err(Pbkdf2ParseError::InvalidPrf(Mismatch { expected: "hmac-sha256".to_owned(), found: unexpected_prf.to_owned() })); }, None => { return Err(Pbkdf2ParseError::InvalidParameter("prf")); }, }, - dkLen: try!(try!(json.get("dklen").ok_or(Pbkdf2ParseError::MissingParameter("dklen"))).as_u64().ok_or(Pbkdf2ParseError::InvalidParameter("dkLen"))) as u32, + dk_len: try!(try!(json.get("dklen").ok_or(Pbkdf2ParseError::MissingParameter("dklen"))).as_u64().ok_or(Pbkdf2ParseError::InvalidParameter("dkLen"))) as u32, c: try!(try!(json.get("c").ok_or(Pbkdf2ParseError::MissingParameter("c"))).as_u64().ok_or(Pbkdf2ParseError::InvalidParameter("c"))) as u32, }) } fn to_json(&self) -> Json { let mut map = BTreeMap::new(); - map.insert("dklen".to_owned(), json_from_u32(self.dkLen)); + map.insert("dklen".to_owned(), json_from_u32(self.dk_len)); map.insert("salt".to_owned(), Json::String(format!("{:?}", self.salt))); map.insert("prf".to_owned(), Json::String("hmac-sha256".to_owned())); map.insert("c".to_owned(), json_from_u32(self.c)); @@ -96,12 +95,11 @@ impl KdfPbkdf2Params { } #[derive(Clone)] -#[allow(non_snake_case)] /// Kdf of type `Scrypt`. /// https://en.wikipedia.org/wiki/Scrypt pub struct KdfScryptParams { /// Desired length of the derived key, in octets. - pub dkLen: u32, + pub dk_len: u32, /// Parallelization parameter. pub p: u32, /// CPU/memory cost parameter. @@ -131,7 +129,7 @@ impl KdfScryptParams { Err(from_hex_error) => { return Err(ScryptParseError::InvalidSaltFormat(from_hex_error)); }, } }, - dkLen: try!(try!(json.get("dklen").ok_or(ScryptParseError::MissingParameter("dklen"))).as_u64().ok_or(ScryptParseError::InvalidParameter("dkLen"))) as u32, + dk_len: try!(try!(json.get("dklen").ok_or(ScryptParseError::MissingParameter("dklen"))).as_u64().ok_or(ScryptParseError::InvalidParameter("dkLen"))) as u32, p: try!(try!(json.get("p").ok_or(ScryptParseError::MissingParameter("p"))).as_u64().ok_or(ScryptParseError::InvalidParameter("p"))) as u32, n: try!(try!(json.get("n").ok_or(ScryptParseError::MissingParameter("n"))).as_u64().ok_or(ScryptParseError::InvalidParameter("n"))) as u32, r: try!(try!(json.get("r").ok_or(ScryptParseError::MissingParameter("r"))).as_u64().ok_or(ScryptParseError::InvalidParameter("r"))) as u32, @@ -140,7 +138,7 @@ impl KdfScryptParams { fn to_json(&self) -> Json { let mut map = BTreeMap::new(); - map.insert("dklen".to_owned(), json_from_u32(self.dkLen)); + map.insert("dklen".to_owned(), json_from_u32(self.dk_len)); map.insert("salt".to_owned(), Json::String(format!("{:?}", self.salt))); map.insert("p".to_owned(), json_from_u32(self.p)); map.insert("n".to_owned(), json_from_u32(self.n)); @@ -267,7 +265,7 @@ impl KeyFileCrypto { cipher_type: CryptoCipherType::Aes128Ctr(iv), cipher_text: cipher_text, kdf: KeyFileKdf::Pbkdf2(KdfPbkdf2Params { - dkLen: dk_len, + dk_len: dk_len, salt: salt, c: c, prf: Pbkdf2CryptoFunction::HMacSha256 @@ -417,10 +415,10 @@ impl KeyFileContent { } #[derive(Debug)] -enum KeyLoadError { - FileTooLarge(OutOfBounds), - FileParseError(KeyFileParseError), - FileReadError(::std::io::Error), +enum KeyFileLoadError { + TooLarge(OutOfBounds), + ParseError(KeyFileParseError), + ReadError(::std::io::Error), } /// Represents directory for saving/loading key files. @@ -510,32 +508,32 @@ impl KeyDirectory { path } - fn load_key(path: &PathBuf) -> Result { + 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() })) } + if metadata.len() > MAX_KEY_FILE_LEN { Err(KeyFileLoadError::TooLarge(OutOfBounds { min: Some(2), max: Some(MAX_KEY_FILE_LEN), found: metadata.len() })) } else { KeyDirectory::load_from_file(&mut open_file) }, - Err(read_error) => Err(KeyLoadError::FileReadError(read_error)) + Err(read_error) => Err(KeyFileLoadError::ReadError(read_error)) } }, - Err(read_error) => Err(KeyLoadError::FileReadError(read_error)) + Err(read_error) => Err(KeyFileLoadError::ReadError(read_error)) } } - fn load_from_file(file: &mut fs::File) -> Result { + fn load_from_file(file: &mut fs::File) -> Result { let mut buf = String::new(); match file.read_to_string(&mut buf) { Ok(_) => {}, - Err(read_error) => { return Err(KeyLoadError::FileReadError(read_error)); } + Err(read_error) => { return Err(KeyFileLoadError::ReadError(read_error)); } } match Json::from_str(&buf) { Ok(json) => match KeyFileContent::from_json(&json) { Ok(key_file_content) => Ok(key_file_content), - Err(parse_error) => Err(KeyLoadError::FileParseError(parse_error)) + Err(parse_error) => Err(KeyFileLoadError::ParseError(parse_error)) }, - Err(_) => Err(KeyLoadError::FileParseError(KeyFileParseError::InvalidJson)) + Err(_) => Err(KeyFileLoadError::ParseError(KeyFileParseError::InvalidJson)) } } } @@ -1074,7 +1072,7 @@ mod specs { } #[test] - fn csn_store_10_keys() { + fn can_store_10_keys() { let temp_path = RandomTempPath::create_dir(); let mut directory = KeyDirectory::new(&temp_path.as_path()); diff --git a/util/src/keys/mod.rs b/util/src/keys/mod.rs new file mode 100644 index 000000000..d7ffdb0dd --- /dev/null +++ b/util/src/keys/mod.rs @@ -0,0 +1,19 @@ +// 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 . + +//! Key management module + +pub mod directory; diff --git a/util/src/lib.rs b/util/src/lib.rs index 59e9b966c..d4f972800 100644 --- a/util/src/lib.rs +++ b/util/src/lib.rs @@ -141,6 +141,7 @@ pub mod io; pub mod network; pub mod log; pub mod panics; +pub mod keys; pub use common::*; pub use misc::*; @@ -161,3 +162,6 @@ pub use semantic_version::*; pub use network::*; pub use io::*; pub use log::*; + +#[cfg(test)] +mod tests; diff --git a/util/src/tests/helpers.rs b/util/src/tests/helpers.rs new file mode 100644 index 000000000..fee3d2cbb --- /dev/null +++ b/util/src/tests/helpers.rs @@ -0,0 +1,31 @@ +use common::*; +use std::path::PathBuf; +use std::fs::{remove_dir_all}; +use std::env; + +pub struct RandomTempPath { + path: PathBuf +} + +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 + } +} + +impl Drop for RandomTempPath { + fn drop(&mut self) { + if let Err(e) = remove_dir_all(self.as_path()) { + panic!("failed to remove temp directory, probably something failed to destroyed ({})", e); + } + } +} diff --git a/util/src/tests/mod.rs b/util/src/tests/mod.rs new file mode 100644 index 000000000..1630fabcd --- /dev/null +++ b/util/src/tests/mod.rs @@ -0,0 +1 @@ +pub mod helpers;