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
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! 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<String>),
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<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,
crypto: KeyFileCrypto,
id: Uuid
/// holds cypher and decrypt function settings
pub crypto: KeyFileCrypto,
/// identifier
pub id: Uuid
}
struct KeyDirectory {
cache: HashMap<Uuid, KeyFileContent>,
path: Path,
#[derive(Debug)]
enum CryptoParseError {
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)]
enum KeyLoadError {
NotFound,
InvalidEncoding,
FileTooLarge(OutOfBounds<u64>),
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 {
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<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()) {
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<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)]
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);
}
}

View File

@ -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;

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 {
&self.path
}