/*
 * 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 <ctype.h>
#include <getopt.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>

#include "2sysincludes.h"
#include "2common.h"
#include "2sha.h"
#include "futility.h"

static const char usage[] = "\n"
	"Usage:  " MYNAME " %s [OPTIONS] DIGEST [...]\n"
	"\n"
	"This simulates a TPM PCR extension, to determine the expected output\n"
	"\n"
	"Each DIGEST arg should be a hex string (spaces optional) of the\n"
	"appropriate length. The PCR is extended with each digest in turn\n"
	"and the new value displayed.\n"
	"\n"
	"Options:\n"
	"  -i      Initialize the PCR with the first DIGEST argument\n"
	"            (the default is to start with all zeros)\n"
	"  -2      Use sha256 DIGESTS (the default is sha1)\n"
	"\n"
	"Examples:\n"
	"\n"
	"  " MYNAME " %s b52791126f96a21a8ba4d511c6f25a1c1eb6dc9e\n"
	"  " MYNAME " %s "
	"'b5 27 91 12 6f 96 a2 1a 8b a4 d5 11 c6 f2 5a 1c 1e b6 dc 9e'\n"
	"\n";

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

static int parse_hex(uint8_t *val, const char *str)
{
	uint8_t v = 0;
	char c;
	int digit;

	for (digit = 0; digit < 2; digit++) {
		c = *str;
		if (!c)
			return 0;
		if (!isxdigit(c))
			return 0;
		c = tolower(c);
		if (c >= '0' && c <= '9')
			v += c - '0';
		else
			v += 10 + c - 'a';
		if (!digit)
			v <<= 4;
		str++;
	}

	*val = v;
	return 1;
}

static void parse_digest_or_die(uint8_t *buf, int len, const char *str)
{
	const char *s = str;
	int i;

	for (i = 0; i < len; i++) {
		/* skip whitespace */
		while (*s && isspace(*s))
			s++;
		if (!*s)
			break;
		if (!parse_hex(buf, s))
			break;

		/* on to the next byte */
		s += 2;
		buf++;
	}

	if (i != len) {
		fprintf(stderr, "Invalid DIGEST \"%s\"\n", str);
		exit(1);
	}
}

static void print_digest(const uint8_t *buf, int len)
{
	int i;
	for (i = 0; i < len; i++)
		printf("%02x", buf[i]);
}

enum {
	OPT_HELP = 1000,
};
static const struct option long_opts[] = {
	{"help",     0, 0, OPT_HELP},
	{NULL, 0, 0, 0}
};
static int do_pcr(int argc, char *argv[])
{
	uint8_t accum[VB2_MAX_DIGEST_SIZE * 2];
	uint8_t pcr[VB2_MAX_DIGEST_SIZE];
	int digest_alg = VB2_HASH_SHA1;
	int digest_size;
	int opt_init = 0;
	int errorcnt = 0;
	int i;

	opterr = 0;		/* quiet, you */
	while ((i = getopt_long(argc, argv, ":i2", long_opts, NULL)) != -1) {
		switch (i) {
		case 'i':
			opt_init = 1;
			break;
		case '2':
			digest_alg = VB2_HASH_SHA256;
			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;
		default:
			FATAL("Unrecognized getopt output: %d\n", i);
		}
	}

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

	if (argc - optind < 1 + opt_init) {
		fprintf(stderr, "You must extend at least one DIGEST\n");
		print_help(argc, argv);
		return 1;
	}

	memset(pcr, 0, sizeof(pcr));

	digest_size = vb2_digest_size(digest_alg);
	if (!digest_size) {
		fprintf(stderr, "Error determining digest size!\n");
		return 1;
	}

	if (opt_init) {
		parse_digest_or_die(pcr, digest_size, argv[optind]);
		optind++;
	}

	printf("PCR: ");
	print_digest(pcr, digest_size);
	printf("\n");

	for (i = optind; i < argc; i++) {
		memcpy(accum, pcr, sizeof(pcr));
		parse_digest_or_die(accum + digest_size, digest_size, argv[i]);

		printf("   + ");
		print_digest(accum + digest_size, digest_size);
		printf("\n");

		if (VB2_SUCCESS != vb2_digest_buffer(accum, digest_size * 2,
						     digest_alg,
						     pcr, digest_size)) {
			fprintf(stderr, "Error computing digest!\n");
			return 1;
		}

		printf("PCR: ");
		print_digest(pcr, digest_size);
		printf("\n");
	}

	return 0;
}

DECLARE_FUTIL_COMMAND(pcr, do_pcr, VBOOT_VERSION_ALL,
		      "Simulate a TPM PCR extension operation");
