Skip to content

Recovery key decided by the boot package, encrypt.state kept on the kept slots - #553

Merged
maralcbr merged 5 commits into
quattro-upstreamfrom
mac/33-recovery-passphrase
Sep 25, 2026
Merged

maralcbr merged 5 commits into
quattro-upstreamfrom
mac/33-recovery-passphrase

Conversation

@maralcbr

@maralcbr maralcbr commented Sep 25, 2026 •

Copy link
Copy Markdown
Collaborator

Ticket 33: recovery passphrase and temporary-key cleanup under interruption.

What changed

  • Where the recovery-key decision lives. Apple owner provisioning through lifecycle dispatch entrypoints #545 left recovery_key_offered() { omarchy-hw-apple-silicon; } in owner provisioning. It now asks the boot package: a platform whose boot package implements the new dispatch operation luks-slots gets a recovery passphrase. omarchy-mac-boot implements it (its boot check proves an owner and a recovery slot), so Macs keep the key and x86 first boot is unchanged, with no extra screen. The mechanism (journal, screen, slot operations) stays core. I kept it out of x86 because the spec lists it as an mx-mac port rather than a generic improvement, and turning it on everywhere would change x86 OEM and factory-reset first boot and break the x86 provision VM gate's harness.
  • Recovery step under interruption. prepare_luks_recovery adds the key with the staged install key before the worker runs. The journal records the reserved slot, then recovery_shown=0 once the key is added, then recovery_shown=1 once the owner acknowledges it. On a retry an acknowledged key is kept. One that was added but never acknowledged is revoked and replaced in its slot, and the owner is told to replace any copy they wrote down. The owner's slot is now only the re-key's step, so a retry can still choose a new password while the staged key opens the disk, as on x86. The recovery key is refused as the password.
  • encrypt.state stays in sync. luks-slots owner=<n> [recovery=<n>] (required on Apple) rewrites owner_slot/recovery_slot in /boot/omarchy/encrypt.state after checking the LUKS header (Keyslots section only) holds them. The shared re-key calls it after it has verified the kept slots and before it destroys the staged key. omarchy-drive-password calls it with sudo after a system-disk change, and only when it resolves, before it drops its journal. Without this, a key that landed in another slot left the boot check failing. On cryptsetup 2.8.8, luksChangeKey moves a LUKS1 key to the first free slot and keeps a LUKS2 key in place, and an interrupted change can also finish in the new slot.
  • omarchy-drive-password refuses the recovery key as the current system-disk password, and a new password in its form, so the recovery slot is never the one that changes. It resolves luks-slots before touching the system disk, so a boot package too old to record the slot stops the change up front. A Mac without omarchy-mac-boot at all (dispatch exit 3, as Post-update boot verification blocks update completion and the reboot #543 treats it) changes its password and records nothing.
  • No new key check: every one goes through luks_slot_for/luks_dump_slots, which Security: keep enrolled LUKS2 tokens from passing LUKS key checks #549 moved to --token-type passphrase-only. The new luksDump parse in omarchy-mac-boot reads only the Keyslots section. Security: keep enrolled LUKS2 tokens from passing LUKS key checks #549's token tests are adapted to the new order: the owner's slot now comes from the re-key, not the recovery step.

Version skew: luks-slots is required on Apple. With a runtime that has this change, an omarchy-mac-boot older than it stops owner setup before the form and refuses a system-disk password change, both with the dispatcher's error naming the package. The published 20260925-2 needs a new build from this branch before the runtime ships to Macs.

Testing

  • test/shell.d/luks-rekey-journal-test.sh now runs the whole setup attempt (recovery step, then worker) and kills it after every durable step: 17 steps on Apple, 11 on x86. It runs on the slot-table fake and on real file-backed LUKS2 and LUKS1 volumes (cryptsetup 2.8.8). Each kill is followed by retries with the same password, with a new one, and with the recovery key, which is refused. After every kill, an acknowledged recovery key opens its slot. Each finished run checks that the volume holds only the owner's slot and the last acknowledged recovery slot, that the recovery key unlocks the volume, that replaced keys, the staged key and the previous owner's key open nothing, that the staged key file and every boot-time unlock are gone, and that the recorded slots match the header. On x86 it checks that no slot is recorded and no recovery key is made.
  • test/shell.d/drive-password-test.sh reruns its kill-at-every-step matrix on an Apple fixture (fake, LUKS2, LUKS1) and checks that the recorded slot follows the owner's key. It also covers a failed record (the journal stays and the rerun finishes), an old boot package (refused before any change), a Mac without the boot package, recovery-key refusal, data drives, and x86 (no dispatch under sudo).
  • packages/omarchy-mac/boot/test/mac-provision-test.sh covers the real luks-slots entrypoint: it records and is idempotent, it refuses slots the header lacks (tokens included), bad arguments and unfinished conversions, and it does nothing on declined or unencrypted Macs. provision-owner-luks-test.sh runs the real entrypoints end to end, including a retry after the commit that changes the password and moves owner_slot.
  • ./test/all and packages/omarchy-mac/boot/test/all as a non-root user in an arm64 Arch container: the same failures as base and nothing new: hermes-remove, snapper and the omarchy-iso-checkout check need files the container lacks, and system-sleep-ownership-migration is flaky (it failed 5 of 8 runs on base). packages/omarchy-mac/boot/test/all passes. Merged with quattro-upstream at 89ef5df (Security: keep enrolled LUKS2 tokens from passing LUKS key checks #549, Post-update boot verification blocks update completion and the reboot #543, Early-load thunderbolt only where the kernel builds the module #551, Apple factory reset through lifecycle dispatch #552).
  • Not run: hardware. The M2 was only inspected (read-only). A real check needs a fresh encrypted install: after setup, sudo cryptsetup open --test-passphrase --token-type passphrase-only /dev/disk/by-uuid/<luks_uuid> with the recovery key, sudo omarchy-apple-silicon-boot-check, then omarchy-drive-password followed by the boot check again.

Second review: it found an acknowledged recovery slot that went missing, which a kill after the re-add could leave holding a key the owner never saw. It also found a disk password change that an old boot package could leave unfinishable, and a failed luksDump read as a missing key. All three are fixed, and the re-check confirmed the fixes with repro scripts. It judged x86 unchanged, no secrets in argv, the journal or logs, and the crash matrix sound. One product gap is left as a follow-up: an owner who has only the recovery key cannot set a new password, since sudo needs the forgotten one.

…n the kept slots

Owner provisioning offers a recovery passphrase where the platform's boot
package implements the new luks-slots dispatch operation, instead of asking
omarchy-hw-apple-silicon: omarchy-mac-boot records the owner's and the
recovery slot for its boot check, so Macs keep the key and x86 first boot is
unchanged. The recovery key is added with the staged install key before the
worker, journaled as reserved, added, shown and acknowledged, so a retry keeps
an acknowledged key, replaces one that was never acknowledged, and may still
choose a new password while the staged key opens the disk. The recovery key is
refused as the password.

The re-key records its final slots through luks-slots before it destroys the
staged key, and omarchy-drive-password records the owner's slot after a system
disk change before it drops its journal, so encrypt.state no longer goes stale
when a key lands in another slot. The drive password change refuses the
recovery key as the current password and a new password in its form.
… disk change

A recovery slot the journal called acknowledged but the header lost is now
journaled as unacknowledged before its replacement goes in, so a retry after a
kill there replaces it again instead of keeping a key the owner never saw.

omarchy-drive-password resolves luks-slots before it changes the system disk,
so a boot package too old to record the slot stops the change up front rather
than leaving a journal only an update can finish. Owner setup names the
recovery key when it is typed as the password, and says why an attempt stops
when dispatch cannot tell whether to add one.
@maralcbr
maralcbr merged commit d310724 into quattro-upstream Sep 25, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant