#!/bin/bash # KNEL-Football ISO Builder - Main Entry Point # Orchestrates Docker-based build process and VM testing # Copyright © 2026 Known Element Enterprises LLC # License: GNU Affero General Public License v3.0 only set -euo pipefail # Configuration variables SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" readonly SCRIPT_DIR readonly DOCKER_IMAGE="knel-football-dev:latest" readonly OUTPUT_DIR="${SCRIPT_DIR}/output" readonly BUILD_DIR="${SCRIPT_DIR}/tmp" readonly BUILD_LOG="/tmp/knel-iso-build.log" # VM Testing Configuration (system libvirt for virt-manager visibility, /tmp for no sudo) readonly ISO_PATH="${SCRIPT_DIR}/output/knel-football-secure.iso" readonly VM_NAME="knel-football-test" readonly VM_RAM="2048" readonly VM_CPUS="2" readonly VM_DISK_SIZE="10" readonly LIBVIRT_URI="qemu:///system" VM_DISK_PATH="/tmp/${VM_NAME}.qcow2" readonly VM_DISK_PATH VM_ISO_PATH="/tmp/${VM_NAME}.iso" readonly VM_ISO_PATH # Colors for output readonly RED='\033[0;31m' readonly GREEN='\033[0;32m' readonly YELLOW='\033[1;33m' readonly NC='\033[0m' # No Color # Logging functions log_info() { echo -e "${GREEN}[INFO]${NC} $1"; } log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } log_error() { echo -e "${RED}[ERROR]${NC} $1"; } # Create output and build directories if they don't exist mkdir -p "${OUTPUT_DIR}" "${BUILD_DIR}" # ============================================================================ # HOST FDE CHECK (MANDATORY) # ============================================================================ # Check if host system has full disk encryption enabled # This is MANDATORY - building or testing a secure OS on an unencrypted host # defeats the entire security model check_host_fde() { log_info "Checking host system for Full Disk Encryption..." local has_luks=false local encrypted_root=false # Method 1: Check for LUKS devices via lsblk if lsblk -o TYPE,FSTYPE 2>/dev/null | grep -q "crypt"; then has_luks=true log_info "Found LUKS encrypted partitions" fi # Method 2: Check if root filesystem is on a dm-crypt device if [[ -e /dev/mapper/root ]] || [[ -e /dev/mapper/rootfs ]]; then encrypted_root=true log_info "Root filesystem appears to be on encrypted device" fi # Method 3: Check /etc/crypttab for configured encrypted partitions if [[ -f /etc/crypttab ]] && grep -qE "^[^#]" /etc/crypttab 2>/dev/null; then has_luks=true log_info "Found encrypted partitions in /etc/crypttab" fi # Method 4: Check for dm-crypt devices in /sys/block if find /sys/block -maxdepth 1 -name 'dm-*' -print -quit 2>/dev/null | grep -q .; then for dm_dev in /sys/block/dm-*; do if [[ -f "${dm_dev}/dm/name" ]]; then local dm_name dm_name=$(cat "${dm_dev}/dm/name" 2>/dev/null) # Check if this is a LUKS device if [[ -f "${dm_dev}/dm/uuid" ]] && grep -qi "CRYPT-LUKS" "${dm_dev}/dm/uuid" 2>/dev/null; then has_luks=true log_info "Found LUKS device: ${dm_name}" fi fi done fi # Method 5: Check root mount point for encryption local root_device root_device=$(findmnt -n -o SOURCE / 2>/dev/null || echo "") if [[ "$root_device" == /dev/mapper/* ]] || [[ "$root_device" == *"crypt"* ]]; then encrypted_root=true log_info "Root filesystem is on encrypted device: $root_device" fi # Require at least one indicator of FDE if [[ "$has_luks" == "true" || "$encrypted_root" == "true" ]]; then log_info "Host FDE check PASSED" return 0 fi # FDE not detected - this is a FATAL error log_error "============================================================" log_error "SECURITY REQUIREMENT VIOLATION" log_error "============================================================" log_error "Host system does NOT have Full Disk Encryption enabled." log_error "" log_error "Building or testing KNEL-Football Secure OS requires the" log_error "host system to be encrypted with LUKS. An unencrypted host" log_error "defeats the entire security model." log_error "" log_error "To enable FDE on Debian/Ubuntu:" log_error " 1. Backup all data" log_error " 2. Reinstall with 'Guided - use entire disk and set up encrypted LVM'" log_error " 3. Or use: https://github.com/The Firefoxlyer/encrypt-existing-debian" log_error "" log_error "This check is MANDATORY and cannot be bypassed." log_error "============================================================" return 1 } # ============================================================================ # VM TESTING FUNCTIONS (merged from test-iso.sh) # ============================================================================ # Check VM testing prerequisites vm_check_prerequisites() { log_info "Checking VM testing prerequisites..." # Check for virsh command if ! command -v virsh &> /dev/null; then log_error "virsh command not found" log_error "Install libvirt: sudo apt install libvirt-clients libvirt-daemon-system qemu-system-x86" return 1 fi # Check system libvirt access (required for virt-manager visibility) if ! virsh -c "$LIBVIRT_URI" list &> /dev/null; then log_error "Cannot connect to system libvirt ($LIBVIRT_URI)" log_error "Ensure libvirtd is running and you have access" log_error "Try: sudo usermod -aG libvirt \$USER && logout/login" return 1 fi # Check for qemu-img command (required for disk creation) if ! command -v qemu-img &> /dev/null; then log_error "qemu-img command not found" log_error "Install qemu: sudo apt install qemu-utils" return 1 fi # Check ISO exists if [[ ! -f "$ISO_PATH" ]]; then log_error "ISO not found at: $ISO_PATH" log_error "Build the ISO first: ./run.sh iso" return 1 fi # Check if libvirt images directory exists if [[ ! -d "/var/lib/libvirt/images" ]]; then log_error "Libvirt images directory not found" log_error "Ensure libvirt is properly installed: sudo apt install libvirt-daemon-system" return 1 fi log_info "All prerequisites satisfied" return 0 } # Create and start VM using virsh define (virt-install requires storage pools) vm_create() { log_info "Creating VM: $VM_NAME (libvirt: $LIBVIRT_URI)" # Destroy existing VM if present virsh -c "$LIBVIRT_URI" destroy "$VM_NAME" 2>/dev/null || true virsh -c "$LIBVIRT_URI" undefine "$VM_NAME" --nvram 2>/dev/null || true # Ensure libvirt images directory exists mkdir -p "$(dirname "$VM_ISO_PATH")" # Copy ISO to user storage (no root required for session libvirt) log_info "Copying ISO to libvirt storage..." if ! cp -f "$ISO_PATH" "$VM_ISO_PATH"; then log_error "Failed to copy ISO" return 1 fi # Find UEFI firmware with Secure Boot support local uefi_code="" local uefi_vars="" for fw_dir in /usr/share/OVMF /usr/share/qemu; do if [[ -f "$fw_dir/OVMF_CODE_4M.secboot.fd" ]]; then uefi_code="$fw_dir/OVMF_CODE_4M.secboot.fd" uefi_vars="$fw_dir/OVMF_VARS_4M.fd" break elif [[ -f "$fw_dir/OVMF_CODE_4M.fd" ]]; then uefi_code="$fw_dir/OVMF_CODE_4M.fd" uefi_vars="$fw_dir/OVMF_VARS_4M.fd" break elif [[ -f "$fw_dir/OVMF_CODE.fd" ]]; then uefi_code="$fw_dir/OVMF_CODE.fd" uefi_vars="$fw_dir/OVMF_VARS.fd" break fi done if [[ -z "$uefi_code" || ! -f "$uefi_code" ]]; then log_error "UEFI firmware (OVMF) not found" log_error "Install required: sudo apt install ovmf" return 1 fi # Determine if Secure Boot is available local secure_boot="no" if [[ "$uefi_code" == *"secboot"* ]]; then secure_boot="yes" log_info "Using UEFI with Secure Boot: $uefi_code" else log_warn "Using UEFI WITHOUT Secure Boot: $uefi_code" fi # Pre-create disk image (no root required for session libvirt) log_info "Creating disk image: $VM_DISK_PATH" rm -f "$VM_DISK_PATH" 2>/dev/null || true mkdir -p "$(dirname "$VM_DISK_PATH")" if ! qemu-img create -f qcow2 "$VM_DISK_PATH" "${VM_DISK_SIZE}G"; then log_error "Failed to create disk image" return 1 fi # Use XML template for VM definition local template="${SCRIPT_DIR}/vm/template.xml" if [[ ! -f "$template" ]]; then log_error "VM template not found: $template" return 1 fi # Generate UUID local vm_uuid vm_uuid=$(cat /proc/sys/kernel/random/uuid) # Create VM XML from template local vm_xml="/tmp/${VM_NAME}.xml" sed -e "s|@VM_NAME@|${VM_NAME}|g" \ -e "s|@VM_UUID@|${vm_uuid}|g" \ -e "s|@VM_RAM@|${VM_RAM}|g" \ -e "s|@VM_CPUS@|${VM_CPUS}|g" \ -e "s|@SECURE_BOOT@|${secure_boot}|g" \ -e "s|@UEFI_CODE@|${uefi_code}|g" \ -e "s|@UEFI_VARS_TEMPLATE@|${uefi_vars}|g" \ -e "s|@VM_DISK@|${VM_DISK_PATH}|g" \ -e "s|@ISO_PATH@|${VM_ISO_PATH}|g" \ "$template" > "$vm_xml" log_info "Defining VM from XML..." # Define the VM if ! virsh -c "$LIBVIRT_URI" define "$vm_xml"; then log_error "Failed to define VM from XML" cat "$vm_xml" return 1 fi # Start the VM log_info "Starting VM..." if ! virsh -c "$LIBVIRT_URI" start "$VM_NAME"; then log_error "Failed to start VM" return 1 fi # Verify VM is running sleep 2 if virsh -c "$LIBVIRT_URI" domstate "$VM_NAME" 2>/dev/null | grep -q "running"; then log_info "VM created and STARTED successfully" else log_warn "VM created but may not be running - check virt-manager" fi # Get VNC display info local vnc_display vnc_display=$(virsh -c "$LIBVIRT_URI" vncdisplay "$VM_NAME" 2>/dev/null || echo "unknown") log_info "VNC display: $vnc_display" log_info "" log_info "Open virt-manager - VM '$VM_NAME' should be visible under QEMU/KVM" } # Connect to VM console vm_console() { log_info "Connecting to VM console..." virsh -c "$LIBVIRT_URI" console "$VM_NAME" } # Get VM status vm_status() { log_info "VM Status for: $VM_NAME" virsh -c "$LIBVIRT_URI" dominfo "$VM_NAME" 2>/dev/null || log_error "VM not running" } # Check if VM is running vm_is_running() { if virsh -c "$LIBVIRT_URI" domstate "$VM_NAME" 2>/dev/null | grep -q "running"; then return 0 fi return 1 } # Capture boot screenshot vm_capture_screen() { local output_dir="${SCRIPT_DIR}/tmp/vm-screenshots" mkdir -p "$output_dir" log_info "Capturing boot screen..." virsh -c "$LIBVIRT_URI" screenshot "$VM_NAME" "${output_dir}/boot-screen.ppm" 2>/dev/null || { log_warn "Could not capture screenshot" } } # Destroy VM and cleanup vm_destroy() { log_info "Destroying VM: $VM_NAME" virsh -c "$LIBVIRT_URI" destroy "$VM_NAME" 2>/dev/null || true virsh -c "$LIBVIRT_URI" undefine "$VM_NAME" --nvram 2>/dev/null || true rm -f "$VM_DISK_PATH" "$VM_ISO_PATH" "/tmp/${VM_NAME}.xml" "/tmp/${VM_NAME}_VARS.fd" # Also delete ISO to avoid confusion over build status if [[ -f "$ISO_PATH" ]]; then log_info "Removing ISO: $ISO_PATH" rm -f "$ISO_PATH" "${ISO_PATH}.md5" "${ISO_PATH}.sha256" fi log_info "Cleanup complete" } # Run automated boot test vm_boot_test() { log_info "Running automated boot test..." if ! vm_check_prerequisites; then return 1 fi vm_create log_info "Waiting for VM to boot (30 seconds)..." sleep 30 if vm_is_running; then log_info "VM is running - boot test PASSED" vm_status vm_capture_screen return 0 else log_error "VM not running - boot test FAILED" return 1 fi } # Test Secure Boot vm_test_secure_boot() { log_info "Testing Secure Boot..." if ! vm_is_running; then log_error "VM not running, start it first" return 1 fi log_info "Secure Boot verification requires manual console inspection" log_info "Use: ./run.sh test:iso console" log_info "Then check: dmesg | grep -i secure" } # Test FDE passphrase prompt vm_test_fde() { log_info "Testing FDE passphrase prompt..." if ! vm_is_running; then log_error "VM not running, start it first" return 1 fi log_info "FDE prompt verification requires manual console inspection" log_info "Use: ./run.sh test:iso console" log_info "Watch for 'Please unlock disk' prompt during boot" } # ============================================================================ # BUILD MONITOR FUNCTION (merged from monitor-build.sh) # ============================================================================ monitor_build() { local check_interval="${1:-180}" echo "=== ISO Build Monitor ===" echo "Started: $(date)" echo "Checking every ${check_interval}s" echo "Log file: $BUILD_LOG" echo "" while true; do if [ -f "$BUILD_LOG" ]; then local lines lines=$(wc -l < "$BUILD_LOG") local last_stage last_stage=$(grep -E "^\[.*\] lb (bootstrap|chroot|installer|binary|source)" "$BUILD_LOG" 2>/dev/null | tail -1) local errors errors=$(grep -ic "error\|failed\|fatal" "$BUILD_LOG" 2>/dev/null || echo "0") echo "[$(date '+%H:%M:%S')] Lines: $lines | Errors: $errors" [ -n "$last_stage" ] && echo " Stage: $last_stage" # Check if build completed if grep -q "ISO build completed" "$BUILD_LOG" 2>/dev/null; then echo "" echo "=== BUILD COMPLETED ===" echo "Finished: $(date)" ls -lh "${OUTPUT_DIR}"/*.iso 2>/dev/null || echo "No ISO found in output/" break fi # Check if build failed if grep -q "ISO build failed" "$BUILD_LOG" 2>/dev/null; then echo "" echo "=== BUILD FAILED ===" echo "Check log: $BUILD_LOG" tail -20 "$BUILD_LOG" break fi else echo "[$(date '+%H:%M:%S')] Waiting for build log..." fi sleep "$check_interval" done } # ============================================================================ # SECURE BOOT FUNCTIONS # ============================================================================ # Secure Boot Configuration readonly SB_KEY_DIR="${BUILD_DIR}/secureboot-keys" readonly SB_KEYS_SRC="${SCRIPT_DIR}/config/secureboot-keys" # Generate Secure Boot keys (PK, KEK, db) # Returns: 0 on success, 1 on failure sb_generate_keys() { log_info "Generating Secure Boot keys..." mkdir -p "${SB_KEY_DIR}" # Check for existing keys in source if [[ -d "${SB_KEYS_SRC}" ]]; then log_info "Using existing keys from ${SB_KEYS_SRC}" cp -r "${SB_KEYS_SRC}"/* "${SB_KEY_DIR}/" return 0 fi # Generate Platform Key (PK) - Root of trust log_info "Generating Platform Key (PK)..." openssl req -new -x509 -newkey rsa:4096 -sha256 -days 3650 \ -nodes -subj "/CN=KNEL-Football PK/" \ -keyout "${SB_KEY_DIR}/PK.key" \ -out "${SB_KEY_DIR}/PK.crt" 2>/dev/null # Generate Key Exchange Key (KEK) log_info "Generating Key Exchange Key (KEK)..." openssl req -new -x509 -newkey rsa:4096 -sha256 -days 3650 \ -nodes -subj "/CN=KNEL-Football KEK/" \ -keyout "${SB_KEY_DIR}/KEK.key" \ -out "${SB_KEY_DIR}/KEK.crt" 2>/dev/null # Generate Signature Database Key (db) log_info "Generating Signature Database Key (db)..." openssl req -new -x509 -newkey rsa:4096 -sha256 -days 3650 \ -nodes -subj "/CN=KNEL-Football db/" \ -keyout "${SB_KEY_DIR}/db.key" \ -out "${SB_KEY_DIR}/db.crt" 2>/dev/null # Verify all keys were created for key in PK KEK db; do if [[ ! -f "${SB_KEY_DIR}/${key}.key" ]] || [[ ! -f "${SB_KEY_DIR}/${key}.crt" ]]; then log_error "Failed to generate ${key} key" return 1 fi done log_info "Secure Boot keys generated successfully" return 0 } # Create EFI Signature List (ESL) from certificate # Args: $1 = key name (PK, KEK, or db) sb_create_esl() { local key_name="$1" local crt_file="${SB_KEY_DIR}/${key_name}.crt" local esl_file="${SB_KEY_DIR}/${key_name}.esl" if [[ ! -f "$crt_file" ]]; then log_error "Certificate not found: $crt_file" return 1 fi log_info "Creating ESL for ${key_name}..." cert-to-efi-sig-list -g "$(uuidgen)" "$crt_file" "$esl_file" if [[ ! -f "$esl_file" ]]; then log_error "Failed to create ESL for ${key_name}" return 1 fi return 0 } # Sign ESL file to create .auth file for UEFI enrollment # Args: $1 = key name to sign, $2 = signing key name sb_sign_esl() { local target_key="$1" local signing_key="$2" local esl_file="${SB_KEY_DIR}/${target_key}.esl" local auth_file="${SB_KEY_DIR}/${target_key}.auth" local sign_key="${SB_KEY_DIR}/${signing_key}.key" local sign_crt="${SB_KEY_DIR}/${signing_key}.crt" if [[ ! -f "$esl_file" ]]; then log_error "ESL file not found: $esl_file" return 1 fi if [[ ! -f "$sign_key" ]] || [[ ! -f "$sign_crt" ]]; then log_error "Signing key not found: ${signing_key}" return 1 fi log_info "Signing ${target_key}.esl with ${signing_key}..." sign-efi-sig-list -t "$(date +'%Y-%m-%d %H:%M:%S')" \ -k "$sign_key" -c "$sign_crt" \ "$target_key" "$esl_file" "$auth_file" if [[ ! -f "$auth_file" ]]; then log_error "Failed to create auth file for ${target_key}" return 1 fi return 0 } # Build Unified Kernel Image (UKI) # Args: $1 = output directory containing chroot/binary uki_build() { local build_dir="$1" local kernel_version local uki_output="${build_dir}/binary/boot/efi/EFI/BOOT/BOOTX64.EFI" log_info "Building Unified Kernel Image (UKI)..." # Find kernel version kernel_version=$(find "${build_dir}/chroot/boot" -maxdepth 1 -name 'vmlinuz-*' -print -quit 2>/dev/null | sed 's/.*vmlinuz-//') if [[ -z "$kernel_version" ]]; then log_error "Kernel not found in chroot" return 1 fi log_info "Kernel version: ${kernel_version}" local kernel="${build_dir}/chroot/boot/vmlinuz-${kernel_version}" local initrd="${build_dir}/chroot/boot/initrd.img-${kernel_version}" if [[ ! -f "$kernel" ]]; then log_error "Kernel not found: $kernel" return 1 fi if [[ ! -f "$initrd" ]]; then log_error "Initrd not found: $initrd" return 1 fi # Create output directory mkdir -p "$(dirname "$uki_output")" # Build UKI using ukify (systemd) or manual objcopy if command -v ukify &>/dev/null; then log_info "Building UKI with ukify..." ukify build \ --linux "$kernel" \ --initrd "$initrd" \ --cmdline "quiet splash" \ --output "$uki_output" \ --efi-arch x64 else log_info "Building UKI with objcopy (manual method)..." # Manual UKI construction using objcopy local stub="${build_dir}/chroot/lib/systemd/boot/efi/linuxx64.efi.stub" if [[ ! -f "$stub" ]]; then stub="${build_dir}/chroot/usr/lib/systemd/boot/efi/linuxx64.efi.stub" fi if [[ ! -f "$stub" ]]; then log_error "EFI stub not found - install systemd-boot" return 1 fi # Create cmdline file local cmdline_file="${build_dir}/cmdline.txt" echo "quiet splash" > "$cmdline_file" # Build UKI with objcopy objcopy \ --add-section .osrel="${build_dir}/chroot/etc/os-release" --change-section-vma .osrel=0x20000 \ --add-section .cmdline="$cmdline_file" --change-section-vma .cmdline=0x30000 \ --add-section .linux="$kernel" --change-section-vma .linux=0x40000 \ --add-section .initrd="$initrd" --change-section-vma .initrd=0x500000 \ "$stub" "$uki_output" fi if [[ ! -f "$uki_output" ]]; then log_error "Failed to build UKI" return 1 fi log_info "UKI built successfully: $uki_output" return 0 } # Sign UKI with db key # Args: $1 = path to UKI file uki_sign() { local uki_file="$1" local db_key="${SB_KEY_DIR}/db.key" local db_crt="${SB_KEY_DIR}/db.crt" if [[ ! -f "$uki_file" ]]; then log_error "UKI not found: $uki_file" return 1 fi if [[ ! -f "$db_key" ]] || [[ ! -f "$db_crt" ]]; then log_error "db key not found - run sb_generate_keys first" return 1 fi log_info "Signing UKI with db key..." sbsign --key "$db_key" --cert "$db_crt" --output "$uki_file" "$uki_file" # Verify signature if sbverify "$uki_file" 2>/dev/null | grep -q "Signature verification OK"; then log_info "UKI signed successfully" return 0 else log_warn "UKI signed but verification uncertain" return 0 fi } # Full Secure Boot setup - generate keys, create ESL, sign ESL, prepare for enrollment secureboot_setup() { log_info "Setting up Secure Boot..." # Generate keys if ! sb_generate_keys; then log_error "Failed to generate Secure Boot keys" return 1 fi # Create ESL files for key in PK KEK db; do if ! sb_create_esl "$key"; then log_error "Failed to create ESL for $key" return 1 fi done # Sign ESL files to create .auth files # PK is self-signed if ! sb_sign_esl "PK" "PK"; then log_error "Failed to sign PK" return 1 fi # KEK is signed by PK if ! sb_sign_esl "KEK" "PK"; then log_error "Failed to sign KEK" return 1 fi # db is signed by KEK if ! sb_sign_esl "db" "KEK"; then log_error "Failed to sign db" return 1 fi log_info "Secure Boot setup complete" log_info "Keys location: ${SB_KEY_DIR}" log_info "Auth files ready for UEFI enrollment:" ls -la "${SB_KEY_DIR}"/*.auth 2>/dev/null || true return 0 } # Get Secure Boot build script for embedding in Docker # This outputs the Secure Boot functions as a string for embedding in Docker bash -c get_secureboot_script() { cat << 'SECUREBOOT_SCRIPT' # Secure Boot functions for Docker build sb_docker_setup() { local key_dir="/tmp/secureboot-keys" mkdir -p "$key_dir" echo "[SecureBoot] Generating keys..." # Generate PK openssl req -new -x509 -newkey rsa:4096 -sha256 -days 3650 \ -nodes -subj "/CN=KNEL-Football PK/" \ -keyout "${key_dir}/PK.key" \ -out "${key_dir}/PK.crt" 2>/dev/null || return 1 # Generate KEK openssl req -new -x509 -newkey rsa:4096 -sha256 -days 3650 \ -nodes -subj "/CN=KNEL-Football KEK/" \ -keyout "${key_dir}/KEK.key" \ -out "${key_dir}/KEK.crt" 2>/dev/null || return 1 # Generate db openssl req -new -x509 -newkey rsa:4096 -sha256 -days 3650 \ -nodes -subj "/CN=KNEL-Football db/" \ -keyout "${key_dir}/db.key" \ -out "${key_dir}/db.crt" 2>/dev/null || return 1 # Create ESL files echo "[SecureBoot] Creating ESL files..." for key in PK KEK db; do cert-to-efi-sig-list -g "$(uuidgen)" \ "${key_dir}/${key}.crt" \ "${key_dir}/${key}.esl" || return 1 done # Create auth files echo "[SecureBoot] Creating auth files..." sign-efi-sig-list -t "$(date +'%Y-%m-%d %H:%M:%S')" \ -k "${key_dir}/PK.key" -c "${key_dir}/PK.crt" \ PK "${key_dir}/PK.esl" "${key_dir}/PK.auth" || return 1 sign-efi-sig-list -t "$(date +'%Y-%m-%d %H:%M:%S')" \ -k "${key_dir}/PK.key" -c "${key_dir}/PK.crt" \ KEK "${key_dir}/KEK.esl" "${key_dir}/KEK.auth" || return 1 sign-efi-sig-list -t "$(date +'%Y-%m-%d %H:%M:%S')" \ -k "${key_dir}/KEK.key" -c "${key_dir}/KEK.crt" \ db "${key_dir}/db.esl" "${key_dir}/db.auth" || return 1 echo "[SecureBoot] Keys generated successfully" echo "$key_dir" } sb_docker_build_uki() { local build_dir="$1" local key_dir="$2" echo "[SecureBoot] Building UKI..." # Find kernel local kernel=$(ls "${build_dir}/chroot/boot/vmlinuz-"* 2>/dev/null | head -1) if [[ -z "$kernel" ]]; then echo "[SecureBoot] ERROR: Kernel not found" return 1 fi local kver=$(echo "$kernel" | sed 's/.*vmlinuz-//') local initrd="${build_dir}/chroot/boot/initrd.img-${kver}" if [[ ! -f "$initrd" ]]; then echo "[SecureBoot] ERROR: Initrd not found" return 1 fi # Find EFI stub local stub="${build_dir}/chroot/usr/lib/systemd/boot/efi/linuxx64.efi.stub" if [[ ! -f "$stub" ]]; then stub="${build_dir}/chroot/lib/systemd/boot/efi/linuxx64.efi.stub" fi if [[ ! -f "$stub" ]]; then echo "[SecureBoot] ERROR: EFI stub not found" return 1 fi # Create output directory local uki_dir="${build_dir}/binary/boot/efi/EFI/BOOT" mkdir -p "$uki_dir" local uki_file="${uki_dir}/BOOTX64.EFI" local cmdline="${build_dir}/cmdline.txt" # Create cmdline echo "quiet splash lockdown=confidentiality" > "$cmdline" # Build UKI echo "[SecureBoot] Bundling kernel+initrd+cmdline..." objcopy \ --add-section .osrel="${build_dir}/chroot/etc/os-release" --change-section-vma .osrel=0x20000 \ --add-section .cmdline="$cmdline" --change-section-vma .cmdline=0x30000 \ --add-section .linux="$kernel" --change-section-vma .linux=0x40000 \ --add-section .initrd="$initrd" --change-section-vma .initrd=0x500000 \ "$stub" "$uki_file" || return 1 # Sign UKI echo "[SecureBoot] Signing UKI..." sbsign --key "${key_dir}/db.key" --cert "${key_dir}/db.crt" \ --output "$uki_file" "$uki_file" || return 1 # Verify if sbverify "$uki_file" 2>&1 | grep -q "OK"; then echo "[SecureBoot] UKI signed and verified: $uki_file" return 0 else echo "[SecureBoot] WARNING: UKI verification uncertain" return 0 fi } sb_docker_copy_keys_to_binary() { local key_dir="$1" local build_dir="$2" echo "[SecureBoot] Copying keys to ISO for installation..." # Create directory for keys in the ISO local keys_dest="${build_dir}/binary/secureboot" mkdir -p "$keys_dest" # Copy auth files for enrollment during installation cp "${key_dir}"/*.auth "$keys_dest/" 2>/dev/null || true # Copy certificates for verification cp "${key_dir}"/*.crt "$keys_dest/" 2>/dev/null || true echo "[SecureBoot] Keys copied to /secureboot/ on ISO" } SECUREBOOT_SCRIPT } # ============================================================================ # USAGE AND MAIN # ============================================================================ usage() { cat < [args] Build Commands: build Build Docker image iso Build ISO (60-90 minutes) monitor [secs] Monitor build progress (default: check every 180s) clean Clean build artifacts Test Commands: test Run all tests test:unit Run unit tests only test:integration Run integration tests only test:security Run security tests only test:system Run system tests only (requires libvirt) lint Run linting checks (shellcheck) VM Testing Commands (requires libvirt on host): test:iso check Check VM testing prerequisites test:iso create Create and start test VM (UEFI/Secure Boot) test:iso console Connect to VM console test:iso status Show VM status test:iso destroy Destroy VM and cleanup test:iso boot-test Run automated boot test test:iso secure-boot Test Secure Boot (manual verification) test:iso fde-test Test FDE passphrase prompt (manual verification) Other Commands: shell Interactive shell in build container help Show this help message Prerequisites for VM Testing: - User must be in libvirt group - libvirtd service must be running - OVMF must be installed (sudo apt install ovmf) - ISO must exist in output/ Examples: $0 build # Build Docker image $0 iso # Build ISO (60-90 min) $0 monitor # Monitor build progress $0 test # Run all tests $0 test:iso boot-test # Boot test in VM $0 test:iso console # Connect to VM console $0 test:iso destroy # Cleanup test VM Note: After adding user to libvirt group, logout and login again. EOF exit 1 } # Main entry point main() { local command="${1:-help}" case "${command}" in build) echo "Building KNEL-Football Docker image..." docker build -t "${DOCKER_IMAGE}" "${SCRIPT_DIR}" ;; test) echo "Running KNEL-Football test suite..." docker run --rm \ -v "${SCRIPT_DIR}:/workspace:ro" \ -v "${BUILD_DIR}:/build" \ -e BATS_TMPDIR=/build/tmp \ "${DOCKER_IMAGE}" \ bash -c "cd /workspace && bats tests/simple_test.bats tests/unit/ tests/integration/ tests/security/ tests/system/" ;; test:unit) echo "Running unit tests..." docker run --rm \ -v "${SCRIPT_DIR}:/workspace:ro" \ -v "${BUILD_DIR}:/build" \ -e BATS_TMPDIR=/build/tmp \ "${DOCKER_IMAGE}" \ bash -c "cd /workspace && bats tests/unit/" ;; test:integration) echo "Running integration tests..." docker run --rm \ -v "${SCRIPT_DIR}:/workspace:ro" \ -v "${BUILD_DIR}:/build" \ -e BATS_TMPDIR=/build/tmp \ "${DOCKER_IMAGE}" \ bash -c "cd /workspace && bats tests/integration/" ;; test:security) echo "Running security tests..." docker run --rm \ -v "${SCRIPT_DIR}:/workspace:ro" \ -v "${BUILD_DIR}:/build" \ -e BATS_TMPDIR=/build/tmp \ "${DOCKER_IMAGE}" \ bash -c "cd /workspace && bats tests/security/" ;; test:system) echo "Running system tests..." docker run --rm \ -v "${SCRIPT_DIR}:/workspace:ro" \ -v "${BUILD_DIR}:/build" \ -e BATS_TMPDIR=/build/tmp \ "${DOCKER_IMAGE}" \ bash -c "cd /workspace && bats tests/system/" ;; lint) echo "Running linting checks..." docker run --rm \ -v "${SCRIPT_DIR}:/workspace:ro" \ "${DOCKER_IMAGE}" \ bash -c "find /workspace -name '*.sh' -print0 | xargs -0 shellcheck" ;; clean) echo "Cleaning build artifacts..." rm -rf "${OUTPUT_DIR:?}"/* rm -rf "${BUILD_DIR:?}"/* ;; shell) echo "Starting interactive shell..." docker run --rm -it \ -v "${SCRIPT_DIR}:/workspace:ro" \ -v "${OUTPUT_DIR}:/output" \ -v "${BUILD_DIR}:/build" \ -u "$(id -u):$(id -g)" \ -e TZ="America/Chicago" \ -e DEBIAN_FRONTEND="noninteractive" \ -e LC_ALL="C" \ "${DOCKER_IMAGE}" \ bash ;; iso) check_host_fde || exit 1 echo "Building KNEL-Football secure ISO..." echo "ALL operations run inside Docker container" echo "Timezone: America/Chicago" echo "Mandatory: Full disk encryption with LUKS2" echo "Mandatory: Secure Boot with UKI" docker run --rm \ --privileged \ --user root \ -v "${SCRIPT_DIR}:/workspace:ro" \ -v "${OUTPUT_DIR}:/output" \ -e TZ="America/Chicago" \ -e DEBIAN_FRONTEND="noninteractive" \ -e LC_ALL="C" \ -e USER_UID="$(id -u)" \ -e USER_GID="$(id -g)" \ "${DOCKER_IMAGE}" \ bash -c ' cd /tmp && rm -rf ./* && echo "Configuring live-build..." && lb config \ --distribution trixie \ --architectures amd64 \ --archive-areas "main contrib non-free" \ --mode debian \ --chroot-filesystem squashfs \ --binary-images iso-hybrid \ --iso-application "KNEL-Football Secure OS" \ --iso-publisher "KNEL-Football Security Team" \ --iso-volume "KNEL-Football Secure" \ --debian-installer netinst \ --debian-installer-gui true \ --source false \ --apt-indices false \ --apt-source-archives false && if [ -d /workspace/config ]; then echo "Applying custom configuration..." cp -r /workspace/config/* ./config/ fi && # Create Secure Boot binary hook inline echo "Creating Secure Boot hook..." && mkdir -p config/hooks/binary && cat > config/hooks/binary/0200-secureboot-uki.hook << '\''SECUREBOOT_HOOK'\'' #!/bin/bash set -e echo "==========================================" echo "Secure Boot UKI Build Hook" echo "==========================================" # Secure Boot key directory SB_KEY_DIR="/tmp/secureboot-keys" mkdir -p "$SB_KEY_DIR" # Generate Secure Boot keys if not present if [[ ! -f "$SB_KEY_DIR/db.key" ]]; then echo "[SB] Generating Platform Key (PK)..." openssl req -new -x509 -newkey rsa:4096 -sha256 -days 3650 \ -nodes -subj "/CN=KNEL-Football PK/" \ -keyout "$SB_KEY_DIR/PK.key" \ -out "$SB_KEY_DIR/PK.crt" 2>/dev/null echo "[SB] Generating Key Exchange Key (KEK)..." openssl req -new -x509 -newkey rsa:4096 -sha256 -days 3650 \ -nodes -subj "/CN=KNEL-Football KEK/" \ -keyout "$SB_KEY_DIR/KEK.key" \ -out "$SB_KEY_DIR/KEK.crt" 2>/dev/null echo "[SB] Generating Signature Database Key (db)..." openssl req -new -x509 -newkey rsa:4096 -sha256 -days 3650 \ -nodes -subj "/CN=KNEL-Football db/" \ -keyout "$SB_KEY_DIR/db.key" \ -out "$SB_KEY_DIR/db.crt" 2>/dev/null # Create ESL files echo "[SB] Creating EFI Signature Lists..." for key in PK KEK db; do cert-to-efi-sig-list -g "$(uuidgen)" \ "$SB_KEY_DIR/${key}.crt" \ "$SB_KEY_DIR/${key}.esl" done # Create auth files for UEFI enrollment echo "[SB] Creating auth files..." sign-efi-sig-list -t "$(date +'\''%Y-%m-%d %H:%M:%S'\'')" \ -k "$SB_KEY_DIR/PK.key" -c "$SB_KEY_DIR/PK.crt" \ PK "$SB_KEY_DIR/PK.esl" "$SB_KEY_DIR/PK.auth" sign-efi-sig-list -t "$(date +'\''%Y-%m-%d %H:%M:%S'\'')" \ -k "$SB_KEY_DIR/PK.key" -c "$SB_KEY_DIR/PK.crt" \ KEK "$SB_KEY_DIR/KEK.esl" "$SB_KEY_DIR/KEK.auth" sign-efi-sig-list -t "$(date +'\''%Y-%m-%d %H:%M:%S'\'')" \ -k "$SB_KEY_DIR/KEK.key" -c "$SB_KEY_DIR/KEK.crt" \ db "$SB_KEY_DIR/db.esl" "$SB_KEY_DIR/db.auth" echo "[SB] Keys generated successfully" fi # Build UKI (Unified Kernel Image) echo "[SB] Building Unified Kernel Image..." # Find kernel in chroot KERNEL=$(ls chroot/boot/vmlinuz-* 2>/dev/null | head -1) if [[ -z "$KERNEL" ]]; then echo "[SB] ERROR: No kernel found in chroot" exit 1 fi KVER=$(echo "$KERNEL" | sed '\''s/.*vmlinuz-//'\'') INITRD="chroot/boot/initrd.img-${KVER}" echo "[SB] Kernel: $KERNEL" echo "[SB] Version: $KVER" if [[ ! -f "$INITRD" ]]; then echo "[SB] ERROR: Initrd not found: $INITRD" exit 1 fi # Find EFI stub STUB="" for stub_path in chroot/usr/lib/systemd/boot/efi/linuxx64.efi.stub \ chroot/lib/systemd/boot/efi/linuxx64.efi.stub; do if [[ -f "$stub_path" ]]; then STUB="$stub_path" break fi done if [[ -z "$STUB" ]]; then echo "[SB] ERROR: EFI stub not found - need systemd-boot package" exit 1 fi echo "[SB] EFI Stub: $STUB" # Create UKI output directory UKI_DIR="binary/boot/efi/EFI/BOOT" mkdir -p "$UKI_DIR" UKI_FILE="${UKI_DIR}/BOOTX64.EFI" CMDLINE_FILE="/tmp/cmdline.txt" # Kernel command line with lockdown mode echo "quiet splash lockdown=confidentiality module.sig_enforce=1" > "$CMDLINE_FILE" # Build UKI using objcopy echo "[SB] Bundling kernel + initramfs + cmdline into UKI..." objcopy \ --add-section .osrel="chroot/etc/os-release" --change-section-vma .osrel=0x20000 \ --add-section .cmdline="$CMDLINE_FILE" --change-section-vma .cmdline=0x30000 \ --add-section .linux="$KERNEL" --change-section-vma .linux=0x40000 \ --add-section .initrd="$INITRD" --change-section-vma .initrd=0x500000 \ "$STUB" "$UKI_FILE" echo "[SB] UKI created: $UKI_FILE" # Sign the UKI echo "[SB] Signing UKI with db key..." sbsign --key "$SB_KEY_DIR/db.key" --cert "$SB_KEY_DIR/db.crt" \ --output "$UKI_FILE" "$UKI_FILE" # Verify signature echo "[SB] Verifying UKI signature..." if sbverify "$UKI_FILE" 2>&1 | grep -q "Signature verification"; then echo "[SB] UKI signature verified successfully" else echo "[SB] WARNING: UKI signature verification uncertain" fi # Copy keys to ISO for installation enrollment echo "[SB] Copying keys to ISO..." KEYS_DEST="binary/secureboot" mkdir -p "$KEYS_DEST" cp "$SB_KEY_DIR"/*.auth "$KEYS_DEST/" 2>/dev/null || true cp "$SB_KEY_DIR"/*.crt "$KEYS_DEST/" 2>/dev/null || true # Create key enrollment README cat > "$KEYS_DEST/README.txt" << '\''README'\'' KNEL-Football Secure Boot Keys ============================== These files are used to enroll Secure Boot keys on the target system. During installation, the following keys must be enrolled in UEFI firmware: 1. PK.auth - Platform Key (root of trust) 2. KEK.auth - Key Exchange Key 3. db.auth - Signature Database (signs the UKI) Enrollment Methods: - Use KeyTool EFI application - Use efivar from Linux: efi-updatevar -f db.auth db - BIOS/UEFI setup utility (manufacturer dependent) SECUREBOOT_HOOK chmod +x config/hooks/binary/0200-secureboot-uki.hook && echo "Starting ISO build..." && timeout 3600 lb build && ISO_FILE=$(find . -name "*.iso" -type f | head -1) && if [ -n "$ISO_FILE" ]; then echo "ISO created: $ISO_FILE" sha256sum "$ISO_FILE" > "${ISO_FILE}.sha256" md5sum "$ISO_FILE" > "${ISO_FILE}.md5" FINAL_ISO="knel-football-secure.iso" mv "$ISO_FILE" "$FINAL_ISO" mv "${ISO_FILE}.sha256" "${FINAL_ISO}.sha256" mv "${ISO_FILE}.md5" "${FINAL_ISO}.md5" USER_UID=${USER_UID:-1000} USER_GID=${USER_GID:-1000} chown "$USER_UID:$USER_GID" "$FINAL_ISO" "${FINAL_ISO}.sha256" "${FINAL_ISO}.md5" cp "$FINAL_ISO" "${FINAL_ISO}.sha256" "${FINAL_ISO}.md5" /output/ chown "$USER_UID:$USER_GID" /output/"$FINAL_ISO" /output/"${FINAL_ISO}.sha256" /output/"${FINAL_ISO}.md5" echo "ISO build completed" echo "==========================================" echo "Secure Boot: ENABLED" echo "UKI: SIGNED" echo "Keys: /secureboot/ on ISO" echo "==========================================" ls -lh /output/ else echo "ISO build failed" exit 1 fi ' 2>&1 | tee "$BUILD_LOG" ;; monitor) monitor_build "${2:-180}" ;; test:iso) check_host_fde || exit 1 shift # Remove 'test:iso' from args local subcmd="${1:-help}" case "$subcmd" in check) vm_check_prerequisites ;; create) vm_check_prerequisites && vm_create ;; console) vm_console ;; status) vm_status ;; destroy) vm_destroy ;; boot-test) vm_boot_test ;; secure-boot) vm_test_secure_boot ;; fde-test) vm_test_fde ;; help|*) echo "VM Testing Commands:" echo " check Check prerequisites" echo " create Create and start test VM" echo " console Connect to VM console" echo " status Show VM status" echo " destroy Destroy VM and cleanup" echo " boot-test Run automated boot test" echo " secure-boot Test Secure Boot" echo " fde-test Test FDE passphrase prompt" ;; esac ;; help|*) usage ;; esac } main "$@"