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

. /etc/sysconfig/fde-tools

. /usr/share/fde/util
. /usr/share/fde/luks
. /usr/share/fde/tpm
. /usr/share/fde/uefi
. /usr/share/fde/grub2

# dialog functions that use the firstboot plumbing
. /usr/share/fde/ui/dialog

if [ -f /etc/default/grub ]; then
    . /etc/default/grub
fi

##################################################################
# Values and locations used by KIWI
##################################################################
KIWI_ROOT_KEYFILE=/root/.root.keyfile
KIWI_REENCRYPTION_KEYFILE=/run/.kiwi_reencrypt.keyfile

##################################################################
# Aliases are not expanded in non-interactive mode.
# Set the bootloader specific functions here as aliases
##################################################################

function bootloader_enable_fde_without_tpm {
    grub_enable_fde_without_tpm "$@"
}

function bootloader_get_fde_password {
    grub_get_fde_password "$@"
}

function bootloader_platform_parameters {
   grub_platform_parameters
}

function bootloader_rsa_sizes {
    grub_rsa_sizes
}

function bootloader_stop_event {
    grub_stop_event
}

##################################################################
# FDE Firstboot functions
##################################################################

function fde_protect_notpm {

    local luks_dev=$1

    bootloader_enable_fde_without_tpm

    return $?
}

function fde_setup_encrypted {

    root_dev=$1
    luks_keyfile="$2"
    luks_recovery_pass="$3"

    with_pass=false
    with_tpm=false
    with_ccid=false

    is_reencrypted=false

    for method in $FDE_PROTECTION; do
	case $method in
	pass) with_pass=true;;
	tpm)  with_tpm=true;;
	ccid) with_ccid=true;;
	esac
    done

    if ! [[ "$root_dev" =~ /dev/mapper/.* ]]; then
	display_errorbox "root device $root_dev does not look like a dm device"
	return 1
    fi

    luks_name=$(expr "$root_dev" : ".*/\(.*\)")
    luks_dev=$(luks_get_underlying_device "$luks_name")
    if [ -z "$luks_dev" ]; then
	display_errorbox "Unable to determine underlying LUKS device for $root_dev"
	return 1
    fi

    if $with_tpm && ! tpm_test $FDE_KEY_SIZE_BYTES; then
	display_errorbox "TPM key sealing and unsealing does not seem to work"
	return 1
    fi

    # KIWI may save sha256sum of the LUKS header in initrd before reencrypting
    # the root partition. If the checksum differs from the one of the current
    # LUKS header, the root partition is already reencryted.
    luks_hdr_sum_kiwi="`lsinitrd --file root/.luks.header /boot/initrd`"
    if [ "${luks_hdr_sum_kiwi}" != "" ]; then
	cryptsetup luksHeaderBackup ${luks_dev} --header-backup-file /root/.luks.header
	luks_hdr_sum_cur="`sha256sum /root/.luks.header | cut -f1 -d' '`"
	rm -f /root/.luks.header

	if [ "${luks_hdr_sum_cur}" != "${luks_hdr_sum_kiwi}" ]; then
	    is_reencrypted=true
	fi
    fi

    luks_current_password="${luks_recovery_pass}"

    # Check if the installer/imager has created a secondary slot that is protected
    # by a random key.
    # "cryptsetup reencrypt" doesn't really deal all that well with a LUKS
    # header that has more than one valid key slot. To avoid any ugly gymnastics,
    # simply drop that slot.
    if [ -n "$luks_keyfile" ]; then
	# Skip luks_drop_key if the partition is already reencrypted
	if [ "$is_reencrypted" == "false" ]; then
	    if ! luks_drop_key "${luks_dev}" "${luks_keyfile}"; then
		display_errorbox "Failed to remove initial random key"
		return 1
	    fi
	fi

	rm -f "${luks_keyfile}"

	# Replace the key file path in /etc/crypttab with "/.virtual-root.key"
	# to avoid errors when unmounting the LUKS partition (bsc#1218181)
	sed -i "s,${luks_keyfile},/.virtual-root.key,g" /etc/crypttab

	luks_keyfile=""
    fi

    # Change the built-in recovery password to the one provided by the user
    # FIXME: only do this if the password is well-known, eg when dealing with a VM image
    # shipped by us.
    if $with_pass; then
	if luks_change_password "${luks_dev}" "${luks_current_password}"; then
	    luks_current_password="${result_password}"
	else
	    display_errorbox "Failed to change recovery password."
	    with_pass=false
	fi
    fi

    # Write the current password to a file for the later operations
    pass_keyfile=$(luks_write_password pass "${luks_current_password}")

    # Reencrypt with the new password
    # FIXME: only do this if the LUKS master key is well-known, eg when dealing with
    # a VM image.
    if [ "$is_reencrypted" = "false" ]; then
        luks_reencrypt "${luks_dev}" "${pass_keyfile}"
    fi

    if $with_tpm; then
	if ! fdectl regenerate-key --passfile "${pass_keyfile}"; then
	    display_errorbox "Failed to protect encrypted volume with TPM"
	    with_tpm=false
	else
	    systemctl enable fde-tpm-enroll.service
	fi
    else
	# Update grub.cfg to attempt a cryptomount and ask the user for the
	# password
	if ! fde_protect_notpm "${luks_dev}"; then
	    display_errorbox "Failed to protect encrypted volume with password"
	    with_pass=false
	fi
    fi

    # Remove the password file
    rm -f ${pass_keyfile}

    # Update initrd and /boot/grub2/grub.cfg
    if test -d "/boot/writable"; then
	transactional-update initrd grub.cfg
	transactional-update apply
    else
	dracut -f
	grub2-mkconfig -o /boot/grub2/grub.cfg
    fi

    if $with_pass || $with_tpm || $with_ccid; then
	: all is well
    else
	return 1
    fi

}

function fde_setup_unencrypted {

    root_dev=$1
    luks_keyfile="$2"
    luks_recovery_pass="$3"

    if ! [[ "$root_dev" =~ /dev/mapper/.* ]]; then
	display_errorbox "root device $root_dev does not look like a dm device"
        return 1
    fi

    luks_name=$(expr "$root_dev" : ".*/\(.*\)")
    luks_dev=$(luks_get_underlying_device "$luks_name")
    if [ -z "$luks_dev" ]; then
        display_errorbox "Unable to determine underlying LUKS device for $root_dev"
	return 1
    fi

    luks_decrypt "${luks_dev}" "${luks_keyfile}"

    rm -f "${luks_keyfile}"
    rm -f /etc/crypttab

    display_infobox "Re-creating initial ramdisk"
    if ! dracut --force >&2; then
	display_errorbox "Failed to rebuild initrd"
	return 1
    fi

    return 0
}


function __fde_valid_protections {

    pass_warn=true
    for tag in $*; do
        case $tag in
        pass) pass_warn=false ;;
        tpm) : ;;
        *)
	    display_errorbox "FDE key protection scheme $tag not yet implemented"
	    return 1;;
        esac
    done

    if $pass_warn; then
        display_errorbox "Pass phrase is mandatory"
        return 1
    fi

    return 0
}

##################################################################
# Check what protection method the system supports and let the
# user choose.
# returns false if the user declined device encryption
# The ccid thing is there just as a teaser for now :-)
##################################################################
function fde_choose_protection {

    declare -g FDE_PROTECTION
    declare -a options

    FDE_PROTECTION=""

    message="This system can be installed with an encrypted root and boot partition. Please choose the desired protection method(s) or press Cancel to install without encryption"
    options+=(pass 'Pass phrase' on)

    if ! tpm_present_and_working; then
	display_errorbox "This system does not seem to have a working TPM device."
    elif ! uefi_secure_boot_enabled; then
	display_errorbox "This system does not seem to use Secure Boot. Full disk encryption with TPM2 disabled"
    else
    	options+=(tpm 'Stored inside the TPM chip' on)
    fi

    # Disable the ccid option until we really implement it
    # options+=(ccid 'Stored inside a CCID capable token' off)

    while true; do
        d --title "Full Disk Encryption" --checklist \
			"$message" 12 74 4 \
			"${options[@]}"

	if [ $? -ne 0 ]; then
	    FDE_PROTECTION=""
	    return 1
	fi

	FDE_PROTECTION="$result"
	fde_trace "user selected protections: <$FDE_PROTECTION>"

	if __fde_valid_protections $FDE_PROTECTION; then
	    break
	fi
    done

    return 0
}

function fde_firstboot {

    if fde_choose_protection; then
	if fde_setup_encrypted "$@"; then
	    return 0
	fi

	display_errorbox "Failed to set up for full disk encryption. Trying to recover"
    fi

    fde_setup_unencrypted "$@"
}

function fde_systemd_firstboot {

    display_infobox "Full Disk Encryption with TPM2 support"

    # Redirect the fde_trace messages from stderr to journald
    exec 2> >(systemd-cat -t fde-tools -p info)

    if [ -f "$KIWI_REENCRYPTION_KEYFILE" ]; then
        # Use the reencryption password from KIWI
        fde_root_passphrase="$(<${KIWI_REENCRYPTION_KEYFILE})"
    else
	# Try the default password
	fde_root_passphrase=$(bootloader_get_fde_password)
    fi

    if [ -z "$fde_root_passphrase" ]; then
	display_errorbox "Cannot find the initial FDE password for the root file system"
	return 1
    fi

    if [ ! -s "$KIWI_ROOT_KEYFILE" ]; then
	KIWI_ROOT_KEYFILE=""
    fi

    fde_firstboot $(luks_device_for_path "/") "$KIWI_ROOT_KEYFILE" "$fde_root_passphrase"

    fde_clean_tempdir
}

function fde_post {

    :
}

function fde_cleanup {

    :
}
