use anyhow::Result;
use rand::{prelude::StdRng, Rng, SeedableRng};
use zeroize::Zeroize;
use crate::cipher::Ciphers;
use crate::header::{Header, HeaderVersion};
use crate::primitives::{MASTER_KEY_LEN, SALT_LEN};
use crate::protected::Protected;
pub fn argon2id_hash(
raw_key: Protected<Vec<u8>>,
salt: &[u8; SALT_LEN],
version: &HeaderVersion,
) -> Result<Protected<[u8; 32]>> {
use argon2::Argon2;
use argon2::Params;
let params = match version {
HeaderVersion::V1 => {
Params::new(8192, 8, 4, Some(Params::DEFAULT_OUTPUT_LEN))
.map_err(|_| anyhow::anyhow!("Error initialising argon2id parameters"))?
}
HeaderVersion::V2 => {
Params::new(262_144, 8, 4, Some(Params::DEFAULT_OUTPUT_LEN))
.map_err(|_| anyhow::anyhow!("Error initialising argon2id parameters"))?
}
HeaderVersion::V3 => {
Params::new(262_144, 10, 4, Some(Params::DEFAULT_OUTPUT_LEN))
.map_err(|_| anyhow::anyhow!("Error initialising argon2id parameters"))?
}
HeaderVersion::V4 | HeaderVersion::V5 => {
return Err(anyhow::anyhow!(
"argon2id is not supported on header versions above V3."
))
}
};
let mut key = [0u8; 32];
let argon2 = Argon2::new(argon2::Algorithm::Argon2id, argon2::Version::V0x13, params);
let result = argon2.hash_password_into(raw_key.expose(), salt, &mut key);
drop(raw_key);
if result.is_err() {
return Err(anyhow::anyhow!("Error while hashing your key"));
}
Ok(Protected::new(key))
}
pub fn balloon_hash(
raw_key: Protected<Vec<u8>>,
salt: &[u8; SALT_LEN],
version: &HeaderVersion,
) -> Result<Protected<[u8; 32]>> {
use balloon_hash::Balloon;
let params = match version {
HeaderVersion::V1 | HeaderVersion::V2 | HeaderVersion::V3 => {
return Err(anyhow::anyhow!(
"Balloon hashing is not supported in header versions below V4."
));
}
HeaderVersion::V4 => balloon_hash::Params::new(262_144, 1, 1)
.map_err(|_| anyhow::anyhow!("Error initialising balloon hashing parameters"))?,
HeaderVersion::V5 => balloon_hash::Params::new(278_528, 1, 1)
.map_err(|_| anyhow::anyhow!("Error initialising balloon hashing parameters"))?,
};
let mut key = [0u8; 32];
let balloon = Balloon::<blake3::Hasher>::new(balloon_hash::Algorithm::Balloon, params, None);
let result = balloon.hash_into(raw_key.expose(), salt, &mut key);
drop(raw_key);
if result.is_err() {
return Err(anyhow::anyhow!("Error while hashing your key"));
}
Ok(Protected::new(key))
}
#[allow(clippy::module_name_repetitions)]
pub fn decrypt_master_key(
raw_key: Protected<Vec<u8>>,
header: &Header,
) -> Result<Protected<[u8; MASTER_KEY_LEN]>> {
match header.header_type.version {
HeaderVersion::V1 | HeaderVersion::V2 | HeaderVersion::V3 => {
argon2id_hash(raw_key, &header.salt.ok_or_else(|| anyhow::anyhow!("Missing salt within the header!"))?, &header.header_type.version)
}
HeaderVersion::V4 => {
let keyslots = header.keyslots.as_ref().ok_or_else(|| anyhow::anyhow!("Unable to find a keyslot!"))?;
let keyslot = keyslots.first().ok_or_else(|| anyhow::anyhow!("Unable to find a match with the key you provided (maybe you supplied the wrong key?)"))?;
let key = keyslot.hash_algorithm.hash(raw_key, &keyslot.salt)?;
let cipher = Ciphers::initialize(key, &header.header_type.algorithm)?;
cipher
.decrypt(&keyslot.nonce, keyslot.encrypted_key.as_slice())
.map(vec_to_arr)
.map(Protected::new)
.map_err(|_| anyhow::anyhow!("Cannot decrypt master key"))
}
HeaderVersion::V5 => {
header
.keyslots
.as_ref()
.ok_or_else(|| anyhow::anyhow!("Unable to find a keyslot!"))?
.iter()
.find_map(|keyslot| {
let key = keyslot.hash_algorithm.hash(raw_key.clone(), &keyslot.salt).ok()?;
let cipher = Ciphers::initialize(key, &header.header_type.algorithm).ok()?;
cipher
.decrypt(&keyslot.nonce, keyslot.encrypted_key.as_slice())
.map(vec_to_arr)
.map(Protected::new)
.ok()
})
.ok_or_else(|| anyhow::anyhow!("Unable to find a match with the key you provided (maybe you supplied the wrong key?)"))
}
}
}
#[must_use]
pub fn vec_to_arr<const N: usize>(mut master_key_vec: Vec<u8>) -> [u8; N] {
let mut master_key = [0u8; N];
let len = N.min(master_key_vec.len());
master_key[..len].copy_from_slice(&master_key_vec[..len]);
master_key_vec.zeroize();
master_key
}
#[must_use]
pub fn generate_passphrase() -> Protected<String> {
let collection = include_str!("wordlist.lst");
let words = collection.lines().collect::<Vec<_>>();
let mut passphrase = String::new();
for _ in 0..3 {
let index = StdRng::from_entropy().gen_range(0..=words.len());
let word = words[index];
let capitalized_word = word
.char_indices()
.map(|(i, ch)| match i {
0 => ch.to_ascii_uppercase(),
_ => ch,
})
.collect::<String>();
passphrase.push_str(&capitalized_word);
passphrase.push('-');
}
for _ in 0..6 {
let number: i64 = StdRng::from_entropy().gen_range(0..=9);
passphrase.push_str(&number.to_string());
}
Protected::new(passphrase)
}