Re-sign historical UKIs before changing USB boot policy
This commit is contained in:
1 parent
db6ab5a8bf
commit
de72b64872
3 files changed
+445
No files matched your search
@@ -381,6 +381,188 @@ usb_authorization_rewrite_snapshot_manifest() {
|
||||
mv -f "$temporary" "$manifest"
|
||||
}
|
||||
|
||||
usb_authorization_verify_signature() {
|
||||
# sbctl verify can return zero for unsigned or unreadable files. Check its
|
||||
# structured result, including the exact path, instead of just the exit code.
|
||||
if ! sbctl verify --json "$1" | jq -e --arg file "$1" \
|
||||
'length == 1 and .[0].file_name == $file and .[0].is_signed == 1' >/dev/null; then
|
||||
echo "Cannot verify the Secure Boot signature of $1 using this machine's signing keys." >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Called with Limine's boot-partition lock held. Prepare signed copies of
|
||||
# conflicting historical UKIs before changing the selected boot policy.
|
||||
usb_authorization_reconcile_archived_images() (
|
||||
local expected="$1" config="$2" esp="$3" machine_id="$4"
|
||||
local cache="${5:-/var/cache/boot/$machine_id/lss/snapshots.json}"
|
||||
local history="$esp/$machine_id/limine_history" staging manifest names name image
|
||||
local cmdline hash_command old_digest new_digest old_hash new_hash replacement index=0
|
||||
local published=0 committed=0 section layout image_base image_size alignment address
|
||||
local -a manifests=() originals=()
|
||||
|
||||
usb_authorization_secure_boot_enabled || return 0
|
||||
[[ -d $history ]] || return 0
|
||||
staging=$(mktemp -d "$esp/.omarchy-usb-uki.XXXXXXXXXX") || return 1
|
||||
trap '
|
||||
if (( published && ! committed )); then
|
||||
for ((index=0; index<${#originals[@]}; index++)); do
|
||||
cp --preserve=mode,ownership "$staging/original.$index" "${originals[index]}" || true
|
||||
done
|
||||
limine-enroll-config >/dev/null 2>&1 || true
|
||||
fi
|
||||
rm -rf "$staging"
|
||||
' EXIT
|
||||
|
||||
for manifest in "$history/snapshots.json" "$history/snapshots.json.old" "$cache"; do
|
||||
[[ -f $manifest ]] || continue
|
||||
jq -e '.jsonFormatVersion == "1.3.0" and (.snapshotEntries | type == "array")' "$manifest" >/dev/null || return 1
|
||||
manifests+=("$manifest")
|
||||
done
|
||||
(( ${#manifests[@]} )) || return 0
|
||||
names=$(jq -sr '[.[] | .snapshotEntries[] | .. | objects | .imageDetails? // empty | .[] | .fileHashName] | unique[]' "${manifests[@]}") || return 1
|
||||
printf '[]\n' >"$staging/replacements.json"
|
||||
|
||||
while IFS= read -r name; do
|
||||
[[ -n $name ]] || continue
|
||||
# Only image names, never arbitrary paths supplied by a manifest.
|
||||
if [[ $name == */* || $name == "." || $name == ".." ]]; then
|
||||
echo "Invalid archived kernel image name: $name" >&2
|
||||
return 1
|
||||
fi
|
||||
image="$history/$name"
|
||||
[[ -f $image && ! -L $image ]] || continue
|
||||
: >"$staging/cmdline"
|
||||
objcopy -O binary --only-section=.cmdline "$image" "$staging/cmdline" 2>/dev/null || continue
|
||||
cmdline=$(tr '\0' '\n' <"$staging/cmdline")
|
||||
[[ -n $cmdline ]] || continue
|
||||
if usb_authorization_cmdline_has_parameter "$cmdline" "$kernel_parameter"; then
|
||||
[[ $expected == "disabled" ]] || continue
|
||||
else
|
||||
[[ $expected == "enabled" ]] || continue
|
||||
fi
|
||||
|
||||
# Multi-profile images and signed PCR policies need their original builder.
|
||||
# Do not silently invalidate their additional boot/unlock guarantees.
|
||||
for section in .profile .pcrsig; do
|
||||
objcopy -O binary --only-section="$section" "$image" "$staging/section" || return 1
|
||||
if [[ -s $staging/section ]]; then
|
||||
echo "Rebuild archived image $image with its original signing policy before changing USB boot protection ($section)." >&2
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
if [[ ! $name =~ ^(.+)_(sha1|sha256|b3|xxh)_([0-9a-f]+)$ ]]; then
|
||||
echo "Cannot verify the content hash of archived image $image." >&2
|
||||
return 1
|
||||
fi
|
||||
case "${BASH_REMATCH[2]}" in
|
||||
sha1) hash_command=sha1sum ;;
|
||||
sha256) hash_command=sha256sum ;;
|
||||
b3) hash_command=b3sum ;;
|
||||
xxh) hash_command=xxhsum ;;
|
||||
esac
|
||||
old_digest=${BASH_REMATCH[3]}
|
||||
new_digest=$("$hash_command" "$image") || return 1
|
||||
if [[ ${new_digest%% *} != "$old_digest" ]]; then
|
||||
echo "Archived image hash mismatch: $image" >&2
|
||||
return 1
|
||||
fi
|
||||
# Verify the old signature before signing a modified copy with this machine's key.
|
||||
usb_authorization_verify_signature "$image" || return 1
|
||||
cp "$image" "$staging/unsigned.efi" || return 1
|
||||
sbattach --remove "$staging/unsigned.efi" || return 1
|
||||
# Preserve the embedded boot arguments and change only our exact USB
|
||||
# parameter. Do not turn a signed command line into an external override.
|
||||
printf '%s' "$cmdline" | jq -Rrs --arg parameter "$kernel_parameter" --arg expected "$expected" '
|
||||
[scan("[^[:space:]]+|[[:space:]]+") | select(. != $parameter)]
|
||||
| join("") | sub("[[:space:]]+$"; "")
|
||||
| if $expected == "enabled" then . + " " + $parameter else . end
|
||||
' >"$staging/new-cmdline" || return 1
|
||||
printf '\0' >>"$staging/new-cmdline"
|
||||
# objcopy --update-section retains PE VirtualSize and can truncate a longer
|
||||
# command line. Add a fresh section after the existing virtual image instead.
|
||||
layout=$(objdump -p "$staging/unsigned.efi" | awk '
|
||||
$1 == "ImageBase" { base = $2 }
|
||||
$1 == "SizeOfImage" { size = $2 }
|
||||
$1 == "SectionAlignment" { alignment = $2 }
|
||||
END {
|
||||
if (base !~ /^[0-9a-fA-F]+$/ || size !~ /^[0-9a-fA-F]+$/ || alignment !~ /^[0-9a-fA-F]+$/) exit 1
|
||||
print "0x" base, "0x" size, "0x" alignment
|
||||
}
|
||||
') || return 1
|
||||
read -r image_base image_size alignment <<<"$layout"
|
||||
(( alignment > 0 )) || return 1
|
||||
address=$((image_base + (image_size + alignment - 1) / alignment * alignment))
|
||||
objcopy --remove-section=.cmdline "$staging/unsigned.efi" "$staging/without-cmdline.efi" || return 1
|
||||
objcopy --add-section .cmdline="$staging/new-cmdline" \
|
||||
--change-section-vma .cmdline="$address" \
|
||||
--set-section-flags .cmdline=alloc,load,readonly,data,contents \
|
||||
"$staging/without-cmdline.efi" "$staging/image.efi" || return 1
|
||||
sbctl sign "$staging/image.efi" || return 1
|
||||
usb_authorization_verify_signature "$staging/image.efi" || return 1
|
||||
objcopy -O binary --only-section=.cmdline "$staging/image.efi" "$staging/cmdline" || return 1
|
||||
cmp -s "$staging/cmdline" "$staging/new-cmdline" || return 1
|
||||
new_digest=$("$hash_command" "$staging/image.efi") || return 1
|
||||
replacement="${name%"$old_digest"}${new_digest%% *}"
|
||||
old_hash=$(b2sum "$image") || return 1
|
||||
new_hash=$(b2sum "$staging/image.efi") || return 1
|
||||
# Publish copies first: every old reference remains usable until its manifest changes.
|
||||
if [[ -e $history/$replacement || -L $history/$replacement ]]; then
|
||||
[[ ! -L $history/$replacement ]] && cmp -s "$staging/image.efi" "$history/$replacement" || return 1
|
||||
else
|
||||
mv "$staging/image.efi" "$history/$replacement" || return 1
|
||||
fi
|
||||
jq --arg old "$name" --arg new "$replacement" \
|
||||
--arg old_hash "${old_hash%% *}" --arg new_hash "${new_hash%% *}" \
|
||||
'. + [{old: $old, new: $new, old_hash: $old_hash, new_hash: $new_hash}]' \
|
||||
"$staging/replacements.json" >"$staging/next.json" || return 1
|
||||
mv "$staging/next.json" "$staging/replacements.json" || return 1
|
||||
done <<<"$names"
|
||||
jq -e 'length > 0' "$staging/replacements.json" >/dev/null || return 0
|
||||
|
||||
originals=("$config" "${manifests[@]}")
|
||||
for ((index=0; index<${#originals[@]}; index++)); do
|
||||
cp --preserve=mode,ownership "${originals[index]}" "$staging/original.$index" || return 1
|
||||
cp --preserve=mode,ownership "${originals[index]}" "$staging/updated.$index" || return 1
|
||||
# Update cached menu lines, structured image metadata, restore paths and
|
||||
# both hashes. The new file name remains a valid Limine content address.
|
||||
if (( index == 0 )); then
|
||||
jq -Rrs --slurpfile replacements "$staging/replacements.json" '
|
||||
split("\n") | map(reduce $replacements[0][] as $r (. ;
|
||||
if contains($r.old) then split($r.old) | join($r.new) | split($r.old_hash) | join($r.new_hash)
|
||||
else . end)) | join("\n")
|
||||
' "$staging/original.$index" >"$staging/updated.$index" || return 1
|
||||
else
|
||||
jq --slurpfile replacements "$staging/replacements.json" '
|
||||
walk(if type == "string" then
|
||||
reduce $replacements[0][] as $r (. ; split($r.old) | join($r.new) | split($r.old_hash) | join($r.new_hash))
|
||||
else . end)
|
||||
' "$staging/original.$index" >"$staging/updated.$index" || return 1
|
||||
fi
|
||||
done
|
||||
published=1
|
||||
for ((index=0; index<${#originals[@]}; index++)); do
|
||||
mv -f "$staging/updated.$index" "${originals[index]}" || return 1
|
||||
done
|
||||
limine-enroll-config || return 1
|
||||
committed=1
|
||||
)
|
||||
|
||||
usb_authorization_prepare_archived_images() (
|
||||
local expected="$1" config="$2" esp="$3" machine_id="${4:-$(</etc/machine-id)}"
|
||||
|
||||
usb_authorization_secure_boot_enabled || return 0
|
||||
# Package hooks acquire the boot lock too; install this optional tool before
|
||||
# taking it, never while rewriting archived images.
|
||||
if omarchy-cmd-missing sbattach; then
|
||||
omarchy-pkg-add sbsigntools || return 1
|
||||
fi
|
||||
exec 9>/run/lock/boot-partition.lock
|
||||
flock --timeout 30 9 || return 1
|
||||
limine-snapper-sync --no-mutex --no-hooks || return 1
|
||||
usb_authorization_reconcile_archived_images "$expected" "$config" "$esp" "$machine_id"
|
||||
)
|
||||
|
||||
usb_authorization_sync_and_verify() (
|
||||
local expected="$1" config="$2" esp="$3"
|
||||
local machine_id="${4:-$(</etc/machine-id)}" manifest
|
||||
@@ -392,6 +574,7 @@ usb_authorization_sync_and_verify() (
|
||||
|
||||
# Recover/upgrade the manifest first, including backups if necessary.
|
||||
limine-snapper-sync --no-mutex --no-hooks || return 1
|
||||
usb_authorization_reconcile_archived_images "$expected" "$config" "$esp" "$machine_id" || return 1
|
||||
for manifest in \
|
||||
"$esp/$machine_id/limine_history/snapshots.json" \
|
||||
"$esp/$machine_id/limine_history/snapshots.json.old" \
|
||||
@@ -433,6 +616,7 @@ main() {
|
||||
exit 1
|
||||
fi
|
||||
|
||||
usb_authorization_prepare_archived_images enabled "$limine_conf" "$boot_path" || exit 1
|
||||
usb_authorization_enable_snapshot_setting "$limine_defaults"
|
||||
|
||||
if [[ ! -e $drop_in ]]; then
|
||||
@@ -468,6 +652,10 @@ main() {
|
||||
exit 1
|
||||
fi
|
||||
had_drop_in=1
|
||||
fi
|
||||
|
||||
usb_authorization_prepare_archived_images disabled "$limine_conf" "$boot_path" || exit 1
|
||||
if (( had_drop_in )); then
|
||||
rm -f "$drop_in"
|
||||
fi
|
||||
|
||||
|
||||
@@ -25,6 +25,8 @@ For machines whose disk unlock and recovery never depend on any USB devices, _Se
|
||||
|
||||
Older snapshots created before USBGuard was installed and enabled keep all USB disabled even after startup, including keyboards, network adapters, and storage. Trusting the devices connected now does not make them available in those snapshots. Enable boot-time protection only if you can unlock and recover the machine without any USB devices. _Remove > Security > USB at Boot_ restores the normal early-boot behavior while keeping USBGuard active after startup.
|
||||
|
||||
With Secure Boot enabled, changing USB boot protection may require access to your signing keys to update older snapshot boot images. If an image uses a custom measured-boot policy or multiple boot profiles, rebuild it with its original signing setup before retrying.
|
||||
|
||||
## Changing your passwords
|
||||
|
||||
You have two passwords on an encrypted install: the one that unlocks the drive at boot, and the one you log in and `sudo` with. Both can be changed under _Update > Password_ in the Omarchy menu — _Drive Encryption_ for the first, _User_ for the second. Changing the drive password asks for the current one first, so have it handy.
|
||||
|
||||
Executable
+255
@@ -0,0 +1,255 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
||||
source "$ROOT/bin/omarchy-usb-authorization-boot"
|
||||
|
||||
scratch=$(mktemp -d)
|
||||
trap 'rm -rf "$scratch"' EXIT
|
||||
machine=12345678901234567890123456789012
|
||||
export calls="$scratch/calls"
|
||||
: >"$calls"
|
||||
|
||||
# Optional integration run uses real sbctl keys and sbattach in isolated paths.
|
||||
sbctl() {
|
||||
printf '%s\n' "$1" >>"$calls"
|
||||
[[ ${SIGN_FAIL:-0} != 1 || $1 != sign ]] || return 1
|
||||
[[ ${VERIFY_FAIL:-0} != 1 || $1 != verify ]] || return 1
|
||||
if [[ -n ${USB_TEST_SBCTL_CONFIG:-} ]]; then
|
||||
/usr/bin/sbctl --config "$USB_TEST_SBCTL_CONFIG" "$@"
|
||||
elif [[ $1 == verify ]]; then
|
||||
jq -n --arg file "${!#}" --argjson signed "${TEST_SIGNED:-1}" '[{file_name: $file, is_signed: $signed}]'
|
||||
fi
|
||||
}
|
||||
sbattach() {
|
||||
if [[ -n ${USB_TEST_SBATTACH:-} ]]; then
|
||||
"$USB_TEST_SBATTACH" "$@"
|
||||
fi
|
||||
}
|
||||
omarchy-cmd-missing() { return 1; }
|
||||
limine-enroll-config() { [[ ${ENROLL_FAIL:-0} != 1 ]]; }
|
||||
usb_authorization_secure_boot_enabled() { [[ ${SECURE_BOOT:-1} == 1 ]]; }
|
||||
|
||||
fixture() {
|
||||
local embedded="$1" hash digest image_base
|
||||
esp="$scratch/case-$RANDOM"
|
||||
history="$esp/$machine/limine_history"
|
||||
manifest="$history/snapshots.json"
|
||||
cache="$esp/cache.json"
|
||||
config="$esp/limine.conf"
|
||||
mkdir -p "$history"
|
||||
printf '%s\0' "$embedded" >"$esp/cmdline"
|
||||
cp /usr/lib/systemd/boot/efi/linuxx64.efi.stub "$esp/image.efi"
|
||||
image_base=$(objdump -p "$esp/image.efi" | awk '$1 == "ImageBase" {print "0x" $2}')
|
||||
objcopy --add-section .cmdline="$esp/cmdline" --change-section-vma .cmdline="$((image_base + 0x100000))" "$esp/image.efi"
|
||||
if [[ -n ${USB_TEST_SBCTL_CONFIG:-} ]]; then
|
||||
sbctl sign "$esp/image.efi" >/dev/null
|
||||
fi
|
||||
digest=$(sha256sum "$esp/image.efi"); digest=${digest%% *}
|
||||
old_name="test.efi_sha256_$digest"
|
||||
cp "$esp/image.efi" "$history/$old_name"
|
||||
hash=$(b2sum "$esp/image.efi"); hash=${hash%% *}
|
||||
printf '/Omarchy\ncomment: machine-id=%s\n //snapshot\n comment: kernel-id=linux\n path: boot():/%s/limine_history/%s#%s\n cmdline: %s rootflags=subvol=/snapshot\n' \
|
||||
"$machine" "$machine" "$old_name" "$hash" "$embedded" >"$config"
|
||||
jq -n --arg name "$old_name" --arg hash "$hash" --arg machine "$machine" --arg cmdline "$embedded" '
|
||||
def kernel:
|
||||
{imageDetails: [{fileHashName: $name, fileName: "test.efi",
|
||||
snapshotFilePathLine: "/" + $machine + "/limine_history/" + $name + "#" + $hash,
|
||||
filePathLine: "/EFI/Linux/test.efi#" + $hash, properties: {HASH: "#" + $hash}}],
|
||||
cmdlineDetails: [{cmdline: $cmdline, snapshotCmdline: $cmdline + " rootflags=subvol=/snapshot"}],
|
||||
allInConfig: ["path: boot():/EFI/Linux/test.efi#" + $hash, "cmdline: " + $cmdline],
|
||||
allInSnapshotConfig: ["comment: kernel-id=linux", "path: boot():/" + $machine + "/limine_history/" + $name + "#" + $hash, "cmdline: " + $cmdline + " rootflags=subvol=/snapshot"], subKernels: []};
|
||||
{jsonFormatVersion: "1.3.0", snapshotEntries: [{kernelEntries: [kernel | .subKernels = [kernel]]}]}
|
||||
' >"$manifest"
|
||||
cp "$manifest" "$manifest.old"
|
||||
cp "$manifest" "$cache"
|
||||
cp "$manifest" "$esp/before.json"
|
||||
cp "$config" "$esp/before.conf"
|
||||
: >"$calls"
|
||||
}
|
||||
|
||||
for expected in enabled disabled; do
|
||||
old='quiet'
|
||||
[[ $expected != disabled ]] || old+=' usbcore.authorized_default=0'
|
||||
fixture "$old"
|
||||
usb_authorization_reconcile_archived_images "$expected" "$config" "$esp" "$machine" "$cache"
|
||||
new_name=$(jq -r '.snapshotEntries[0].kernelEntries[0].imageDetails[0].fileHashName' "$manifest")
|
||||
[[ $new_name != "$old_name" && -f $history/$new_name ]] || fail "conflicting archives get new content-addressed copies"
|
||||
cmp "$history/$old_name" "$esp/image.efi" || fail "original archived image remains unchanged"
|
||||
digest=$(sha256sum "$history/$new_name")
|
||||
[[ $new_name == *"${digest%% *}" ]] || fail "replacement name matches its content hash"
|
||||
objcopy -O binary --only-section=.cmdline "$history/$new_name" "$esp/section"
|
||||
embedded=$(tr '\0' '\n' <"$esp/section")
|
||||
wanted=quiet
|
||||
[[ $expected != enabled ]] || wanted+=' usbcore.authorized_default=0'
|
||||
[[ $embedded == "$wanted" ]] || fail "replacement must preserve embedded boot arguments and change only USB policy"
|
||||
objcopy -O binary --only-section=.text "$history/$old_name" "$esp/text.before"
|
||||
objcopy -O binary --only-section=.text "$history/$new_name" "$esp/text.after"
|
||||
cmp "$esp/text.before" "$esp/text.after" || fail "archive conversion must not alter executable code"
|
||||
[[ $(grep -c '^sign$' "$calls") == 1 && $(grep -c '^verify$' "$calls") == 2 ]] || fail "deduplicated image must be verified before and after signing"
|
||||
for file in "$manifest" "$manifest.old" "$cache"; do
|
||||
! grep -Fq "$old_name" "$file" || fail "no old image references may survive in manifests"
|
||||
usb_authorization_rewrite_snapshot_manifest "$expected" "$file"
|
||||
# Both cached and structured restore representations must regenerate valid entries.
|
||||
for format in cached structured; do
|
||||
{
|
||||
printf '/Omarchy\ncomment: machine-id=%s\n //snapshot\n' "$machine"
|
||||
if [[ $format == cached ]]; then
|
||||
jq -r '.snapshotEntries[0].kernelEntries[0].allInSnapshotConfig[]' "$file"
|
||||
else
|
||||
printf 'comment: kernel-id=linux\n'
|
||||
jq -r '.snapshotEntries[0].kernelEntries[0].subKernels[0] | "path: boot():" + .imageDetails[0].snapshotFilePathLine, "cmdline: " + .cmdlineDetails[0].snapshotCmdline' "$file"
|
||||
fi
|
||||
} >"$esp/regenerated.conf"
|
||||
usb_authorization_verify_boot_entries "$expected" "$esp/regenerated.conf" "$esp" "$machine" enabled || fail "regenerated $format $expected snapshot must verify"
|
||||
done
|
||||
done
|
||||
usb_authorization_rewrite_boot_cmdlines "$expected" "$config" "$machine"
|
||||
usb_authorization_verify_boot_entries "$expected" "$config" "$esp" "$machine" enabled || fail "$expected archive transition must verify under Secure Boot"
|
||||
usb_authorization_reconcile_archived_images "$expected" "$config" "$esp" "$machine" "$cache"
|
||||
[[ $(grep -c '^sign$' "$calls") == 1 ]] || fail "retry must not resign an already-compatible archive"
|
||||
if [[ -n ${USB_TEST_SBCTL_CONFIG:-} ]]; then
|
||||
usb_authorization_verify_signature "$history/$new_name"
|
||||
fi
|
||||
pass "USB $expected transition updates signed history and all cached/structured references"
|
||||
done
|
||||
|
||||
fixture quiet
|
||||
old_hash=$(b2sum "$esp/image.efi"); old_hash=${old_hash%% *}
|
||||
printf ' //current\n comment: kernel-id=current\n path: boot():/image.efi#%s\n cmdline: quiet\n' "$old_hash" >>"$config"
|
||||
usb_authorization_reconcile_archived_images enabled "$config" "$esp" "$machine" "$cache"
|
||||
grep -Fq "path: boot():/image.efi#$old_hash" "$config" || fail "preflight must not rewrite hashes of unchanged current images sharing archive content"
|
||||
pass "USB archive preflight preserves unchanged current-image hashes"
|
||||
|
||||
fixture 'quiet rd.test="two spaces"'
|
||||
usb_authorization_reconcile_archived_images enabled "$config" "$esp" "$machine" "$cache"
|
||||
new_name=$(jq -r '.snapshotEntries[0].kernelEntries[0].imageDetails[0].fileHashName' "$manifest")
|
||||
objcopy -O binary --only-section=.cmdline "$history/$new_name" "$esp/section"
|
||||
[[ $(tr '\0' '\n' <"$esp/section") == 'quiet rd.test="two spaces" usbcore.authorized_default=0' ]] || fail "unrelated quoted arguments must retain their whitespace"
|
||||
pass "USB archive policy changes preserve unrelated embedded arguments and executable code"
|
||||
|
||||
for failure in SIGN_FAIL VERIFY_FAIL ENROLL_FAIL; do
|
||||
fixture quiet
|
||||
if (export "$failure=1"; usb_authorization_reconcile_archived_images enabled "$config" "$esp" "$machine" "$cache") >"$esp/failure.log" 2>&1; then
|
||||
fail "$failure must prevent publication"
|
||||
fi
|
||||
cmp "$config" "$esp/before.conf" || fail "$failure must preserve the original boot menu"
|
||||
for file in "$manifest" "$manifest.old" "$cache"; do
|
||||
cmp "$file" "$esp/before.json" || fail "$failure must preserve every manifest"
|
||||
done
|
||||
cmp "$history/$old_name" "$esp/image.efi" || fail "$failure must preserve the archived image"
|
||||
done
|
||||
pass "USB archive signing, verification and enrollment failures preserve original references"
|
||||
|
||||
fixture quiet
|
||||
printf corrupt >>"$history/$old_name"
|
||||
if usb_authorization_reconcile_archived_images enabled "$config" "$esp" "$machine" "$cache" >"$esp/hash-failure" 2>&1; then
|
||||
fail "corrupt original archive must never be signed"
|
||||
fi
|
||||
[[ ! -s $calls ]] || fail "hash failure must occur before signing"
|
||||
cmp "$config" "$esp/before.conf"
|
||||
SECURE_BOOT=0 usb_authorization_reconcile_archived_images enabled "$config" "$esp" "$machine" "$cache"
|
||||
[[ ! -s $calls ]] || fail "non-Secure-Boot transitions must not rewrite archive images"
|
||||
pass "USB archive conversion rejects corruption and skips non-Secure-Boot systems"
|
||||
|
||||
if [[ -z ${USB_TEST_SBCTL_CONFIG:-} ]]; then
|
||||
fixture quiet
|
||||
if TEST_SIGNED=0 usb_authorization_reconcile_archived_images enabled "$config" "$esp" "$machine" "$cache" >"$esp/unsigned" 2>&1; then
|
||||
fail "a false-success sbctl result cannot authorize signing"
|
||||
fi
|
||||
! grep -qx sign "$calls" || fail "an unsigned original must not be resigned"
|
||||
fi
|
||||
for missing in menu manifest; do
|
||||
fixture quiet
|
||||
if [[ $missing == menu ]]; then
|
||||
sed -i '/cmdline:/d' "$config"
|
||||
else
|
||||
jq '.snapshotEntries[0].kernelEntries[0].cmdlineDetails = []' "$manifest" >"$esp/modified"
|
||||
mv "$esp/modified" "$manifest"
|
||||
fi
|
||||
usb_authorization_reconcile_archived_images enabled "$config" "$esp" "$machine" "$cache" || fail "embedded-only $missing must support policy changes"
|
||||
usb_authorization_rewrite_boot_cmdlines enabled "$config" "$machine"
|
||||
usb_authorization_verify_boot_entries enabled "$config" "$esp" "$machine" enabled || fail "embedded-only $missing must retain a valid command line"
|
||||
done
|
||||
for section in .profile .pcrsig; do
|
||||
fixture quiet
|
||||
printf protected >"$esp/protected"
|
||||
objcopy --add-section "$section=$esp/protected" "$history/$old_name" 2>/dev/null
|
||||
if usb_authorization_reconcile_archived_images enabled "$config" "$esp" "$machine" "$cache" >"$esp/protected-failure" 2>&1; then
|
||||
fail "a $section image must require its original builder"
|
||||
fi
|
||||
grep -Fq "($section)" "$esp/protected-failure" || fail "unsupported signing policies need an actionable error"
|
||||
[[ ! -s $calls ]] || fail "unsupported signing policies must not be resigned"
|
||||
done
|
||||
pass "USB archive conversion requires authentic signatures and preserves boot/unlock requirements"
|
||||
|
||||
# Package hooks use this same lock. Installing the optional signature tool
|
||||
# must finish before preparation takes the lock, while sync must run under it.
|
||||
fixture quiet
|
||||
prepare=$(declare -f usb_authorization_prepare_archived_images)
|
||||
prepare=${prepare//\/run\/lock\/boot-partition.lock/$scratch\/preflight.lock}
|
||||
eval "$prepare"
|
||||
omarchy-cmd-missing() { return 0; }
|
||||
omarchy-pkg-add() {
|
||||
flock --nonblock "$scratch/preflight.lock" true || fail "package installation must precede the boot lock"
|
||||
printf 'package\n' >>"$calls"
|
||||
}
|
||||
limine-snapper-sync() {
|
||||
[[ $* == '--no-mutex --no-hooks' ]] || fail "preparation must use the already-held lock"
|
||||
if flock --nonblock "$scratch/preflight.lock" true; then
|
||||
fail "snapshot synchronization must hold the boot lock"
|
||||
fi
|
||||
}
|
||||
usb_authorization_prepare_archived_images enabled "$config" "$esp" "$machine"
|
||||
grep -qx package "$calls" || fail "missing signature tooling must be installed before preparing archives"
|
||||
pass "USB archive preparation installs optional tools before taking Limine's lock"
|
||||
|
||||
# The disposable Lab runs the actual root-only main function with only boot paths
|
||||
# and external build/sync commands redirected into this fixture.
|
||||
if [[ ${USB_TEST_MAIN:-0} == 1 ]]; then
|
||||
(( EUID == 0 )) || fail "main-path fixtures require the disposable guest's root"
|
||||
usb_authorization_prepare_archived_images() {
|
||||
usb_authorization_reconcile_archived_images "$1" "$2" "$3" "$machine" "$cache"
|
||||
}
|
||||
limine-mkinitcpio() { return 0; }
|
||||
usb_authorization_sync_and_verify() {
|
||||
local file
|
||||
for file in "$manifest" "$manifest.old" "$cache"; do
|
||||
usb_authorization_rewrite_snapshot_manifest "$1" "$file" || return 1
|
||||
done
|
||||
usb_authorization_rewrite_boot_cmdlines "$1" "$2" "$machine" || return 1
|
||||
usb_authorization_verify_boot_entries "$1" "$2" "$3" "$machine" enabled
|
||||
}
|
||||
for action in enable disable; do
|
||||
old=quiet
|
||||
[[ $action != disable ]] || old+=' usbcore.authorized_default=0'
|
||||
fixture "$old"
|
||||
drop_in="$esp/entry-tool/usb-authorization.conf"
|
||||
limine_defaults="$esp/defaults"
|
||||
limine_conf="$config"
|
||||
boot_path="$esp"
|
||||
printf '# fixture defaults\n' >"$limine_defaults"
|
||||
if [[ $action == disable ]]; then
|
||||
mkdir -p "${drop_in%/*}"
|
||||
printf '%s\n' "$setting" >"$drop_in"
|
||||
usb_authorization_enable_snapshot_setting "$limine_defaults"
|
||||
fi
|
||||
cp "$limine_defaults" "$esp/defaults.before"
|
||||
if (export SIGN_FAIL=1; main "$action") >"$esp/main-failed" 2>&1; then
|
||||
fail "$action must stop if archive preparation fails"
|
||||
fi
|
||||
cmp "$limine_defaults" "$esp/defaults.before" || fail "$action failure must precede snapshot-setting changes"
|
||||
if [[ $action == enable ]]; then
|
||||
[[ ! -e $drop_in ]] || fail "enable preflight failure must not create the boot setting"
|
||||
else
|
||||
[[ $(<"$drop_in") == "$setting" ]] || fail "disable preflight failure must preserve the boot setting"
|
||||
fi
|
||||
(main "$action")
|
||||
if [[ $action == enable ]]; then
|
||||
[[ $(<"$drop_in") == "$setting" ]] || fail "successful enable sets the boot policy"
|
||||
else
|
||||
[[ ! -e $drop_in ]] || fail "successful disable clears the boot policy"
|
||||
fi
|
||||
pass "USB $action main path prepares archives before changing boot policy"
|
||||
done
|
||||
fi
|
||||
Reference in new issue
Block a user