#!/bin/bash # SPDX-Version: 3.0 # SPDX-CreationInfo: 2026-06-04; WEIDNER, Marc S.; # SPDX-ExternalRef: GIT https://git.coresecret.dev/msw/CISS.debian.live.builder.git # SPDX-FileContributor: WEIDNER, Marc S.; Centurion Intelligence Consulting Agency # SPDX-FileCopyrightText: 2024-2026; WEIDNER, Marc S.; # SPDX-FileType: SOURCE # SPDX-License-Identifier: LicenseRef-CNCL-1.1 OR LicenseRef-CCLA-1.1 # SPDX-LicenseComment: This file is part of the CISS.debian.installer.secure framework. # SPDX-PackageName: CISS.debian.live.builder # SPDX-Security-Contact: security@coresecret.eu set -Ceuo pipefail ####################################### # Prints failure message. # Globals: # None # Arguments: # 1: Message to print # Returns: # 42: on failure ####################################### die() { declare message="$1" printf "\e[91m++++ ++++ ++++ ++++ ++++ ++++ ++ ❌ %s \e[0m\n" "${message}" >&2 exit 42 } ####################################### # Checking command to execute for existence. # Globals: # None # Arguments: # 1: Command # Returns: # 0: on success ####################################### require_command() { declare command_name="$1" command -v "${command_name}" >/dev/null 2>&1 || die "Required command not found: '${command_name}'." return 0 } ####################################### # Checking file for existence. # Globals: # None # Arguments: # 1: /PATH/TO/FILE # 2: Description # Returns: # 0: on success ####################################### require_file() { declare file_path="$1" declare description="$2" [[ -f "${file_path}" ]] || die "Missing ${description}: '${file_path}'." return 0 } ####################################### # Checking file for existence. # Globals: # None # Arguments: # 1: /PATH/TO/FILE # 2: Description # Returns: # 0: on success ####################################### read_bootappend_live() { declare config_file="$1" declare output_var="$2" declare -a matches=() declare value="" require_file "${config_file}" "live-build binary configuration" mapfile -t matches < <(grep -E '^LB_BOOTAPPEND_LIVE=' "${config_file}" || true) if (( ${#matches[@]} != 1 )); then die "Expected exactly one LB_BOOTAPPEND_LIVE entry in '${config_file}', found '${#matches[@]}'." fi value="${matches[0]#LB_BOOTAPPEND_LIVE=}" if [[ "${value}" == \"*\" ]]; then value="${value#\"}" value="${value%\"}" fi [[ -n "${value}" ]] || die "LB_BOOTAPPEND_LIVE in '${config_file}' is empty." printf -v "${output_var}" "%s" "${value}" return 0 } collect_artifacts_from_dir() { declare artifact_dir="$1" declare kernel_output_var="$2" declare initrd_output_var="$3" declare -a kernels=() declare -a initrds=() if [[ ! -d "${artifact_dir}" ]]; then printf -v "${kernel_output_var}" "%s" "" printf -v "${initrd_output_var}" "%s" "" return 0 fi mapfile -d '' -t kernels < <(find "${artifact_dir}" -maxdepth 1 -type f -name "vmlinuz-*" -print0 | LC_ALL=C sort -z) mapfile -d '' -t initrds < <(find "${artifact_dir}" -maxdepth 1 -type f -name "initrd.img-*" -print0 | LC_ALL=C sort -z) if (( ${#kernels[@]} > 1 )); then die "Ambiguous kernel candidates in '${artifact_dir}'. Refusing to select automatically." fi if (( ${#initrds[@]} > 1 )); then die "Ambiguous initrd candidates in '${artifact_dir}'. Refusing to select automatically." fi printf -v "${kernel_output_var}" "%s" "${kernels[0]:-}" printf -v "${initrd_output_var}" "%s" "${initrds[0]:-}" return 0 } select_kernel_initrd_pair() { declare kernel_output_var="$1" declare initrd_output_var="$2" declare binary_kernel="" declare binary_initrd="" declare fallback_kernel="" declare fallback_initrd="" collect_artifacts_from_dir "binary/live" binary_kernel binary_initrd if [[ -n "${binary_kernel}" && -n "${binary_initrd}" ]]; then printf "\e[92m++++ ++++ ++++ ++++ ++++ ++++ ++ [INFO] Using final binary/live kernel and initrd artifacts. \e[0m\n" printf -v "${kernel_output_var}" "%s" "${binary_kernel}" printf -v "${initrd_output_var}" "%s" "${binary_initrd}" return 0 fi if [[ -n "${binary_kernel}" || -n "${binary_initrd}" ]]; then die "Incomplete binary/live kernel/initrd pair. Refusing to mix final and fallback artifacts." fi printf "\e[93m++++ ++++ ++++ ++++ ++++ ++++ ++ [WARN] No complete binary/live kernel/initrd pair found; checking chroot/boot fallback. \e[0m\n" collect_artifacts_from_dir "chroot/boot" fallback_kernel fallback_initrd if [[ -n "${fallback_kernel}" && -n "${fallback_initrd}" ]]; then printf "\e[93m++++ ++++ ++++ ++++ ++++ ++++ ++ [WARN] Using chroot/boot fallback artifacts because binary/live has no complete pair. \e[0m\n" printf -v "${kernel_output_var}" "%s" "${fallback_kernel}" printf -v "${initrd_output_var}" "%s" "${fallback_initrd}" return 0 fi die "No complete kernel/initrd pair found in binary/live or chroot/boot." } collect_optional_microcode() { declare artifact_dir="$1" declare output_var="$2" declare -a microcode_candidates=() mapfile -d '' -t microcode_candidates < <( find "${artifact_dir}" -maxdepth 1 -type f \( -name "*microcode*.cpio" -o -name "*ucode*.cpio" \) -print0 | LC_ALL=C sort -z ) if (( ${#microcode_candidates[@]} > 1 )); then die "Ambiguous separate early microcode cpio candidates in '${artifact_dir}'. Refusing to select automatically." fi printf -v "${output_var}" "%s" "${microcode_candidates[0]:-}" return 0 } guard_private_key_leaks() { declare -a guard_roots=(binary chroot config/includes.binary config/includes.chroot config/includes.installer) declare guard_root="" declare private_file="" for guard_root in "${guard_roots[@]}"; do if [[ ! -d "${guard_root}" ]]; then continue fi while IFS= read -r -d '' private_file; do die "Refusing private Secure Boot key inside build artifact path: '${private_file}'." done < <(find "${guard_root}" -xdev -type f \( -name "ciss-efi-image.key" -o -name "ciss-module-signing.key" \) -print0) done return 0 } main() { declare profile="${VAR_CISS_SECUREBOOT_PROFILE:-debian-shim}" declare build_dir="${VAR_HANDLER_BUILD_DIR:-${PWD}}" declare secureboot_dir="${VAR_CISS_SECUREBOOT_DIR:-${VAR_WORKDIR:-${build_dir}}/ciss.secureboot}" declare secureboot_key="${VAR_CISS_SECUREBOOT_EFI_KEY:-${secureboot_dir}/private/ciss-efi-image.key}" declare secureboot_cert="${VAR_CISS_SECUREBOOT_EFI_CERT:-${secureboot_dir}/public/ciss-efi-image.crt}" declare stub="/usr/lib/systemd/boot/efi/linuxx64.efi.stub" declare os_release="chroot/usr/lib/os-release" declare kernel_path="" declare initrd_path="" declare kernel_base="" declare initrd_base="" declare kernel_version="" declare initrd_version="" declare cmdline="" declare microcode_initrd="" declare output_root="" declare uki_dir="" declare manifest_dir="" declare unsigned_uki="" declare signed_uki="" declare manifest="" declare -a ukify_args=() if [[ "${profile}" != "ciss-uki" ]]; then printf "\e[92m++++ ++++ ++++ ++++ ++++ ++++ ++ [INFO] Secure Boot profile '%s'; skipping CISS UKI build. \e[0m\n" "${profile}" return 0 fi printf "\e[95m++++ ++++ ++++ ++++ ++++ ++++ ++ [INFO] Building CISS Secure Boot UKI ... \e[0m\n" cd "${build_dir}" require_command ukify require_command sbverify require_command sha512sum require_file "${stub}" "systemd EFI stub" require_file "${secureboot_key}" "CISS EFI image signing key" require_file "${secureboot_cert}" "CISS EFI image signing certificate" require_file "${os_release}" "target os-release metadata" guard_private_key_leaks select_kernel_initrd_pair kernel_path initrd_path kernel_base="${kernel_path##*/}" initrd_base="${initrd_path##*/}" kernel_version="${kernel_base#vmlinuz-}" initrd_version="${initrd_base#initrd.img-}" [[ -n "${kernel_version}" && "${kernel_base}" != "${kernel_version}" ]] || die "Kernel artifact name does not match vmlinuz-: '${kernel_path}'." [[ -n "${initrd_version}" && "${initrd_base}" != "${initrd_version}" ]] || die "Initrd artifact name does not match initrd.img-: '${initrd_path}'." if [[ "${kernel_version}" != "${initrd_version}" ]]; then die "Kernel/initrd version mismatch: kernel='${kernel_version}', initrd='${initrd_version}'." fi read_bootappend_live "config/binary" cmdline collect_optional_microcode "${initrd_path%/*}" microcode_initrd output_root="${build_dir}/ciss.secureboot" uki_dir="${output_root}/uki" manifest_dir="${output_root}/manifests" unsigned_uki="${uki_dir}/CISS-LIVE-${kernel_version}.unsigned.efi" signed_uki="${uki_dir}/CISS-LIVE-${kernel_version}.signed.efi" manifest="${manifest_dir}/CISS-LIVE-${kernel_version}.uki-build.txt" install -d -m 0755 "${uki_dir}" "${manifest_dir}" rm -f -- "${unsigned_uki}" "${signed_uki}" "${manifest}" ukify_args=( build --stub="${stub}" --linux="${kernel_path}" --cmdline="${cmdline}" --os-release="@${os_release}" --uname="${kernel_version}" ) if [[ -n "${microcode_initrd}" ]]; then printf "\e[92m++++ ++++ ++++ ++++ ++++ ++++ ++ [INFO] Embedding separate early microcode cpio before normal initrd: '%s'. \e[0m\n" "${microcode_initrd}" ukify_args+=(--initrd="${microcode_initrd}") else printf "\e[92m++++ ++++ ++++ ++++ ++++ ++++ ++ [INFO] No separate early microcode cpio found; using normal initrd only. \e[0m\n" fi ukify_args+=(--initrd="${initrd_path}") printf "\e[95m++++ ++++ ++++ ++++ ++++ ++++ ++ [INFO] Creating unsigned UKI: '%s'. \e[0m\n" "${unsigned_uki}" ukify "${ukify_args[@]}" --output="${unsigned_uki}" printf "\e[95m++++ ++++ ++++ ++++ ++++ ++++ ++ [INFO] Creating signed UKI: '%s'. \e[0m\n" "${signed_uki}" ukify "${ukify_args[@]}" \ --secureboot-private-key="${secureboot_key}" \ --secureboot-certificate="${secureboot_cert}" \ --output="${signed_uki}" require_file "${unsigned_uki}" "unsigned CISS UKI" require_file "${signed_uki}" "signed CISS UKI" { printf "CISS Secure Boot UKI build manifest\n" printf "Kernel: %s\n" "${kernel_path}" printf "Initrd: %s\n" "${initrd_path}" printf "Microcode initrd: %s\n" "${microcode_initrd:-none}" printf "Uname: %s\n" "${kernel_version}" printf "OS release: %s\n" "${os_release}" printf "Command line: %s\n" "${cmdline}" printf "\nSHA512:\n" sha512sum "${unsigned_uki}" "${signed_uki}" printf "\nukify inspect:\n" ukify inspect "${signed_uki}" printf "\nsbverify:\n" sbverify --cert "${secureboot_cert}" "${signed_uki}" } >| "${manifest}" 2>&1 printf "\e[92m++++ ++++ ++++ ++++ ++++ ++++ ++ [INFO] UKI inspection and signature verification written to '%s'. \e[0m\n" "${manifest}" printf "\e[92m++++ ++++ ++++ ++++ ++++ ++++ ++ [INFO] CISS Secure Boot UKI build completed. \e[0m\n" return 0 } main "$@" exit 0 # vim: number et ts=2 sw=2 sts=2 ai tw=128 ft=sh