4 Commits

24 changed files with 756 additions and 1289 deletions
+17 -29
View File
@@ -60,15 +60,12 @@ and spoofing surfaces.
Internally, the builder employs a dedicated secret-handling pipeline backed by a tmpfs-only secrets directory Internally, the builder employs a dedicated secret-handling pipeline backed by a tmpfs-only secrets directory
(`/dev/shm/cdlb_secrets`). Sensitive material such as root passwords, SSH keys, and signing keys never appears on the command (`/dev/shm/cdlb_secrets`). Sensitive material such as root passwords, SSH keys, and signing keys never appears on the command
line, is guarded by a `0700 root:root` secret root and single-link regular `0400` or `0600` root-owned files, and any symlink line, is guarded by strict `0400 root:root` permissions, and any symlink inside the secret path is treated as a hard failure
inside the secret path is treated as a hard failure that aborts the run. Filename-only secret arguments reject slashes and that aborts the run. Critical code paths temporarily disable Bash xtrace so that credentials never leak into debug logs, and
traversal. transient secret files are shredded (`shred -fzu`) as soon as they are no longer needed. GNUPG homes used for signing are
Critical code paths temporarily disable Bash xtrace, and a final exact-value debug-log sanitisation pass provides additional wiped, unencrypted chroot artifacts and includes are removed after `lb build`, and the final artifact is reduced to the
defence in depth. Transient secret files are shredded (`shred -fzu`) as soon as they are no longer needed, but this is only a encrypted SquashFS inside the LUKS2 container. At runtime, LUKS passphrases in the live ISO and installer are transported via
best-effort cleanup on SSD, NVMe, copy-on-write, journaled, and virtualised storage. Use tmpfs for secrets and encrypted storage named pipes inside the initramfs instead of process arguments, further minimizing exposure in process listings.
for build workspaces. Destructive build cleanup is restricted to the exact canonical directory carrying the
`.ciss-live-builder-owned` marker. This private operator workflow still requires strict local path validation; it does not
define public ISO release policy.
Check out more leading world-class services powered by Centurion Intelligence Consulting Agency: Check out more leading world-class services powered by Centurion Intelligence Consulting Agency:
* [CenturionDNS Resolver](https://eddns.eu/) * [CenturionDNS Resolver](https://eddns.eu/)
@@ -496,14 +493,10 @@ To use **``CISS.debian.live.builder``** as intended, the following baseline is e
2. Preparation: 2. Preparation:
1. Ensure you are root. 1. Ensure you are root.
2. Create the empty build directory with `install -d -m 0700 -o root -g root /opt/cdlb`. 2. Create the build directory `mkdir /opt/cdlb` and the tmpfs secrets directory `mkdir /dev/shm/cdlb_secrets`.
3. Create the tmpfs secret root with `install -d -m 0700 -o root -g root /dev/shm/cdlb_secrets`. 3. Place your desired SSH public key in the `authorized_keys` file, for example, in the `/dev/shm/cdlb_secrets` directory.
4. Place required secret files in the secret root as single-link regular, non-symlink, root-owned files with mode `0400` 4. Place your desired Password in the `password.txt` file, for example, in the `/dev/shm/cdlb_secrets` directory.
or `0600`. 5. Make any other changes you need to.
5. Place your desired SSH public key in `/dev/shm/cdlb_secrets/authorized_keys`.
6. Place your desired root password in `/dev/shm/cdlb_secrets/password.txt`.
7. Use filename-only values without slashes, `.` or `..` for `--key_age`, `--key_luks`, and signing-file arguments.
8. Make any other changes you need to.
3. Run the config builder script `./ciss_live_builder.sh` and the integrated `lb build` command (example): 3. Run the config builder script `./ciss_live_builder.sh` and the integrated `lb build` command (example):
@@ -545,10 +538,6 @@ To use **``CISS.debian.live.builder``** as intended, the following baseline is e
both the newer Sigstore bundle asset, and the legacy-split certificate/signature assets before checking the downloaded both the newer Sigstore bundle asset, and the legacy-split certificate/signature assets before checking the downloaded
SOPS binary with `sha256sum -c --ignore-missing`. SOPS binary with `sha256sum -c --ignore-missing`.
On the first run, the builder creates `.ciss-live-builder-owned` in a new or empty build directory whose canonical parent
already exists. A populated directory without that marker is rejected and is never adopted automatically. Cleanup remains
intentionally destructive inside the exact validated marker-owned directory.
4. Locate your ISO in the `--build-directory`. 4. Locate your ISO in the `--build-directory`.
5. Boot from the ISO and login to the live image via the console, or the multi-layer secured **coresecret** SSH tunnel. 5. Boot from the ISO and login to the live image via the console, or the multi-layer secured **coresecret** SSH tunnel.
6. Type `sysp` for the final kernel hardening features. 6. Type `sysp` for the final kernel hardening features.
@@ -570,8 +559,7 @@ preview it or run it.
2. Preparation: 2. Preparation:
1. Ensure you are root. 1. Ensure you are root.
2. Create the empty build directory and tmpfs secret root with restrictive ownership and permissions: 2. Create the build directory `mkdir /opt/cdlb` and the tmpfs secrets directory `mkdir /dev/shm/cdlb_secrets`.
`install -d -m 0700 -o root -g root /opt/cdlb /dev/shm/cdlb_secrets`.
3. Place your desired SSH public key in the `authorized_keys` file, for example, in the `/dev/shm/cdlb_secrets` directory. 3. Place your desired SSH public key in the `authorized_keys` file, for example, in the `/dev/shm/cdlb_secrets` directory.
4. Place your desired Password in the `password.txt` file, for example, in the `/dev/shm/cdlb_secrets` directory. 4. Place your desired Password in the `password.txt` file, for example, in the `/dev/shm/cdlb_secrets` directory.
5. Copy and edit the sample and set your options (no spaces around commas in lists): 5. Copy and edit the sample and set your options (no spaces around commas in lists):
@@ -668,10 +656,10 @@ The private directory is ignored by Git. The hooks fail if the CISS EFI image si
#... #...
- name: Preparing the build environment. - name: Preparing the build environment.
run: | run: |
install -d -m 0700 -o root -g root /opt/livebuild /dev/shm/cdlb_secrets mkdir -p /opt/config
umask 0077 mkdir -p /opt/livebuild
printf '%s\n' "${{ secrets.CHANGE_ME }}" >| /dev/shm/cdlb_secrets/password.txt echo "${{ secrets.CHANGE_ME }}" >| /opt/config/password.txt
printf '%s\n' "${{ secrets.CHANGE_ME }}" >| /dev/shm/cdlb_secrets/authorized_keys echo "${{ secrets.CHANGE_ME }}" >| /opt/config/authorized_keys
#... #...
- name: Starting CISS.debian.live.builder. This may take a while ... - name: Starting CISS.debian.live.builder. This may take a while ...
run: | run: |
@@ -684,9 +672,9 @@ The private directory is ignored by Git. The hooks fail if the CISS EFI image si
--build-directory /opt/livebuild \ --build-directory /opt/livebuild \
--control "${timestamp}" \ --control "${timestamp}" \
--jump-host "${{ secrets.CHANGE_ME }}" \ --jump-host "${{ secrets.CHANGE_ME }}" \
--root-password-file /dev/shm/cdlb_secrets/password.txt \ --root-password-file /opt/config/password.txt \
--ssh-port CHANGE_ME \ --ssh-port CHANGE_ME \
--ssh-pubkey /dev/shm/cdlb_secrets --ssh-pubkey /opt/config
#... #...
### SKIP OR CHANGE ALL REMAINING STEPS ### SKIP OR CHANGE ALL REMAINING STEPS
``` ```
+17 -19
View File
@@ -124,28 +124,22 @@ sleep 4
printf "\e[95m🧪 %s starting ... \e[0m\n" "${BASH_SOURCE[0]}" printf "\e[95m🧪 %s starting ... \e[0m\n" "${BASH_SOURCE[0]}"
declare -grx VAR_SETUP="true" declare -grx VAR_SETUP="true"
### SECURING SECRETS ARTIFACTS.
test ! -L "${VAR_TMP_SECRET}" || {
. ./var/global.var.sh
printf "\e[91m❌ Refusing symlink: '%s'! Bye... \e[0m\n" "${VAR_TMP_SECRET}" >&2
exit "${ERR_SECRETSSYM}"
}
find "${VAR_TMP_SECRET}" -type f -exec chmod 0400 {} +
find "${VAR_TMP_SECRET}" -type f -exec chown root:root {} +
### SOURCING VARIABLES. ### SOURCING VARIABLES.
[[ "${VAR_SETUP}" == true ]] && { [[ "${VAR_SETUP}" == true ]] && {
source_guard "./var/color.var.sh" source_guard "./var/color.var.sh"
source_guard "./var/global.var.sh" source_guard "./var/global.var.sh"
} }
### SOURCE THE MINIMUM REQUIRED FOR EARLY EXIT CLEANUP COVERAGE. ### SOURCING LIBRARIES.
[[ "${VAR_SETUP}" == true ]] && {
source_guard "./lib/lib_secret_validation.sh"
source_guard "./lib/lib_build_directory.sh"
source_guard "./lib/lib_debug_sanitizer.sh"
source_guard "./lib/lib_clean_up.sh"
source_guard "./lib/lib_trap_on_err.sh"
source_guard "./lib/lib_trap_on_exit.sh"
}
trap 'trap_on_exit "$?" "${BASH_SOURCE[0]}" "${LINENO}" "${FUNCNAME[0]:-main}" "${BASH_COMMAND}"' EXIT
### Validate the fixed tmpfs secret staging area without modifying operator-provided files.
validate_secret_staging_area
### SOURCING REMAINING LIBRARIES.
[[ "${VAR_SETUP}" == true ]] && { [[ "${VAR_SETUP}" == true ]] && {
source_guard "./lib/lib_arg_parser.sh" source_guard "./lib/lib_arg_parser.sh"
source_guard "./lib/lib_arg_priority_check.sh" source_guard "./lib/lib_arg_priority_check.sh"
@@ -164,6 +158,7 @@ validate_secret_staging_area
source_guard "./lib/lib_ciss_upgrades_boot.sh" source_guard "./lib/lib_ciss_upgrades_boot.sh"
source_guard "./lib/lib_ciss_upgrades_build.sh" source_guard "./lib/lib_ciss_upgrades_build.sh"
source_guard "./lib/lib_clean_screen.sh" source_guard "./lib/lib_clean_screen.sh"
source_guard "./lib/lib_clean_up.sh"
source_guard "./lib/lib_copy_integrity.sh" source_guard "./lib/lib_copy_integrity.sh"
source_guard "./lib/lib_gnupg.sh" source_guard "./lib/lib_gnupg.sh"
source_guard "./lib/lib_hardening_root_pw.sh" source_guard "./lib/lib_hardening_root_pw.sh"
@@ -179,13 +174,12 @@ validate_secret_staging_area
source_guard "./lib/lib_run_analysis.sh" source_guard "./lib/lib_run_analysis.sh"
source_guard "./lib/lib_sanitizer.sh" source_guard "./lib/lib_sanitizer.sh"
source_guard "./lib/lib_secureboot_profile.sh" source_guard "./lib/lib_secureboot_profile.sh"
source_guard "./lib/lib_trap_on_err.sh"
source_guard "./lib/lib_trap_on_exit.sh"
source_guard "./lib/lib_update_microcode.sh" source_guard "./lib/lib_update_microcode.sh"
source_guard "./lib/lib_usage.sh" source_guard "./lib/lib_usage.sh"
} }
### Add ERR handling after all remaining libraries are available.
trap 'trap_on_err "$?" "${BASH_SOURCE[0]}" "${LINENO}" "${FUNCNAME[0]:-main}" "${BASH_COMMAND}"' ERR
### PRE-SCAN SECURE BOOT PROFILE FOR BUILD-HOST PACKAGE CHECKS. ### PRE-SCAN SECURE BOOT PROFILE FOR BUILD-HOST PACKAGE CHECKS.
### Formal validation still happens in arg_parser(). ### Formal validation still happens in arg_parser().
for ((idx=0; idx<${#ARY_PARAM_ARRAY[@]}; idx++)); do for ((idx=0; idx<${#ARY_PARAM_ARRAY[@]}; idx++)); do
@@ -229,6 +223,10 @@ if ! ${VAR_HANDLER_AUTOBUILD}; then printf "XXX\nInitialization done ... \nXXX\n
### Updating Status of Dialog Gauge Bar. ### Updating Status of Dialog Gauge Bar.
if ! ${VAR_HANDLER_AUTOBUILD}; then printf "XXX\nActivate traps ... \nXXX\n50\n" >&3; fi if ! ${VAR_HANDLER_AUTOBUILD}; then printf "XXX\nActivate traps ... \nXXX\n50\n" >&3; fi
### Following the CISS Bash naming and ordering scheme:
trap 'trap_on_exit "$?" "${BASH_SOURCE[0]}" "${LINENO}" "${FUNCNAME[0]:-main}" "${BASH_COMMAND}"' EXIT
trap 'trap_on_err "$?" "${BASH_SOURCE[0]}" "${LINENO}" "${FUNCNAME[0]:-main}" "${BASH_COMMAND}"' ERR
### Updating Status of Dialog Gauge Bar. ### Updating Status of Dialog Gauge Bar.
if ! ${VAR_HANDLER_AUTOBUILD}; then printf "XXX\nSanitizing Arguments ... \nXXX\n75\n" >&3; fi if ! ${VAR_HANDLER_AUTOBUILD}; then printf "XXX\nSanitizing Arguments ... \nXXX\n75\n" >&3; fi
arg_check "$@" arg_check "$@"
@@ -9,17 +9,90 @@
# SPDX-LicenseComment: This file is part of the CISS.debian.installer.secure framework. # SPDX-LicenseComment: This file is part of the CISS.debian.installer.secure framework.
# SPDX-PackageName: CISS.debian.live.builder # SPDX-PackageName: CISS.debian.live.builder
# SPDX-Security-Contact: security@coresecret.eu # SPDX-Security-Contact: security@coresecret.eu
# shellcheck disable=SC2154
set -Ceuo pipefail set -Ceuo pipefail
# Final live-build binary hook for encrypted root filesystem packaging. Preallocate a LUKS2 container, formats it with the # Final live-build binary hook for encrypted root filesystem packaging. Preallocate a LUKS2 container, format it with the
# generated build secret, copies the generated filesystem.squashfs into the opened encrypted mapping, then closes the container, # generated build secret, copy the generated filesystem.squashfs into the opened encrypted mapping, generate and sign a
# shreds the temporary LUKS secret, and removes the plaintext SquashFS from the ISO payload. # SHA-512 attestation manifest for the complete decrypted mapper, then close the container, shred the temporary LUKS secret,
# and remove the plaintext SquashFS from the ISO payload.
printf "\e[95m🧪 '%s' starting ... \e[0m\n" "${0}" printf "\e[95m🧪 '%s' starting ... \e[0m\n" "${0}"
__umask=$(umask) __umask=$(umask)
umask 0077 umask 0077
#######################################
# Prints a fatal error message and terminates the hook.
# Globals:
# None
# Arguments:
# 1: Error message
# Returns:
# 42: always exits with failure
#######################################
die() {
declare message="${1}"
printf "\e[91m❌ %s \e[0m\n" "${message}" >&2
exit 42
}
#######################################
# Checks whether a required command exists.
# Globals:
# None
# Arguments:
# 1: Command name
# Returns:
# 0: on success
# 42: if the command is missing
#######################################
require_command() {
declare command_name="${1}"
command -v "${command_name}" >/dev/null 2>&1 || die "Required command not found: '${command_name}'."
return 0
}
#######################################
# Checks whether a required file exists and is readable.
# Globals:
# None
# Arguments:
# 1: File path
# 2: Human-readable file description
# Returns:
# 0: on success
# 42: if the file is missing or unreadable
#######################################
require_file() {
declare file_path="${1}"
declare description="${2}"
[[ -f "${file_path}" && -r "${file_path}" ]] || die "Missing or unreadable ${description}: '${file_path}'."
return 0
}
#######################################
# Checks whether a required environment variable is non-empty.
# Globals:
# None
# Arguments:
# 1: Variable name
# Returns:
# 0: on success
# 42: if the variable is empty or unset
#######################################
require_variable() {
declare variable_name="${1}"
[[ -n "${!variable_name:-}" ]] || die "Required environment variable is empty or unset: '${variable_name}'."
return 0
}
####################################### #######################################
# Pre allocates space for LUKS container. # Pre allocates space for LUKS container.
# Globals: # Globals:
@@ -65,50 +138,24 @@ readonly -f preallocate
declare ROOTFS="${VAR_HANDLER_BUILD_DIR}/binary/live/filesystem.squashfs" declare ROOTFS="${VAR_HANDLER_BUILD_DIR}/binary/live/filesystem.squashfs"
declare LUKSFS="${VAR_HANDLER_BUILD_DIR}/binary/live/ciss_rootfs.crypt" declare LUKSFS="${VAR_HANDLER_BUILD_DIR}/binary/live/ciss_rootfs.crypt"
declare MAPPER_DEV="/dev/mapper/crypt_liveiso"
declare ROOTFS_ATTESTATION="${LUKSFS}.decrypted.sha512sum.txt"
declare ROOTFS_ATTESTATION_SIG="${ROOTFS_ATTESTATION}.sig"
declare KEYFD="" declare KEYFD=""
declare LUKS_KEY_FILE=""
declare LUKS_KEY_FILENAME="${VAR_LUKS_KEY:-luks.txt}"
declare LUKS_KEY_LINK_COUNT=""
declare LUKS_KEY_MODE=""
declare LUKS_KEY_OWNER=""
declare SECRET_ROOT_FS=""
declare SECRET_ROOT_MODE=""
declare SECRET_ROOT_OWNER=""
if [[ -L "${VAR_TMP_SECRET}" || ! -d "${VAR_TMP_SECRET}" ]]; then require_command gpg
printf "\e[91m❌ Unsafe secret root rejected. \e[0m\n" >&2 require_command gpgv
exit 42 require_command sha512sum
fi require_file "${ROOTFS}" "final SquashFS payload"
require_variable VAR_SIGNING_KEY_FPR
require_variable VAR_SIGNING_KEY_PASSFILE
require_variable VAR_VERIFY_KEYRING
require_file "${VAR_SIGNING_KEY_PASSFILE}" "GPG signing passphrase file"
require_file "${VAR_VERIFY_KEYRING}" "GPG verification keyring"
SECRET_ROOT_OWNER="$(stat -c '%u' "${VAR_TMP_SECRET}")" [[ "${VAR_SIGNER:-false}" == "true" ]] || die "Rootfs attestation requires an enabled artifact signer."
SECRET_ROOT_MODE="$(stat -c '%a' "${VAR_TMP_SECRET}")"
SECRET_ROOT_FS="$(stat -f -c '%T' "${VAR_TMP_SECRET}")"
if [[ "${SECRET_ROOT_OWNER}" != "${EUID}" || "${SECRET_ROOT_MODE}" != "700" \
|| ( "${SECRET_ROOT_FS}" != "tmpfs" && "${SECRET_ROOT_FS}" != "ramfs" ) ]]; then
printf "\e[91m❌ Unsafe secret-root ownership, permissions, or filesystem rejected. \e[0m\n" >&2
exit 42
fi
if [[ -z "${LUKS_KEY_FILENAME}" || "${LUKS_KEY_FILENAME}" == "." || "${LUKS_KEY_FILENAME}" == ".." \ rm -f -- "${ROOTFS_ATTESTATION}" "${ROOTFS_ATTESTATION_SIG}"
|| "${LUKS_KEY_FILENAME}" == */* || ! "${LUKS_KEY_FILENAME}" =~ ^[A-Za-z0-9._@%+=:,~-]+$ ]]; then
printf "\e[91m❌ Unsafe LUKS key filename rejected. \e[0m\n" >&2
exit 42
fi
LUKS_KEY_FILE="${VAR_TMP_SECRET}/${LUKS_KEY_FILENAME}"
if [[ -L "${LUKS_KEY_FILE}" || ! -f "${LUKS_KEY_FILE}" ]]; then
printf "\e[91m❌ Unsafe LUKS key file rejected. \e[0m\n" >&2
exit 42
fi
LUKS_KEY_OWNER="$(stat -c '%u' "${LUKS_KEY_FILE}")"
LUKS_KEY_MODE="$(stat -c '%a' "${LUKS_KEY_FILE}")"
LUKS_KEY_LINK_COUNT="$(stat -c '%h' "${LUKS_KEY_FILE}")"
if [[ "${LUKS_KEY_OWNER}" != "${EUID}" || "${LUKS_KEY_LINK_COUNT}" != "1" \
|| ( "${LUKS_KEY_MODE}" != "400" && "${LUKS_KEY_MODE}" != "600" ) ]]; then
printf "\e[91m❌ Unsafe LUKS key ownership, permissions, or link count rejected. \e[0m\n" >&2
exit 42
fi
# shellcheck disable=SC2155 # shellcheck disable=SC2155
declare -i VAR_ROOTFS_SIZE=$(stat -c%s -- "${ROOTFS}") declare -i VAR_ROOTFS_SIZE=$(stat -c%s -- "${ROOTFS}")
@@ -125,7 +172,7 @@ declare -i VAR_LUKSFS_SIZE=$(( ( (BASE_SIZE + ALIGN_BYTES - 1) / ALIGN_BYTES ) *
preallocate "${LUKSFS}" "${VAR_LUKSFS_SIZE}" preallocate "${LUKSFS}" "${VAR_LUKSFS_SIZE}"
exec {KEYFD}<"${LUKS_KEY_FILE}" exec {KEYFD}<"${VAR_TMP_SECRET}/luks.txt"
if [[ "${VAR_CDLB_INSIDE_RUNNER}" == "false" ]]; then if [[ "${VAR_CDLB_INSIDE_RUNNER}" == "false" ]]; then
@@ -169,7 +216,7 @@ fi
cryptsetup open --key-file "/proc/$$/fd/${KEYFD}" "${LUKSFS}" crypt_liveiso cryptsetup open --key-file "/proc/$$/fd/${KEYFD}" "${LUKSFS}" crypt_liveiso
# shellcheck disable=SC2155 # shellcheck disable=SC2155
declare -i LUKS_FREE=$(blockdev --getsize64 /dev/mapper/crypt_liveiso) declare -i LUKS_FREE=$(blockdev --getsize64 "${MAPPER_DEV}")
declare -i SQUASH_FS="${VAR_ROOTFS_SIZE}" declare -i SQUASH_FS="${VAR_ROOTFS_SIZE}"
if (( LUKS_FREE >= SQUASH_FS )); then if (( LUKS_FREE >= SQUASH_FS )); then
@@ -183,13 +230,27 @@ else
fi fi
dd if="${ROOTFS}" of=/dev/mapper/crypt_liveiso bs=8M status=progress conv=fsync dd if="${ROOTFS}" of="${MAPPER_DEV}" bs=8M status=progress conv=fsync
sync sync
# The selected boot root is the complete decrypted mapper. Hashing this exact block payload binds the signed manifest to the
# bytes later mounted as SquashFS, including the mapper padding after the SquashFS image.
LC_ALL=C sha512sum "${MAPPER_DEV}" >| "${ROOTFS_ATTESTATION}"
gpg --batch --yes --pinentry-mode loopback --passphrase-file "${VAR_SIGNING_KEY_PASSFILE}" --local-user "${VAR_SIGNING_KEY_FPR}" \
--detach-sign --output "${ROOTFS_ATTESTATION_SIG}" "${ROOTFS_ATTESTATION}"
gpgv --keyring "${VAR_VERIFY_KEYRING}" "${ROOTFS_ATTESTATION_SIG}" "${ROOTFS_ATTESTATION}"
(cd / && LC_ALL=C sha512sum -c --strict --quiet "${ROOTFS_ATTESTATION}")
chmod 0444 "${ROOTFS_ATTESTATION}" "${ROOTFS_ATTESTATION_SIG}"
cryptsetup close crypt_liveiso cryptsetup close crypt_liveiso
exec {KEYFD}<&- exec {KEYFD}<&-
shred -fzu -n 5 -- "${LUKS_KEY_FILE}" shred -fzu -n 5 -- "${VAR_TMP_SECRET}/luks.txt"
rm -f -- "${ROOTFS}" rm -f -- "${ROOTFS}"
@@ -89,9 +89,20 @@ Verify_checksums() {
_KEYFILE="" _KEYFILE=""
_MANIFEST_FOUND="false"
_MP="" _MP=""
_RETURN_PGP=""
_RETURN_SHA=""
_VERIFICATION_EXECUTED="false"
_VERIFICATION_SUCCEEDED="false"
### Parse commandline arguments ---------------------------------------------------------------------------------------------- ### Parse commandline arguments ----------------------------------------------------------------------------------------------
# shellcheck disable=SC2154
for _PARAMETER in ${LIVE_BOOT_CMDLINE}; do for _PARAMETER in ${LIVE_BOOT_CMDLINE}; do
case "${_PARAMETER}" in case "${_PARAMETER}" in
@@ -244,10 +255,12 @@ Verify_checksums() {
if [ -e "${_CHECKSUM}" ]; then if [ -e "${_CHECKSUM}" ]; then
_MANIFEST_FOUND="true"
log_in "Found: [${_CHECKSUM}] ..." log_in "Found: [${_CHECKSUM}] ..."
if [ -e "/usr/bin/${_DIGEST}sum" ]; then if [ -e "/usr/bin/${_DIGEST}sum" ]; then
_VERIFICATION_EXECUTED="true"
log_in "Found: [/usr/bin/${_DIGEST}sum] ..." log_in "Found: [/usr/bin/${_DIGEST}sum] ..."
if [ "${LIVE_VERIFY_CHECKSUMS_SIGNATURES}" = "true" ]; then if [ "${LIVE_VERIFY_CHECKSUMS_SIGNATURES}" = "true" ]; then
@@ -279,6 +292,7 @@ Verify_checksums() {
if grep -v '^#' "${_CHECKSUM}" | LC_ALL=C /usr/bin/"${_DIGEST}"sum -c > "${_CHECKSUM_LOG}" 2>&1; then if grep -v '^#' "${_CHECKSUM}" | LC_ALL=C /usr/bin/"${_DIGEST}"sum -c > "${_CHECKSUM_LOG}" 2>&1; then
_RETURN_SHA="${?}" _RETURN_SHA="${?}"
_VERIFICATION_SUCCEEDED="true"
cat "${_CHECKSUM_LOG}" > "${_TTY}" cat "${_CHECKSUM_LOG}" > "${_TTY}"
log_ok "Found: [/usr/bin/${_DIGEST}sum] successful verified: [${_CHECKSUM}]" log_ok "Found: [/usr/bin/${_DIGEST}sum] successful verified: [${_CHECKSUM}]"
@@ -313,6 +327,33 @@ Verify_checksums() {
log_end_msg log_end_msg
printf "\n" printf "\n"
if [ "${_MANIFEST_FOUND}" != "true" ]; then
log_er "No supported checksum manifest found."
sleep 8
panic "No supported checksum manifest found."
return 1
fi
if [ "${_VERIFICATION_EXECUTED}" != "true" ]; then
log_er "No supported checksum verification tool was available."
sleep 8
panic "No supported checksum verification tool was available."
return 1
fi
if [ "${_VERIFICATION_SUCCEEDED}" != "true" ]; then
log_er "No supported checksum manifest was verified successfully."
sleep 8
panic "No supported checksum manifest was verified successfully."
return 1
fi
case "${_RETURN_PGP},${_RETURN_SHA}" in case "${_RETURN_PGP},${_RETURN_SHA}" in
"0,0") "0,0")
@@ -331,18 +372,28 @@ Verify_checksums() {
log_er "Verification of [GPG signature] file successful, while verification of [sha checksum] file failed." log_er "Verification of [GPG signature] file successful, while verification of [sha checksum] file failed."
sleep 8 sleep 8
panic "Verification of [GPG signature] file successful, while verification of [sha checksum] file failed." panic "Verification of [GPG signature] file successful, while verification of [sha checksum] file failed."
return 1
;; ;;
*",0") *",0")
log_er "Verification of [GPG signature] file failed, while verification of [sha checksum] file successful." log_er "Verification of [GPG signature] file failed, while verification of [sha checksum] file successful."
sleep 8 sleep 8
panic "Verification of [GPG signature] file failed, while verification of [sha checksum] file successful." panic "Verification of [GPG signature] file failed, while verification of [sha checksum] file successful."
return 1
;; ;;
"na,"*) "na,"*)
log_er "Verification of [sha checksum] file failed." log_er "Verification of [sha checksum] file failed."
sleep 8 sleep 8
panic "Verification of [sha checksum] file failed." panic "Verification of [sha checksum] file failed."
return 1
;;
*)
log_er "Checksum verification ended in an unsupported state."
sleep 8
panic "Checksum verification ended in an unsupported state."
return 1
;; ;;
esac esac
@@ -15,14 +15,11 @@
# SPDX-Security-Contact: security@coresecret.eu # SPDX-Security-Contact: security@coresecret.eu
# Module summary: # Module summary:
# - Runs after the encrypted live root filesystem has been decrypted. # - Runs after the encrypted live root filesystem has been decrypted and selected for the SquashFS root mount.
# - Requires the pinned public key, attestation hash file, and detached signature to exist as readable, non-empty regular files # - Requires the pinned public key and the signed decrypted-mapper SHA-512 manifest from the mounted live medium.
# inside the decrypted rootfs. # - Verifies the manifest signature and pinned signer fingerprint, then verifies the complete selected decrypted mapper against
# - Verifies the attestation signature with gpgv against the pinned key material. # the manifest.
# - Confirms that the signature fingerprint matches the build-time expected rootfs fingerprint and panics on missing, malformed, # - Panics on missing, malformed, mismatched, or unverifiable evidence.
# or mismatched evidence.
_SAVED_SET_OPTS="$(set +o)"
set -eu set -eu
@@ -37,9 +34,12 @@ export CDLB_EXP_CA_FPR="@EXP_CA_FPR@"
### Name of the top-level dm-crypt mapping (e.g., cryptsetup --label): zzzz_ciss_crypt_squash.hook.binary ---------------------- ### Name of the top-level dm-crypt mapping (e.g., cryptsetup --label): zzzz_ciss_crypt_squash.hook.binary ----------------------
export CDLB_MAPPER_NAME="${CDLB_MAPPER_NAME:-crypt_liveiso}" export CDLB_MAPPER_NAME="${CDLB_MAPPER_NAME:-crypt_liveiso}"
### Attestation file locations inside decrypted rootfs. ------------------------------------------------------------------------ ### Rootfs selection and attestation file locations. ---------------------------------------------------------------------------
CDLB_ATTEST_FPR_SHA="${CDLB_ATTEST_FPR_SHA:-/root/root/.ciss/attestation/${CDLB_EXP_FPR}.gpg.sha512sum.txt}" CDLB_LUKS_FS="${CDLB_LUKS_FS:-/live/ciss_rootfs.crypt}"
CDLB_ATTEST_FPR_SIG="${CDLB_ATTEST_FPR_SIG:-/root/root/.ciss/attestation/${CDLB_EXP_FPR}.gpg.sha512sum.txt.sig}" CDLB_MAPPER_DEV="${CDLB_MAPPER_DEV:-/dev/mapper/${CDLB_MAPPER_NAME}}"
CDLB_MNT_MEDIUM="${CDLB_MNT_MEDIUM:-/run/live/medium}"
CDLB_ATTEST_ROOTFS_SHA="${CDLB_ATTEST_ROOTFS_SHA:-${CDLB_MNT_MEDIUM}${CDLB_LUKS_FS}.decrypted.sha512sum.txt}"
CDLB_ATTEST_ROOTFS_SIG="${CDLB_ATTEST_ROOTFS_SIG:-${CDLB_ATTEST_ROOTFS_SHA}.sig}"
CDLB_KEY_DIR="${CDLB_KEY_DIR:-/etc/ciss/keys}" CDLB_KEY_DIR="${CDLB_KEY_DIR:-/etc/ciss/keys}"
### Declare functions ---------------------------------------------------------------------------------------------------------- ### Declare functions ----------------------------------------------------------------------------------------------------------
@@ -91,11 +91,13 @@ require_attestation_file() {
log_er "0042() : ${artifact_label} is a broken symlink, not a regular file: [${artifact_path}]" log_er "0042() : ${artifact_label} is a broken symlink, not a regular file: [${artifact_path}]"
panic "0042() : ${artifact_label} is a broken symlink, not a regular file: [${artifact_path}]" panic "0042() : ${artifact_label} is a broken symlink, not a regular file: [${artifact_path}]"
return 1
fi fi
log_er "0042() : ${artifact_label} missing: [${artifact_path}]" log_er "0042() : ${artifact_label} missing: [${artifact_path}]"
panic "0042() : ${artifact_label} missing: [${artifact_path}]" panic "0042() : ${artifact_label} missing: [${artifact_path}]"
return 1
fi fi
@@ -103,6 +105,7 @@ require_attestation_file() {
log_er "0042() : ${artifact_label} is not a regular file: [${artifact_path}]" log_er "0042() : ${artifact_label} is not a regular file: [${artifact_path}]"
panic "0042() : ${artifact_label} is not a regular file: [${artifact_path}]" panic "0042() : ${artifact_label} is not a regular file: [${artifact_path}]"
return 1
fi fi
@@ -110,6 +113,7 @@ require_attestation_file() {
log_er "0042() : ${artifact_label} is empty: [${artifact_path}]" log_er "0042() : ${artifact_label} is empty: [${artifact_path}]"
panic "0042() : ${artifact_label} is empty: [${artifact_path}]" panic "0042() : ${artifact_label} is empty: [${artifact_path}]"
return 1
fi fi
@@ -117,23 +121,56 @@ require_attestation_file() {
log_er "0042() : ${artifact_label} is not readable: [${artifact_path}]" log_er "0042() : ${artifact_label} is not readable: [${artifact_path}]"
panic "0042() : ${artifact_label} is not readable: [${artifact_path}]" panic "0042() : ${artifact_label} is not readable: [${artifact_path}]"
return 1
fi fi
return 0 return 0
} }
HASH_FILE="${CDLB_ATTEST_FPR_SHA}" #######################################
SIGN_FILE="${CDLB_ATTEST_FPR_SIG}" # Validate the selected decrypted rootfs payload.
# Globals:
# None
# Arguments:
# 1: Absolute payload path
# Returns:
# 0: on success
#######################################
require_rootfs_payload() {
payload_path="${1}"
if [ ! -b "${payload_path}" ]; then
log_er "0042() : Selected rootfs payload is not a block device: [${payload_path}]"
panic "0042() : Selected rootfs payload is not a block device: [${payload_path}]"
return 1
fi
if [ ! -r "${payload_path}" ]; then
log_er "0042() : Selected rootfs payload is not readable: [${payload_path}]"
panic "0042() : Selected rootfs payload is not readable: [${payload_path}]"
return 1
fi
return 0
}
HASH_FILE="${CDLB_ATTEST_ROOTFS_SHA}"
SIGN_FILE="${CDLB_ATTEST_ROOTFS_SIG}"
KEYFILE="${CDLB_KEY_DIR}/${CDLB_EXP_FPR}.gpg" KEYFILE="${CDLB_KEY_DIR}/${CDLB_EXP_FPR}.gpg"
require_attestation_file "Public key" "${KEYFILE}" require_attestation_file "Public key" "${KEYFILE}"
require_attestation_file "Attestation data" "${HASH_FILE}" require_attestation_file "Rootfs attestation manifest" "${HASH_FILE}"
require_attestation_file "Attestation signature" "${SIGN_FILE}" require_attestation_file "Rootfs attestation signature" "${SIGN_FILE}"
require_rootfs_payload "${CDLB_MAPPER_DEV}"
log_in "0042() : Verifying rootfs attestation with 'gpgv' and inside LUKS encrypted rootfs pinned GPG FPR." log_in "0042() : Verifying signed rootfs attestation manifest with pinned GPG FPR."
if ! _STATUS="$(/usr/bin/gpgv --keyring "${KEYFILE}" --status-fd 1 "${SIGN_FILE}" "${HASH_FILE}" 2>&1)"; then if ! _STATUS="$(/usr/bin/gpgv --no-default-keyring --keyring "${KEYFILE}" --status-fd 1 "${SIGN_FILE}" "${HASH_FILE}" 2>&1)"; then
log_er "0042() : gpgv verification failed for signature: [${SIGN_FILE}]" log_er "0042() : gpgv verification failed for signature: [${SIGN_FILE}]"
@@ -145,6 +182,7 @@ if ! _STATUS="$(/usr/bin/gpgv --keyring "${KEYFILE}" --status-fd 1 "${SIGN_FILE}
sleep 8 sleep 8
panic "0042() : gpgv verification failed for signature: [${SIGN_FILE}]" panic "0042() : gpgv verification failed for signature: [${SIGN_FILE}]"
exit 1
fi fi
@@ -160,10 +198,51 @@ else
log_er "0042() : Signature FPR mismatch: got: [${_CDLB_SIG_FILE_FPR}] expected: [${CDLB_EXP_FPR}]" log_er "0042() : Signature FPR mismatch: got: [${_CDLB_SIG_FILE_FPR}] expected: [${CDLB_EXP_FPR}]"
sleep 8 sleep 8
panic "[FATAL] Signature FPR mismatch: got: [${_CDLB_SIG_FILE_FPR}] expected: [${CDLB_EXP_FPR}]." panic "[FATAL] Signature FPR mismatch: got: [${_CDLB_SIG_FILE_FPR}] expected: [${CDLB_EXP_FPR}]."
exit 1
fi fi
eval "${_SAVED_SET_OPTS}" _ATTEST_RECORD_COUNT="$(awk 'NF && $1 !~ /^#/ { count++ } END { print count + 0 }' "${HASH_FILE}")"
if [ "${_ATTEST_RECORD_COUNT}" -ne 1 ]; then
log_er "0042() : Rootfs attestation manifest must contain exactly one checksum record: [${HASH_FILE}]"
sleep 8
panic "0042() : Rootfs attestation manifest must contain exactly one checksum record: [${HASH_FILE}]"
exit 1
fi
_ATTESTED_PAYLOAD="$(awk 'NF && $1 !~ /^#/ { print $2; exit }' "${HASH_FILE}")"
if [ "${_ATTESTED_PAYLOAD}" != "${CDLB_MAPPER_DEV}" ]; then
log_er "0042() : Rootfs attestation manifest targets [${_ATTESTED_PAYLOAD}], expected selected payload [${CDLB_MAPPER_DEV}]"
sleep 8
panic "0042() : Rootfs attestation manifest does not target the selected rootfs payload."
exit 1
fi
log_in "0042() : Verifying selected decrypted rootfs mapper content: [${CDLB_MAPPER_DEV}]"
if ! _CHECKSUM_STATUS="$(cd / && LC_ALL=C /usr/bin/sha512sum -c --strict --quiet "${HASH_FILE}" 2>&1)"; then
log_er "0042() : Rootfs payload checksum verification failed: [${CDLB_MAPPER_DEV}]"
if [ -n "${_CHECKSUM_STATUS}" ]; then
printf '%s\n' "${_CHECKSUM_STATUS}" >&2
fi
sleep 8
panic "0042() : Rootfs payload checksum verification failed: [${CDLB_MAPPER_DEV}]"
exit 1
fi
log_ok "0042() : Rootfs payload checksum verification successful: [${CDLB_MAPPER_DEV}]"
printf "\e[92m[INFO] Successfully applied : [/usr/lib/live/boot/0042_ciss_post_decrypt_attest] \n\e[0m" printf "\e[92m[INFO] Successfully applied : [/usr/lib/live/boot/0042_ciss_post_decrypt_attest] \n\e[0m"
+2 -13
View File
@@ -37,10 +37,6 @@ A lightweight Shell Wrapper for building a hardened Debian Live ISO Image.
--build-directory </path/to/build_directory> --build-directory </path/to/build_directory>
Where the Debian Live Build Image should be generated. RECOMMENDED path: </opt/cdlb> Where the Debian Live Build Image should be generated. RECOMMENDED path: </opt/cdlb>
The path MUST be canonical and dedicated to the builder; a new directory's canonical parent MUST already exist.
New or empty directories receive the
'.ciss-live-builder-owned' marker; populated unmarked directories are rejected. Cleanup is intentionally destructive
only inside the exact validated marker-owned directory.
MUST be provided. MUST be provided.
--change-splash <STRING> one of <club | hexagon> --change-splash <STRING> one of <club | hexagon>
@@ -61,7 +57,6 @@ A lightweight Shell Wrapper for building a hardened Debian Live ISO Image.
--debug, -d --debug, -d
Enables debug logging for the main program routine. Detailed logging information are written to: Enables debug logging for the main program routine. Detailed logging information are written to:
</tmp/ciss_live_builder_1801049.log> </tmp/ciss_live_builder_1801049.log>
A final exact-value sanitisation pass is defence in depth and does not replace careful tracing discipline.
--dhcp-centurion --dhcp-centurion
If a DHCP lease is provided, the provider's name server will be overridden and the hardened, privacy-focused If a DHCP lease is provided, the provider's name server will be overridden and the hardened, privacy-focused
@@ -91,13 +86,11 @@ A lightweight Shell Wrapper for building a hardened Debian Live ISO Image.
--key_age=* --key_age=*
The SOPS AGE private keyring for decryption operations. Change '*' to your desired SOPS AGE key file. The SOPS AGE private keyring for decryption operations. Change '*' to your desired SOPS AGE key file.
'*' MUST be a filename only without slashes, '.' or '..' traversal.
File MUST be placed in: File MUST be placed in:
</dev/shm/cdlb_secrets> </dev/shm/cdlb_secrets>
--key_luks=* --key_luks=*
The LUKS encryption / decryption passphrase for '/'-fs-encryption. Change '*' to your desired passphrase file. The LUKS encryption / decryption passphrase for '/'-fs-encryption. Change '*' to your desired passphrase file.
'*' MUST be a filename only without slashes, '.' or '..' traversal.
File MUST be placed in: File MUST be placed in:
</dev/shm/cdlb_secrets> </dev/shm/cdlb_secrets>
@@ -147,7 +140,7 @@ A lightweight Shell Wrapper for building a hardened Debian Live ISO Image.
--root-password-file </dev/shm/cdlb_secrets/password.txt>> --root-password-file </dev/shm/cdlb_secrets/password.txt>>
Password file for 'root', if given, MUST be a string of 42 to 64 characters. Password file for 'root', if given, MUST be a string of 42 to 64 characters.
If the argument is omitted, no further login authentication is required for the local console. If the argument is omitted, no further login authentication is required for the local console.
Safe absolute paths remain supported and are validated separately. RECOMMENDED path: MUST be placed in:
</dev/shm/cdlb_secrets/password.txt> </dev/shm/cdlb_secrets/password.txt>
--secure-boot-profile <STRING> one of <debian-shim | ciss-uki> --secure-boot-profile <STRING> one of <debian-shim | ciss-uki>
@@ -163,8 +156,7 @@ A lightweight Shell Wrapper for building a hardened Debian Live ISO Image.
specified via '--signing_key=*'. If the keyring is protected, then provide the passphrase in its own file. specified via '--signing_key=*'. If the keyring is protected, then provide the passphrase in its own file.
Specify the fingerprint of the key to use via '--signing_key_fpr=*'. Specify the fingerprint of the key to use via '--signing_key_fpr=*'.
Optionally import an offline GPG CA signing public key via: '--signing_ca=*'. Optionally import an offline GPG CA signing public key via: '--signing_ca=*'.
Change '*' to your desired filename-only files / fingerprint. Filename-only values MUST NOT contain slashes or traversal. Change '*' to your desired files / fingerprint. Files MUST be placed in:
Files MUST be placed in:
</dev/shm/cdlb_secrets> </dev/shm/cdlb_secrets>
--sshfp --sshfp
@@ -190,9 +182,6 @@ A lightweight Shell Wrapper for building a hardened Debian Live ISO Image.
💡 Notes: 💡 Notes:
🔵 You MUST be 'root' to run this script. 🔵 You MUST be 'root' to run this script.
🔵 Private operator control does not remove the requirement for strict local secret path validation.
🔵 '/dev/shm/cdlb_secrets' MUST be tmpfs-backed, root-owned, mode 0700, and contain only single-link regular non-symlink files
with mode 0400 or 0600. Secure deletion with shred is best-effort only on modern storage.
💷 Please consider donating to my work at: 💷 Please consider donating to my work at:
🌐 https://coresecret.eu/spenden/ 🌐 https://coresecret.eu/spenden/
+48 -19
View File
@@ -12,19 +12,24 @@ include_toc: true
# 2. CISS.debian.live.builder Boot & Trust Chain (Technical Documentation) # 2. CISS.debian.live.builder Boot & Trust Chain (Technical Documentation)
**Status:** 2025-11-12<br> **Status:** 2026-06-10<br>
**Audience:** CICA CISO, CISS staff, technically proficient administrators<br> **Audience:** CICA CISO, CISS staff, technically proficient administrators<br>
**Summary:** The **CISS.debian.live.builder** Live-ISO establishes a two-stage verification chain around the live root: an early ISO-edge check (signature and FPR pin) *before* LUKS unlock, and a late root-FS attestation *after* unlock, reinforced by `dm-crypt (AES-XTS)` and `dm-integrity (HMAC-SHA-512)`. UEFI Secure Boot can use either the default Microsoft/Debian shim chain, or a CISS-signed UKI chain for systems that trust the CISS Secure Boot key material.<br> **Summary:** The **CISS.debian.live.builder** Live-ISO establishes a two-stage verification chain around the live root: an
ISO-edge checksum-manifest check and a late attestation of the complete selected decrypted rootfs mapper. The late check verifies
both the signed SHA-512 manifest and the mapper bytes before boot continues. `dm-integrity` separately provides sector-integrity
protection and is not a substitute for origin-bound signature and checksum attestation. UEFI Secure Boot can use either the
default Microsoft/Debian shim chain, or a CISS-signed UKI chain for systems that trust the CISS Secure Boot key material.<br>
# 3. Overview # 3. Overview
* **Trust anchor:** Pinned fingerprint (FPR) of the signing key embedded at build time in initramfs hooks. * **Trust anchor:** Pinned fingerprint (FPR) of the signing key embedded at build time in initramfs hooks.
* **Integrity & authenticity verification:** * **Integrity & authenticity verification:**
1. **Early:** Verify `sha512sum.txt` at the ISO edge using `gpgv` and FPR pin. 1. **ISO edge:** Verify one supported checksum manifest using `gpgv`, FPR pinning, and the matching checksum tool.
2. **Late:** Verify an attestation hash list inside the decrypted root FS using `gpgv` and FPR pin. 2. **Late rootfs attestation:** Verify the signed mapper manifest and the complete selected decrypted mapper
`/dev/mapper/crypt_liveiso` with `sha512sum -c`.
* **Storage-level AEAD (functional):** `dm-crypt` (AES-XTS-512) and `dm-integrity` (HMAC-SHA-512, 4 KiB). * **Storage-level protection:** `dm-crypt` (AES-XTS-512) and, outside runner builds, `dm-integrity` (HMAC-SHA-512, 4 KiB).
* **Remotely unlock:** CISS hardened and build dropbear, modern primitives only, no passwords, no agent/forwarding. * **Remotely unlock:** CISS hardened and build dropbear, modern primitives only, no passwords, no agent/forwarding.
# 3.1. Secure Boot Profiles # 3.1. Secure Boot Profiles
@@ -52,11 +57,11 @@ private Secure Boot key names are detected in those paths before live-build chec
| Component | Primitive / Parameter | Purpose | | Component | Primitive / Parameter | Purpose |
|--------------|-----------------------------------------------------------|--------------------------------------------------------| |--------------|-----------------------------------------------------------|--------------------------------------------------------|
| LUKS2 | `aes-xts-plain64`, `--key-size 512`, `--sector-size 4096` | Confidentiality (2×256-bit XTS) | | LUKS2 | `aes-xts-plain64`, `--key-size 512`, `--sector-size 4096` | Confidentiality (2×256-bit XTS) |
| dm-integrity | `hmac-sha512` (keyed), journal | Adversary-resistant per-sector integrity, authenticity | | dm-integrity | `hmac-sha512` (keyed), journal | Per-sector integrity inside the LUKS mapping; not origin attestation |
| PBKDF | `argon2id`, `--iter-time 1000` ms | Key derivation, hardware-agnostic | | PBKDF | `argon2id`, `--iter-time 1000` ms | Key derivation, hardware-agnostic |
| Signatures | Ed25519 or RSA-4096 (FPR pinned) | Public verifiability, non-repudiation | | Signatures | Ed25519 or RSA-4096 (FPR pinned) | Public verifiability, non-repudiation |
| Verification | `gpgv --no-default-keyring` | No agent dependency in initramfs | | Verification | `gpgv --no-default-keyring` | No agent dependency in initramfs |
| Hash lists | `sha512sum` format | Deterministic content verification | | Hash lists | `sha512sum` format | Deterministic ISO-edge and decrypted-mapper verification |
| Dropbear | Modern KEX/AEAD (per `localoptions.h`) | Minimal attack surface, remote unlock | | Dropbear | Modern KEX/AEAD (per `localoptions.h`) | Minimal attack surface, remote unlock |
# 5. Diagram: CISS Live ISO Boot Flow # 5. Diagram: CISS Live ISO Boot Flow
@@ -150,7 +155,8 @@ flowchart TD
``` ```
**Note:** Encrypt-then-MAC at the block layer (functionally AEAD-equivalent). Any manipulation ⇒ hard I/O error. **Note:** `dm-integrity` detects sector corruption within its keyed mapping. The signed SHA-512 mapper manifest independently
binds the selected decrypted rootfs payload to the signing key. Neither property substitutes for the other.
# 7. CISS Live ISO LUKS Build-Time Core Steps # 7. CISS Live ISO LUKS Build-Time Core Steps
```sh ```sh
@@ -172,16 +178,24 @@ cryptsetup luksFormat \
"${LUKSFS}" "${LUKSFS}"
``` ```
After `filesystem.squashfs` is copied to `/dev/mapper/crypt_liveiso`, the binary hook hashes the complete decrypted mapper,
including the mapper padding after the SquashFS image. It writes
`binary/live/ciss_rootfs.crypt.decrypted.sha512sum.txt`, signs that manifest with the existing artifact-signing key, verifies the
signature and checksum locally, and only then closes the mapping and removes the plaintext `filesystem.squashfs`.
**Signing keys:** Ed25519 and RSA-4096; **FPR pinned at build time** in hooks. Signing keys are **additionally** signed by an offline GPG Root-CA (out-of-band trust chain). **Signing keys:** Ed25519 and RSA-4096; **FPR pinned at build time** in hooks. Signing keys are **additionally** signed by an offline GPG Root-CA (out-of-band trust chain).
# 8. Early ISO-Edge Verification (CISS modified hook 0030-ciss-verify-checksums, live-bottom) # 8. Early ISO-Edge Verification (CISS modified hook 0030-ciss-verify-checksums, live-bottom)
**Goal:** Before consuming any medium content, verify: **Goal:** During live-boot, require one supported ISO-edge checksum manifest to be successfully verified:
1. **Detached signature of `sha512sum.txt`** using `gpgv` against the embedded public key. 1. **Detached signature of `sha512sum.txt`** using `gpgv` against the embedded public key.
2. **FPR pinning:** Parse `VALIDSIG` and require exact match with the build-time pinned FPR. 2. **FPR pinning:** Parse `VALIDSIG` and require exact match with the build-time pinned FPR.
3. **Optional:** *Script self-IA* hash the executed hook and compare against the signed list (drift/bitrot detector). 3. **Optional:** *Script self-IA* hash the executed hook and compare against the signed list (drift/bitrot detector).
Verification is fail-closed. Boot panics if no supported manifest is present, no matching checksum tool is available, checksum
verification does not succeed, signature verification fails when requested, or verification ends in an unknown state.
**Core call (initramfs):** **Core call (initramfs):**
```sh ```sh
@@ -189,12 +203,16 @@ cryptsetup luksFormat \
# parse [GNUPG:] VALIDSIG ... <FPR> ... # parse [GNUPG:] VALIDSIG ... <FPR> ...
``` ```
# 9. Late Root-FS Attestation and dmsetup Health (CISS hook 0042_ciss_post_decrypt_attest, called by 9990-overlay.sh) # 9. Late Root-FS Payload Attestation (CISS hook 0042_ciss_post_decrypt_attest, called by 9990-overlay.sh)
**Goal:** After LUKS unlock, validate the **decrypted** contents and the **actual** mapping topology. **Goal:** After LUKS unlock and rootfs selection, verify the authenticity of the manifest and the content of the actual selected
decrypted rootfs payload.
* **Attestation files:** `/root/.ciss/attestation/<FPR>.sha512sum.txt[.sig]` * **Exact attested boundary:** the complete `/dev/mapper/crypt_liveiso` block payload selected by `9990-overlay.sh` and mounted
* **Key source:** `/etc/ciss/keys/*.gpg` (accepted only if FPR == build-pin) read-only as SquashFS, including mapper padding after the SquashFS image.
* **Attestation files:** `/run/live/medium/live/ciss_rootfs.crypt.decrypted.sha512sum.txt[.sig]`
* **Key source:** `/etc/ciss/keys/<FPR>.gpg` in the initramfs; the valid signature FPR must equal the build-time pin.
* **Manifest constraint:** exactly one checksum record is accepted, and it must target the selected mapper path.
**Core calls (initramfs):** **Core calls (initramfs):**
@@ -202,13 +220,20 @@ cryptsetup luksFormat \
# 1) Signature and FPR pin (no agent) # 1) Signature and FPR pin (no agent)
/usr/bin/gpgv --no-default-keyring --keyring "$KEYFILE" --status-fd 1 --verify "$SIG" "$DATA" /usr/bin/gpgv --no-default-keyring --keyring "$KEYFILE" --status-fd 1 --verify "$SIG" "$DATA"
# 2) Optional: Content hash verification # 2) Required selected-payload verification
( cd "$ROOTMP" && /usr/bin/sha512sum -c --strict --quiet "$DATA" ) ( cd / && /usr/bin/sha512sum -c --strict --quiet "$DATA" )
``` ```
The signed manifest alone is not sufficient: boot continues only after the selected decrypted mapper content matches it.
This boundary does not individually attest the mounted file tree, OverlayFS upper layer, runtime mutations, or secrets after
unlock.
# 10. Failure Policy (fail-closed, deterministic) # 10. Failure Policy (fail-closed, deterministic)
* **Abort** on: missing `VALIDSIG`, FPR mismatch, missing key / signature. * **ISO-edge abort:** no supported manifest, no supported verification tool, failed checksum, failed requested signature, or
unknown verification state.
* **Rootfs-attestation abort:** missing or malformed manifest/signature/key, missing `VALIDSIG`, FPR mismatch, manifest target
mismatch, unreadable or non-block selected mapper, or mapper checksum mismatch.
# 11. CISS hardened and built dropbear # 11. CISS hardened and built dropbear
@@ -251,17 +276,19 @@ flowchart TD
subgraph ISO Build Time subgraph ISO Build Time
A["Embed and pin GPG FPR (into ISO & RootFS as needed)"] e00@--> B["Generate ISO-edge sha512sum.txt and .sig"]; A["Embed and pin GPG FPR (into ISO & RootFS as needed)"] e00@--> B["Generate ISO-edge sha512sum.txt and .sig"];
B e01@--> C["Build filesystem.squashfs and wrap it into ciss_rootfs.crypt"]; B e01@--> C["Build filesystem.squashfs and wrap it into ciss_rootfs.crypt"];
C e01a@--> C2["Hash complete decrypted mapper and sign rootfs manifest"];
e00@{ animation: fast } e00@{ animation: fast }
e01@{ animation: fast } e01@{ animation: fast }
e01a@{ animation: fast }
end end
subgraph ISO Boot Time subgraph ISO Boot Time
C e02@--> D["0024 LUKS2, dm-integrity HMAC-SHA512"]; C2 e02@--> D["0024 LUKS2, dm-integrity HMAC-SHA512"];
D e03@-->|SUCCESSFUL| E["ciss_rootfs.crypt opened"]; D e03@-->|SUCCESSFUL| E["ciss_rootfs.crypt opened"];
E e04@--> F["Mounting RootFS"]; E e04@--> F["Mounting RootFS"];
F e05@--> G["0030 verification of authenticity and integrity via embedded and pinned GPG of ISO edge"]; F e05@--> G["0030 verification of authenticity and integrity via embedded and pinned GPG of ISO edge"];
G e06@-->|SUCCESSFUL| H["ISO edge verified"]; G e06@-->|SUCCESSFUL| H["ISO edge verified"];
H e07@--> I["0042 post-decrypt-attestation of RootFS"]; H e07@--> I["0042 verifies signed manifest and selected decrypted mapper"];
I e08@-->|SUCCESSFUL| J["RootFS attestation successful"]; I e08@-->|SUCCESSFUL| J["RootFS attestation successful"];
e02@{ animation: fast } e02@{ animation: fast }
e03@{ animation: fast } e03@{ animation: fast }
@@ -285,7 +312,9 @@ I -- FAIL --> X;
# 14. Closing Remarks # 14. Closing Remarks
This achieves a portable, self-contained trust chain without a Microsoft-db, providing strong protection against medium tampering, bitrot, and active attacks **both before and after decryption**. The dual-verification phases make the state transparent and deterministic. The verification path is fail-closed and binds the selected decrypted rootfs mapper to the pinned signing key. `dm-integrity`
adds a separate sector-integrity property where enabled. The implemented attestation does not cover runtime OverlayFS changes,
post-unlock secrets, or an individually enumerated mounted file tree.
--- ---
**[no tracking | no logging | no advertising | no profiling | no bullshit](https://coresecret.eu/)** **[no tracking | no logging | no advertising | no profiling | no bullshit](https://coresecret.eu/)**
+50 -26
View File
@@ -57,17 +57,20 @@ guard_sourcing || return "${ERR_GUARD_SRCE}"
# None # None
# Returns: # Returns:
# ERR_ARG_MSMTCH: on failure # ERR_ARG_MSMTCH: on failure
# ERR_BUILD_PATH: on failure # ERR_ARG_MSMTCH: on failure
# ERR_CONTROL_CT: on failure # ERR_CONTROL_CT: on failure
# ERR_DROPBEAR_V: on failure # ERR_DROPBEAR_V: on failure
# ERR_MISS_PWD_F: on failure
# ERR_MISS_PWD_P: on failure # ERR_MISS_PWD_P: on failure
# ERR_NOTABSPATH: on failure
# ERR_OWNS_PWD_F: on failure
# ERR_PASS_LENGH: on failure # ERR_PASS_LENGH: on failure
# ERR_PASS_PLICY: on failure # ERR_PASS_PLICY: on failure
# ERR_REIONICE_P: on failure # ERR_REIONICE_P: on failure
# ERR_REIO_C_VAL: on failure # ERR_REIO_C_VAL: on failure
# ERR_REIO_P_VAL: on failure # ERR_REIO_P_VAL: on failure
# ERR_RENICE_PRI: on failure # ERR_RENICE_PRI: on failure
# ERR_SECRET_PATH: on failure # ERR_RGHT_PWD_F: on failure
# ERR_SPLASH_PNG: on failure # ERR_SPLASH_PNG: on failure
# ERR__SOPS__VER: on failure # ERR__SOPS__VER: on failure
# ERR__SSH__PORT: on failure # ERR__SSH__PORT: on failure
@@ -131,9 +134,12 @@ arg_parser() {
;; ;;
--build-directory) --build-directory)
declare build_directory="${2-}" declare -gx VAR_HANDLER_BUILD_DIR="${2}"
validate_build_directory_path "${build_directory}" build_directory || exit "${ERR_BUILD_PATH}" if [[ ! "${VAR_HANDLER_BUILD_DIR}" =~ ^/ ]]; then
declare -gx VAR_HANDLER_BUILD_DIR="${build_directory}" if ! ${VAR_HANDLER_AUTOBUILD}; then boot_screen_cleaner; fi
printf "\e[91m❌ Error: --build-directory MUST be an absolute path. Got: '%s'\n" "${VAR_HANDLER_BUILD_DIR}" >&2
exit "${ERR_NOTABSPATH}"
fi
declare -gx VAR_BUILD_LOG="${VAR_HANDLER_BUILD_DIR}/cdlb_${VAR_ISO8601}_build.log" declare -gx VAR_BUILD_LOG="${VAR_HANDLER_BUILD_DIR}/cdlb_${VAR_ISO8601}_build.log"
shift 2 shift 2
;; ;;
@@ -260,22 +266,18 @@ arg_parser() {
;; ;;
--key_age=*) --key_age=*)
declare age_key="${1#*=}"
validate_secret_file_in_root "${age_key}" "SOPS Age key" || exit "${ERR_SECRET_PATH}"
# shellcheck disable=SC2034 # shellcheck disable=SC2034
declare -gx VAR_AGE="true" declare -gx VAR_AGE="true"
# shellcheck disable=SC2034 # shellcheck disable=SC2034
declare -gx VAR_AGE_KEY="${age_key}" declare -gx VAR_AGE_KEY="${1#*=}"
shift 1 shift 1
;; ;;
--key_luks=*) --key_luks=*)
declare luks_key="${1#*=}"
validate_secret_file_in_root "${luks_key}" "LUKS key file" || exit "${ERR_SECRET_PATH}"
# shellcheck disable=SC2034 # shellcheck disable=SC2034
declare -gx VAR_LUKS="true" declare -gx VAR_LUKS="true"
# shellcheck disable=SC2034 # shellcheck disable=SC2034
declare -gx VAR_LUKS_KEY="${luks_key}" declare -gx VAR_LUKS_KEY="${1#*=}"
shift 1 shift 1
;; ;;
@@ -471,7 +473,39 @@ arg_parser() {
fi fi
validate_secret_absolute_file "${pw_file}" "root password file" || exit "${ERR_SECRET_PATH}" if [[ ! -f "${pw_file}" ]]; then
if ! ${VAR_HANDLER_AUTOBUILD}; then boot_screen_cleaner; fi
printf "\e[91m❌ Error: --root-password-file password file '%s' does not exist.\e[0m\n" "${pw_file}" >&2
# shellcheck disable=SC2162
read -p $'\e[92m✅ Press \'ENTER\' to exit the script ... \e[0m'
exit "${ERR_MISS_PWD_F}"
fi
declare owner
owner=$(stat -c '%U:%G' "${pw_file}")
if [[ "${owner}" != "root:root" ]]; then
chown root:root "${pw_file}" || {
if ! ${VAR_HANDLER_AUTOBUILD}; then boot_screen_cleaner; fi
printf "\e[91m❌ Error: --root-password-file failed to set owner root:root on '%s'.\e[0m\n" "${pw_file}" >&2
# shellcheck disable=SC2162
read -p $'\e[92m✅ Press \'ENTER\' to exit the script ... \e[0m'
exit "${ERR_OWNS_PWD_F}"
}
fi
declare perms
perms=$(stat -c '%a' "${pw_file}")
if [[ "${perms}" -ne 400 ]]; then
chmod 0400 "${pw_file}" || {
if ! ${VAR_HANDLER_AUTOBUILD}; then boot_screen_cleaner; fi
printf "\e[91m❌ Error: --root-password-file failed to set permissions 0400 on '%s'.\e[0m\n" "${pw_file}" >&2
# shellcheck disable=SC2162
read -p $'\e[92m✅ Press \'ENTER\' to exit the script ... \e[0m'
exit "${ERR_RGHT_PWD_F}"
}
fi
declare plaintext_pw declare plaintext_pw
### No tracing for security reasons ---------------------------------------------------------------------------------- ### No tracing for security reasons ----------------------------------------------------------------------------------
@@ -524,7 +558,6 @@ arg_parser() {
[[ "${VAR_EARLY_DEBUG}" == "true" ]] && set +x [[ "${VAR_EARLY_DEBUG}" == "true" ]] && set +x
hash_temp=$(mkpasswd --method=sha-512 --salt="${salt}" --rounds=8388608 "${plaintext_pw}") hash_temp=$(mkpasswd --method=sha-512 --salt="${salt}" --rounds=8388608 "${plaintext_pw}")
register_secret_value "${hash_temp}"
### Turn on tracing again -------------------------------------------------------------------------------------------- ### Turn on tracing again --------------------------------------------------------------------------------------------
[[ "${VAR_EARLY_DEBUG}" == "true" ]] && set -x [[ "${VAR_EARLY_DEBUG}" == "true" ]] && set -x
@@ -573,20 +606,16 @@ arg_parser() {
;; ;;
--signing_ca=*) --signing_ca=*)
declare signing_ca="${1#*=}"
validate_secret_file_in_root "${signing_ca}" "signing CA file" || exit "${ERR_SECRET_PATH}"
# shellcheck disable=SC2034 # shellcheck disable=SC2034
declare -gx VAR_SIGNING_CA="${signing_ca}" declare -gx VAR_SIGNING_CA="${1#*=}"
shift 1 shift 1
;; ;;
--signing_key=*) --signing_key=*)
declare signing_key="${1#*=}"
validate_secret_file_in_root "${signing_key}" "signing key file" || exit "${ERR_SECRET_PATH}"
# shellcheck disable=SC2034 # shellcheck disable=SC2034
declare -gx VAR_SIGNER="true" declare -gx VAR_SIGNER="true"
# shellcheck disable=SC2034 # shellcheck disable=SC2034
declare -gx VAR_SIGNING_KEY="${signing_key}" declare -gx VAR_SIGNING_KEY="${1#*=}"
shift 1 shift 1
;; ;;
@@ -597,10 +626,8 @@ arg_parser() {
;; ;;
--signing_key_pass=*) --signing_key_pass=*)
declare signing_key_pass="${1#*=}"
validate_secret_file_in_root "${signing_key_pass}" "signing passphrase file" || exit "${ERR_SECRET_PATH}"
# shellcheck disable=SC2034 # shellcheck disable=SC2034
declare -gx VAR_SIGNING_KEY_PASS="${signing_key_pass}" declare -gx VAR_SIGNING_KEY_PASS="${1#*=}"
shift 1 shift 1
;; ;;
@@ -667,11 +694,8 @@ arg_parser() {
;; ;;
--ssh-pubkey) --ssh-pubkey)
declare ssh_pubkey_dir="${2-}"
validate_secret_absolute_directory "${ssh_pubkey_dir}" "SSH public-key directory" || exit "${ERR_SECRET_PATH}"
validate_secret_file "${ssh_pubkey_dir}/authorized_keys" "SSH authorized_keys file" || exit "${ERR_SECRET_PATH}"
# shellcheck disable=SC2034 # shellcheck disable=SC2034
declare -gx VAR_SSHPUBKEY="${ssh_pubkey_dir}" declare -gx VAR_SSHPUBKEY="${2}"
shift 2 shift 2
;; ;;
-340
View File
@@ -1,340 +0,0 @@
#!/bin/bash
# SPDX-Version: 3.0
# SPDX-CreationInfo: 2026-06-11; 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
guard_sourcing || return "${ERR_GUARD_SRCE}"
#######################################
# Prints a generic build-directory validation error without disclosing a path.
# Arguments:
# 1: unsafe input class
# 2: quiet flag
# Returns:
# ERR_BUILD_PATH
#######################################
build_directory_validation_error() {
declare error_class="$1" quiet="${2:-false}"
if [[ "${quiet}" != "true" ]]; then
printf "\e[91m❌ Unsafe build-directory input rejected: %s. \e[0m\n" "${error_class}" >&2
fi
return "${ERR_BUILD_PATH:-217}"
}
### Prevents accidental 'unset -f'.
# shellcheck disable=SC2034
readonly -f build_directory_validation_error
#######################################
# Canonicalises an existing path or a path whose parent exists.
# Arguments:
# 1: candidate path
# 2: output variable name
# Returns:
# 0: on success
# ERR_BUILD_PATH: on failure
#######################################
canonicalize_build_directory() {
declare candidate="$1" output_variable="$2" basename="" parent="" resolved_path=""
[[ -n "${candidate}" && "${candidate}" == /* ]] || return "${ERR_BUILD_PATH:-217}"
[[ "${candidate}" == "/" ]] || candidate="${candidate%/}"
[[ ! -L "${candidate}" ]] || return "${ERR_BUILD_PATH:-217}"
if [[ -e "${candidate}" ]]; then
resolved_path="$(realpath "${candidate}" 2>/dev/null)" || return "${ERR_BUILD_PATH:-217}"
else
basename="${candidate##*/}"
parent="${candidate%/*}"
[[ -n "${parent}" ]] || parent="/"
[[ -n "${basename}" && "${basename}" != "." && "${basename}" != ".." && -d "${parent}" ]] || return "${ERR_BUILD_PATH:-217}"
resolved_path="$(realpath "${parent}" 2>/dev/null)" || return "${ERR_BUILD_PATH:-217}"
resolved_path="${resolved_path%/}/${basename}"
fi
[[ "${candidate}" == "${resolved_path}" ]] || return "${ERR_BUILD_PATH:-217}"
printf -v "${output_variable}" '%s' "${resolved_path}"
return 0
}
### Prevents accidental 'unset -f'.
# shellcheck disable=SC2034
readonly -f canonicalize_build_directory
#######################################
# Validates a build directory against the repository cleanup policy.
# Globals:
# VAR_TMP_SECRET
# VAR_WORKDIR
# Arguments:
# 1: candidate path
# 2: output variable name
# 3: quiet flag
# Returns:
# 0: on success
# ERR_BUILD_PATH: on failure
#######################################
validate_build_directory_path() {
declare candidate="$1" output_variable="${2:-}" quiet="${3:-false}" secret_root="" validated_path="" workdir=""
declare -a rejected_paths=(
"/" "/bin" "/boot" "/dev" "/etc" "/home" "/lib" "/lib64" "/media" "/mnt" "/opt" "/proc" "/root" "/run" "/sbin" "/srv"
"/sys" "/tmp" "/usr" "/usr/local" "/var" "/var/lib" "/var/tmp"
)
declare rejected_path=""
canonicalize_build_directory "${candidate}" validated_path || {
build_directory_validation_error \
"path is empty, non-absolute, non-canonical, missing its parent, or is a symlink" "${quiet}"
return "${ERR_BUILD_PATH:-217}"
}
for rejected_path in "${rejected_paths[@]}"; do
if [[ "${validated_path}" == "${rejected_path}" ]]; then
build_directory_validation_error "broad or system parent directory" "${quiet}"
return "${ERR_BUILD_PATH:-217}"
fi
done
workdir="$(realpath "${VAR_WORKDIR}" 2>/dev/null)" || {
build_directory_validation_error "repository work directory cannot be resolved" "${quiet}"
return "${ERR_BUILD_PATH:-217}"
}
secret_root="$(realpath "${VAR_TMP_SECRET}" 2>/dev/null)" || {
build_directory_validation_error "secret root cannot be resolved" "${quiet}"
return "${ERR_BUILD_PATH:-217}"
}
if [[ "${workdir}" == "${validated_path}" || "${workdir}" == "${validated_path}/"* || "${validated_path}" == "${workdir}/"* \
|| "${validated_path}" == "${secret_root}" || "${validated_path}" == "${secret_root}/"* ]]; then
build_directory_validation_error "path is outside the dedicated build-directory policy" "${quiet}"
return "${ERR_BUILD_PATH:-217}"
fi
if [[ -n "${output_variable}" ]]; then
printf -v "${output_variable}" '%s' "${validated_path}"
fi
return 0
}
### Prevents accidental 'unset -f'.
# shellcheck disable=SC2034
readonly -f validate_build_directory_path
#######################################
# Validates the builder-owned marker for an exact build directory.
# Globals:
# EUID
# Arguments:
# 1: candidate path
# 2: quiet flag
# Returns:
# 0: on success
# ERR_BUILD_PATH: on failure
#######################################
validate_build_directory_marker() {
declare candidate="$1" quiet="${2:-false}" directory_mode="" directory_owner="" marker="" marker_build_dir=""
declare expected_marker_value="" marker_link_count="" marker_value="" mode="" owner=""
validate_build_directory_path "${candidate}" marker_build_dir "${quiet}" || return "${ERR_BUILD_PATH:-217}"
[[ -d "${marker_build_dir}" ]] || {
build_directory_validation_error "build directory does not exist" "${quiet}"
return "${ERR_BUILD_PATH:-217}"
}
directory_owner="$(secure_stat -c '%u' "${marker_build_dir}" 2>/dev/null)" || return "${ERR_BUILD_PATH:-217}"
directory_mode="$(secure_stat -c '%a' "${marker_build_dir}" 2>/dev/null)" || return "${ERR_BUILD_PATH:-217}"
if [[ "${directory_owner}" != "${EUID}" ]] || (( (8#${directory_mode} & 022) != 0 )); then
build_directory_validation_error "build directory ownership or permissions are unsafe" "${quiet}"
return "${ERR_BUILD_PATH:-217}"
fi
marker="${marker_build_dir}/.ciss-live-builder-owned"
if [[ -L "${marker}" || ! -f "${marker}" ]]; then
build_directory_validation_error "builder-owned marker is missing or unsafe" "${quiet}"
return "${ERR_BUILD_PATH:-217}"
fi
owner="$(secure_stat -c '%u' "${marker}" 2>/dev/null)" || return "${ERR_BUILD_PATH:-217}"
mode="$(secure_stat -c '%a' "${marker}" 2>/dev/null)" || return "${ERR_BUILD_PATH:-217}"
marker_link_count="$(secure_stat -c '%h' "${marker}" 2>/dev/null)" || return "${ERR_BUILD_PATH:-217}"
marker_value="$(cat "${marker}" || exit $?; printf '.')" || return "${ERR_BUILD_PATH:-217}"
marker_value="${marker_value%.}"
expected_marker_value="${marker_build_dir}"$'\n'
if [[ "${owner}" != "${EUID}" || "${mode}" != "400" || "${marker_link_count}" != "1" \
|| "${marker_value}" != "${expected_marker_value}" ]]; then
build_directory_validation_error "builder-owned marker does not match the exact directory" "${quiet}"
return "${ERR_BUILD_PATH:-217}"
fi
return 0
}
### Prevents accidental 'unset -f'.
# shellcheck disable=SC2034
readonly -f validate_build_directory_marker
#######################################
# Validates an existing exact subpath below a marker-owned build directory.
# Arguments:
# 1: build directory
# 2: relative subpath
# 3: output variable name
# 4: quiet flag
# Returns:
# 0: on success
# ERR_BUILD_PATH: on failure
#######################################
validate_build_directory_subpath() {
declare build_directory="$1" relative_path="$2" output_variable="$3" quiet="${4:-false}"
declare candidate_subpath="" resolved_subpath=""
validate_build_directory_marker "${build_directory}" "${quiet}" || return "${ERR_BUILD_PATH:-217}"
if [[ -z "${relative_path}" || "${relative_path}" == /* || "${relative_path}" == "." || "${relative_path}" == ".." \
|| "${relative_path}" == ../* || "${relative_path}" == */../* || "${relative_path}" == */.. \
|| "${relative_path}" == ./* || "${relative_path}" == */./* || "${relative_path}" == */. ]]; then
build_directory_validation_error "unsafe relative cleanup subpath" "${quiet}"
return "${ERR_BUILD_PATH:-217}"
fi
candidate_subpath="${build_directory}/${relative_path}"
if [[ -L "${candidate_subpath}" || ! -e "${candidate_subpath}" ]]; then
build_directory_validation_error "cleanup subpath is missing or is a symlink" "${quiet}"
return "${ERR_BUILD_PATH:-217}"
fi
resolved_subpath="$(realpath "${candidate_subpath}" 2>/dev/null)" || return "${ERR_BUILD_PATH:-217}"
if [[ "${resolved_subpath}" != "${candidate_subpath}" || "${resolved_subpath}" != "${build_directory}/"* ]]; then
build_directory_validation_error "cleanup subpath escapes the exact build directory" "${quiet}"
return "${ERR_BUILD_PATH:-217}"
fi
printf -v "${output_variable}" '%s' "${resolved_subpath}"
return 0
}
### Prevents accidental 'unset -f'.
# shellcheck disable=SC2034
readonly -f validate_build_directory_subpath
#######################################
# Initialises a new or empty build directory and its ownership marker.
# Arguments:
# 1: candidate path
# 2: output variable name
# Returns:
# 0: on success
# ERR_BUILD_PATH: on failure
#######################################
initialize_build_directory() {
declare candidate="$1" output_variable="$2" directory_mode="" directory_owner="" existing_entry=""
declare initialized_build_dir="" marker=""
validate_build_directory_path "${candidate}" initialized_build_dir || return "${ERR_BUILD_PATH:-217}"
if [[ ! -e "${initialized_build_dir}" ]]; then
mkdir -m 0700 "${initialized_build_dir}" || return "${ERR_BUILD_PATH:-217}"
fi
validate_build_directory_path "${initialized_build_dir}" initialized_build_dir || return "${ERR_BUILD_PATH:-217}"
[[ -d "${initialized_build_dir}" && ! -L "${initialized_build_dir}" ]] || return "${ERR_BUILD_PATH:-217}"
directory_owner="$(secure_stat -c '%u' "${initialized_build_dir}" 2>/dev/null)" || return "${ERR_BUILD_PATH:-217}"
directory_mode="$(secure_stat -c '%a' "${initialized_build_dir}" 2>/dev/null)" || return "${ERR_BUILD_PATH:-217}"
if [[ "${directory_owner}" != "${EUID}" ]] || (( (8#${directory_mode} & 022) != 0 )); then
build_directory_validation_error "build directory ownership or permissions are unsafe"
return "${ERR_BUILD_PATH:-217}"
fi
marker="${initialized_build_dir}/.ciss-live-builder-owned"
if [[ -e "${marker}" || -L "${marker}" ]]; then
validate_build_directory_marker "${initialized_build_dir}" || return "${ERR_BUILD_PATH:-217}"
else
existing_entry="$(find "${initialized_build_dir}" -mindepth 1 -maxdepth 1 -print -quit)" || return "${ERR_BUILD_PATH:-217}"
if [[ -n "${existing_entry}" ]]; then
build_directory_validation_error "non-empty directory has no builder-owned marker"
return "${ERR_BUILD_PATH:-217}"
fi
(umask 077; printf '%s\n' "${initialized_build_dir}" >| "${marker}") || return "${ERR_BUILD_PATH:-217}"
chmod 0400 "${marker}" || return "${ERR_BUILD_PATH:-217}"
validate_build_directory_marker "${initialized_build_dir}" || return "${ERR_BUILD_PATH:-217}"
fi
printf -v "${output_variable}" '%s' "${initialized_build_dir}"
return 0
}
### Prevents accidental 'unset -f'.
# shellcheck disable=SC2034
readonly -f initialize_build_directory
#######################################
# Removes paths with a one-filesystem boundary where supported.
# Arguments:
# paths to remove
# Returns:
# rm exit status
#######################################
remove_build_paths() {
# shellcheck disable=SC2312
if rm --help 2>&1 | grep -q -- '--one-file-system'; then
rm -rf --one-file-system -- "$@"
else
rm -rf -- "$@"
fi
}
### Prevents accidental 'unset -f'.
# shellcheck disable=SC2034
readonly -f remove_build_paths
#######################################
# Deletes all content except the ownership marker from an exact build directory.
# Arguments:
# 1: candidate path
# Returns:
# 0: on success
# ERR_BUILD_PATH: on failure
#######################################
clean_build_directory_contents() {
declare candidate="$1" build_entry=""
declare -a build_entries=()
declare -i old_dotglob=0 old_failglob=0 old_nullglob=0
validate_build_directory_marker "${candidate}" || return "${ERR_BUILD_PATH:-217}"
shopt -q dotglob && old_dotglob=1
shopt -q failglob && old_failglob=1
shopt -q nullglob && old_nullglob=1
shopt -s dotglob nullglob
shopt -u failglob
build_entries=("${candidate}"/*)
for build_entry in "${build_entries[@]}"; do
[[ "${build_entry}" == "${candidate}/.ciss-live-builder-owned" ]] && continue
remove_build_paths "${build_entry}" || {
if (( old_dotglob )); then shopt -s dotglob; else shopt -u dotglob; fi
if (( old_failglob )); then shopt -s failglob; else shopt -u failglob; fi
if (( old_nullglob )); then shopt -s nullglob; else shopt -u nullglob; fi
return "${ERR_BUILD_PATH:-217}"
}
done
if (( old_dotglob )); then shopt -s dotglob; else shopt -u dotglob; fi
if (( old_failglob )); then shopt -s failglob; else shopt -u failglob; fi
if (( old_nullglob )); then shopt -s nullglob; else shopt -u nullglob; fi
validate_build_directory_marker "${candidate}" || return "${ERR_BUILD_PATH:-217}"
return 0
}
### Prevents accidental 'unset -f'.
# shellcheck disable=SC2034
readonly -f clean_build_directory_contents
# vim: number et ts=2 sw=2 sts=2 ai tw=128 ft=sh
+24 -14
View File
@@ -23,26 +23,36 @@ guard_sourcing || return "${ERR_GUARD_SRCE}"
# 0: on success # 0: on success
####################################### #######################################
x_remove() { x_remove() {
declare luks_key_filename="${VAR_LUKS_KEY:-luks.txt}" luks_key_path="" signing_pass_path=""
declare -a find_args=("${VAR_TMP_SECRET}" -xdev -type f)
printf "\e[95m🧪 %s starting ... \e[0m\n" "${BASH_SOURCE[0]}" printf "\e[95m🧪 %s starting ... \e[0m\n" "${BASH_SOURCE[0]}"
validate_secret_staging_area || return "${ERR_SECRET_PATH}" declare _old_nullglob="" _old_dotglob=""
### Enable nullglob/dotglob, disable failglob for safe globbing.
_old_nullglob="$(shopt -p nullglob || true)"
_old_dotglob="$( shopt -p dotglob || true)"
shopt -s nullglob dotglob
if [[ "${VAR_SIGNER}" == "true" ]]; then if [[ "${VAR_SIGNER}" == "true" ]]; then
validate_secret_file_in_root "${VAR_SIGNING_KEY_PASS}" "signing passphrase file" || return "${ERR_SECRET_PATH}"
signing_pass_path="${VAR_TMP_SECRET}/${VAR_SIGNING_KEY_PASS}" # shellcheck disable=SC2312
find_args+=(! -path "${signing_pass_path}") find "${VAR_TMP_SECRET}" -xdev -type f \
! -path "${VAR_TMP_SECRET}/signing_key_pass.txt" \
! -path "${VAR_TMP_SECRET}/luks.txt" \
-print0 \
| xargs -0 --no-run-if-empty shred -fzu -n 5 --
else
### Removes secrets securely.
# shellcheck disable=SC2312
find "${VAR_TMP_SECRET}" -xdev -type f -print0 | xargs -0 --no-run-if-empty shred -fzu -n 5 --
find "${VAR_TMP_SECRET}" -xdev -depth -type d -empty -delete
fi fi
validate_secret_file_in_root "${luks_key_filename}" "LUKS key file" || return "${ERR_SECRET_PATH}" eval "${_old_nullglob}" 2>/dev/null || true
luks_key_path="${VAR_TMP_SECRET}/${luks_key_filename}" eval "${_old_dotglob}" 2>/dev/null || true
find_args+=(! -path "${luks_key_path}")
# shellcheck disable=SC2312
find "${find_args[@]}" -print0 | xargs -0 --no-run-if-empty shred -fzu -n 5 --
find "${VAR_TMP_SECRET}" -xdev -depth -type d -empty -delete
printf "\e[92m✅ %s successfully applied. \e[0m\n" "${BASH_SOURCE[0]}" printf "\e[92m✅ %s successfully applied. \e[0m\n" "${BASH_SOURCE[0]}"
+1 -4
View File
@@ -9,12 +9,12 @@
# SPDX-LicenseComment: This file is part of the CISS.debian.installer.secure framework. # SPDX-LicenseComment: This file is part of the CISS.debian.installer.secure framework.
# SPDX-PackageName: CISS.debian.live.builder # SPDX-PackageName: CISS.debian.live.builder
# SPDX-Security-Contact: security@coresecret.eu # SPDX-Security-Contact: security@coresecret.eu
# shellcheck disable=SC2154
guard_sourcing || return "${ERR_GUARD_SRCE}" guard_sourcing || return "${ERR_GUARD_SRCE}"
####################################### #######################################
# Integrates and generates sha512sum and GPG signatures on CISS specific LIVE boot artifacts: # Integrates and generates sha512sum and GPG signatures on CISS specific LIVE boot artifacts:
# - /root/.ciss/attestation/VAR_SIGNING_KEY_FPR.*
# - /etc/initramfs-tools/files/unlock_wrapper.sh # - /etc/initramfs-tools/files/unlock_wrapper.sh
# - /usr/lib/live/boot/0030-ciss-verify-checksums # - /usr/lib/live/boot/0030-ciss-verify-checksums
# Globals: # Globals:
@@ -31,10 +31,7 @@ guard_sourcing || return "${ERR_GUARD_SRCE}"
ciss_upgrades_boot() { ciss_upgrades_boot() {
printf "\e[95m🧪 %s starting ... \e[0m\n" "${BASH_SOURCE[0]}" printf "\e[95m🧪 %s starting ... \e[0m\n" "${BASH_SOURCE[0]}"
gpg --batch --yes --export "${VAR_SIGNING_KEY_FPR}" >| "${VAR_HANDLER_BUILD_DIR}/config/includes.chroot/root/.ciss/attestation/${VAR_SIGNING_KEY_FPR}.gpg"
declare -ar _ary_target=( declare -ar _ary_target=(
"/root/.ciss/attestation/${VAR_SIGNING_KEY_FPR}.gpg"
"/etc/initramfs-tools/files/unlock_wrapper.sh" "/etc/initramfs-tools/files/unlock_wrapper.sh"
"/usr/lib/live/boot/0030-ciss-verify-checksums" "/usr/lib/live/boot/0030-ciss-verify-checksums"
) )
+32 -29
View File
@@ -32,7 +32,15 @@ guard_sourcing || return "${ERR_GUARD_SRCE}"
# 0: on success # 0: on success
####################################### #######################################
clean_up() { clean_up() {
declare chroot_directory="" clean_exit_code="$1" fs_type="" includes_directory="" declare clean_exit_code="$1" fs_type="" _old_nullglob="" _old_dotglob="" _old_failglob=""
### Enable nullglob/dotglob, disable failglob for safe globbing.
_old_nullglob="$(shopt -p nullglob || true)"
_old_dotglob="$( shopt -p dotglob || true)"
_old_failglob="$(shopt -p failglob || true)"
shopt -s nullglob dotglob
shopt -u failglob
if [[ -e /dev/mapper/crypt_liveiso ]]; then if [[ -e /dev/mapper/crypt_liveiso ]]; then
cryptsetup close crypt_liveiso || true cryptsetup close crypt_liveiso || true
@@ -44,10 +52,10 @@ clean_up() {
rm -f -- "${VAR_NOTES}" rm -f -- "${VAR_NOTES}"
### Release advisory lock on FD 127. ### Release advisory lock on FD 127.
flock -u 127 2>/dev/null || true flock -u 127
### Close file descriptor 127. ### Close file descriptor 127.
exec 127>&- 2>/dev/null || true exec 127>&-
### Remove the lockfile artifact. ### Remove the lockfile artifact.
rm -f /run/lock/ciss_live_builder.lock rm -f /run/lock/ciss_live_builder.lock
@@ -92,41 +100,36 @@ clean_up() {
### No tracing for security reasons ------------------------------------------------------------------------------------------ ### No tracing for security reasons ------------------------------------------------------------------------------------------
[[ "${VAR_EARLY_DEBUG}" == "true" ]] && set +x [[ "${VAR_EARLY_DEBUG}" == "true" ]] && set +x
### Removes secrets securely only after re-validating the fixed tmpfs staging area. ### Removes secrets securely.
if validate_secret_staging_area "true"; then # shellcheck disable=SC2312
find "${VAR_TMP_SECRET}" -xdev -type f -print0 | xargs -0 --no-run-if-empty shred -fzu -n 5 --
find "${VAR_TMP_SECRET}" -xdev -depth -type d -empty -delete
### Securely shred all regular files below ./includes.chroot, then remove empty dirs.
if [[ -d "${VAR_HANDLER_BUILD_DIR}/config/includes.chroot" ]]; then
# shellcheck disable=SC2312 # shellcheck disable=SC2312
find "${VAR_TMP_SECRET}" -xdev -type f -print0 | xargs -0 --no-run-if-empty shred -fzu -n 5 -- || true find "${VAR_HANDLER_BUILD_DIR}/config/includes.chroot" -xdev -type f -print0 | xargs -0 --no-run-if-empty shred -fzu -n 5 --
find "${VAR_TMP_SECRET}" -xdev -depth -type d -empty -delete || true
else ### Remove empty directories (bottom-up).
printf "\e[93m⚠ Secret cleanup skipped because the staging area failed validation. \e[0m\n" >&2 find "${VAR_HANDLER_BUILD_DIR}/config/includes.chroot" -depth -xdev -type d -empty -delete
fi fi
### Destructive build cleanup requires the exact builder-owned directory marker. ### Delete all files and directories below ./chroot.
if [[ -n "${VAR_HANDLER_BUILD_DIR}" ]] && validate_build_directory_marker "${VAR_HANDLER_BUILD_DIR}" "true"; then if [[ -d "${VAR_HANDLER_BUILD_DIR}/chroot" ]]; then
if [[ -e "${VAR_HANDLER_BUILD_DIR}/config/includes.chroot" || -L "${VAR_HANDLER_BUILD_DIR}/config/includes.chroot" ]]; then
if validate_build_directory_subpath "${VAR_HANDLER_BUILD_DIR}" "config/includes.chroot" includes_directory "true"; then rm -rf "${VAR_HANDLER_BUILD_DIR}/chroot"
# shellcheck disable=SC2312
find "${includes_directory}" -xdev -type f -print0 | xargs -0 --no-run-if-empty shred -fzu -n 5 -- || true
find "${includes_directory}" -depth -xdev -type d -empty -delete || true
else
printf "\e[93m⚠ Build includes cleanup skipped because the exact subpath failed validation. \e[0m\n" >&2
fi
fi
if [[ -e "${VAR_HANDLER_BUILD_DIR}/chroot" || -L "${VAR_HANDLER_BUILD_DIR}/chroot" ]]; then
if validate_build_directory_subpath "${VAR_HANDLER_BUILD_DIR}" "chroot" chroot_directory "true"; then
remove_build_paths "${chroot_directory}" || true
else
printf "\e[93m⚠ Build chroot cleanup skipped because the exact subpath failed validation. \e[0m\n" >&2
fi
fi
elif [[ -n "${VAR_HANDLER_BUILD_DIR}" ]]; then
printf "\e[93m⚠ Build-directory cleanup skipped because the exact builder-owned marker failed validation. \e[0m\n" >&2
fi fi
### Turn on tracing again ---------------------------------------------------------------------------------------------------- ### Turn on tracing again ----------------------------------------------------------------------------------------------------
[[ "${VAR_EARLY_DEBUG}" == "true" ]] && set -x [[ "${VAR_EARLY_DEBUG}" == "true" ]] && set -x
eval "${_old_nullglob}" 2>/dev/null || true
eval "${_old_dotglob}" 2>/dev/null || true
eval "${_old_failglob}" 2>/dev/null || true
return 0 return 0
} }
### Prevents accidental 'unset -f'. ### Prevents accidental 'unset -f'.
-92
View File
@@ -1,92 +0,0 @@
#!/bin/bash
# SPDX-Version: 3.0
# SPDX-CreationInfo: 2026-06-11; 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
guard_sourcing || return "${ERR_GUARD_SRCE}"
#######################################
# Replaces exact registered secret values in one controlled log file.
# Globals:
# _ARY_SECRET_REDACTION_VALUES
# Arguments:
# 1: log file
# Returns:
# 0: on success or missing log
# ERR_SANITIZING: on failure
#######################################
sanitize_debug_log() {
declare log_file="$1" log_text="" replacement="" secret_value="" tmp_file=""
[[ -n "${log_file}" && -f "${log_file}" ]] || return 0
[[ ! -L "${log_file}" ]] || return "${ERR_SANITIZING:-133}"
log_text="$(cat "${log_file}" || exit $?; printf '.')" || return "${ERR_SANITIZING:-133}"
log_text="${log_text%.}"
for secret_value in "${_ARY_SECRET_REDACTION_VALUES[@]}"; do
[[ -n "${secret_value}" ]] || continue
printf -v replacement '%*s' "${#secret_value}" ''
replacement="${replacement// /*}"
log_text="${log_text//"${secret_value}"/"${replacement}"}"
done
tmp_file="$(mktemp "${log_file}.sanitize.XXXXXX")" || return "${ERR_SANITIZING:-133}"
chmod 0600 "${tmp_file}" || {
rm -f "${tmp_file}"
return "${ERR_SANITIZING:-133}"
}
printf '%s' "${log_text}" >| "${tmp_file}" || {
rm -f "${tmp_file}"
return "${ERR_SANITIZING:-133}"
}
mv -f "${tmp_file}" "${log_file}" || {
rm -f "${tmp_file}"
return "${ERR_SANITIZING:-133}"
}
return 0
}
### Prevents accidental 'unset -f'.
# shellcheck disable=SC2034
readonly -f sanitize_debug_log
#######################################
# Runs the final exact-value sanitisation pass for controlled logs.
# Globals:
# LOG_DEBUG
# LOG_ERROR
# LOG_VAR
# Arguments:
# None
# Returns:
# 0: on success
# ERR_SANITIZING: on failure
#######################################
sanitize_debug_logs() {
declare log_file=""
declare -a log_files=("${LOG_DEBUG:-}" "${LOG_VAR:-}" "${LOG_ERROR:-}")
set +x
if [[ -e "/proc/$$/fd/42" || -e "/dev/fd/42" ]]; then
exec 42>&-
fi
for log_file in "${log_files[@]}"; do
sanitize_debug_log "${log_file}" || return "${ERR_SANITIZING:-133}"
done
return 0
}
### Prevents accidental 'unset -f'.
# shellcheck disable=SC2034
readonly -f sanitize_debug_logs
# vim: number et ts=2 sw=2 sts=2 ai tw=128 ft=sh
-7
View File
@@ -41,12 +41,6 @@ init_gnupg() {
if [[ "${VAR_SIGNER}" == "true" ]]; then if [[ "${VAR_SIGNER}" == "true" ]]; then
validate_secret_file_in_root "${VAR_SIGNING_KEY}" "signing key file" || return "${ERR_SECRET_PATH}"
validate_secret_file_in_root "${VAR_SIGNING_KEY_PASS}" "signing passphrase file" || return "${ERR_SECRET_PATH}"
if [[ -n "${VAR_SIGNING_CA}" ]]; then
validate_secret_file_in_root "${VAR_SIGNING_CA}" "signing CA file" || return "${ERR_SECRET_PATH}"
fi
__umask=$(umask) __umask=$(umask)
umask 0077 umask 0077
@@ -88,7 +82,6 @@ EOF
declare __pw="" declare __pw=""
__pw="$(<"${VAR_SIGNING_KEY_PASSFILE}")"; __pw="${__pw%$'\r'}"; printf '%s' "${__pw}" >| "${VAR_SIGNING_KEY_PASSFILE}" __pw="$(<"${VAR_SIGNING_KEY_PASSFILE}")"; __pw="${__pw%$'\r'}"; printf '%s' "${__pw}" >| "${VAR_SIGNING_KEY_PASSFILE}"
register_secret_value "${__pw}"
__pw="" && unset __pw __pw="" && unset __pw
### Turn on tracing again ---------------------------------------------------------------------------------------------------- ### Turn on tracing again ----------------------------------------------------------------------------------------------------
-2
View File
@@ -182,8 +182,6 @@ hardening_ultra() {
printf "\e[95m🧪 Updating SSH Keys, Ports ... \e[0m\n" printf "\e[95m🧪 Updating SSH Keys, Ports ... \e[0m\n"
### ./config/includes.chroot/root/.ssh --------------------------------------------------------------------------------------- ### ./config/includes.chroot/root/.ssh ---------------------------------------------------------------------------------------
validate_secret_absolute_directory "${VAR_SSHPUBKEY}" "SSH public-key directory" || return "${ERR_SECRET_PATH}"
validate_secret_file "${VAR_SSHPUBKEY}/authorized_keys" "SSH authorized_keys file" || return "${ERR_SECRET_PATH}"
install -d -m 0700 "${VAR_HANDLER_BUILD_DIR}/config/includes.chroot/root/.ssh" install -d -m 0700 "${VAR_HANDLER_BUILD_DIR}/config/includes.chroot/root/.ssh"
install -m 0600 -o root -g root "${VAR_SSHPUBKEY}/authorized_keys" "${VAR_HANDLER_BUILD_DIR}/config/includes.chroot/root/.ssh/" install -m 0600 -o root -g root "${VAR_SSHPUBKEY}/authorized_keys" "${VAR_HANDLER_BUILD_DIR}/config/includes.chroot/root/.ssh/"
+21 -7
View File
@@ -23,22 +23,36 @@ guard_sourcing || return "${ERR_GUARD_SRCE}"
# 0: on success # 0: on success
####################################### #######################################
lb_config_start() { lb_config_start() {
declare canonical_build_dir=""
printf "\e[95m🧪 %s starting ... \e[0m\n" "${BASH_SOURCE[0]}" printf "\e[95m🧪 %s starting ... \e[0m\n" "${BASH_SOURCE[0]}"
initialize_build_directory "${VAR_HANDLER_BUILD_DIR}" canonical_build_dir || return "${ERR_BUILD_PATH}" if [[ ! -d ${VAR_HANDLER_BUILD_DIR} ]]; then
VAR_HANDLER_BUILD_DIR="${canonical_build_dir}"
cd "${VAR_HANDLER_BUILD_DIR}" || return "${ERR_BUILD_PATH}" mkdir -p "${VAR_HANDLER_BUILD_DIR}"
# shellcheck disable=SC2164
cd "${VAR_HANDLER_BUILD_DIR}"
printf "\e[92m✅ '%s' created. \e[0m\n" "${VAR_HANDLER_BUILD_DIR}"
else
# shellcheck disable=SC2164
cd "${VAR_HANDLER_BUILD_DIR}"
fi
if [[ -d "${VAR_HANDLER_BUILD_DIR}/.build" ]]; then if [[ -d "${VAR_HANDLER_BUILD_DIR}/.build" ]]; then
validate_build_directory_marker "${VAR_HANDLER_BUILD_DIR}" || return "${ERR_BUILD_PATH}" # shellcheck disable=SC2164
cd "${VAR_HANDLER_BUILD_DIR}"
printf "\e[95m🧪 Deleting former config, binary and cache ... \e[0m\n" printf "\e[95m🧪 Deleting former config, binary and cache ... \e[0m\n"
lb clean --binary --cache --purge --source lb clean --binary --cache --purge --source
clean_build_directory_contents "${VAR_HANDLER_BUILD_DIR}" || return "${ERR_BUILD_PATH}"
if [[ "${PWD}" == "${VAR_HANDLER_BUILD_DIR}" && "${PWD}" != "/" && -n "${PWD}" ]]; then
rm -rf -- ./* ./.??*
fi
printf "\e[92m✅ Deleting former config, binary and cache done.\e[0m\n" printf "\e[92m✅ Deleting former config, binary and cache done.\e[0m\n"
+4 -24
View File
@@ -84,7 +84,6 @@ init_primordial() {
### Check for SOPS AGE key integration --------------------------------------------------------------------------------------- ### Check for SOPS AGE key integration ---------------------------------------------------------------------------------------
if [[ "${VAR_AGE,,}" == "true" ]]; then if [[ "${VAR_AGE,,}" == "true" ]]; then
validate_secret_file_in_root "${VAR_AGE_KEY}" "SOPS Age key" || return "${ERR_SECRET_PATH}"
install -d -m 0700 "${VAR_HANDLER_BUILD_DIR}/config/includes.chroot/root/.config/sops/age" install -d -m 0700 "${VAR_HANDLER_BUILD_DIR}/config/includes.chroot/root/.config/sops/age"
install -m 0400 "${VAR_TMP_SECRET}/${VAR_AGE_KEY}" "${VAR_HANDLER_BUILD_DIR}/config/includes.chroot/root/.config/sops/age/keys.txt" install -m 0400 "${VAR_TMP_SECRET}/${VAR_AGE_KEY}" "${VAR_HANDLER_BUILD_DIR}/config/includes.chroot/root/.config/sops/age/keys.txt"
shred -fzu -n 5 -- "${VAR_TMP_SECRET}/${VAR_AGE_KEY}" 2>/dev/null || rm -f "${VAR_TMP_SECRET}/${VAR_AGE_KEY}" shred -fzu -n 5 -- "${VAR_TMP_SECRET}/${VAR_AGE_KEY}" 2>/dev/null || rm -f "${VAR_TMP_SECRET}/${VAR_AGE_KEY}"
@@ -93,35 +92,16 @@ init_primordial() {
### Check for SSH CISS and PhysNet Primordial-Workflow™ integration ------------------------------------------------------- ### Check for SSH CISS and PhysNet Primordial-Workflow™ integration -------------------------------------------------------
if [[ "${VAR_SSHFP,,}" == "true" ]]; then if [[ "${VAR_SSHFP,,}" == "true" ]]; then
declare secret_key_file=""
declare -a identity_files=() host_key_files=()
validate_secret_directory "${VAR_TMP_SECRET}" "secret root" "true" || return "${ERR_SECRET_PATH}"
while IFS= read -r -d '' secret_key_file; do
validate_secret_file "${secret_key_file}" "primordial SSH identity file" || return "${ERR_SECRET_PATH}"
identity_files+=("${secret_key_file}")
done < <(find "${VAR_TMP_SECRET}" -maxdepth 1 -type f -name 'id*' -print0)
while IFS= read -r -d '' secret_key_file; do
validate_secret_file "${secret_key_file}" "primordial SSH host-key file" || return "${ERR_SECRET_PATH}"
host_key_files+=("${secret_key_file}")
done < <(find "${VAR_TMP_SECRET}" -maxdepth 1 -type f -name 'ssh_host_*' -print0)
(( ${#identity_files[@]} > 0 && ${#host_key_files[@]} > 0 )) || {
secret_validation_error "required primordial SSH key files are missing"
return "${ERR_SECRET_PATH}"
}
install -d -m 0700 "${VAR_HANDLER_BUILD_DIR}/config/includes.chroot/root/.ssh" install -d -m 0700 "${VAR_HANDLER_BUILD_DIR}/config/includes.chroot/root/.ssh"
install -m 0600 "${identity_files[@]}" "${VAR_HANDLER_BUILD_DIR}/config/includes.chroot/root/.ssh/" install -m 0600 "${VAR_TMP_SECRET}/id"* "${VAR_HANDLER_BUILD_DIR}/config/includes.chroot/root/.ssh/"
normalize_ssh_keys_in_dir "${VAR_HANDLER_BUILD_DIR}/config/includes.chroot/root/.ssh" normalize_ssh_keys_in_dir "${VAR_HANDLER_BUILD_DIR}/config/includes.chroot/root/.ssh"
shred -fzu -n 5 -- "${identity_files[@]}" 2>/dev/null || rm -f "${identity_files[@]}" shred -fzu -n 5 -- "${VAR_TMP_SECRET}/id"* 2>/dev/null || rm -f "${VAR_TMP_SECRET}/id"*
install -d -m 0700 "${VAR_HANDLER_BUILD_DIR}/config/includes.chroot/root/ssh" install -d -m 0700 "${VAR_HANDLER_BUILD_DIR}/config/includes.chroot/root/ssh"
install -m 0600 "${host_key_files[@]}" "${VAR_HANDLER_BUILD_DIR}/config/includes.chroot/root/ssh/" install -m 0600 "${VAR_TMP_SECRET}/ssh_host_"* "${VAR_HANDLER_BUILD_DIR}/config/includes.chroot/root/ssh/"
normalize_ssh_keys_in_dir "${VAR_HANDLER_BUILD_DIR}/config/includes.chroot/root/ssh/" normalize_ssh_keys_in_dir "${VAR_HANDLER_BUILD_DIR}/config/includes.chroot/root/ssh/"
shred -fzu -n 5 -- "${host_key_files[@]}" 2>/dev/null || rm -f "${host_key_files[@]}" shred -fzu -n 5 -- "${VAR_TMP_SECRET}/ssh_host_"* 2>/dev/null || rm -f "${VAR_TMP_SECRET}/ssh_host_"*
fi fi
-384
View File
@@ -1,384 +0,0 @@
#!/bin/bash
# SPDX-Version: 3.0
# SPDX-CreationInfo: 2026-06-11; 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
guard_sourcing || return "${ERR_GUARD_SRCE}"
if ! declare -p _ARY_SECRET_REDACTION_VALUES >/dev/null 2>&1; then
declare -ga _ARY_SECRET_REDACTION_VALUES=()
fi
#######################################
# Runs GNU stat on Debian and gstat on macOS development hosts.
# Arguments:
# stat arguments
# Returns:
# stat exit status
#######################################
secure_stat() {
if command -v gstat >/dev/null 2>&1; then
gstat "$@"
else
stat "$@"
fi
}
### Prevents accidental 'unset -f'.
# shellcheck disable=SC2034
readonly -f secure_stat
#######################################
# Prints a generic secret validation error without disclosing a path or value.
# Arguments:
# 1: unsafe input class
# 2: quiet flag
# Returns:
# ERR_SECRET_PATH
#######################################
secret_validation_error() {
declare error_class="$1" quiet="${2:-false}"
if [[ "${quiet}" != "true" ]]; then
printf "\e[91m❌ Unsafe secret input rejected: %s. \e[0m\n" "${error_class}" >&2
fi
return "${ERR_SECRET_PATH:-216}"
}
### Prevents accidental 'unset -f'.
# shellcheck disable=SC2034
readonly -f secret_validation_error
#######################################
# Registers an exact known secret value for final log redaction.
# Globals:
# _ARY_SECRET_REDACTION_VALUES
# Arguments:
# 1: secret value
# Returns:
# 0: on success
#######################################
register_secret_value() {
declare secret_value="$1" registered_value="" was_traced="false"
[[ $- == *x* ]] && was_traced="true"
set +x
if [[ -n "${secret_value}" ]]; then
for registered_value in "${_ARY_SECRET_REDACTION_VALUES[@]}"; do
if [[ "${registered_value}" == "${secret_value}" ]]; then
[[ "${was_traced}" == "true" ]] && set -x
return 0
fi
done
_ARY_SECRET_REDACTION_VALUES+=("${secret_value}")
fi
[[ "${was_traced}" == "true" ]] && set -x
return 0
}
### Prevents accidental 'unset -f'.
# shellcheck disable=SC2034
readonly -f register_secret_value
#######################################
# Registers exact text values from a controlled secret file.
# Globals:
# _ARY_SECRET_REDACTION_VALUES
# Arguments:
# 1: secret file
# Returns:
# 0: on success
#######################################
register_secret_file_for_redaction() {
declare secret_file="$1" secret_line="" secret_text="" was_traced="false"
[[ $- == *x* ]] && was_traced="true"
set +x
secret_text="$(cat "${secret_file}" || exit $?; printf '.')" || {
[[ "${was_traced}" == "true" ]] && set -x
return "${ERR_SANITIZING:-133}"
}
secret_text="${secret_text%.}"
register_secret_value "${secret_text}"
while IFS= read -r secret_line || [[ -n "${secret_line}" ]]; do
register_secret_value "${secret_line}"
done < "${secret_file}"
[[ "${was_traced}" == "true" ]] && set -x
return 0
}
### Prevents accidental 'unset -f'.
# shellcheck disable=SC2034
readonly -f register_secret_file_for_redaction
#######################################
# Validates a filename-only secret argument.
# Arguments:
# 1: filename
# 2: input class
# Returns:
# 0: on success
# ERR_SECRET_PATH: on failure
#######################################
validate_secret_filename() {
declare filename="$1" input_class="${2:-filename-only secret argument}"
declare filename_regex='^[A-Za-z0-9._@%+=:,~-]+$'
if [[ -z "${filename}" || "${filename}" == "." || "${filename}" == ".." || "${filename}" == */* \
|| ! "${filename}" =~ ${filename_regex} ]]; then
secret_validation_error "${input_class}"
return "${ERR_SECRET_PATH:-216}"
fi
return 0
}
### Prevents accidental 'unset -f'.
# shellcheck disable=SC2034
readonly -f validate_secret_filename
#######################################
# Validates a restrictively permissioned secret directory.
# Arguments:
# 1: directory path
# 2: input class
# 3: require tmpfs
# 4: quiet flag
# Returns:
# 0: on success
# ERR_SECRET_PATH: on failure
#######################################
validate_secret_directory() {
declare directory="$1" input_class="${2:-secret directory}" require_tmpfs="${3:-false}" quiet="${4:-false}"
declare fs_type="" mode="" owner=""
if [[ -z "${directory}" || -L "${directory}" || ! -d "${directory}" ]]; then
secret_validation_error "${input_class}" "${quiet}"
return "${ERR_SECRET_PATH:-216}"
fi
owner="$(secure_stat -c '%u' "${directory}" 2>/dev/null)" || {
secret_validation_error "${input_class} ownership" "${quiet}"
return "${ERR_SECRET_PATH:-216}"
}
mode="$(secure_stat -c '%a' "${directory}" 2>/dev/null)" || {
secret_validation_error "${input_class} permissions" "${quiet}"
return "${ERR_SECRET_PATH:-216}"
}
if [[ "${owner}" != "${EUID}" || "${mode}" != "700" ]]; then
secret_validation_error "${input_class} ownership or permissions" "${quiet}"
return "${ERR_SECRET_PATH:-216}"
fi
if [[ "${require_tmpfs}" == "true" ]]; then
fs_type="$(secure_stat -f -c '%T' "${directory}" 2>/dev/null)" || {
secret_validation_error "${input_class} filesystem" "${quiet}"
return "${ERR_SECRET_PATH:-216}"
}
if [[ "${fs_type}" != "tmpfs" && "${fs_type}" != "ramfs" ]]; then
secret_validation_error "${input_class} is not tmpfs-backed" "${quiet}"
return "${ERR_SECRET_PATH:-216}"
fi
fi
return 0
}
### Prevents accidental 'unset -f'.
# shellcheck disable=SC2034
readonly -f validate_secret_directory
#######################################
# Validates and registers a secret file.
# Arguments:
# 1: file path
# 2: input class
# 3: register for redaction
# 4: quiet flag
# Returns:
# 0: on success
# ERR_SECRET_PATH: on failure
#######################################
validate_secret_file() {
declare secret_file="$1" input_class="${2:-secret file}" register_value="${3:-true}" quiet="${4:-false}"
declare link_count="" mode="" owner=""
if [[ -z "${secret_file}" || -L "${secret_file}" || ! -f "${secret_file}" ]]; then
secret_validation_error "${input_class}" "${quiet}"
return "${ERR_SECRET_PATH:-216}"
fi
owner="$(secure_stat -c '%u' "${secret_file}" 2>/dev/null)" || {
secret_validation_error "${input_class} ownership" "${quiet}"
return "${ERR_SECRET_PATH:-216}"
}
mode="$(secure_stat -c '%a' "${secret_file}" 2>/dev/null)" || {
secret_validation_error "${input_class} permissions" "${quiet}"
return "${ERR_SECRET_PATH:-216}"
}
link_count="$(secure_stat -c '%h' "${secret_file}" 2>/dev/null)" || {
secret_validation_error "${input_class} link count" "${quiet}"
return "${ERR_SECRET_PATH:-216}"
}
if [[ "${owner}" != "${EUID}" || "${link_count}" != "1" || ( "${mode}" != "400" && "${mode}" != "600" ) ]]; then
secret_validation_error "${input_class} ownership, permissions, or link count" "${quiet}"
return "${ERR_SECRET_PATH:-216}"
fi
if [[ "${register_value}" == "true" ]]; then
register_secret_file_for_redaction "${secret_file}"
fi
return 0
}
### Prevents accidental 'unset -f'.
# shellcheck disable=SC2034
readonly -f validate_secret_file
#######################################
# Validates an explicitly supported absolute secret file path.
# Arguments:
# 1: file path
# 2: input class
# Returns:
# 0: on success
# ERR_SECRET_PATH: on failure
#######################################
validate_secret_absolute_file() {
declare secret_file="$1" input_class="${2:-absolute secret file}" resolved_file=""
if [[ -z "${secret_file}" || "${secret_file}" != /* ]]; then
secret_validation_error "${input_class} must be an absolute path"
return "${ERR_SECRET_PATH:-216}"
fi
resolved_file="$(realpath "${secret_file}" 2>/dev/null)" || {
secret_validation_error "${input_class} cannot be resolved"
return "${ERR_SECRET_PATH:-216}"
}
if [[ "${resolved_file}" != "${secret_file}" ]]; then
secret_validation_error "${input_class} must be canonical and must not traverse symlinked parents"
return "${ERR_SECRET_PATH:-216}"
fi
validate_secret_file "${secret_file}" "${input_class}" || return "${ERR_SECRET_PATH:-216}"
return 0
}
### Prevents accidental 'unset -f'.
# shellcheck disable=SC2034
readonly -f validate_secret_absolute_file
#######################################
# Validates an explicitly supported absolute secret directory path.
# Arguments:
# 1: directory path
# 2: input class
# Returns:
# 0: on success
# ERR_SECRET_PATH: on failure
#######################################
validate_secret_absolute_directory() {
declare directory="$1" input_class="${2:-absolute secret directory}" resolved_directory=""
if [[ -z "${directory}" || "${directory}" != /* ]]; then
secret_validation_error "${input_class} must be an absolute path"
return "${ERR_SECRET_PATH:-216}"
fi
resolved_directory="$(realpath "${directory}" 2>/dev/null)" || {
secret_validation_error "${input_class} cannot be resolved"
return "${ERR_SECRET_PATH:-216}"
}
if [[ "${resolved_directory}" != "${directory}" ]]; then
secret_validation_error "${input_class} must be canonical and must not traverse symlinked parents"
return "${ERR_SECRET_PATH:-216}"
fi
validate_secret_directory "${directory}" "${input_class}" || return "${ERR_SECRET_PATH:-216}"
return 0
}
### Prevents accidental 'unset -f'.
# shellcheck disable=SC2034
readonly -f validate_secret_absolute_directory
#######################################
# Validates a filename-only secret file below the fixed secret root.
# Globals:
# VAR_TMP_SECRET
# Arguments:
# 1: filename
# 2: input class
# 3: register for redaction
# Returns:
# 0: on success
# ERR_SECRET_PATH: on failure
#######################################
validate_secret_file_in_root() {
declare filename="$1" input_class="${2:-secret file}" register_value="${3:-true}"
validate_secret_directory "${VAR_TMP_SECRET}" "secret root" "true" || return "${ERR_SECRET_PATH:-216}"
validate_secret_filename "${filename}" "${input_class} filename" || return "${ERR_SECRET_PATH:-216}"
validate_secret_file "${VAR_TMP_SECRET}/${filename}" "${input_class}" "${register_value}" || return "${ERR_SECRET_PATH:-216}"
return 0
}
### Prevents accidental 'unset -f'.
# shellcheck disable=SC2034
readonly -f validate_secret_file_in_root
#######################################
# Validates the fixed tmpfs secret staging area and all entries below it.
# Globals:
# VAR_TMP_SECRET
# Arguments:
# 1: quiet flag
# Returns:
# 0: on success
# ERR_SECRET_PATH: on failure
#######################################
validate_secret_staging_area() {
declare quiet="${1:-false}" secret_entries_file="" secret_entry=""
declare -a secret_entries=()
validate_secret_directory "${VAR_TMP_SECRET}" "secret root" "true" "${quiet}" || return "${ERR_SECRET_PATH:-216}"
secret_entries_file="$(mktemp)" || return "${ERR_SECRET_PATH:-216}"
if ! find "${VAR_TMP_SECRET}" -xdev -mindepth 1 -print0 >| "${secret_entries_file}"; then
rm -f "${secret_entries_file}"
secret_validation_error "secret-root enumeration failed" "${quiet}"
return "${ERR_SECRET_PATH:-216}"
fi
mapfile -d '' -t secret_entries < "${secret_entries_file}"
rm -f "${secret_entries_file}"
for secret_entry in "${secret_entries[@]}"; do
if [[ -L "${secret_entry}" ]]; then
secret_validation_error "symlink below secret root" "${quiet}"
return "${ERR_SECRET_PATH:-216}"
elif [[ -d "${secret_entry}" ]]; then
validate_secret_directory "${secret_entry}" "directory below secret root" "false" "${quiet}" \
|| return "${ERR_SECRET_PATH:-216}"
elif [[ -f "${secret_entry}" ]]; then
validate_secret_file "${secret_entry}" "file below secret root" "true" "${quiet}" || return "${ERR_SECRET_PATH:-216}"
else
secret_validation_error "non-regular entry below secret root" "${quiet}"
return "${ERR_SECRET_PATH:-216}"
fi
done
return 0
}
### Prevents accidental 'unset -f'.
# shellcheck disable=SC2034
readonly -f validate_secret_staging_area
# vim: number et ts=2 sw=2 sts=2 ai tw=128 ft=sh
+3 -8
View File
@@ -49,6 +49,8 @@ trap_on_exit() {
print_scr_exit "${errcode}" print_scr_exit "${errcode}"
exit "${errcode}"
else else
if [[ "${ERRTRAP}" != "true" ]]; then if [[ "${ERRTRAP}" != "true" ]]; then
@@ -61,16 +63,9 @@ trap_on_exit() {
fi fi
fi exit "${errcode}"
if ! sanitize_debug_logs; then
printf "\e[93m⚠ Final debug-log sanitisation failed; preserving original exit status %s. \e[0m\n" "${errcode}" >&2
if [[ -n "${LOG_ERROR:-}" && -f "${LOG_ERROR}" && ! -L "${LOG_ERROR}" ]]; then
printf "⚠ Final debug-log sanitisation failed; original exit status: %s.\n" "${errcode}" >> "${LOG_ERROR}" || true
fi
fi fi
exit "${errcode}"
} }
### Prevents accidental 'unset -f'. ### Prevents accidental 'unset -f'.
# shellcheck disable=SC2034 # shellcheck disable=SC2034
+2 -13
View File
@@ -67,10 +67,6 @@ usage() {
echo echo
echo -e "\e[97m --build-directory </path/to/build_directory> \e[0m" echo -e "\e[97m --build-directory </path/to/build_directory> \e[0m"
echo " Where the Debian Live Build Image should be generated. RECOMMENDED path: </opt/cdlb>" echo " Where the Debian Live Build Image should be generated. RECOMMENDED path: </opt/cdlb>"
echo " The path MUST be canonical and dedicated to the builder; a new directory's canonical parent MUST already exist."
echo " New or empty directories receive the"
echo " '.ciss-live-builder-owned' marker; populated unmarked directories are rejected. Cleanup is intentionally"
echo " destructive only inside the exact validated marker-owned directory."
echo " MUST be provided." echo " MUST be provided."
echo echo
echo -e "\e[97m --change-splash <STRING> one of <club | hexagon> \e[0m" echo -e "\e[97m --change-splash <STRING> one of <club | hexagon> \e[0m"
@@ -91,7 +87,6 @@ usage() {
echo -e "\e[97m --debug, -d \e[0m" echo -e "\e[97m --debug, -d \e[0m"
echo " Enables debug logging for the main program routine. Detailed logging information are written to:" echo " Enables debug logging for the main program routine. Detailed logging information are written to:"
echo " </tmp/ciss_live_builder_$$.log>" echo " </tmp/ciss_live_builder_$$.log>"
echo " A final exact-value sanitisation pass is defence in depth and does not replace careful tracing discipline."
echo echo
echo -e "\e[97m --dhcp-centurion \e[0m" echo -e "\e[97m --dhcp-centurion \e[0m"
echo " If a DHCP lease is provided, the provider's name server will be overridden and the hardened, privacy-focused " echo " If a DHCP lease is provided, the provider's name server will be overridden and the hardened, privacy-focused "
@@ -113,13 +108,11 @@ usage() {
echo echo
echo -e "\e[97m --key_age=* \e[0m" echo -e "\e[97m --key_age=* \e[0m"
echo " The SOPS AGE private keyring for decryption operations. Change '*' to your desired SOPS AGE key file." echo " The SOPS AGE private keyring for decryption operations. Change '*' to your desired SOPS AGE key file."
echo " '*' MUST be a filename only without slashes, '.' or '..' traversal."
echo " File MUST be placed in:" echo " File MUST be placed in:"
echo " </dev/shm/cdlb_secrets>" echo " </dev/shm/cdlb_secrets>"
echo echo
echo -e "\e[97m --key_luks=* \e[0m" echo -e "\e[97m --key_luks=* \e[0m"
echo " The LUKS encryption / decryption passphrase for '/'-fs-encryption. Change '*' to your desired passphrase file." echo " The LUKS encryption / decryption passphrase for '/'-fs-encryption. Change '*' to your desired passphrase file."
echo " '*' MUST be a filename only without slashes, '.' or '..' traversal."
echo " File MUST be placed in:" echo " File MUST be placed in:"
echo " </dev/shm/cdlb_secrets>" echo " </dev/shm/cdlb_secrets>"
echo echo
@@ -169,7 +162,7 @@ usage() {
echo -e "\e[97m --root-password-file </dev/shm/cdlb_secrets/password.txt>> \e[0m" echo -e "\e[97m --root-password-file </dev/shm/cdlb_secrets/password.txt>> \e[0m"
echo " Password file for 'root', if given, MUST be a string of 42 to 64 characters." echo " Password file for 'root', if given, MUST be a string of 42 to 64 characters."
echo " If the argument is omitted, no further login authentication is required for the local console." echo " If the argument is omitted, no further login authentication is required for the local console."
echo " Safe absolute paths remain supported and are validated separately. RECOMMENDED path:" echo " MUST be placed in:"
echo " </dev/shm/cdlb_secrets/password.txt>" echo " </dev/shm/cdlb_secrets/password.txt>"
echo echo
echo -e "\e[97m --secure-boot-profile <STRING> one of <debian-shim | ciss-uki> \e[0m" echo -e "\e[97m --secure-boot-profile <STRING> one of <debian-shim | ciss-uki> \e[0m"
@@ -185,8 +178,7 @@ usage() {
echo " specified via '--signing_key=*'. If the keyring is protected, then provide the passphrase in its own file." echo " specified via '--signing_key=*'. If the keyring is protected, then provide the passphrase in its own file."
echo " Specify the fingerprint of the key to use via '--signing_key_fpr=*'." echo " Specify the fingerprint of the key to use via '--signing_key_fpr=*'."
echo " Optionally import an offline GPG CA signing public key via: '--signing_ca=*'." echo " Optionally import an offline GPG CA signing public key via: '--signing_ca=*'."
echo " Change '*' to your desired filename-only files / fingerprint. Filename-only values MUST NOT contain slashes" echo " Change '*' to your desired files / fingerprint. Files MUST be placed in:"
echo " or traversal. Files MUST be placed in:"
echo " </dev/shm/cdlb_secrets>" echo " </dev/shm/cdlb_secrets>"
echo echo
echo -e "\e[97m --sops-version <STRING> \e[0m" echo -e "\e[97m --sops-version <STRING> \e[0m"
@@ -220,9 +212,6 @@ usage() {
echo echo
echo -e "\e[93m💡 Notes: \e[0m" echo -e "\e[93m💡 Notes: \e[0m"
echo -e "\e[93m🔵 You MUST be 'root' to run this script. \e[0m" echo -e "\e[93m🔵 You MUST be 'root' to run this script. \e[0m"
echo -e "\e[93m🔵 Private operator control does not remove the requirement for strict local secret path validation. \e[0m"
echo -e "\e[93m🔵 '/dev/shm/cdlb_secrets' MUST be tmpfs-backed, root-owned, mode 0700, and contain only \e[0m"
echo -e "\e[93m single-link regular secret files with mode 0400 or 0600. Secure deletion with shred is best-effort only. \e[0m"
echo echo
echo -e "\e[95m💷 Please consider donating to my work at: \e[0m" echo -e "\e[95m💷 Please consider donating to my work at: \e[0m"
echo -e "\e[95m🌐 https://coresecret.eu/spenden/ \e[0m" echo -e "\e[95m🌐 https://coresecret.eu/spenden/ \e[0m"
@@ -48,9 +48,20 @@ Verify_checksums() {
_KEYFILE="" _KEYFILE=""
_MANIFEST_FOUND="false"
_MP="" _MP=""
_RETURN_PGP=""
_RETURN_SHA=""
_VERIFICATION_EXECUTED="false"
_VERIFICATION_SUCCEEDED="false"
### Parse commandline arguments ---------------------------------------------------------------------------------------------- ### Parse commandline arguments ----------------------------------------------------------------------------------------------
# shellcheck disable=SC2154
for _PARAMETER in ${LIVE_BOOT_CMDLINE}; do for _PARAMETER in ${LIVE_BOOT_CMDLINE}; do
case "${_PARAMETER}" in case "${_PARAMETER}" in
@@ -203,10 +214,12 @@ Verify_checksums() {
if [ -e "${_CHECKSUM}" ]; then if [ -e "${_CHECKSUM}" ]; then
_MANIFEST_FOUND="true"
printf "\e[95m[INFO] Found: [%s] ... \n\e[0m" "${_CHECKSUM}" printf "\e[95m[INFO] Found: [%s] ... \n\e[0m" "${_CHECKSUM}"
if [ -e "/usr/bin/${_DIGEST}sum" ]; then if [ -e "/usr/bin/${_DIGEST}sum" ]; then
_VERIFICATION_EXECUTED="true"
printf "\e[95m[INFO] Found: [%s] ... \n\e[0m" "/usr/bin/${_DIGEST}sum" printf "\e[95m[INFO] Found: [%s] ... \n\e[0m" "/usr/bin/${_DIGEST}sum"
if [ "${LIVE_VERIFY_CHECKSUMS_SIGNATURES}" = "true" ]; then if [ "${LIVE_VERIFY_CHECKSUMS_SIGNATURES}" = "true" ]; then
@@ -237,6 +250,7 @@ Verify_checksums() {
if grep -v '^#' "${_CHECKSUM}" | /usr/bin/"${_DIGEST}"sum -c > "${_TTY}"; then if grep -v '^#' "${_CHECKSUM}" | /usr/bin/"${_DIGEST}"sum -c > "${_TTY}"; then
_RETURN_SHA="${?}" _RETURN_SHA="${?}"
_VERIFICATION_SUCCEEDED="true"
printf "\e[92m[INFO] Found: [%s] successful verified: [%s] \n\e[0m" "/usr/bin/${_DIGEST}sum" "${_CHECKSUM}" printf "\e[92m[INFO] Found: [%s] successful verified: [%s] \n\e[0m" "/usr/bin/${_DIGEST}sum" "${_CHECKSUM}"
else else
@@ -265,6 +279,33 @@ Verify_checksums() {
log_end_msg log_end_msg
printf "\n" printf "\n"
if [ "${_MANIFEST_FOUND}" != "true" ]; then
printf "\e[91m[FATAL] No supported checksum manifest found. \n\e[0m"
sleep 8
panic "No supported checksum manifest found."
return 1
fi
if [ "${_VERIFICATION_EXECUTED}" != "true" ]; then
printf "\e[91m[FATAL] No supported checksum verification tool was available. \n\e[0m"
sleep 8
panic "No supported checksum verification tool was available."
return 1
fi
if [ "${_VERIFICATION_SUCCEEDED}" != "true" ]; then
printf "\e[91m[FATAL] No supported checksum manifest was verified successfully. \n\e[0m"
sleep 8
panic "No supported checksum manifest was verified successfully."
return 1
fi
case "${_RETURN_PGP},${_RETURN_SHA}" in case "${_RETURN_PGP},${_RETURN_SHA}" in
"0,0") "0,0")
@@ -288,6 +329,7 @@ Verify_checksums() {
printf "\e[91m[FATAL] CDLB modified: [%s] done. \n\e[0m" "${CDLB_SCRIPT_FULL}" printf "\e[91m[FATAL] CDLB modified: [%s] done. \n\e[0m" "${CDLB_SCRIPT_FULL}"
sleep 8 sleep 8
panic "Verification of [GPG signature] file successful, while verification of [sha checksum] file failed." panic "Verification of [GPG signature] file successful, while verification of [sha checksum] file failed."
return 1
;; ;;
*",0") *",0")
@@ -295,6 +337,7 @@ Verify_checksums() {
printf "\e[91m[FATAL] CDLB modified: [%s] done. \n\e[0m" "${CDLB_SCRIPT_FULL}" printf "\e[91m[FATAL] CDLB modified: [%s] done. \n\e[0m" "${CDLB_SCRIPT_FULL}"
sleep 8 sleep 8
panic "Verification of [GPG signature] file failed, while verification of [sha checksum] file successful." panic "Verification of [GPG signature] file failed, while verification of [sha checksum] file successful."
return 1
;; ;;
"na,"*) "na,"*)
@@ -302,6 +345,14 @@ Verify_checksums() {
printf "\e[91m[FATAL] CDLB modified: [%s] done. \n\e[0m" "${CDLB_SCRIPT_FULL}" printf "\e[91m[FATAL] CDLB modified: [%s] done. \n\e[0m" "${CDLB_SCRIPT_FULL}"
sleep 8 sleep 8
panic "Verification of checksum file failed." panic "Verification of checksum file failed."
return 1
;;
*)
printf "\e[91m[FATAL] Checksum verification ended in an unsupported state. \n\e[0m"
sleep 8
panic "Checksum verification ended in an unsupported state."
return 1
;; ;;
esac esac
+228
View File
@@ -0,0 +1,228 @@
#!/bin/bash
# SPDX-Version: 3.0
# SPDX-FileCopyrightText: 2026; WEIDNER, Marc S.; <msw@coresecret.dev>
# SPDX-FileType: SOURCE
# SPDX-License-Identifier: LicenseRef-CNCL-1.1 OR LicenseRef-CCLA-1.1
# SPDX-PackageName: CISS.debian.live.builder
set -Ceuo pipefail
declare ROOT_DIR=""
declare SHA512SUM=""
declare SHASUM=""
declare TMP_BASE=""
declare TMP_ROOT=""
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
SHA512SUM="$(command -v sha512sum)"
SHASUM="$(command -v shasum || true)"
TMP_BASE="${TMPDIR:-/tmp}"
TMP_BASE="${TMP_BASE%/}"
TMP_ROOT="$(mktemp -d "${TMP_BASE}/ciss-boot-attestation.XXXXXXXX")"
cleanup() {
case "${TMP_ROOT}" in
"${TMP_BASE}/ciss-boot-attestation."*)
rm -rf -- "${TMP_ROOT}"
;;
*)
printf 'Refusing to clean unexpected test path: %s\n' "${TMP_ROOT}" >&2
return 1
;;
esac
}
trap cleanup EXIT
fail() {
printf 'FAIL: %s\n' "${1}" >&2
exit 1
}
prepare_checksum_hook() {
declare source_hook="${1}"
declare target_hook="${2}"
declare fault_mode="${3}"
declare test_bin="${4}"
declare test_tty="${5}"
declare test_runtime=""
test_runtime="$(dirname "${test_tty}")"
sed \
-e "s|/usr/bin/|${test_bin}/|g" \
-e "s|/run/ciss-|${test_runtime}/ciss-|g" \
-e "s|_TTY=\"/dev/tty8\"|_TTY=\"${test_tty}\"|" \
"${source_hook}" > "${target_hook}"
if [[ "${fault_mode}" == "unknown-state" ]]; then
sed \
-e 's/_RETURN_PGP="na"/_RETURN_PGP="unknown"/' \
-e 's/_RETURN_SHA="${?}"/_RETURN_SHA="unknown"/g' \
"${target_hook}" > "${target_hook}.new"
mv "${target_hook}.new" "${target_hook}"
fi
}
install_test_sha512sum() {
declare test_bin="${1}"
if printf '' | "${SHA512SUM}" -c >/dev/null 2>&1; then
ln -s "${SHA512SUM}" "${test_bin}/sha512sum"
elif [[ -n "${SHASUM}" ]]; then
printf '#!/bin/sh\nexec "%s" -a 512 "$@"\n' "${SHASUM}" > "${test_bin}/sha512sum"
chmod 0755 "${test_bin}/sha512sum"
else
fail "No SHA-512 tool with checksum verification support is available."
fi
}
run_checksum_case() {
declare source_hook="${1}"
declare case_name="${2}"
declare expected_status="${3}"
declare expected_message="${4}"
declare case_dir=""
declare hook_copy=""
declare output_file=""
declare test_bin=""
declare test_tty=""
declare source_id=""
declare fault_mode=""
declare panic_returns="false"
declare status=0
source_id="$(printf '%s' "${source_hook#"${ROOT_DIR}"/}" | tr '/' '_')"
case_dir="${TMP_ROOT}/${source_id}-${case_name}"
hook_copy="${case_dir}/0030-ciss-verify-checksums"
output_file="${case_dir}/output.log"
test_bin="${case_dir}/bin"
test_tty="${case_dir}/tty.log"
mkdir -p "${case_dir}" "${test_bin}"
: >| "${test_tty}"
case "${case_name}" in
valid)
install_test_sha512sum "${test_bin}"
printf 'trusted rootfs payload\n' > "${case_dir}/payload"
(cd "${case_dir}" && "${test_bin}/sha512sum" payload > sha512sum.txt)
;;
missing-manifest)
install_test_sha512sum "${test_bin}"
;;
unsupported-manifest)
install_test_sha512sum "${test_bin}"
printf 'unsupported\n' > "${case_dir}/md5sum.txt"
;;
failed-checksum)
install_test_sha512sum "${test_bin}"
printf 'trusted rootfs payload\n' > "${case_dir}/payload"
(cd "${case_dir}" && "${test_bin}/sha512sum" payload > sha512sum.txt)
printf 'tampered rootfs payload\n' >| "${case_dir}/payload"
;;
missing-tool)
install_test_sha512sum "${test_bin}"
printf 'trusted rootfs payload\n' > "${case_dir}/payload"
(cd "${case_dir}" && "${test_bin}/sha512sum" payload > sha512sum.txt)
rm -f "${test_bin}/sha512sum"
;;
unknown-state)
fault_mode="unknown-state"
panic_returns="true"
install_test_sha512sum "${test_bin}"
printf 'trusted rootfs payload\n' > "${case_dir}/payload"
(cd "${case_dir}" && "${test_bin}/sha512sum" payload > sha512sum.txt)
;;
*)
fail "Unknown checksum test case: ${case_name}"
;;
esac
prepare_checksum_hook "${source_hook}" "${hook_copy}" "${fault_mode}" "${test_bin}" "${test_tty}"
set +e
# shellcheck disable=SC2034,SC2329
(
set +C
CDLB_SCRIPT_FULL="${hook_copy}"
LIVE_BOOT_CMDLINE="verify-checksums=sha512"
LIVE_VERIFY_CHECKSUMS=""
log_begin_msg() { :; }
log_end_msg() { :; }
log_success_msg() { :; }
panic() {
printf 'PANIC: %s\n' "${1}" >&2
if [[ "${panic_returns}" == "true" ]]; then
return 0
fi
exit 97
}
sleep() { :; }
# shellcheck source=/dev/null
. "${hook_copy}"
Verify_checksums "${case_dir}"
) > "${output_file}" 2>&1
status="${?}"
set -e
[[ "${status}" -eq "${expected_status}" ]] || {
cat "${output_file}" >&2
fail "${source_hook} ${case_name}: expected status ${expected_status}, got ${status}"
}
grep -Fq "${expected_message}" "${output_file}" || {
cat "${output_file}" >&2
fail "${source_hook} ${case_name}: missing expected message '${expected_message}'"
}
}
test_rootfs_payload_tamper() {
declare case_dir="${TMP_ROOT}/rootfs-payload-tamper"
declare test_bin="${case_dir}/bin"
declare payload="${case_dir}/crypt_liveiso"
declare manifest="${case_dir}/ciss_rootfs.crypt.decrypted.sha512sum.txt"
declare build_hook="${ROOT_DIR}/config/hooks/live/zzzz_ciss_crypt_squash.hook.binary"
declare boot_hook="${ROOT_DIR}/config/includes.chroot/usr/lib/live/boot/0042_ciss_post_decrypt_attest"
mkdir -p "${case_dir}" "${test_bin}"
install_test_sha512sum "${test_bin}"
printf 'trusted selected rootfs mapper payload\n' > "${payload}"
"${test_bin}/sha512sum" "${payload}" > "${manifest}"
"${test_bin}/sha512sum" -c --strict --quiet "${manifest}"
printf 'tampered selected rootfs mapper payload\n' >> "${payload}"
if "${test_bin}/sha512sum" -c --strict --quiet "${manifest}" >/dev/null 2>&1; then
fail "Modified selected rootfs payload unexpectedly passed checksum verification."
fi
# shellcheck disable=SC2016
grep -Fq 'sha512sum "${MAPPER_DEV}" >| "${ROOTFS_ATTESTATION}"' "${build_hook}" || \
fail "Build hook does not generate the attestation from the decrypted mapper."
# shellcheck disable=SC2016
grep -Fq '/usr/bin/sha512sum -c --strict --quiet "${HASH_FILE}"' "${boot_hook}" || \
fail "Boot hook does not verify the selected rootfs payload with sha512sum -c."
# shellcheck disable=SC2016
grep -Fq '[ "${_ATTESTED_PAYLOAD}" != "${CDLB_MAPPER_DEV}" ]' "${boot_hook}" || \
fail "Boot hook does not require the manifest to target the selected rootfs payload."
}
declare -a CHECKSUM_HOOKS=(
"${ROOT_DIR}/config/includes.chroot/usr/lib/live/boot/0030-ciss-verify-checksums"
"${ROOT_DIR}/scripts/usr/lib/live/boot/0030-ciss-verify-checksums"
)
declare checksum_hook=""
for checksum_hook in "${CHECKSUM_HOOKS[@]}"; do
run_checksum_case "${checksum_hook}" "valid" 0 "Verification of [sha checksum] file successful"
run_checksum_case "${checksum_hook}" "missing-manifest" 97 "No supported checksum manifest found."
run_checksum_case "${checksum_hook}" "unsupported-manifest" 97 "No supported checksum manifest found."
run_checksum_case "${checksum_hook}" "failed-checksum" 97 "No supported checksum manifest was verified successfully."
run_checksum_case "${checksum_hook}" "missing-tool" 97 "No supported checksum verification tool was available."
run_checksum_case "${checksum_hook}" "unknown-state" 1 "Checksum verification ended in an unsupported state."
done
test_rootfs_payload_tamper
printf 'PASS: checksum verification fails closed and modified rootfs payloads fail attestation.\n'
-191
View File
@@ -1,191 +0,0 @@
#!/bin/bash
# SPDX-Version: 3.0
# SPDX-CreationInfo: 2026-06-11; 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
# shellcheck disable=SC1091,SC2034
declare TEST_ROOT=""
declare TEST_TMP=""
TEST_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
TEST_TMP="$(realpath "$(mktemp -d)")"
readonly TEST_ROOT TEST_TMP
declare -r ERR_BUILD_PATH=217
declare -r ERR_GUARD_SRCE=131
declare -r ERR_SANITIZING=133
declare -r ERR_SECRET_PATH=216
declare -r VAR_WORKDIR="${TEST_ROOT}"
declare VAR_TMP_SECRET="${TEST_TMP}/secret-root"
declare LOG_DEBUG=""
declare LOG_ERROR=""
declare LOG_VAR=""
declare VAR_EARLY_DEBUG="false"
declare ERRTRAP="true"
cleanup_test() {
chmod -R u+rwX "${TEST_TMP}" 2>/dev/null || true
rm -rf "${TEST_TMP}"
}
trap cleanup_test EXIT
guard_sourcing() {
return 0
}
# shellcheck source=../lib/lib_secret_validation.sh
. "${TEST_ROOT}/lib/lib_secret_validation.sh"
# shellcheck source=../lib/lib_build_directory.sh
. "${TEST_ROOT}/lib/lib_build_directory.sh"
# shellcheck source=../lib/lib_debug_sanitizer.sh
. "${TEST_ROOT}/lib/lib_debug_sanitizer.sh"
# shellcheck source=../lib/lib_trap_on_exit.sh
. "${TEST_ROOT}/lib/lib_trap_on_exit.sh"
fail() {
printf 'FAIL: %s\n' "$1" >&2
exit 1
}
expect_failure() {
declare description="$1"
shift
if "$@" >/dev/null 2>&1; then
fail "${description}"
fi
}
mkdir -m 0700 "${VAR_TMP_SECRET}"
printf 'safe-private-value\n' > "${VAR_TMP_SECRET}/safe.txt"
chmod 0400 "${VAR_TMP_SECRET}/safe.txt"
validate_secret_directory "${VAR_TMP_SECRET}" "test secret root" "false" || fail "safe secret root rejected"
validate_secret_filename "safe.txt" "test secret filename" || fail "safe secret filename rejected"
validate_secret_file "${VAR_TMP_SECRET}/safe.txt" "test secret file" || fail "safe secret file rejected"
validate_secret_absolute_file "${VAR_TMP_SECRET}/safe.txt" "test absolute secret file" \
|| fail "safe absolute secret file rejected"
expect_failure "relative external secret path accepted" \
validate_secret_absolute_file "secret-root/safe.txt" "test absolute secret file"
mkdir -m 0700 "${TEST_TMP}/external-secret-dir"
printf 'external-private-value\n' > "${TEST_TMP}/external-secret-dir/external.txt"
chmod 0400 "${TEST_TMP}/external-secret-dir/external.txt"
ln -s "${TEST_TMP}/external-secret-dir" "${TEST_TMP}/external-secret-dir-link"
expect_failure "external secret file through a symlinked parent accepted" \
validate_secret_absolute_file "${TEST_TMP}/external-secret-dir-link/external.txt" "test absolute secret file"
expect_failure "external secret directory through a symlinked parent accepted" \
validate_secret_absolute_directory "${TEST_TMP}/external-secret-dir-link" "test absolute secret directory"
declare secret_root_fs=""
secret_root_fs="$(secure_stat -f -c '%T' "${VAR_TMP_SECRET}")"
if [[ "${secret_root_fs}" != "tmpfs" && "${secret_root_fs}" != "ramfs" ]]; then
expect_failure "persistent secret staging area accepted" validate_secret_staging_area
fi
expect_failure "secret filename traversal accepted" validate_secret_filename "../safe.txt" "test secret filename"
expect_failure "absolute filename-only secret accepted" validate_secret_filename "/tmp/safe.txt" "test secret filename"
expect_failure "slash in filename-only secret accepted" validate_secret_filename "subdir/safe.txt" "test secret filename"
ln -s "${VAR_TMP_SECRET}" "${TEST_TMP}/secret-root-link"
expect_failure "secret-root symlink accepted" \
validate_secret_directory "${TEST_TMP}/secret-root-link" "test secret root" "false"
mkdir -m 0700 "${TEST_TMP}/unsafe-secret-root-mode"
chmod 0755 "${TEST_TMP}/unsafe-secret-root-mode"
expect_failure "broad secret-root permissions accepted" \
validate_secret_directory "${TEST_TMP}/unsafe-secret-root-mode" "test secret root" "false"
ln -s "${VAR_TMP_SECRET}/safe.txt" "${VAR_TMP_SECRET}/unsafe-link"
expect_failure "secret-file symlink accepted" validate_secret_file "${VAR_TMP_SECRET}/unsafe-link" "test secret file"
rm "${VAR_TMP_SECRET}/unsafe-link"
ln "${VAR_TMP_SECRET}/safe.txt" "${VAR_TMP_SECRET}/unsafe-hardlink"
expect_failure "hardlinked secret file accepted" validate_secret_file "${VAR_TMP_SECRET}/safe.txt" "test secret file"
rm "${VAR_TMP_SECRET}/unsafe-hardlink"
declare fake_secret='CISS-CANARY-[exact]-value'
declare expected_redaction=""
declare sanitisation_status=0
printf -v expected_redaction '%*s' "${#fake_secret}" ''
expected_redaction="${expected_redaction// /*}"
register_secret_value "${fake_secret}"
LOG_DEBUG="${TEST_TMP}/debug.log"
LOG_VAR="${TEST_TMP}/var.log"
LOG_ERROR="${TEST_TMP}/error.log"
printf 'before %s after\nunrelated line\n' "${fake_secret}" > "${LOG_DEBUG}"
printf 'unrelated vars\n' > "${LOG_VAR}"
printf 'unrelated error\n' > "${LOG_ERROR}"
chmod 0600 "${LOG_DEBUG}" "${LOG_VAR}" "${LOG_ERROR}"
sanitize_debug_logs || fail "debug-log sanitisation failed"
grep -Fq "${fake_secret}" "${LOG_DEBUG}" && fail "debug-log canary remained"
grep -Fq "${expected_redaction}" "${LOG_DEBUG}" || fail "expected exact-value redaction missing"
grep -Fq 'unrelated line' "${LOG_DEBUG}" || fail "unrelated debug content changed"
ln -s "${LOG_DEBUG}" "${TEST_TMP}/unsafe-debug-link"
(
LOG_DEBUG="${TEST_TMP}/unsafe-debug-link"
trap_on_exit 73 "test" 1 "test" "false"
) 2>/dev/null || sanitisation_status=$?
[[ ${sanitisation_status} -eq 73 ]] || fail "sanitisation failure masked the original exit status"
expect_failure "empty build-directory path accepted" validate_build_directory_path ""
expect_failure "root build-directory path accepted" validate_build_directory_path "/"
expect_failure "broad parent build-directory path accepted" validate_build_directory_path "/tmp"
expect_failure "secret root accepted as build directory" validate_build_directory_path "${VAR_TMP_SECRET}"
mkdir -m 0700 "${VAR_TMP_SECRET}/unsafe-build-child"
expect_failure "secret-root descendant accepted as build directory" \
validate_build_directory_path "${VAR_TMP_SECRET}/unsafe-build-child"
mkdir -m 0700 "${TEST_TMP}/unmarked"
expect_failure "build directory without marker accepted" validate_build_directory_marker "${TEST_TMP}/unmarked"
printf 'do-not-adopt\n' > "${TEST_TMP}/unmarked/content"
expect_failure "non-empty unmarked build directory adopted" initialize_build_directory "${TEST_TMP}/unmarked" unsafe_result
mkdir -m 0700 "${TEST_TMP}/unsafe-build-mode"
chmod 0777 "${TEST_TMP}/unsafe-build-mode"
expect_failure "unsafe build-directory permissions accepted" \
initialize_build_directory "${TEST_TMP}/unsafe-build-mode" unsafe_result
mkdir -m 0700 "${TEST_TMP}/marker-link-dir"
printf '%s\n' "${TEST_TMP}/marker-link-dir" > "${TEST_TMP}/marker-target"
chmod 0400 "${TEST_TMP}/marker-target"
ln -s "${TEST_TMP}/marker-target" "${TEST_TMP}/marker-link-dir/.ciss-live-builder-owned"
expect_failure "symlinked builder-owned marker accepted" validate_build_directory_marker "${TEST_TMP}/marker-link-dir"
mkdir -m 0700 "${TEST_TMP}/marker-hardlink-dir"
printf '%s\n' "${TEST_TMP}/marker-hardlink-dir" > "${TEST_TMP}/marker-hardlink-target"
chmod 0400 "${TEST_TMP}/marker-hardlink-target"
ln "${TEST_TMP}/marker-hardlink-target" "${TEST_TMP}/marker-hardlink-dir/.ciss-live-builder-owned"
expect_failure "hardlinked builder-owned marker accepted" validate_build_directory_marker "${TEST_TMP}/marker-hardlink-dir"
mkdir -m 0700 "${TEST_TMP}/marker-extra-content-dir"
printf '%s\nunexpected\n' "${TEST_TMP}/marker-extra-content-dir" \
> "${TEST_TMP}/marker-extra-content-dir/.ciss-live-builder-owned"
chmod 0400 "${TEST_TMP}/marker-extra-content-dir/.ciss-live-builder-owned"
expect_failure "builder-owned marker with extra content accepted" \
validate_build_directory_marker "${TEST_TMP}/marker-extra-content-dir"
ln -s "${TEST_TMP}/unmarked" "${TEST_TMP}/build-link"
expect_failure "build-directory symlink accepted" validate_build_directory_path "${TEST_TMP}/build-link"
declare validated_build_dir=""
initialize_build_directory "${TEST_TMP}/owned-build" validated_build_dir || fail "safe builder-owned directory rejected"
validate_build_directory_marker "${validated_build_dir}" || fail "builder marker rejected"
mkdir "${validated_build_dir}/subdir"
printf 'remove me\n' > "${validated_build_dir}/artifact"
printf 'remove me too\n' > "${validated_build_dir}/.hidden-artifact"
printf 'nested\n' > "${validated_build_dir}/subdir/nested"
clean_build_directory_contents "${validated_build_dir}" || fail "safe builder-owned cleanup failed"
validate_build_directory_marker "${validated_build_dir}" || fail "builder marker removed by cleanup"
[[ -z "$(find "${validated_build_dir}" -mindepth 1 ! -name '.ciss-live-builder-owned' -print -quit)" ]] \
|| fail "builder-owned cleanup left unexpected content"
mkdir -m 0700 "${TEST_TMP}/outside-build"
mkdir "${TEST_TMP}/outside-build/includes.chroot"
ln -s "${TEST_TMP}/outside-build" "${validated_build_dir}/config"
expect_failure "cleanup subpath through a symlinked parent accepted" \
validate_build_directory_subpath "${validated_build_dir}" "config/includes.chroot" unsafe_subpath
printf 'PASS: secret validation, debug sanitisation, and build cleanup guards\n'
-3
View File
@@ -28,7 +28,6 @@ touch "${LOG_ERROR}" && chmod 0600 "${LOG_ERROR}"
declare -g __umask="" declare -g __umask=""
declare -g VAR_ARCHITECTURE="" declare -g VAR_ARCHITECTURE=""
declare -g VAR_ARG_SANITIZED=""
declare -g VAR_DROPBEAR_VERSION="2026.91" declare -g VAR_DROPBEAR_VERSION="2026.91"
declare -g VAR_HANDLER_BUILD_DIR="" declare -g VAR_HANDLER_BUILD_DIR=""
declare -g VAR_HANDLER_CDI="false" declare -g VAR_HANDLER_CDI="false"
@@ -92,8 +91,6 @@ declare -gir ERR__SSH__PORT=212 # --ssh-port MUST be an integer between '1' and
declare -gir ERR_ARG_MSMTCH=213 # Wrong Number of optional Arguments provided declare -gir ERR_ARG_MSMTCH=213 # Wrong Number of optional Arguments provided
declare -gir ERR_DROPBEAR_V=214 # --dropbear-version MUST match the bundled Dropbear tarball version format declare -gir ERR_DROPBEAR_V=214 # --dropbear-version MUST match the bundled Dropbear tarball version format
declare -gir ERR__SOPS__VER=215 # --sops-version MUST match the upstream SOPS semantic version format declare -gir ERR__SOPS__VER=215 # --sops-version MUST match the upstream SOPS semantic version format
declare -gir ERR_SECRET_PATH=216 # Unsafe secret root, filename, or file.
declare -gir ERR_BUILD_PATH=217 # Unsafe build-directory path or marker.
declare -gir ERR_SECRETSSYM=251 # VAR_TMP_SECRET is a symlink. declare -gir ERR_SECRETSSYM=251 # VAR_TMP_SECRET is a symlink.
declare -gir ERR_NOTABSPATH=252 # Not an absolute path declare -gir ERR_NOTABSPATH=252 # Not an absolute path
declare -gir ERR_INVLD_CHAR=253 # Invalid Character declare -gir ERR_INVLD_CHAR=253 # Invalid Character