/*
 * Copyright 2012 <James.Bottomley@HansenPartnership.com>
 *
 * see COPYING file
 *
 * Simple key manager for both MOK and SecureBoot variables
 */

#include <efi.h>
#include <efilib.h>

#include <simple_file.h>
#include <sha256.h>
#include <variables.h>
#include <console.h>
#include <efiauthenticated.h>
#include <guid.h>
#include <execute.h>

static CHAR16* keytoolbin = L"KeyTool.efi";
static int transition_to_setup = 0, reboot_to_uefi_menu = 0;
static EFI_HANDLE im;

#if 0
/* some UEFI machines have a buggy implementation
 * see if we can tip the system into Setup Mode */
static EFI_STATUS
change_setup_mode(int user_mode)
{
	static UINT8 *data = NULL;
	static UINTN len = 0;
	EFI_STATUS status;

	if (user_mode) {
		if (!data)
			/* can only do this if we previously reset to setup */
			return EFI_INVALID_PARAMETER;

		status = SetSecureVariable(L"PK", data, len, GV_GUID, 0, 0);	

		if (status == EFI_SUCCESS) {
			data = NULL;
			len = 0;
		}
		return status;
		
	} else {
		status = get_variable(L"PK", &data, &len, GV_GUID);
		if (status != EFI_SUCCESS)
			return status;
		/* try to update it to nothing */
		return SetSecureVariable(L"PK", data, 0, GV_GUID, 0, 0);	
	}

}
#endif

static void
enroll_hash(void)
{
	EFI_STATUS efi_status;
	CHAR16 *file_name = NULL, *title[6], buf0[256], buf1[256], buf2[256],
		*var;
	EFI_GUID *owner;
	UINT8 hash[SHA256_DIGEST_SIZE];
	int i, setupmode = variable_is_setupmode();

	simple_file_selector(&im, (CHAR16 *[]){
			L"Select Binary",
			L"",
			L"The Selected Binary will have its hash Enrolled",
			L"This means it will Subsequently Boot with no prompting",
			L"Remember to make sure it is a genuine binary before Enroling its hash",
			NULL
		}, L"\\", L"", &file_name);

	if (!file_name)
		/* user pressed ESC */
		return;

	efi_status = sha256_get_pecoff_digest(im, file_name, hash);
	if (efi_status != EFI_SUCCESS) {
		console_error(L"Hash failed (is efi binary valid?)",
			      efi_status);
		return;
	}

	
	StrCpy(buf0, L"Enroll this hash into ");
	if (setupmode)
		StrCat(buf0, L"UEFI signature database?");
	else
		StrCat(buf0, L"MOK database?");
	title[0] = buf0;
	title[1] = L"";
	StrCpy(buf1, L"File: ");
	StrCat(buf1, file_name);
	title[2] = buf1;
	StrCpy(buf2, L"Hash: ");
	sha256_StrCat_hash(buf2, hash);
	title[3] = buf2;
	title[4] = NULL;
	i = console_yes_no(title);
	if (i == 0)
		return;

	/* We're in setup mode and the User asked us to add the signature
	 * of this binary to the authorized signatures database */
	if (setupmode) {
		var = L"db";
		owner = &SIG_DB;
	} else {
		var = L"MokList";
		owner = &MOK_OWNER;
	}

	efi_status = variable_enroll_hash(var, *owner, hash);	
	if (efi_status != EFI_SUCCESS && efi_status != EFI_ALREADY_STARTED) {
		console_error(L"Failed to add signature to db", efi_status);
		return;
	}
}

void
transition_to_uefi_menu(void)
{
	int option;
	UINT64 indications = GetOSIndications();

	if ((indications & EFI_OS_INDICATIONS_BOOT_TO_FW_UI) == 0) {
		console_errorbox(L"Platform Does not Support rebooting to firmware menu");
		return;
	}

	option = console_yes_no( (CHAR16 *[]){
			L"About to reboot to UEFI Setup Menu",
			L"",
			L"For more details about your system's setup menu",
			L"Including how to reset the system to setup mode, see",
			L"",
			L"http://www.linuxfoundation.org/uefi",
			NULL
		});
	/* user said no */
	if (option == 0)
		return;

	SETOSIndicationsAndReboot(EFI_OS_INDICATIONS_BOOT_TO_FW_UI);

	return;
}

EFI_STATUS
efi_main (EFI_HANDLE image, EFI_SYSTEM_TABLE *systab)
{
	UINT64 indications;
	int option = 0;

	im = image;

	InitializeLib(image, systab);

	indications = GetOSIndications();

	if (indications & EFI_OS_INDICATIONS_BOOT_TO_FW_UI)
		reboot_to_uefi_menu = 1;

#if 0
	/*
	 * Microsoft objects to this code.  What it does is test to see
	 * if the platform key is removable and offer a menu item to 
	 * transition to setup mode
	 */
	if (!variable_is_setupmode()) {
		if (change_setup_mode(0) == EFI_SUCCESS) {
			transition_to_setup = 1;
			change_setup_mode(1);
		}
	}
#endif

	for (;;) {

		CHAR16 line2[80], line3[80], **title, *options[6];
		int c = 0, setup_mode = NOSEL, uefi_reboot = NOSEL,
			reboot = NOSEL,	exit_moktool = NOSEL, SetupMode,
			setup_mode_arg = 0, keytool = NOSEL;
		EFI_FILE *file;

		if (simple_file_open(image, keytoolbin, &file, EFI_FILE_MODE_READ)
		    == EFI_SUCCESS) {
			keytool = 1;
			simple_file_close(file);
		}
		
		SetupMode = variable_is_setupmode();

		StrCpy(line2, L"Platform is in ");
		StrCat(line2, SetupMode ? L"Setup Mode" : L"User Mode");
		StrCpy(line3, L"Secure Boot is ");
		StrCat(line3, variable_is_secureboot() ? L"on" : L"off");
		title =  (CHAR16 *[]){L"Hash Tool main menu", L"", line2, line3, NULL };
		options[c++] = L"Enroll Hash";
		if (keytool != NOSEL) {
			keytool = c;
			options[c++] = L"Start UEFI Key Tool";
		}

		if (transition_to_setup) {
			setup_mode = c;

			if (SetupMode) {
				setup_mode_arg = 1;
				options[c++] = L"Enter User Mode";
			} else {
				setup_mode_arg = 0;
				options[c++] = L"Enter Setup Mode";
			}
		}
		if (reboot_to_uefi_menu) {
			uefi_reboot = c;

			options[c++] = L"Reboot to UEFI Menu";
		}

		reboot = c;
		options[c++] = L"Reboot System";
		exit_moktool = c;
		options[c++] = L"Exit";
		options[c++] = NULL;

		option = console_select(title, options, option);

		if (option == 0) {
			enroll_hash();
		} else if (option == keytool) {
			EFI_STATUS status;

			status = execute(image, keytoolbin);
			if (status != EFI_SUCCESS)
				console_error(L"Failed to execute KeyTool", status);
#if 0
		} else if (option == setup_mode) {
			change_setup_mode(setup_mode_arg);
#endif
		} else if (option == uefi_reboot) {
			transition_to_uefi_menu();
		} else if (option == reboot) {
			int selection;
			selection = console_yes_no((CHAR16 *[]) {
					L"Are you sure you want to reboot?",
					NULL
				});
			if (selection == 1)
			  RT->ResetSystem(EfiResetWarm, EFI_SUCCESS, 0, NULL);
		} else if (option == exit_moktool) {
			break;
		}
	}
	return EFI_SUCCESS;
}
