#
#   Copyright (C) 2022, 2023 SUSE LLC
#
#   This program is free software; you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation; either version 2 of the License, or
#   (at your option) any later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with this program; if not, write to the Free Software
#   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
#   Written by Olaf Kirch <okir@suse.com>

. $SHAREDIR/commands/remove-secondary-password

alias cmd_requires_luks_device=true
alias cmd_perform=cmd_tpm_enable

function tpm_enable {

    local luks_devices="$1"

    st=1

    # The installer may have set a random password to avoid the password
    # prompt on firstboot.
    # We must clear this before computing any PCR policies, otherwise
    # we end up hashing the wrong grub.cfg file
    remove_secondary_password "${luks_devices}"

    if [[ "$FDE_USE_AUTHORIZED_POLICIES" =~ y.* ]]; then
	if [ -z "$FDE_AUTHORIZED_POLICY" ]; then
	    display_errorbox "Cannot enable TPM - authorized policies requested, but none created yet"
	    return 1
	fi

	# Tell the boot loader about the authorized policy and the misc files
	# that go with it...
	if tpm_enable_authorized_policy "$luks_devices"; then
	    # ... and authorize the current system configuration.
	    # This is what "fdectl tpm-authorize" does, inlined.
	    bootloader_authorize_pcr_policy "$FDE_AP_SECRET_KEY" "$FDE_AP_SEALED_SECRET"
	    st=$?
	fi
    elif [ -n "$opt_keyfile" ]; then
	# We were invoked with --keyfile "/foo/bar"
	tpm_enable_pcr_policy "${luks_devices}" "$opt_keyfile"
	st=$?
    elif [ -n "$FDE_ENROLL_NEW_KEY" ]; then
	# We're asked to enroll a secondary key. Do so, and zap the
	# setting in /etc/sysconfig/fde-tools
	tpm_enable_pcr_policy "${luks_devices}" "$FDE_ENROLL_NEW_KEY"
	st=$?

	rm -vf "$FDE_ENROLL_NEW_KEY"
	fde_set_variable FDE_ENROLL_NEW_KEY ""
    else
	# We are just checkin' whether anything needs to be done.
	# This is usually called from the fde activation service (by
	# systemd). Obviously, there was no key to enroll, so we might
	# as well return.
	#
	# However, if there was a firstboot password, fall thru to remove the password.
	# After all, it's supposed to be good for one reboot only.
	if [ "$(bootloader_get_fde_password)" = "" ]; then
	    return 0
	fi
	st=0
    fi

    if ! bootloader_commit_config; then
	display_errorbox "Failed to update bootloader configuration"
	return 1
    fi

    return $st
}

function tpm_enable_authorized_policy {

    local luks_devices="$1"

    # When we get here, the installer should already have
    #  - created an authorized policy
    #  - added a secondary key
    #  - signed the secondary key against the authorized policy
    if [ -n "$FDE_ENROLL_NEW_KEY" ]; then
	display_errorbox "Bad, there's a secondary key in $FDE_ENROLL_NEW_KEY - please remove from LUKS volume"
	return 1
    fi

    tpm_set_authorized_policy_paths "$FDE_AUTHORIZED_POLICY"

    # The secret key should have been sealed to the authorized policy during
    # add-secondary-key already.
    if [ ! -f "$FDE_AP_SEALED_SECRET" ]; then
	display_errorbox "Cannot initialize TPM based boot with authorized policy - no sealed key found"
	return 1
    fi

    # Copy authorized policy and other constant files to the ESP, and
    # tell the boot loader to use these.
    # Eventually, we will bless a specfic configuration by creating a PCR policy
    # and signing it with our RSA key.
    bootloader_enable_fde_authorized_policy "$FDE_AP_SEALED_SECRET" \
		    "$FDE_AP_AUTHPOLICY" "$FDE_AP_PUBLIC_KEY"
    return $?
}

function tpm_enable_pcr_policy {

    local luks_devices="$1"
    local luks_keyfile="$2"

    if [ -n "$luks_keyfile" ]; then
	if [ ! -f "$luks_keyfile" ]; then
	    display_errorbox "Cannot enroll new FDE key $luks_keyfile: file not found"
	    return 1
	fi

	# We consider the key compromised, because it resided on disk - even if only
	# for a short amount of time. It may have made its way into a btrfs snapshot,
	# which may hang around forever...
	# So what we do here is generate a new key and replace the key slot with the
	# compromised key with this new key. Note that the new key is created below
	# /dev/shm, which is an in-memory file system.
	luks_new_keyfile=$(fde_make_tempfile new.key)
	luks_generate_random_key ${luks_new_keyfile}
	for luks_dev in ${luks_devices}; do
	    if ! luks_set_key "$luks_dev" "$luks_keyfile" "$luks_new_keyfile"; then
		display_errorbox "Failed to change secondary LUKS key(${luks_dev})"
		rm -f "$luks_keyfile" "$luks_new_keyfile"
		return 1
	    fi
	done
    else
	luks_keyfile=$(fde_make_tempfile pass.key)
	if ! fde_request_recovery_passfile "$luks_keyfile"; then
	    display_errorbox "Unable to obtain recovery password; aborting."
	    return 1
	fi

	for luks_dev in ${luks_devices}; do
	    if ! luks_verify_password "$luks_dev" "$luks_keyfile"; then
		rm -f "$luks_keyfile"
		display_errorbox "Failed to verify password on LUKS partition(${luks_dev})"
		return 1
	    fi
	done

	luks_new_keyfile=$(fde_make_tempfile new.key)

	luks_generate_random_key ${luks_new_keyfile}
	for luks_dev in ${luks_devices}; do
	    if ! luks_add_key "${luks_dev}" "${luks_keyfile}" "${luks_new_keyfile}"; then
		display_errorbox "Failed to add secondary LUKS key(${luks_dev})"
		rm -f "$luks_keyfile" "$luks_new_keyfile"
		return 1
	    fi
	done

	rm -f "$luks_keyfile"
    fi

    if ! bootloader_enable_fde_pcr_policy "${luks_new_keyfile}"; then
	display_errorbox "Failed to protect encrypted volume with TPM"
	return 1
    fi

    rm -f "$luks_new_keyfile"
}

function cmd_tpm_enable {
    if ! tpm_enable "$1"; then
	return 1
    fi

    return 0
}
