#!/bin/bash
# LUKS related functions

. /etc/functions
. /etc/gui_functions
. /tmp/config

#List all LUKS devices on the system
list_luks_devices() {
	#generate a list of devices to choose from that contain a LUKS header
	lvm vgscan || true
	blkid | cut -d ':' -f 1 | while read device; do
		if cryptsetup isLuks $device; then echo $device; fi
	done | sort
}

#Whiptail prompt asking user to select ratio of device to use for LUKS container between: 25, 50, 75
select_luks_container_size_percent() {
	TRACE_FUNC
	if [ -x /bin/whiptail ]; then
		#whiptail prompt asking user to select ratio of device to use for LUKS container between: 25, 50, 75
		#whiptail returns the percentage of the device to use for LUKS container
		whiptail --title "Select LUKS container size percentage of device" --menu \
			"Select LUKS container size percentage of device:" 0 80 10 \
			"10" "10%" \
			"25" "25%" \
			"50" "50%" \
			"75" "75%" \
			2> /tmp/luks_container_size_percent \
			|| die "Error selecting LUKS container size percentage of device"
	else
		#console prompt asking user to select ratio of device to use for LUKS container between: 10, 25, 50, 75
		#console prompt returns the percentage of the device to use for LUKS container
		echo "Select LUKS container size percentage of device:"
		echo "1. 10%"
		echo "2. 25%"
		echo "3. 50%"
		echo "4. 75%"
		read -p "Choose your LUKS container size percentage of device [1-3]: " option_index
		if [ "$option_index" = "1" ]; then
			echo "10" > /tmp/luks_container_size_percent
		elif [ "$option_index" = "2" ]; then
			echo "25" > /tmp/luks_container_size_percent
		elif [ "$option_index" = "3" ]; then
			echo "50" > /tmp/luks_container_size_percent
		elif [ "$option_index" = "4" ]; then
			echo "75" > /tmp/luks_container_size_percent
		else
			die "Error selecting LUKS container size percentage of device"
		fi
	fi
}

# Partition a device interactively with two partitions: a LUKS container
# containing private ext4 partition and second public exFAT partition
# Size provisioning is done by percentage of the device
interactive_prepare_thumb_drive()
{
	TRACE_FUNC
	#Refactoring: only one parameter needed to be prompted for: the passphrase for LUKS container if not coming from oem-provisioning
	#If no passphrase was provided, ask user to select passphrase for LUKS container
	# if no device provided as parameter, we will ask user to select device to partition
	# if no percentage provided as parameter, we will default to 10% of device to use for LUKS container
	# we will validate parameters and not make them positional and print a usage function first
	
	#Set defaults
	DEVICE="" #Will list all usb storage devices if not provided as parameter
	PERCENTAGE="10" #default to 10% of device to use for LUKS container (requires a LUKS partition bigger then 32mb!)
	PASSPHRASE="" #Will prompt user for passphrase if not provided as parameter
	
	#Parse parameters
	while [ $# -gt 0 ]; do
		case "$1" in
			--device)
				DEVICE=$2
				shift 2
				;;
			--percentage)
				PERCENTAGE=$2
				shift 2
				;;
			--pass)
				PASSPHRASE=$2
				shift 2
				;;
			*)
				echo "usage: prepare_thumb_drive [--device device] [--percentage percentage] [--pass passphrase]"
				return 1
				;;
		esac
	done

	DEBUG "DEVICE to partition: $DEVICE"
	DEBUG "PERCENTAGE of device that will be used for LUKS container: $PERCENTAGE"
	#Output provided if passphrase is provided as parameter
	DEBUG "PASSPHRASE for LUKS container: ${PASSPHRASE:+provided}"

	#Prompt for passphrase if not provided as parameter
	if [ -z "$PASSPHRASE" ]; then
		#If no passphrase was provided, ask user to select passphrase for LUKS container
		#console based no whiptail
		while [[ ${#PASSPHRASE} -lt 8 ]]; do
		{
			echo -e "\nEnter passphrase for LUKS container (At least 8 characters long):"
			#hide passphrase input from read command
			read -r -s PASSPHRASE
			#skip confirmation if passphrase is less then 8 characters long (continue)
			if [[ ${#PASSPHRASE} -lt 8 ]]; then
				echo -e "\nPassphrase must be at least 8 characters long. Please try again."
				unset PASSPHRASE
				continue
			fi
			#validate passphrase and ask user to re-enter if not at least 8 characters long
			#confirm passphrase
			echo -e "\nConfirm passphrase for LUKS container:"
			#hide passphrase input from read command
			read -r -s PASSPHRASE_CONFIRM
			#compare passphrase and passphrase confirmation
			if [ "$PASSPHRASE" != "$PASSPHRASE_CONFIRM" ]; then
				echo -e "\nPassphrases do not match. Please try again."
				unset PASSPHRASE
				unset PASSPHRASE_CONFIRM
			fi

		};done
	fi

	#If no device was provided, ask user to select device to partition
	if [ -z "$DEVICE" ]; then
		#warn user to disconnect all external drives
		if [ -x /bin/whiptail ]; then
			whiptail $BG_COLOR_WARNING --title "WARNING: Disconnect all external drives" --msgbox \
				"WARNING: Please disconnect all external drives before proceeding.\n\nHit Enter to continue." 0 80 \
				|| die "User cancelled wiping and repartitioning of $DEVICE"
		else
			echo -e -n "Warning: Please disconnect all external drives before proceeding.\n\nHit Enter to continue?"
			read -r -p " [Y/n] " response
			#transform response to uppercase with bash parameter expansion
			response=${response^^}
			#continue if response different then uppercase N
			if [[ $response =~ ^(N)$ ]]; then
				die "User cancelled wiping and repartitioning of $DEVICE"
			fi
		fi

		#enable usb
		enable_usb
		#enable usb storage
		enable_usb_storage

		#list all usb storage devices
		list_usb_storage disks > /tmp/devices.txt
		if [ $(cat /tmp/devices.txt | wc -l) -gt 0 ]; then
			file_selector "/tmp/devices.txt" "Select device to partition"
			if [ "$FILE" == "" ]; then
				die "Error: No device selected"
			else
				DEVICE=$FILE
			fi
		else
			die "Error: No device found"
		fi
	fi

	#Check if device is a block device
	if [ ! -b $DEVICE ]; then
		die "Error: $DEVICE is not a block device"
	fi

	if [ -z "$PERCENTAGE" ]; then
		#If no percentage was provided, ask user to select percentage of device to use for LUKS container
		select_luks_container_size_percent
		PERCENTAGE=$(cat /tmp/luks_container_size_percent)
	fi

	confirm_thumb_drive_format "$DEVICE" "$PERCENTAGE" ||
		die "User cancelled wiping and repartitioning of $DEVICE"

	prepare_thumb_drive "$DEVICE" "$PERCENTAGE" "$PASSPHRASE"
}

# Show a prompt to confirm formatting a flash drive with a percentage allocated
# to LUKS.  interactive_prepare_thumb_drive() uses this; during OEM reset it is
# used separately before performing any reset actions
#
# parameters:
# $1 - block device of flash drive
# $2 - percent of device allocated to LUKS [1-99]
confirm_thumb_drive_format()
{
	TRACE_FUNC
	local DEVICE LUKS_PERCENTAGE DISK_SIZE_BYTES DISK_SIZE_DISPLAY LUKS_PERCENTAGE LUKS_SIZE_MB MSG

	DEVICE="$1"
	LUKS_PERCENTAGE="$2"

	LUKS_SIZE_MB=

	#Get disk size in bytes
	DISK_SIZE_BYTES="$(blockdev --getsize64 "$DEVICE")"
	DISK_SIZE_DISPLAY="$(display_size "$DISK_SIZE_BYTES")"
	#Convert disk size to MB
	DISK_SIZE_MB=$((DISK_SIZE_BYTES/1024/1024))
	#Calculate percentage of device in MB
	LUKS_SIZE_MB="$((DISK_SIZE_BYTES*LUKS_PERCENTAGE/100/1024/1024))"

	MSG="WARNING: Wiping and repartitioning $DEVICE ($DISK_SIZE_DISPLAY) with $LUKS_SIZE_MB MB\n assigned to private LUKS ext4 partition,\n rest assigned to exFAT public partition.\n\nAre you sure you want to continue?"
	if [ -x /bin/whiptail ]; then
		whiptail $BG_COLOR_WARNING --title "WARNING: Wiping and repartitioning $DEVICE ($DISK_SIZE_DISPLAY)" --yesno \
			"$MSG" 0 80
	else
		echo -e -n "$MSG"
		read -r -p " [Y/n] " response
		#transform response to uppercase with bash parameter expansion
		response=${response^^}
		#continue if response is Y, y, or empty, abort for anything else
		if [ -n "$response" ] && [ "${response^^}" != Y ]; then
			return 1
		fi
	fi
}

# Prepare a flash drive with a private LUKS-encrypted ext4 partition and a
# public exFAT partition.  This is not interactive - during OEM reset, any
# selections/confirmations must occur before OEM reset starts resetting the
# system.
#
# $1 - block device of flash drive
# $2 - percentage of flash drive to allocate to LUKS [1-99]
# $3 - passphrase for LUKS container
prepare_thumb_drive()
{
	TRACE_FUNC

	local DEVICE PERCENTAGE PASSPHRASE DISK_SIZE_BYTES PERCENTAGE_MB
	DEVICE="$1"
	PERCENTAGE="$2"
	PASSPHRASE="$3"

	#Get disk size in bytes
	DISK_SIZE_BYTES="$(blockdev --getsize64 "$DEVICE")"
	#Calculate percentage of device in MB
	PERCENTAGE_MB="$((DISK_SIZE_BYTES*PERCENTAGE/100/1024/1024))"

	echo -e "Preparing $DEVICE with $PERCENTAGE_MB MB for private LUKS container while rest of device will be assigned to exFAT public partition...\n"
	echo "Please wait..."
	DEBUG "Creating empty DOS partition table on device through fdisk to start clean"
	echo -e "o\nw\n" | fdisk $DEVICE >/dev/null 2>&1 || die "Error creating partition table"
	DEBUG "partition device with two partitions: first one being the percent applied and rest for second partition through fdisk"
	echo -e "n\np\n1\n\n+"$PERCENTAGE_MB"M\nn\np\n2\n\n\nw\n" | fdisk $DEVICE >/dev/null 2>&1 || die "Error partitioning device"
	DEBUG "cryptsetup luksFormat  first partition with LUKS container aes-xts-plain64 cipher with sha256 hash and 512 bit key"
	DEBUG "Creating ${PERCENTAGE_MB}MB LUKS container on ${DEVICE}1..."
	DO_WITH_DEBUG cryptsetup --batch-mode -c aes-xts-plain64 -h sha256 -s 512 -y luksFormat ${DEVICE}1 \
		--key-file <(echo -n "${PASSPHRASE}") > /dev/null 2>&1 \
		|| die "Error formatting LUKS container"
	DEBUG "Opening LUKS device and mapping under /dev/mapper/private..."
	DO_WITH_DEBUG cryptsetup open ${DEVICE}1 private --key-file <(echo -n "${PASSPHRASE}") > /dev/null 2>&1 \
		|| die "Error opening LUKS container"
	DEBUG "Formatting LUKS container mapped under /dev/mapper/private as an ext4 partition..."
	mke2fs -t ext4 -L private /dev/mapper/private >/dev/null 2>&1 || die "Error formatting LUKS container's ext4 filesystem"
	DEBUG "Closing LUKS device /dev/mapper/private..."
	cryptsetup close private > /dev/null 2>&1 || die "Error closing LUKS container"
	DEBUG "Formatting second partition ${DEVICE}2 with exfat filesystem..."
	mkfs.exfat -L public ${DEVICE}2 >/dev/null 2>&1 || die "Error formatting second partition with exfat filesystem"
	echo "Done."
}

select_luks_container()
{
	TRACE_FUNC
	if [ -s /boot/kexec_key_devices.txt ]; then
		DEBUG "Reusing known good LUKS container device from /boot/kexec_key_devices.txt"
		DEBUG "LUKS container device: $(cut -d ' ' -f1 /boot/kexec_key_devices.txt)"
		LUKS=$(cut -d ' ' -f1 /boot/kexec_key_devices.txt)
	else
		list_luks_devices > /tmp/luks_devices.txt
		#if /tmp/luks_devices.txt exists and is not empty
		if [ -s /tmp/luks_devices.txt ]; then
			file_selector "/tmp/luks_devices.txt" "Select LUKS container device"
			if [ "$FILE" == "" ]; then
				return 1
			else
				LUKS=$FILE
				detect_boot_device
				mount -o remount,rw /boot
				echo "$LUKS $(cryptsetup luksUUID $LUKS)" >/boot/kexec_key_devices.txt
				mount -o remount,ro /boot
			fi
		else
			warn "No encrypted device found"
			return 1
		fi
	fi
}

test_luks_current_disk_recovery_key_passphrase()
{
	TRACE_FUNC
	while :; do
		select_luks_container || return 1
		if [ -z "$luks_current_Disk_Recovery_Key_passphrase" ]; then
			#if no external provisioning provides current LUKS Disk Recovery Key passphrase
			echo -e "\nEnter the current LUKS Disk Recovery Key passphrase (Configured at OS installation or by OEM):"
			read -r luks_current_Disk_Recovery_Key_passphrase
			echo -n "$luks_current_Disk_Recovery_Key_passphrase" >/tmp/luks_current_Disk_Recovery_Key_passphrase
			warn "Testing opening "$LUKS" LUKS encrypted drive content with the current LUKS Disk Recovery Key passphrase..."
			cryptsetup open $LUKS test --key-file /tmp/luks_current_Disk_Recovery_Key_passphrase
		else
			echo -n "$luks_current_Disk_Recovery_Key_passphrase" >/tmp/luks_current_Disk_Recovery_Key_passphrase
			warn "Testing opening "$LUKS" LUKS encrypted drive content with the current LUKS Disk Recovery Key passphrase..."
			cryptsetup open $LUKS test --key-file /tmp/luks_current_Disk_Recovery_Key_passphrase
		fi
		#Validate past cryptsetup-reencrypt attempts
		if [ $? -eq 0 ]; then
			whiptail --title 'Invalid Actual LUKS Disk Recovery Key passphrase?' --msgbox \
				"If you previously changed it and do not remember it, you will have to\n reinstall the OS from a an external drive.\n\nTo do so, place the ISO file and its signature file on root of an\n external drive, and select Options-> Boot from USB \n\nHit Enter to retry." 30 60
			shred -n 10 -z -u /tmp/luks_current_Disk_Recovery_Key_passphrase 2>/dev/null
			#unsetting luks_current_Disk_Recovery_Key_passphrase so we prompt for it again LUKS Disk Recovery Key passphrase prompt on next round
			unset luks_current_Disk_Recovery_Key_passphrase
			#remove "known good" selected LUKS container so that next pass asks again user to select LUKS container.
			#maybe the container was not the right one
			detect_boot_device
			mount -o remount,rw /boot
			rm -f /boot/kexec_key_devices.txt
			mount -o remount,ro /boot
		else
			#LuksOpen test was successful. Cleanup should be called only when done
			#Exporting successfully used passphrase possibly reused by oem-factory-reset

			#We close the volume
			cryptsetup close test
			export luks_current_Disk_Recovery_Key_passphrase
			break;
		fi
	done
}

luks_reencrypt() {
	TRACE_FUNC
	while :; do
		select_luks_container || return 1
                #If the user just set a new LUKS Disk Recovery Key passphrase
                if [ -n "$luks_new_Disk_Recovery_Key_passphrase" ]; then
                        luks_current_Disk_Recovery_Key_passphrase="$luks_new_Disk_Recovery_Key_passphrase"
                fi
		if [ -z "$luks_current_Disk_Recovery_Key_passphrase" ]; then
			#if no external provisioning provides current LUKS Disk Recovery Key passphrase
			msg=$(echo -e "This will replace the encrypted container content and its LUKS Disk Recovery Key.\n\nThe passphrase associated with this key will be asked from the user under the following conditions:\n 1-Every boot if no Disk Unlock Key was added to the TPM\n 2-If the TPM fails (hardware failure)\n 3-If the firmware has been tampered with/modified by the user\n\nThis process requires you to type the current LUKS Disk Recovery Key passphrase and will delete the LUKS TPM Disk Unlock Key slot, if set up, by setting a default boot LUKS key slot (1) if present.\n\nAt the next prompt, you may be asked to select which file corresponds to the LUKS device container.\n\nHit Enter to continue." | fold -w 70 -s)
			whiptail --title 'Reencrypt LUKS encrypted container ?' \
			--msgbox "$msg" 0 80
			echo -e "\nEnter the current LUKS Disk Recovery Key passphrase:"
			read -r luks_current_Disk_Recovery_Key_passphrase
			echo -n "$luks_current_Disk_Recovery_Key_passphrase" >/tmp/luks_current_Disk_Recovery_Key_passphrase
			warn "Reencrypting "$LUKS" LUKS encrypted drive content with a new LUKS Disk Recovery Key. Do NOT shut down or reboot!"
			cryptsetup-reencrypt -B 64 --use-directio "$LUKS" --key-slot 0 --key-file /tmp/luks_current_Disk_Recovery_Key_passphrase
		else
			echo -n "$luks_current_Disk_Recovery_Key_passphrase" >/tmp/luks_current_Disk_Recovery_Key_passphrase
			warn "Reencrypting "$LUKS" LUKS encrypted drive content with a new LUKS Disk Recovery Key. Do NOT shut down or reboot!"
			cryptsetup-reencrypt -B 64 --use-directio "$LUKS" --key-slot 0 --key-file /tmp/luks_current_Disk_Recovery_Key_passphrase
		fi
		#Validate past cryptsetup-reencrypt attempts
		if [ $(echo $?) -ne 0 ]; then
			whiptail --title 'Invalid Actual LUKS Disk Recovery Key passphrase?' --msgbox \
				"If you previously changed it and do not remember it, you will have to\n reinstall the OS from a an external drive.\n\nTo do so, place the ISO file and its signature file on root of an\n external drive, and select Options-> Boot from USB \n\nHit Enter to retry." 30 60
			shred -n 10 -z -u /tmp/luks_current_Disk_Recovery_Key_passphrase 2>/dev/null
			#unsetting luks_current_Disk_Recovery_Key_passphrase so we prompt for it again LUKS Disk Recovery Key passphrase prompt on next round
			unset luks_current_Disk_Recovery_Key_passphrase
			#remove "known good" selected LUKS container so that next pass asks again user to select LUKS container.
			#maybe the container was not the right one
			detect_boot_device
			mount -o remount,rw /boot
			rm -f /boot/kexec_key_devices.txt
			mount -o remount,ro /boot
		else
			#Reencryption was successful. Cleanup should be called only when done
			#Exporting successfully used passphrase possibly reused by oem-factory-reset
			export luks_current_Disk_Recovery_Key_passphrase
		break;
		fi
	done
}

luks_change_passphrase()
{
	TRACE_FUNC
	while :; do
		select_luks_container || return 1
		#if actual or new LUKS Disk Recovery Key is not provisioned by oem-provisioning file
		if [ -z "$luks_current_Disk_Recovery_Key_passphrase" ] || [ -z "$luks_new_Disk_Recovery_Key_passphrase" ]; then
			whiptail --title 'Changing LUKS Disk Recovery Key passphrase' --msgbox \
				"Please enter the current LUKS Disk Recovery Key passphrase (slot 0).\nThen choose a strong passphrase of your own.\n\n**DICEWARE passphrase methodology is STRONGLY ADVISED.**\n\nHit Enter to continue" 30 60
			if [ -z "$luks_new_Disk_Recovery_Key_passphrase" ]; then
				echo -e "\nEnter your desired replacement for the actual LUKS Disk Recovery Key passphrase (At least 8 characters long):"
				while [[ ${#luks_new_Disk_Recovery_Key_passphrase} -lt 8 ]]; do
				{
					read -r luks_new_Disk_Recovery_Key_passphrase
			        };done
			fi
			if [ -z "$luks_current_Disk_Recovery_Key_passphrase" ]; then
				echo -e "\nEnter the current LUKS Disk Recovery Key passphrase (Configured at OS installation or by OEM):"
				read -r luks_current_Disk_Recovery_Key_passphrase
			fi
			export luks_current_Disk_Recovery_Key_passphrase
			export luks_new_Disk_Recovery_Key_passphrase
			echo -n "$luks_new_Disk_Recovery_Key_passphrase" >/tmp/luks_new_Disk_Recovery_Key_passphrase
			echo -n "$luks_current_Disk_Recovery_Key_passphrase" >/tmp/luks_current_Disk_Recovery_Key_passphrase
			warn "Changing "$LUKS" LUKS encrypted disk passphrase to the new LUKS Disk Recovery Key passphrase..."
			cryptsetup luksChangeKey "$LUKS" --key-slot 0 --key-file=/tmp/luks_current_Disk_Recovery_Key_passphrase /tmp/luks_new_Disk_Recovery_Key_passphrase
		else
			#If current and new LUKS Disk Recovery Key were exported
			echo -n "$luks_new_Disk_Recovery_Key_passphrase" >/tmp/luks_new_Disk_Recovery_Key_passphrase
			echo -n "$luks_current_Disk_Recovery_Key_passphrase" >/tmp/luks_current_Disk_Recovery_Key_passphrase
			warn "Changing "$LUKS" LUKS encrypted disk passphrase to the new LUKS Disk Recovery Key passphrase..."
			cryptsetup luksChangeKey "$LUKS" --key-slot 0 --key-file=/tmp/luks_current_Disk_Recovery_Key_passphrase /tmp/luks_new_Disk_Recovery_Key_passphrase
		fi

		#Validate past cryptsetup attempts
		if [ $(echo $?) -ne 0 ]; then
			#Cryptsetup luksChangeKey was unsuccessful
			whiptail --title 'Invalid LUKS passphrase?' --msgbox \
				"The LUKS Disk Recovery Key passphrase was provided to you by the OEM over\n a secure communication channel.\n\nIf you previously changed it and do not remember it,\n you will have to reinstall the OS from a USB drive.\nTo do so, put OS ISO file and it's signature file on root of a USB drive,\n and select Boot from USB\n\nHit Enter to continue." 30 60
			unset luks_current_Disk_Recovery_Key_passphrase
			unset luks_new_Disk_Recovery_Key_passphrase
			#remove "known good" selected LUKS container so that next pass asks again user to select LUKS container.
			#maybe the container was not the right one
			detect_boot_device
			mount -o remount,rw /boot
			rm -f /boot/kexec_key_devices.txt
			mount -o remount,ro /boot
		else
			#Cryptsetup was successful.
			#Cleanup should be called seperately.
			#Exporting successfully used passphrase possibly reused by oem-factory-reset
			export luks_new_Disk_Recovery_Key_passphrase
	break;		
		fi
	done
}

luks_secrets_cleanup()
{
	#Cleanup
	shred -n 10 -z -u /tmp/luks_new_Disk_Recovery_Key_passphrase 2>/dev/null || true
	shred -n 10 -z -u /tmp/luks_current_Disk_Recovery_Key_passphrase 2>/dev/null || true
	unset luks_current_Disk_Recovery_Key_passphrase
	unset luks_new_Disk_Recovery_Key_passphrase
}
