TPM解锁luks的Check脚本

#!/usr/bin/env bash
set -euo pipefail

TOTAL=0
WARNINGS=0
WARNINGS_BLOCK=0
LUKS_DEV="${1:-/dev/nvme0n1p2}"
CMDLINE_FILE="/etc/kernel/cmdline"
CRYPTTAB_FILE="/etc/crypttab.initramfs"
MKINIT_CONF="/etc/mkinitcpio.conf"

note() { TOTAL=$((TOTAL+1)); printf "\e[32m[OK]\e[0m %s\n" "$*"; }
warn() { TOTAL=$((TOTAL+1)); WARNINGS=$((WARNINGS+1)); WARNINGS_BLOCK=$((WARNINGS_BLOCK+1)); printf "\e[33m[WARN]\e[0m %s\n" "$*"; }
warn_perm() { TOTAL=$((TOTAL+1)); WARNINGS=$((WARNINGS+1)); printf "\e[33m[WARN]\e[0m %s\n" "$*"; }
err()  { printf "\e[31m[ERR]\e[0m %s\n" "$*"; }

on_exit() {
  local status=$1
  if [ $status -ne 0 ]; then
    printf "\e[31m[EXIT]\e[0m 脚本异常退出,已执行 %d 项检查\n" "$TOTAL"
  else
    printf "\e[36m[SUMMARY]\e[0m 共执行 %d 项检查,警告 %d 项(阻断 %d 项)\n" "$TOTAL" "$WARNINGS" "$WARNINGS_BLOCK"
    if [ "$WARNINGS_BLOCK" -eq 0 ]; then
      printf "\e[32m[FINAL]\e[0m 无阻断警告,尝试 TPM 解锁验证...\n"
    fi
  fi
}

require() {
  command -v "$1" >/dev/null 2>&1 || { err "缺少命令:$1"; exit 1; }
}

# 兼容不同 systemd-cryptenroll 版本的 TPM 解锁探测(不依赖 $?,避免 set -e 干扰)
try_unlock_tpm2() {
  # 变体1:--unlock-tpm2 + --tpm2-device=auto
  if systemd-cryptenroll "$LUKS_DEV" --unlock-tpm2 --tpm2-device=auto >/dev/null 2>&1; then
    note "TPM 解锁验证(--unlock-tpm2 --tpm2-device=auto)成功"
    return 0
  fi
  # 变体2:仅 --unlock-tpm2
  if systemd-cryptenroll "$LUKS_DEV" --unlock-tpm2 >/dev/null 2>&1; then
    note "TPM 解锁验证(--unlock-tpm2)成功"
    return 0
  fi
  # 变体3:--unlock-tpm2 + --unlock-tpm2-device=auto(你的环境可用)
  if systemd-cryptenroll "$LUKS_DEV" --unlock-tpm2 --unlock-tpm2-device=auto >/dev/null 2>&1; then
    note "TPM 解锁验证(--unlock-tpm2 --unlock-tpm2-device=auto)成功"
    return 0
  fi
  # 变体4:--tpm2-device=auto + --unlock-tpm2
  if systemd-cryptenroll "$LUKS_DEV" --tpm2-device=auto --unlock-tpm2 >/dev/null 2>&1; then
    note "TPM 解锁验证(--tpm2-device=auto --unlock-tpm2)成功"
    return 0
  fi
  return 1
}

main() {
  trap 'on_exit $?' EXIT
  if [ "$EUID" -ne 0 ]; then exec sudo "$0" "$@"; fi

  require cryptsetup
  require lsblk
  require awk
  require grep
  require blkid
  if command -v bootctl >/dev/null 2>&1; then :; else warn "未安装/不可用 bootctl(systemd-boot 检查跳过)"; fi
  if command -v tpm2_getrandom >/dev/null 2>&1; then :; else warn "未安装 tpm2-tools,无法进行 TPM 自检"; fi
  if ! command -v systemd-cryptenroll >/dev/null 2>&1; then
    err "缺少 systemd-cryptenroll"
    exit 1
  fi

  # systemd 版本
  if command -v systemd >/dev/null 2>&1; then
    SDV=$(systemd --version | awk 'NR==1{print $2}')
    if [ "${SDV:-0}" -ge 250 ]; then
      note "systemd 版本 ${SDV}(满足 ≥250)"
    else
      warn "systemd 版本 ${SDV} 低于 250,TPM 集成可能不完整"
    fi
  fi

  # TPM 设备存在性(优先 tpmrm0)
  if [ -e /dev/tpmrm0 ]; then
    note "检测到 /dev/tpmrm0(资源管理器设备)"
  else
    warn "未检测到 /dev/tpmrm0,TPM 并发与隔离较弱;尝试使用 /dev/tpm0"
  fi
  if [ -e /dev/tpm0 ]; then
    note "检测到 /dev/tpm0"
  else
    err "未检测到 TPM 设备 /dev/tpm0"; exit 1
  fi

  if [ ! -b "$LUKS_DEV" ]; then
    LUKS_DEV="$(blkid -t TYPE=crypto_LUKS -o device | head -n1 || true)"
    if [ -z "$LUKS_DEV" ]; then err "未找到 LUKS 分区,请指定设备"; exit 1; fi
    note "自动检测 LUKS 设备:$LUKS_DEV"
  fi

  LUKS_UUID=$(cryptsetup luksUUID "$LUKS_DEV" 2>/dev/null || true)
  if [ -z "$LUKS_UUID" ]; then err "获取 LUKS UUID 失败,确认设备为 LUKS2 且可读(需要 root)"; exit 1; fi
  note "LUKS 设备:$LUKS_DEV UUID=$LUKS_UUID"

  # 确认 LUKS2
  if cryptsetup luksDump "$LUKS_DEV" | grep -q "Version:.*2"; then
    note "LUKS 版本为 LUKS2"
  else
    warn "LUKS 版本非 LUKS2,TPM 集成建议使用 LUKS2(可 convert)"
  fi

  # TPM keyslot & 密码 keyslot
  DUMP=$(cryptsetup luksDump "$LUKS_DEV" 2>/dev/null || true)
  if [ -z "$DUMP" ]; then
    warn "无法读取 LUKS 头,请确认设备与权限"
  fi
  if grep -q "systemd-tpm2" <<<"$DUMP"; then
    note "检测到 TPM2 keyslot(systemd-tpm2 token)"
  else
    warn "未检测到 TPM2 keyslot,请执行:systemd-cryptenroll ${LUKS_DEV} --tpm2-device=auto [--tpm2-pcrs=7+11]"
  fi
  PASS_SLOTS_COUNT=$(printf "%s\n" "$DUMP" | grep -E '^[[:space:]]*[0-9]+: luks' | wc -l || true)
  if [ "${PASS_SLOTS_COUNT:-0}" -gt 0 ]; then
    note "检测到密码 keyslot 数:$PASS_SLOTS_COUNT,可回退密码解锁"
  else
    warn "未检测到密码 keyslot,建议保留密码以便回退;添加:cryptsetup luksAddKey ${LUKS_DEV}"
  fi

  # TPM 可用性
  if command -v tpm2_getrandom >/dev/null 2>&1; then
    if tpm2_getrandom 8 >/dev/null 2>&1; then
      note "TPM 设备可用(tpm2_getrandom 成功)"
    else
      warn "TPM 命令失败,重启后可能无法自动解锁,需密码回退"
    fi
  else
    warn "未安装 tpm2-tools,无法测试 TPM"
  fi

  # cmdline 检查
  if [ -f "$CMDLINE_FILE" ]; then
    CMD=$(cat "$CMDLINE_FILE")
    if grep -q "rd.luks.name=${LUKS_UUID}=cryptroot" <<<"$CMD"; then
      note "cmdline 含 rd.luks.name=${LUKS_UUID}=cryptroot"
    else
      warn "cmdline 未包含 rd.luks.name=${LUKS_UUID}=cryptroot"
    fi
    if grep -q "root=/dev/mapper/cryptroot" <<<"$CMD"; then
      note "cmdline root=/dev/mapper/cryptroot 匹配映射名"
    else
      warn "cmdline root= 未匹配 /dev/mapper/cryptroot"
    fi
  else
    warn "未找到 $CMDLINE_FILE"
  fi

  # crypttab 检查(可选)
  if [ -f "$CRYPTTAB_FILE" ]; then
    if grep -q "$LUKS_UUID" "$CRYPTTAB_FILE"; then
      note "crypttab.initramfs UUID 正确"
    else
      warn "crypttab.initramfs 未匹配 LUKS UUID=$LUKS_UUID"
    fi
  fi

  # mkinitcpio HOOKS 检查
  if [ -f "$MKINIT_CONF" ]; then
    HOOKS_LINE=$(grep '^HOOKS=' "$MKINIT_CONF" || true)
    if grep -q "sd-encrypt" <<<"$HOOKS_LINE" && grep -q "systemd" <<<"$HOOKS_LINE"; then
      note "mkinitcpio HOOKS 包含 systemd 与 sd-encrypt"
    else
      warn "mkinitcpio HOOKS 缺少 systemd/sd-encrypt,请修正后 mkinitcpio -P"
    fi
  fi

  # UKI/boot 状态(可选)
  UKI_PATH="/boot/EFI/Linux/arch-linux.efi"
  if [ -f "$UKI_PATH" ]; then
    note "检测到 UKI:$UKI_PATH(时间:$(stat -c '%y' "$UKI_PATH"))"
  else
    warn "未检测到默认 UKI 路径 $UKI_PATH(如使用 GRUB 或自定义路径可忽略)"
  fi
  if command -v bootctl >/dev/null 2>&1; then
    if bootctl status >/dev/null 2>&1; then
      note "bootctl status 可用,确认引导项存在"
    else
      warn "bootctl status 失败,请检查 ESP 挂载与 systemd-boot"
    fi
  fi

  # 权限检查(非阻断)
  if [ -d /boot ]; then
    PERM_BOOT=$(stat -c '%a' /boot)
    if [ "$PERM_BOOT" != "700" ]; then
      warn_perm "/boot 权限为 $PERM_BOOT(系统默认常为 755),可选加固:sudo chmod 700 /boot"
    else
      note "/boot 权限 700"
    fi
  fi
  if [ -f /boot/loader/random-seed ]; then
    PERM_SEED=$(stat -c '%a' /boot/loader/random-seed)
    OWNER_SEED=$(stat -c '%U:%G' /boot/loader/random-seed)
    if [ "$PERM_SEED" != "600" ] || [ "$OWNER_SEED" != "root:root" ]; then
      warn_perm "random-seed 权限/属主为 $PERM_SEED $OWNER_SEED(系统默认可能 755 root:root),可选加固:sudo chown root:root /boot/loader/random-seed && sudo chmod 600 /boot/loader/random-seed"
    else
      note "random-seed 权限与属主符合要求"
    fi
  fi

  # 最终:TPM 解锁自检(仅当无阻断警告时执行)
  if [ "$WARNINGS_BLOCK" -eq 0 ]; then
    if try_unlock_tpm2; then
      note "最终 TPM 解锁验证成功:无需密码即可解锁 LUKS"
      printf "\e[32m[SUCCESS]\e[0m 无阻断警告,TPM 将在启动时自动解锁 LUKS。\n"
    else
      err "最终 TPM 解锁验证失败。可能原因:PCR 变化、Secure Boot 状态变化、UKI 更新。尝试:使用密码解锁后,执行 systemd-cryptenroll ${LUKS_DEV} --wipe-slot=tpm2 && systemd-cryptenroll ${LUKS_DEV} --tpm2-device=auto --tpm2-pcrs=7+11"
      exit 1
    fi
  else
    warn "存在阻断警告,跳过最终 TPM 解锁验证。请先修复上述问题。"
  fi
}

main "$@"

“您的支持是我持续分享的动力”

微信收款码
微信
支付宝收款码
支付宝

采唐
采唐的狐言狐语🦊
公告

欢迎欢迎!这是采唐的新博客页!
最新评论

加载中...