/* Copyright 2014 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include <openssl/rsa.h>

#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <inttypes.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#include "2api.h"
#include "2common.h"
#include "2sha.h"
#include "2sysincludes.h"
#include "file_type_bios.h"
#include "file_type.h"
#include "fmap.h"
#include "futility.h"
#include "futility_options.h"
#include "host_common.h"
#include "host_key21.h"
#include "util_misc.h"
#include "vb1_helper.h"
#include "vb2_common.h"

/* Options */
struct show_option_s show_option = {
	.padding = 65536,
	.type = FILE_TYPE_UNKNOWN,
};

/* Shared work buffer */
static uint8_t workbuf[VB2_KERNEL_WORKBUF_RECOMMENDED_SIZE]
	__attribute__((aligned(VB2_WORKBUF_ALIGN)));
static struct vb2_workbuf wb;

void show_pubkey(const struct vb2_packed_key *pubkey, const char *sp)
{
	printf("%sVboot API:           1.0\n", sp);
	printf("%sAlgorithm:           %d %s\n", sp, pubkey->algorithm,
	       vb2_get_crypto_algorithm_name(pubkey->algorithm));
	printf("%sKey Version:         %d\n", sp, pubkey->key_version);
	printf("%sKey sha1sum:         %s\n",
	       sp, packed_key_sha1_string(pubkey));
}

static void show_keyblock(struct vb2_keyblock *keyblock, const char *name,
			  int sign_key, int good_sig)
{
	if (name)
		printf("Keyblock:                %s\n", name);
	else
		printf("Keyblock:\n");
	printf("  Signature:             %s\n",
	       sign_key ? (good_sig ? "valid" : "invalid") : "ignored");
	printf("  Size:                  %#x\n", keyblock->keyblock_size);
	printf("  Flags:                 %d ", keyblock->keyblock_flags);
	if (keyblock->keyblock_flags & VB2_KEYBLOCK_FLAG_DEVELOPER_0)
		printf(" !DEV");
	if (keyblock->keyblock_flags & VB2_KEYBLOCK_FLAG_DEVELOPER_1)
		printf(" DEV");
	if (keyblock->keyblock_flags & VB2_KEYBLOCK_FLAG_RECOVERY_0)
		printf(" !REC");
	if (keyblock->keyblock_flags & VB2_KEYBLOCK_FLAG_RECOVERY_1)
		printf(" REC");
	printf("\n");

	struct vb2_packed_key *data_key = &keyblock->data_key;
	printf("  Data key algorithm:    %d %s\n", data_key->algorithm,
	       vb2_get_crypto_algorithm_name(data_key->algorithm));
	printf("  Data key version:      %d\n", data_key->key_version);
	printf("  Data key sha1sum:      %s\n",
	       packed_key_sha1_string(data_key));
}

int ft_show_pubkey(const char *name, uint8_t *buf, uint32_t len, void *data)
{
	struct vb2_packed_key *pubkey = (struct vb2_packed_key *)buf;

	if (vb2_packed_key_looks_ok(pubkey, len)) {
		printf("%s looks bogus\n", name);
		return 1;
	}

	printf("Public Key file:       %s\n", name);
	show_pubkey(pubkey, "  ");

	return 0;
}

int ft_show_privkey(const char *name, uint8_t *buf, uint32_t len, void *data)
{
	struct vb2_packed_private_key *pkey =
		(struct vb2_packed_private_key *)buf;
	struct vb2_private_key key;
	const unsigned char *start = pkey->key_data;

	if (len <= sizeof(*pkey)) {
		printf("%s looks bogus\n", name);
		return 1;
	}
	len -= sizeof(*pkey);
	key.rsa_private_key = d2i_RSAPrivateKey(NULL, &start, len);

	printf("Private Key file:      %s\n", name);
	printf("  Vboot API:           1.0\n");
	printf("  Algorithm:           %u %s\n", pkey->algorithm,
	       vb2_get_crypto_algorithm_name(pkey->algorithm));
	printf("  Key sha1sum:         %s\n",
	       private_key_sha1_string(&key));

	return 0;
}

int ft_show_keyblock(const char *name, uint8_t *buf, uint32_t len, void *data)
{
	struct vb2_keyblock *block = (struct vb2_keyblock *)buf;
	struct vb2_public_key *sign_key = show_option.k;
	int good_sig = 0;
	int retval = 0;

	/* Check the hash only first */
	if (0 != vb2_verify_keyblock_hash(block, len, &wb)) {
		printf("%s is invalid\n", name);
		return 1;
	}

	/* Check the signature if we have one */
	if (sign_key &&
	    VB2_SUCCESS == vb2_verify_keyblock(block, len, sign_key, &wb))
		good_sig = 1;

	if (show_option.strict && (!sign_key || !good_sig))
		retval = 1;

	show_keyblock(block, name, !!sign_key, good_sig);

	return retval;
}

int ft_show_fw_preamble(const char *name, uint8_t *buf, uint32_t len,
			void *data)
{
	struct vb2_keyblock *keyblock = (struct vb2_keyblock *)buf;
	struct bios_state_s *state = (struct bios_state_s *)data;
	struct vb2_public_key *sign_key = show_option.k;
	uint8_t *fv_data = show_option.fv;
	uint64_t fv_size = show_option.fv_size;
	struct bios_area_s *fw_body_area = 0;
	int good_sig = 0;
	int retval = 0;

	/* Check the hash... */
	if (VB2_SUCCESS != vb2_verify_keyblock_hash(keyblock, len, &wb)) {
		printf("%s keyblock component is invalid\n", name);
		return 1;
	}

	/*
	 * If we're being invoked while poking through a BIOS, we should
	 * be given the keys and data to verify as part of the state. If we
	 * have no state, then we're just looking at a standalone fw_preamble,
	 * so we'll have to get any keys or data from options.
	 */
	struct vb2_public_key root_key;
	if (state) {
		if (!sign_key &&
		    state->rootkey.is_valid &&
		    VB2_SUCCESS == vb2_unpack_key_buffer(&root_key,
							 state->rootkey.buf,
							 state->rootkey.len)) {
			/* BIOS should have a rootkey in the GBB */
			sign_key = &root_key;
		}

		/* Identify the firmware body for this VBLOCK */
		enum bios_component body_c = state->c == BIOS_FMAP_VBLOCK_A
			? BIOS_FMAP_FW_MAIN_A
			: BIOS_FMAP_FW_MAIN_B;
		fw_body_area = &state->area[body_c];
	}

	/* If we have a key, check the signature too */
	if (sign_key && VB2_SUCCESS ==
	    vb2_verify_keyblock(keyblock, len, sign_key, &wb))
		good_sig = 1;

	show_keyblock(keyblock, name, !!sign_key, good_sig);

	if (show_option.strict && (!sign_key || !good_sig))
		retval = 1;

	struct vb2_public_key data_key;
	if (VB2_SUCCESS != vb2_unpack_key(&data_key, &keyblock->data_key)) {
		fprintf(stderr, "Error parsing data key in %s\n", name);
		return 1;
	}

	uint32_t more = keyblock->keyblock_size;
	struct vb2_fw_preamble *pre2 = (struct vb2_fw_preamble *)(buf + more);
	if (VB2_SUCCESS != vb2_verify_fw_preamble(pre2, len - more,
						  &data_key, &wb)) {
		printf("%s is invalid\n", name);
		return 1;
	}

	uint32_t flags = pre2->flags;
	if (pre2->header_version_minor < 1)
		flags = 0;  /* Old 2.0 structure didn't have flags */

	printf("Firmware Preamble:\n");
	printf("  Size:                  %d\n", pre2->preamble_size);
	printf("  Header version:        %d.%d\n",
	       pre2->header_version_major, pre2->header_version_minor);
	printf("  Firmware version:      %d\n", pre2->firmware_version);

	struct vb2_packed_key *kernel_subkey = &pre2->kernel_subkey;
	printf("  Kernel key algorithm:  %d %s\n",
	       kernel_subkey->algorithm,
	       vb2_get_crypto_algorithm_name(kernel_subkey->algorithm));
	if (kernel_subkey->algorithm >= VB2_ALG_COUNT)
		retval = 1;
	printf("  Kernel key version:    %d\n", kernel_subkey->key_version);
	printf("  Kernel key sha1sum:    %s\n",
	       packed_key_sha1_string(kernel_subkey));
	printf("  Firmware body size:    %d\n", pre2->body_signature.data_size);
	printf("  Preamble flags:        %d\n", flags);

	if (flags & VB2_FIRMWARE_PREAMBLE_USE_RO_NORMAL) {
		printf("Preamble requests USE_RO_NORMAL;"
		       " skipping body verification.\n");
		goto done;
	}

	/* We'll need to get the firmware body from somewhere... */
	if (fw_body_area && fw_body_area->is_valid) {
		fv_data = fw_body_area->buf;
		fv_size = fw_body_area->len;
	}

	if (!fv_data) {
		printf("No firmware body available to verify.\n");
		if (show_option.strict)
			return 1;
		return 0;
	}

	if (VB2_SUCCESS !=
	    vb2_verify_data(fv_data, fv_size, &pre2->body_signature,
			    &data_key, &wb)) {
		fprintf(stderr, "Error verifying firmware body.\n");
		return 1;
	}

done:
	/* Can't trust the BIOS unless everything is signed (in which case
	 * we've already returned), but standalone files are okay. */
	if (state || (sign_key && good_sig)) {
		if (!(flags & VB2_FIRMWARE_PREAMBLE_USE_RO_NORMAL))
			printf("Body verification succeeded.\n");
		if (state)
			state->area[state->c].is_valid = 1;
	} else {
		printf("Seems legit, but the signature is unverified.\n");
		if (show_option.strict)
			retval = 1;
	}

	return retval;
}

int ft_show_kernel_preamble(const char *name, uint8_t *buf, uint32_t len,
			    void *data)
{
	struct vb2_keyblock *keyblock = (struct vb2_keyblock *)buf;
	struct vb2_public_key *sign_key = show_option.k;
	int retval = 0;

	/* Check the hash... */
	if (VB2_SUCCESS != vb2_verify_keyblock_hash(keyblock, len, &wb)) {
		printf("%s keyblock component is invalid\n", name);
		return 1;
	}

	/* If we have a key, check the signature too */
	int good_sig = 0;
	if (sign_key && VB2_SUCCESS ==
	    vb2_verify_keyblock(keyblock, len, sign_key, &wb))
		good_sig = 1;

	printf("Kernel partition:        %s\n", name);
	show_keyblock(keyblock, NULL, !!sign_key, good_sig);

	if (show_option.strict && (!sign_key || !good_sig))
		retval = 1;

	struct vb2_public_key data_key;
	if (VB2_SUCCESS != vb2_unpack_key(&data_key, &keyblock->data_key)) {
		fprintf(stderr, "Error parsing data key in %s\n", name);
		return 1;
	}

	uint32_t more = keyblock->keyblock_size;
	struct vb2_kernel_preamble *pre2 =
		(struct vb2_kernel_preamble *)(buf + more);

	if (VB2_SUCCESS != vb2_verify_kernel_preamble(pre2, len - more,
						      &data_key, &wb)) {
		printf("%s is invalid\n", name);
		return 1;
	}

	printf("Kernel Preamble:\n");
	printf("  Size:                  %#x\n", pre2->preamble_size);
	printf("  Header version:        %u.%u\n",
	       pre2->header_version_major,
	       pre2->header_version_minor);
	printf("  Kernel version:        %u\n", pre2->kernel_version);
	printf("  Body load address:     0x%" PRIx64 "\n",
	       pre2->body_load_address);
	printf("  Body size:             %#x\n",
	       pre2->body_signature.data_size);
	printf("  Bootloader address:    0x%" PRIx64 "\n",
	       pre2->bootloader_address);
	printf("  Bootloader size:       %#x\n", pre2->bootloader_size);

	uint64_t vmlinuz_header_address = 0;
	uint32_t vmlinuz_header_size = 0;
	vb2_kernel_get_vmlinuz_header(pre2,
				      &vmlinuz_header_address,
				      &vmlinuz_header_size);
	if (vmlinuz_header_size) {
		printf("  Vmlinuz_header address:    0x%" PRIx64 "\n",
		       vmlinuz_header_address);
		printf("  Vmlinuz header size:       %#x\n",
		       vmlinuz_header_size);
	}

	printf("  Flags:                 %#x\n", vb2_kernel_get_flags(pre2));

	/* Verify kernel body */
	uint8_t *kernel_blob = 0;
	uint64_t kernel_size = 0;
	if (show_option.fv) {
		/* It's in a separate file, which we've already read in */
		kernel_blob = show_option.fv;
		kernel_size = show_option.fv_size;
	} else if (len > show_option.padding) {
		/* It should be at an offset within the input file. */
		kernel_blob = buf + show_option.padding;
		kernel_size = len - show_option.padding;
	}

	if (!kernel_blob) {
		/* TODO: Is this always a failure? The preamble is okay. */
		fprintf(stderr, "No kernel blob available to verify.\n");
		return 1;
	}

	if (VB2_SUCCESS !=
	    vb2_verify_data(kernel_blob, kernel_size, &pre2->body_signature,
			    &data_key, &wb)) {
		fprintf(stderr, "Error verifying kernel body.\n");
		return 1;
	}

	printf("Body verification succeeded.\n");

	printf("Config:\n%s\n", kernel_blob + kernel_cmd_line_offset(pre2));

	return retval;
}

enum no_short_opts {
	OPT_PADDING = 1000,
	OPT_TYPE,
	OPT_PUBKEY,
	OPT_HELP,
};

static const char usage[] = "\n"
	"Usage:  " MYNAME " %s [OPTIONS] FILE [...]\n"
	"\n"
	"Where FILE could be\n"
	"\n"
	"  a boot descriptor block (BDB)\n"
	"  a keyblock (.keyblock)\n"
	"  a firmware preamble signature (VBLOCK_A/B)\n"
	"  a firmware image (bios.bin)\n"
	"  a kernel partition (/dev/sda2, /dev/mmcblk0p2)\n"
	"  keys in various formats (.vbpubk, .vbprivk, .pem)\n"
	"  several other file types related to verified boot\n"
	"\n"
	"Options:\n"
	"  -t                               Just show the type of each file\n"
	"  --type           TYPE            Override the detected file type\n"
	"                                     Use \"--type help\" for a list\n"
	"Type-specific options:\n"
	"  -k|--publickey   FILE.vbpubk     Public key in vb1 format\n"
	"  --pubkey         FILE.vpubk2     Public key in vb2 format\n"
	"  -f|--fv          FILE            Verify this payload (FW_MAIN_A/B)\n"
	"  --pad            NUM             Kernel vblock padding size\n"
	"  --strict                         "
	"Fail unless all signatures are valid\n"
	"\n";

static void print_help(int argc, char *argv[])
{
	if (!strcmp(argv[0], "verify"))
		printf("\nUsage:  " MYNAME " %s [OPTIONS] FILE [...]\n\n"
		       "This is just an alias for\n\n"
		       "  " MYNAME " show --strict\n\n",
		       argv[0]);

	printf(usage, "show");
}

static const struct option long_opts[] = {
	/* name    hasarg *flag val */
	{"publickey",   1, 0, 'k'},
	{"fv",          1, 0, 'f'},
	{"pad",         1, NULL, OPT_PADDING},
	{"type",        1, NULL, OPT_TYPE},
	{"strict",      0, &show_option.strict, 1},
	{"pubkey",      1, NULL, OPT_PUBKEY},
	{"help",        0, NULL, OPT_HELP},
	{NULL, 0, NULL, 0},
};
static const char *short_opts = ":f:k:t";


static int show_type(char *filename)
{
	enum futil_file_err err;
	enum futil_file_type type;
	err = futil_file_type(filename, &type);
	switch (err) {
	case FILE_ERR_NONE:
		printf("%s:\t%s\n", filename, futil_file_type_name(type));
		/* Only our recognized types return success */
		return 0;
	case FILE_ERR_DIR:
		printf("%s:\t%s\n", filename, "directory");
		break;
	case FILE_ERR_CHR:
		printf("%s:\t%s\n", filename, "character special");
		break;
	case FILE_ERR_FIFO:
		printf("%s:\t%s\n", filename, "FIFO");
		break;
	case FILE_ERR_SOCK:
		printf("%s:\t%s\n", filename, "socket");
		break;
	default:
		break;
	}
	/* Everything else is an error */
	return 1;
}

static int do_show(int argc, char *argv[])
{
	uint8_t *pubkbuf = NULL;
	struct vb2_public_key pubk2;
	char *infile = 0;
	int ifd, i;
	int errorcnt = 0;
	uint8_t *buf;
	uint32_t len;
	char *e = 0;
	int type_override = 0;
	enum futil_file_type type;

	vb2_workbuf_init(&wb, workbuf, sizeof(workbuf));

	opterr = 0;		/* quiet, you */
	while ((i = getopt_long(argc, argv, short_opts, long_opts, 0)) != -1) {
		switch (i) {
		case 'f':
			show_option.fv = ReadFile(optarg,
						  &show_option.fv_size);
			if (!show_option.fv) {
				fprintf(stderr, "Error reading %s: %s\n",
					optarg, strerror(errno));
				errorcnt++;
			}
			break;
		case 'k':
			if (VB2_SUCCESS !=
			    vb2_read_file(optarg, &pubkbuf, &len)) {
				fprintf(stderr, "Error reading %s\n", optarg);
				errorcnt++;
				break;
			}

			if (VB2_SUCCESS !=
			    vb2_unpack_key_buffer(&pubk2, pubkbuf, len)) {
				fprintf(stderr, "Error unpacking %s\n", optarg);
				errorcnt++;
				break;
			}

			show_option.k = &pubk2;
			break;
		case 't':
			show_option.t_flag = 1;
			break;
		case OPT_PADDING:
			show_option.padding = strtoul(optarg, &e, 0);
			if (!*optarg || (e && *e)) {
				fprintf(stderr,
					"Invalid --padding \"%s\"\n", optarg);
				errorcnt++;
			}
			break;
		case OPT_TYPE:
			if (!futil_str_to_file_type(optarg,
						    &show_option.type)) {
				if (!strcasecmp("help", optarg))
					print_file_types_and_exit(errorcnt);
				fprintf(stderr,
					"Invalid --type \"%s\"\n", optarg);
				errorcnt++;
			}
			type_override = 1;
			break;
		case OPT_PUBKEY:
			if (vb21_packed_key_read(&show_option.pkey, optarg)) {
				fprintf(stderr, "Error reading %s\n", optarg);
				errorcnt++;
			}
			break;
		case OPT_HELP:
			print_help(argc, argv);
			return !!errorcnt;

		case '?':
			if (optopt)
				fprintf(stderr, "Unrecognized option: -%c\n",
					optopt);
			else
				fprintf(stderr, "Unrecognized option\n");
			errorcnt++;
			break;
		case ':':
			fprintf(stderr, "Missing argument to -%c\n", optopt);
			errorcnt++;
			break;
		case 0:				/* handled option */
			break;
		default:
			FATAL("Unrecognized getopt output: %d\n", i);
		}
	}

	if (errorcnt) {
		print_help(argc, argv);
		return 1;
	}

	if (argc - optind < 1) {
		fprintf(stderr, "ERROR: missing input filename\n");
		print_help(argc, argv);
		return 1;
	}

	if (show_option.t_flag) {
		for (i = optind; i < argc; i++)
			errorcnt += show_type(argv[i]);
		goto done;
	}

	for (i = optind; i < argc; i++) {
		infile = argv[i];
		ifd = open(infile, O_RDONLY);
		if (ifd < 0) {
			errorcnt++;
			fprintf(stderr, "Can't open %s: %s\n",
				infile, strerror(errno));
			continue;
		}

		if (0 != futil_map_file(ifd, MAP_RO, &buf, &len)) {
			errorcnt++;
			goto boo;
		}

		/* Allow the user to override the type */
		if (type_override)
			type = show_option.type;
		else
			type = futil_file_type_buf(buf, len);

		errorcnt += futil_file_type_show(type, infile, buf, len);

		errorcnt += futil_unmap_file(ifd, MAP_RO, buf, len);
boo:
		if (close(ifd)) {
			errorcnt++;
			fprintf(stderr, "Error when closing %s: %s\n",
				infile, strerror(errno));
		}
	}

done:
	if (pubkbuf)
		free(pubkbuf);
	if (show_option.fv)
		free(show_option.fv);

	return !!errorcnt;
}

DECLARE_FUTIL_COMMAND(show, do_show, VBOOT_VERSION_ALL,
		      "Display the content of various binary components");

static int do_verify(int argc, char *argv[])
{
	show_option.strict = 1;
	return do_show(argc, argv);
}

DECLARE_FUTIL_COMMAND(verify, do_verify,
		      VBOOT_VERSION_ALL,
		      "Verify the signatures of various binary components");
