use foundation_auth::shared::password_hash::pbkdf2_derive;
use crate::core::error::AppResult;
pub const KEY_LEN: u32 = 32;
pub const SERVER_ITERATIONS: u32 =
foundation_auth::shared::password_hash::PBKDF2_SERVER_ITERATIONS;
#[cfg(not(target_family = "wasm"))]
pub async fn derive_password_hash(
client_hash: &[u8],
salt: &[u8],
iterations: u32,
) -> AppResult<Vec<u8>> {
Ok(pbkdf2_derive(client_hash, salt, iterations, KEY_LEN)
.as_bytes()
.to_vec())
}
#[cfg(target_family = "wasm")]
pub async fn derive_password_hash(
client_hash: &[u8],
salt: &[u8],
iterations: u32,
) -> AppResult<Vec<u8>> {
let key = pbkdf2_derive(client_hash, salt, iterations, KEY_LEN)
.await
.map_err(|e| crate::core::error::AppError::Internal(format!("pbkdf2 derive failed: {e:?}")))?;
Ok(key.as_bytes().to_vec())
}
pub async fn verify_password_hash(
client_hash: &[u8],
salt: &[u8],
expected: &[u8],
iterations: u32,
) -> AppResult<bool> {
let derived = derive_password_hash(client_hash, salt, iterations).await?;
Ok(constant_time_eq(&derived, expected))
}
#[must_use]
pub fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
if a.len() != b.len() {
return false;
}
let mut diff = 0u8;
for (x, y) in a.iter().zip(b.iter()) {
diff |= x ^ y;
}
diff == 0
}
#[must_use]
pub fn random_salt() -> Vec<u8> {
uuid::Uuid::new_v4().as_bytes().to_vec()
}
pub async fn derive_client_hash(
password: &str,
email: &str,
client_iterations: u32,
) -> Result<Option<String>, crate::core::error::AppError> {
use base64::Engine;
use pbkdf2::pbkdf2_hmac;
use sha2::Sha256;
let email_lower = email.trim().to_lowercase();
if password.len() < 8 {
return Ok(None);
}
let mut mk = vec![0u8; 32];
pbkdf2_hmac::<Sha256>(password.as_bytes(), email_lower.as_bytes(), client_iterations, &mut mk);
let mut lh = vec![0u8; 32];
pbkdf2_hmac::<Sha256>(&mk, password.as_bytes(), 1, &mut lh);
Ok(Some(base64::engine::general_purpose::STANDARD.encode(&lh)))
}
#[must_use]
pub fn random_key() -> String {
use base64::Engine;
let a = uuid::Uuid::new_v4();
let b = uuid::Uuid::new_v4();
let mut bytes = [0u8; 32];
bytes[..16].copy_from_slice(a.as_bytes());
bytes[16..].copy_from_slice(b.as_bytes());
base64::engine::general_purpose::STANDARD.encode(bytes)
}