use crate::error::{FnoxError, Result};
use aes_gcm::aead::{Aead, KeyInit};
use aes_gcm::{Aes256Gcm, Nonce};
use hkdf::Hkdf;
use sha2::Sha256;
fn derive_key(hw_secret: &[u8], context: &[u8]) -> [u8; 32] {
let hk = Hkdf::<Sha256>::new(None, hw_secret);
let mut key = [0u8; 32];
hk.expand(context, &mut key)
.expect("HKDF-SHA256 expand should not fail for 32 bytes");
key
}
pub fn encrypt(hw_secret: &[u8], context: &[u8], plaintext: &str) -> Result<String> {
let key_bytes = derive_key(hw_secret, context);
let cipher = Aes256Gcm::new_from_slice(&key_bytes)
.map_err(|e| FnoxError::Provider(format!("Failed to create AES-256-GCM cipher: {}", e)))?;
let mut nonce_bytes = [0u8; 12];
rand::fill(&mut nonce_bytes);
let nonce = Nonce::from(nonce_bytes);
let ciphertext = cipher
.encrypt(&nonce, plaintext.as_bytes())
.map_err(|e| FnoxError::Provider(format!("AES-256-GCM encryption failed: {}", e)))?;
let mut output = Vec::with_capacity(12 + ciphertext.len());
output.extend_from_slice(&nonce);
output.extend_from_slice(&ciphertext);
use base64::Engine;
Ok(base64::engine::general_purpose::STANDARD.encode(&output))
}
pub fn decrypt(hw_secret: &[u8], context: &[u8], encrypted: &str) -> Result<String> {
let key_bytes = derive_key(hw_secret, context);
let cipher = Aes256Gcm::new_from_slice(&key_bytes)
.map_err(|e| FnoxError::Provider(format!("Failed to create AES-256-GCM cipher: {}", e)))?;
use base64::Engine;
let data = base64::engine::general_purpose::STANDARD
.decode(encrypted)
.map_err(|e| FnoxError::Provider(format!("Invalid base64: {}", e)))?;
if data.len() < 12 {
return Err(FnoxError::Provider(
"Encrypted data too short (missing nonce)".to_string(),
));
}
let (nonce_bytes, ciphertext) = data.split_at(12);
let nonce_arr: [u8; 12] = nonce_bytes
.try_into()
.map_err(|_| FnoxError::Provider("Invalid nonce length".to_string()))?;
let nonce = Nonce::from(nonce_arr);
let plaintext = cipher.decrypt(&nonce, ciphertext).map_err(|_| {
FnoxError::Provider("Decryption failed — wrong hardware key or corrupted data".to_string())
})?;
String::from_utf8(plaintext)
.map_err(|e| FnoxError::Provider(format!("Decrypted data is not valid UTF-8: {}", e)))
}