use argon2::{Algorithm, Argon2, Params, Version};
use zeroize::Zeroizing;
use super::super::format::Argon2Params;
use super::UnlockError;
const MAX_M_COST_KIB: u32 = 1_048_576;
const MIN_M_COST_KIB: u32 = 8;
const MAX_T_COST: u32 = 64;
const MAX_P_COST: u32 = 64;
pub fn derive_slot_key(
secret: &[u8],
salt: &[u8],
params: Argon2Params,
) -> Result<Zeroizing<[u8; 32]>, UnlockError> {
if !(MIN_M_COST_KIB..=MAX_M_COST_KIB).contains(¶ms.m_cost_kib)
|| !(1..=MAX_T_COST).contains(¶ms.t_cost)
|| !(1..=MAX_P_COST).contains(¶ms.p_cost)
{
return Err(UnlockError::ParamsMismatch(format!(
"argon2 costs out of accepted band (m_cost_kib {MIN_M_COST_KIB}..={MAX_M_COST_KIB}, \
t_cost 1..={MAX_T_COST}, p_cost 1..={MAX_P_COST}): \
m={}, t={}, p={}",
params.m_cost_kib, params.t_cost, params.p_cost
)));
}
let p = Params::new(params.m_cost_kib, params.t_cost, params.p_cost, Some(32))
.map_err(|e| UnlockError::Crypto(format!("argon2 params: {e}")))?;
let kdf = Argon2::new(Algorithm::Argon2id, Version::V0x13, p);
let mut out = Zeroizing::new([0u8; 32]);
kdf.hash_password_into(secret, salt, out.as_mut())
.map_err(|e| UnlockError::Crypto(format!("argon2 derive: {e}")))?;
Ok(out)
}
pub fn wrap_aad(header_aad: &[u8], slot_id: u32) -> Vec<u8> {
let mut aad = Vec::with_capacity(header_aad.len() + 4);
aad.extend_from_slice(header_aad);
aad.extend_from_slice(&slot_id.to_be_bytes());
aad
}
#[cfg(test)]
mod tests {
use super::*;
const fn params(m_cost_kib: u32, t_cost: u32, p_cost: u32) -> Argon2Params {
Argon2Params {
m_cost_kib,
t_cost,
p_cost,
}
}
#[test]
fn production_params_are_inside_the_band() {
let m = Argon2Params::PRODUCTION.m_cost_kib;
let t = Argon2Params::PRODUCTION.t_cost;
let p = Argon2Params::PRODUCTION.p_cost;
assert!((MIN_M_COST_KIB..=MAX_M_COST_KIB).contains(&m));
assert!((1..=MAX_T_COST).contains(&t));
assert!((1..=MAX_P_COST).contains(&p));
}
#[test]
fn oversized_costs_are_rejected_before_allocation() {
for bad in [
params(268_435_456, 3, 1),
params(MIN_M_COST_KIB, MAX_T_COST + 1, 1),
params(MIN_M_COST_KIB, 1, MAX_P_COST + 1),
params(0, 3, 1),
params(MIN_M_COST_KIB, 0, 1),
params(MIN_M_COST_KIB, 1, 0),
] {
let err = derive_slot_key(b"secret", b"0123456789abcdef", bad)
.expect_err("out-of-band costs must be rejected");
assert!(matches!(err, UnlockError::ParamsMismatch(_)), "{err}");
}
}
#[test]
fn in_band_costs_derive_a_key() {
let key = derive_slot_key(b"secret", b"0123456789abcdef", params(8, 1, 1))
.expect("minimal in-band costs derive");
assert_ne!(*key, [0u8; 32]);
}
}