83f6f8488c
🛡️ Shell Script Linting / 🛡️ Shell Script Linting (push) Has been cancelled
Signed-off-by: Marc S. Weidner <msw@coresecret.dev>
328 lines
11 KiB
Bash
328 lines
11 KiB
Bash
#!/bin/bash
|
|
# SPDX-Version: 3.0
|
|
# SPDX-CreationInfo: 2026-06-04; WEIDNER, Marc S.; <msw@coresecret.dev>
|
|
# 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.; <msw@coresecret.dev>
|
|
# 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-<version>: '${kernel_path}'."
|
|
[[ -n "${initrd_version}" && "${initrd_base}" != "${initrd_version}" ]] || die "Initrd artifact name does not match initrd.img-<version>: '${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
|