diff --git a/crates/idfoto-core/src/vault.rs b/crates/idfoto-core/src/vault.rs new file mode 100644 index 0000000..d496cf7 --- /dev/null +++ b/crates/idfoto-core/src/vault.rs @@ -0,0 +1,99 @@ +use crate::crypto; +use crate::entry::{Entry, Manifest}; +use crate::error::Result; + +pub fn encrypt_entry(master_key: &[u8; 32], entry: &Entry) -> Result> { + let json = serde_json::to_vec(entry)?; + crypto::encrypt(master_key, &json) +} + +pub fn decrypt_entry(master_key: &[u8; 32], data: &[u8]) -> Result { + let json = crypto::decrypt(master_key, data)?; + let entry: Entry = serde_json::from_slice(&json)?; + Ok(entry) +} + +pub fn encrypt_manifest(master_key: &[u8; 32], manifest: &Manifest) -> Result> { + let json = serde_json::to_vec(manifest)?; + crypto::encrypt(master_key, &json) +} + +pub fn decrypt_manifest(master_key: &[u8; 32], data: &[u8]) -> Result { + let json = crypto::decrypt(master_key, data)?; + let manifest: Manifest = serde_json::from_slice(&json)?; + Ok(manifest) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::entry::ManifestEntry; + + fn test_key_a() -> [u8; 32] { + [0x42u8; 32] + } + + fn test_key_b() -> [u8; 32] { + [0x99u8; 32] + } + + fn sample_entry() -> Entry { + Entry { + name: "GitHub".to_string(), + url: Some("https://github.com".to_string()), + username: Some("alice".to_string()), + password: "secret123".to_string(), + notes: None, + totp_secret: None, + created_at: "2024-01-01T00:00:00Z".to_string(), + updated_at: "2024-01-01T00:00:00Z".to_string(), + } + } + + #[test] + fn entry_encrypt_decrypt_round_trip() { + let key = test_key_a(); + let entry = sample_entry(); + + let ciphertext = encrypt_entry(&key, &entry).unwrap(); + let decoded = decrypt_entry(&key, &ciphertext).unwrap(); + + assert_eq!(decoded.name, "GitHub"); + assert_eq!(decoded.password, "secret123"); + assert_eq!(decoded.username, Some("alice".to_string())); + } + + #[test] + fn manifest_encrypt_decrypt_round_trip() { + let key = test_key_a(); + let mut manifest = Manifest::new(); + manifest.add_entry( + "deadbeef".to_string(), + ManifestEntry { + name: "GitHub".to_string(), + url: Some("https://github.com".to_string()), + username: Some("alice".to_string()), + updated_at: "2024-01-01T00:00:00Z".to_string(), + }, + ); + + let ciphertext = encrypt_manifest(&key, &manifest).unwrap(); + let decoded = decrypt_manifest(&key, &ciphertext).unwrap(); + + assert_eq!(decoded.version, 1); + assert!(decoded.entries.contains_key("deadbeef")); + assert_eq!(decoded.entries["deadbeef"].name, "GitHub"); + } + + #[test] + fn entry_wrong_key_fails() { + let key_a = test_key_a(); + let key_b = test_key_b(); + let entry = sample_entry(); + + let ciphertext = encrypt_entry(&key_a, &entry).unwrap(); + let result = decrypt_entry(&key_b, &ciphertext); + + assert!(result.is_err()); + } +}