Files
CISS.debian.live.builder/config/includes.chroot/etc/initramfs-tools/scripts/init-bottom/0042-ciss-post-decrypt-attest.sh
Marc S. Weidner 75b8a062fd
Some checks failed
💙 Generating a PUBLIC Live ISO. / 💙 Generating a PUBLIC Live ISO. (push) Has been cancelled
🔐 Generating a Private Live ISO TRIXIE. / 🔐 Generating a Private Live ISO TRIXIE. (push) Has been cancelled
🛡️ Retrieve DNSSEC status of coresecret.dev. / 🛡️ Retrieve DNSSEC status of coresecret.dev. (push) Successful in 1m16s
🛡️ Shell Script Linting / 🛡️ Shell Script Linting (push) Successful in 1m49s
V8.13.512.2025.11.28
Signed-off-by: Marc S. Weidner <msw@coresecret.dev>
2025-11-28 00:06:18 +00:00

182 lines
6.6 KiB
Bash

#!/bin/sh
# bashsupport disable=BP5007
# shellcheck disable=SC2249
# shellcheck shell=sh
# SPDX-Version: 3.0
# SPDX-CreationInfo: 2025-11-12; 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-2025; 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
# Purpose: Late rootfs attestation and dmsetup health checking.
# Phase : bottom (executed by live-boot inside the initramfs).
_SAVED_SET_OPTS="$(set +o)"
set -eu
printf "\e[95m[INFO] Starting : [/etc/initramfs-tools/scripts/init-bottom/0042-ciss-post-decrypt-attest.sh] \n\e[0m"
### Declare variables ----------------------------------------------------------------------------------------------------------
### Will be replaced at build time:
export CDLB_EXP_FPR="@EXP_FPR@"
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 ----------------------
CDLB_MAPPER_NAME="${CDLB_MAPPER_NAME:-crypt_liveiso}"
### Attestation file locations inside decrypted rootfs. ------------------------------------------------------------------------
CDLB_ATTEST_FPR_SHA="${CDLB_ATTEST_FPR_SHA:-/root/.ciss/attest/${CDLB_EXP_FPR}.sha512sum.txt}"
CDLB_ATTEST_FPR_SIG="${CDLB_ATTEST_FPR_SIG:-/root/.ciss/attest/${CDLB_EXP_FPR}.sha512sum.txt.sig}"
CDLB_KEY_DIR="${CDLB_KEY_DIR:-/etc/ciss/keys}"
### Declare functions ----------------------------------------------------------------------------------------------------------
#######################################
# Helper for colored text output on stdout.
# Globals:
# None
# Arguments:
# *: String to print
#######################################
log_in() { printf '\e[95m[INFO] %s \n\e[0m' "$*"; }
#######################################
# Helper for colored text output on stdout.
# Globals:
# None
# Arguments:
# *: String to print
#######################################
log_ok() { printf '\e[92m[INFO] %s \n\e[0m' "$*"; }
#######################################
# Helper for colored text output on stdout.
# Globals:
# None
# Arguments:
# *: String to print
#######################################
log_er() { printf '\e[91m[FATAL] %s \n\e[0m' "$*"; }
### Locate decrypted rootfs mount ----------------------------------------------------------------------------------------------
_mp=""
ROOTMP=""
for _mp in /run/live/rootfs /run/live/rootfs.squashfs /run/live/overlay /root ; do
if [ -d "${_mp}" ] && [ -e "${_mp}/etc" ]; then ROOTMP="${_mp}"; break; fi
done
if [ -z "${ROOTMP}" ]; then
log_er "No decrypted rootfs mount found."
sleep 8
panic "[FATAL] No decrypted rootfs mount found."
fi
log_ok "Decrypted rootfs at: [${ROOTMP}]"
HASH_FILE="${ROOTMP}${CDLB_ATTEST_FPR_SHA}"
SIGN_FILE="${ROOTMP}${CDLB_ATTEST_FPR_SIG}"
KEYFILE="${ROOTMP}${CDLB_KEY_DIR}/${CDLB_EXP_FPR}.gpg"
[ -s "${KEYFILE}" ] || { log_er "No public key found under: [${ROOTMP}${CDLB_KEY_DIR}/${CDLB_EXP_FPR}.gpg]"; exit 42; }
[ -s "${HASH_FILE}" ] || { log_er "Attestation data missing: [${HASH_FILE}]"; exit 42; }
[ -s "${SIGN_FILE}" ] || { log_er "Attestation signature missing: [${SIGN_FILE}]"; exit 42; }
log_in "Verifying rootfs attestation with 'gpgv' and inside LUKS encrypted rootfs pinned GPG FPR."
_STATUS="$(gpgv --no-default-keyring --keyring "${KEYFILE}" --status-fd 1 --verify "${SIGN_FILE}" "${HASH_FILE}" 2>/dev/null)"
_CDLB_SIG_FILE_FPR="$(printf '%s\n' "${_STATUS}" | awk '/^\[GNUPG:\] VALIDSIG /{print $3; exit}')"
### Compare against pinned and expected fingerprint. ---------------------------------------------------------------------------
if [ "${_CDLB_SIG_FILE_FPR}" = "${CDLB_EXP_FPR}" ]; then
log_ok "Signature FPR valid: got: [${_CDLB_SIG_FILE_FPR}] expected: [${CDLB_EXP_FPR}]"
else
log_er "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}]."
fi
### 'dmsetup' health check -----------------------------------------------------------------------------------------------------
MAP_DEV="/dev/mapper/${CDLB_MAPPER_NAME}"
if [ -e "${MAP_DEV}" ]; then
log_in "Checking dmsetup table for ${MAP_DEV}"
TOP_LINE="$(/usr/sbin/dmsetup table --showkeys "${MAP_DEV}" 2>/dev/null | awk 'NR==1{print; exit}')"
if printf '%s\n' "${TOP_LINE}" | grep -q ' crypt '; then
log_ok "Top layer is 'crypt'."
else
log_er "Top layer is NOT 'crypt'."
sleep 8
panic "[FATAL] Top layer is NOT 'crypt'."
fi
if printf '%s\n' "${TOP_LINE}" | grep -Eq ' xts|aes-xts'; then
log_ok "Cipher looks like AES-XTS."
else
log_er "Cipher does not look like AES-XTS."
sleep 8
panic "[FATAL] Cipher does not look like AES-XTS."
fi
### Extract child device token (the second last field is 'device', the last is 'offset.') --------------------------------------
CHILD_TOK="$(printf '%s\n' "${TOP_LINE}" | awk '{print $(NF-1)}')"
CHILD_NAME="${CHILD_TOK}"
case "${CHILD_TOK}" in
*:* )
if [ -e "/sys/dev/block/${CHILD_TOK}/dm/name" ]; then
CHILD_NAME="$(cat "/sys/dev/block/${CHILD_TOK}/dm/name" 2>/dev/null || true)"
[ -n "${CHILD_NAME}" ] || CHILD_NAME="${CHILD_TOK}"
fi
;;
/dev/* )
CHILD_NAME="$(basename -- "${CHILD_TOK}")"
;;
esac
#### Child layer must be 'integrity' with hmac and sha512 and 4096-byte sectors (best-effort greps). ---------------------------
log_in "Checking underlying integrity target: ${CHILD_NAME}"
CHILD_TAB="$(/usr/sbin/dmsetup table --showkeys "${CHILD_NAME}" 2>/dev/null || true)"
printf '%s\n' "${CHILD_TAB}" | grep -q ' integrity ' || { log_er "Underlying layer is not 'integrity'"; }
printf '%s\n' "${CHILD_TAB}" | grep -qi 'hmac' || { log_er "Integrity target not using keyed MAC (hmac)"; }
printf '%s\n' "${CHILD_TAB}" | grep -qi 'sha512' || { log_er "Integrity algo not sha512"; }
printf '%s\n' "${CHILD_TAB}" | grep -Eq '\b4096\b' || { log_er "Expected 4096-byte sector size not found"; }
log_ok "dm-crypt and dm-integrity(HMAC-SHA512, 4096B) chain looks healthy."
fi
eval "${_SAVED_SET_OPTS}"
printf "\e[92m[INFO] Successfully applied : [/etc/initramfs-tools/scripts/init-bottom/0042-ciss-post-decrypt-attest.sh] \n\e[0m"
# vim: number et ts=2 sw=2 sts=2 ai tw=128 ft=sh