1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
//! Persistent backing for the sealed-transfer `NonceStore` trait.
//!
//! Stores one row per sealed `bundle_id` under the `sealed-nonce:<hex>` key
//! prefix in a dedicated keyspace, so repeated seals of the same request
//! (typically: the operator re-running `vta bootstrap seal` after a network
//! glitch, or a replayed Mode A / Mode B request) are rejected with
//! [`SealedTransferError::NonceReplay`].
//!
//! Mode A token consumption and Mode B carve-out sentinels already prevent
//! cross-restart replay at the policy layer — this store is belt-and-
//! suspenders for anyone who calls `seal_payload` directly and a meaningful
//! guard for the Mode C offline CLI where there is no other anti-replay state.
use std::future::Future;
use std::pin::Pin;
use tokio::sync::Mutex;
use vta_sdk::sealed_transfer::{NonceStore, SealedTransferError};
use crate::store::KeyspaceHandle;
const KEY_PREFIX: &str = "sealed-nonce:";
/// Fjall-backed (or vsock-backed) persistent nonce store. Any
/// [`KeyspaceHandle`] will do; the TEE-mode service uses an unencrypted
/// keyspace since the bundle_id itself is not secret.
pub struct PersistentNonceStore {
ks: KeyspaceHandle,
/// Serialises the check-and-record critical section across threads so
/// two concurrent `seal_payload` calls with the same bundle_id cannot
/// both pass the absence check. Callers hit the store very rarely
/// (at most a few bootstrap flows per minute) so a mutex is fine.
lock: Mutex<()>,
}
impl PersistentNonceStore {
pub fn new(ks: KeyspaceHandle) -> Self {
Self {
ks,
lock: Mutex::new(()),
}
}
fn key(bundle_id: &[u8; 16]) -> String {
let mut s = String::with_capacity(KEY_PREFIX.len() + 32);
s.push_str(KEY_PREFIX);
const T: &[u8; 16] = b"0123456789abcdef";
for &b in bundle_id {
s.push(T[(b >> 4) as usize] as char);
s.push(T[(b & 0xf) as usize] as char);
}
s
}
}
impl NonceStore for PersistentNonceStore {
fn check_and_record<'a>(
&'a self,
bundle_id: &'a [u8; 16],
) -> Pin<Box<dyn Future<Output = Result<(), SealedTransferError>> + Send + 'a>> {
let key = Self::key(bundle_id);
Box::pin(async move {
let _guard = self.lock.lock().await;
if self
.ks
.get_raw(key.clone())
.await
.map_err(|e| SealedTransferError::NonceStore(e.to_string()))?
.is_some()
{
return Err(SealedTransferError::NonceReplay);
}
self.ks
.insert_raw(key, b"1".to_vec())
.await
.map_err(|e| SealedTransferError::NonceStore(e.to_string()))?;
Ok(())
})
}
}