V9.14.024.2026.06.11
Signed-off-by: Marc S. Weidner <msw@coresecret.dev>
This commit is contained in:
@@ -15,12 +15,14 @@
|
||||
# SPDX-Security-Contact: security@coresecret.eu
|
||||
|
||||
# Module summary:
|
||||
# This live-boot component implements the verify-checksums mode for the mounted live medium.
|
||||
# It reads the live-boot command line to decide whether checksum verification is enabled and which digests to accept.
|
||||
# It locates the pinned CISS GPG key material on the live medium, optionally verifies this script's signed hash,
|
||||
# optionally verifies signed checksum files, and checks the first matching checksum manifest with the matching digest tool. It
|
||||
# writes detailed checksum output to the verification TTY. It panics instead of continuing boot when integrity or
|
||||
# authenticity verification fails.
|
||||
# This live-boot component implements verify-checksums mode for the mounted live medium.
|
||||
# It reads the live-boot command line to decide whether checksum verification is enabled, which digests to accept, and
|
||||
# whether checksum signature verification is required. When signature verification is enabled, it requires to be pinned CISS GPG
|
||||
# key material from the live medium, verifies this script's signed SHA-512 hash, and verifies the selected checksum manifest
|
||||
# signature before accepting checksum results. It checks the first supported checksum manifest with an available matching digest
|
||||
# tool and writes detailed checksum command output to the verification TTY when checksum execution runs. It fails closed by
|
||||
# panicking on missing manifests, missing digest tools, empty manifests, failed signatures, failed checksums, or unknown
|
||||
# verification states.
|
||||
|
||||
### Modified Version of the original file:
|
||||
### https://salsa.debian.org/live-team/live-boot 'components/0030-ciss-verify-checksums'
|
||||
@@ -87,11 +89,26 @@ Verify_checksums() {
|
||||
|
||||
_CHECKSUM_LOG=""
|
||||
|
||||
_CHECKSUM_LOG_DIR="${LIVE_VERIFY_CHECKSUMS_LOG_DIR:-/run}"
|
||||
|
||||
_KEYFILE=""
|
||||
|
||||
_MANIFEST_FOUND="false"
|
||||
|
||||
_MP=""
|
||||
|
||||
_RETURN_PGP=""
|
||||
|
||||
_RETURN_SHA=""
|
||||
|
||||
_TOOL_FOUND="false"
|
||||
|
||||
_VERIFICATION_EXECUTED="false"
|
||||
|
||||
_VERIFICATION_SUCCEEDED="false"
|
||||
|
||||
### Parse commandline arguments ----------------------------------------------------------------------------------------------
|
||||
# shellcheck disable=SC2154
|
||||
for _PARAMETER in ${LIVE_BOOT_CMDLINE}; do
|
||||
|
||||
case "${_PARAMETER}" in
|
||||
@@ -148,6 +165,14 @@ Verify_checksums() {
|
||||
|
||||
done
|
||||
|
||||
if [ "${LIVE_VERIFY_CHECKSUMS_SIGNATURES}" = "true" ] && [ -z "${_KEYFILE}" ]; then
|
||||
|
||||
log_er "No pinned GPG key file found while checksum signature verification is enabled."
|
||||
sleep 8
|
||||
panic "[FATAL] No pinned GPG key file found while checksum signature verification is enabled."
|
||||
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC2164
|
||||
cd "${_MOUNTPOINT}"
|
||||
|
||||
@@ -244,10 +269,14 @@ Verify_checksums() {
|
||||
|
||||
if [ -e "${_CHECKSUM}" ]; then
|
||||
|
||||
_MANIFEST_FOUND="true"
|
||||
|
||||
log_in "Found: [${_CHECKSUM}] ..."
|
||||
|
||||
if [ -e "/usr/bin/${_DIGEST}sum" ]; then
|
||||
|
||||
_TOOL_FOUND="true"
|
||||
|
||||
log_in "Found: [/usr/bin/${_DIGEST}sum] ..."
|
||||
|
||||
if [ "${LIVE_VERIFY_CHECKSUMS_SIGNATURES}" = "true" ]; then
|
||||
@@ -275,8 +304,15 @@ Verify_checksums() {
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC2312
|
||||
_CHECKSUM_LOG="/run/ciss-${_DIGEST}sum-check.log"
|
||||
if grep -v '^#' "${_CHECKSUM}" | LC_ALL=C /usr/bin/"${_DIGEST}"sum -c > "${_CHECKSUM_LOG}" 2>&1; then
|
||||
_CHECKSUM_LOG="${_CHECKSUM_LOG_DIR}/ciss-${_DIGEST}sum-check.log"
|
||||
_VERIFICATION_EXECUTED="true"
|
||||
if ! grep -v '^#' "${_CHECKSUM}" | grep -q '[^[:space:]]'; then
|
||||
|
||||
_RETURN_SHA="254"
|
||||
: > "${_CHECKSUM_LOG}"
|
||||
log_er "Checksum manifest has no checksum entries: [${_CHECKSUM}]"
|
||||
|
||||
elif grep -v '^#' "${_CHECKSUM}" | LC_ALL=C /usr/bin/"${_DIGEST}"sum -c > "${_CHECKSUM_LOG}" 2>&1; then
|
||||
|
||||
_RETURN_SHA="${?}"
|
||||
cat "${_CHECKSUM_LOG}" > "${_TTY}"
|
||||
@@ -294,6 +330,12 @@ Verify_checksums() {
|
||||
|
||||
fi
|
||||
|
||||
if { [ "${_RETURN_PGP}" = "0" ] || [ "${_RETURN_PGP}" = "na" ]; } && [ "${_RETURN_SHA}" = "0" ]; then
|
||||
|
||||
_VERIFICATION_SUCCEEDED="true"
|
||||
|
||||
fi
|
||||
|
||||
# Stop after the first verification.
|
||||
break 2
|
||||
|
||||
@@ -313,6 +355,36 @@ Verify_checksums() {
|
||||
log_end_msg
|
||||
printf "\n"
|
||||
|
||||
if [ "${_MANIFEST_FOUND}" != "true" ]; then
|
||||
|
||||
log_er "No supported checksum manifest found. Checksum verification is fail-closed."
|
||||
sleep 8
|
||||
panic "[FATAL] No supported checksum manifest found. Checksum verification is fail-closed."
|
||||
|
||||
fi
|
||||
|
||||
if [ "${_TOOL_FOUND}" != "true" ]; then
|
||||
|
||||
log_er "Checksum manifest found, but no supported checksum tool is available. Checksum verification is fail-closed."
|
||||
sleep 8
|
||||
panic "[FATAL] Checksum manifest found, but no supported checksum tool is available. Checksum verification is fail-closed."
|
||||
|
||||
fi
|
||||
|
||||
if [ "${_VERIFICATION_EXECUTED}" != "true" ]; then
|
||||
|
||||
log_er "Checksum verification was not executed. Checksum verification is fail-closed."
|
||||
sleep 8
|
||||
panic "[FATAL] Checksum verification was not executed. Checksum verification is fail-closed."
|
||||
|
||||
fi
|
||||
|
||||
if [ "${_VERIFICATION_SUCCEEDED}" != "true" ]; then
|
||||
|
||||
log_er "[FATAL] Checksum verification did not complete successfully. Evaluating fail-closed failure state."
|
||||
|
||||
fi
|
||||
|
||||
case "${_RETURN_PGP},${_RETURN_SHA}" in
|
||||
|
||||
"0,0")
|
||||
@@ -345,6 +417,12 @@ Verify_checksums() {
|
||||
panic "Verification of [sha checksum] file failed."
|
||||
;;
|
||||
|
||||
*)
|
||||
log_er "Unknown checksum verification state: [${_RETURN_PGP:-unset},${_RETURN_SHA:-unset}]."
|
||||
sleep 8
|
||||
panic "[FATAL] Unknown checksum verification state. Checksum verification is fail-closed."
|
||||
;;
|
||||
|
||||
esac
|
||||
}
|
||||
# vim: number et ts=2 sw=2 sts=2 ai tw=128 ft=sh
|
||||
|
||||
@@ -16,18 +16,27 @@
|
||||
|
||||
# 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)"
|
||||
# - Requires the pinned public key, external rootfs attestation manifest, and detached signature to exist as readable,
|
||||
# non-empty regular files.
|
||||
# - Verifies the attestation signature with gpgv against the pinned key material and expected signer fingerprint.
|
||||
# - Verifies the exact final SquashFS byte stream copied into the decrypted LUKS mapper. The signed manifest provides both the
|
||||
# SHA-512 digest and the exact byte length; allocation slack after that SquashFS payload is intentionally out of scope.
|
||||
# - Panics on missing, malformed, unauthentic, or mismatched evidence.
|
||||
|
||||
set -eu
|
||||
|
||||
printf "\e[95m[INFO] Starting : [/usr/lib/live/boot/0042_ciss_post_decrypt_attest] \n\e[0m"
|
||||
|
||||
### Check panic command availability -------------------------------------------------------------------------------------------
|
||||
if ! command -v panic >/dev/null 2>&1; then
|
||||
|
||||
panic() {
|
||||
printf '\e[91m[FATAL] %s \n\e[0m' "${*}" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
fi
|
||||
|
||||
### Declare variables ----------------------------------------------------------------------------------------------------------
|
||||
|
||||
### Will be replaced at build time:
|
||||
@@ -36,10 +45,13 @@ 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}"
|
||||
export CDLB_MAPPER_DEV="${CDLB_MAPPER_DEV:-/dev/mapper/${CDLB_MAPPER_NAME}}"
|
||||
export CDLB_MNT_MEDIUM="${CDLB_MNT_MEDIUM:-/run/live/medium}"
|
||||
|
||||
### 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}"
|
||||
### Locations of the attestation file of filesystem.squashfs on the verified live medium. --------------------------------------
|
||||
CDLB_ROOTFS_ATTEST_MANIFEST="${CDLB_ROOTFS_ATTEST_MANIFEST:-${CDLB_MNT_MEDIUM}/live/filesystem.squashfs.sha512sum.txt}"
|
||||
CDLB_ROOTFS_ATTEST_SIGNATURE="${CDLB_ROOTFS_ATTEST_SIGNATURE:-${CDLB_ROOTFS_ATTEST_MANIFEST}.sig}"
|
||||
CDLB_ROOTFS_ATTEST_CHECK="${CDLB_ROOTFS_ATTEST_CHECK:-/run/ciss-rootfs-attestation.sha512sum}"
|
||||
CDLB_KEY_DIR="${CDLB_KEY_DIR:-/etc/ciss/keys}"
|
||||
|
||||
### Declare functions ----------------------------------------------------------------------------------------------------------
|
||||
@@ -123,15 +135,162 @@ require_attestation_file() {
|
||||
return 0
|
||||
}
|
||||
|
||||
HASH_FILE="${CDLB_ATTEST_FPR_SHA}"
|
||||
SIGN_FILE="${CDLB_ATTEST_FPR_SIG}"
|
||||
#######################################
|
||||
# Validate the decrypted rootfs payload device.
|
||||
# Globals:
|
||||
# None
|
||||
# Arguments:
|
||||
# 1: Absolute payload device path
|
||||
# Returns:
|
||||
# 0: on success
|
||||
#######################################
|
||||
require_rootfs_payload_device() {
|
||||
artifact_path="${1}"
|
||||
|
||||
if [ ! -e "${artifact_path}" ]; then
|
||||
|
||||
log_er "0042() : Rootfs payload device missing: [${artifact_path}]"
|
||||
panic "0042() : Rootfs payload device missing: [${artifact_path}]"
|
||||
|
||||
fi
|
||||
|
||||
if [ -L "${artifact_path}" ] || { [ ! -b "${artifact_path}" ] && [ ! -f "${artifact_path}" ]; }; then
|
||||
|
||||
log_er "0042() : Rootfs payload must be a block device or regular test fixture: [${artifact_path}]"
|
||||
panic "0042() : Rootfs payload must be a block device or regular test fixture: [${artifact_path}]"
|
||||
|
||||
fi
|
||||
|
||||
if [ ! -r "${artifact_path}" ]; then
|
||||
|
||||
log_er "0042() : Rootfs payload is not readable: [${artifact_path}]"
|
||||
panic "0042() : Rootfs payload is not readable: [${artifact_path}]"
|
||||
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Stream exactly the attested SquashFS payload bytes from the decrypted mapper.
|
||||
# Globals:
|
||||
# None
|
||||
# Arguments:
|
||||
# 1: Payload device or regular test fixture
|
||||
# 2: Exact payload byte count
|
||||
# Returns:
|
||||
# 0: on success
|
||||
#######################################
|
||||
stream_rootfs_payload() {
|
||||
payload_device="${1}"
|
||||
payload_size="${2}"
|
||||
block_size=1048576
|
||||
full_blocks=$((payload_size / block_size))
|
||||
remainder=$((payload_size % block_size))
|
||||
remainder_offset=$((full_blocks * block_size))
|
||||
|
||||
if [ "${full_blocks}" -gt 0 ]; then
|
||||
|
||||
dd if="${payload_device}" bs="${block_size}" count="${full_blocks}" 2>/dev/null || return 1
|
||||
|
||||
fi
|
||||
|
||||
if [ "${remainder}" -gt 0 ]; then
|
||||
|
||||
dd if="${payload_device}" bs=1 skip="${remainder_offset}" count="${remainder}" 2>/dev/null || return 1
|
||||
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Verify the attested SquashFS payload hash against the decrypted mapper bytes.
|
||||
# Globals:
|
||||
# CDLB_ROOTFS_ATTEST_CHECK
|
||||
# Arguments:
|
||||
# 1: Manifest path
|
||||
# 2: Payload device path
|
||||
# Returns:
|
||||
# 0: on success
|
||||
#######################################
|
||||
verify_rootfs_payload() {
|
||||
manifest_path="${1}"
|
||||
payload_device="${2}"
|
||||
payload_size=""
|
||||
payload_hash=""
|
||||
|
||||
payload_size="$(awk -F': ' '/^# rootfs-size-bytes: /{print $2; exit}' "${manifest_path}")"
|
||||
payload_hash="$(awk '($0 !~ /^#/ && NF >= 2){print $1; exit}' "${manifest_path}")"
|
||||
|
||||
case "${payload_size}" in
|
||||
|
||||
""|*[!0-9]*)
|
||||
log_er "0042() : Rootfs attestation manifest has invalid payload size."
|
||||
panic "0042() : Rootfs attestation manifest has invalid payload size."
|
||||
;;
|
||||
|
||||
esac
|
||||
|
||||
if [ "${payload_size}" -le 0 ]; then
|
||||
|
||||
log_er "0042() : Rootfs attestation manifest has empty payload size."
|
||||
panic "0042() : Rootfs attestation manifest has empty payload size."
|
||||
|
||||
fi
|
||||
|
||||
case "${payload_hash}" in
|
||||
|
||||
""|*[!0123456789abcdefABCDEF]*)
|
||||
log_er "0042() : Rootfs attestation manifest has invalid SHA-512 payload hash."
|
||||
panic "0042() : Rootfs attestation manifest has invalid SHA-512 payload hash."
|
||||
;;
|
||||
|
||||
esac
|
||||
|
||||
if [ "${#payload_hash}" -ne 128 ]; then
|
||||
|
||||
log_er "0042() : Rootfs attestation manifest SHA-512 payload hash has invalid length."
|
||||
panic "0042() : Rootfs attestation manifest SHA-512 payload hash has invalid length."
|
||||
|
||||
fi
|
||||
|
||||
if ! printf '%s -\n' "${payload_hash}" > "${CDLB_ROOTFS_ATTEST_CHECK}"; then
|
||||
|
||||
log_er "0042() : Failed to prepare transient rootfs payload checksum file."
|
||||
panic "0042() : Failed to prepare transient rootfs payload checksum file."
|
||||
|
||||
fi
|
||||
|
||||
chmod 0600 "${CDLB_ROOTFS_ATTEST_CHECK}" 2>/dev/null || :
|
||||
|
||||
log_in "0042() : Verifying exact SquashFS payload bytes from: [${payload_device}]"
|
||||
|
||||
# stream_rootfs_payload may be evaluated in a pipeline here; sha512sum -c is the fail-closed authority for truncated or
|
||||
# tampered payload bytes.
|
||||
# shellcheck disable=SC2310
|
||||
if ! stream_rootfs_payload "${payload_device}" "${payload_size}" | /usr/bin/sha512sum -c "${CDLB_ROOTFS_ATTEST_CHECK}"; then
|
||||
|
||||
log_er "0042() : Rootfs payload SHA-512 verification failed."
|
||||
panic "0042() : Rootfs payload SHA-512 verification failed."
|
||||
|
||||
fi
|
||||
|
||||
log_ok "0042() : Rootfs payload SHA-512 verification successful."
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
HASH_FILE="${CDLB_ROOTFS_ATTEST_MANIFEST}"
|
||||
SIGN_FILE="${CDLB_ROOTFS_ATTEST_SIGNATURE}"
|
||||
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 "Public key" "${KEYFILE}"
|
||||
require_attestation_file "Rootfs attestation manifest" "${HASH_FILE}"
|
||||
require_attestation_file "Rootfs attestation signature" "${SIGN_FILE}"
|
||||
require_rootfs_payload_device "${CDLB_MAPPER_DEV}"
|
||||
|
||||
log_in "0042() : Verifying rootfs attestation with 'gpgv' and inside LUKS encrypted rootfs pinned GPG FPR."
|
||||
log_in "0042() : Verifying rootfs attestation manifest with 'gpgv' and pinned GPG FPR."
|
||||
|
||||
if ! _STATUS="$(/usr/bin/gpgv --keyring "${KEYFILE}" --status-fd 1 "${SIGN_FILE}" "${HASH_FILE}" 2>&1)"; then
|
||||
|
||||
@@ -163,7 +322,7 @@ else
|
||||
|
||||
fi
|
||||
|
||||
eval "${_SAVED_SET_OPTS}"
|
||||
verify_rootfs_payload "${HASH_FILE}" "${CDLB_MAPPER_DEV}"
|
||||
|
||||
printf "\e[92m[INFO] Successfully applied : [/usr/lib/live/boot/0042_ciss_post_decrypt_attest] \n\e[0m"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user