#!/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"