use argon2::{Algorithm, Argon2, Params, Version};
use rand::TryRngCore;
use crate::errors::{EnvVaultError, Result};
const SALT_LEN: usize = 32;
const KEY_LEN: usize = 32;
#[derive(Debug, Clone, Copy)]
pub struct Argon2Params {
pub memory_kib: u32,
pub iterations: u32,
pub parallelism: u32,
}
impl Default for Argon2Params {
fn default() -> Self {
Self {
memory_kib: 65_536,
iterations: 3,
parallelism: 4,
}
}
}
pub fn derive_master_key(password: &[u8], salt: &[u8]) -> Result<[u8; KEY_LEN]> {
derive_master_key_with_params(password, salt, &Argon2Params::default())
}
const MIN_MEMORY_KIB: u32 = 8_192;
pub fn derive_master_key_with_params(
password: &[u8],
salt: &[u8],
argon2_params: &Argon2Params,
) -> Result<[u8; KEY_LEN]> {
if argon2_params.memory_kib < MIN_MEMORY_KIB {
return Err(EnvVaultError::KeyDerivationFailed(format!(
"Argon2 memory_kib must be at least {MIN_MEMORY_KIB} (got {})",
argon2_params.memory_kib
)));
}
if argon2_params.iterations < 1 {
return Err(EnvVaultError::KeyDerivationFailed(
"Argon2 iterations must be at least 1".into(),
));
}
if argon2_params.parallelism < 1 {
return Err(EnvVaultError::KeyDerivationFailed(
"Argon2 parallelism must be at least 1".into(),
));
}
let params = Params::new(
argon2_params.memory_kib,
argon2_params.iterations,
argon2_params.parallelism,
Some(KEY_LEN),
)
.map_err(|e| EnvVaultError::KeyDerivationFailed(format!("invalid Argon2 params: {e}")))?;
let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
let mut key = [0u8; KEY_LEN];
argon2
.hash_password_into(password, salt, &mut key)
.map_err(|e| EnvVaultError::KeyDerivationFailed(format!("Argon2id hashing failed: {e}")))?;
Ok(key)
}
pub fn generate_salt() -> [u8; SALT_LEN] {
let mut salt = [0u8; SALT_LEN];
rand::rngs::OsRng
.try_fill_bytes(&mut salt)
.expect("OS RNG failed");
salt
}