use sha1::Sha1;
use sha2::{Digest, Sha512};
use crate::crypto::AuthKey;
use crate::{Error, Result};
pub const PBKDF2_ITERATIONS: u32 = 100_000;
pub const PBKDF2_ITERATIONS_EMPTY: u32 = 1;
pub const LEGACY_PBKDF2_ITERATIONS: u32 = 4000;
pub const LEGACY_PBKDF2_ITERATIONS_EMPTY: u32 = 4;
pub const PASSCODE_SALT_MIN_SIZE: usize = 8;
pub const ARGON2_MAX_MEMORY_KIB: u32 = 262_144;
pub const ARGON2_MAX_TIME: u32 = 64;
pub const ARGON2_MAX_LANES: u32 = 16;
pub const SCRYPT_MAX_N: u32 = 1 << 20;
pub const SCRYPT_MAX_R: u32 = 32;
pub const SCRYPT_MAX_P: u32 = 16;
pub const SCRYPT_MAX_MEM: u64 = 192 * 1024 * 1024;
#[must_use]
pub fn create_local_key(passcode: &[u8], salt: &[u8]) -> AuthKey {
let hash_key = Sha512::new()
.chain_update(salt)
.chain_update(passcode)
.chain_update(salt)
.finalize();
let rounds = if passcode.is_empty() {
PBKDF2_ITERATIONS_EMPTY
} else {
PBKDF2_ITERATIONS
};
let mut key = [0u8; AuthKey::SIZE];
pbkdf2::pbkdf2_hmac::<Sha512>(&hash_key, salt, rounds, &mut key);
AuthKey::new(key)
}
#[must_use]
pub fn create_legacy_local_key(passcode: &[u8], salt: &[u8]) -> AuthKey {
let rounds = if passcode.is_empty() {
LEGACY_PBKDF2_ITERATIONS_EMPTY
} else {
LEGACY_PBKDF2_ITERATIONS
};
let mut key = [0u8; AuthKey::SIZE];
pbkdf2::pbkdf2_hmac::<Sha1>(passcode, salt, rounds, &mut key);
AuthKey::new(key)
}
fn usize_of(value: u32) -> usize {
usize::try_from(value).unwrap_or(usize::MAX)
}
fn cap(what: &'static str, value: u32, max: u32) -> Result<()> {
if value > max {
return Err(Error::BadLength {
what,
expected: usize_of(max),
actual: usize_of(value),
});
}
Ok(())
}
fn check_wrap_inputs(passcode: &[u8], salt: &[u8]) -> Result<()> {
if passcode.is_empty() {
return Err(Error::BadLength {
what: "passcode",
expected: 1,
actual: 0,
});
}
if salt.len() < PASSCODE_SALT_MIN_SIZE {
return Err(Error::BadLength {
what: "passcode salt",
expected: PASSCODE_SALT_MIN_SIZE,
actual: salt.len(),
});
}
Ok(())
}
pub fn argon2id(
passcode: &[u8],
salt: &[u8],
memory_kib: u32,
time: u32,
lanes: u32,
) -> Result<[u8; AuthKey::SIZE]> {
check_wrap_inputs(passcode, salt)?;
cap("Argon2id memory_kib", memory_kib, ARGON2_MAX_MEMORY_KIB)?;
cap("Argon2id time", time, ARGON2_MAX_TIME)?;
cap("Argon2id lanes", lanes, ARGON2_MAX_LANES)?;
if time == 0 || lanes == 0 || memory_kib < 8 * lanes {
return Err(Error::BadDecryptKey);
}
let params = argon2::Params::new(memory_kib, time, lanes, Some(AuthKey::SIZE))
.map_err(|_| Error::BadDecryptKey)?;
let hasher = argon2::Argon2::new(argon2::Algorithm::Argon2id, argon2::Version::V0x13, params);
let mut out = [0u8; AuthKey::SIZE];
hasher
.hash_password_into(passcode, salt, &mut out)
.map_err(|_| Error::BadDecryptKey)?;
Ok(out)
}
pub fn scrypt(passcode: &[u8], salt: &[u8], n: u32, r: u32, p: u32) -> Result<[u8; AuthKey::SIZE]> {
check_wrap_inputs(passcode, salt)?;
cap("scrypt N", n, SCRYPT_MAX_N)?;
cap("scrypt r", r, SCRYPT_MAX_R)?;
cap("scrypt p", p, SCRYPT_MAX_P)?;
if n < 2 || !n.is_power_of_two() || r == 0 || p == 0 {
return Err(Error::BadDecryptKey);
}
let memory = 128 * u64::from(r) * u64::from(p) + 128 * u64::from(r) * (u64::from(n) + 2);
if memory > SCRYPT_MAX_MEM {
return Err(Error::BadDecryptKey);
}
let log_n = u8::try_from(n.trailing_zeros()).map_err(|_| Error::BadDecryptKey)?;
let params = scrypt::Params::new(log_n, r, p).map_err(|_| Error::BadDecryptKey)?;
let mut out = [0u8; AuthKey::SIZE];
scrypt::scrypt(passcode, salt, ¶ms, &mut out).map_err(|_| Error::BadDecryptKey)?;
Ok(out)
}