2016-02-10 23:06:35 +01:00
|
|
|
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
|
|
|
// This file is part of Parity.
|
|
|
|
|
|
|
|
// Parity is free software: you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU General Public License as published by
|
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
// (at your option) any later version.
|
|
|
|
|
|
|
|
// Parity is distributed in the hope that it will be useful,
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
// GNU General Public License for more details.
|
|
|
|
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
|
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
2016-02-12 16:39:47 +01:00
|
|
|
//! Keys Directory
|
2016-02-10 23:06:35 +01:00
|
|
|
|
|
|
|
use common::*;
|
2016-02-11 23:43:37 +01:00
|
|
|
use std::path::{PathBuf};
|
2016-02-10 23:06:35 +01:00
|
|
|
|
2016-02-11 16:25:00 +01:00
|
|
|
const CURRENT_DECLARED_VERSION: u64 = 3;
|
2016-02-11 17:48:47 +01:00
|
|
|
const MAX_KEY_FILE_LEN: u64 = 1024 * 80;
|
2016-02-12 23:12:32 +01:00
|
|
|
const MAX_CACHE_USAGE_TRACK: usize = 128;
|
2016-02-11 17:48:47 +01:00
|
|
|
|
2016-02-12 16:39:47 +01:00
|
|
|
/// Cipher type (currently only aes-128-ctr)
|
2016-02-12 21:27:09 +01:00
|
|
|
#[derive(PartialEq, Debug, Clone)]
|
2016-02-12 16:39:47 +01:00
|
|
|
pub enum CryptoCipherType {
|
|
|
|
/// aes-128-ctr with 128-bit initialisation vector(iv)
|
2016-02-16 09:30:22 +01:00
|
|
|
Aes128Ctr(H128)
|
2016-02-10 23:06:35 +01:00
|
|
|
}
|
|
|
|
|
2016-02-12 21:27:09 +01:00
|
|
|
#[derive(PartialEq, Debug, Clone)]
|
2016-02-10 23:06:35 +01:00
|
|
|
enum KeyFileVersion {
|
2016-02-11 01:32:44 +01:00
|
|
|
V3(u64)
|
2016-02-10 23:06:35 +01:00
|
|
|
}
|
|
|
|
|
2016-02-12 16:39:47 +01:00
|
|
|
/// key generator function
|
2016-02-12 21:27:09 +01:00
|
|
|
#[derive(PartialEq, Debug, Clone)]
|
2016-02-12 16:39:47 +01:00
|
|
|
pub enum Pbkdf2CryptoFunction {
|
|
|
|
/// keyed-hash generator (HMAC-256)
|
2016-02-10 23:06:35 +01:00
|
|
|
HMacSha256
|
|
|
|
}
|
|
|
|
|
2016-02-12 21:27:09 +01:00
|
|
|
#[derive(Clone)]
|
2016-02-12 16:39:47 +01:00
|
|
|
/// Kdf of type `Pbkdf2`
|
|
|
|
/// https://en.wikipedia.org/wiki/PBKDF2
|
|
|
|
pub struct KdfPbkdf2Params {
|
|
|
|
/// desired length of the derived key, in octets
|
2016-02-15 16:01:52 +01:00
|
|
|
pub dk_len: u32,
|
2016-02-12 16:39:47 +01:00
|
|
|
/// cryptographic salt
|
|
|
|
pub salt: H256,
|
|
|
|
/// number of iterations for derived key
|
|
|
|
pub c: u32,
|
|
|
|
/// pseudo-random 2-parameters function
|
|
|
|
pub prf: Pbkdf2CryptoFunction
|
2016-02-10 23:06:35 +01:00
|
|
|
}
|
|
|
|
|
2016-02-11 01:32:44 +01:00
|
|
|
#[derive(Debug)]
|
2016-02-11 02:22:59 +01:00
|
|
|
enum Pbkdf2ParseError {
|
2016-02-11 14:17:38 +01:00
|
|
|
InvalidParameter(&'static str),
|
|
|
|
InvalidPrf(Mismatch<String>),
|
|
|
|
InvalidSaltFormat(UtilError),
|
|
|
|
MissingParameter(&'static str),
|
2016-02-11 01:32:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
impl KdfPbkdf2Params {
|
2016-02-12 10:52:42 +01:00
|
|
|
fn from_json(json: &BTreeMap<String, Json>) -> Result<KdfPbkdf2Params, Pbkdf2ParseError> {
|
2016-02-11 02:22:59 +01:00
|
|
|
Ok(KdfPbkdf2Params{
|
2016-02-11 14:17:38 +01:00
|
|
|
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")); },
|
|
|
|
},
|
2016-02-15 16:01:52 +01:00
|
|
|
dk_len: try!(try!(json.get("dklen").ok_or(Pbkdf2ParseError::MissingParameter("dklen"))).as_u64().ok_or(Pbkdf2ParseError::InvalidParameter("dkLen"))) as u32,
|
2016-02-11 14:17:38 +01:00
|
|
|
c: try!(try!(json.get("c").ok_or(Pbkdf2ParseError::MissingParameter("c"))).as_u64().ok_or(Pbkdf2ParseError::InvalidParameter("c"))) as u32,
|
2016-02-11 02:22:59 +01:00
|
|
|
})
|
2016-02-11 01:32:44 +01:00
|
|
|
}
|
2016-02-11 16:25:00 +01:00
|
|
|
|
|
|
|
fn to_json(&self) -> Json {
|
|
|
|
let mut map = BTreeMap::new();
|
2016-02-15 16:01:52 +01:00
|
|
|
map.insert("dklen".to_owned(), json_from_u32(self.dk_len));
|
2016-02-11 16:25:00 +01:00
|
|
|
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)
|
|
|
|
}
|
2016-02-11 01:32:44 +01:00
|
|
|
}
|
|
|
|
|
2016-02-12 21:27:09 +01:00
|
|
|
#[derive(Clone)]
|
2016-02-15 14:21:45 +01:00
|
|
|
/// Kdf of type `Scrypt`.
|
2016-02-12 16:39:47 +01:00
|
|
|
/// https://en.wikipedia.org/wiki/Scrypt
|
|
|
|
pub struct KdfScryptParams {
|
2016-02-15 14:21:45 +01:00
|
|
|
/// Desired length of the derived key, in octets.
|
2016-02-15 16:01:52 +01:00
|
|
|
pub dk_len: u32,
|
2016-02-15 14:21:45 +01:00
|
|
|
/// Parallelization parameter.
|
2016-02-12 16:39:47 +01:00
|
|
|
pub p: u32,
|
2016-02-15 14:21:45 +01:00
|
|
|
/// CPU/memory cost parameter.
|
2016-02-12 16:39:47 +01:00
|
|
|
pub n: u32,
|
|
|
|
/// TODO: comment
|
|
|
|
pub r: u32,
|
2016-02-15 14:21:45 +01:00
|
|
|
/// Cryptographic salt.
|
2016-02-12 16:39:47 +01:00
|
|
|
pub salt: H256,
|
2016-02-10 23:06:35 +01:00
|
|
|
}
|
|
|
|
|
2016-02-11 01:32:44 +01:00
|
|
|
#[derive(Debug)]
|
|
|
|
enum ScryptParseError {
|
2016-02-11 14:17:38 +01:00
|
|
|
InvalidParameter(&'static str),
|
|
|
|
InvalidSaltFormat(UtilError),
|
|
|
|
MissingParameter(&'static str),
|
2016-02-11 01:32:44 +01:00
|
|
|
}
|
|
|
|
|
2016-02-11 16:25:00 +01:00
|
|
|
fn json_from_u32(number: u32) -> Json { Json::U64(number as u64) }
|
|
|
|
|
2016-02-11 01:32:44 +01:00
|
|
|
impl KdfScryptParams {
|
2016-02-12 10:52:42 +01:00
|
|
|
fn from_json(json: &BTreeMap<String, Json>) -> Result<KdfScryptParams, ScryptParseError> {
|
2016-02-11 01:32:44 +01:00
|
|
|
Ok(KdfScryptParams{
|
2016-02-11 14:17:38 +01:00
|
|
|
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)); },
|
|
|
|
}
|
|
|
|
},
|
2016-02-15 16:01:52 +01:00
|
|
|
dk_len: try!(try!(json.get("dklen").ok_or(ScryptParseError::MissingParameter("dklen"))).as_u64().ok_or(ScryptParseError::InvalidParameter("dkLen"))) as u32,
|
2016-02-11 14:17:38 +01:00
|
|
|
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,
|
2016-02-11 01:32:44 +01:00
|
|
|
})
|
|
|
|
}
|
2016-02-11 16:25:00 +01:00
|
|
|
|
|
|
|
fn to_json(&self) -> Json {
|
|
|
|
let mut map = BTreeMap::new();
|
2016-02-15 16:01:52 +01:00
|
|
|
map.insert("dklen".to_owned(), json_from_u32(self.dk_len));
|
2016-02-11 16:25:00 +01:00
|
|
|
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)
|
|
|
|
}
|
2016-02-11 01:32:44 +01:00
|
|
|
}
|
2016-02-10 23:06:35 +01:00
|
|
|
|
2016-02-12 21:27:09 +01:00
|
|
|
#[derive(Clone)]
|
2016-02-15 14:21:45 +01:00
|
|
|
/// Settings for password derived key geberator function.
|
2016-02-12 16:39:47 +01:00
|
|
|
pub enum KeyFileKdf {
|
2016-02-15 14:21:45 +01:00
|
|
|
/// Password-Based Key Derivation Function 2 (PBKDF2) type.
|
2016-02-12 16:39:47 +01:00
|
|
|
/// https://en.wikipedia.org/wiki/PBKDF2
|
2016-02-11 01:32:44 +01:00
|
|
|
Pbkdf2(KdfPbkdf2Params),
|
2016-02-15 14:21:45 +01:00
|
|
|
/// Scrypt password-based key derivation function.
|
2016-02-12 16:39:47 +01:00
|
|
|
/// https://en.wikipedia.org/wiki/Scrypt
|
2016-02-11 01:32:44 +01:00
|
|
|
Scrypt(KdfScryptParams)
|
|
|
|
}
|
|
|
|
|
2016-02-12 21:27:09 +01:00
|
|
|
#[derive(Clone)]
|
2016-02-12 16:39:47 +01:00
|
|
|
/// Encrypted password or other arbitrary message
|
2016-02-15 14:21:45 +01:00
|
|
|
/// with settings for password derived key generator for decrypting content.
|
2016-02-12 16:39:47 +01:00
|
|
|
pub struct KeyFileCrypto {
|
2016-02-15 14:21:45 +01:00
|
|
|
/// Cipher type.
|
2016-02-12 16:39:47 +01:00
|
|
|
pub cipher_type: CryptoCipherType,
|
2016-02-15 14:21:45 +01:00
|
|
|
/// Cipher text (encrypted message).
|
2016-02-12 16:39:47 +01:00
|
|
|
pub cipher_text: Bytes,
|
2016-02-15 14:21:45 +01:00
|
|
|
/// Password derived key generator function settings.
|
2016-02-12 16:39:47 +01:00
|
|
|
pub kdf: KeyFileKdf,
|
2016-02-16 17:19:32 +01:00
|
|
|
/// Mac
|
|
|
|
pub mac: H256
|
2016-02-11 01:32:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
impl KeyFileCrypto {
|
2016-02-12 10:52:42 +01:00
|
|
|
fn from_json(json: &Json) -> Result<KeyFileCrypto, CryptoParseError> {
|
2016-02-11 01:32:44 +01:00
|
|
|
let as_object = match json.as_object() {
|
|
|
|
None => { return Err(CryptoParseError::InvalidJsonFormat); }
|
|
|
|
Some(obj) => obj
|
|
|
|
};
|
|
|
|
|
2016-02-12 18:09:24 +01:00
|
|
|
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() })); }
|
2016-02-11 01:32:44 +01:00
|
|
|
Some("aes-128-ctr") => CryptoCipherType::Aes128Ctr(
|
2016-02-12 18:09:24 +01:00
|
|
|
match try!(as_object.get("cipherparams").ok_or(CryptoParseError::NoCipherParameters)).as_object() {
|
2016-02-11 01:32:44 +01:00
|
|
|
None => { return Err(CryptoParseError::NoCipherParameters); },
|
2016-02-16 09:30:22 +01:00
|
|
|
Some(cipher_param) => match H128::from_str(match cipher_param["iv"].as_string() {
|
2016-02-11 02:22:59 +01:00
|
|
|
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)); }
|
|
|
|
}
|
2016-02-11 01:32:44 +01:00
|
|
|
}
|
|
|
|
),
|
2016-02-11 02:22:59 +01:00
|
|
|
Some(other_cipher_type) => {
|
2016-02-11 01:32:44 +01:00
|
|
|
return Err(CryptoParseError::InvalidCipherType(
|
|
|
|
Mismatch { expected: "aes-128-ctr".to_owned(), found: other_cipher_type.to_owned() }));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-02-12 18:09:24 +01:00
|
|
|
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()) {
|
2016-02-11 01:32:44 +01:00
|
|
|
(None, _) => { return Err(CryptoParseError::NoKdfType); },
|
|
|
|
(Some("scrypt"), Some(kdf_params)) =>
|
2016-02-12 10:52:42 +01:00
|
|
|
match KdfScryptParams::from_json(kdf_params) {
|
2016-02-11 02:22:59 +01:00
|
|
|
Err(scrypt_params_error) => { return Err(CryptoParseError::Scrypt(scrypt_params_error)); },
|
|
|
|
Ok(scrypt_params) => KeyFileKdf::Scrypt(scrypt_params)
|
2016-02-11 01:32:44 +01:00
|
|
|
},
|
|
|
|
(Some("pbkdf2"), Some(kdf_params)) =>
|
2016-02-12 10:52:42 +01:00
|
|
|
match KdfPbkdf2Params::from_json(kdf_params) {
|
2016-02-11 16:25:00 +01:00
|
|
|
Err(pbkdf2_params_error) => { return Err(CryptoParseError::KdfPbkdf2(pbkdf2_params_error)); },
|
|
|
|
Ok(pbkdf2_params) => KeyFileKdf::Pbkdf2(pbkdf2_params)
|
2016-02-11 01:32:44 +01:00
|
|
|
},
|
|
|
|
(Some(other_kdf), _) => {
|
|
|
|
return Err(CryptoParseError::InvalidKdfType(
|
2016-02-11 02:22:59 +01:00
|
|
|
Mismatch { expected: "pbkdf2/scrypt".to_owned(), found: other_kdf.to_owned()}));
|
2016-02-11 01:32:44 +01:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-02-16 17:19:32 +01:00
|
|
|
let cipher_text = match try!(as_object.get("ciphertext").ok_or(CryptoParseError::NoCipherText)).as_string() {
|
|
|
|
None => { return Err(CryptoParseError::InvalidCipherText); }
|
2016-02-11 01:32:44 +01:00
|
|
|
Some(text) => text
|
|
|
|
};
|
|
|
|
|
2016-02-16 17:19:32 +01:00
|
|
|
let mac: H256 = match try!(as_object.get("mac").ok_or(CryptoParseError::NoMac)).as_string() {
|
|
|
|
None => { return Err(CryptoParseError::InvalidMacFormat(None)) },
|
|
|
|
Some(salt_value) => match H256::from_str(salt_value) {
|
|
|
|
Ok(salt_hex_value) => salt_hex_value,
|
|
|
|
Err(from_hex_error) => { return Err(CryptoParseError::InvalidMacFormat(Some(from_hex_error))); },
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-02-11 01:32:44 +01:00
|
|
|
Ok(KeyFileCrypto {
|
2016-02-17 00:32:16 +01:00
|
|
|
cipher_text: match FromHex::from_hex(cipher_text) { Ok(bytes) => bytes, Err(_) => { return Err(CryptoParseError::InvalidCipherText); } },
|
2016-02-11 01:32:44 +01:00
|
|
|
cipher_type: cipher_type,
|
|
|
|
kdf: kdf,
|
2016-02-16 17:19:32 +01:00
|
|
|
mac: mac,
|
2016-02-11 01:32:44 +01:00
|
|
|
})
|
|
|
|
}
|
2016-02-11 16:25:00 +01:00
|
|
|
|
|
|
|
fn to_json(&self) -> Json {
|
|
|
|
let mut map = BTreeMap::new();
|
2016-02-12 18:09:24 +01:00
|
|
|
match self.cipher_type {
|
2016-02-16 09:30:22 +01:00
|
|
|
CryptoCipherType::Aes128Ctr(ref iv) => {
|
2016-02-12 18:09:24 +01:00
|
|
|
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(
|
2016-02-11 16:25:00 +01:00
|
|
|
self.cipher_text.iter().map(|b| format!("{:02x}", b)).collect::<Vec<String>>().join("")));
|
2016-02-12 18:09:24 +01:00
|
|
|
|
|
|
|
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 {
|
2016-02-11 16:25:00 +01:00
|
|
|
KeyFileKdf::Pbkdf2(ref pbkdf2_params) => pbkdf2_params.to_json(),
|
|
|
|
KeyFileKdf::Scrypt(ref scrypt_params) => scrypt_params.to_json()
|
|
|
|
});
|
|
|
|
|
2016-02-16 17:19:32 +01:00
|
|
|
map.insert("mac".to_owned(), Json::String(format!("{:?}", self.mac)));
|
|
|
|
|
2016-02-11 16:25:00 +01:00
|
|
|
Json::Object(map)
|
|
|
|
}
|
2016-02-12 16:39:47 +01:00
|
|
|
|
2016-02-15 14:21:45 +01:00
|
|
|
/// 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.
|
2016-02-16 18:52:36 +01:00
|
|
|
pub fn new_pbkdf2(cipher_text: Bytes, iv: H128, salt: H256, mac: H256, c: u32, dk_len: u32) -> KeyFileCrypto {
|
2016-02-12 16:39:47 +01:00
|
|
|
KeyFileCrypto {
|
|
|
|
cipher_type: CryptoCipherType::Aes128Ctr(iv),
|
|
|
|
cipher_text: cipher_text,
|
|
|
|
kdf: KeyFileKdf::Pbkdf2(KdfPbkdf2Params {
|
2016-02-15 16:01:52 +01:00
|
|
|
dk_len: dk_len,
|
2016-02-12 16:39:47 +01:00
|
|
|
salt: salt,
|
|
|
|
c: c,
|
|
|
|
prf: Pbkdf2CryptoFunction::HMacSha256
|
|
|
|
}),
|
2016-02-16 18:52:36 +01:00
|
|
|
mac: mac,
|
2016-02-12 16:39:47 +01:00
|
|
|
}
|
|
|
|
}
|
2016-02-11 01:32:44 +01:00
|
|
|
}
|
|
|
|
|
2016-02-12 16:39:47 +01:00
|
|
|
/// Universally unique identifier
|
|
|
|
pub type Uuid = H128;
|
2016-02-11 01:32:44 +01:00
|
|
|
|
2016-02-12 16:39:47 +01:00
|
|
|
fn new_uuid() -> Uuid {
|
|
|
|
H128::random()
|
2016-02-10 23:06:35 +01:00
|
|
|
}
|
2016-02-11 01:32:44 +01:00
|
|
|
|
2016-02-12 16:39:47 +01:00
|
|
|
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())
|
2016-02-11 17:48:47 +01:00
|
|
|
}
|
|
|
|
|
2016-02-12 16:39:47 +01:00
|
|
|
fn uuid_from_string(s: &str) -> Result<Uuid, UtilError> {
|
|
|
|
let parts: Vec<&str> = s.split("-").collect();
|
|
|
|
if parts.len() != 5 { return Err(UtilError::BadSize); }
|
2016-02-11 17:48:47 +01:00
|
|
|
|
2016-02-12 16:39:47 +01:00
|
|
|
let mut uuid = H128::zero();
|
2016-02-11 17:48:47 +01:00
|
|
|
|
2016-02-12 16:39:47 +01:00
|
|
|
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])));
|
2016-02-11 17:48:47 +01:00
|
|
|
|
2016-02-12 16:39:47 +01:00
|
|
|
Ok(uuid)
|
|
|
|
}
|
2016-02-11 23:43:37 +01:00
|
|
|
|
2016-02-12 21:27:09 +01:00
|
|
|
|
|
|
|
#[derive(Clone)]
|
2016-02-12 16:39:47 +01:00
|
|
|
/// Stored key file struct with encrypted message (cipher_text)
|
|
|
|
/// also contains password derivation function settings (PBKDF2/Scrypt)
|
|
|
|
pub struct KeyFileContent {
|
|
|
|
version: KeyFileVersion,
|
2016-02-15 14:21:45 +01:00
|
|
|
/// Holds cypher and decrypt function settings.
|
2016-02-12 16:39:47 +01:00
|
|
|
pub crypto: KeyFileCrypto,
|
2016-02-15 14:21:45 +01:00
|
|
|
/// The identifier.
|
2016-02-12 16:39:47 +01:00
|
|
|
pub id: Uuid
|
2016-02-11 17:48:47 +01:00
|
|
|
}
|
|
|
|
|
2016-02-11 01:32:44 +01:00
|
|
|
#[derive(Debug)]
|
|
|
|
enum CryptoParseError {
|
2016-02-16 17:19:32 +01:00
|
|
|
InvalidMacFormat(Option<UtilError>),
|
|
|
|
NoMac,
|
2016-02-11 02:22:59 +01:00
|
|
|
NoCipherText,
|
2016-02-16 17:19:32 +01:00
|
|
|
InvalidCipherText,
|
2016-02-11 02:22:59 +01:00
|
|
|
NoCipherType,
|
2016-02-11 01:32:44 +01:00
|
|
|
InvalidJsonFormat,
|
|
|
|
InvalidKdfType(Mismatch<String>),
|
|
|
|
InvalidCipherType(Mismatch<String>),
|
2016-02-11 02:22:59 +01:00
|
|
|
NoInitialVector,
|
|
|
|
NoCipherParameters,
|
2016-02-16 09:30:22 +01:00
|
|
|
InvalidInitialVector(UtilError),
|
2016-02-12 18:09:24 +01:00
|
|
|
NoKdf,
|
2016-02-11 01:32:44 +01:00
|
|
|
NoKdfType,
|
|
|
|
Scrypt(ScryptParseError),
|
2016-02-11 02:22:59 +01:00
|
|
|
KdfPbkdf2(Pbkdf2ParseError)
|
2016-02-11 01:32:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
enum KeyFileParseError {
|
|
|
|
InvalidVersion,
|
|
|
|
UnsupportedVersion(OutOfBounds<u64>),
|
|
|
|
InvalidJsonFormat,
|
2016-02-12 21:27:09 +01:00
|
|
|
InvalidJson,
|
2016-02-11 14:17:38 +01:00
|
|
|
InvalidIdentifier,
|
2016-02-11 01:32:44 +01:00
|
|
|
NoCryptoSection,
|
|
|
|
Crypto(CryptoParseError),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl KeyFileContent {
|
2016-02-15 14:21:45 +01:00
|
|
|
/// New stored key file struct with encrypted message (cipher_text)
|
2016-02-12 16:39:47 +01:00
|
|
|
/// also contains password derivation function settings (PBKDF2/Scrypt)
|
2016-02-15 14:21:45 +01:00
|
|
|
/// to decrypt cipher_text given the password is provided.
|
2016-02-12 16:39:47 +01:00
|
|
|
pub fn new(crypto: KeyFileCrypto) -> KeyFileContent {
|
|
|
|
KeyFileContent {
|
|
|
|
id: new_uuid(),
|
|
|
|
version: KeyFileVersion::V3(3),
|
|
|
|
crypto: crypto
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-15 14:21:45 +01:00
|
|
|
/// Returns key file version if it is known.
|
2016-02-12 18:09:24 +01:00
|
|
|
pub fn version(&self) -> Option<u64> {
|
|
|
|
match self.version {
|
|
|
|
KeyFileVersion::V3(declared) => Some(declared)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-12 10:52:42 +01:00
|
|
|
fn from_json(json: &Json) -> Result<KeyFileContent, KeyFileParseError> {
|
2016-02-11 01:32:44 +01:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-02-12 16:39:47 +01:00
|
|
|
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
|
|
|
|
};
|
2016-02-11 01:32:44 +01:00
|
|
|
|
|
|
|
let crypto = match as_object.get("crypto") {
|
|
|
|
None => { return Err(KeyFileParseError::NoCryptoSection); }
|
2016-02-12 10:52:42 +01:00
|
|
|
Some(crypto_json) => match KeyFileCrypto::from_json(crypto_json) {
|
2016-02-11 01:32:44 +01:00
|
|
|
Ok(crypto) => crypto,
|
|
|
|
Err(crypto_error) => { return Err(KeyFileParseError::Crypto(crypto_error)); }
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
Ok(KeyFileContent {
|
|
|
|
version: version,
|
2016-02-12 16:39:47 +01:00
|
|
|
id: id.clone(),
|
2016-02-11 01:32:44 +01:00
|
|
|
crypto: crypto
|
|
|
|
})
|
|
|
|
}
|
2016-02-11 16:25:00 +01:00
|
|
|
|
|
|
|
fn to_json(&self) -> Json {
|
|
|
|
let mut map = BTreeMap::new();
|
2016-02-12 16:39:47 +01:00
|
|
|
map.insert("id".to_owned(), Json::String(uuid_to_string(&self.id)));
|
2016-02-11 16:25:00 +01:00
|
|
|
map.insert("version".to_owned(), Json::U64(CURRENT_DECLARED_VERSION));
|
|
|
|
map.insert("crypto".to_owned(), self.crypto.to_json());
|
|
|
|
Json::Object(map)
|
|
|
|
}
|
2016-02-11 01:32:44 +01:00
|
|
|
}
|
2016-02-11 14:17:38 +01:00
|
|
|
|
2016-02-12 16:39:47 +01:00
|
|
|
#[derive(Debug)]
|
2016-02-15 16:01:52 +01:00
|
|
|
enum KeyFileLoadError {
|
|
|
|
TooLarge(OutOfBounds<u64>),
|
|
|
|
ParseError(KeyFileParseError),
|
|
|
|
ReadError(::std::io::Error),
|
2016-02-12 16:39:47 +01:00
|
|
|
}
|
|
|
|
|
2016-02-15 14:21:45 +01:00
|
|
|
/// Represents directory for saving/loading key files.
|
2016-02-12 16:39:47 +01:00
|
|
|
pub struct KeyDirectory {
|
2016-02-15 14:21:45 +01:00
|
|
|
/// Directory path for key management.
|
2016-02-12 16:39:47 +01:00
|
|
|
path: String,
|
2016-02-15 22:56:25 +01:00
|
|
|
cache: RefCell<HashMap<Uuid, KeyFileContent>>,
|
|
|
|
cache_usage: RefCell<VecDeque<Uuid>>,
|
2016-02-12 16:39:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
impl KeyDirectory {
|
|
|
|
/// Initializes new cache directory context with a given `path`
|
|
|
|
pub fn new(path: &Path) -> KeyDirectory {
|
|
|
|
KeyDirectory {
|
2016-02-15 22:56:25 +01:00
|
|
|
cache: RefCell::new(HashMap::new()),
|
2016-02-12 16:39:47 +01:00
|
|
|
path: path.to_str().expect("Initialized key directory with empty path").to_owned(),
|
2016-02-15 22:56:25 +01:00
|
|
|
cache_usage: RefCell::new(VecDeque::new()),
|
2016-02-12 16:39:47 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// saves (inserts or updates) given key
|
2016-02-12 18:09:24 +01:00
|
|
|
pub fn save(&mut self, key_file: KeyFileContent) -> Result<(Uuid), ::std::io::Error> {
|
2016-02-12 16:39:47 +01:00
|
|
|
{
|
|
|
|
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));
|
|
|
|
}
|
2016-02-15 22:56:25 +01:00
|
|
|
let mut cache = self.cache.borrow_mut();
|
2016-02-12 18:09:24 +01:00
|
|
|
let id = key_file.id.clone();
|
2016-02-15 22:56:25 +01:00
|
|
|
cache.insert(id.clone(), key_file);
|
2016-02-12 18:09:24 +01:00
|
|
|
Ok(id.clone())
|
2016-02-12 16:39:47 +01:00
|
|
|
}
|
|
|
|
|
2016-02-15 14:21:45 +01:00
|
|
|
/// Returns key given by id if corresponding file exists and no load error occured.
|
|
|
|
/// Warns if any error occured during the key loading
|
2016-02-15 22:56:25 +01:00
|
|
|
pub fn get(&self, id: &Uuid) -> Option<Ref<KeyFileContent>> {
|
2016-02-12 16:39:47 +01:00
|
|
|
let path = self.key_path(id);
|
2016-02-15 22:56:25 +01:00
|
|
|
{
|
|
|
|
let mut usage = self.cache_usage.borrow_mut();
|
|
|
|
usage.push_back(id.clone());
|
|
|
|
}
|
|
|
|
|
|
|
|
if !self.cache.borrow().contains_key(id) {
|
2016-02-12 16:39:47 +01:00
|
|
|
match KeyDirectory::load_key(&path) {
|
2016-02-15 22:56:25 +01:00
|
|
|
Ok(loaded_key) => {
|
|
|
|
self.cache.borrow_mut().insert(id.to_owned(), loaded_key);
|
|
|
|
}
|
2016-02-12 16:39:47 +01:00
|
|
|
Err(error) => {
|
|
|
|
warn!(target: "sstore", "error loading key {:?}: {:?}", id, error);
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
}
|
2016-02-15 22:56:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
Some(Ref::map(self.cache.borrow(), |c| c.get(id).expect("should be they key, we have just inserted or checked it")))
|
2016-02-12 16:39:47 +01:00
|
|
|
}
|
|
|
|
|
2016-02-15 14:21:45 +01:00
|
|
|
/// Returns current path to the directory with keys
|
2016-02-12 16:39:47 +01:00
|
|
|
pub fn path(&self) -> &str {
|
|
|
|
&self.path
|
|
|
|
}
|
|
|
|
|
2016-02-15 14:21:45 +01:00
|
|
|
/// Removes keys that never been requested during last `MAX_USAGE_TRACK` times
|
2016-02-12 23:12:32 +01:00
|
|
|
pub fn collect_garbage(&mut self) {
|
2016-02-15 22:56:25 +01:00
|
|
|
let mut cache_usage = self.cache_usage.borrow_mut();
|
|
|
|
|
|
|
|
let total_usages = cache_usage.len();
|
2016-02-12 23:12:32 +01:00
|
|
|
let untracked_usages = max(total_usages as i64 - MAX_CACHE_USAGE_TRACK as i64, 0) as usize;
|
|
|
|
if untracked_usages > 0 {
|
2016-02-15 22:56:25 +01:00
|
|
|
cache_usage.drain(..untracked_usages);
|
2016-02-12 23:12:32 +01:00
|
|
|
}
|
|
|
|
|
2016-02-15 22:56:25 +01:00
|
|
|
let mut cache = self.cache.borrow_mut();
|
|
|
|
if cache.len() <= MAX_CACHE_USAGE_TRACK { return; }
|
2016-02-12 23:12:32 +01:00
|
|
|
|
2016-02-15 22:56:25 +01:00
|
|
|
let uniqs: HashSet<&Uuid> = cache_usage.iter().collect();
|
2016-02-12 23:12:32 +01:00
|
|
|
let mut removes = HashSet::new();
|
|
|
|
|
2016-02-15 22:56:25 +01:00
|
|
|
for key in cache.keys() {
|
2016-02-12 23:12:32 +01:00
|
|
|
if !uniqs.contains(key) {
|
|
|
|
removes.insert(key.clone());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-15 22:56:25 +01:00
|
|
|
for removed_key in removes { cache.remove(&removed_key); }
|
2016-02-12 23:12:32 +01:00
|
|
|
}
|
|
|
|
|
2016-02-15 14:21:45 +01:00
|
|
|
/// Reports how many keys are currently cached.
|
2016-02-12 23:12:32 +01:00
|
|
|
pub fn cache_size(&self) -> usize {
|
2016-02-15 22:56:25 +01:00
|
|
|
self.cache.borrow().len()
|
2016-02-12 23:12:32 +01:00
|
|
|
}
|
|
|
|
|
2016-02-16 18:52:36 +01:00
|
|
|
/// Removes key file from key directory
|
|
|
|
pub fn delete(&mut self, id: &Uuid) -> Result<(), ::std::io::Error> {
|
|
|
|
let path = self.key_path(id);
|
|
|
|
|
|
|
|
if !self.cache.borrow().contains_key(id) {
|
|
|
|
return match fs::remove_file(&path) {
|
|
|
|
Ok(_) => {
|
|
|
|
self.cache.borrow_mut().remove(&id);
|
|
|
|
Ok(())
|
|
|
|
},
|
|
|
|
Err(e) => Err(e)
|
|
|
|
};
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2016-02-17 09:48:12 +01:00
|
|
|
/// Enumerates all keys in the directory
|
|
|
|
pub fn list(&self) -> Result<Vec<Uuid>, ::std::io::Error> {
|
|
|
|
let mut result = Vec::new();
|
|
|
|
for entry in try!(fs::read_dir(&self.path)) {
|
|
|
|
let entry = try!(entry);
|
|
|
|
if !try!(fs::metadata(entry.path())).is_dir() {
|
|
|
|
match entry.file_name().to_str() {
|
|
|
|
Some(ref name) => {
|
|
|
|
if let Ok(uuid) = uuid_from_string(name) { result.push(uuid); }
|
|
|
|
},
|
|
|
|
None => { continue; }
|
|
|
|
};
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(result)
|
|
|
|
}
|
|
|
|
|
2016-02-12 16:39:47 +01:00
|
|
|
fn key_path(&self, id: &Uuid) -> PathBuf {
|
|
|
|
let mut path = PathBuf::new();
|
|
|
|
path.push(self.path.clone());
|
|
|
|
path.push(uuid_to_string(&id));
|
|
|
|
path
|
|
|
|
}
|
|
|
|
|
2016-02-15 16:01:52 +01:00
|
|
|
fn load_key(path: &PathBuf) -> Result<KeyFileContent, KeyFileLoadError> {
|
2016-02-12 16:39:47 +01:00
|
|
|
match fs::File::open(path.clone()) {
|
|
|
|
Ok(mut open_file) => {
|
|
|
|
match open_file.metadata() {
|
|
|
|
Ok(metadata) =>
|
2016-02-15 16:01:52 +01:00
|
|
|
if metadata.len() > MAX_KEY_FILE_LEN { Err(KeyFileLoadError::TooLarge(OutOfBounds { min: Some(2), max: Some(MAX_KEY_FILE_LEN), found: metadata.len() })) }
|
2016-02-12 21:27:09 +01:00
|
|
|
else { KeyDirectory::load_from_file(&mut open_file) },
|
2016-02-15 16:01:52 +01:00
|
|
|
Err(read_error) => Err(KeyFileLoadError::ReadError(read_error))
|
2016-02-12 16:39:47 +01:00
|
|
|
}
|
|
|
|
},
|
2016-02-15 16:01:52 +01:00
|
|
|
Err(read_error) => Err(KeyFileLoadError::ReadError(read_error))
|
2016-02-12 16:39:47 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-15 16:01:52 +01:00
|
|
|
fn load_from_file(file: &mut fs::File) -> Result<KeyFileContent, KeyFileLoadError> {
|
2016-02-12 21:27:09 +01:00
|
|
|
let mut buf = String::new();
|
|
|
|
match file.read_to_string(&mut buf) {
|
2016-02-12 16:39:47 +01:00
|
|
|
Ok(_) => {},
|
2016-02-15 16:01:52 +01:00
|
|
|
Err(read_error) => { return Err(KeyFileLoadError::ReadError(read_error)); }
|
2016-02-12 16:39:47 +01:00
|
|
|
}
|
2016-02-12 21:27:09 +01:00
|
|
|
match Json::from_str(&buf) {
|
|
|
|
Ok(json) => match KeyFileContent::from_json(&json) {
|
|
|
|
Ok(key_file_content) => Ok(key_file_content),
|
2016-02-15 16:01:52 +01:00
|
|
|
Err(parse_error) => Err(KeyFileLoadError::ParseError(parse_error))
|
2016-02-12 16:39:47 +01:00
|
|
|
},
|
2016-02-15 16:01:52 +01:00
|
|
|
Err(_) => Err(KeyFileLoadError::ParseError(KeyFileParseError::InvalidJson))
|
2016-02-12 16:39:47 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-02-11 14:17:38 +01:00
|
|
|
#[cfg(test)]
|
2016-02-12 21:27:09 +01:00
|
|
|
mod file_tests {
|
2016-02-14 16:22:42 +01:00
|
|
|
use super::{KeyFileContent, KeyFileVersion, KeyFileKdf, KeyFileParseError, CryptoParseError, uuid_from_string, uuid_to_string, KeyFileCrypto, KdfPbkdf2Params};
|
2016-02-11 14:17:38 +01:00
|
|
|
use common::*;
|
|
|
|
|
2016-02-12 16:39:47 +01:00
|
|
|
#[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");
|
|
|
|
}
|
|
|
|
|
2016-02-11 14:17:38 +01:00
|
|
|
#[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();
|
|
|
|
|
2016-02-12 10:52:42 +01:00
|
|
|
match KeyFileContent::from_json(&json) {
|
2016-02-11 14:17:38 +01:00
|
|
|
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();
|
|
|
|
|
2016-02-12 10:52:42 +01:00
|
|
|
match KeyFileContent::from_json(&json) {
|
2016-02-11 14:17:38 +01:00
|
|
|
Ok(key_file) => {
|
|
|
|
match key_file.crypto.kdf {
|
2016-02-12 16:39:47 +01:00
|
|
|
KeyFileKdf::Scrypt(_) => {},
|
2016-02-11 14:17:38 +01:00
|
|
|
_ => { 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();
|
|
|
|
|
2016-02-12 10:52:42 +01:00
|
|
|
match KeyFileContent::from_json(&json) {
|
2016-02-11 16:25:00 +01:00
|
|
|
Ok(_) => {
|
2016-02-11 14:17:38 +01:00
|
|
|
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();
|
|
|
|
|
2016-02-12 10:52:42 +01:00
|
|
|
match KeyFileContent::from_json(&json) {
|
2016-02-11 16:25:00 +01:00
|
|
|
Ok(_) => {
|
2016-02-11 14:17:38 +01:00
|
|
|
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"
|
|
|
|
},
|
2016-02-11 16:25:00 +01:00
|
|
|
"id" : "3198bc9c-6672-5ab3-d995-4942343ae5b6",
|
2016-02-11 14:17:38 +01:00
|
|
|
"version" : 1
|
|
|
|
}
|
|
|
|
"#).unwrap();
|
|
|
|
|
2016-02-12 10:52:42 +01:00
|
|
|
match KeyFileContent::from_json(&json) {
|
2016-02-11 16:25:00 +01:00
|
|
|
Ok(_) => {
|
2016-02-11 14:17:38 +01:00
|
|
|
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); }
|
|
|
|
}
|
|
|
|
}
|
2016-02-11 16:25:00 +01:00
|
|
|
|
|
|
|
|
|
|
|
#[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();
|
|
|
|
|
2016-02-12 10:52:42 +01:00
|
|
|
match KeyFileContent::from_json(&json) {
|
2016-02-11 16:25:00 +01:00
|
|
|
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); }
|
|
|
|
}
|
|
|
|
}
|
2016-02-12 18:09:24 +01:00
|
|
|
|
2016-02-12 23:29:28 +01:00
|
|
|
#[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());
|
|
|
|
}
|
|
|
|
|
2016-02-12 18:09:24 +01:00
|
|
|
#[test]
|
|
|
|
fn can_create_key_with_new_id() {
|
|
|
|
let cipher_text: Bytes = FromHex::from_hex("a0f05555").unwrap();
|
2016-02-16 18:52:36 +01:00
|
|
|
let key = KeyFileContent::new(KeyFileCrypto::new_pbkdf2(cipher_text, H128::zero(), H256::random(), H256::random(), 32, 32));
|
2016-02-12 18:09:24 +01:00
|
|
|
assert!(!uuid_to_string(&key.id).is_empty());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn can_load_json_from_itself() {
|
|
|
|
let cipher_text: Bytes = FromHex::from_hex("aaaaaaaaaaaaaaaaaaaaaaaaaaa22222222222222222222222").unwrap();
|
2016-02-16 18:52:36 +01:00
|
|
|
let key = KeyFileContent::new(KeyFileCrypto::new_pbkdf2(cipher_text, H128::zero(), H256::random(), H256::random(), 32, 32));
|
2016-02-12 18:09:24 +01:00
|
|
|
let json = key.to_json();
|
|
|
|
|
|
|
|
let loaded_key = KeyFileContent::from_json(&json).unwrap();
|
|
|
|
|
|
|
|
assert_eq!(loaded_key.id, key.id);
|
|
|
|
}
|
2016-02-12 21:27:09 +01:00
|
|
|
|
2016-02-14 16:22:42 +01:00
|
|
|
#[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());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-14 17:09:36 +01:00
|
|
|
#[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());
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2016-02-12 21:27:09 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod directory_tests {
|
2016-02-12 23:12:32 +01:00
|
|
|
use super::{KeyDirectory, new_uuid, uuid_to_string, KeyFileContent, KeyFileCrypto, MAX_CACHE_USAGE_TRACK};
|
2016-02-12 21:27:09 +01:00
|
|
|
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());
|
2016-02-16 18:52:36 +01:00
|
|
|
let uuid = directory.save(KeyFileContent::new(KeyFileCrypto::new_pbkdf2(cipher_text, H128::zero(), H256::random(), H256::random(), 32, 32))).unwrap();
|
2016-02-12 21:27:09 +01:00
|
|
|
let path = directory.key_path(&uuid);
|
|
|
|
|
|
|
|
let key = KeyDirectory::load_key(&path).unwrap();
|
|
|
|
|
|
|
|
assert_eq!(key.id, uuid);
|
|
|
|
}
|
2016-02-12 23:12:32 +01:00
|
|
|
|
|
|
|
#[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 {
|
2016-02-16 18:52:36 +01:00
|
|
|
let key = KeyFileContent::new(KeyFileCrypto::new_pbkdf2(cipher_text.clone(), H128::zero(), H256::random(), H256::random(), 32, 32));
|
2016-02-12 23:12:32 +01:00
|
|
|
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 {
|
2016-02-16 18:52:36 +01:00
|
|
|
let key = KeyFileContent::new(KeyFileCrypto::new_pbkdf2(cipher_text.clone(), H128::zero(), H256::random(), H256::random(), 32, 32));
|
2016-02-12 23:12:32 +01:00
|
|
|
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())
|
|
|
|
}
|
2016-02-11 14:17:38 +01:00
|
|
|
}
|
2016-02-12 16:39:47 +01:00
|
|
|
|
|
|
|
#[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);
|
|
|
|
}
|
2016-02-12 18:09:24 +01:00
|
|
|
|
|
|
|
#[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());
|
|
|
|
|
2016-02-16 18:52:36 +01:00
|
|
|
let uuid = directory.save(KeyFileContent::new(KeyFileCrypto::new_pbkdf2(cipher_text, H128::zero(), H256::random(), H256::random(), 32, 32)));
|
2016-02-12 18:09:24 +01:00
|
|
|
|
|
|
|
assert!(uuid.is_ok());
|
|
|
|
}
|
2016-02-12 21:27:09 +01:00
|
|
|
|
|
|
|
#[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());
|
2016-02-16 18:52:36 +01:00
|
|
|
let uuid = directory.save(KeyFileContent::new(KeyFileCrypto::new_pbkdf2(cipher_text.clone(), H128::zero(), H256::random(), H256::random(), 32, 32))).unwrap();
|
2016-02-12 21:27:09 +01:00
|
|
|
|
|
|
|
let key = directory.get(&uuid).unwrap();
|
|
|
|
|
|
|
|
assert_eq!(key.crypto.cipher_text, cipher_text);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2016-02-15 16:01:52 +01:00
|
|
|
fn can_store_10_keys() {
|
2016-02-12 21:27:09 +01:00
|
|
|
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 {
|
2016-02-16 18:52:36 +01:00
|
|
|
let key = KeyFileContent::new(KeyFileCrypto::new_pbkdf2(cipher_text.clone(), H128::zero(), H256::random(), H256::random(), 32, 32));
|
2016-02-12 21:27:09 +01:00
|
|
|
keys.push(directory.save(key).unwrap());
|
|
|
|
}
|
|
|
|
|
|
|
|
assert_eq!(10, keys.len())
|
|
|
|
}
|
2016-02-17 09:48:12 +01:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn can_list_keys() {
|
|
|
|
let temp_path = RandomTempPath::create_dir();
|
|
|
|
let mut directory = KeyDirectory::new(&temp_path.as_path());
|
|
|
|
|
|
|
|
let cipher_text: Bytes = FromHex::from_hex("a0f05555").unwrap();
|
|
|
|
let mut keys = Vec::new();
|
|
|
|
for _ in 0..33 {
|
|
|
|
let key = KeyFileContent::new(KeyFileCrypto::new_pbkdf2(cipher_text.clone(), H128::zero(), H256::random(), H256::random(), 32, 32));
|
|
|
|
keys.push(directory.save(key).unwrap());
|
|
|
|
}
|
|
|
|
|
|
|
|
assert_eq!(33, directory.list().unwrap().len());
|
|
|
|
}
|
2016-02-12 16:39:47 +01:00
|
|
|
}
|