use std::time::{SystemTime, UNIX_EPOCH};
use aes_gcm::{
Aes256Gcm, KeyInit, Nonce,
aead::{Aead, AeadCore, OsRng},
};
use argon2::{
Argon2,
password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString, rand_core},
};
use ed25519_dalek::SigningKey;
use zeroize::Zeroize;
use super::errors::UserError;
use crate::Result;
pub const SALT_LENGTH: usize = 22;
pub const NONCE_LENGTH: usize = 12;
pub const KEY_LENGTH: usize = 32;
pub fn hash_password(password: impl AsRef<str>) -> Result<(String, String)> {
let salt = SaltString::generate(&mut rand_core::OsRng);
let argon2 = Argon2::default();
let password_hash = argon2
.hash_password(password.as_ref().as_bytes(), &salt)
.map_err(|e| UserError::EncryptionFailed {
reason: format!("Password hashing failed: {e}"),
})?
.to_string();
let salt_string = salt.as_str().to_string();
Ok((password_hash, salt_string))
}
pub fn verify_password(password: impl AsRef<str>, password_hash: impl AsRef<str>) -> Result<()> {
let parsed_hash = PasswordHash::new(password_hash.as_ref())
.map_err(|_| UserError::PasswordVerificationFailed)?;
Argon2::default()
.verify_password(password.as_ref().as_bytes(), &parsed_hash)
.map_err(|_| UserError::InvalidPassword.into())
}
pub fn derive_encryption_key(password: impl AsRef<str>, salt: impl AsRef<str>) -> Result<Vec<u8>> {
let salt_str = salt.as_ref();
if salt_str.len() != SALT_LENGTH {
return Err(UserError::InvalidSaltLength {
expected: SALT_LENGTH,
actual: salt_str.len(),
}
.into());
}
let salt = SaltString::from_b64(salt_str).map_err(|e| UserError::EncryptionFailed {
reason: format!("Invalid salt format: {e}"),
})?;
let argon2 = Argon2::default();
let mut key = vec![0u8; KEY_LENGTH];
argon2
.hash_password_into(
password.as_ref().as_bytes(),
salt.as_str().as_bytes(),
&mut key,
)
.map_err(|e| UserError::EncryptionFailed {
reason: format!("Key derivation failed: {e}"),
})?;
Ok(key)
}
pub fn encrypt_private_key(
private_key: &SigningKey,
encryption_key: impl AsRef<[u8]>,
) -> Result<(Vec<u8>, Vec<u8>)> {
let encryption_key = encryption_key.as_ref();
if encryption_key.len() != KEY_LENGTH {
return Err(UserError::EncryptionFailed {
reason: format!(
"Invalid key length: expected {}, got {}",
KEY_LENGTH,
encryption_key.len()
),
}
.into());
}
let mut key_bytes = private_key.to_bytes();
let cipher =
Aes256Gcm::new_from_slice(encryption_key).map_err(|e| UserError::EncryptionFailed {
reason: format!("Failed to create cipher: {e}"),
})?;
let nonce = Aes256Gcm::generate_nonce(&mut OsRng);
let ciphertext = cipher.encrypt(&nonce, key_bytes.as_ref()).map_err(|e| {
key_bytes.zeroize();
UserError::EncryptionFailed {
reason: format!("Encryption failed: {e}"),
}
})?;
key_bytes.zeroize();
Ok((ciphertext, nonce.to_vec()))
}
pub fn decrypt_private_key(
ciphertext: impl AsRef<[u8]>,
nonce: impl AsRef<[u8]>,
encryption_key: impl AsRef<[u8]>,
) -> Result<SigningKey> {
let encryption_key = encryption_key.as_ref();
let nonce_bytes = nonce.as_ref();
let ciphertext = ciphertext.as_ref();
if encryption_key.len() != KEY_LENGTH {
return Err(UserError::DecryptionFailed {
reason: format!(
"Invalid key length: expected {}, got {}",
KEY_LENGTH,
encryption_key.len()
),
}
.into());
}
if nonce_bytes.len() != NONCE_LENGTH {
return Err(UserError::InvalidNonceLength {
expected: NONCE_LENGTH,
actual: nonce_bytes.len(),
}
.into());
}
let cipher =
Aes256Gcm::new_from_slice(encryption_key).map_err(|e| UserError::DecryptionFailed {
reason: format!("Failed to create cipher: {e}"),
})?;
let nonce_array: [u8; NONCE_LENGTH] =
nonce_bytes
.try_into()
.map_err(|_| UserError::InvalidNonceLength {
expected: NONCE_LENGTH,
actual: nonce_bytes.len(),
})?;
let nonce = Nonce::from(nonce_array);
let mut plaintext =
cipher
.decrypt(&nonce, ciphertext)
.map_err(|e| UserError::DecryptionFailed {
reason: format!("Decryption failed: {e}"),
})?;
if plaintext.len() != 32 {
plaintext.zeroize();
return Err(UserError::DecryptionFailed {
reason: format!(
"Invalid key length after decryption: expected 32, got {}",
plaintext.len()
),
}
.into());
}
let mut key_bytes: [u8; 32] =
plaintext
.try_into()
.map_err(|_| UserError::DecryptionFailed {
reason: "Failed to convert plaintext to key bytes".to_string(),
})?;
let signing_key = SigningKey::from_bytes(&key_bytes);
key_bytes.zeroize();
Ok(signing_key)
}
pub fn current_timestamp() -> Result<i64> {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs() as i64)
.or_else(|e| {
Ok(-(e.duration().as_secs() as i64))
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::auth::crypto::generate_keypair;
#[test]
fn test_password_hash_and_verify() {
let password = "test_password_123";
let (hash, _salt) = hash_password(password).unwrap();
assert!(verify_password(password, &hash).is_ok());
assert!(verify_password("wrong_password", &hash).is_err());
}
#[test]
fn test_password_hash_unique() {
let password = "test_password_123";
let (hash1, _) = hash_password(password).unwrap();
let (hash2, _) = hash_password(password).unwrap();
assert_ne!(hash1, hash2);
assert!(verify_password(password, &hash1).is_ok());
assert!(verify_password(password, &hash2).is_ok());
}
#[test]
fn test_key_encryption_round_trip() {
let (private_key, _) = generate_keypair();
let password = "encryption_password";
let (_, salt) = hash_password(password).unwrap();
let encryption_key = derive_encryption_key(password, &salt).unwrap();
let (ciphertext, nonce) = encrypt_private_key(&private_key, &encryption_key).unwrap();
let decrypted_key = decrypt_private_key(&ciphertext, &nonce, &encryption_key).unwrap();
assert_eq!(private_key.to_bytes(), decrypted_key.to_bytes());
}
#[test]
fn test_encryption_wrong_key_fails() {
let (private_key, _) = generate_keypair();
let password1 = "password1";
let password2 = "password2";
let (_, salt) = hash_password(password1).unwrap();
let encryption_key1 = derive_encryption_key(password1, &salt).unwrap();
let (ciphertext, nonce) = encrypt_private_key(&private_key, &encryption_key1).unwrap();
let encryption_key2 = derive_encryption_key(password2, &salt).unwrap();
let result = decrypt_private_key(&ciphertext, &nonce, &encryption_key2);
assert!(result.is_err());
}
#[test]
fn test_nonce_uniqueness() {
let (private_key, _) = generate_keypair();
let password = "password";
let (_, salt) = hash_password(password).unwrap();
let encryption_key = derive_encryption_key(password, &salt).unwrap();
let (_, nonce1) = encrypt_private_key(&private_key, &encryption_key).unwrap();
let (_, nonce2) = encrypt_private_key(&private_key, &encryption_key).unwrap();
assert_ne!(nonce1, nonce2);
}
}