use crate::{
backend::{BackendType, WrappedKey},
error::{Result, VeilError},
recovery::{BackupBundle, RecoveryStrategy, RecoveryType},
};
pub struct PassphraseRecovery;
impl RecoveryStrategy for PassphraseRecovery {
fn backup(&self, key: &WrappedKey, secret: Option<&[u8]>) -> Result<BackupBundle> {
let passphrase = secret.ok_or_else(|| {
VeilError::RecoveryFailed("passphrase is required for PassphraseRecovery".into())
})?;
let mut derived = derive_key_from_passphrase(passphrase)?;
let encrypted = aes_gcm_encrypt(&derived.key, &key.blob);
zeroize::Zeroize::zeroize(&mut derived.key);
let encrypted = encrypted?;
let mut data = Vec::with_capacity(16 + encrypted.len());
data.extend_from_slice(&derived.salt);
data.extend_from_slice(&encrypted);
Ok(BackupBundle {
recovery_type: RecoveryType::Passphrase,
data,
user_secret: None,
})
}
fn restore(&self, bundle: &BackupBundle, secret: &[u8]) -> Result<WrappedKey> {
if bundle.data.len() < 16 + 12 + 16 {
return Err(VeilError::RecoveryFailed("backup too short".into()));
}
let salt = &bundle.data[..16];
let encrypted = &bundle.data[16..];
let mut derived = derive_key_with_salt(secret, salt)?;
let blob = aes_gcm_decrypt(&derived.key, encrypted)
.map_err(|_| VeilError::RecoveryFailed("wrong passphrase or corrupted backup".into()));
zeroize::Zeroize::zeroize(&mut derived.key);
Ok(WrappedKey {
blob: blob?,
backend: BackendType::Tpm,
})
}
fn recovery_type(&self) -> RecoveryType {
RecoveryType::Passphrase
}
}
pub(crate) fn aes_gcm_encrypt(key: &[u8], plaintext: &[u8]) -> Result<Vec<u8>> {
use aes_gcm::{aead::Aead, aead::OsRng, Aes256Gcm, AeadCore, KeyInit};
let cipher = Aes256Gcm::new_from_slice(key)
.map_err(|e| VeilError::Serialization(e.to_string()))?;
let nonce = Aes256Gcm::generate_nonce(&mut OsRng);
let ciphertext = cipher
.encrypt(&nonce, plaintext)
.map_err(|_| VeilError::SealMismatch)?;
let mut output = Vec::with_capacity(12 + ciphertext.len());
output.extend_from_slice(&nonce);
output.extend_from_slice(&ciphertext);
Ok(output)
}
pub(crate) fn aes_gcm_decrypt(key: &[u8], sealed: &[u8]) -> Result<Vec<u8>> {
use aes_gcm::{aead::Aead, Aes256Gcm, KeyInit, Nonce};
if sealed.len() < 12 {
return Err(VeilError::InvalidKey);
}
let cipher = Aes256Gcm::new_from_slice(key)
.map_err(|e| VeilError::Serialization(e.to_string()))?;
let nonce = Nonce::from_slice(&sealed[..12]);
let ciphertext = &sealed[12..];
cipher
.decrypt(nonce, ciphertext)
.map_err(|_| VeilError::SealMismatch)
}
struct DerivedKey {
key: Vec<u8>,
salt: [u8; 16],
}
fn derive_key_from_passphrase(passphrase: &[u8]) -> Result<DerivedKey> {
let mut salt = [0u8; 16];
getrandom::getrandom(&mut salt)
.map_err(|e| VeilError::RecoveryFailed(format!("random salt generation failed: {e}")))?;
derive_key_with_salt(passphrase, &salt)
}
fn derive_key_with_salt(passphrase: &[u8], salt: &[u8]) -> Result<DerivedKey> {
use argon2::Argon2;
let mut key = vec![0u8; 32];
let argon2 = Argon2::default();
argon2
.hash_password_into(passphrase, salt, &mut key)
.map_err(|e| VeilError::RecoveryFailed(format!("key derivation failed: {e}")))?;
let mut salt_arr = [0u8; 16];
salt_arr.copy_from_slice(&salt[..16]);
Ok(DerivedKey {
key,
salt: salt_arr,
})
}