99 lines
4.4 KiB
Bash
99 lines
4.4 KiB
Bash
#!/usr/bin/env bash
|
|
# fleet-sb-enroll — enrol the fleet's custom Secure Boot keys (PK/KEK/db) from the
|
|
# baked PUBLIC .auth files into firmware, via efivarfs.
|
|
#
|
|
# EL9 ships no efitools (no efi-updatevar), so this is the OS-side equivalent: it
|
|
# prepends the EFI authenticated-variable attribute mask to each signed .auth
|
|
# payload and writes it to /sys/firmware/efi/efivars in a single write(), in the
|
|
# order db -> KEK -> PK (PK last leaves Setup Mode and turns Secure Boot on).
|
|
#
|
|
# Run as root over SSH on a workstation that is in Secure Boot SETUP MODE. Only
|
|
# PUBLIC material is used (the .auth files carry certs + signatures, never the
|
|
# PK/KEK/db private keys, which stay offline/HSM — see docs/secure-boot-keys.md).
|
|
#
|
|
# Usage:
|
|
# fleet-sb-enroll --check # print SB state + which keys are baked; do nothing
|
|
# fleet-sb-enroll # enrol db, KEK, PK (PK last) — requires Setup Mode
|
|
# fleet-sb-enroll --force # attempt even if not in Setup Mode (User-Mode
|
|
# # writes only succeed if each .auth is signed by
|
|
# # the in-place parent key)
|
|
#
|
|
# NOTE: this is a deliberate trust-bootstrap action. In Setup Mode the firmware
|
|
# accepts these writes unauthenticated, so only run it on a host you control, on a
|
|
# trusted/isolated network. See docs/TODO.md #10 for the auto-enrol risk model.
|
|
set -euo pipefail
|
|
|
|
KEYDIR=/usr/share/fleet-secureboot
|
|
EFIVARS=/sys/firmware/efi/efivars
|
|
GUID_GLOBAL=8be4df61-93ca-11d2-aa0d-00e098032b8c # PK, KEK, SetupMode
|
|
GUID_DB=d719b2cb-3d3a-4596-a3bc-dad00e67656f # db, dbx
|
|
# EFI var attributes: NON_VOLATILE|BOOTSERVICE_ACCESS|RUNTIME_ACCESS|
|
|
# TIME_BASED_AUTHENTICATED_WRITE_ACCESS = 0x27, written as a 4-byte little-endian
|
|
# u32 prefixed to the .auth payload. These 4 bytes contain NULs, so they are
|
|
# emitted with printf at WRITE TIME, never stored in a variable: a bash string
|
|
# can't hold a NUL (it truncates at the first one), which would leave only the
|
|
# 0x27 byte, corrupt the 4-byte attribute field efivarfs reads, and make the
|
|
# kernel reject the write with EINVAL ("dd: Invalid argument").
|
|
|
|
die() { echo "fleet-sb-enroll: $*" >&2; exit 1; }
|
|
|
|
[ "$(id -u)" = 0 ] || die "must run as root (use sudo)"
|
|
[ -d "$EFIVARS" ] || die "efivarfs not present ($EFIVARS) — not a UEFI system / not booted via UEFI?"
|
|
|
|
setup_mode() { # echoes yes|no|unknown
|
|
local f="$EFIVARS/SetupMode-$GUID_GLOBAL"
|
|
[ -e "$f" ] || { echo unknown; return; }
|
|
# 4 attribute bytes then 1 data byte: 1 = Setup Mode, 0 = User Mode.
|
|
if [ "$(od -An -tu1 -j4 -N1 "$f" | tr -d ' ')" = 1 ]; then echo yes; else echo no; fi
|
|
}
|
|
|
|
show_state() {
|
|
echo "Secure Boot: $(mokutil --sb-state 2>/dev/null | tr '\n' ' ' || echo '?')"
|
|
echo "Setup Mode: $(setup_mode)"
|
|
echo "Baked keys ($KEYDIR):"
|
|
local k
|
|
for k in db KEK PK; do
|
|
if [ -f "$KEYDIR/$k.auth" ]; then echo " $k.auth present"; else echo " $k.auth MISSING"; fi
|
|
done
|
|
}
|
|
|
|
enrol_one() { # $1=var name $2=guid $3=.auth path
|
|
local f="$EFIVARS/$1-$2" tmp
|
|
[ -f "$3" ] || die "missing $3"
|
|
tmp=$(mktemp)
|
|
printf '\x27\x00\x00\x00' > "$tmp" # 4-byte LE attribute prefix (see note above)
|
|
cat "$3" >> "$tmp"
|
|
# Existing EFI vars carry the immutable flag; clear it before overwriting.
|
|
[ -e "$f" ] && chattr -i "$f" 2>/dev/null || true
|
|
# efivarfs requires the whole variable in ONE write() — dd, single block.
|
|
dd if="$tmp" of="$f" bs="$(stat -c%s "$tmp")" count=1 status=none \
|
|
|| { rm -f "$tmp"; die "write to '$1' failed (in Setup Mode? .auth signed by the right parent key?)"; }
|
|
rm -f "$tmp"
|
|
echo " enrolled $1"
|
|
}
|
|
|
|
FORCE=0
|
|
case "${1:-}" in
|
|
--check) show_state; exit 0 ;;
|
|
--force) FORCE=1 ;;
|
|
"") FORCE=0 ;;
|
|
*) die "usage: fleet-sb-enroll [--check|--force]" ;;
|
|
esac
|
|
|
|
# Require all three baked keys.
|
|
for k in db KEK PK; do
|
|
[ -f "$KEYDIR/$k.auth" ] || die "no baked keys at $KEYDIR/$k.auth — supply public PK/KEK/db .auth via the build (files/secureboot/keys/)"
|
|
done
|
|
|
|
if [ "$(setup_mode)" != yes ] && [ "$FORCE" != 1 ]; then
|
|
show_state
|
|
die "not in Setup Mode — clear/reset Secure Boot keys in firmware first, or pass --force"
|
|
fi
|
|
|
|
echo "Enrolling fleet Secure Boot keys (order: db -> KEK -> PK)..."
|
|
enrol_one db "$GUID_DB" "$KEYDIR/db.auth"
|
|
enrol_one KEK "$GUID_GLOBAL" "$KEYDIR/KEK.auth"
|
|
enrol_one PK "$GUID_GLOBAL" "$KEYDIR/PK.auth" # PK last -> exits Setup Mode
|
|
echo "Done."
|
|
echo "Verify with: mokutil --db and mokutil --sb-state"
|