Per ADR-038, DerivedKey.private_key now derives Zeroize with #[zeroize(drop)] ensuring sensitive key material is zeroized before deallocation. DerivedKey is now move-only (no Clone), and JSON/debug output redacts private_key as "[REDACTED]". Deserialization still works for postcard/irpc wire format. Also fixes clippy needless_borrows_for_generic_args in encryption.rs and applies cargo fmt to existing code.
99 lines
2.8 KiB
Rust
99 lines
2.8 KiB
Rust
//! Integration tests for the SecretService lifecycle.
|
|
//!
|
|
//! These tests verify the unlock/lock lifecycle, error conditions,
|
|
//! and that the service correctly manages state transitions.
|
|
|
|
use alknet_secret::derivation::PATHS;
|
|
use alknet_secret::service::{SecretServiceError, SecretServiceHandle};
|
|
|
|
#[test]
|
|
fn test_full_lifecycle() {
|
|
let service = SecretServiceHandle::new();
|
|
|
|
// Starts locked
|
|
assert!(!service.is_unlocked());
|
|
|
|
// Can't derive while locked
|
|
let result = service.derive_ed25519(PATHS::IDENTITY);
|
|
assert!(matches!(result, Err(SecretServiceError::ServiceLocked)));
|
|
|
|
// Unlock
|
|
let phrase = service.unlock_new(24).unwrap();
|
|
assert!(service.is_unlocked());
|
|
assert!(!phrase.is_empty());
|
|
|
|
// Can derive while unlocked
|
|
let key = service.derive_ed25519(PATHS::IDENTITY).unwrap();
|
|
assert!(!key.private_key.is_empty());
|
|
|
|
// Lock
|
|
service.lock();
|
|
assert!(!service.is_unlocked());
|
|
|
|
// Can't derive again
|
|
let result = service.derive_ed25519(PATHS::IDENTITY);
|
|
assert!(matches!(result, Err(SecretServiceError::ServiceLocked)));
|
|
}
|
|
|
|
#[test]
|
|
fn test_unlock_with_known_phrase() {
|
|
let service = SecretServiceHandle::new();
|
|
|
|
// Generate a phrase
|
|
let phrase = service.unlock_new(24).unwrap();
|
|
service.lock();
|
|
|
|
// Re-unlock with the same phrase
|
|
service.unlock(&phrase, None).unwrap();
|
|
assert!(service.is_unlocked());
|
|
|
|
// Different passphrase produces different seed
|
|
// (tested by deriving keys with different passphrases)
|
|
}
|
|
|
|
#[test]
|
|
fn test_double_unlock_fails() {
|
|
let service = SecretServiceHandle::new();
|
|
service.unlock_new(24).unwrap();
|
|
|
|
let result = service.unlock_new(12);
|
|
assert!(matches!(result, Err(SecretServiceError::AlreadyUnlocked)));
|
|
}
|
|
|
|
#[test]
|
|
fn test_lock_when_already_locked_is_noop() {
|
|
let service = SecretServiceHandle::new();
|
|
assert!(!service.is_unlocked());
|
|
|
|
// Lock on already-locked service is a no-op
|
|
service.lock();
|
|
assert!(!service.is_unlocked());
|
|
}
|
|
|
|
#[test]
|
|
fn test_encrypt_decrypt_lifecycle() {
|
|
let service = SecretServiceHandle::new();
|
|
service.unlock_new(24).unwrap();
|
|
|
|
let plaintext = "my-api-key-12345";
|
|
let encrypted = service.encrypt(plaintext, 1).unwrap();
|
|
let decrypted = service.decrypt(&encrypted).unwrap();
|
|
assert_eq!(decrypted, plaintext);
|
|
|
|
// After lock, can't decrypt
|
|
service.lock();
|
|
let result = service.decrypt(&encrypted);
|
|
assert!(matches!(result, Err(SecretServiceError::ServiceLocked)));
|
|
}
|
|
|
|
#[test]
|
|
fn test_multiple_derive_paths_succeed() {
|
|
let service = SecretServiceHandle::new();
|
|
service.unlock_new(24).unwrap();
|
|
|
|
// All standard paths should work
|
|
let _identity = service.derive_ed25519(PATHS::IDENTITY).unwrap();
|
|
let _ssh = service.derive_ed25519(PATHS::SSH_HOST).unwrap();
|
|
let _enc = service.derive_encryption_key(PATHS::ENCRYPTION).unwrap();
|
|
}
|