Signed-off-by: Marc S. Weidner <msw@coresecret.dev>
This commit is contained in:
@@ -0,0 +1,327 @@
|
||||
#!/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
|
||||
Reference in New Issue
Block a user