feat(storage): per-app/owner/system encryption live: gatekeeper hands seed + fresh flag, storage unlocks layer, init seeds areas from factory/ (empties on new key); rmdir op; test-qemu checks sealed home + reboot persistence

This commit is contained in:
2026-10-28 00:07:41 +01:00
parent e8191be160
commit 9dc751c466
20 changed files with 344 additions and 72 deletions

View File

@@ -50,9 +50,11 @@ pub struct StorageServer<F: FileSystem, H: Host> {
clients: [Option<Client>; MAX_CLIENTS],
owners: [Option<Owner>; MAX_HANDLES],
log: Option<CapSlot>,
/// The master key's halves, from the gatekeeper through init: what the
/// disk's encryption will be keyed with.
/// The master key's and the seed's halves, from the gatekeeper through
/// init; handed to the file system (and forgotten here) once all in.
key: [Option<[usize; 2]>; 2],
seed: [Option<[usize; 2]>; 2],
unlocked: bool,
}
impl<F: FileSystem, H: Host> StorageServer<F, H> {
@@ -65,6 +67,8 @@ impl<F: FileSystem, H: Host> StorageServer<F, H> {
owners: [None; MAX_HANDLES],
log: None,
key: [None; 2],
seed: [None; 2],
unlocked: false,
}
}
@@ -88,20 +92,29 @@ impl<F: FileSystem, H: Host> StorageServer<F, H> {
}
/// Only the unbadged holder (init), each half only once: nobody can
/// replace the key later.
fn set_key(&mut self, badge: usize, half: usize, words: [usize; 2]) -> StorageResponse {
match (badge, self.key.get_mut(half)) {
(0, Some(slot @ None)) => {
*slot = Some(words);
StorageResponse::Ok
}
_ => StorageResponse::Error,
/// replace the key or the seed later.
fn set_half(&mut self, badge: usize, seed: bool, half: usize, words: [usize; 2]) -> StorageResponse {
let halves = if seed { &mut self.seed } else { &mut self.key };
match (badge, self.unlocked, halves.get_mut(half)) {
(0, false, Some(slot @ None)) => *slot = Some(words),
_ => return StorageResponse::Error,
}
if let ([Some(k0), Some(k1)], [Some(s0), Some(s1)]) = (self.key, self.seed) {
let bytes = |w: [usize; 4]| -> [u8; 32] { core::array::from_fn(|i| w[i / 8].to_le_bytes()[i % 8]) };
let (mut master, mut seed) = (bytes([k0[0], k0[1], k1[0], k1[1]]), bytes([s0[0], s0[1], s1[0], s1[1]]));
self.fs.unlock(&master, &seed);
master.fill(0);
seed.fill(0);
self.key = [None; 2];
self.seed = [None; 2];
self.unlocked = true;
}
StorageResponse::Ok
}
/// Whether the whole master key has arrived.
/// Whether the encrypted areas are open: the key and seed are in.
pub fn unlocked(&self) -> bool {
self.key.iter().all(Option::is_some)
self.unlocked
}
pub fn filesystem(&self) -> &F {
@@ -225,7 +238,7 @@ impl<F: FileSystem, H: Host> StorageServer<F, H> {
Write { handle, .. } => self
.owner(handle)
.is_some_and(|o| o.badge == badge && o.write),
Create { .. } | Delete { .. } | Mkdir { .. } => scope.write,
Create { .. } | Delete { .. } | Mkdir { .. } | Rmdir { .. } => scope.write,
_ => true,
};
if !allowed {
@@ -233,7 +246,7 @@ impl<F: FileSystem, H: Host> StorageServer<F, H> {
}
let mutates = matches!(
request,
Write { .. } | Create { .. } | Delete { .. } | Mkdir { .. }
Write { .. } | Create { .. } | Delete { .. } | Mkdir { .. } | Rmdir { .. }
);
if mutates && !self.fs.writable() {
return (R::ReadOnly, None);
@@ -263,6 +276,7 @@ impl<F: FileSystem, H: Host> StorageServer<F, H> {
| Create { path_len }
| Delete { path_len }
| Mkdir { path_len }
| Rmdir { path_len }
| ListDir { path_len } => Some(path_len),
_ => None,
};
@@ -286,7 +300,7 @@ impl<F: FileSystem, H: Host> StorageServer<F, H> {
None => R::NotFound,
};
let response = match request {
Attach | SetLog | SetKey { .. } | Release => R::Error,
Attach | SetLog | SetKey { .. } | SetSeed { .. } | Release => R::Error,
Grant { path_len, write } => {
let narrower = scope.narrow(&buf[sent(path_len)], write);
return match narrower.and_then(|s| grants.add(badge, s, |b| host.mint(b))) {
@@ -353,6 +367,13 @@ impl<F: FileSystem, H: Host> StorageServer<F, H> {
R::Error
}
}
Rmdir { .. } => {
if fs.rmdir(path) {
R::Ok
} else {
R::Error
}
}
};
(response, None)
}
@@ -389,7 +410,8 @@ impl<F: FileSystem, H: Host> Server for StorageServer<F, H> {
) -> ([usize; 4], Option<Give>) {
match StorageRequest::decode(words) {
Some(StorageRequest::SetLog) => (self.set_log(badge, cap).encode(), None),
Some(StorageRequest::SetKey { half, words }) => (self.set_key(badge, half, words).encode(), None),
Some(StorageRequest::SetKey { half, words }) => (self.set_half(badge, false, half, words).encode(), None),
Some(StorageRequest::SetSeed { half, words }) => (self.set_half(badge, true, half, words).encode(), None),
// A badge storage never issued, or one it has taken back.
Some(request) => match self.grants.scope(badge) {
None => (StorageResponse::Denied.encode(), None),