use crate::prelude_internal::*;
use rand::{CryptoRng, RngCore};
use std::fmt::{Display, Formatter};
use std::str::FromStr;
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
#[derive(Default, Clone, Ord, PartialOrd, Eq, PartialEq, Zeroize, ZeroizeOnDrop)]
pub struct Secret(pub(crate) [u8; 32]);
impl Secret {
pub const ZERO: Secret = Secret([0; 32]);
pub fn from_passphrase<T: AsRef<[u8]>>(passphrase: T) -> Secret {
let passphrase = passphrase.as_ref();
let salt = b"MSecret_Passphrase_v1";
const MSECRET_ARGON2_CONFIG: argon2::Config = argon2::Config {
ad: &[],
hash_length: 32,
lanes: 4,
mem_cost: 262_144,
secret: &[],
thread_mode: argon2::ThreadMode::Sequential,
time_cost: 3,
variant: argon2::Variant::Argon2id,
version: argon2::Version::Version13,
};
let secret =
Zeroizing::new(argon2::hash_raw(passphrase, salt, &MSECRET_ARGON2_CONFIG).unwrap());
Secret::try_from_bytes(&secret).unwrap()
}
pub fn generate() -> Secret {
Self::generate_from_rng(&mut rand::rngs::OsRng)
}
pub fn generate_from_rng<RNG: RngCore + CryptoRng>(rng: &mut RNG) -> Secret {
let mut ret = Secret::ZERO;
rng.fill_bytes(&mut ret.0);
ret
}
pub fn try_from_bytes_or_hex<T: AsRef<[u8]>>(data: T) -> Result<Secret> {
let data = data.as_ref();
if data.len() == 32 {
Self::try_from_bytes(data)
} else if data.len() == 64 {
Self::try_from_hex(data)
} else {
bail!("Key data not a known length")
}
}
pub fn try_from_bytes<T: AsRef<[u8]>>(data: T) -> Result<Secret> {
let data = data.as_ref();
if data.len() == 32 {
let mut ret = Self::ZERO;
ret.0.copy_from_slice(data);
Ok(ret)
} else {
bail!("Key bytes not the correct length (should be 32)")
}
}
pub fn try_from_hex<T: AsRef<[u8]>>(data: T) -> Result<Secret> {
if data.as_ref().len() != 64 {
bail!("Incorrect key string length (should be 64)");
}
let mut ret = Self::ZERO;
hex::decode_to_slice(data, &mut ret.0[0..32])?;
Ok(ret)
}
pub fn to_hex(&self) -> String {
hex::encode(self.0)
}
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
}
#[cfg(feature = "share")]
impl Secret {
pub const SHARE_CRC: crc::Crc<u8> = crc::Crc::<u8>::new(&crc::CRC_8_BLUETOOTH);
pub const SHARE_LEN: usize = 35;
pub fn verify_share<S: AsRef<[u8]>>(share: S) -> Result<()> {
let share = share.as_ref();
ensure!(share.len() == Self::SHARE_LEN, "Bad Length");
ensure!(Self::SHARE_CRC.checksum(share) == 0, "Bad CRC");
Ok(())
}
pub fn try_from_shares<S: AsRef<[u8]>>(shares: &[S]) -> Result<Secret> {
ensure!(!shares.is_empty(), "No shares given");
for i in 0..shares.len() {
let share = shares.get(i).unwrap().as_ref();
if let Err(err) = Self::verify_share(share) {
bail!("{} for share", err);
}
}
let recovered: Vec<u8> = gf256::shamir::shamir::reconstruct(shares);
ensure!(
Self::SHARE_CRC.checksum(&recovered[..recovered.len() - 1]) == 0,
"Bad CRC for recovered secret"
);
Secret::try_from_bytes(&recovered[..recovered.len() - 2])
}
pub fn split_shares(&self, n: u8, k: u8) -> Result<Vec<Vec<u8>>> {
ensure!(k != 0, "`k` cannot be zero.");
ensure!(n != 0, "`n` cannot be zero.");
ensure!(k <= n, "`k` cannot be larger than `n`.");
let mut secret = Zeroizing::new(self.0.to_vec());
let crc = Self::SHARE_CRC.checksum(secret.as_slice());
secret.push(crc);
assert_eq!(Self::SHARE_CRC.checksum(secret.as_slice()), 0);
let mut shares: Vec<Vec<u8>> =
gf256::shamir::shamir::generate(&secret, n as usize, k as usize);
for share in shares.iter_mut() {
let crc = Self::SHARE_CRC.checksum(share.as_slice());
share.push(crc);
assert_eq!(Self::SHARE_CRC.checksum(share.as_slice()), 0);
}
Ok(shares)
}
}
impl Derivable for Secret {
fn bytes(&self) -> Result<[u8; 32]> {
Ok(self.0)
}
fn id(&self) -> SecretId {
let Secret(bytes) = self.subsecret_from_salt(b"\x00SecretId").unwrap();
let mut ret = [0u8; SecretId::LEN];
ret.copy_from_slice(&bytes.as_slice()[0..SecretId::LEN]);
ret.into()
}
}
impl Debug for Secret {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "Secret({})", hex::encode(self.0))
}
}
impl FromStr for Secret {
type Err = anyhow::Error;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
Self::try_from_hex(s)
}
}
impl Display for Secret {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", hex::encode(self.0))
}
}
#[cfg(test)]
mod tests {
#[allow(unused_imports)]
use super::*;
#[test]
#[cfg(feature = "share")]
fn test_secret_sharing() {
let mut shares = Secret::ZERO.split_shares(5, 3).unwrap();
assert_eq!(Secret::try_from_shares(&shares).unwrap(), Secret::ZERO);
shares.pop();
assert_eq!(Secret::try_from_shares(&shares).unwrap(), Secret::ZERO);
shares.pop();
assert_eq!(Secret::try_from_shares(&shares).unwrap(), Secret::ZERO);
shares.pop();
assert!(Secret::try_from_shares(&shares).is_err());
}
}