documentation effort

This commit is contained in:
Nikolay Volf 2016-02-12 18:39:47 +03:00
parent 1c57214786
commit f198e53891
3 changed files with 270 additions and 138 deletions

View File

@ -14,19 +14,18 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! SecretStore //! Keys Directory
//! module for managing key files, decrypting and encrypting arbitrary data
use common::*; use common::*;
use std::path::{PathBuf}; use std::path::{PathBuf};
const CURRENT_DECLARED_VERSION: u64 = 3; const CURRENT_DECLARED_VERSION: u64 = 3;
const MAX_KEY_FILE_LEN: u64 = 1024 * 80; const MAX_KEY_FILE_LEN: u64 = 1024 * 80;
/// Cipher type (currently only aes-128-ctr)
#[derive(PartialEq, Debug)] #[derive(PartialEq, Debug)]
enum CryptoCipherType { pub enum CryptoCipherType {
// aes-128-ctr with 128-bit initialisation vector(iv) /// aes-128-ctr with 128-bit initialisation vector(iv)
Aes128Ctr(U128) Aes128Ctr(U128)
} }
@ -35,23 +34,25 @@ enum KeyFileVersion {
V3(u64) V3(u64)
} }
/// key generator function
#[derive(PartialEq, Debug)] #[derive(PartialEq, Debug)]
enum Pbkdf2CryptoFunction { pub enum Pbkdf2CryptoFunction {
/// keyed-hash generator (HMAC-256)
HMacSha256 HMacSha256
} }
#[allow(non_snake_case)] #[allow(non_snake_case)]
// Kdf of type `Pbkdf2` /// Kdf of type `Pbkdf2`
// https://en.wikipedia.org/wiki/PBKDF2 /// https://en.wikipedia.org/wiki/PBKDF2
struct KdfPbkdf2Params { pub struct KdfPbkdf2Params {
// desired length of the derived key, in octets /// desired length of the derived key, in octets
dkLen: u32, pub dkLen: u32,
// cryptographic salt /// cryptographic salt
salt: H256, pub salt: H256,
// number of iterations for derived key /// number of iterations for derived key
c: u32, pub c: u32,
// pseudo-random 2-parameters function /// pseudo-random 2-parameters function
prf: Pbkdf2CryptoFunction pub prf: Pbkdf2CryptoFunction
} }
#[derive(Debug)] #[derive(Debug)]
@ -94,25 +95,24 @@ impl KdfPbkdf2Params {
} }
#[allow(non_snake_case)] #[allow(non_snake_case)]
// Kdf of type `Scrypt` /// Kdf of type `Scrypt`
// https://en.wikipedia.org/wiki/Scrypt /// https://en.wikipedia.org/wiki/Scrypt
struct KdfScryptParams { pub struct KdfScryptParams {
// desired length of the derived key, in octets /// desired length of the derived key, in octets
dkLen: u32, pub dkLen: u32,
// parallelization /// parallelization
p: u32, pub p: u32,
// cpu cost /// cpu cost
n: u32, pub n: u32,
// TODO: comment /// TODO: comment
r: u32, pub r: u32,
// cryptographic salt /// cryptographic salt
salt: H256, pub salt: H256,
} }
#[derive(Debug)] #[derive(Debug)]
enum ScryptParseError { enum ScryptParseError {
InvalidParameter(&'static str), InvalidParameter(&'static str),
InvalidPrf(Mismatch<String>),
InvalidSaltFormat(UtilError), InvalidSaltFormat(UtilError),
MissingParameter(&'static str), 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), Pbkdf2(KdfPbkdf2Params),
/// Scrypt password-based key derivation function
/// https://en.wikipedia.org/wiki/Scrypt
Scrypt(KdfScryptParams) Scrypt(KdfScryptParams)
} }
struct KeyFileCrypto { /// Encrypted password or other arbitrary message
cipher_type: CryptoCipherType, /// with settings for password derived key generator for decrypting content
cipher_text: Bytes, pub struct KeyFileCrypto {
kdf: KeyFileKdf, pub cipher_type: CryptoCipherType,
pub cipher_text: Bytes,
pub kdf: KeyFileKdf,
} }
impl KeyFileCrypto { impl KeyFileCrypto {
@ -229,38 +236,185 @@ impl KeyFileCrypto {
Json::Object(map) 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<Uuid, UtilError> {
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, version: KeyFileVersion,
crypto: KeyFileCrypto, /// holds cypher and decrypt function settings
id: Uuid pub crypto: KeyFileCrypto,
/// identifier
pub id: Uuid
} }
struct KeyDirectory { #[derive(Debug)]
cache: HashMap<Uuid, KeyFileContent>, enum CryptoParseError {
path: Path, NoCipherText,
NoCipherType,
InvalidJsonFormat,
InvalidKdfType(Mismatch<String>),
InvalidCipherType(Mismatch<String>),
NoInitialVector,
NoCipherParameters,
InvalidInitialVector(FromHexError),
NoKdfType,
Scrypt(ScryptParseError),
KdfPbkdf2(Pbkdf2ParseError)
}
#[derive(Debug)]
enum KeyFileParseError {
InvalidVersion,
UnsupportedVersion(OutOfBounds<u64>),
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<KeyFileContent, KeyFileParseError> {
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)] #[derive(Debug)]
enum KeyLoadError { enum KeyLoadError {
NotFound,
InvalidEncoding, InvalidEncoding,
FileTooLarge(OutOfBounds<u64>), FileTooLarge(OutOfBounds<u64>),
FileParseError(KeyFileParseError), 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<Uuid, KeyFileContent>,
cache_usage: VecDeque<Uuid>,
} }
impl KeyDirectory { impl KeyDirectory {
fn key_path(&self, id: &Uuid) -> PathBuf { /// Initializes new cache directory context with a given `path`
let mut path = self.path.to_path_buf(); pub fn new(path: &Path) -> KeyDirectory {
path.push(&id); KeyDirectory {
path 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 mut file = try!(fs::File::create(self.key_path(&key_file.id)));
let json = key_file.to_json(); let json = key_file.to_json();
@ -272,28 +426,40 @@ impl KeyDirectory {
Ok(()) Ok(())
} }
fn get(&mut self, id: &Uuid) -> Option<&KeyFileContent> { /// returns key given by id if corresponding file exists and no load error occured
let path = { /// warns if any error occured during the key loading
let mut path = self.path.to_path_buf(); pub fn get(&mut self, id: &Uuid) -> Option<&KeyFileContent> {
path.push(&id); let path = self.key_path(id);
path
};
Some(self.cache.entry(id.to_owned()).or_insert( 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, 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<KeyFileContent, KeyLoadError> { /// 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<KeyFileContent, KeyLoadError> {
match fs::File::open(path.clone()) { match fs::File::open(path.clone()) {
Ok(mut open_file) => { Ok(mut open_file) => {
match open_file.metadata() { match open_file.metadata() {
Ok(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(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()) }, 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))
} }
}, },
@ -315,90 +481,31 @@ impl KeyDirectory {
Ok(key_file_content) => Ok(key_file_content), Ok(key_file_content) => Ok(key_file_content),
Err(parse_error) => Err(KeyLoadError::FileParseError(parse_error)) 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<String>),
InvalidCipherType(Mismatch<String>),
NoInitialVector,
NoCipherParameters,
InvalidInitialVector(FromHexError),
NoKdfType,
NoKdfParams,
Scrypt(ScryptParseError),
KdfPbkdf2(Pbkdf2ParseError)
}
#[derive(Debug)]
enum KeyFileParseError {
InvalidVersion,
UnsupportedVersion(OutOfBounds<u64>),
InvalidJsonFormat,
InvalidIdentifier,
NoCryptoSection,
Crypto(CryptoParseError),
}
impl KeyFileContent {
fn from_json(json: &Json) -> Result<KeyFileContent, KeyFileParseError> {
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)] #[cfg(test)]
mod tests { mod tests {
use super::{KeyFileContent, KeyFileVersion, KeyFileKdf, KeyFileParseError, CryptoParseError}; use super::{KeyFileContent, KeyFileVersion, KeyFileKdf, KeyFileParseError, CryptoParseError, uuid_from_string, uuid_to_string};
use common::*; 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] #[test]
fn can_read_keyfile() { fn can_read_keyfile() {
let json = Json::from_str( let json = Json::from_str(
@ -461,7 +568,7 @@ mod tests {
match KeyFileContent::from_json(&json) { match KeyFileContent::from_json(&json) {
Ok(key_file) => { Ok(key_file) => {
match key_file.crypto.kdf { match key_file.crypto.kdf {
KeyFileKdf::Scrypt(scrypt_params) => {}, KeyFileKdf::Scrypt(_) => {},
_ => { panic!("expected kdf params of crypto to be of scrypt type" ); } _ => { 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);
}
}

View File

@ -101,6 +101,7 @@ pub mod spec;
pub mod transaction; pub mod transaction;
pub mod views; pub mod views;
pub mod receipt; pub mod receipt;
pub mod keys_directory;
mod common; mod common;
mod basic_types; mod basic_types;
@ -123,7 +124,6 @@ mod substate;
mod executive; mod executive;
mod externalities; mod externalities;
mod verification; mod verification;
mod secret_store;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;

View File

@ -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 { pub fn as_path(&self) -> &PathBuf {
&self.path &self.path
} }