* Revert "Merge pull request #11874 from acrogenesis/feature/usb-device-authorization" This reverts commit5cb31317b4, reversing changes made to9d48fc402e. * Undo USB and Thunderbolt device approval on machines that ran it * Verify boot rebuilds, recover Thunderbolt journals and prove USBGuard ownership in the removal migration * Read the boot partition from ESP_PATH and stop on failed Bolt reads in the removal migration * Recover Bolt's original mode from the setup journal in the removal migration
898 lines
41 KiB
Bash
Executable File
898 lines
41 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
set -euo pipefail
|
|
|
|
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
|
|
|
# Owner provisioning's LUKS re-key, killed after every durable step and rerun.
|
|
# Each attempt is its own process, like a reboot: omarchy-provision-owner's
|
|
# setup attempt from the accepted owner form on, the shared re-key, and a
|
|
# cryptsetup that is either a slot-table fake or the real binary on a
|
|
# file-backed LUKS2 or LUKS1 volume. The callbacks reach the platform through
|
|
# the real omarchy-lifecycle-dispatch: on an x86 fixture it is a no-op and the
|
|
# Limine UKI path runs; on an Apple fixture a fake boot package with
|
|
# provisioning and luks-slots entrypoints owns an unlock kept on a boot
|
|
# partition and the kernel command line and records the kept slot.
|
|
|
|
tmp=$(mktemp -d)
|
|
token_key=""
|
|
trap 'rm -rf "$tmp"; [[ -z $token_key ]] || keyctl unlink "$token_key" @s >/dev/null 2>&1 || true' EXIT
|
|
|
|
fake_platform "$tmp/x86" x86
|
|
fake_platform "$tmp/apple" aarch64-apple
|
|
platform=x86
|
|
|
|
# Root's dispatcher ignores the fixtures and sees this machine, which stands in
|
|
# for x86 only where no boot package is registered.
|
|
platforms=(x86 apple)
|
|
if (( EUID == 0 )); then
|
|
if [[ $("$ROOT/bin/omarchy-hw-platform") == "aarch64-apple" ]]; then
|
|
skip "running as root on Apple Silicon, where dispatch ignores fixtures; skipping"
|
|
exit 0
|
|
fi
|
|
platforms=(x86)
|
|
skip "running as root, where dispatch ignores fixtures; skipping the Apple runs"
|
|
fi
|
|
runtime=$ROOT
|
|
|
|
mac_boot=$tmp/lifecycle/usr/lib/omarchy/mac-boot
|
|
mkdir -p "$mac_boot"
|
|
cat >"$tmp/mac-boot-lib.sh" <<'SH'
|
|
ran() { echo "$1" >>"$TMP/mac-boot-ran"; }
|
|
crash() {
|
|
local count
|
|
count=$(( $(cat "$TMP/steps") + 1 ))
|
|
echo "$count" >"$TMP/steps"
|
|
printf '%s %s\n' "$count" "$1" >>"$TMP/trace"
|
|
if (( count == $(cat "$TMP/crash-at") )); then
|
|
kill -9 "$PPID"
|
|
kill -9 $$
|
|
fi
|
|
}
|
|
SH
|
|
mac_boot_entrypoint() {
|
|
{ printf '#!/bin/bash\nset -euo pipefail\nTMP=%q\nsource "$TMP/mac-boot-lib.sh"\nran %s\n' "$tmp" "$1"; cat; } >"$mac_boot/$1"
|
|
}
|
|
mac_boot_entrypoint provision-prepare <<'SH'
|
|
if [[ -e $TMP/prepare-fail ]]; then
|
|
echo "The boot partition is not mounted." >&2
|
|
exit 1
|
|
fi
|
|
SH
|
|
mac_boot_entrypoint provision-commit <<'SH'
|
|
[[ ! -e $TMP/rebuild-fail ]] || exit 1
|
|
rm -f "$TMP/boot/omarchy/luks-key"
|
|
crash "boot-partition key removed"
|
|
sed -i 's/ rd\.luks\.key=[^" ]*//' "$TMP/etc/default/grub"
|
|
echo rebuild >>"$TMP/rebuilds"
|
|
crash "boot rebuilt"
|
|
SH
|
|
mac_boot_entrypoint provision-verify <<'SH'
|
|
[[ ! -e $TMP/boot/omarchy/luks-key ]] && ! grep -q 'rd\.luks\.key=' "$TMP/etc/default/grub"
|
|
SH
|
|
mac_boot_entrypoint luks-slots <<'SH'
|
|
[[ ! -e $TMP/record-fail ]] || exit 1
|
|
printf '%s\n' "$@" >"$TMP/slot-record"
|
|
crash "slots recorded"
|
|
SH
|
|
chmod 755 "$mac_boot"/*
|
|
chmod -R go-w "$tmp/lifecycle"
|
|
|
|
staged_key=staged-install-key
|
|
seller_key=previous-owner-key
|
|
owner_password=owner-password
|
|
|
|
sed -n '/^PROVISIONING_UNLOCK_FILES=(/,/^)/p; /^UNLOCK_OWNER=/p; /^limine_auto_unlock_present() {/,/^}/p; /^limine_auto_unlock_drop() {/,/^}/p
|
|
/^unlock_owner() {/,/^}/p; /^luks_auto_unlock_present() {/,/^}/p; /^luks_auto_unlock_drop() {/,/^}/p
|
|
/^luks_record_slots() {/,/^}/p' \
|
|
"$ROOT/bin/omarchy-provision-owner" | sed "s|/etc/|$tmp/etc/|g" >"$tmp/unlock.sh"
|
|
grep -q '^luks_auto_unlock_drop() {' "$tmp/unlock.sh" && grep -q '^limine_auto_unlock_drop() {' "$tmp/unlock.sh" &&
|
|
grep -q '^luks_record_slots() {' "$tmp/unlock.sh" ||
|
|
fail "omarchy-provision-owner defines the dispatched and Limine auto-unlock callbacks and the slot record"
|
|
sed -n '/^rekey_luks() {/,/^}/p; /^run_provisioning() {/,/^}/p; /^cleanup_oem_state() {/,/^}/p
|
|
/^platform_ready() {/,/^}/p; /^run_setup() {/,/^}/p
|
|
/^rekey_accepts_password() {/,/^}/p' \
|
|
"$ROOT/bin/omarchy-provision-owner" |
|
|
sed "s|/etc/|$tmp/etc/|g" >"$tmp/provision.sh"
|
|
grep -q '^run_provisioning() {' "$tmp/provision.sh" && grep -q '^run_setup() {' "$tmp/provision.sh" ||
|
|
fail "omarchy-provision-owner defines its setup and provisioning worker"
|
|
|
|
cat >"$tmp/attempt.sh" <<'SH'
|
|
set -euo pipefail
|
|
|
|
source "$ROOT/install/provisioning/luks-rekey.sh"
|
|
source "$TMP/unlock.sh"
|
|
source <(sed -n '/^luks_boot_layout() {/,/^}/p' "$ROOT/bin/omarchy-provision-owner" | sed "s|/etc/|$TMP/etc/|g")
|
|
declare -F luks_boot_layout >/dev/null
|
|
|
|
PROVISIONING_DIR=$TMP/provisioning
|
|
REKEY_STATE=$PROVISIONING_DIR/luks-rekey.state
|
|
LOG_FILE=$TMP/log
|
|
password=$PASSWORD
|
|
|
|
log_step() { printf '%s\n' "$*" >>"$LOG_FILE"; }
|
|
say() { :; }
|
|
|
|
crash_point() {
|
|
local count
|
|
count=$(( $(cat "$TMP/steps") + 1 ))
|
|
echo "$count" >"$TMP/steps"
|
|
printf '%s %s\n' "$count" "$1" >>"$TMP/trace"
|
|
if (( count == ${CRASH_AT:-0} )); then
|
|
kill -9 $$
|
|
kill -9 "$BASHPID"
|
|
fi
|
|
}
|
|
|
|
eval "$(declare -f rekey_state_put | sed '1s/^rekey_state_put /journal_write /')"
|
|
rekey_state_put() {
|
|
journal_write "$@" || return 1
|
|
crash_point "journal $*"
|
|
}
|
|
|
|
reset_limine_config() {
|
|
echo reset >>"$TMP/limine-ran"
|
|
crash_point "auto-unlock files removed"
|
|
}
|
|
|
|
limine-update() {
|
|
echo update >>"$TMP/limine-ran"
|
|
echo rebuild >>"$TMP/rebuilds"
|
|
[[ ! -e $TMP/rebuild-fail ]] || return 1
|
|
crash_point "boot rebuilt"
|
|
}
|
|
|
|
shred() {
|
|
command rm -f -- "${@: -1}"
|
|
crash_point "staged key destroyed"
|
|
}
|
|
|
|
fake_cryptsetup() {
|
|
local op=$1 key_file="" token_type="" target="" extra="" requested="" material slot next
|
|
shift
|
|
while (( $# )); do
|
|
case $1 in
|
|
--key-file) key_file=$2; shift 2 ;;
|
|
--key-slot) requested=$2; shift 2 ;;
|
|
--token-type) token_type=$2; shift 2 ;;
|
|
-*) shift ;;
|
|
*)
|
|
if [[ -z $target ]]; then target=$1; else extra=$1; fi
|
|
shift
|
|
;;
|
|
esac
|
|
done
|
|
[[ $target == "$DEVICE" ]] || return 4
|
|
|
|
if [[ $op == "luksDump" ]]; then
|
|
echo "Keyslots:"
|
|
awk '{ printf " %s: luks2\n", $1 }' "$TMP/slots"
|
|
if [[ -s $TMP/token-slot ]]; then
|
|
printf 'Tokens:\n 0: luks2-keyring\n\tKeyslot: %s\n' "$(cat "$TMP/token-slot")"
|
|
fi
|
|
printf 'Digests:\n 0: pbkdf2\n'
|
|
return 0
|
|
fi
|
|
if [[ $op == "luksKillSlot" ]]; then
|
|
[[ ! -e $TMP/kill-noop ]] || return 0
|
|
awk -v s="$extra" '$1 != s' "$TMP/slots" >"$TMP/slots.next"
|
|
command mv -f "$TMP/slots.next" "$TMP/slots"
|
|
return 0
|
|
fi
|
|
|
|
# An enrolled token unlocks its slot whatever key is given, unless the
|
|
# allowed token types exclude it, as with cryptsetup.
|
|
if [[ $op == "open" && -z $token_type && -s $TMP/token-slot ]]; then
|
|
echo "Key slot $(cat "$TMP/token-slot") unlocked."
|
|
return 0
|
|
fi
|
|
|
|
material=$(cat "$key_file")
|
|
slot=$(awk -v m="$material" '$2 == m { print $1; exit }' "$TMP/slots")
|
|
if [[ -z $slot ]]; then
|
|
echo "No key available with this passphrase."
|
|
return 2
|
|
fi
|
|
case $op in
|
|
open) echo "Key slot $slot unlocked." ;;
|
|
luksAddKey)
|
|
next=$requested
|
|
if [[ -z $next ]]; then
|
|
for (( next = 0; next < 32; next++ )); do
|
|
awk '{ print $1 }' "$TMP/slots" | grep -qx "$next" || break
|
|
done
|
|
fi
|
|
! awk '{ print $1 }' "$TMP/slots" | grep -qx "$next" || return 1
|
|
printf '%s %s\n' "$next" "$(cat "$extra")" >>"$TMP/slots"
|
|
;;
|
|
*) return 1 ;;
|
|
esac
|
|
}
|
|
|
|
cryptsetup() {
|
|
local status=0
|
|
if [[ $BACKEND == "fake" ]]; then
|
|
fake_cryptsetup "$@" || status=$?
|
|
elif [[ $1 == "luksAddKey" ]]; then
|
|
command cryptsetup luksAddKey --pbkdf pbkdf2 --pbkdf-force-iterations 1000 "${@:2}" || status=$?
|
|
else
|
|
command cryptsetup "$@" || status=$?
|
|
fi
|
|
(( status == 0 )) || return "$status"
|
|
case $1 in
|
|
luksAddKey)
|
|
echo owner >>"$TMP/adds"
|
|
crash_point "owner key added"
|
|
;;
|
|
luksKillSlot) crash_point "slot ${*: -1} killed" ;;
|
|
esac
|
|
}
|
|
|
|
# omarchy-provision-owner's setup functions, with account and boot setup and
|
|
# the screen stubbed.
|
|
setup_functions() {
|
|
source "$TMP/provision.sh"
|
|
STATE_FILE=$TMP/state
|
|
FINALIZE_WARNING_FLAG=$TMP/finalize-warning
|
|
username=owner hostname="" timezone=""
|
|
create_user() { :; }
|
|
install_authorized_keys() { :; }
|
|
configure_login() { :; }
|
|
configure_hostname() { :; }
|
|
configure_timezone() { :; }
|
|
finalize_user() { :; }
|
|
limine_entries_stale() {
|
|
crash_point "re-key returned"
|
|
[[ -e $TMP/stale ]]
|
|
}
|
|
luks_device() { echo "$DEVICE"; }
|
|
systemctl() { :; }
|
|
clear_logo() { :; }
|
|
sleep() { :; }
|
|
say() { printf '%s\n' "$*" >>"$TMP/screen"; }
|
|
}
|
|
|
|
# The first-boot worker around the re-key.
|
|
provision() {
|
|
setup_functions
|
|
run_provisioning
|
|
}
|
|
|
|
# A whole setup attempt once the owner form is filled in: the password check,
|
|
# then the worker.
|
|
attempt() {
|
|
setup_functions
|
|
keyboard_form() { :; }
|
|
user_form() { :; }
|
|
confirm_form() { :; }
|
|
notice() {
|
|
printf 'refused: %s\n' "$1" >>"$TMP/screen"
|
|
exit 3
|
|
}
|
|
set_now() { NOW=0; }
|
|
render_setup_static() { :; }
|
|
render_setup_dynamic() { :; }
|
|
SHOW_CURSOR=""
|
|
run_setup
|
|
}
|
|
|
|
# First-boot setup up to the owner form, with the screen captured.
|
|
setup() {
|
|
source "$TMP/provision.sh"
|
|
clear_logo() { :; }
|
|
sleep() { :; }
|
|
say() { printf '%s\n' "$*" >>"$TMP/screen"; }
|
|
keyboard_form() {
|
|
echo "owner form" >>"$TMP/screen"
|
|
exit 0
|
|
}
|
|
run_setup
|
|
}
|
|
|
|
case $MODE in
|
|
provision) provision ;;
|
|
attempt) attempt ;;
|
|
setup) setup ;;
|
|
rekey) luks_rekey "$DEVICE" ;;
|
|
accepts) luks_rekey_accepts_password "$DEVICE" ;;
|
|
remains) luks_staged_unlock_remains ;;
|
|
esac
|
|
SH
|
|
|
|
run() {
|
|
local mode=$1 password=$2 crash_at=${3:-0}
|
|
echo "$crash_at" >"$tmp/crash-at"
|
|
{
|
|
ROOT=$ROOT TMP=$tmp BACKEND=$backend DEVICE=$device MODE=$mode PASSWORD=$password CRASH_AT=$crash_at \
|
|
OMARCHY_PATH=$runtime OMARCHY_PROC_ROOT=$tmp/$platform/proc OMARCHY_LIFECYCLE_ROOT=$tmp/lifecycle \
|
|
PATH="$tmp/$platform/bin:$PATH" bash "$tmp/attempt.sh"
|
|
} >>"$tmp/output" 2>&1
|
|
}
|
|
|
|
# The slot the key opens on the volume, or nothing. Tokens never answer.
|
|
opens() {
|
|
if [[ $backend == "fake" ]]; then
|
|
awk -v m="$1" '$2 == m { print $1; exit }' "$tmp/slots"
|
|
else
|
|
LC_ALL=C cryptsetup open --test-passphrase --verbose --token-type passphrase-only --key-file <(printf '%s' "$1") "$device" 2>&1 |
|
|
grep -o 'Key slot [0-9]* unlocked' | grep -o '[0-9]*' || true
|
|
fi
|
|
}
|
|
|
|
slot_count() {
|
|
if [[ $backend == "fake" ]]; then
|
|
wc -l <"$tmp/slots"
|
|
else
|
|
cryptsetup luksDump "$device" 2>/dev/null | grep -cE '^ +[0-9]+: luks2$|^Key Slot [0-9]+: ENABLED'
|
|
fi
|
|
}
|
|
|
|
fixture() {
|
|
local format=${1:-luks2}
|
|
rm -rf "$tmp/provisioning" "$tmp/etc" "$tmp/boot" "$tmp/log" "$tmp/output" "$tmp/trace" "$tmp/rebuilds" "$tmp/adds" "$tmp/rebuild-fail" "$tmp/kill-noop" \
|
|
"$tmp/prepare-fail" "$tmp/screen" "$tmp/stale" "$tmp/token-slot" "$tmp/slot-record" "$tmp/record-fail"
|
|
mkdir -p "$tmp/provisioning"
|
|
chmod 755 "$tmp/provisioning"
|
|
touch "$tmp/provisioning/pending"
|
|
printf '%s' "$staged_key" >"$tmp/provisioning/luks-key"
|
|
if [[ $platform == "apple" ]]; then
|
|
mkdir -p "$tmp/boot/omarchy" "$tmp/etc/default"
|
|
printf '%s' "$staged_key" >"$tmp/boot/omarchy/luks-key"
|
|
echo 'GRUB_CMDLINE_LINUX="rd.luks.name=root-uuid=root rd.luks.key=root-uuid=/omarchy/luks-key:UUID=boot-uuid"' >"$tmp/etc/default/grub"
|
|
else
|
|
mkdir -p "$tmp/etc/omarchy" "$tmp/etc/limine-entry-tool.d" "$tmp/etc/mkinitcpio.conf.d"
|
|
printf '%s' "$staged_key" >"$tmp/etc/omarchy/provisioning.key"
|
|
echo 'KERNEL_CMDLINE[default]+=" cryptkey=rootfs:/etc/omarchy/provisioning.key"' \
|
|
>"$tmp/etc/limine-entry-tool.d/99-omarchy-provisioning-unlock.conf"
|
|
echo 'FILES+=(/etc/omarchy/provisioning.key)' >"$tmp/etc/mkinitcpio.conf.d/99-omarchy-provisioning-key.conf"
|
|
fi
|
|
echo 0 >"$tmp/steps"
|
|
: >"$tmp/log"
|
|
|
|
# A reset leaves the previous owner's slot next to the staged key.
|
|
if [[ $backend == "fake" ]]; then
|
|
device=$tmp/volume
|
|
: >"$device"
|
|
printf '0 %s\n1 %s\n' "$staged_key" "$seller_key" >"$tmp/slots"
|
|
else
|
|
device=$tmp/volume.img
|
|
rm -f "$device"
|
|
truncate -s 32M "$device"
|
|
local pbkdf=(--pbkdf pbkdf2 --pbkdf-force-iterations 1000)
|
|
[[ $format == "luks1" ]] && pbkdf=(--pbkdf-force-iterations 1000)
|
|
cryptsetup luksFormat -q --type "$format" "${pbkdf[@]}" "$device" <(printf '%s' "$staged_key")
|
|
cryptsetup luksAddKey "${pbkdf[@]}" --key-file <(printf '%s' "$staged_key") "$device" <(printf '%s' "$seller_key")
|
|
fi
|
|
}
|
|
|
|
# What a previous owner can leave on the real volume: a systemd-tpm2 token and
|
|
# a luks2-keyring token on slot $1, the second answered from the session keyring
|
|
# with $2. Fails when the kernel keyring is out of reach (no keyctl, or a
|
|
# container's seccomp profile), so that a bare cryptsetup open does not take the
|
|
# token in place of any key.
|
|
enroll_tokens() {
|
|
local slot=$1 key=$2 description=omarchy-test-token-$$
|
|
command -v keyctl >/dev/null || return 1
|
|
token_key=$(printf '%s' "$key" | keyctl padd user "$description" @s 2>/dev/null) || return 1
|
|
keyctl timeout "$token_key" 600 >/dev/null 2>&1 || true
|
|
printf '{"type":"systemd-tpm2","keyslots":["%s"],"tpm2-blob":"AA==","tpm2-pcrs":[7],"tpm2-pcr-bank":"sha256","tpm2-primary-alg":"ecc","tpm2-policy-hash":"00","tpm2-pin":false}' "$slot" |
|
|
cryptsetup token import --disable-external-tokens "$device" || return 1
|
|
cryptsetup token add --key-description "$description" --key-slot "$slot" "$device" >/dev/null || return 1
|
|
LC_ALL=C cryptsetup open --test-passphrase --verbose --key-file <(printf 'not-a-key') "$device" 2>&1 | grep -qx "Key slot $slot unlocked."
|
|
}
|
|
|
|
unlock_files_present() {
|
|
[[ -e $tmp/etc/omarchy/provisioning.key || -e $tmp/etc/limine-entry-tool.d/99-omarchy-provisioning-unlock.conf ||
|
|
-e $tmp/etc/mkinitcpio.conf.d/99-omarchy-provisioning-key.conf || -e $tmp/boot/omarchy/luks-key ]] ||
|
|
grep -qs 'rd\.luks\.key=' "$tmp/etc/default/grub"
|
|
}
|
|
|
|
no_secrets_in() {
|
|
local file
|
|
local -a keys=(-e "$staged_key" -e "$seller_key" -e "$owner_password" -e "other-password")
|
|
for file in "$@"; do
|
|
[[ -e $file ]] || continue
|
|
! grep -Fq "${keys[@]}" "$file" ||
|
|
fail "$backend: $(basename "$file") holds no key material" "$(cat "$file")"
|
|
done
|
|
}
|
|
|
|
# Between attempts the disk must still open at the next boot: unattended while
|
|
# the auto-unlock remains, otherwise with a password the owner typed.
|
|
assert_recoverable() {
|
|
local context=$1 password=$2
|
|
if unlock_files_present; then
|
|
[[ -n $(opens "$staged_key") ]] || fail "$backend: $context: the auto-unlock key still opens the disk"
|
|
else
|
|
[[ -n $(opens "$password") ]] || fail "$backend: $context: the owner's password opens the disk once auto-unlock is gone"
|
|
fi
|
|
if [[ -n $(opens "$staged_key") && ! -f $tmp/provisioning/luks-key ]]; then
|
|
fail "$backend: $context: the staged key file is kept while it still unlocks the volume"
|
|
fi
|
|
if [[ -f $tmp/provisioning/luks-rekey.state ]]; then
|
|
[[ $(stat -c %a "$tmp/provisioning/luks-rekey.state") == "600" ]] || fail "$backend: $context: the journal is private"
|
|
fi
|
|
[[ $(stat -c %a "$tmp/provisioning") == "755" ]] || fail "$backend: $context: the provisioning directory stays readable for the new user"
|
|
no_secrets_in "$tmp/provisioning/luks-rekey.state" "$tmp/log"
|
|
}
|
|
|
|
# The re-key finished: the owner's slot is all the volume holds.
|
|
assert_finished() {
|
|
local context=$1 password=$2 max_adds=${3:-1} adds=0 owner
|
|
owner=$(opens "$password")
|
|
[[ $(slot_count) == "1" && -n $owner ]] || fail "$backend: $context: only the owner's slot remains" "$(cat "$tmp/log")"
|
|
[[ -z $(opens "$staged_key") ]] || fail "$backend: $context: the staged install key no longer unlocks the volume"
|
|
[[ -z $(opens "$seller_key") ]] || fail "$backend: $context: the previous owner's key no longer unlocks the volume"
|
|
[[ ! -e $tmp/provisioning/luks-key ]] || fail "$backend: $context: the staged key file is destroyed"
|
|
! unlock_files_present || fail "$backend: $context: no boot-time auto-unlock remains"
|
|
[[ -f $tmp/adds ]] && adds=$(grep -c '^owner' "$tmp/adds" || true)
|
|
(( adds <= max_adds )) || fail "$backend: $context: the owner's key is added at most $max_adds time(s)"
|
|
! run remains "$password" || fail "$backend: $context: nothing of the staged unlock remains"
|
|
if [[ $platform == "apple" ]]; then
|
|
[[ $(cat "$tmp/slot-record" 2>/dev/null) == "owner=$owner"$'\n'"recovery=" ]] ||
|
|
fail "$backend: $context: the boot package records the owner's slot and no recovery slot" "$(cat "$tmp/slot-record" 2>/dev/null)"
|
|
else
|
|
[[ ! -e $tmp/slot-record ]] || fail "$backend: $context: x86 records no slots"
|
|
fi
|
|
no_secrets_in "$tmp/provisioning/luks-rekey.state" "$tmp/log" "$tmp/output" "$tmp/slot-record"
|
|
}
|
|
|
|
# Setup completed: the disk opens with the password the account got, and the
|
|
# provisioning state is gone.
|
|
assert_provisioned() {
|
|
assert_finished "$@"
|
|
[[ ! -e $tmp/provisioning/pending && ! -e $tmp/provisioning/luks-rekey.state ]] ||
|
|
fail "$backend: $1: setup drops pending and the journal together"
|
|
}
|
|
|
|
backends=(fake)
|
|
if command -v cryptsetup >/dev/null; then
|
|
backends+=(luks2 luks1)
|
|
else
|
|
skip "cryptsetup is not installed; skipping the file-backed volume runs"
|
|
fi
|
|
|
|
# The crash matrix runs whole setup attempts on every backend, for x86 and for
|
|
# Apple.
|
|
matrix=()
|
|
for backend in "${backends[@]}"; do
|
|
matrix+=("x86 $backend")
|
|
[[ " ${platforms[*]} " != *" apple "* ]] || matrix+=("apple $backend")
|
|
done
|
|
|
|
for run_spec in "${matrix[@]}"; do
|
|
read -r platform backend <<<"$run_spec"
|
|
rm -f "$tmp/mac-boot-ran" "$tmp/limine-ran"
|
|
format=$backend
|
|
[[ $backend == "fake" ]] && format=luks2
|
|
|
|
fixture "$format"
|
|
run attempt "$owner_password" || fail "$backend: uninterrupted setup completes" "$(cat "$tmp/log" "$tmp/output")"
|
|
assert_provisioned "uninterrupted" "$owner_password"
|
|
total_steps=$(cat "$tmp/steps")
|
|
(( total_steps >= 11 )) || fail "$backend: every durable step is a crash point" "$(cat "$tmp/trace")"
|
|
if [[ $platform == "apple" ]]; then
|
|
pass "apple $backend: uninterrupted setup leaves only the owner's slot, records it and destroys the staged key"
|
|
else
|
|
pass "x86 $backend: uninterrupted setup leaves only the owner's slot and destroys the staged key"
|
|
fi
|
|
|
|
# After each kill the owner reboots and answers the form again, with the same
|
|
# password or a new one. The account takes whatever the form accepts, so the
|
|
# disk must open with that password when setup finishes.
|
|
for (( step = 1; step <= total_steps; step++ )); do
|
|
for retry_password in "$owner_password" other-password; do
|
|
fixture "$format"
|
|
if run attempt "$owner_password" "$step"; then
|
|
fail "$backend: setup is killed at step $step"
|
|
fi
|
|
point=$(sed -n "${step}p" "$tmp/trace")
|
|
assert_recoverable "killed after '$point'" "$owner_password"
|
|
[[ -e $tmp/provisioning/pending ]] || fail "$backend: killed after '$point', setup runs again"
|
|
staged_alive=$([[ -n $(opens "$staged_key") ]] && echo 1 || echo 0)
|
|
|
|
if run accepts "$retry_password"; then
|
|
[[ $retry_password == "$owner_password" ]] || (( staged_alive )) ||
|
|
fail "$backend: after '$point' a new password is refused once the staged key is retired"
|
|
run attempt "$retry_password" || fail "$backend: after '$point' setup completes" "$(cat "$tmp/log")"
|
|
if [[ $retry_password == "$owner_password" ]]; then
|
|
assert_provisioned "rerun after '$point'" "$retry_password"
|
|
if head -n "$step" "$tmp/trace" | grep -q 'journal phase boot'; then
|
|
[[ $(wc -l <"$tmp/rebuilds") == "1" ]] || fail "$backend: rerun after '$point' does not rebuild boot again"
|
|
fi
|
|
else
|
|
assert_provisioned "new password after '$point'" "$retry_password" 2
|
|
[[ -z $(opens "$owner_password") ]] || fail "$backend: after '$point' the abandoned password is retired"
|
|
fi
|
|
else
|
|
[[ $retry_password != "$owner_password" ]] || fail "$backend: after '$point' the original password is always accepted"
|
|
(( ! staged_alive )) || fail "$backend: after '$point' a new password is accepted while the staged key works"
|
|
if run attempt "$retry_password"; then fail "$backend: after '$point' setup refuses a new password"; fi
|
|
assert_recoverable "refused new password after '$point'" "$owner_password"
|
|
[[ -e $tmp/provisioning/pending ]] || fail "$backend: after '$point' a refused password keeps setup pending"
|
|
run attempt "$owner_password" || fail "$backend: after '$point' the original password completes" "$(cat "$tmp/log")"
|
|
assert_provisioned "original password after '$point'" "$owner_password"
|
|
fi
|
|
done
|
|
done
|
|
if [[ $platform == "apple" ]]; then
|
|
pass "apple $backend: killed after each of $total_steps steps, setup resumes to a disk that opens with the account's password, with its slot recorded"
|
|
else
|
|
pass "x86 $backend: killed after each of $total_steps steps, setup resumes to a disk that opens with the account's password"
|
|
fi
|
|
|
|
if [[ $platform == "x86" ]]; then
|
|
[[ ! -e $tmp/mac-boot-ran ]] || fail "x86 $backend: no Mac boot entrypoint runs" "$(cat "$tmp/mac-boot-ran")"
|
|
pass "x86 $backend: dispatch is a no-op and the Limine UKI path runs unchanged"
|
|
else
|
|
[[ ! -e $tmp/limine-ran ]] || fail "apple $backend: the Limine UKI path never runs on Apple"
|
|
grep -qx provision-commit "$tmp/mac-boot-ran" && grep -qx provision-verify "$tmp/mac-boot-ran" ||
|
|
fail "apple $backend: the boot package commits and verifies the unlock"
|
|
pass "apple $backend: the boot package owns the boot-time unlock through dispatch"
|
|
fi
|
|
done
|
|
|
|
platform=x86
|
|
backend=fake
|
|
|
|
fixture
|
|
touch "$tmp/rebuild-fail"
|
|
if run rekey "$owner_password"; then fail "a failed boot rebuild fails the attempt"; fi
|
|
unlock_files_present || fail "a failed boot rebuild restores the auto-unlock"
|
|
[[ $(cat "$tmp/etc/omarchy/provisioning.key") == "$staged_key" ]] || fail "the restored keyfile is the staged key"
|
|
[[ -n $(opens "$staged_key") && -n $(opens "$seller_key") ]] || fail "a failed boot rebuild retires no slot"
|
|
rm "$tmp/rebuild-fail"
|
|
run rekey "$owner_password" || fail "the retry after a failed rebuild completes" "$(cat "$tmp/log")"
|
|
assert_finished "retry after a failed rebuild" "$owner_password"
|
|
pass "a failed boot rebuild keeps the unattended unlock and every slot for the retry"
|
|
|
|
fixture
|
|
printf '2 %s\n' tpm-sealed-key >>"$tmp/slots"
|
|
echo 2 >"$tmp/token-slot"
|
|
run provision "$owner_password" || fail "setup completes beside an enrolled token" "$(cat "$tmp/log")"
|
|
assert_provisioned "beside the previous owner's token" "$owner_password"
|
|
pass "a token the previous owner enrolled never answers for a key and is retired with its slot"
|
|
|
|
if [[ " ${backends[*]} " == *" luks2 "* ]]; then
|
|
backend=luks2
|
|
fixture luks2
|
|
if enroll_tokens 1 "$seller_key"; then
|
|
run provision "$owner_password" || fail "luks2: setup completes beside live tokens" "$(cat "$tmp/log" "$tmp/output")"
|
|
assert_provisioned "beside the previous owner's live tokens" "$owner_password"
|
|
pass "luks2: with a live keyring token and a TPM2 token on the previous owner's slot, only real keys count"
|
|
else
|
|
skip "the kernel keyring is out of reach; skipping the live token run"
|
|
fi
|
|
backend=fake
|
|
fi
|
|
|
|
fixture
|
|
touch "$tmp/kill-noop"
|
|
if run rekey "$owner_password"; then fail "a slot kill that changes nothing fails the re-key"; fi
|
|
[[ -f $tmp/provisioning/luks-key && -f $tmp/provisioning/luks-rekey.state ]] || fail "an unverified retirement keeps the staged key and journal"
|
|
rm "$tmp/kill-noop"
|
|
run rekey "$owner_password" || fail "the retry after an ineffective slot kill completes" "$(cat "$tmp/log")"
|
|
assert_finished "retry after an ineffective slot kill" "$owner_password"
|
|
pass "the staged key is destroyed only after the volume proves it no longer opens"
|
|
|
|
fixture
|
|
printf '0 %s\n1 %s\n2 %s\n' "$staged_key" "$seller_key" "$owner_password" >"$tmp/slots"
|
|
printf 'staged_slot=0\nowner_slot=2\nphase=boot\n' >"$tmp/provisioning/luks-rekey.state"
|
|
run rekey "$owner_password" || fail "a leftover auto-unlock after the boot step is removed" "$(cat "$tmp/log")"
|
|
assert_finished "leftover auto-unlock after the boot step" "$owner_password"
|
|
[[ $(wc -l <"$tmp/rebuilds") == "1" ]] || fail "the leftover auto-unlock triggers a boot rebuild"
|
|
pass "an auto-unlock that outlives the boot step is rebuilt away rather than blocking forever"
|
|
|
|
# The boot files ask for the disk password in the layout vconsole.conf held
|
|
# when they were built. A retry after the owner picked another layout rebuilds
|
|
# them once, at the boot step and after done; the same layout rebuilds nothing,
|
|
# and a journal from before the fingerprint rebuilds once.
|
|
choose_layout() {
|
|
mkdir -p "$tmp/etc"
|
|
printf 'KEYMAP=%s\nXKBLAYOUT=%s\nXKBMODEL=pc105\n' "$1" "$2" >"$tmp/etc/vconsole.conf"
|
|
}
|
|
|
|
rebuild_count() {
|
|
wc -l <"$tmp/rebuilds" 2>/dev/null || echo 0
|
|
}
|
|
|
|
assert_fingerprint() {
|
|
local context=$1 journal=$tmp/provisioning/luks-rekey.state
|
|
grep -Eqx 'boot_layout=[0-9a-f]{64}' "$journal" || fail "$context: the journal records the layout as a hash" "$(cat "$journal")"
|
|
! grep -Eqw 'dk|us' "$journal" "$tmp/log" "$tmp/trace" || fail "$context: the journal and log never name the layout"
|
|
}
|
|
|
|
boot_layout_runs() {
|
|
local name=$1 stop step refresh before
|
|
|
|
fixture
|
|
choose_layout us us
|
|
run rekey "$owner_password" || fail "$name: the re-key completes with a layout" "$(cat "$tmp/log")"
|
|
assert_fingerprint "$name: uninterrupted"
|
|
cp "$tmp/trace" "$tmp/layout-trace"
|
|
|
|
for stop in boot done; do
|
|
step=$(grep -m1 "^[0-9]* journal phase $stop" "$tmp/layout-trace" | cut -d' ' -f1)
|
|
[[ -n $step ]] || fail "$name: the trace names the $stop journal write" "$(cat "$tmp/layout-trace")"
|
|
|
|
fixture
|
|
choose_layout us us
|
|
if run rekey "$owner_password" "$step"; then fail "$name: the re-key is killed after the $stop journal write"; fi
|
|
[[ $(rebuild_count) == 1 ]] || fail "$name: killed at $stop after one rebuild"
|
|
run rekey "$owner_password" || fail "$name: the same layout completes after $stop" "$(cat "$tmp/log")"
|
|
[[ $(rebuild_count) == 1 ]] || fail "$name: an unchanged layout after $stop rebuilds nothing"
|
|
assert_finished "$name: unchanged layout after $stop" "$owner_password"
|
|
|
|
# The owner picks another layout on the retry right after the interruption.
|
|
fixture
|
|
choose_layout us us
|
|
if run rekey "$owner_password" "$step"; then fail "$name: the re-key is killed after the $stop journal write"; fi
|
|
before=$(grep -m1 '^boot_layout=' "$tmp/provisioning/luks-rekey.state")
|
|
choose_layout dk dk
|
|
run rekey "$owner_password" || fail "$name: a changed layout after $stop completes" "$(cat "$tmp/log")"
|
|
[[ $(rebuild_count) == 2 ]] || fail "$name: a changed layout after $stop rebuilds the boot files"
|
|
[[ $(grep -m1 '^boot_layout=' "$tmp/provisioning/luks-rekey.state") != "$before" ]] ||
|
|
fail "$name: the journal takes the new layout's fingerprint after $stop"
|
|
[[ $(grep -c '^[0-9]* journal boot_layout ' "$tmp/trace") == 1 && $(grep -c "^[0-9]* journal phase $stop" "$tmp/trace") == 1 ]] ||
|
|
fail "$name: the rebuild after $stop records only the fingerprint, keeping the phase" "$(cat "$tmp/trace")"
|
|
refresh=$(awk -v s="$step" '$1 > s && / boot rebuilt$/ { print $1; exit }' "$tmp/trace")
|
|
run rekey "$owner_password" || fail "$name: a rerun after the rebuild completes" "$(cat "$tmp/log")"
|
|
[[ $(rebuild_count) == 2 ]] || fail "$name: the changed layout after $stop rebuilds only once"
|
|
assert_finished "$name: changed layout after $stop" "$owner_password"
|
|
assert_fingerprint "$name: changed layout after $stop"
|
|
|
|
# Killed between that rebuild and its fingerprint: the retry rebuilds again.
|
|
fixture
|
|
choose_layout us us
|
|
if run rekey "$owner_password" "$step"; then fail "$name: the re-key is killed after the $stop journal write"; fi
|
|
choose_layout dk dk
|
|
if run rekey "$owner_password" "$refresh"; then fail "$name: the re-key is killed after the layout rebuild at $stop"; fi
|
|
[[ $(grep -m1 '^boot_layout=' "$tmp/provisioning/luks-rekey.state") == "$before" ]] ||
|
|
fail "$name: a layout rebuild killed before its journal write keeps the old fingerprint"
|
|
run rekey "$owner_password" || fail "$name: the retry after a killed layout rebuild at $stop completes" "$(cat "$tmp/log")"
|
|
[[ $(rebuild_count) == 3 ]] || fail "$name: a layout rebuild killed at $stop runs again"
|
|
assert_finished "$name: killed layout rebuild at $stop" "$owner_password"
|
|
assert_fingerprint "$name: killed layout rebuild at $stop"
|
|
done
|
|
|
|
# Interrupted at the boot step, the owner picks another layout and a new
|
|
# password while the staged key still opens the disk.
|
|
step=$(grep -m1 "^[0-9]* journal phase boot" "$tmp/layout-trace" | cut -d' ' -f1)
|
|
fixture
|
|
choose_layout us us
|
|
if run rekey "$owner_password" "$step"; then fail "$name: the re-key is killed after the boot journal write"; fi
|
|
choose_layout dk dk
|
|
run rekey other-password || fail "$name: a new layout and password after the boot step complete" "$(cat "$tmp/log")"
|
|
[[ $(rebuild_count) == 2 ]] || fail "$name: a new layout and password after the boot step rebuild once"
|
|
assert_finished "$name: new layout and password after the boot step" other-password 2
|
|
assert_fingerprint "$name: new layout and password after the boot step"
|
|
|
|
# A journal from before the fingerprint, at the boot step with the auto-unlock
|
|
# already gone and after done: rebuilt once, then recorded.
|
|
for stop in boot done; do
|
|
fixture
|
|
choose_layout dk dk
|
|
run rekey "$owner_password" || fail "$name: the re-key completes" "$(cat "$tmp/log")"
|
|
step=$(grep -m1 -n "^[0-9]* journal phase $stop" "$tmp/trace" | cut -d: -f1)
|
|
|
|
fixture
|
|
choose_layout dk dk
|
|
if run rekey "$owner_password" "$step"; then fail "$name: the re-key is killed after the $stop journal write"; fi
|
|
sed -i '/^boot_layout=/d' "$tmp/provisioning/luks-rekey.state"
|
|
rm -f "$tmp/rebuilds"
|
|
run rekey "$owner_password" || fail "$name: an older journal at $stop completes" "$(cat "$tmp/log")"
|
|
[[ $(rebuild_count) == 1 ]] || fail "$name: an older journal at $stop rebuilds the boot files once"
|
|
run rekey "$owner_password" || fail "$name: a rerun of an older journal at $stop completes" "$(cat "$tmp/log")"
|
|
[[ $(rebuild_count) == 1 ]] || fail "$name: an older journal at $stop rebuilds only once"
|
|
assert_finished "$name: older journal at $stop" "$owner_password"
|
|
assert_fingerprint "$name: older journal at $stop"
|
|
done
|
|
|
|
# After done a failed rebuild keeps the old fingerprint, so the retry rebuilds.
|
|
fixture
|
|
choose_layout us us
|
|
run rekey "$owner_password" || fail "$name: the re-key completes" "$(cat "$tmp/log")"
|
|
before=$(grep -m1 '^boot_layout=' "$tmp/provisioning/luks-rekey.state")
|
|
choose_layout dk dk
|
|
touch "$tmp/rebuild-fail"
|
|
if run rekey "$owner_password"; then fail "$name: a failed layout rebuild after done fails the attempt"; fi
|
|
[[ $(grep -m1 '^boot_layout=' "$tmp/provisioning/luks-rekey.state") == "$before" ]] ||
|
|
fail "$name: a failed layout rebuild keeps the old fingerprint"
|
|
rm "$tmp/rebuild-fail"
|
|
run rekey "$owner_password" || fail "$name: the retry after a failed layout rebuild completes" "$(cat "$tmp/log")"
|
|
assert_finished "$name: retry after a failed layout rebuild" "$owner_password"
|
|
assert_fingerprint "$name: retry after a failed layout rebuild"
|
|
}
|
|
|
|
boot_layout_runs x86
|
|
pass "x86: a changed keyboard layout rebuilds the boot files once, an unchanged one never, and an older journal once"
|
|
|
|
fixture
|
|
printf 'wrong-key' >"$tmp/provisioning/luks-key"
|
|
if run rekey "$owner_password"; then fail "a staged key that opens nothing fails the re-key"; fi
|
|
[[ ! -e $tmp/provisioning/luks-rekey.state && $(slot_count) == "2" ]] || fail "a dead staged key changes nothing"
|
|
pass "a fresh re-key refuses a staged key that no longer unlocks the volume"
|
|
|
|
fixture
|
|
rm "$tmp/provisioning/luks-key"
|
|
run remains "$owner_password" || fail "a leftover auto-unlock counts as the staged unlock remaining"
|
|
if run rekey "$owner_password"; then fail "a leftover auto-unlock without its staged key fails closed"; fi
|
|
unlock_files_present || fail "the leftover auto-unlock is not silently accepted"
|
|
pass "a boot-time auto-unlock without its staged key keeps provisioning from finishing"
|
|
|
|
fixture
|
|
if run rekey "$staged_key"; then fail "the staged key is refused as the owner's password"; fi
|
|
[[ $(slot_count) == "2" ]] && unlock_files_present || fail "refusing the staged key as a password changes nothing"
|
|
run rekey "$owner_password" || fail "a retry with a real password completes" "$(cat "$tmp/log")"
|
|
assert_finished "retry after refusing the staged key" "$owner_password"
|
|
pass "the owner's password must differ from the staged install key"
|
|
|
|
fixture
|
|
printf 'phase=owner\nstaged_slot=0\nowner_slot=2\nrecovery_slot=5\n' >"$tmp/provisioning/luks-rekey.state"
|
|
chmod 600 "$tmp/provisioning/luks-rekey.state"
|
|
(
|
|
source "$ROOT/install/provisioning/luks-rekey.sh"
|
|
REKEY_STATE=$tmp/provisioning/luks-rekey.state
|
|
rekey_state_put phase boot
|
|
)
|
|
[[ $(grep -c . "$tmp/provisioning/luks-rekey.state") == "4" ]] && grep -qx 'phase=boot' "$tmp/provisioning/luks-rekey.state" &&
|
|
grep -qx 'recovery_slot=5' "$tmp/provisioning/luks-rekey.state" || fail "a journal write keeps every other recorded key" "$(cat "$tmp/provisioning/luks-rekey.state")"
|
|
pass "journal writes replace only their own keys"
|
|
|
|
fixture
|
|
rm "$tmp/provisioning/luks-key"
|
|
if run provision "$owner_password"; then fail "provisioning with a leftover auto-unlock and no staged key fails"; fi
|
|
[[ -e $tmp/provisioning/pending ]] && unlock_files_present || fail "provisioning keeps its state while the auto-unlock remains"
|
|
pass "first-boot provisioning never finishes with the boot-time auto-unlock still configured"
|
|
|
|
fixture
|
|
rm -rf "$tmp/provisioning/luks-key" "$tmp/etc"
|
|
run provision "$owner_password" || fail "unencrypted provisioning finishes" "$(cat "$tmp/log" "$tmp/output")"
|
|
[[ ! -e $tmp/provisioning/pending && ! -e $tmp/provisioning/luks-rekey.state && $(slot_count) == "2" ]] ||
|
|
fail "unencrypted provisioning skips the re-key"
|
|
pass "provisioning without a staged key or auto-unlock skips the re-key"
|
|
|
|
if [[ " ${platforms[*]} " == *" apple "* ]]; then
|
|
# Apple: a failed boot-package commit keeps the unattended unlock and every slot.
|
|
platform=apple
|
|
fixture
|
|
touch "$tmp/rebuild-fail"
|
|
if run rekey "$owner_password"; then fail "apple: a failed boot-package commit fails the attempt"; fi
|
|
unlock_files_present || fail "apple: a failed commit keeps the boot-time unlock"
|
|
[[ -n $(opens "$staged_key") && -n $(opens "$seller_key") ]] || fail "apple: a failed commit retires no slot"
|
|
rm "$tmp/rebuild-fail"
|
|
run rekey "$owner_password" || fail "apple: the retry after a failed commit completes" "$(cat "$tmp/log")"
|
|
assert_finished "apple: retry after a failed commit" "$owner_password"
|
|
pass "apple: a failed boot-package commit keeps the unattended unlock and every slot for the retry"
|
|
|
|
# Apple: an inherited decision never replaces resolving who owns the unlock.
|
|
fixture
|
|
rm -f "$tmp/limine-ran"
|
|
export UNLOCK_OWNER=limine
|
|
run provision "$owner_password" || fail "apple: setup completes with UNLOCK_OWNER in its environment" "$(cat "$tmp/log")"
|
|
unset UNLOCK_OWNER
|
|
assert_provisioned "apple: UNLOCK_OWNER inherited" "$owner_password"
|
|
[[ ! -e $tmp/limine-ran ]] || fail "apple: an inherited UNLOCK_OWNER does not select the Limine path"
|
|
pass "apple: an inherited UNLOCK_OWNER is ignored"
|
|
|
|
# Apple: the boot package's commit rebuilds for a changed layout, after done too.
|
|
boot_layout_runs apple
|
|
pass "apple: a changed keyboard layout reruns the boot package's commit once, an unchanged one never, and an older journal once"
|
|
fi
|
|
|
|
# After a factory reset left entries for another machine identity, the menu
|
|
# starts over and limine-update rebuilds, on Apple too, and setup still
|
|
# finishes only once the boot package verifies nothing of the staged unlock
|
|
# remains.
|
|
for platform in "${platforms[@]}"; do
|
|
fixture
|
|
rm -rf "$tmp/provisioning/luks-key" "$tmp/etc" "$tmp/boot" "$tmp/limine-ran" "$tmp/mac-boot-ran"
|
|
touch "$tmp/stale"
|
|
run provision "$owner_password" || fail "$platform: stale boot entries are refreshed" "$(cat "$tmp/log" "$tmp/output")"
|
|
[[ ! -e $tmp/provisioning/pending && $(cat "$tmp/limine-ran") == $'reset\nupdate' ]] ||
|
|
fail "$platform: the Limine menu is reset and limine-update rebuilds" "$(cat "$tmp/limine-ran")"
|
|
if [[ $platform == "x86" ]]; then
|
|
[[ ! -e $tmp/mac-boot-ran ]] || fail "x86: no Mac entrypoint runs"
|
|
else
|
|
grep -qx provision-verify "$tmp/mac-boot-ran" || fail "apple: the boot package still has the last word"
|
|
fi
|
|
done
|
|
if [[ " ${platforms[*]} " == *" apple "* ]]; then
|
|
platform=apple
|
|
fixture
|
|
rm -rf "$tmp/provisioning/luks-key" "$tmp/limine-ran"
|
|
touch "$tmp/stale"
|
|
if run provision "$owner_password"; then fail "apple: a leftover boot-partition unlock still stops setup after the refresh"; fi
|
|
[[ -e $tmp/provisioning/pending ]] || fail "apple: setup stays pending"
|
|
fi
|
|
pass "stale boot entries are rebuilt by limine-update, behind the boot package's verify on Apple"
|
|
|
|
# Before the owner form: a no-op on x86 even with Mac entrypoints on disk, the
|
|
# boot package's own answer on Apple.
|
|
platform=x86
|
|
fixture
|
|
rm -f "$tmp/mac-boot-ran"
|
|
touch "$tmp/prepare-fail"
|
|
run setup "$owner_password" || fail "x86: setup reaches the owner form without a boot package" "$(cat "$tmp/output")"
|
|
[[ $(cat "$tmp/screen") == "owner form" && ! -e $tmp/mac-boot-ran ]] ||
|
|
fail "x86: no Mac boot entrypoint runs before the owner form" "$(cat "$tmp/screen")"
|
|
pass "x86: the platform check before the owner form is a no-op"
|
|
|
|
if [[ " ${platforms[*]} " == *" apple "* ]]; then
|
|
platform=apple
|
|
fixture
|
|
run setup "$owner_password" && grep -qx 'owner form' "$tmp/screen" ||
|
|
fail "apple: setup reaches the owner form with the boot package ready" "$(cat "$tmp/output")"
|
|
fixture
|
|
touch "$tmp/prepare-fail"
|
|
if run setup "$owner_password"; then fail "apple: setup stops when the boot package is not ready"; fi
|
|
! grep -qx 'owner form' "$tmp/screen" || fail "apple: the owner is asked nothing when the boot package is not ready"
|
|
grep -q 'The boot partition is not mounted.' "$tmp/screen" && grep -q 'The boot partition is not mounted.' "$tmp/log" ||
|
|
fail "apple: the boot package's reason reaches the screen and the log" "$(cat "$tmp/screen" "$tmp/log" 2>/dev/null)"
|
|
pass "apple: setup stops before the owner form when the boot package is not ready"
|
|
|
|
# Apple without omarchy-mac-boot, or with one older than its provisioning
|
|
# entrypoints: setup stops before the owner form naming the package, and a
|
|
# worker that got past it anyway never finishes while any part of the staged
|
|
# unlock remains.
|
|
mv "$mac_boot" "$tmp/mac-boot.off"
|
|
for package in missing old; do
|
|
# An older package can already ship other files in that directory.
|
|
if [[ $package == old ]]; then
|
|
install -d -m 755 "$mac_boot"
|
|
install -m 755 /dev/null "$mac_boot/boot-hook"
|
|
fi
|
|
fixture
|
|
rm -f "$tmp/limine-ran" "$tmp/mac-boot-ran"
|
|
if run setup "$owner_password"; then fail "apple ($package package): setup refuses without provisioning entrypoints"; fi
|
|
! grep -qx 'owner form' "$tmp/screen" || fail "apple ($package package): the owner is asked nothing"
|
|
grep -q 'provision-prepare on aarch64-apple needs omarchy-mac-boot' "$tmp/screen" ||
|
|
fail "apple ($package package): the boot package is named on the screen" "$(cat "$tmp/screen" 2>/dev/null)"
|
|
grep -q '/usr/lib/omarchy/mac-boot/provision-prepare' "$tmp/log" || fail "apple ($package package): the log names the entrypoint" "$(cat "$tmp/log")"
|
|
if run provision "$owner_password"; then fail "apple ($package package): provisioning fails"; fi
|
|
[[ -e $tmp/provisioning/pending && -f $tmp/provisioning/luks-key ]] && unlock_files_present ||
|
|
fail "apple ($package package): provisioning keeps its state and the staged unlock"
|
|
[[ -n $(opens "$staged_key") && -n $(opens "$seller_key") ]] || fail "apple ($package package): no slot is retired"
|
|
fixture
|
|
rm "$tmp/provisioning/luks-key"
|
|
if run provision "$owner_password"; then fail "apple ($package package): a leftover boot-partition unlock fails provisioning"; fi
|
|
[[ -e $tmp/provisioning/pending ]] && unlock_files_present || fail "apple ($package package): a leftover boot-partition unlock keeps provisioning pending"
|
|
[[ ! -e $tmp/limine-ran && ! -e $tmp/mac-boot-ran ]] || fail "apple ($package package): the Limine UKI path is no fallback"
|
|
[[ $package == missing ]] || rm -r "$mac_boot"
|
|
done
|
|
mv "$tmp/mac-boot.off" "$mac_boot"
|
|
pass "apple: without omarchy-mac-boot's provisioning entrypoints setup stops naming the package, and the worker fails closed"
|
|
fi
|
|
|
|
# A boot package that implements only one of the commit/verify pair owns
|
|
# nothing: provisioning fails closed rather than mixing it with the Limine path.
|
|
mkdir -p "$tmp/half/bin"
|
|
cat >"$tmp/half/bin/omarchy-lifecycle-dispatch" <<SH
|
|
#!/bin/bash
|
|
echo "\$*" >>"$tmp/half-ran"
|
|
[[ \$* != "--resolve provision-commit" ]] || echo /usr/lib/omarchy/mac-boot/provision-commit
|
|
SH
|
|
chmod +x "$tmp/half/bin/omarchy-lifecycle-dispatch"
|
|
runtime=$tmp/half
|
|
fixture
|
|
rm -f "$tmp/limine-ran" "$tmp/half-ran"
|
|
if run provision "$owner_password"; then fail "a half-implemented unlock fails provisioning"; fi
|
|
[[ -e $tmp/provisioning/pending ]] && unlock_files_present || fail "a half-implemented unlock keeps provisioning pending"
|
|
[[ -s $tmp/half-ran && ! -e $tmp/limine-ran ]] && ! grep -qv -- '--resolve' "$tmp/half-ran" ||
|
|
fail "a half-implemented unlock runs neither the platform nor the Limine path" "$(cat "$tmp/half-ran")"
|
|
runtime=$ROOT
|
|
pass "a boot package implementing only half of the unlock pair fails closed"
|
|
|
|
# Any slot but the owner's is retired, a recovery key an earlier setup added
|
|
# and recorded in the journal included.
|
|
platform=x86
|
|
backend=fake
|
|
fixture
|
|
printf '2 recovery-key\n' >>"$tmp/slots"
|
|
printf 'recovery_slot=2\nrecovery_shown=1\n' >"$tmp/provisioning/luks-rekey.state"
|
|
chmod 600 "$tmp/provisioning/luks-rekey.state"
|
|
run rekey "$owner_password" || fail "the re-key beside an earlier recovery slot completes" "$(cat "$tmp/log")"
|
|
[[ -z $(opens "$staged_key") && -z $(opens "$seller_key") && -z $(opens recovery-key) && -n $(opens "$owner_password") && $(slot_count) == 1 ]] ||
|
|
fail "every slot but the owner's is retired" "$(cat "$tmp/slots")"
|
|
pass "the re-key retires every slot but the owner's, an earlier recovery slot included"
|