#!/bin/bash
set -euo pipefail
shopt -s inherit_errexit

# shellcheck source=scripts/libcc
. /usr/lib/common-criteria/scripts/libcc

###########################################
# FIPS mode (guide: "In the evaluated configuration FIPS mode MUST be
# enabled"). On x86_64, fips-mode-setup --enable adds fips=1 to the
# bootloader's kernel command line via the same GRUB_CMDLINE_LINUX_DEFAULT
# mechanism as 50-config-crypto-hwaccel's clearcpuid=sha_ni - that only
# takes effect on the *next* boot, so this has the identical "configured
# now, active only after a reboot" shape, checked the same way: config
# files first, then the running kernel's own /proc/cmdline and
# /proc/sys/crypto/fips_enabled as the final, most-authoritative signal.
# IBM Z has no GRUB at all (zipl instead) and isn't handled here yet - same
# "not yet implemented" state as 50-config-crypto-hwaccel's own aarch64/
# s390x branches, rather than assuming a bootloader mechanism that doesn't
# apply there.

GRUB_DEFAULT="/etc/default/grub"

check_52_config_fips() {
	local arch active uses_grub=1
	arch=$(uname -m)

	if [[ "$arch" == ppc64* || "$arch" == "s390x" ]]; then
		uses_grub=0
	fi


# GRUB checks (executed for all architectures except power and s390x)
	if [ "$uses_grub" -eq 1 ]; then
		if [ ! -f "$GRUB_DEFAULT" ]; then
			echo "$GRUB_DEFAULT missing"
			return 1
		fi
		if ! grep -q "fips=1" "$GRUB_DEFAULT"; then
			echo "$GRUB_DEFAULT missing fips=1"
			return 1
		fi
	fi

	if cc_is_chroot; then
		# In a chroot/build environment, we cannot check the running kernel's /proc state.
		return 2
	fi

	if [ "$uses_grub" -eq 1 ]; then
		if ! cc_grub_cfg_has "fips=1"; then
			echo "not every grub.cfg has fips=1 (grub2-mkconfig not yet regenerated with it, or no grub.cfg found at all)"
			return 1
		fi
	fi

	# General FIPS checks (always executed regardless of architecture)
	if ! grep -q "fips=1" /proc/cmdline; then
	    echo "configured correctly, but the running kernel's cmdline does not have fips=1 yet - reboot required"
	    return 2
	fi

	if [ "$(cat /proc/sys/crypto/fips_enabled 2>/dev/null)" != "1" ]; then
	    echo "fips=1 is on the running kernel's cmdline, but /proc/sys/crypto/fips_enabled is not 1 - FIPS self-test may have failed"
	    return 1
	fi

	active=$(update-crypto-policies --show)
	if [ "$active" != "FIPS:OSPP:CC-STRICT" ]; then
	    echo "active crypto policy is '$active', expected FIPS:OSPP:CC-STRICT"
	    return 1
	fi
}

apply_52_config_fips() {
	local arch
	local rc
	arch=$(uname -m)

	# Initial status check (all architectures)
	rc=0
	check_52_config_fips >/dev/null 2>&1 || rc=$?
	if [ "$rc" -eq 0 ]; then
		cc_echo "FIPS mode already active"
		return 0
	fi
	if [ "$rc" -eq 2 ]; then
		cc_echo "FIPS mode already configured, reboot required before it's fully in effect"
		return 2
	fi

	# GRUB configuration (x86 / x86_64 only)
	case "$arch" in
	x86_64)
		# Ensure fips=1 is also explicitly configured in GRUB_DEFAULT (/etc/default/grub).
		# This is necessary because fips-mode-setup --enable uses grubby which may fail to
		# edit /etc/default/grub directly under a build-time chroot or in some SLES versions.
		# Adding fips=1 to GRUB_CMDLINE_LINUX_DEFAULT guarantees the setting persists
		# even if the administrator or a system update later regenerates grub.cfg via grub2-mkconfig.
		if [ -f "$GRUB_DEFAULT" ] && ! grep -q "fips=1" "$GRUB_DEFAULT"; then
			sed '/^GRUB_CMDLINE_LINUX_DEFAULT=/ s/"$/ fips=1"/' "$GRUB_DEFAULT" > "$GRUB_DEFAULT.$$"
			cc_replace "$GRUB_DEFAULT.$$" "$GRUB_DEFAULT"
			rm -f "$GRUB_DEFAULT.$$"
			cc_grub_regenerate
			cc_echo "fips=1 added to $GRUB_DEFAULT and every applicable grub.cfg regenerated"
		fi
		;;
	*)
		# Non-x86 architectures (e.g. s390x, aarch64, ppc64le) rely on native boot tools / fips-mode-setup
		;;
	esac

	# Enable FIPS mode and set crypto policy (all architectures)
	if command -v fips-mode-setup >/dev/null 2>&1; then
		# fips-mode-setup --enable interactively prompts with a 15-second
		# "press control-C to abort" warning unless this is set. It also
		# preserves an already FIPS-based crypto policy rather than
		# resetting it to plain "FIPS" (confirmed in its own source: it
		# only switches to plain FIPS when the current policy is not
		# already FIPS-based), so running this after
		# 51-config-crypto-policies has already set FIPS:OSPP:CC-STRICT
		# is safe regardless of dispatch order.
		if cc_is_chroot; then
			FIPS_MODE_SETUP_SKIP_WARNING=1 fips-mode-setup --enable || cc_echo "WARNING: fips-mode-setup --enable failed in chroot, ignoring"
			update-crypto-policies --set FIPS:OSPP:CC-STRICT || cc_echo "WARNING: update-crypto-policies failed in chroot, ignoring"
		else
			FIPS_MODE_SETUP_SKIP_WARNING=1 fips-mode-setup --enable
			update-crypto-policies --set FIPS:OSPP:CC-STRICT
		fi
	else
		if cc_is_chroot; then
			cc_echo "WARNING: fips-mode-setup and crypto policies not found in chroot, skipping"
		else
			cc_echo "FAILED: fips-mode-setup and crypto policies not found - is the 'fips' pattern/crypto-policies-scripts installed?"
			return 1
		fi
	fi

	# Post-configuration check (all architectures)
	rc=0
	check_52_config_fips >/dev/null 2>&1 || rc=$?
	if [ "$rc" -eq 2 ]; then
		cc_echo "fips=1 configured - reboot required before it's fully in effect"
		return 2
	fi
	if [ "$rc" -ne 0 ]; then
		cc_echo "FAILED: FIPS mode not consistent after fips-mode-setup --enable"
		return 1
	fi
	cc_echo "FIPS mode active"
}

if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
	cc_start_logging
	trap 'cc_exit $?' ERR
	apply_52_config_fips
	cc_exit 0
fi
