#
#   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>

##################################################################
# Define aliases for the grub_* functions we export to other
# parts of fdectl
##################################################################
alias bootloader_enable_fde_without_tpm=grub_enable_fde_without_tpm
alias bootloader_enable_fde_pcr_policy=grub_enable_fde_pcr_policy
alias bootloader_enable_fde_authorized_policy=grub_enable_fde_authorized_policy
alias bootloader_check_sealed_key=grub_check_sealed_key
alias bootloader_remove_sealed_key=grub_remove_sealed_key
alias bootloader_authorize_pcr_policy=grub_authorize_pcr_policy
alias bootloader_set_fde_password=grub_set_fde_password
alias bootloader_get_fde_password=grub_get_fde_password
alias bootloader_commit_config=grub_commit_config
alias bootloader_get_keyslots=grub_get_keyslots
alias bootloader_remove_keyslots=grub_remove_keyslots
alias bootloader_wipe=grub_wipe

##################################################################
# Edit a variable in /etc/default/grub
##################################################################
function grub_set_control {

    sysconfig_set_variable /etc/default/grub "$@"
}

##################################################################
# Configure the boot loader to use a clear-text password to unlock
# the LUKS partition.
# This is useful in several scenarios:
#  - when shipping KVM images with encrypted system partition,
#    we need to be able to unlock the partition with a well-known
#    secret on firstboot.
#  - for headless systems, the installer may choose to set a
#    well-known secret that allows the first reboot to proceed
#    without prompting the user for a password.
##################################################################
function grub_set_fde_password {

    grub_set_control GRUB_CRYPTODISK_PASSWORD "$1"
}

##################################################################
# Obtain the password that protects the LUKS partition.
##################################################################
function grub_get_fde_password {

    . /etc/default/grub

    if [ -z "$GRUB_CRYPTODISK_PASSWORD" ]; then
	return 1
    fi

    echo "$GRUB_CRYPTODISK_PASSWORD"
}

##################################################################
# Update the grub.cfg residing on the EFI partition to properly
# unseal the TPM protected LUKS partition
##################################################################
function grub_update_early_config {

    sealed_key_file="$1"

    grub_set_control GRUB_ENABLE_CRYPTODISK "y"
    grub_set_control GRUB_TPM2_SEALED_KEY "$sealed_key_file"

    # Do not clear the password implicitly; require fdectl or
    # jeos firstboot to do so explicitly.
    # grub_set_control GRUB_CRYPTODISK_PASSWORD ""

    # Note that we *must* recreate grub.cfg here so that the
    # subsequent prediction by pcr-oracle is based on the grub.cfg
    # file that will be hashed into PCR9 on boot
    grub_commit_config
}

function grub_commit_config {

    extra_opts=
    if [ "$(ls /boot/efi/EFI)" = "BOOT" ]; then
	extra_opts="--removable"
    fi

    shim-install --no-grub-install $extra_opts
}

function grub_enable_fde_authorized_policy {

    # Set up grub.cfg
    grub_update_early_config sealed.tpm
}

function grub_authorize_pcr_policy {

    private_key_file="$1"
    sealed_key_file="$2"

    grub_efi_dir=$(uefi_get_current_efidir)
    if [ -z "$grub_efi_dir" ]; then
	return 1
    fi

    # Right now, we support only a single authorization. Down the road,
    # we should probably create sub-directories (using a hash of the
    # PCR policy as name), store the signature inside that subdir, and
    # append the valid signatures into the key file.

    tpm_authorize "$private_key_file" "$sealed_key_file" \
		  "$grub_efi_dir/sealed.tpm"
}

function grub_enable_fde_pcr_policy {

    luks_keyfile="$1"

    grub_efi_dir=$(uefi_get_current_efidir)
    if [ -z "$grub_efi_dir" ]; then
	return 1
    fi

    # First update grub.cfg...
    grub_update_early_config sealed.tpm

    # ... then seal the key against a PCR9 value that covers grub.cfg
    tpm_seal_secret "${luks_keyfile}" "$grub_efi_dir/sealed.tpm"
}

function grub_enable_fde_without_tpm {

    grub_efi_dir=$(uefi_get_current_efidir)
    if [ -z "$grub_efi_dir" ]; then
	return 1
    fi

    # Update grub.cfg inside the EFI partition without enabling the TPM
    # key protector.
    grub_update_early_config
}

function grub_check_sealed_key {

    grub_efi_dir=$(uefi_get_current_efidir)
    if [ -z "$grub_efi_dir" ]; then
	return 1
    fi

    if [ -f "$grub_efi_dir/sealed.tpm" ]; then
        return 0
    fi

    return 1
}

function grub_remove_sealed_key {

    # Remove the sealed key file in the EFI system partition
    grub_efi_dir=$(uefi_get_current_efidir)
    if [ -z "$grub_efi_dir" ]; then
	return 1
    fi
    grub_sealed_key="$grub_efi_dir/sealed.tpm"

    if [ -f "${grub_sealed_key}" ]; then
        rm -f "${grub_sealed_key}"
    fi
}

function grub_get_keyslots {
    local luks_dev=$1

    fdectl-grub-tpm2 list --key-only ${luks_dev}
}

function grub_remove_keyslots {
    local luks_dev=$1
    local keyslots="$2"

    grub_tpm2_slots=$(grub_get_keyslots ${luks_dev})
    if [ "$?" -ne 0 ]; then
        return 1
    fi

    if [ -z "${grub_tpm2_slots}" ]; then
        return 0
    fi

    # Remove all grub-tpm2 keyslots if no keyslot is specified
    if [ -z "${keyslots}" ]; then
        keyslots=${grub_tpm2_slots}
    fi

    # TODO Avoid removing the non-grub-tpm2 keyslot or the last keyslot

    # Remove the keyslots
    for slot in ${keyslots}; do
        cryptsetup luksKillSlot -q ${luks_dev} ${slot}
    done

    fdectl-grub-tpm2 clean ${luks_dev}
}

function grub_wipe {
    local luks_dev=$1

    grub_remove_keyslots ${luks_dev}
}
