#!/bin/bash
# SPDX-Version: 3.0
# SPDX-CreationInfo: 2025-05-05; WEIDNER, Marc S.; <msw@coresecret.dev>
# SPDX-ExternalRef: GIT https://git.coresecret.dev/msw/CISS.debian.installer.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: EUPL-1.2 OR LicenseRef-CCLA-1.0
# SPDX-LicenseComment: This file is part of the CISS.hardened.installer framework.
# SPDX-PackageName: CISS.debian.installer
# SPDX-Security-Contact: security@coresecret.eu
# SPDX-Comment: unlock_wrapper.sh to be executed after dropbear SSH login as forced command

set -Ceuo pipefail
IFS=$(printf ' \n\t')

# shellcheck disable=SC2155
declare -gr CURRENTDATE=$(date +"%F %T")
declare -gir MAX_RETRIES=2

#######################################
# Generates informative shell prompt.
# Globals:
#   PS1
# Arguments:
#   None
#######################################
prompt_string() {
  declare -gx PS1="\
\[\033[1;91m\]\d\[\033[0m\]|\[\033[1;91m\]\u\[\033[0m\]@\
\[\033[1;95m\]\h\[\033[0m\]:\
\[\033[1;96m\]\w\[\033[0m\]/>>\
\$(if [[ \$? -eq 0 ]]; then \
  # Show exit status in green if zero
  echo -e \"\[\033[1;92m\]\$?\[\033[0m\]\"; \
else \
  # Show exit status in red otherwise
  echo -e \"\[\033[1;91m\]\$?\[\033[0m\]\"; \
fi)\
|~\$ "
}

#######################################
# Trap function to be called on 'ERR'.
# Arguments:
#   $1: ${?}
#   $2: ${BASH_SOURCE[0]}
#   $3: ${LINENO}
#   $4: ${FUNCNAME[0]:-main}
#   $5: ${BASH_COMMAND}
#######################################
trap_on_err() {
  declare -r errcode="$1"
  declare -r errscrt="$2"
  declare -r errline="$3"
  declare -r errfunc="$4"
  declare -r errcmmd="$5"
  trap - ERR
  stty echo
  if [[ ${errcode} -eq 0 ]]; then
    print_scr_scc
    prompt_string
    exit 0
  else
    print_scr_err "${errcode}" "${errscrt}" "${errline}" "${errfunc}" "${errcmmd}"
    sleep 15
    sync
    set +C
    echo 1 > /proc/sys/kernel/sysrq
    echo o > /proc/sysrq-trigger
  fi
}

#######################################
# Security Trap on 'INT' and 'TERM' to provide a deterministic way to not circumvent the Nuke Routine
# Globals:
#   DEVICES_LUKS
#   DEVICES_NUKE
# Arguments:
#  None
#######################################
trap_on_term() {
  trap - INT
  stty echo
  printf "\n"
  printf "\e[0;91m✘ System caught a 'SIGINT'. System Power Off in 3 seconds. \e[0m\n" >&2
  sync
  sleep 3
  set +C
  echo 1 > /proc/sys/kernel/sysrq
  echo o > /proc/sysrq-trigger
}

#######################################
# Print Error Message for 'non-0' Trap on 'EXIT' on Terminal.
# Arguments:
#   $1: ${?}
#   $2: ${BASH_SOURCE[0]}
#   $3: ${LINENO}
#   $4: ${FUNCNAME[0]:-main}
#   $5: ${BASH_COMMAND}
#######################################
print_scr_err() {
  declare -r scr_err_errcode="$1"
  declare -r scr_err_errscrt="$2"
  declare -r scr_err_errline="$3"
  declare -r scr_err_errfunc="$4"
  declare -r scr_err_errcmmd="$5"
  printf "\n"
  printf "\e[0;91m✘ System caught an 'ERROR'. System Power Off in 15 seconds. \e[0m\n" >&2
  printf "\n"
  printf "\e[0;91m✘ Error               : %s \e[0m\n" "${scr_err_errcode}" >&2
  printf "\e[0;91m✘ Line                : %s \e[0m\n" "${scr_err_errline}" >&2
  printf "\e[0;91m✘ Script              : %s \e[0m\n" "${scr_err_errscrt}" >&2
  printf "\e[0;91m✘ Function            : %s \e[0m\n" "${scr_err_errfunc}" >&2
  printf "\e[0;91m✘ Command             : %s \e[0m\n" "${scr_err_errcmmd}" >&2
  printf "\n"
}

#######################################
# Print Error Message for '0' Trap on 'ERR' on Terminal.
# Arguments:
#   none
#######################################
print_scr_scc() {
  printf "\e[0;92m✅ Script exited successfully. Proceeding with booting. \e[0m\n"
  printf "\n"
}

#######################################
# Gather information of all LUKS Devices available on the system
# Arguments:
#  None
#######################################
gather_luks_devices() {
  declare prev=() curr=() dev="" tries=0

  while ((tries < 10)); do
    mapfile -t curr < <(blkid -t TYPE=crypto_LUKS -o device)

    if [[ ${curr[*]} == "${prev[*]}"   ]]; then
      break
    fi

    prev=("${curr[@]}")
    tries=$((tries + 1))
    sleep 1
  done

  ### Print one device per line for mapfile compatibility
  for dev in "${curr[@]}"; do
    printf '%s\n' "${dev}"
  done
}

#######################################
# Gather information of all NUKE Devices available on the system
# Globals:
#   DEVICES_LUKS
# Arguments:
#  None
#######################################
gather_nuke_devices() {
  ### 'DEVICES_LUKS' must already be a bash array of device paths
  declare dev=""
  declare result=()

  for dev in "${DEVICES_LUKS[@]}"; do
    ### Check slot 31 for 'luks2'
    if cryptsetup luksDump "${dev}" 2> /dev/null | grep -qE '^[[:space:]]*31: luks2'; then
      result+=("${dev}")
    fi
  done

  ### Print one device per line for mapfile compatibility
  for dev in "${result[@]}"; do
    printf '%s\n' "${dev}"
  done
}

#######################################
# Read passphrase interactively.
# Arguments:
#  None
#######################################
passphrase_ask() {
  declare -g PASSPHRASE=""
  printf "\n"
  stty -echo
  printf "\e[0;95m🔐 Enter passphrase for decryption: \e[0m\n"
  read -r PASSPHRASE
  stty echo
  printf "\n"
}

#######################################
# Test the entered passphrase against the dedicated Nuke Keyslot #31.
# Arguments:
#   $1: DEVICE
#   $2: PASSWD
#######################################
passphrase_test() {
  declare -r DEVICE="$1"
  declare -r PASPHR="$2"
  printf '%s' "${PASPHR}" | cryptsetup open --batch-mode --test-passphrase --key-slot 31 "${DEVICE}" > /dev/null 2>&1
}

#######################################
# Check the integrity and authenticity of this script itself.
# Arguments:
#   $0: Script Name
#######################################
verify_coresecret() {
  ### Directory of this script
  # shellcheck disable=SC2155
  declare dir="$(dirname "$(readlink -f "${0}")")"
  # shellcheck disable=SC2155
  declare script="$(basename "${0}")"
  declare algo

  for algo in sha512 sha384; do
    # shellcheck disable=SC2155
    declare hashfile="${dir}/${script}.${algo}"
    # shellcheck disable=SC2155
    declare sigfile="${hashfile}.sig"
    # shellcheck disable=SC2155
    declare cmd="${algo}sum"

    printf "\e[0;95m🔏 Verifying signature of: [%s] \e[0m\n" "${hashfile}"
    gpgv --keyring /etc/keys/pubring.gpg "${sigfile}" "${hashfile}" || {
      printf "\e[0;91m✘ Signature verification failed for: [%s] \e[0m\n" "${hashfile}" >&2
      printf "\e[0;91m✘ System Power Off in 3 seconds. \e[0m\n" >&2
      sync
      sleep 3
      set +C
      echo 1 > /proc/sys/kernel/sysrq
      echo o > /proc/sysrq-trigger
    }
    printf "\e[0;92m🔏 Verifying signature of: [%s] successful. \e[0m\n" "${hashfile}"

    printf "\e[0;95m🔢 Recomputing Hash: [%s] \e[0m\n" "${algo}"
    # shellcheck disable=SC2155
    declare computed=$($cmd "${dir}/${script}" | awk '{print $1}')
    # shellcheck disable=SC2155
    declare expected=$(cat "${hashfile}")

    if [[ ${computed} != "${expected}" ]]; then
      printf "\e[0;91m✘ Hash mismatch for: [%s] \e[0m\n" "${algo}" >&2
      printf "\e[0;91m✘ System Power Off in 3 seconds. \e[0m\n" >&2
      sync
      sleep 3
      set +C
      echo 1 > /proc/sys/kernel/sysrq
      echo o > /proc/sysrq-trigger
    fi
    printf "\e[0;92m🔢 Recomputing Hash: [%s] successful. \e[0m\n" "${algo}"
  done

  printf "\e[0;92m🔏 All signatures and hashes verified successfully. Proceeding. \e[0m\n"
}

### Main Programm
trap 'trap_on_err "$?" "${BASH_SOURCE[0]}" "${LINENO}" "${FUNCNAME[0]:-main}" "${BASH_COMMAND}"' ERR
trap 'trap_on_term' INT TERM

printf "\e[0;91mCoresecret Connection established.\e[0m\n"
printf "\e[0;91mStarting Time: %s\e[0m\n" "${CURRENTDATE}"
printf "\n"

verify_coresecret

### Read newline-separated output into an array
mapfile -t DEVICES_LUKS < <(gather_luks_devices)
mapfile -t DEVICES_NUKE < <(gather_nuke_devices)

### Debug output: list each element with its index
#for idx in "${!DEVICES_LUKS[@]}"; do
#  printf 'Luks[%d]: %s\n' "${idx}" "${DEVICES_LUKS[${idx}]}"
#done

### Debug output: list each element with its index
#for idx in "${!DEVICES_NUKE[@]}"; do
#  printf 'Nuke[%d]: %s\n' "${idx}" "${DEVICES_NUKE[${idx}]}"
#done

### # If there are no LUKS devices at all, drop to bash
[[ -n ${DEVICES_LUKS[*]} ]] || {
  printf "\e[0;92m✘ No LUKS Devices found. Dropping to bash ... \e[0m\n"
  prompt_string
  exec /bin/bash -i
}

### If there are LUKS devices but no Nuke devices, try unlocking flow
if [[ -n ${DEVICES_LUKS[*]} ]] && [[ -z ${DEVICES_NUKE[*]} ]]; then
  ### Attempt interactive unlock with cryptroot-unlock
  if cryptroot-unlock; then

    exit 0

  else

    printf "\n"
    printf "\e[0;91m✘ Unsuccessful command 'cryptroot-unlock'. \e[0m\n"
    printf "\e[0;92m✘ No LUKS operations performed. Dropping to bash ... \e[0m\n"
    printf "\e[0;92m✘ To unlock 'root' partition, and maybe others like 'swap', run 'cryptroot-unlock'. \e[0m\n"
    prompt_string
    exec /bin/bash -i

  fi

elif [[ -n ${DEVICES_LUKS[*]} ]] && [[ -n ${DEVICES_NUKE[*]} ]]; then

  declare -i attempt=1
  declare NUKED=false
  declare TEST_DEV="${DEVICES_NUKE[0]}"

  while ((attempt <= MAX_RETRIES)); do

    printf "\e[0;95m🔐Attempt %s/%s: \e[0m\n" "${attempt}" "${MAX_RETRIES}"

    passphrase_ask
    declare -g PASSWD="${PASSPHRASE}"

    if passphrase_test "${TEST_DEV}" "${PASSWD}"; then

      for dev in "${DEVICES_NUKE[@]}"; do
        cryptsetup erase --batch-mode "${dev}" > /dev/null 2>&1
        printf "%s:\e[0;95m✘ LUKS Device Header malfunction. \e[0m\n" "${dev}"
      done

      declare -r NUKED=true
      unset PASSWD
      break

    else

      declare code="$?"

      case "${code}" in
        1) printf "\e[0;91m✘ No usable key slot is available. \e[0m\n" ;;
        2) printf "\e[0;91m✘ No key available with this passphrase. \e[0m\n" ;;
        3) printf "\e[0;93m✘ Out of memory. \e[0m\n" ;;
        *) printf "\e[0;91m✘ Unexpected Return Code. \e[0m\n" ;;
      esac

    fi

    attempt=$((attempt + 1))

  done

  if [[ ${NUKED} == true ]]; then
    stty echo
    sleep 3
    sync
    set +C
    echo 1 > /proc/sys/kernel/sysrq
    echo o > /proc/sysrq-trigger
  fi

  if cryptroot-unlock; then

    exit 0

  else

    printf "\e[0;91m✘ Unsuccessful command 'cryptroot-unlock'. \e[0m\n"
    printf "\e[0;92m✘ No LUKS operations performed. Dropping to bash ... \e[0m\n"
    printf "\e[0;92m✘ To unlock 'root' partition, and maybe others like 'swap', run 'cryptroot-unlock'. \e[0m\n"
    prompt_string
    exec /bin/bash -i

  fi

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