#!/usr/bin/bash
#
# SPDX-License-Identifier: GPL-2.0-or-later
#
# Copyright (c) 2024, Oracle and/or its affiliates.
#
# 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
#
set -euo pipefail

X509_CA_NICKNAME="myca"
X509_KERNEL_NICKNAME="kernel"
X509_MODULE_NICKNAME="module"
X509_IMA_NICKNAME="ima"
X509_DB_NICKNAME="db"
X509_KEK_NICKNAME="kek"
X509_PK_NICKNAME="pk"
SIGN_DIR="etc/sign"
SIGN_CONF="/etc/sign/sign.conf"
SIGN_CONF_LOCAL="/etc/sign/local.conf"
RANDOM_NUMBER=$((RANDOM % 90000000 + 10000000))

if [[ -s $SIGN_CONF ]]; then
	# shellcheck source=./sign.conf
	. "$SIGN_CONF"
else
	echo "Unable to find sign config file" 1>&2
	exit 1
fi

if [[ -s $SIGN_CONF_LOCAL ]]; then
	# shellcheck source=./local.conf
	. "$SIGN_CONF_LOCAL"
fi

init_setup_usage='
  init-setup			Initialize and enroll keys (call once)'
usage+=$init_setup_usage
commands+="init-setup"

init-ima() {

	TMPCSR=$(mktemp -t XXXXXXXX.pem)
	TMPCA=$(mktemp -t XXXXXXXX)
	TMPCAPEM=$(mktemp -t XXXXXXXX.pem)

	trap 'rm -f -- "$TMPCSR" "$TMPCA" "$TMPCAPEM"' EXIT

	openssl req -new  -nodes -utf8 -sha512 -out "$TMPCSR" -keyout "$IMA_PRIV_KEY" \
		-subj '/'"$X509_IMA_COMMON_NAME" -config <( \
		printf "[cert]\n%s\n[req]\ndistinguished_name =  \
		cert\ndefault_bits = 4096\n" "$X509_IMA_COMMON_NAME")

	pk12util -o "$TMPCA" -n $X509_CA_NICKNAME -d "$NSSDIR" -W $RANDOM_NUMBER
	openssl pkcs12 -in "$TMPCA" -out "$TMPCAPEM" -nodes -passin pass:$RANDOM_NUMBER

	openssl x509 -req -in "$TMPCSR" -days 3650 -CA "$TMPCAPEM" -CAkey "$TMPCAPEM" \
		-CAcreateserial -outform DER -out "$IMA_CERT"  -extensions EXT \
		-extfile <( \
		printf "[EXT]\nkeyUsage=digitalSignature\n\
		extendedKeyUsage=codeSigning\nsubjectKeyIdentifier=hash\n\
		authorityKeyIdentifier=keyid:always,issuer")

	rm -f -- "$TMPCSR" "$TMPCA" "$TMPCAPEM"
	trap - EXIT
}

init-setup() {

	if [ -f "$MOK_CA_CERT" ]; then
		echo "Already initialized"
		exit 1
	fi

        if [[ -v LEGACY_KEYGEN ]]; then
		ca_arg="-C -k"
	else
		ca_arg="-C"
	fi

	efikeygen -d "$NSSDIR" $ca_arg -n $X509_CA_NICKNAME -S  \
			--common-name="""$X509_CA_COMMON_NAME"""

	efikeygen -d "$NSSDIR" -n $X509_KERNEL_NICKNAME --signer $X509_CA_NICKNAME -k \
			--common-name="""$X509_KERNEL_COMMON_NAME"""

	efikeygen -d "$NSSDIR" -n $X509_IMA_NICKNAME --signer $X509_CA_NICKNAME -k \
			--common-name="""$X509_IMA_COMMON_NAME"""

	efikeygen -d "$NSSDIR" -n $X509_MODULE_NICKNAME --signer $X509_CA_NICKNAME -m \
			--common-name="""$X509_MODULE_COMMON_NAME"""

	efikeygen -d "$NSSDIR" $ca_arg -n $X509_DB_NICKNAME -S \
			--common-name="""$X509_DB_COMMON_NAME"""

	efikeygen -d "$NSSDIR" $ca_arg -n $X509_KEK_NICKNAME -S \
			--common-name="""$X509_KEK_COMMON_NAME"""

	efikeygen -d "$NSSDIR" $ca_arg -n $X509_PK_NICKNAME -S \
			--common-name="""$X509_PK_COMMON_NAME"""

	certutil -L -d "$NSSDIR" -n $X509_KERNEL_NICKNAME -o "$MOK_KERNEL_CERT" -r
	certutil -L -d "$NSSDIR" -n $X509_MODULE_NICKNAME -o "$MOK_MODULE_CERT" -r
	certutil -L -d "$NSSDIR" -n $X509_IMA_NICKNAME -o "$IMA_CERT" -r
	certutil -L -d "$NSSDIR" -n $X509_DB_NICKNAME -o "$DB_CERT" -r
	certutil -L -d "$NSSDIR" -n $X509_KEK_NICKNAME -o "$KEK_CERT" -r
	certutil -L -d "$NSSDIR" -n $X509_PK_NICKNAME -o "$PK_CERT" -r
	certutil -L -d "$NSSDIR" -n $X509_CA_NICKNAME -o "$MOK_CA_CERT" -r
	certutil -L -d "$NSSDIR_OL" -n """$X509_TEST_NICKNAME""" -o "$TEST_CERT" -r

	init-ima

	mokutil --import "$MOK_MODULE_CERT" "$MOK_KERNEL_CERT" "$MOK_CA_CERT" "$TEST_CERT" --root-pw
	echo "****************************************************************************"
	echo "* REBOOT MACHINE AND ENROLL NEW MOK KEYS (use root password in mokmanager) *"
	echo "****************************************************************************"
}

init-check() {
	if [ ! -f "$MOK_CA_CERT" ]; then
		echo "Uninitialized call init-setup first"
		exit 1
	fi
}

destroy_setup_usage='
  destroy-setup			Destory setup and unenroll keys'
usage+=$destroy_setup_usage
commands+="|destroy-setup"

destroy-setup() {
	init-check
	certutil -d "$NSSDIR" -D -n $X509_CA_NICKNAME
	certutil -d "$NSSDIR" -D -n $X509_KERNEL_NICKNAME
	certutil -d "$NSSDIR" -D -n $X509_MODULE_NICKNAME
	certutil -d "$NSSDIR" -D -n $X509_IMA_NICKNAME
	certutil -d "$NSSDIR" -D -n $X509_DB_NICKNAME
	certutil -d "$NSSDIR" -D -n $X509_KEK_NICKNAME
	certutil -d "$NSSDIR" -D -n $X509_PK_NICKNAME
	mokutil --delete "$MOK_CA_CERT" "$MOK_KERNEL_CERT" "$MOK_MODULE_CERT" "$TEST_CERT" --root-pw
	rm -f "$MOK_KERNEL_CERT"
	rm -f "$MOK_MODULE_CERT"
	rm -f "$IMA_CERT"
	rm -f "$DB_CERT"
	rm -f "$KEK_CERT"
	rm -f "$PK_CERT"
	rm -f "$MOK_CA_CERT"
	rm -f "$TEST_CERT"
	echo "***************************************************************************"
	echo "* REBOOT MACHINE NOW TO DELETE MOK KEYS (use root password in mokmanager) *"
	echo "***************************************************************************"
}

setuppriv() {
	TMPP12=$(mktemp -t XXXXXXXXXX.p12)
	TMPPRIV=$(mktemp -t XXXXXXXXXX.priv)
	trap 'rm -f -- "$TMPP12" "$TMPPRIV"' EXIT
}

destroypriv(){
	rm -f -- "$TMPPRIV" "$TMPP12"
	trap - EXIT
}

ima_usage='
  ima <file name>		Sign the file with the ima key'
usage+=$ima_usage
commands+="|ima"

ima() {
	init-check
	evmctl ima_sign --key "$IMA_PRIV_KEY" "$1"
}

module_usage='
  module <module name>		Sign the module with the module key'
usage+=$module_usage
commands+="|module"

module() {
	init-check
	setuppriv
	pk12util -o "$TMPP12" -n $X509_MODULE_NICKNAME -d "$NSSDIR" -W $RANDOM_NUMBER
	openssl pkcs12 -in "$TMPP12" -out "$TMPPRIV" -nocerts -nodes -passin pass:$RANDOM_NUMBER
	SFILE=$(echo "$SIGN_FILE" | cut -f1 -d ' ')
	eval "$($SFILE sha256 "$TMPPRIV" "$MOK_MODULE_CERT" "$1")"
	destroypriv
}

backup_usage='
  backup <file name>		Backup everything to move to a different machine'
usage+=$backup_usage
commands+="|backup"

backup() {
	if [ $# -lt 1 ]; then
		echo "$backup_usage"
		exit 1
	fi
	init-check
	read -rsp "Enter new backup password: " password
	pk12util -o "$MOK_KERNEL_KEY" -n $X509_KERNEL_NICKNAME -d "$NSSDIR" -W "$password"
	pk12util -o "$MOK_MODULE_KEY" -n $X509_MODULE_NICKNAME -d "$NSSDIR" -W "$password"
	pk12util -o "$DB_KEY" -n $X509_DB_NICKNAME -d "$NSSDIR" -W "$password"
	pk12util -o "$KEK_KEY" -n $X509_KEK_NICKNAME -d "$NSSDIR" -W "$password"
	pk12util -o "$PK_KEY" -n $X509_PK_NICKNAME -d "$NSSDIR" -W "$password"
	pk12util -o "$IMA_CA_KEY" -n $X509_IMA_NICKNAME -d "$NSSDIR" -W "$password"
	tar -czf "$1" -C / $SIGN_DIR/
}

restore_usage='
  restore <file name>		Restore everything from a different machine'
usage+=$restore_usage
commands+="|restore"

restore() {
	if [ $# -lt 1 ]; then
		echo "$restore_usage"
		exit 1
	fi
	if [ -f "$MOK_CA_CERT" ]; then
		echo "Restore failed machine is already setup"
		exit 1
	fi
	tar -xzf "$1" -C /
	read -rsp "Enter restore password: " password
	pk12util -i "$MOK_KERNEL_KEY" -n $X509_KERNEL_NICKNAME -d "$NSSDIR" -W "$password"
	pk12util -i "$MOK_MODULE_KEY" -n $X509_MODULE_NICKNAME -d "$NSSDIR" -W "$password"
	pk12util -i "$DB_KEY" -n $X509_DB_NICKNAME -d "$NSSDIR" -W "$password"
	pk12util -i "$KEK_KEY" -n $X509_KEK_NICKNAME -d "$NSSDIR" -W "$password"
	pk12util -i "$PK_KEY" -n $X509_PK_NICKNAME -d "$NSSDIR" -W "$password"
	pk12util -i "$IMA_CA_KEY" -n $X509_IMA_NICKNAME -d "$NSSDIR" -W "$password"
	rm -f "$MOK_KERNEL_KEY" "$MOK_MODULE_KEY" "$DB_KEY" "$KEK_KEY" "$PK_KEY" "$IMA_CA_KEY"
	mokutil --import "$MOK_MODULE_CERT" "$MOK_KERNEL_CERT" "$MOK_CA_CERT" "$TEST_CERT" --root-pw
	echo "****************************************************************************"
	echo "* REBOOT MACHINE AND ENROLL NEW MOK KEYS (use root password in mokmanager) *"
	echo "****************************************************************************"
}

kernel_usage='
  kernel <kernal name>		Sign the kernel with the kernel key '
usage+=$kernel_usage
commands+="|kernel"

kernel() {
	init-check
	TMPKERNEL=$(mktemp -t XXXXXXXX)
	trap 'rm -f -- "$TMPKERNEL"' EXIT
	pesign --force -i "$1" -o "$TMPKERNEL" -c $X509_KERNEL_NICKNAME -s
	mv "$TMPKERNEL" "$1"
	trap - EXIT
}

bootloader_usage='
  bootloader <bootloader name>	Sign the bootloader with the db key '
usage+=$bootloader_usage
commands+="|bootloader"

bootloader() {
	init-check
	TMPBOOTLOADER=$(mktemp -t XXXXXXXX)
	trap 'rm -f -- "$TMPBOOTLOADER"' EXIT
	pesign --force -i "$1" -o "$TMPBOOTLOADER" -c $X509_DB_NICKNAME -s
	mv "$TMPBOOTLOADER" "$1"
	trap - EXIT
}

if [ $# -lt 1 ]; then
	echo "Usage $0 [$commands]"
	exit 0
fi

command=$1 ; shift

case "$command" in
	-h|--help)
		echo "sign - Signing tools for Developers"
		echo "$usage"
		exit 0
		;;
	init-setup|destroy-setup|backup|restore|module|kernel|ima|bootloader)
		$command "$@"
		;;
	*)
		echo "$0: subcommand $command unknown"
		exit 255
		;;
esac
