#!/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

# Module summary:
# - 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

printf "\e[95m[INFO] Starting             : [/usr/lib/live/boot/0042_ciss_post_decrypt_attest] \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 ----------------------
export CDLB_MAPPER_NAME="${CDLB_MAPPER_NAME:-crypt_liveiso}"

### 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 ----------------------------------------------------------------------------------------------------------

#######################################
# 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' "$*"; }

#######################################
# Validate a boot-time attestation input file.
# Globals:
#   None
# Arguments:
#   1: Human-readable artifact label
#   2: Absolute artifact path
# Returns:
#   0: on success
#######################################
require_attestation_file() {
  artifact_label="${1}"
  artifact_path="${2}"

  if [ ! -e "${artifact_path}" ]; then

    if [ -L "${artifact_path}" ]; then

      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

  if [ -L "${artifact_path}" ] || [ ! -f "${artifact_path}" ]; then

    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

  if [ ! -s "${artifact_path}" ]; then

    log_er "0042()              : ${artifact_label} is empty: [${artifact_path}]"
    panic  "0042()              : ${artifact_label} is empty: [${artifact_path}]"
    return 1

  fi

  if [ ! -r "${artifact_path}" ]; then

    log_er "0042()              : ${artifact_label} is not readable: [${artifact_path}]"
    panic  "0042()              : ${artifact_label} is not readable: [${artifact_path}]"
    return 1

  fi

  return 0
}

#######################################
# 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 "Rootfs attestation manifest"  "${HASH_FILE}"
require_attestation_file "Rootfs attestation signature" "${SIGN_FILE}"
require_rootfs_payload "${CDLB_MAPPER_DEV}"

log_in "0042()               : Verifying signed rootfs attestation manifest with pinned GPG FPR."

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}]"

  if [ -n "${_STATUS}" ]; then

    printf '%s\n' "${_STATUS}" >&2

  fi

  sleep 8
  panic  "0042()              : gpgv verification failed for signature: [${SIGN_FILE}]"
  exit 1

fi

_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 "0042()               : Signature FPR valid: got: [${_CDLB_SIG_FILE_FPR}] expected: [${CDLB_EXP_FPR}]"

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

_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"

# vim: number et ts=2 sw=2 sts=2 ai tw=128 ft=sh
