/*
 * Copyright (c) 2010-2012 United States Government, as represented by
 * the Secretary of Defense.  All rights reserved.
 *
 * based off of the original tools/vtpm_manager code base which is:
 * Copyright (c) 2005, Intel Corp.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above
 *     copyright notice, this list of conditions and the following
 *     disclaimer in the documentation and/or other materials provided
 *     with the distribution.
 *   * Neither the name of Intel Corporation nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include <inttypes.h>
#include <string.h>
#include <stdlib.h>
#include <mini-os/console.h>
#include <mini-os/lib.h>
#include <polarssl/sha1.h>
#include <polarssl/sha2.h>

#include "marshal.h"
#include "log.h"
#include "vtpm_disk.h"
#include "vtpmmgr.h"
#include "tpm.h"
#include "tpm2.h"
#include "tpmrsa.h"
#include "tcg.h"
#include "mgmt_authority.h"
#include "disk_crypto.h"

static void gen_random_uuid(uuid_t uuid)
{
	do_random(uuid, 16);
	// make the 128-bit random number a valid UUID (122 bits remain)
	uuid[6] = 0x40 | (uuid[6] & 0x0F);
	uuid[8] = 0x80 | (uuid[8] & 0x3F);
}

/*
 * Instead of using a kernel hash, which requires a trusted domain builder to
 * report, use the XSM label as a substitute.
 */
static TPM_RESULT find_vtpm_khash(int domid, struct tpm_opaque *opq)
{
	char buf[128];
	int i, rv;
	buf[127] = 0;
	rv = tpmback_get_peercontext(opq->domid, opq->handle, buf, sizeof(buf) - 1);
	if (rv < 0)
		return TPM_FAIL;

	sha1((void*)buf, strlen(buf), opq->kern_hash);

	/*
	 * As a hack to support the use of the XSM user field as an optional
	 * wildcard, check the hash against the group here. If it fails, replace
	 * the user field with a "*" and return the hash of that value.
	 */
	for(i=0; i < be32_native(opq->group->seal_bits.nr_kerns); i++) {
		if (!memcmp(opq->group->seal_bits.kernels[i].bits, opq->kern_hash, 20)) {
			return TPM_SUCCESS;
		}
	}

	char* upos = strchr(buf, ':');
	if (upos == NULL || upos == buf)
		return TPM_SUCCESS;

	upos--;
	upos[0] = '*';

	sha1((void*)upos, strlen(upos), opq->kern_hash);
	return TPM_SUCCESS;
}

static TPM_RESULT find_vtpm_verified(int domid, struct tpm_opaque *opq)
{
	TPM_RESULT rc;
	int i;
	if (opq->vtpm)
		return TPM_SUCCESS;

	rc = find_vtpm(&opq->group, &opq->vtpm, *opq->uuid);
	if (rc)
		return TPM_BAD_PARAMETER;

	if (opq->vtpm->flags & VTPM_FLAG_OPEN) {
		printk("Attempted to open vTPM twice!\n");
		opq->vtpm = NULL;
		opq->group = NULL;
		return TPM_DISABLED;
	}

	rc = find_vtpm_khash(domid, opq);
	if (rc)
		return rc;

	for(i=0; i < be32_native(opq->group->seal_bits.nr_kerns); i++) {
		if (!memcmp(opq->group->seal_bits.kernels[i].bits, opq->kern_hash, 20)) {
			opq->vtpm->flags |= VTPM_FLAG_OPEN;
			return TPM_SUCCESS;
		}
	}
	printk("Unauthorized vTPM kernel image used!\n");
	return TPM_DISABLED;
}

static TPM_RESULT vtpmmgr_SaveHashKey(struct tpm_opaque *opq, tpmcmd_t* tpmcmd)
{
	TPM_RESULT status = TPM_SUCCESS;
	int rc = 0;

	size_t bufsize = tpmcmd->req_len - VTPM_COMMAND_HEADER_SIZE;
	const void *buf = tpmcmd->req + VTPM_COMMAND_HEADER_SIZE;

	if (bufsize < 52) {
		vtpmlogerror(VTPM_LOG_VTPM, "VTPM_ORD_SAVEHASHKEY hashkey too short!\n");
		status = TPM_BAD_PARAMETER;
		goto abort_egress;
	}
	if (bufsize > 64) {
		vtpmlogerror(VTPM_LOG_VTPM, "VTPM_ORD_SAVEHASHKEY hashkey too long!\n");
		status = TPM_BAD_PARAMETER;
		goto abort_egress;
	}

	vtpmloginfo(VTPM_LOG_VTPM, "vtpmmgr_SaveHashKey\n");
	status = find_vtpm_verified(tpmcmd->domid, opq);

	// auto-create vTPMs in group0 when saving a new UUID
	// TODO restrict to certain UUIDs (such as all-zero)
	// this is not done yet to simplify use of the TPM Manager
	if (status == TPM_BAD_PARAMETER) {
		opq->group = g_mgr->groups[0].v;
		rc = create_vtpm(opq->group, &opq->vtpm, *opq->uuid);
		if (rc) {
			status = TPM_BAD_PARAMETER;
			goto abort_egress;
		}
		if (opq->group->nr_vtpms == 1)
			opq->vtpm->flags = VTPM_FLAG_ADMIN;
		printk("SaveHashKey with unknown UUID="UUID_FMT" - creating in auth0 (f=%d)\n",
				UUID_BYTES((*opq->uuid)), opq->vtpm->flags);
		status = TPM_SUCCESS;
	}
	if (status)
		goto abort_egress;

	memcpy(opq->vtpm->data, buf, bufsize);
	memset(opq->vtpm->data + bufsize, 0, 64 - bufsize);

	vtpm_sync(opq->group, opq->vtpm);

abort_egress:
	pack_TPM_RSP_HEADER(tpmcmd->resp, VTPM_TAG_RSP, VTPM_COMMAND_HEADER_SIZE, status);
	tpmcmd->resp_len = VTPM_COMMAND_HEADER_SIZE;

	return status;
}

static TPM_RESULT vtpmmgr_LoadHashKey(struct tpm_opaque *opq, tpmcmd_t* tpmcmd)
{
	TPM_RESULT status = TPM_SUCCESS;
	int i;
	uint8_t *buf = tpmcmd->resp + VTPM_COMMAND_HEADER_SIZE;

	vtpmloginfo(VTPM_LOG_VTPM, "vtpmmgr_LoadHashKey\n");
	tpmcmd->resp_len = VTPM_COMMAND_HEADER_SIZE;

	status = find_vtpm_verified(tpmcmd->domid, opq);
	if (status)
		goto abort_egress;

	memcpy(buf, opq->vtpm->data, 64);

	for(i=52; i < 64; i++) {
		if (buf[i]) {
			tpmcmd->resp_len += 64;
			goto abort_egress;
		}
	}
	tpmcmd->resp_len += 52;

abort_egress:
	pack_TPM_RSP_HEADER(tpmcmd->resp, VTPM_TAG_RSP, tpmcmd->resp_len, status);

	return status;
}

#define CMD_BEGIN \
	TPM_RESULT status = TPM_SUCCESS; \
	uint32_t in_pos = VTPM_COMMAND_HEADER_SIZE; \
	tpmcmd->resp_len = VTPM_COMMAND_HEADER_SIZE; \
	vtpmloginfo(VTPM_LOG_TPM, "%s\n", __func__);

#define CMD_END \
 abort_egress: \
	if (status) \
		tpmcmd->resp_len = VTPM_COMMAND_HEADER_SIZE; \
	pack_TPM_RSP_HEADER(tpmcmd->resp, VTPM_TAG_RSP, tpmcmd->resp_len, status); \
	return status

#define UNPACK_IN(type, item...) do { \
	status = unpack3_ ## type (tpmcmd->req, &in_pos, tpmcmd->req_len, item); \
	if (status) { \
		status = TPM_BAD_PARAMETER; \
		goto abort_egress; \
	} \
} while (0)

#define UNPACK_GROUP(group) do { \
	uint32_t group_idx; \
	UNPACK_IN(UINT32, &group_idx); \
	if (group_idx >= g_mgr->nr_groups) { \
		status = TPM_BADINDEX; \
		goto abort_egress; \
	} \
	group = g_mgr->groups[group_idx].v; \
	if (!group) { \
		status = TPM_AUTHFAIL; \
		goto abort_egress; \
	} \
} while (0)

#define UNPACK_DONE() do { \
	if (in_pos != tpmcmd->req_len) { \
		status = TPM_BAD_PARAMETER; \
		goto abort_egress; \
	} \
} while (0)

#define PACK_OUT(type, item...) do { \
	UINT32 isize = sizeof_ ## type(item); \
	if (isize + tpmcmd->resp_len > TCPA_MAX_BUFFER_LENGTH) { \
		status = TPM_SIZE; \
		goto abort_egress; \
	} \
	pack_ ## type (tpmcmd->resp + tpmcmd->resp_len, item); \
	tpmcmd->resp_len += isize; \
} while (0)

#define PACK_BUF ((void*)(tpmcmd->resp + tpmcmd->resp_len))

static TPM_RESULT vtpmmgr_GetBootHash(struct tpm_opaque *opq, tpmcmd_t* tpmcmd)
{
	CMD_BEGIN;
	UNPACK_DONE();

	PACK_OUT(BUFFER, opq->kern_hash, 20);

	CMD_END;
}

static TPM_RESULT vtpmmgr_GetQuote(struct tpm_opaque *opq, tpmcmd_t* tpmcmd)
{
	CMD_BEGIN;
	int i;
	void *ibuf;
	uint32_t pcr_size;
	TPM_PCR_SELECTION sel;
	uint32_t extra_info_flags;

	UNPACK_IN(VPTR, &ibuf, 20, UNPACK_ALIAS);
	UNPACK_IN(TPM_PCR_SELECTION, &sel, UNPACK_ALIAS);
	UNPACK_IN(TPM_DEEP_QUOTE_INFO, &extra_info_flags);
	UNPACK_DONE();

	if (!opq->vtpm) {
		status = TPM_BAD_PARAMETER;
		goto abort_egress;
	}

	printk("ibuf: ");
	for (i=0; i < 20; i++)
		printk("%02x", ((uint8_t*)ibuf)[i]);
	printk("\n");

	status = vtpm_do_quote(opq->group, *opq->uuid, opq->kern_hash, ibuf, &sel, extra_info_flags, PACK_BUF + 256, &pcr_size, PACK_BUF);
	if (status)
		goto abort_egress;
	tpmcmd->resp_len += 256 + pcr_size;

	CMD_END;
}

static TPM_RESULT vtpmmgr_GroupList(tpmcmd_t* tpmcmd)
{
	CMD_BEGIN;
	UNPACK_DONE();
	PACK_OUT(UINT32, g_mgr->nr_groups);
	CMD_END;
}

static TPM_RESULT vtpmmgr_GroupNew(tpmcmd_t* tpmcmd)
{
	void *privCADigest;
	BYTE *pubkey;
	struct mem_group *group;
	uint32_t group_idx;
	CMD_BEGIN;

	UNPACK_IN(VPTR, &privCADigest, 20, UNPACK_ALIAS);
	UNPACK_IN(PTR, &pubkey, 256, UNPACK_ALIAS);
	UNPACK_DONE();

	group = vtpm_new_group(privCADigest);
	if (!group) {
		status = TPM_FAIL;
		goto abort_egress;
	}

	memcpy(group->id_data.saa_pubkey, pubkey, 256);

	PACK_OUT(BUFFER, group->id_data.uuid, 16);
	PACK_OUT(BUFFER, group->id_data.tpm_aik_public, 256);
	PACK_OUT(BUFFER, group->details.recovery_data, 256);

	memset(group->details.recovery_data, 0, 256);

	group->details.sequence = native_be64(g_mgr->sequence);

	if (group != g_mgr->groups[0].v) {
		group_idx = g_mgr->nr_groups;
		g_mgr->nr_groups++;
		g_mgr->groups = realloc(g_mgr->groups, g_mgr->nr_groups*sizeof(struct mem_group_hdr));
		memset(&g_mgr->groups[group_idx], 0, sizeof(g_mgr->groups[0]));
		g_mgr->groups[group_idx].v = group;
	}

	vtpm_sync_group(group, SEQ_UPDATE);
	CMD_END;
}

static TPM_RESULT vtpmmgr_GroupDel(tpmcmd_t* tpmcmd)
{
	CMD_BEGIN;
	struct mem_group *group;
	uint32_t group_idx, nr_mov;

	UNPACK_IN(UINT32, &group_idx);
	UNPACK_DONE();

	if (group_idx > g_mgr->nr_groups) {
		status = TPM_BADINDEX;
		goto abort_egress;
	}
	group = g_mgr->groups[group_idx].v;

	if (group) {
		int i, j;
		for (i = 0; i < group->nr_pages; i++) {
			for (j = 0; j < group->data[i].size; j++) {
				if (group->data[i].vtpms[j]->flags & VTPM_FLAG_OPEN) {
					status = TPM_FAIL;
					goto abort_egress;
				}
			}
		}

		for (i = 0; i < group->nr_pages; i++) {
			for (j = 0; j < group->data[i].size; j++) {
				free(group->data[i].vtpms[j]);
			}
		}
		free(group->data);
		free(group->seals);
		free(group);
	}

	g_mgr->nr_groups--;
	nr_mov = g_mgr->nr_groups - group_idx;
	memmove(&g_mgr->groups[group_idx], &g_mgr->groups[group_idx + 1], nr_mov * sizeof(g_mgr->groups[0]));

	vtpm_sync_disk(g_mgr, CTR_UPDATE);

	CMD_END;
}

static int pack_cfg_list(void* buf, struct mem_group *group)
{
	int i;
	void *bstart = buf;
	memcpy(buf, &group->details.cfg_seq, 8); buf += 8;
	buf = pack_UINT32(buf, group->nr_seals);
	for(i=0; i < group->nr_seals; i++) {
		memcpy(buf, &group->seals[i].digest_release, 20);
		buf += 20;
	}
	memcpy(buf, &group->seal_bits.nr_kerns, 4); buf += 4;
	memcpy(buf, &group->seal_bits.kernels, 20 * be32_native(group->seal_bits.nr_kerns));
	return buf - bstart + 20 * be32_native(group->seal_bits.nr_kerns);
}

static TPM_RESULT vtpmmgr_GroupShow(tpmcmd_t* tpmcmd)
{
	CMD_BEGIN;
	struct mem_group *group;

	UNPACK_GROUP(group);
	UNPACK_DONE();

	// TODO show is read-only access, need to hit disk if group is NULL

	PACK_OUT(BUFFER, group->id_data.uuid, 16);
	PACK_OUT(BUFFER, group->id_data.saa_pubkey, 256);
	tpmcmd->resp_len += pack_cfg_list(PACK_BUF, group);

	CMD_END;
}

static TPM_RESULT vtpmmgr_GroupActivate(tpmcmd_t* tpmcmd)
{
	CMD_BEGIN;
	struct mem_group *group;
	uint32_t blobSize;
	void *blob;

	UNPACK_GROUP(group);
	UNPACK_IN(UINT32, &blobSize);
	UNPACK_IN(VPTR, &blob, blobSize, UNPACK_ALIAS);
	UNPACK_DONE();

	status = group_do_activate(group, blob, blobSize, tpmcmd->resp, &tpmcmd->resp_len);

	CMD_END;
}

/* 2048-bit MODP Group from RFC3526:
 *   2^2048 - 2^1984 - 1 + 2^64 * { [2^1918 pi] + 124476 }
 */
static uint8_t P_bytes[256] = {
	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2,
	0x21, 0x68, 0xC2, 0x34, 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1,
	0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, 0x02, 0x0B, 0xBE, 0xA6,
	0x3B, 0x13, 0x9B, 0x22, 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD,
	0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, 0x30, 0x2B, 0x0A, 0x6D,
	0xF2, 0x5F, 0x14, 0x37, 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45,
	0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, 0xF4, 0x4C, 0x42, 0xE9,
	0xA6, 0x37, 0xED, 0x6B, 0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED,
	0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, 0xAE, 0x9F, 0x24, 0x11,
	0x7C, 0x4B, 0x1F, 0xE6, 0x49, 0x28, 0x66, 0x51, 0xEC, 0xE4, 0x5B, 0x3D,
	0xC2, 0x00, 0x7C, 0xB8, 0xA1, 0x63, 0xBF, 0x05, 0x98, 0xDA, 0x48, 0x36,
	0x1C, 0x55, 0xD3, 0x9A, 0x69, 0x16, 0x3F, 0xA8, 0xFD, 0x24, 0xCF, 0x5F,
	0x83, 0x65, 0x5D, 0x23, 0xDC, 0xA3, 0xAD, 0x96, 0x1C, 0x62, 0xF3, 0x56,
	0x20, 0x85, 0x52, 0xBB, 0x9E, 0xD5, 0x29, 0x07, 0x70, 0x96, 0x96, 0x6D,
	0x67, 0x0C, 0x35, 0x4E, 0x4A, 0xBC, 0x98, 0x04, 0xF1, 0x74, 0x6C, 0x08,
	0xCA, 0x18, 0x21, 0x7C, 0x32, 0x90, 0x5E, 0x46, 0x2E, 0x36, 0xCE, 0x3B,
	0xE3, 0x9E, 0x77, 0x2C, 0x18, 0x0E, 0x86, 0x03, 0x9B, 0x27, 0x83, 0xA2,
	0xEC, 0x07, 0xA2, 0x8F, 0xB5, 0xC5, 0x5D, 0xF0, 0x6F, 0x4C, 0x52, 0xC9,
	0xDE, 0x2B, 0xCB, 0xF6, 0x95, 0x58, 0x17, 0x18, 0x39, 0x95, 0x49, 0x7C,
	0xEA, 0x95, 0x6A, 0xE5, 0x15, 0xD2, 0x26, 0x18, 0x98, 0xFA, 0x05, 0x10,
	0x15, 0x72, 0x8E, 0x5A, 0x8A, 0xAC, 0xAA, 0x68, 0xFF, 0xFF, 0xFF, 0xFF,
	0xFF, 0xFF, 0xFF, 0xFF,
};
static t_uint Gp[] = { 2 };

static void tm_dhkx_gen(void* dhkx1, void* dhkx2, void* out)
{
	mpi GX = { 0 }, GY = { 0 }, K = { 0 }, RP = { 0 };
	mpi P = { 0 };

	t_uint Xp[sizeof(P_bytes) / sizeof(t_uint)];
	mpi X = {
		.s = 1,
		.n = ARRAY_SIZE(Xp),
		.p = Xp
	};
	mpi G = {
		.s = 1,
		.n = 1,
		.p = Gp,
	};
	mpi_read_binary(&P, P_bytes, sizeof(P_bytes));

	do_random(Xp, sizeof(Xp));

	mpi_exp_mod(&GX, &G, &X, &P, &RP);
	mpi_write_binary(&GX, dhkx2, 256);
	mpi_free(&GX);

	mpi_read_binary(&GY, dhkx1, 256);
	mpi_exp_mod(&K, &GY, &X, &P, &RP);
	mpi_free(&P);
	mpi_free(&RP);
	mpi_free(&GY);

	mpi_write_binary(&K, (void*)Xp, 256);
	mpi_free(&K);
	sha2((void*)Xp, 256, out, 0);
}

static void xor2_256b(void *xv, const void* yv)
{
	int i;
	uint64_t *x = xv;
	const uint64_t *y = yv;
	for(i=0; i < 4; i++)
		x[i] ^= y[i];
}

static TPM_RESULT vtpmmgr_GroupRegister(tpmcmd_t* tpmcmd)
{
	CMD_BEGIN;
	struct mem_group *group = NULL;
	tpmrsa_context saa_rsa = TPMRSA_CTX_INIT;
	struct tpm_authdata digest;
	sha1_context ctx;
	TPM_PCR_SELECTION sel;
	void *dhkx1, *dhkx2, *gk, *sig;
	uint32_t extra_info_flags = 0;

	UNPACK_GROUP(group);
	UNPACK_IN(VPTR, &dhkx1, 256, UNPACK_ALIAS);
	UNPACK_IN(VPTR, &sig, 256, UNPACK_ALIAS);
	UNPACK_IN(TPM_PCR_SELECTION, &sel, UNPACK_ALIAS);
	UNPACK_DONE();

	/* Only generating this quote during the same boot that this group was
	 * created in allows the quote to prove that the group key has never
	 * been available outside a configuration approved by its SAA.
	 */
	if (!(group->flags & MEM_GROUP_FLAG_FIRSTBOOT)) {
		status = TPM_FAIL;
		goto abort_egress;
	}

	sha1(dhkx1, 256, digest.bits);
	tpmrsa_set_pubkey(&saa_rsa, group->id_data.saa_pubkey, 256, 0, 0);
	if (tpmrsa_sigcheck(&saa_rsa, sig, digest.bits))
		status = TPM_FAIL;
	tpmrsa_free(&saa_rsa);
	if (status)
		goto abort_egress;

	dhkx2 = PACK_BUF;
	tpmcmd->resp_len += 256;
	gk = PACK_BUF;
	tpmcmd->resp_len += 32;

	tm_dhkx_gen(dhkx1, dhkx2, gk);
	xor2_256b(gk, &group->group_key);

	sha1_starts(&ctx);
	sha1_update(&ctx, (void*)"REGR", 4);
	sha1_update(&ctx, dhkx1, 256);
	sha1_update(&ctx, dhkx2, 256 + 32);
	sha1_finish(&ctx, digest.bits);

	status = vtpm_do_quote(group, NULL, NULL, &digest, &sel, extra_info_flags,NULL, NULL, PACK_BUF);
	tpmcmd->resp_len += 256;

	CMD_END;
}

static TPM_RESULT vtpmmgr_GroupUpdate(tpmcmd_t* tpmcmd)
{
	CMD_BEGIN;
	struct mem_group *group;
	int i;
	int hstart;
	uint32_t nr_kerns, nr_seals;
	uint64_t old_seq, new_seq;
	struct mem_seal *seals = NULL;
	tpmrsa_context saa_rsa = TPMRSA_CTX_INIT;
	unsigned char digest[20];
	TPM_RESULT rc;
	void *sig, *seal_bits, *kern_bits;

	UNPACK_GROUP(group);
	UNPACK_IN(VPTR, &sig, 256, UNPACK_ALIAS);

	// Hash starts here
	hstart = in_pos;

	new_seq = be64_native(*(be64_t*)(tpmcmd->req + in_pos));
	old_seq = be64_native(group->details.cfg_seq);
	in_pos += 8;
	if (old_seq > new_seq) {
		status = TPM_FAIL;
		goto abort_egress;
	}

	UNPACK_IN(UINT32, &nr_seals);
	UNPACK_IN(VPTR, &seal_bits, nr_seals * 20, UNPACK_ALIAS);

	UNPACK_IN(UINT32, &nr_kerns);
	UNPACK_IN(VPTR, &kern_bits, nr_kerns * 20, UNPACK_ALIAS);

	// TODO handle saving larger lists on disk
	if (nr_seals > NR_SEALS_PER_GROUP) {
		status = TPM_SIZE;
		goto abort_egress;
	}

	if (nr_kerns > NR_KERNS_PER_GROUP) {
		status = TPM_SIZE;
		goto abort_egress;
	}

	sha1(tpmcmd->req + hstart, in_pos - hstart, digest);

	seals = calloc(nr_seals, sizeof(seals[0]));

	for(i=0; i < nr_seals; i++) {
		TPM_PCR_SELECTION sel;
		UNPACK_IN(TPM_PCR_SELECTION, &sel, UNPACK_ALIAS);
		memcpy(&seals[i].digest_release, seal_bits, 20);
		seal_bits += 20;
		if (sel.sizeOfSelect > 4) {
			status = TPM_BAD_PARAMETER;
			goto abort_egress;
		}
		seals[i].pcr_selection = native_le32(0);
		memcpy(&seals[i].pcr_selection, sel.pcrSelect, sel.sizeOfSelect);
	}

	UNPACK_DONE();

	tpmrsa_set_pubkey(&saa_rsa, group->id_data.saa_pubkey, 256, 0, 0);
	rc = tpmrsa_sigcheck(&saa_rsa, sig, digest);
	tpmrsa_free(&saa_rsa);
	if (rc) {
		printk("sigcheck failed: %d\n", rc);
		status = rc;
		goto abort_egress;
	}

	// Commit
	free(group->seals);

	memcpy(&group->seal_bits.kernels, kern_bits, 20 * nr_kerns);
	group->details.cfg_seq = native_be64(new_seq);
	group->nr_seals = nr_seals;
	group->seals = seals;
	group->seal_bits.nr_kerns = native_be32(nr_kerns);

	seals = NULL;

	group->flags &= ~MEM_GROUP_FLAG_SEAL_VALID;
	if (group == g_mgr->groups[0].v)
		g_mgr->root_seals_valid = 0;

	// TODO use GROUP_KEY_UPDATE or MGR_KEY_UPDATE here?
	// only required if this update was to address a potential key leak
	vtpm_sync_group(group, SEQ_UPDATE);

 abort_egress:
	free(seals);

	pack_TPM_RSP_HEADER(tpmcmd->resp, VTPM_TAG_RSP, tpmcmd->resp_len, status);
	return status;
}

static TPM_RESULT vtpmmgr_VtpmList(tpmcmd_t* tpmcmd)
{
	CMD_BEGIN;
	struct mem_group *group;
	uint32_t vtpm_offset;
	int i, j;

	UNPACK_GROUP(group);
	UNPACK_IN(UINT32, &vtpm_offset);

	PACK_OUT(UINT32, group->nr_vtpms);
	if (vtpm_offset > group->nr_vtpms)
		goto egress;

	for(i=0; i < group->nr_pages; i++) {
		struct mem_vtpm_page *pg = &group->data[i];
		for(j=0; j < pg->size; j++) {
			if (vtpm_offset) {
				// TODO a proper seek would be far faster
				vtpm_offset--;
				continue;
			}
			memcpy(PACK_BUF, pg->vtpms[j]->uuid, 16);
			tpmcmd->resp_len += 16;
			if (tpmcmd->resp_len + 16 > TCPA_MAX_BUFFER_LENGTH)
				goto egress;
		}
	}

 egress:
	CMD_END;
}

static TPM_RESULT vtpmmgr_VtpmNew(tpmcmd_t* tpmcmd)
{
	CMD_BEGIN;
	struct mem_group *group;
	struct mem_vtpm *vtpm;
	uuid_t newuuid;
	int rc;

	UNPACK_GROUP(group);

	// XXX allow non-random UUIDs for testing
	if (tpmcmd->req_len == 14 + 16)
		UNPACK_IN(BUFFER, newuuid, 16);
	else
		gen_random_uuid(newuuid);
	UNPACK_DONE();

	rc = create_vtpm(group, &vtpm, newuuid);
	if (rc) {
		status = TPM_FAIL;
		goto abort_egress;
	}
	memset(vtpm->data, 0, 64);
	vtpm_sync(group, vtpm);

	PACK_OUT(BUFFER, newuuid, 16);
	CMD_END;
}

static TPM_RESULT vtpmmgr_VtpmDel(tpmcmd_t* tpmcmd)
{
	CMD_BEGIN;
	uuid_t uuid;
	struct mem_group *group;
	struct mem_vtpm *vtpm;
	int rc;

	UNPACK_IN(BUFFER, uuid, 16);
	UNPACK_DONE();
	rc = find_vtpm(&group, &vtpm, uuid);
	if (rc) {
		status = TPM_FAIL;
		goto abort_egress;
	}

	if (vtpm->flags & VTPM_FLAG_OPEN) {
		status = TPM_FAIL;
		goto abort_egress;
	}

	delete_vtpm(group, vtpm);

	CMD_END;
}

static int vtpmmgr_permcheck(struct tpm_opaque *opq)
{
	if (!opq->vtpm)
		return 1;
	if (opq->vtpm->flags & VTPM_FLAG_ADMIN)
		return 0;
	return 1;
}

TPM_RESULT vtpmmgr_handle_getrandom(struct tpm_opaque *opaque,
				    tpmcmd_t* tpmcmd)
{
	TPM_RESULT status = TPM_SUCCESS;
	TPM_TAG tag;
	UINT32 size;
	const int max_rand_size = TCPA_MAX_BUFFER_LENGTH -
				  sizeof_TPM_RQU_GetRandom(tpmcmd->req);
	UINT32 rand_offset;
	UINT32 rand_size;
	TPM_COMMAND_CODE ord;
	BYTE *p;

	if (tpmcmd->req_len != sizeof_TPM_RQU_GetRandom(tpmcmd->req)) {
		status = TPM_BAD_PARAMETER;
		tag = TPM_TAG_RQU_COMMAND;
		goto abort_egress;
	}

	p = unpack_TPM_RQU_HEADER(tpmcmd->req, &tag, &size, &ord);

	if (!hw_is_tpm2()) {
		size = TCPA_MAX_BUFFER_LENGTH;
		TPMTRYRETURN(TPM_TransmitData(tpmcmd->req, tpmcmd->req_len,
					      tpmcmd->resp, &size));
		tpmcmd->resp_len = size;

		return TPM_SUCCESS;
	}

	/* TPM_GetRandom req: <header><uint32 num bytes> */
	unpack_UINT32(p, &rand_size);

	/* Returning fewer bytes is acceptable per the spec. */
	if (rand_size > max_rand_size)
		rand_size = max_rand_size;

	/* Call TPM2_GetRandom but return a TPM_GetRandom response. */
	/* TPM_GetRandom resp: <header><uint32 num bytes><num random bytes> */
	rand_offset = sizeof_TPM_RSP_HEADER(tpmcmd->resp) +
		      sizeof_UINT32(tpmcmd->resp);

	TPMTRYRETURN(TPM2_GetRandom(&rand_size, tpmcmd->resp + rand_offset));

	p = pack_TPM_RSP_HEADER(tpmcmd->resp, TPM_TAG_RSP_COMMAND,
				rand_offset + rand_size, status);
	p = pack_UINT32(p, rand_size);
	tpmcmd->resp_len = rand_offset + rand_size;

	return status;

abort_egress:
	tpmcmd->resp_len = VTPM_COMMAND_HEADER_SIZE;
	pack_TPM_RSP_HEADER(tpmcmd->resp, tag + 3, tpmcmd->resp_len, status);

	return status;
}

TPM_RESULT vtpmmgr_handle_cmd(
		struct tpm_opaque *opaque,
		tpmcmd_t* tpmcmd)
{
	TPM_RESULT status = TPM_SUCCESS;
	TPM_TAG tag;
	UINT32 size;
	TPM_COMMAND_CODE ord;

	if (tpmcmd->req_len < sizeof_TPM_RQU_HEADER(tpmcmd->req)) {
		status = TPM_BAD_PARAMETER;
		tag = TPM_TAG_RQU_COMMAND;
		goto abort_egress;
	}

	unpack_TPM_RQU_HEADER(tpmcmd->req,
			&tag, &size, &ord);

	/* Handle the command now */
	switch(tag) {
	case VTPM_TAG_REQ:
		// This is a vTPM command
		switch(ord) {
		case VTPM_ORD_SAVEHASHKEY:
			return vtpmmgr_SaveHashKey(opaque, tpmcmd);
		case VTPM_ORD_LOADHASHKEY:
			return vtpmmgr_LoadHashKey(opaque, tpmcmd);
		case VTPM_ORD_GET_BOOT_HASH:
			return vtpmmgr_GetBootHash(opaque, tpmcmd);
		case VTPM_ORD_GET_QUOTE:
			return vtpmmgr_GetQuote(opaque, tpmcmd);
		default:
			vtpmlogerror(VTPM_LOG_VTPM, "Invalid vTPM Ordinal %" PRIu32 "\n", ord);
			status = TPM_BAD_ORDINAL;
		}
		break;
	case VTPM_TAG_REQ2:
		// This is a management command
		if (vtpmmgr_permcheck(opaque)) {
			status = TPM_AUTHFAIL;
			vtpmlogerror(VTPM_LOG_VTPM, "Rejected attempt to use management command from client\n");
			break;
		}
		switch (ord) {
		case VTPM_ORD_GROUP_LIST:
			return vtpmmgr_GroupList(tpmcmd);
		case VTPM_ORD_GROUP_NEW:
			return vtpmmgr_GroupNew(tpmcmd);
		case VTPM_ORD_GROUP_DEL:
			return vtpmmgr_GroupDel(tpmcmd);
		case VTPM_ORD_GROUP_ACTIVATE:
			return vtpmmgr_GroupActivate(tpmcmd);
		case VTPM_ORD_GROUP_REGISTER:
			return vtpmmgr_GroupRegister(tpmcmd);
		case VTPM_ORD_GROUP_UPDATE:
			return vtpmmgr_GroupUpdate(tpmcmd);
		case VTPM_ORD_GROUP_SHOW:
			return vtpmmgr_GroupShow(tpmcmd);
		case VTPM_ORD_VTPM_LIST:
			return vtpmmgr_VtpmList(tpmcmd);
		case VTPM_ORD_VTPM_NEW:
			return vtpmmgr_VtpmNew(tpmcmd);
		case VTPM_ORD_VTPM_DEL:
			return vtpmmgr_VtpmDel(tpmcmd);
		default:
			vtpmlogerror(VTPM_LOG_VTPM, "Invalid TM Ordinal %" PRIu32 "\n", ord);
			status = TPM_BAD_ORDINAL;
		}
		break;
	case TPM_TAG_RQU_COMMAND:
	case TPM_TAG_RQU_AUTH1_COMMAND:
	case TPM_TAG_RQU_AUTH2_COMMAND:
		//This is a TPM passthrough command
		switch(ord) {
		case TPM_ORD_GetRandom:
			vtpmloginfo(VTPM_LOG_VTPM, "Passthrough: TPM_GetRandom\n");
			return vtpmmgr_handle_getrandom(opaque, tpmcmd);
		case TPM_ORD_PcrRead:
			vtpmloginfo(VTPM_LOG_VTPM, "Passthrough: TPM_PcrRead\n");
			// Quotes also need to be restricted to hide PCR values
			break;
		case TPM_ORD_Extend:
			// TODO allow to certain clients? A malicious client
			// could scramble PCRs and make future quotes invalid.
			if (vtpmmgr_permcheck(opaque)) {
				vtpmlogerror(VTPM_LOG_VTPM, "Disallowed TPM_Extend\n");
				status = TPM_DISABLED_CMD;
				goto abort_egress;
			} else {
				vtpmloginfo(VTPM_LOG_VTPM, "Passthrough: TPM_Extend\n");
			}
			break;
		default:
			vtpmlogerror(VTPM_LOG_VTPM, "TPM Disallowed Passthrough ord=%" PRIu32 "\n", ord);
			status = TPM_DISABLED_CMD;
			goto abort_egress;
		}

		size = TCPA_MAX_BUFFER_LENGTH;
		TPMTRYRETURN(TPM_TransmitData(tpmcmd->req, tpmcmd->req_len, tpmcmd->resp, &size));
		tpmcmd->resp_len = size;

		return TPM_SUCCESS;
	default:
		vtpmlogerror(VTPM_LOG_VTPM, "Invalid tag=%" PRIu16 "\n", tag);
		status = TPM_BADTAG;
	}

abort_egress:
	tpmcmd->resp_len = VTPM_COMMAND_HEADER_SIZE;
	pack_TPM_RSP_HEADER(tpmcmd->resp, tag + 3, tpmcmd->resp_len, status);

	return status;
}
