/* 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 key utility
 */

#include <getopt.h>
#include <inttypes.h>		/* For PRIu64 */
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "cryptolib.h"
#include "futility.h"
#include "host_common.h"
#include "util_misc.h"
#include "vboot_common.h"

/* Command line options */
enum {
	OPT_INKEY = 1000,
	OPT_KEY_VERSION,
	OPT_ALGORITHM,
	OPT_MODE_PACK,
	OPT_MODE_UNPACK,
	OPT_COPYTO,
};

static const struct option long_opts[] = {
	{"key", 1, 0, OPT_INKEY},
	{"version", 1, 0, OPT_KEY_VERSION},
	{"algorithm", 1, 0, OPT_ALGORITHM},
	{"pack", 1, 0, OPT_MODE_PACK},
	{"unpack", 1, 0, OPT_MODE_UNPACK},
	{"copyto", 1, 0, OPT_COPYTO},
	{NULL, 0, 0, 0}
};

static void print_help(const char *progname)
{
	int i;

	printf("\n"
	       "Usage:  " MYNAME " %s --pack <outfile> [PARAMETERS]\n"
	       "\n"
	       "  Required parameters:\n"
	       "    --key <infile>              RSA key file (.keyb or .pem)\n"
	       "    --version <number>          Key version number "
	       "(required for .keyb,\n"
	       "                                  ignored for .pem)\n"
	       "    --algorithm <number>        "
	       "Signing algorithm to use with key:\n", progname);

	for (i = 0; i < kNumAlgorithms; i++) {
		printf("                                  %d = (%s)\n",
		       i, algo_strings[i]);
	}

	printf("\nOR\n\n"
	       "Usage:  " MYNAME " %s --unpack <infile>\n"
	       "\n"
	       "  Optional parameters:\n"
	       "    --copyto <file>             "
	       "Write a copy of the key to this file.\n\n", progname);
}

/* Pack a .keyb file into a .vbpubk, or a .pem into a .vbprivk */
static int Pack(const char *infile, const char *outfile, uint64_t algorithm,
		uint64_t version)
{
	VbPublicKey *pubkey;
	VbPrivateKey *privkey;

	if (!infile || !outfile) {
		fprintf(stderr, "vbutil_key: Must specify --in and --out\n");
		return 1;
	}

	pubkey = PublicKeyReadKeyb(infile, algorithm, version);
	if (pubkey) {
		if (0 != PublicKeyWrite(outfile, pubkey)) {
			fprintf(stderr, "vbutil_key: Error writing key.\n");
			return 1;
		}
		free(pubkey);
		return 0;
	}

	privkey = PrivateKeyReadPem(infile, algorithm);
	if (privkey) {
		if (0 != PrivateKeyWrite(outfile, privkey)) {
			fprintf(stderr, "vbutil_key: Error writing key.\n");
			return 1;
		}
		free(privkey);
		return 0;
	}

	VbExError("Unable to parse either .keyb or .pem from %s\n", infile);
	return 1;
}

/* Unpack a .vbpubk or .vbprivk */
static int Unpack(const char *infile, const char *outfile)
{
	VbPublicKey *pubkey;
	VbPrivateKey *privkey;

	if (!infile) {
		fprintf(stderr, "Need file to unpack\n");
		return 1;
	}

	pubkey = PublicKeyRead(infile);
	if (pubkey) {
		printf("Public Key file:   %s\n", infile);
		printf("Algorithm:         %" PRIu64 " %s\n", pubkey->algorithm,
		       (pubkey->algorithm < kNumAlgorithms ?
			algo_strings[pubkey->algorithm] : "(invalid)"));
		printf("Key Version:       %" PRIu64 "\n", pubkey->key_version);
		printf("Key sha1sum:       ");
		PrintPubKeySha1Sum(pubkey);
		printf("\n");
		if (outfile) {
			if (0 != PublicKeyWrite(outfile, pubkey)) {
				fprintf(stderr,
					"vbutil_key: Error writing key copy\n");
				free(pubkey);
				return 1;
			}
		}
		free(pubkey);
		return 0;
	}

	privkey = PrivateKeyRead(infile);
	if (privkey) {
		printf("Private Key file:  %s\n", infile);
		printf("Algorithm:         %" PRIu64 " %s\n",
		       privkey->algorithm,
		       (privkey->algorithm <
			kNumAlgorithms ? algo_strings[privkey->
						      algorithm] :
			"(invalid)"));
		if (outfile) {
			if (0 != PrivateKeyWrite(outfile, privkey)) {
				fprintf(stderr,
					"vbutil_key: Error writing key copy\n");
				free(privkey);
				return 1;
			}
		}
		free(privkey);
		return 0;
	}

	VbExError("Unable to parse either .vbpubk or vbprivk from %s\n",
		  infile);
	return 1;
}

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

	char *infile = NULL;
	char *outfile = NULL;
	int mode = 0;
	int parse_error = 0;
	uint64_t version = 1;
	uint64_t algorithm = kNumAlgorithms;
	char *e;
	int i;

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

		case OPT_INKEY:
			infile = optarg;
			break;

		case OPT_KEY_VERSION:
			version = strtoul(optarg, &e, 0);
			if (!*optarg || (e && *e)) {
				VbExError("Invalid --version\n");
				parse_error = 1;
			}
			break;

		case OPT_ALGORITHM:
			algorithm = strtoul(optarg, &e, 0);
			if (!*optarg || (e && *e)) {
				VbExError("Invalid --algorithm\n");
				parse_error = 1;
			}
			break;

		case OPT_MODE_PACK:
			mode = i;
			outfile = optarg;
			break;

		case OPT_MODE_UNPACK:
			mode = i;
			infile = optarg;
			break;

		case OPT_COPYTO:
			outfile = optarg;
			break;
		}
	}

	if (parse_error) {
		print_help(argv[0]);
		return 1;
	}

	switch (mode) {
	case OPT_MODE_PACK:
		return Pack(infile, outfile, algorithm, version);
	case OPT_MODE_UNPACK:
		return Unpack(infile, outfile);
	default:
		printf("Must specify a mode.\n");
		print_help(argv[0]);
		return 1;
	}
}

DECLARE_FUTIL_COMMAND(vbutil_key, do_vbutil_key,
		      VBOOT_VERSION_1_0,
		      "Wraps RSA keys with vboot headers",
		      print_help);