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:
@@ -151,14 +151,21 @@ fn setup(ks: &mut impl Keystore, rng: &mut impl Random, prompt: &mut impl Prompt
|
||||
Ok(key)
|
||||
}
|
||||
|
||||
/// The owner is in: the master key, and whether it is new (a fresh
|
||||
/// device, or one just wiped), so nothing sealed under an old one exists.
|
||||
pub struct Unlocked {
|
||||
pub key: Key,
|
||||
pub fresh: bool,
|
||||
}
|
||||
|
||||
/// Authenticates the owner and returns the master key: setting one up on
|
||||
/// a fresh device, wiping on duress or too many failures (the device then
|
||||
/// looks fresh, and is set up anew).
|
||||
pub fn run(ks: &mut impl Keystore, rng: &mut impl Random, prompt: &mut impl Prompt, params: KdfParams, memory: &mut [Block]) -> Result<Key, Failure> {
|
||||
pub fn run(ks: &mut impl Keystore, rng: &mut impl Random, prompt: &mut impl Prompt, params: KdfParams, memory: &mut [Block]) -> Result<Unlocked, Failure> {
|
||||
loop {
|
||||
let (record, next) = load(ks)?;
|
||||
let Some(record) = record else {
|
||||
return setup(ks, rng, prompt, params, memory);
|
||||
return setup(ks, rng, prompt, params, memory).map(|key| Unlocked { key, fresh: true });
|
||||
};
|
||||
if record.must_wipe() {
|
||||
wipe(ks, &record, next, rng)?;
|
||||
@@ -174,7 +181,7 @@ pub fn run(ks: &mut impl Keystore, rng: &mut impl Random, prompt: &mut impl Prom
|
||||
match record.unlock(typed.as_bytes(), memory) {
|
||||
Outcome::Real(key) => {
|
||||
store(ks, 1 - next, &counted.next(|r| r.failures = 0))?;
|
||||
return Ok(key);
|
||||
return Ok(Unlocked { key, fresh: false });
|
||||
}
|
||||
Outcome::Duress => {
|
||||
// Recorded as worn out first: a wipe cut short resumes.
|
||||
|
||||
@@ -18,7 +18,7 @@ use alloc::vec;
|
||||
use keystore::{Block, KdfParams};
|
||||
use libkos::syscall::{self, Received};
|
||||
use types::bounded::CapSlot;
|
||||
use types::gatekeeper::{KEY, UNLOCK, reply};
|
||||
use types::gatekeeper::{KEY, SEED, UNLOCK, reply};
|
||||
|
||||
/// Where init's manifest put them: its own endpoint (devices arrive on
|
||||
/// it, then init's calls), then the terminal.
|
||||
@@ -53,9 +53,10 @@ pub fn run() -> ! {
|
||||
};
|
||||
let mut memory = vec![Block::default(); KdfParams::DEFAULT.memory_kib as usize];
|
||||
libkos::println!("gatekeeper: waiting for the owner");
|
||||
let key = flow::run(
|
||||
let mut random = devices::Hardware(random);
|
||||
let unlocked = flow::run(
|
||||
&mut devices::Disk(keystore),
|
||||
&mut devices::Hardware(random),
|
||||
&mut random,
|
||||
&mut devices::Terminal(TERMINAL),
|
||||
KdfParams::DEFAULT,
|
||||
&mut memory,
|
||||
@@ -65,14 +66,19 @@ pub fn run() -> ! {
|
||||
*b = Block::default();
|
||||
}
|
||||
drop(memory);
|
||||
match key {
|
||||
Ok(key) => {
|
||||
match unlocked {
|
||||
Ok(flow::Unlocked { key, fresh }) => {
|
||||
libkos::println!("gatekeeper: unlocked");
|
||||
libkos::terminal::clear(TERMINAL);
|
||||
let word = |i: usize| usize::from_le_bytes(key.0[i * 8..i * 8 + 8].try_into().unwrap_or([0; 8]));
|
||||
if answer(UNLOCK, [reply::OK, 0, 0, 0]) {
|
||||
let _ = answer(KEY, [word(0), word(1), word(2), word(3)]);
|
||||
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 {
|
||||
let _ = answer(UNLOCK, [reply::FAILED, 0, 0, 0]);
|
||||
}
|
||||
seed.fill(0);
|
||||
}
|
||||
Err(why) => {
|
||||
libkos::println!("gatekeeper: cannot go on: {:?}", why);
|
||||
|
||||
@@ -101,7 +101,11 @@ fn memory() -> Vec<Block> {
|
||||
}
|
||||
|
||||
fn run_with(dev: &mut Device, owner: &mut Owner) -> Result<keystore::Key, Failure> {
|
||||
run(dev, &mut Counter(5), owner, FAST, &mut memory())
|
||||
run(dev, &mut Counter(5), owner, FAST, &mut memory()).map(|u| u.key)
|
||||
}
|
||||
|
||||
fn fresh(dev: &mut Device, owner: &mut Owner) -> bool {
|
||||
run(dev, &mut Counter(5), owner, FAST, &mut memory()).unwrap().fresh
|
||||
}
|
||||
|
||||
/// A device set up with passcode 1234 and duress 0000; its master key.
|
||||
@@ -219,3 +223,11 @@ fn a_duress_whose_wipe_is_torn_still_wipes_next_boot() {
|
||||
dev.cut_after = None;
|
||||
assert!(dev.record().unwrap().must_wipe(), "the duress was recorded before the wipe began");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn only_a_new_master_key_counts_as_fresh() {
|
||||
let mut dev = Device::new();
|
||||
assert!(fresh(&mut dev, &mut Owner::types(&["1234", "1234", "0000", "0000"])), "first boot");
|
||||
assert!(!fresh(&mut dev, &mut Owner::types(&["1234"])), "an unlock");
|
||||
assert!(fresh(&mut dev, &mut Owner::types(&["0000", "5678", "5678", "", ""])), "after a duress wipe");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user