use std::future::Future;
use std::pin::Pin;
use std::sync::{Arc, Mutex};
use vti_common::acl::{AclEntry, Role, delete_acl_entry, store_acl_entry};
use vti_common::config::StoreConfig;
use vti_common::error::AppError;
use vti_common::integrity::{AnchorCounter, BootOutcome, boot_verify_and_install, derive_mac_key};
use vti_common::store::{KeyspaceHandle, Store, counter};
struct MockCounter(Mutex<Option<u64>>);
impl MockCounter {
fn new() -> Arc<Self> {
Arc::new(Self(Mutex::new(None)))
}
fn current(&self) -> Option<u64> {
*self.0.lock().unwrap()
}
}
impl AnchorCounter for MockCounter {
fn read(&self) -> Pin<Box<dyn Future<Output = Result<Option<u64>, AppError>> + Send + '_>> {
let v = *self.0.lock().unwrap();
Box::pin(async move { Ok(v) })
}
fn init(
&self,
version: u64,
_digest: [u8; 32],
) -> Pin<Box<dyn Future<Output = Result<(), AppError>> + Send + '_>> {
let mut g = self.0.lock().unwrap();
let r = if g.is_some() {
Err(AppError::Conflict("exists".into()))
} else {
*g = Some(version);
Ok(())
};
Box::pin(async move { r })
}
fn set(
&self,
expected: u64,
new: u64,
_digest: [u8; 32],
) -> Pin<Box<dyn Future<Output = Result<(), AppError>> + Send + '_>> {
let mut g = self.0.lock().unwrap();
let r = if *g == Some(expected) {
*g = Some(new);
Ok(())
} else {
Err(AppError::Conflict("CAS".into()))
};
Box::pin(async move { r })
}
}
struct Env {
keys: KeyspaceHandle,
bootstrap: KeyspaceHandle,
acl: KeyspaceHandle,
contexts: KeyspaceHandle,
mac_key: [u8; 32],
counter: Arc<MockCounter>,
_dir: tempfile::TempDir,
}
fn open() -> Env {
let dir = tempfile::tempdir().unwrap();
let store = Store::open(&StoreConfig {
data_dir: dir.path().to_path_buf(),
})
.unwrap();
Env {
keys: store.keyspace("keys").unwrap(),
bootstrap: store.keyspace("bootstrap").unwrap(),
acl: store.keyspace("acl").unwrap(),
contexts: store.keyspace("contexts").unwrap(),
mac_key: derive_mac_key(&[0x5Au8; 32]),
counter: MockCounter::new(),
_dir: dir,
}
}
async fn boot(env: &Env, allow_init: bool) -> Result<BootOutcome, AppError> {
boot_verify_and_install(
env.mac_key,
env.keys.clone(),
env.bootstrap.clone(),
env.acl.clone(),
env.contexts.clone(),
Some(env.counter.clone()),
allow_init,
false,
)
.await
}
#[tokio::test]
async fn anchor_end_to_end_through_chokepoints() {
let env = open();
env.keys
.insert_raw("tee:bootstrap-carveout-closed", b"closed".to_vec())
.await
.unwrap();
counter::allocate_u32(&env.keys, "path_counter:m/26'/0'")
.await
.unwrap();
assert_eq!(boot(&env, true).await.unwrap(), BootOutcome::Baselined);
assert_eq!(env.counter.current(), Some(0), "counter initialized at v0");
let alice = AclEntry::new("did:key:zAlice", Role::Admin, "test");
store_acl_entry(&env.acl, &alice).await.unwrap(); counter::allocate_u32(&env.contexts, "ctx_counter")
.await
.unwrap(); assert_eq!(
env.counter.current(),
Some(2),
"each covered mutation advanced the external counter"
);
assert_eq!(boot(&env, false).await.unwrap(), BootOutcome::Verified);
env.acl.remove("acl:did:key:zAlice").await.unwrap();
let err = boot(&env, false)
.await
.expect_err("out-of-band ACL deletion must be detected at boot");
let msg = format!("{err:?}");
assert!(msg.contains("mismatch"), "{msg}");
assert!(msg.contains("ACL root"), "{msg}");
delete_acl_entry(&env.acl, "did:key:zMissing")
.await
.unwrap(); assert_eq!(env.counter.current(), Some(3));
assert_eq!(boot(&env, false).await.unwrap(), BootOutcome::Verified);
}