Files
alkvault/tests/encryption_tests.rs
T
glm-5.3-flash bcfb368b7b decrypt returns Zeroizing<String>; close the plaintext window (review 001)
Review 001 (consumer input from alkssh): VaultServiceHandle::decrypt
returned a bare heap String — decrypted secret material crossed the API
boundary with no zeroization guarantee, a window consumers could not
close from the consumer seat. Resolution paths 1+2 of the review.

- decrypt -> Result<Zeroizing<String>, VaultServiceError> (breaking;
  the unlock_new precedent for the same class of secret)
- encryption::decrypt (module-internal) -> Zeroizing<String>, zeroizes
  the decrypted buffer on all paths including the UTF-8 failure path,
  which previously leaked unzeroized bytes inside the error value
- rotate transits through the zeroizing decrypt output; no bare String
  during re-encryption
- test assertions use .as_str() for the explicit PartialEq
- docs: README example, service.md decrypt section, encryption.md
  internal signatures, review 001 closed as resolved
- CHANGELOG.md established (house format); version 0.1.0 -> 0.2.0

Verification: cargo test --all-features (108) and cargo test (101) pass,
clippy --all-features --all-targets clean, cargo doc clean,
cargo publish --dry-run clean at 0.2.0.
2026-11-04 06:08:08 +00:00

59 lines
1.9 KiB
Rust

//! Integration tests for AES-256-GCM encryption and decryption.
//!
//! These tests verify round-trip encryption, key version handling,
//! and wire format compatibility.
use alkvault::encryption::CURRENT_KEY_VERSION;
use alkvault::service::VaultServiceHandle;
#[test]
fn test_encrypt_decrypt_round_trip_via_service() {
let service = VaultServiceHandle::new();
service.unlock_new(24).unwrap();
let plaintext = "sk-proj-abc123xyz789";
let encrypted = service.encrypt(plaintext, CURRENT_KEY_VERSION).unwrap();
let decrypted = service.decrypt(&encrypted).unwrap();
assert_eq!(decrypted.as_str(), plaintext);
}
#[test]
fn test_encrypt_produces_different_ciphertext_each_time() {
let service = VaultServiceHandle::new();
service.unlock_new(24).unwrap();
let plaintext = "same input different ciphertexts";
let encrypted1 = service.encrypt(plaintext, CURRENT_KEY_VERSION).unwrap();
let encrypted2 = service.encrypt(plaintext, CURRENT_KEY_VERSION).unwrap();
// Different IVs mean different ciphertexts
assert_ne!(encrypted1.iv, encrypted2.iv);
assert_ne!(encrypted1.data, encrypted2.data);
// But same key version
assert_eq!(encrypted1.key_version, encrypted2.key_version);
}
#[test]
fn test_encrypted_data_serialization() {
let service = VaultServiceHandle::new();
service.unlock_new(24).unwrap();
let plaintext = "test serialization";
let encrypted = service.encrypt(plaintext, CURRENT_KEY_VERSION).unwrap();
// Verify EncryptedData serializes to JSON
let json = serde_json::to_string(&encrypted).unwrap();
assert!(json.contains("key_version"));
assert!(json.contains("salt"));
assert!(json.contains("iv"));
assert!(json.contains("data"));
// Verify round-trip through JSON
let deserialized: alkvault::encryption::EncryptedData =
serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, encrypted);
}