feat(users): keystore v2 (8 slots, per-user salt+name tag, owner admin key opens sealed user dir, per-user duress); gatekeeper login by name+passcode, owner menu (add/remove/guests), passcode change, guest sessions

This commit is contained in:
2026-10-28 00:29:07 +01:00
parent 9dc751c466
commit 29a25bd8c2
9 changed files with 1232 additions and 417 deletions

View File

@@ -18,7 +18,7 @@ use alloc::vec;
use keystore::{Block, KdfParams};
use libkos::syscall::{self, Received};
use types::bounded::CapSlot;
use types::gatekeeper::{KEY, SEED, UNLOCK, reply};
use types::gatekeeper::{KEY, NAME, REMOVED, SEED, UNLOCK, reply, unlocked};
/// Where init's manifest put them: its own endpoint (devices arrive on
/// it, then init's calls), then the terminal.
@@ -67,18 +67,48 @@ pub fn run() -> ! {
}
drop(memory);
match unlocked {
Ok(flow::Unlocked { key, fresh }) => {
Ok(flow::Unlocked { user, key, fresh, removed }) => {
libkos::println!("gatekeeper: unlocked");
libkos::terminal::clear(TERMINAL);
let words = |b: &[u8; 32]| core::array::from_fn::<usize, 4, _>(|i| usize::from_le_bytes(b[i * 8..i * 8 + 8].try_into().unwrap_or([0; 8])));
let mut seed = [0u8; 32];
let seeded = keystore::Random::fill(&mut random, &mut seed);
if seeded && answer(UNLOCK, [reply::OK, usize::from(fresh), 0, 0]) && answer(KEY, words(&key.0)) {
let _ = answer(SEED, words(&seed));
} else if !seeded {
if !keystore::Random::fill(&mut random, &mut seed) {
let _ = answer(UNLOCK, [reply::FAILED, 0, 0, 0]);
syscall::exit();
}
let flags = if fresh { unlocked::FRESH } else { 0 } | if user.is_none() { unlocked::GUEST } else { 0 };
let id = user.map_or(0, |u| usize::from_le_bytes(u.id.0));
let role = match user.map(|u| u.role) {
Some(keystore::Role::Owner) => unlocked::OWNER,
Some(keystore::Role::Standard) => unlocked::STANDARD,
None => 0,
};
let mut name_buf = [0u8; 16];
let len = match &user {
Some(u) => {
let n = u.name.as_bytes();
name_buf[..n.len()].copy_from_slice(n);
n.len()
}
None => {
name_buf[..5].copy_from_slice(b"guest");
5
}
};
let name = &name_buf[..len];
let handed = answer(UNLOCK, [reply::OK, flags, id, role])
&& answer(KEY, words(&key.0))
&& answer(SEED, words(&seed))
&& answer(NAME, types::log::text_words(name));
seed.fill(0);
if handed {
for gone in removed.iter().flatten() {
if !answer(REMOVED, [reply::OK, usize::from_le_bytes(gone.0), 0, 0]) {
break;
}
}
let _ = answer(REMOVED, [reply::DONE, 0, 0, 0]);
}
}
Err(why) => {
libkos::println!("gatekeeper: cannot go on: {:?}", why);