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:
@@ -76,6 +76,6 @@ pub fn run() -> ! {
|
||||
libkos::println!("storage: no ext4 filesystem it can read, exiting");
|
||||
syscall::exit();
|
||||
};
|
||||
let mut srv = server::StorageServer::new(ext4, Kernel);
|
||||
let mut srv = server::StorageServer::new(encrypted::Encrypted::new(ext4), Kernel);
|
||||
server_framework::serve(&mut srv, ep)
|
||||
}
|
||||
|
||||
@@ -27,6 +27,9 @@ pub enum StorageRequest {
|
||||
SetLog,
|
||||
/// Half (`half`: 0 or 1) of the master key.
|
||||
SetKey { half: usize, words: [usize; 2] },
|
||||
/// Half of the seed for randomness.
|
||||
SetSeed { half: usize, words: [usize; 2] },
|
||||
Rmdir { path_len: usize },
|
||||
/// A capability to the path (`path_len` bytes of the buffer), inside
|
||||
/// the caller's scope.
|
||||
Grant {
|
||||
@@ -77,6 +80,8 @@ impl Encode for StorageRequest {
|
||||
Mkdir { path_len } => [op::MKDIR, path_len, 0, 0],
|
||||
SetLog => [op::SET_LOG, 0, 0, 0],
|
||||
SetKey { half, words } => [op::SET_KEY, half, words[0], words[1]],
|
||||
SetSeed { half, words } => [op::SET_SEED, half, words[0], words[1]],
|
||||
Rmdir { path_len } => [op::RMDIR, path_len, 0, 0],
|
||||
Grant { path_len, write } => [op::GRANT, path_len, usize::from(write), 0],
|
||||
Release => [op::RELEASE, 0, 0, 0],
|
||||
}
|
||||
@@ -99,6 +104,8 @@ impl Decode for StorageRequest {
|
||||
op::MKDIR => Mkdir { path_len: w[1] },
|
||||
op::SET_LOG => SetLog,
|
||||
op::SET_KEY => SetKey { half: w[1], words: [w[2], w[3]] },
|
||||
op::SET_SEED => SetSeed { half: w[1], words: [w[2], w[3]] },
|
||||
op::RMDIR => Rmdir { path_len: w[1] },
|
||||
op::GRANT => Grant {
|
||||
path_len: w[1],
|
||||
write: w[2] != 0,
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -26,6 +26,8 @@ fn every_request_roundtrips() {
|
||||
},
|
||||
StorageRequest::Release,
|
||||
StorageRequest::SetKey { half: 1, words: [7, 9] },
|
||||
StorageRequest::SetSeed { half: 0, words: [1, 2] },
|
||||
StorageRequest::Rmdir { path_len: 3 },
|
||||
] {
|
||||
assert_eq!(StorageRequest::decode(req.encode()), Some(req));
|
||||
}
|
||||
|
||||
@@ -474,14 +474,19 @@ fn the_unbadged_holder_cannot_be_released() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn only_init_sets_the_key_and_only_once() {
|
||||
fn only_init_unlocks_and_only_once() {
|
||||
let mut s = server();
|
||||
let set = |s: &mut Srv, badge, half| call(s, badge, StorageRequest::SetKey { half, words: [1, 2] }).0;
|
||||
assert_eq!(set(&mut s, 5, 0), StorageResponse::Error, "a client cannot");
|
||||
assert_eq!(set(&mut s, 0, 0), StorageResponse::Ok);
|
||||
assert!(!s.unlocked());
|
||||
assert_eq!(set(&mut s, 0, 0), StorageResponse::Error, "nor can it be replaced");
|
||||
assert_eq!(set(&mut s, 0, 2), StorageResponse::Error, "two halves only");
|
||||
assert_eq!(set(&mut s, 0, 1), StorageResponse::Ok);
|
||||
let set = |s: &mut Srv, badge, req| call(s, badge, req).0;
|
||||
let key = |half| StorageRequest::SetKey { half, words: [1, 2] };
|
||||
let seed = |half| StorageRequest::SetSeed { half, words: [3, 4] };
|
||||
assert_eq!(set(&mut s, 5, key(0)), StorageResponse::Error, "a client cannot");
|
||||
assert_eq!(set(&mut s, 0, key(0)), StorageResponse::Ok);
|
||||
assert_eq!(set(&mut s, 0, key(0)), StorageResponse::Error, "nor can a half be replaced");
|
||||
assert_eq!(set(&mut s, 0, key(2)), StorageResponse::Error, "two halves only");
|
||||
assert_eq!(set(&mut s, 0, key(1)), StorageResponse::Ok);
|
||||
assert_eq!(set(&mut s, 0, seed(0)), StorageResponse::Ok);
|
||||
assert!(!s.unlocked(), "not without the whole seed");
|
||||
assert_eq!(set(&mut s, 0, seed(1)), StorageResponse::Ok);
|
||||
assert!(s.unlocked());
|
||||
assert_eq!(set(&mut s, 0, key(0)), StorageResponse::Error, "once unlocked, never again");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user