/*
 * Copyright 2020 Intel Corporation
 * SPDX-License-Identifier: Apache 2.0
 */

/*!
 * \file
 * \brief This file implements msg70 of TO2 state machine.
 */

#include "fdoCrypto.h"
#include "load_credentials.h"
#include "fdoprot.h"
#include "util.h"

/**
 * msg70() - TO2.Done
 *
 * TO2.Done = [
 *   NonceTO2ProveDv         ;; Nonce generated by Owner Onboarding Service
 *                  ;; ...and sent to Device ROE in Msg TO2.ProveOVHdr
 * ]
 */
int32_t msg70(fdo_prot_t *ps)
{
	int ret = -1;
	// GUID needs (2 * 16) + 4 + 1 sized buffer for format 8-4-4-4-12,
	// simplifying by using a larger buffer
	char guid_buf[BUFF_SIZE_48_BYTES] = {0};

	if (!ps) {
		LOG(LOG_ERROR, "Invalid protocol state\n");
		return ret;
	}

	LOG(LOG_DEBUG, "TO2.Done started\n");

	LOG(LOG_DEBUG, "(Old) GUID before TO2: %s\n",
		fdo_guid_to_string(ps->dev_cred->owner_blk->guid, guid_buf, sizeof(guid_buf)));

	/*
	 * TODO: Writing credentials to TEE!
	 * This GUID came as TO2SetupDevicePayload.Guid - "the new transaction GUID"
	 * which will overwrite GUID in initial credential data.
	 * A new transaction will start fresh, taking the latest
	 * credential (among them this, new GUID). That's why
	 * simple memorizing GUID in RAM is not needed.
	 */
	fdo_byte_array_free(ps->dev_cred->owner_blk->guid);
	ps->dev_cred->owner_blk->guid = ps->osc->guid;

	fdo_rendezvous_list_free(ps->dev_cred->owner_blk->rvlst);
	ps->dev_cred->owner_blk->rvlst = ps->osc->rvlst;

	fdo_public_key_free(ps->dev_cred->owner_blk->pk);
	ps->dev_cred->owner_blk->pk = ps->osc->pubkey;

	if (ps->reuse_enabled && reuse_supported) {
		// Reuse scenario, moving to post DI state
		ps->dev_cred->ST = FDO_DEVICE_STATE_READY1;
	} else if (resale_supported) {
		// Done with FIDO Device Onboard.
		// As of now moving to done state for resale
		ps->dev_cred->ST = FDO_DEVICE_STATE_IDLE;
		// create new Owner's public key hash
		fdo_hash_free(ps->dev_cred->owner_blk->pkh);
		ps->dev_cred->owner_blk->pkh = fdo_pub_key_hash(ps->dev_cred->owner_blk->pk);
		if (!ps->dev_cred->owner_blk->pkh) {
			LOG(LOG_ERROR, "TO2.Done: Hash creation of TO2.SetupDevice.Owner2Key failed\n");
			goto err;
		}
	}

	// clear and reuse the buffer to print new guid
	if (0 != memset_s(guid_buf, sizeof(guid_buf), 0)) {
		LOG(LOG_ERROR, "TO2.Done: Failed to clear GUID buffer\n");
		goto err;
	}
	LOG(LOG_DEBUG, "(New) GUID after TO2: %s\n",
		fdo_guid_to_string(ps->dev_cred->owner_blk->guid, guid_buf, sizeof(guid_buf)));

	/* Rotate Data Protection Key */
	if (0 != fdo_generate_storage_hmac_key()) {
		LOG(LOG_ERROR, "TO2.Done: Failed to rotate data protection key.\n");
	}
	LOG(LOG_DEBUG, "TO2.Done: Data protection key rotated successfully!!\n");

	if (!ps->reuse_enabled) {
		/* Commit the replacement hmac key only if reuse was not triggered*/
		if (fdo_commit_ov_replacement_hmac_key() != 0) {
			LOG(LOG_ERROR, "TO2.Done: Failed to store new device hmac key.\n");
			goto err;
		}
		LOG(LOG_DEBUG, "TO2.Done: Updated device's new hmac key\n");
	} else {
		LOG(LOG_DEBUG, "TO2.Done: Device hmac key is unchanged as reuse was triggered.\n");
	}

	/* Write new device credentials and state*/
	if (!store_device_status(&ps->dev_cred->ST)) {
		LOG(LOG_ERROR, "TO2.Done: Failed to store updated device status\n");
		goto err;
	}

	if (store_credential(ps->dev_cred) != 0) {
		LOG(LOG_ERROR, "TO2.Done: Failed to store new device creds\n");
		goto err;
	}
	LOG(LOG_DEBUG, "TO2.Done: Updated device with new credentials\n");

	// Do not point to ps->osc contents anymore.
	// This keeps it clean for freeing memory at TO2 exit at all times.
	ps->dev_cred->owner_blk->guid = NULL;
	ps->dev_cred->owner_blk->rvlst = NULL;
	ps->dev_cred->owner_blk->pk = NULL;

	fdow_next_block(&ps->fdow, FDO_TO2_DONE);

	if (!fdow_start_array(&ps->fdow, 1)) {
		LOG(LOG_ERROR, "TO2.Done: Failed to start array\n");
		return false;
	}

	if(!ps->nonce_to2provedv) {
		LOG(LOG_ERROR, "TO2.Done: NonceTO2ProveDv not found\n");
		return false;
	}

	if (!fdow_byte_string(&ps->fdow, ps->nonce_to2provedv->bytes,
		ps->nonce_to2provedv->byte_sz)) {
		LOG(LOG_ERROR, "TO2.Done: Failed to write NonceTO2ProveDv\n");
		return false;
	}

	if (!fdow_end_array(&ps->fdow)) {
		LOG(LOG_ERROR, "TO2.Done: Failed to end array\n");
		return false;
	}

	if (!fdo_encrypted_packet_windup(&ps->fdow, FDO_TO2_DONE)) {
		LOG(LOG_ERROR, "TO2.Done: Failed to create Encrypted Message\n");
		goto err;
	}

	ps->success = true;
	ps->state = FDO_STATE_TO2_RCV_DONE_2;
	LOG(LOG_DEBUG, "TO2.Done completed successfully\n");
	ret = 0; /*Mark as success */

err:
	return ret;
}
