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

@@ -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.