Compare commits
4 Commits
9ef535554a
...
6aeb9037ea
| Author | SHA256 | Date | |
|---|---|---|---|
|
6aeb9037ea
|
|||
|
eeecefc966
|
|||
|
42e2ae6b0e
|
|||
|
b78779e790
|
@@ -9,17 +9,90 @@
|
||||
# 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
|
||||
# shellcheck disable=SC2154
|
||||
set -Ceuo pipefail
|
||||
|
||||
# Final live-build binary hook for encrypted root filesystem packaging. Preallocate a LUKS2 container, formats it with the
|
||||
# generated build secret, copies the generated filesystem.squashfs into the opened encrypted mapping, then closes the container,
|
||||
# shreds the temporary LUKS secret, and removes the plaintext SquashFS from the ISO payload.
|
||||
# Final live-build binary hook for encrypted root filesystem packaging. Preallocate a LUKS2 container, format it with the
|
||||
# generated build secret, copy the generated filesystem.squashfs into the opened encrypted mapping, generate and sign a
|
||||
# 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}"
|
||||
|
||||
__umask=$(umask)
|
||||
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.
|
||||
# Globals:
|
||||
@@ -65,8 +138,25 @@ readonly -f preallocate
|
||||
|
||||
declare ROOTFS="${VAR_HANDLER_BUILD_DIR}/binary/live/filesystem.squashfs"
|
||||
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=""
|
||||
|
||||
require_command gpg
|
||||
require_command gpgv
|
||||
require_command sha512sum
|
||||
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"
|
||||
|
||||
[[ "${VAR_SIGNER:-false}" == "true" ]] || die "Rootfs attestation requires an enabled artifact signer."
|
||||
|
||||
rm -f -- "${ROOTFS_ATTESTATION}" "${ROOTFS_ATTESTATION_SIG}"
|
||||
|
||||
# shellcheck disable=SC2155
|
||||
declare -i VAR_ROOTFS_SIZE=$(stat -c%s -- "${ROOTFS}")
|
||||
|
||||
@@ -126,7 +216,7 @@ fi
|
||||
cryptsetup open --key-file "/proc/$$/fd/${KEYFD}" "${LUKSFS}" crypt_liveiso
|
||||
|
||||
# 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}"
|
||||
|
||||
if (( LUKS_FREE >= SQUASH_FS )); then
|
||||
@@ -140,8 +230,22 @@ else
|
||||
|
||||
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
|
||||
|
||||
# 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
|
||||
|
||||
exec {KEYFD}<&-
|
||||
|
||||
@@ -89,9 +89,20 @@ Verify_checksums() {
|
||||
|
||||
_KEYFILE=""
|
||||
|
||||
_MANIFEST_FOUND="false"
|
||||
|
||||
_MP=""
|
||||
|
||||
_RETURN_PGP=""
|
||||
|
||||
_RETURN_SHA=""
|
||||
|
||||
_VERIFICATION_EXECUTED="false"
|
||||
|
||||
_VERIFICATION_SUCCEEDED="false"
|
||||
|
||||
### Parse commandline arguments ----------------------------------------------------------------------------------------------
|
||||
# shellcheck disable=SC2154
|
||||
for _PARAMETER in ${LIVE_BOOT_CMDLINE}; do
|
||||
|
||||
case "${_PARAMETER}" in
|
||||
@@ -244,10 +255,12 @@ Verify_checksums() {
|
||||
|
||||
if [ -e "${_CHECKSUM}" ]; then
|
||||
|
||||
_MANIFEST_FOUND="true"
|
||||
log_in "Found: [${_CHECKSUM}] ..."
|
||||
|
||||
if [ -e "/usr/bin/${_DIGEST}sum" ]; then
|
||||
|
||||
_VERIFICATION_EXECUTED="true"
|
||||
log_in "Found: [/usr/bin/${_DIGEST}sum] ..."
|
||||
|
||||
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
|
||||
|
||||
_RETURN_SHA="${?}"
|
||||
_VERIFICATION_SUCCEEDED="true"
|
||||
cat "${_CHECKSUM_LOG}" > "${_TTY}"
|
||||
log_ok "Found: [/usr/bin/${_DIGEST}sum] successful verified: [${_CHECKSUM}]"
|
||||
|
||||
@@ -313,6 +327,33 @@ Verify_checksums() {
|
||||
log_end_msg
|
||||
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
|
||||
|
||||
"0,0")
|
||||
@@ -331,18 +372,28 @@ Verify_checksums() {
|
||||
log_er "Verification of [GPG signature] file successful, while verification of [sha checksum] file failed."
|
||||
sleep 8
|
||||
panic "Verification of [GPG signature] file successful, while verification of [sha checksum] file failed."
|
||||
return 1
|
||||
;;
|
||||
|
||||
*",0")
|
||||
log_er "Verification of [GPG signature] file failed, while verification of [sha checksum] file successful."
|
||||
sleep 8
|
||||
panic "Verification of [GPG signature] file failed, while verification of [sha checksum] file successful."
|
||||
return 1
|
||||
;;
|
||||
|
||||
"na,"*)
|
||||
log_er "Verification of [sha checksum] file failed."
|
||||
sleep 8
|
||||
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
|
||||
|
||||
@@ -15,14 +15,11 @@
|
||||
# SPDX-Security-Contact: security@coresecret.eu
|
||||
|
||||
# Module summary:
|
||||
# - Runs after the encrypted live root filesystem has been decrypted.
|
||||
# - Requires the pinned public key, attestation hash file, and detached signature to exist as readable, non-empty regular files
|
||||
# inside the decrypted rootfs.
|
||||
# - Verifies the attestation signature with gpgv against the pinned key material.
|
||||
# - Confirms that the signature fingerprint matches the build-time expected rootfs fingerprint and panics on missing, malformed,
|
||||
# or mismatched evidence.
|
||||
|
||||
_SAVED_SET_OPTS="$(set +o)"
|
||||
# - Runs after the encrypted live root filesystem has been decrypted and selected for the SquashFS root mount.
|
||||
# - Requires the pinned public key and the signed decrypted-mapper SHA-512 manifest from the mounted live medium.
|
||||
# - Verifies the manifest signature and pinned signer fingerprint, then verifies the complete selected decrypted mapper against
|
||||
# the manifest.
|
||||
# - Panics on missing, malformed, mismatched, or unverifiable evidence.
|
||||
|
||||
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 ----------------------
|
||||
export CDLB_MAPPER_NAME="${CDLB_MAPPER_NAME:-crypt_liveiso}"
|
||||
|
||||
### Attestation file locations inside decrypted rootfs. ------------------------------------------------------------------------
|
||||
CDLB_ATTEST_FPR_SHA="${CDLB_ATTEST_FPR_SHA:-/root/root/.ciss/attestation/${CDLB_EXP_FPR}.gpg.sha512sum.txt}"
|
||||
CDLB_ATTEST_FPR_SIG="${CDLB_ATTEST_FPR_SIG:-/root/root/.ciss/attestation/${CDLB_EXP_FPR}.gpg.sha512sum.txt.sig}"
|
||||
### Rootfs selection and attestation file locations. ---------------------------------------------------------------------------
|
||||
CDLB_LUKS_FS="${CDLB_LUKS_FS:-/live/ciss_rootfs.crypt}"
|
||||
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}"
|
||||
|
||||
### Declare functions ----------------------------------------------------------------------------------------------------------
|
||||
@@ -91,11 +91,13 @@ require_attestation_file() {
|
||||
|
||||
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}]"
|
||||
return 1
|
||||
|
||||
fi
|
||||
|
||||
log_er "0042() : ${artifact_label} missing: [${artifact_path}]"
|
||||
panic "0042() : ${artifact_label} missing: [${artifact_path}]"
|
||||
return 1
|
||||
|
||||
fi
|
||||
|
||||
@@ -103,6 +105,7 @@ require_attestation_file() {
|
||||
|
||||
log_er "0042() : ${artifact_label} is not a regular file: [${artifact_path}]"
|
||||
panic "0042() : ${artifact_label} is not a regular file: [${artifact_path}]"
|
||||
return 1
|
||||
|
||||
fi
|
||||
|
||||
@@ -110,6 +113,7 @@ require_attestation_file() {
|
||||
|
||||
log_er "0042() : ${artifact_label} is empty: [${artifact_path}]"
|
||||
panic "0042() : ${artifact_label} is empty: [${artifact_path}]"
|
||||
return 1
|
||||
|
||||
fi
|
||||
|
||||
@@ -117,23 +121,56 @@ require_attestation_file() {
|
||||
|
||||
log_er "0042() : ${artifact_label} is not readable: [${artifact_path}]"
|
||||
panic "0042() : ${artifact_label} is not readable: [${artifact_path}]"
|
||||
return 1
|
||||
|
||||
fi
|
||||
|
||||
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"
|
||||
|
||||
require_attestation_file "Public key" "${KEYFILE}"
|
||||
require_attestation_file "Attestation data" "${HASH_FILE}"
|
||||
require_attestation_file "Attestation signature" "${SIGN_FILE}"
|
||||
require_attestation_file "Rootfs attestation manifest" "${HASH_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}]"
|
||||
|
||||
@@ -145,6 +182,7 @@ if ! _STATUS="$(/usr/bin/gpgv --keyring "${KEYFILE}" --status-fd 1 "${SIGN_FILE}
|
||||
|
||||
sleep 8
|
||||
panic "0042() : gpgv verification failed for signature: [${SIGN_FILE}]"
|
||||
exit 1
|
||||
|
||||
fi
|
||||
|
||||
@@ -160,10 +198,51 @@ else
|
||||
log_er "0042() : Signature FPR mismatch: got: [${_CDLB_SIG_FILE_FPR}] expected: [${CDLB_EXP_FPR}]"
|
||||
sleep 8
|
||||
panic "[FATAL] Signature FPR mismatch: got: [${_CDLB_SIG_FILE_FPR}] expected: [${CDLB_EXP_FPR}]."
|
||||
exit 1
|
||||
|
||||
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"
|
||||
|
||||
|
||||
@@ -12,19 +12,24 @@ include_toc: true
|
||||
|
||||
# 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>
|
||||
**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
|
||||
|
||||
* **Trust anchor:** Pinned fingerprint (FPR) of the signing key embedded at build time in initramfs hooks.
|
||||
* **Integrity & authenticity verification:**
|
||||
|
||||
1. **Early:** Verify `sha512sum.txt` at the ISO edge using `gpgv` and FPR pin.
|
||||
2. **Late:** Verify an attestation hash list inside the decrypted root FS using `gpgv` and FPR pin.
|
||||
1. **ISO edge:** Verify one supported checksum manifest using `gpgv`, FPR pinning, and the matching checksum tool.
|
||||
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.
|
||||
|
||||
# 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 |
|
||||
|--------------|-----------------------------------------------------------|--------------------------------------------------------|
|
||||
| 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 |
|
||||
| Signatures | Ed25519 or RSA-4096 (FPR pinned) | Public verifiability, non-repudiation |
|
||||
| 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 |
|
||||
|
||||
# 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
|
||||
```sh
|
||||
@@ -172,16 +178,24 @@ cryptsetup luksFormat \
|
||||
"${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).
|
||||
|
||||
# 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.
|
||||
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).
|
||||
|
||||
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):**
|
||||
|
||||
```sh
|
||||
@@ -189,12 +203,16 @@ cryptsetup luksFormat \
|
||||
# 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]`
|
||||
* **Key source:** `/etc/ciss/keys/*.gpg` (accepted only if FPR == build-pin)
|
||||
* **Exact attested boundary:** the complete `/dev/mapper/crypt_liveiso` block payload selected by `9990-overlay.sh` and mounted
|
||||
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):**
|
||||
|
||||
@@ -202,13 +220,20 @@ cryptsetup luksFormat \
|
||||
# 1) Signature and FPR pin (no agent)
|
||||
/usr/bin/gpgv --no-default-keyring --keyring "$KEYFILE" --status-fd 1 --verify "$SIG" "$DATA"
|
||||
|
||||
# 2) Optional: Content hash verification
|
||||
( cd "$ROOTMP" && /usr/bin/sha512sum -c --strict --quiet "$DATA" )
|
||||
# 2) Required selected-payload verification
|
||||
( 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)
|
||||
|
||||
* **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
|
||||
|
||||
@@ -251,17 +276,19 @@ flowchart TD
|
||||
subgraph ISO Build Time
|
||||
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"];
|
||||
C e01a@--> C2["Hash complete decrypted mapper and sign rootfs manifest"];
|
||||
e00@{ animation: fast }
|
||||
e01@{ animation: fast }
|
||||
e01a@{ animation: fast }
|
||||
end
|
||||
|
||||
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"];
|
||||
E e04@--> F["Mounting RootFS"];
|
||||
F e05@--> G["0030 verification of authenticity and integrity via embedded and pinned GPG of ISO edge"];
|
||||
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"];
|
||||
e02@{ animation: fast }
|
||||
e03@{ animation: fast }
|
||||
@@ -285,7 +312,9 @@ I -- FAIL --> X;
|
||||
|
||||
# 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/)**
|
||||
|
||||
@@ -9,12 +9,12 @@
|
||||
# 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
|
||||
# shellcheck disable=SC2154
|
||||
|
||||
guard_sourcing || return "${ERR_GUARD_SRCE}"
|
||||
|
||||
#######################################
|
||||
# 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
|
||||
# - /usr/lib/live/boot/0030-ciss-verify-checksums
|
||||
# Globals:
|
||||
@@ -31,10 +31,7 @@ guard_sourcing || return "${ERR_GUARD_SRCE}"
|
||||
ciss_upgrades_boot() {
|
||||
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=(
|
||||
"/root/.ciss/attestation/${VAR_SIGNING_KEY_FPR}.gpg"
|
||||
"/etc/initramfs-tools/files/unlock_wrapper.sh"
|
||||
"/usr/lib/live/boot/0030-ciss-verify-checksums"
|
||||
)
|
||||
|
||||
@@ -48,9 +48,20 @@ Verify_checksums() {
|
||||
|
||||
_KEYFILE=""
|
||||
|
||||
_MANIFEST_FOUND="false"
|
||||
|
||||
_MP=""
|
||||
|
||||
_RETURN_PGP=""
|
||||
|
||||
_RETURN_SHA=""
|
||||
|
||||
_VERIFICATION_EXECUTED="false"
|
||||
|
||||
_VERIFICATION_SUCCEEDED="false"
|
||||
|
||||
### Parse commandline arguments ----------------------------------------------------------------------------------------------
|
||||
# shellcheck disable=SC2154
|
||||
for _PARAMETER in ${LIVE_BOOT_CMDLINE}; do
|
||||
|
||||
case "${_PARAMETER}" in
|
||||
@@ -203,10 +214,12 @@ Verify_checksums() {
|
||||
|
||||
if [ -e "${_CHECKSUM}" ]; then
|
||||
|
||||
_MANIFEST_FOUND="true"
|
||||
printf "\e[95m[INFO] Found: [%s] ... \n\e[0m" "${_CHECKSUM}"
|
||||
|
||||
if [ -e "/usr/bin/${_DIGEST}sum" ]; then
|
||||
|
||||
_VERIFICATION_EXECUTED="true"
|
||||
printf "\e[95m[INFO] Found: [%s] ... \n\e[0m" "/usr/bin/${_DIGEST}sum"
|
||||
|
||||
if [ "${LIVE_VERIFY_CHECKSUMS_SIGNATURES}" = "true" ]; then
|
||||
@@ -237,6 +250,7 @@ Verify_checksums() {
|
||||
if grep -v '^#' "${_CHECKSUM}" | /usr/bin/"${_DIGEST}"sum -c > "${_TTY}"; then
|
||||
|
||||
_RETURN_SHA="${?}"
|
||||
_VERIFICATION_SUCCEEDED="true"
|
||||
printf "\e[92m[INFO] Found: [%s] successful verified: [%s] \n\e[0m" "/usr/bin/${_DIGEST}sum" "${_CHECKSUM}"
|
||||
|
||||
else
|
||||
@@ -265,6 +279,33 @@ Verify_checksums() {
|
||||
log_end_msg
|
||||
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
|
||||
|
||||
"0,0")
|
||||
@@ -288,6 +329,7 @@ Verify_checksums() {
|
||||
printf "\e[91m[FATAL] CDLB modified: [%s] done. \n\e[0m" "${CDLB_SCRIPT_FULL}"
|
||||
sleep 8
|
||||
panic "Verification of [GPG signature] file successful, while verification of [sha checksum] file failed."
|
||||
return 1
|
||||
;;
|
||||
|
||||
*",0")
|
||||
@@ -295,6 +337,7 @@ Verify_checksums() {
|
||||
printf "\e[91m[FATAL] CDLB modified: [%s] done. \n\e[0m" "${CDLB_SCRIPT_FULL}"
|
||||
sleep 8
|
||||
panic "Verification of [GPG signature] file failed, while verification of [sha checksum] file successful."
|
||||
return 1
|
||||
;;
|
||||
|
||||
"na,"*)
|
||||
@@ -302,6 +345,14 @@ Verify_checksums() {
|
||||
printf "\e[91m[FATAL] CDLB modified: [%s] done. \n\e[0m" "${CDLB_SCRIPT_FULL}"
|
||||
sleep 8
|
||||
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
|
||||
|
||||
Executable
+228
@@ -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'
|
||||
Reference in New Issue
Block a user