use crypto_core::{CryptoError, KdfAlgorithm, KdfFailureKind, KdfProfile};
use crate::constants::{
ARGON2ID_V1_LANES, ARGON2ID_V1_MEMORY_COST_KIB, ARGON2ID_V1_TIME_COST, ARGON2ID_V2_LANES,
ARGON2ID_V2_MEMORY_COST_KIB, ARGON2ID_V2_TIME_COST,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Argon2Profile {
V1,
V2,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Argon2KdfVersion {
V1 = 1,
V2 = 2,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Argon2PlatformClass {
MobileModern,
DesktopModern,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Argon2Caps {
pub mem_cap_kib: u32,
pub time_cost_cap: u32,
pub lanes_cap: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Argon2ParamsProfile {
pub mem_kib: u32,
pub time_cost: u32,
pub lanes: u32,
}
impl Argon2Caps {
pub const fn for_platform(platform: Argon2PlatformClass) -> Self {
match platform {
Argon2PlatformClass::MobileModern => Self {
mem_cap_kib: 524_288,
time_cost_cap: 4,
lanes_cap: 4,
},
Argon2PlatformClass::DesktopModern => Self {
mem_cap_kib: 2_097_152,
time_cost_cap: 6,
lanes_cap: 4,
},
}
}
}
impl TryFrom<u32> for Argon2KdfVersion {
type Error = CryptoError;
fn try_from(value: u32) -> Result<Self, Self::Error> {
match value {
1 => Ok(Self::V1),
2 => Ok(Self::V2),
_ => Err(CryptoError::Kdf {
algorithm: KdfAlgorithm::Argon2id,
profile: KdfProfile::Argon2idV1,
kind: KdfFailureKind::InvalidParams,
}),
}
}
}
impl From<Argon2KdfVersion> for Argon2Profile {
fn from(value: Argon2KdfVersion) -> Self {
match value {
Argon2KdfVersion::V1 => Self::V1,
Argon2KdfVersion::V2 => Self::V2,
}
}
}
impl Argon2Profile {
pub const fn to_kdf_profile(self) -> KdfProfile {
match self {
Self::V1 => KdfProfile::Argon2idV1,
Self::V2 => KdfProfile::Argon2idV2,
}
}
const fn params(self) -> Argon2ParamsProfile {
match self {
Self::V1 => Argon2ParamsProfile {
mem_kib: ARGON2ID_V1_MEMORY_COST_KIB,
time_cost: ARGON2ID_V1_TIME_COST,
lanes: ARGON2ID_V1_LANES,
},
Self::V2 => Argon2ParamsProfile {
mem_kib: ARGON2ID_V2_MEMORY_COST_KIB,
time_cost: ARGON2ID_V2_TIME_COST,
lanes: ARGON2ID_V2_LANES,
},
}
}
pub(crate) const fn params_tuple(self) -> (u32, u32, u32) {
let params = self.params();
(params.mem_kib, params.time_cost, params.lanes)
}
}
fn validate_caps(
profile: Argon2Profile,
caps: Argon2Caps,
) -> Result<Argon2ParamsProfile, CryptoError> {
let params = profile.params();
let profile_tag = profile.to_kdf_profile();
if params.mem_kib > caps.mem_cap_kib
|| params.time_cost > caps.time_cost_cap
|| params.lanes > caps.lanes_cap
{
return Err(CryptoError::Kdf {
algorithm: KdfAlgorithm::Argon2id,
profile: profile_tag,
kind: KdfFailureKind::InvalidParams,
});
}
Ok(params)
}
pub fn resolve_profile_params_for_platform(
kdf_version: u32,
platform: Argon2PlatformClass,
) -> Result<Argon2ParamsProfile, CryptoError> {
let version = Argon2KdfVersion::try_from(kdf_version)?;
let profile = Argon2Profile::from(version);
validate_caps(profile, Argon2Caps::for_platform(platform))
}
pub fn resolve_profile_params_with_caps(
kdf_version: u32,
caps: Argon2Caps,
) -> Result<Argon2ParamsProfile, CryptoError> {
let version = Argon2KdfVersion::try_from(kdf_version)?;
let profile = Argon2Profile::from(version);
validate_caps(profile, caps)
}
pub fn resolve_mobile_profile_for_unlock() -> Result<Argon2KdfVersion, CryptoError> {
let preferred = Argon2KdfVersion::V2;
let profile = Argon2Profile::from(preferred);
let caps = Argon2Caps::for_platform(Argon2PlatformClass::MobileModern);
if validate_caps(profile, caps).is_ok() {
return Ok(preferred);
}
let fallback = Argon2KdfVersion::V1;
let fallback_profile = Argon2Profile::from(fallback);
validate_caps(fallback_profile, caps)?;
Ok(fallback)
}