/* Copyright (c) 2011 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.
 *
 * Verified boot keyblock utility
 */

#include <getopt.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "2common.h"
#include "2rsa.h"
#include "2sysincludes.h"
#include "futility.h"
#include "host_common.h"
#include "host_key21.h"
#include "util_misc.h"
#include "vb1_helper.h"

/* Command line options */
enum {
	OPT_MODE_PACK = 1000,
	OPT_MODE_UNPACK,
	OPT_DATAPUBKEY,
	OPT_SIGNPUBKEY,
	OPT_SIGNPRIVATE,
	OPT_SIGNPRIVATE_PEM,
	OPT_PEM_ALGORITHM,
	OPT_EXTERNAL_SIGNER,
	OPT_FLAGS,
	OPT_HELP,
};

static const struct option long_opts[] = {
	{"pack", 1, 0, OPT_MODE_PACK},
	{"unpack", 1, 0, OPT_MODE_UNPACK},
	{"datapubkey", 1, 0, OPT_DATAPUBKEY},
	{"signpubkey", 1, 0, OPT_SIGNPUBKEY},
	{"signprivate", 1, 0, OPT_SIGNPRIVATE},
	{"signprivate_pem", 1, 0, OPT_SIGNPRIVATE_PEM},
	{"pem_algorithm", 1, 0, OPT_PEM_ALGORITHM},
	{"externalsigner", 1, 0, OPT_EXTERNAL_SIGNER},
	{"flags", 1, 0, OPT_FLAGS},
	{"help", 0, 0, OPT_HELP},
	{NULL, 0, 0, 0}
};

static const char usage[] =
	"\n"
	"Usage:  " MYNAME " %s <--pack|--unpack> <file> [OPTIONS]\n"
	"\n"
	"For '--pack <file>', required OPTIONS are:\n"
	"  --datapubkey <file>         Data public key in .vbpubk format\n"
	"\n"
	"Optional OPTIONS are:\n"
	"  --signprivate <file>"
	"        Signing private key in .vbprivk format.\n"
	"OR\n"
	"  --signprivate_pem <file>\n"
	"  --pem_algorithm <algo>\n"
	"        Signing private key in .pem format and algorithm id.\n"
	"(If one of the above arguments is not specified, the keyblock will\n"
	"not be signed.)\n"
	"\n"
	"  --flags <number>            Specifies allowed use conditions.\n"
	"  --externalsigner \"cmd\""
	"        Use an external program cmd to calculate the signatures.\n"
	"\n"
	"For '--unpack <file>', optional OPTIONS are:\n"
	"  --signpubkey <file>"
	"        Signing public key in .vbpubk format. This is required to\n"
	"                                verify a signed keyblock.\n"
	"  --datapubkey <file>"
	"        Write the data public key to this file.\n\n";

static void print_help(int argc, char *argv[])
{
	printf(usage, argv[0]);
}

/* Pack a .keyblock */
static int Pack(const char *outfile, const char *datapubkey,
		const char *signprivate,
		const char *signprivate_pem, uint64_t pem_algorithm,
		uint64_t flags, const char *external_signer)
{
	struct vb2_private_key *signing_key = NULL;
	struct vb2_keyblock *block;

	if (!outfile) {
		fprintf(stderr,
			"vbutil_keyblock: Must specify output filename.\n");
		return 1;
	}
	if (!datapubkey) {
		fprintf(stderr,
			"vbutil_keyblock: Must specify data public key.\n");
		return 1;
	}

	struct vb2_packed_key *data_key = vb2_read_packed_key(datapubkey);
	if (!data_key) {
		fprintf(stderr, "vbutil_keyblock: Error reading data key.\n");
		return 1;
	}

	if (signprivate_pem) {
		if (pem_algorithm >= VB2_ALG_COUNT) {
			fprintf(stderr,
				"vbutil_keyblock: Invalid --pem_algorithm %"
				PRIu64 "\n", pem_algorithm);
			return 1;
		}
		if (external_signer) {
			/* External signing uses the PEM file directly. */
			block = vb2_create_keyblock_external(data_key,
							     signprivate_pem,
							     pem_algorithm,
							     flags,
							     external_signer);
		} else {
			signing_key =
				vb2_read_private_key_pem(signprivate_pem,
							 pem_algorithm);
			if (!signing_key) {
				fprintf(stderr, "vbutil_keyblock:"
					" Error reading signing key.\n");
				return 1;
			}
			block = vb2_create_keyblock(data_key, signing_key,
						    flags);
		}
	} else {
		if (signprivate) {
			signing_key = vb2_read_private_key(signprivate);
			if (!signing_key) {
				fprintf(stderr, "vbutil_keyblock:"
					" Error reading signing key.\n");
				return 1;
			}
		}
		block = vb2_create_keyblock(data_key, signing_key, flags);
	}

	free(data_key);
	if (signing_key)
		free(signing_key);

	if (VB2_SUCCESS != vb2_write_keyblock(outfile, block)) {
		fprintf(stderr, "vbutil_keyblock: Error writing keyblock.\n");
		return 1;
	}
	free(block);
	return 0;
}

static int Unpack(const char *infile, const char *datapubkey,
		  const char *signpubkey)
{
	struct vb2_packed_key *sign_key = NULL;

	if (!infile) {
		fprintf(stderr, "vbutil_keyblock: Must specify filename\n");
		return 1;
	}

	struct vb2_keyblock *block = vb2_read_keyblock(infile);
	if (!block) {
		fprintf(stderr, "vbutil_keyblock: Error reading keyblock.\n");
		return 1;
	}

	/* If the signing public key is provided, then verify the block
	 * signature, since vb2_read_keyblock() only verified the hash. */
	if (signpubkey) {
		static uint8_t workbuf[VB2_FIRMWARE_WORKBUF_RECOMMENDED_SIZE]
			__attribute__((aligned(VB2_WORKBUF_ALIGN)));
		static struct vb2_workbuf wb;

		if (block->keyblock_signature.sig_size == 0) {
			fprintf(stderr,
				"vbutil_keyblock: signpubkey provided but keyblock is not signed.\n");
			return 1;
		}

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

		sign_key = vb2_read_packed_key(signpubkey);
		if (!sign_key) {
			fprintf(stderr,
				"vbutil_keyblock: Error reading signpubkey.\n");
			return 1;
		}
		struct vb2_public_key key;
		if (VB2_SUCCESS != vb2_unpack_key(&key, sign_key)) {
			fprintf(stderr,
				"vbutil_keyblock: Error reading signpubkey.\n");
			return 1;
		}

		if (VB2_SUCCESS !=
		    vb2_verify_keyblock(block, block->keyblock_size,
					&key, &wb)) {
			fprintf(stderr, "vbutil_keyblock:"
				" Error verifying keyblock.\n");
			return 1;
		}
		free(sign_key);
	}

	printf("Keyblock file:        %s\n", infile);
	printf("Signature             %s\n", sign_key ? "valid" : "ignored");
	printf("Flags:                %u ", block->keyblock_flags);
	if (block->keyblock_flags & VB2_KEYBLOCK_FLAG_DEVELOPER_0)
		printf(" !DEV");
	if (block->keyblock_flags & VB2_KEYBLOCK_FLAG_DEVELOPER_1)
		printf(" DEV");
	if (block->keyblock_flags & VB2_KEYBLOCK_FLAG_RECOVERY_0)
		printf(" !REC");
	if (block->keyblock_flags & VB2_KEYBLOCK_FLAG_RECOVERY_1)
		printf(" REC");
	if (block->keyblock_flags & VB2_KEYBLOCK_FLAG_MINIOS_0)
		printf(" !MINIOS");
	if (block->keyblock_flags & VB2_KEYBLOCK_FLAG_MINIOS_1)
		printf(" MINIOS");
	printf("\n");

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

	if (datapubkey &&
	    VB2_SUCCESS != vb2_write_packed_key(datapubkey, data_key)) {
		fprintf(stderr, "vbutil_keyblock: error writing public key\n");
		return 1;
	}

	free(block);
	return 0;
}

static int do_vbutil_keyblock(int argc, char *argv[])
{

	char *filename = NULL;
	char *datapubkey = NULL;
	char *signpubkey = NULL;
	char *signprivate = NULL;
	char *signprivate_pem = NULL;
	char *external_signer = NULL;
	uint64_t flags = 0;
	uint64_t pem_algorithm = 0;
	int is_pem_algorithm = 0;
	int mode = 0;
	int parse_error = 0;
	char *e;
	int i;

	while ((i = getopt_long(argc, argv, "", long_opts, NULL)) != -1) {
		switch (i) {
		case '?':
			/* Unhandled option */
			printf("Unknown option\n");
			parse_error = 1;
			break;
		case OPT_HELP:
			print_help(argc, argv);
			return !!parse_error;

		case OPT_MODE_PACK:
		case OPT_MODE_UNPACK:
			mode = i;
			filename = optarg;
			break;

		case OPT_DATAPUBKEY:
			datapubkey = optarg;
			break;

		case OPT_SIGNPUBKEY:
			signpubkey = optarg;
			break;

		case OPT_SIGNPRIVATE:
			signprivate = optarg;
			break;

		case OPT_SIGNPRIVATE_PEM:
			signprivate_pem = optarg;
			break;

		case OPT_PEM_ALGORITHM:
			pem_algorithm = strtoul(optarg, &e, 0);
			if (!*optarg || (e && *e)) {
				fprintf(stderr, "Invalid --pem_algorithm\n");
				parse_error = 1;
			} else {
				is_pem_algorithm = 1;
			}
			break;

		case OPT_EXTERNAL_SIGNER:
			external_signer = optarg;
			break;

		case OPT_FLAGS:
			flags = strtoul(optarg, &e, 0);
			if (!*optarg || (e && *e)) {
				fprintf(stderr, "Invalid --flags\n");
				parse_error = 1;
			}
			break;
		}
	}

	/* Check if the right combination of options was provided. */
	if (signprivate && signprivate_pem) {
		fprintf(stderr,
			"Only one of --signprivate or --signprivate_pem must"
			" be specified\n");
		parse_error = 1;
	}

	if (signprivate_pem && !is_pem_algorithm) {
		fprintf(stderr, "--pem_algorithm must be used with"
			" --signprivate_pem\n");
		parse_error = 1;
	}

	if (external_signer && !signprivate_pem) {
		fprintf(stderr,
			"--externalsigner must be used with --signprivate_pem"
			"\n");
		parse_error = 1;
	}

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

	switch (mode) {
	case OPT_MODE_PACK:
		return Pack(filename, datapubkey, signprivate,
			    signprivate_pem, pem_algorithm,
			    flags, external_signer);
	case OPT_MODE_UNPACK:
		return Unpack(filename, datapubkey, signpubkey);
	default:
		printf("Must specify a mode.\n");
		print_help(argc, argv);
		return 1;
	}
}

DECLARE_FUTIL_COMMAND(vbutil_keyblock, do_vbutil_keyblock, VBOOT_VERSION_1_0,
		      "Creates, signs, and verifies a keyblock");
