use std::collections::HashSet;
use ring::{hkdf, hmac};
use ring::rand::{SecureRandom, SystemRandom};
use ring::digest::{Context, SHA256};
use rand::{ChaChaRng, Rng, SeedableRng};
use errors::*;
use dss::{thss, AccessStructure};
use dss::thss::{MetaData, ThSS};
use dss::random::{random_bytes_count, FixedRandom, MAX_MESSAGE_SIZE};
use share::validation::{validate_share_count, validate_shares};
use super::share::*;
use dss::utils;
use vol_hash::VOLHash;
const MAX_SECRET_SIZE: usize = MAX_MESSAGE_SIZE;
const DEFAULT_PRESEED: &[u8] = b"rusty_secrets::dss::ss1";
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Reproducibility {
Reproducible,
None,
Seeded(Vec<u8>),
WithEntropy(Vec<u8>),
}
impl Reproducibility {
pub fn reproducible() -> Self {
Reproducibility::Reproducible
}
pub fn seeded(seed: Vec<u8>) -> Self {
assert!(!seed.is_empty(), "Reproducibility: seed cannot be empty");
Reproducibility::Seeded(seed)
}
pub fn with_entropy(entropy: Vec<u8>) -> Self {
assert!(
!entropy.is_empty(),
"Reproducibility: entropy cannot be empty"
);
Reproducibility::WithEntropy(entropy)
}
pub fn none() -> Self {
Reproducibility::None
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(crate) struct SS1 {
pub random_padding_len: usize,
pub hash_len: usize,
}
static DEFAULT_RANDOM_PADDING_LEN: usize = 512; static MIN_RANDOM_PADDING_LEN: usize = 128; static DEFAULT_HASH_LEN: usize = 256; static MIN_HASH_LEN: usize = 128;
impl Default for SS1 {
fn default() -> Self {
Self::new(DEFAULT_RANDOM_PADDING_LEN, DEFAULT_HASH_LEN).unwrap()
}
}
impl SS1 {
pub fn new(random_padding_len: usize, hash_len: usize) -> Result<Self> {
if random_padding_len < MIN_RANDOM_PADDING_LEN || hash_len < MIN_HASH_LEN {
bail!(ErrorKind::InvalidSS1Parameters(
random_padding_len,
hash_len,
));
}
Ok(Self {
random_padding_len,
hash_len,
})
}
pub fn split_secret(
&self,
threshold: u8,
shares_count: u8,
secret: &[u8],
reproducibility: Reproducibility,
metadata: &Option<MetaData>,
) -> Result<Vec<Share>> {
let (threshold, shares_count) = validate_share_count(threshold, shares_count)?;
let secret_len = secret.len();
if secret_len == 0 {
bail!(ErrorKind::EmptySecret);
}
if secret_len > MAX_SECRET_SIZE {
bail!(ErrorKind::SecretTooBig(secret_len, MAX_SECRET_SIZE));
}
let random_padding = self.generate_random_padding(reproducibility, secret, metadata)?;
let mut vol_hash = VOLHash::new(&SHA256);
vol_hash.process(&[0]);
vol_hash.process(&[threshold, shares_count]);
vol_hash.process(secret);
vol_hash.process(&random_padding);
let randomness_len = random_bytes_count(threshold, secret.len() + self.random_padding_len);
let total_hash_len = self.hash_len + randomness_len;
let mut full_hash = vec![0; total_hash_len];
vol_hash.finish(&mut full_hash);
let (hash, randomness) = full_hash.split_at(self.hash_len);
let underlying = ThSS::new(Box::new(FixedRandom::new(randomness.to_vec())));
let message = [secret, &random_padding].concat();
let shares = underlying.split_secret(threshold, shares_count, &message, metadata)?;
let res = shares
.into_iter()
.map(|share| Share {
id: share.id,
threshold: share.threshold,
shares_count: share.shares_count,
data: share.data,
hash: hash.to_vec(),
metadata: share.metadata.clone(),
})
.collect();
Ok(res)
}
fn generate_random_padding(
&self,
reproducibility: Reproducibility,
secret: &[u8],
metadata: &Option<MetaData>,
) -> Result<Vec<u8>> {
match reproducibility {
Reproducibility::None => {
let rng = SystemRandom::new();
let mut result = vec![0u8; self.random_padding_len];
rng.fill(&mut result)
.chain_err(|| ErrorKind::CannotGenerateRandomNumbers)?;
Ok(result)
}
Reproducibility::Reproducible => {
let seed = self.generate_seed(DEFAULT_PRESEED, secret, metadata);
let mut rng = ChaChaRng::from_seed(&seed);
let mut result = vec![0u8; self.random_padding_len];
rng.fill_bytes(result.as_mut_slice());
Ok(result)
}
Reproducibility::Seeded(preseed) => {
let seed = self.generate_seed(&preseed, secret, metadata);
let mut rng = ChaChaRng::from_seed(&seed);
let mut result = vec![0u8; self.random_padding_len];
rng.fill_bytes(result.as_mut_slice());
Ok(result)
}
Reproducibility::WithEntropy(entropy) => Ok(entropy),
}
}
fn generate_seed(
&self,
preseed: &[u8],
secret: &[u8],
metadata: &Option<MetaData>,
) -> Vec<u32> {
let mut ctx = Context::new(&SHA256);
ctx.update(preseed);
ctx.update(secret);
for md in metadata {
md.hash_into(&mut ctx);
}
let preseed_hash = ctx.finish();
let salt = hmac::SigningKey::new(&SHA256, &[]);
let mut seed_bytes = vec![0u8; 32];
hkdf::extract_and_expand(&salt, preseed_hash.as_ref(), &[], &mut seed_bytes);
utils::slice_u8_to_slice_u32(&seed_bytes).to_vec()
}
pub fn recover_secret(
&self,
shares: &[Share],
) -> Result<(Vec<u8>, AccessStructure, Option<MetaData>)> {
let (_, shares) = validate_shares(shares.to_vec())?;
let underlying_shares = shares
.iter()
.map(|share| thss::Share {
id: share.id,
threshold: share.threshold,
shares_count: share.shares_count,
data: share.data.clone(),
metadata: share.metadata.clone(),
})
.collect::<Vec<_>>();
let underlying = ThSS::default();
let (mut secret, _, metadata) = underlying.recover_secret(&underlying_shares)?;
let secret_len = secret.len() - self.random_padding_len;
let random_padding = secret.split_off(secret_len);
let sub_scheme = Self::new(self.random_padding_len, self.hash_len)?;
let test_shares = sub_scheme.split_secret(
shares[0].threshold,
shares[0].shares_count,
&secret,
Reproducibility::WithEntropy(random_padding.to_vec()),
&metadata,
)?;
let access_structure = {
let first_share = shares.first().unwrap();
AccessStructure {
threshold: first_share.threshold,
shares_count: first_share.shares_count,
}
};
self.verify_test_shares(shares, test_shares)?;
Ok((secret, access_structure, metadata))
}
fn verify_test_shares(
&self,
mut shares: Vec<Share>,
mut test_shares: Vec<Share>,
) -> Result<()> {
shares.sort_by_key(|share| share.id);
test_shares.sort_by_key(|share| share.id);
let relevant_ids = shares.iter().map(|share| share.id).collect::<HashSet<_>>();
let relevant_test_shares = test_shares
.iter()
.filter(|share| relevant_ids.contains(&share.id));
let matching_shares = shares.iter().zip(relevant_test_shares);
for (share, test_share) in matching_shares {
if share != test_share {
bail!(ErrorKind::MismatchingShares(
share.clone(),
test_share.clone(),
));
}
}
Ok(())
}
}