Compare commits
3 Commits
ff50ccea09
...
e9d8896309
| Author | SHA1 | Date | |
|---|---|---|---|
| e9d8896309 | |||
| f413719971 | |||
| 389a9e93f7 |
@@ -206,6 +206,84 @@ impl Default for KeyCache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod drop_tracker {
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
struct DropTrackedKey {
|
||||||
|
flag: Arc<AtomicBool>,
|
||||||
|
bytes: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DropTrackedKey {
|
||||||
|
fn new(flag: &Arc<AtomicBool>) -> Self {
|
||||||
|
Self {
|
||||||
|
flag: flag.clone(),
|
||||||
|
bytes: vec![0xABu8; 32],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for DropTrackedKey {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
for b in self.bytes.iter_mut() {
|
||||||
|
*b = 0;
|
||||||
|
}
|
||||||
|
self.flag.store(true, Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_hashmap_clear_drops_values_triggering_drop_impls() {
|
||||||
|
let flag1 = Arc::new(AtomicBool::new(false));
|
||||||
|
let flag2 = Arc::new(AtomicBool::new(false));
|
||||||
|
let mut map: HashMap<String, DropTrackedKey> = HashMap::new();
|
||||||
|
map.insert("path1".to_string(), DropTrackedKey::new(&flag1));
|
||||||
|
map.insert("path2".to_string(), DropTrackedKey::new(&flag2));
|
||||||
|
|
||||||
|
assert!(!flag1.load(Ordering::SeqCst));
|
||||||
|
assert!(!flag2.load(Ordering::SeqCst));
|
||||||
|
|
||||||
|
map.clear();
|
||||||
|
|
||||||
|
assert!(flag1.load(Ordering::SeqCst));
|
||||||
|
assert!(flag2.load(Ordering::SeqCst));
|
||||||
|
assert!(map.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_hashmap_remove_drops_value_triggering_drop_impl() {
|
||||||
|
let flag = Arc::new(AtomicBool::new(false));
|
||||||
|
let mut map: HashMap<String, DropTrackedKey> = HashMap::new();
|
||||||
|
map.insert("path1".to_string(), DropTrackedKey::new(&flag));
|
||||||
|
|
||||||
|
assert!(!flag.load(Ordering::SeqCst));
|
||||||
|
|
||||||
|
map.remove("path1");
|
||||||
|
|
||||||
|
assert!(flag.load(Ordering::SeqCst));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_hashmap_insert_replace_drops_old_value() {
|
||||||
|
let flag_old = Arc::new(AtomicBool::new(false));
|
||||||
|
let mut map: HashMap<String, DropTrackedKey> = HashMap::new();
|
||||||
|
map.insert("path1".to_string(), DropTrackedKey::new(&flag_old));
|
||||||
|
|
||||||
|
assert!(!flag_old.load(Ordering::SeqCst));
|
||||||
|
|
||||||
|
let flag_new = Arc::new(AtomicBool::new(false));
|
||||||
|
map.insert("path1".to_string(), DropTrackedKey::new(&flag_new));
|
||||||
|
|
||||||
|
assert!(flag_old.load(Ordering::SeqCst));
|
||||||
|
assert!(!flag_new.load(Ordering::SeqCst));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -336,4 +414,56 @@ mod tests {
|
|||||||
assert_eq!(entry.private_key, vec![3u8; 32]);
|
assert_eq!(entry.private_key, vec![3u8; 32]);
|
||||||
assert_eq!(cache.len(), 1);
|
assert_eq!(cache.len(), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lru_eviction_drops_evicted_cached_key() {
|
||||||
|
let mut config = CacheConfig::default();
|
||||||
|
config.max_entries = 2;
|
||||||
|
|
||||||
|
let mut cache = KeyCache::new(config);
|
||||||
|
|
||||||
|
cache.insert("path1", make_cached_key(KeyType::Ed25519));
|
||||||
|
cache.insert("path2", make_cached_key(KeyType::Aes256Gcm));
|
||||||
|
assert_eq!(cache.len(), 2);
|
||||||
|
|
||||||
|
cache.insert("path3", make_cached_key(KeyType::Secp256k1));
|
||||||
|
|
||||||
|
assert_eq!(cache.len(), 2);
|
||||||
|
assert!(cache.get("path1").is_none());
|
||||||
|
assert!(cache.get("path2").is_some());
|
||||||
|
assert!(cache.get("path3").is_some());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ttl_expiry_evicts_entry_on_access() {
|
||||||
|
let mut config = CacheConfig::default();
|
||||||
|
config.ttl = Duration::from_millis(1);
|
||||||
|
|
||||||
|
let mut cache = KeyCache::new(config);
|
||||||
|
cache.insert("path1", make_cached_key(KeyType::Ed25519));
|
||||||
|
assert_eq!(cache.len(), 1);
|
||||||
|
|
||||||
|
std::thread::sleep(Duration::from_millis(5));
|
||||||
|
|
||||||
|
assert!(cache.get("path1").is_none());
|
||||||
|
assert_eq!(cache.len(), 0);
|
||||||
|
assert!(cache.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_clear_removes_all_entries_and_empties_cache() {
|
||||||
|
let mut cache = KeyCache::with_defaults();
|
||||||
|
cache.insert("path1", make_cached_key(KeyType::Ed25519));
|
||||||
|
cache.insert("path2", make_cached_key(KeyType::Aes256Gcm));
|
||||||
|
cache.insert("path3", make_cached_key(KeyType::Secp256k1));
|
||||||
|
assert_eq!(cache.len(), 3);
|
||||||
|
|
||||||
|
cache.clear();
|
||||||
|
|
||||||
|
assert_eq!(cache.len(), 0);
|
||||||
|
assert!(cache.is_empty());
|
||||||
|
assert!(cache.get("path1").is_none());
|
||||||
|
assert!(cache.get("path2").is_none());
|
||||||
|
assert!(cache.get("path3").is_none());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
id: vault/cache-zeroization-test
|
id: vault/cache-zeroization-test
|
||||||
name: Verify and test that HashMap::clear() drops CachedKey values triggering zeroization
|
name: Verify and test that HashMap::clear() drops CachedKey values triggering zeroization
|
||||||
status: pending
|
status: completed
|
||||||
depends_on: []
|
depends_on: []
|
||||||
scope: single
|
scope: single
|
||||||
risk: low
|
risk: low
|
||||||
@@ -82,4 +82,10 @@ file. It can run in parallel with drift #4.
|
|||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
> To be filled on completion
|
Added a `drop_tracker` test module proving `HashMap::clear()`/`remove()`/`insert`
|
||||||
|
(replace) drop values triggering their `Drop` impls, plus explicit tests for LRU
|
||||||
|
eviction (`test_lru_eviction_drops_evicted_cached_key`), TTL expiry
|
||||||
|
(`test_ttl_expiry_evicts_entry_on_access`), and `clear()`
|
||||||
|
(`test_clear_removes_all_entries_and_empties_cache`). The lock()-clears-cache
|
||||||
|
criterion is covered by existing `test_lock_clears_all_cache_entries` in
|
||||||
|
service.rs. All lib + integration tests pass; clippy clean. Merged to develop.
|
||||||
Reference in New Issue
Block a user