age-vault 0.1.0

A secure vault for managing age-encrypted accounts and data.
Documentation
//! Cryptographic utilities for key derivation and secret key encryption.
//!
//! This module provides functions to derive a Key Encryption Key (KEK) from a master
//! password using Argon2, and to encrypt/decrypt Age secret keys using that KEK.

use crate::error::{Error, Result};
use argon2::password_hash::SaltString;
use argon2::{Argon2, PasswordHasher};
use secrecy::SecretString;

/// Derives a Key Encryption Key (KEK) from a master password and a salt.
///
/// The function uses Argon2id with default parameters to hash the master password,
/// producing a string that can be used as a KEK. The returned `SecretString` is
/// zeroized on drop for security.
///
/// # Parameters
///
/// * `master_password` - The master password (UTF-8 string).
/// * `salt_bytes` - Cryptographic salt (16 bytes recommended).
///
/// # Returns
///
/// A `SecretString` containing the derived KEK.
///
/// # Errors
///
/// Returns [`Error::Kdf`] if the salt encoding or hashing fails.
///
/// # Panics
///
/// This function does not panic.
///
/// # Examples
///
/// ```
/// use age_vault::crypto::derive_kek;
/// # use age_vault::Error;
/// # fn main() -> Result<(), Error> {
/// let password = "my password";
/// let salt = b"0123456789abcdef"; // 16 bytes
/// let kek = derive_kek(password, salt)?;
/// # Ok(())
/// # }
/// ```
pub fn derive_kek(master_password: &str, salt_bytes: &[u8]) -> Result<SecretString> {
    let salt = SaltString::encode_b64(salt_bytes).map_err(|e| Error::Kdf(e.to_string()))?;
    let argon2 = Argon2::default();
    let password_hash = argon2
        .hash_password(master_password.as_bytes(), &salt)
        .map_err(|e| Error::Kdf(e.to_string()))?;
    Ok(SecretString::from(password_hash.to_string()))
}

/// Encrypts an Age secret key using a KEK.
///
/// The secret key is encrypted with the KEK using age's passphrase-based encryption.
/// The result is a byte vector that can be stored securely.
///
/// # Parameters
///
/// * `secret_key` - The plaintext Age secret key string.
/// * `kek` - The Key Encryption Key (as a string slice, e.g., derived from `derive_kek`).
///
/// # Returns
///
/// Encrypted ciphertext as `Vec<u8>`.
///
/// # Errors
///
/// Returns [`Error::Crypto`] if the underlying age encryption fails.
///
/// # Panics
///
/// This function does not panic.
///
/// # Examples
///
/// ```
/// use secrecy::ExposeSecret;
/// use age_vault::crypto::{derive_kek, encrypt_secret_key};
/// # use age_vault::Error;
/// # fn main() -> Result<(), Error> {
/// let password = "master";
/// let salt = b"0123456789abcdef";
/// let kek = derive_kek(password, salt)?;
/// let secret_key = "AGE-SECRET-KEY-...";
/// let encrypted = encrypt_secret_key(secret_key, kek.expose_secret())?;
/// # Ok(())
/// # }
/// ```
pub fn encrypt_secret_key(secret_key: &str, kek: &str) -> Result<Vec<u8>> {
    age_crypto::encrypt_with_passphrase(secret_key.as_bytes(), kek)
        .map(|data| data.to_vec())
        .map_err(Into::into)
}

/// Decrypts an encrypted Age secret key using a KEK.
///
/// Reverses `encrypt_secret_key`. The returned plaintext is a UTF-8 string.
///
/// # Parameters
///
/// * `encrypted` - The encrypted secret key bytes.
/// * `kek` - The Key Encryption Key (same as used for encryption).
///
/// # Returns
///
/// The decrypted secret key as a `String`.
///
/// # Errors
///
/// Returns [`Error::Crypto`] if decryption fails (wrong KEK or corrupted data).
/// Returns [`Error::DecryptionFailed`] if the decrypted data is not valid UTF-8.
///
/// # Panics
///
/// This function does not panic.
///
/// # Examples
///
/// ```
/// use secrecy::ExposeSecret;
/// use age_vault::crypto::{derive_kek, encrypt_secret_key, decrypt_secret_key};
/// # use age_vault::Error;
/// # fn main() -> Result<(), Error> {
/// let password = "master";
/// let salt = b"0123456789abcdef";
/// let kek = derive_kek(password, salt)?;
/// let original = "AGE-SECRET-KEY-...";
/// let encrypted = encrypt_secret_key(original, kek.expose_secret())?;
/// let decrypted = decrypt_secret_key(&encrypted, kek.expose_secret())?;
/// assert_eq!(original, decrypted);
/// # Ok(())
/// # }
/// ```
pub fn decrypt_secret_key(encrypted: &[u8], kek: &str) -> Result<String> {
    let plain = age_crypto::decrypt_with_passphrase(encrypted, kek)?;
    String::from_utf8(plain).map_err(|e| Error::DecryptionFailed(e.to_string()))
}