/* Copyright 2013-2017 IBM Corp.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * 	http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef pr_fmt
#define pr_fmt(fmt) "STB: " fmt
#endif

#include <skiboot.h>
#include <device.h>
#include <nvram.h>
#include <opal-api.h>
#include "secureboot.h"
#include "trustedboot.h"
#include "tpm_chip.h"

/* For debugging only */
//#define STB_DEBUG

static bool trusted_mode = false;
static bool trusted_init = false;
static bool boot_services_exited = false;

/*
 * This maps a PCR for each resource we can measure. The PCR number is
 * mapped according to the TCG PC Client Platform Firmware Profile
 * specification, Revision 00.21
 * Only resources included in this whitelist can be measured.
 */
static struct {
	enum resource_id id;
	TPM_Pcr pcr;
} resources[] = {
	{ RESOURCE_ID_IMA_CATALOG, PCR_2 },
	{ RESOURCE_ID_KERNEL, PCR_4 },
	{ RESOURCE_ID_CAPP,   PCR_2 },
	{ RESOURCE_ID_VERSION, PCR_3 },
};

/*
 * Event Separator - digest of 0xFFFFFFFF
 */
static struct {
	const unsigned char *event;
	const unsigned char *sha1;
	const unsigned char *sha256;
} ev_separator = {

	.event = "\xff\xff\xff\xff",

	.sha1   = "\xd9\xbe\x65\x24\xa5\xf5\x04\x7d\xb5\x86"
		  "\x68\x13\xac\xf3\x27\x78\x92\xa7\xa3\x0a",

	.sha256 = "\xad\x95\x13\x1b\xc0\xb7\x99\xc0\xb1\xaf"
		  "\x47\x7f\xb1\x4f\xcf\x26\xa6\xa9\xf7\x60"
		  "\x79\xe4\x8b\xf0\x90\xac\xb7\xe8\x36\x7b"
		  "\xfd\x0e"
};

static TPM_Pcr map_pcr(enum resource_id id)
{
	int i;
	for (i = 0; i < ARRAY_SIZE(resources); i++) {
		if (resources[i].id == id)
			return resources[i].pcr;
	}
	return -1;
}

void trustedboot_init(void)
{
	struct dt_node *node;

	node = dt_find_by_path(dt_root, "/ibm,secureboot");
	if (!node) {
		prlog(PR_NOTICE, "trusted boot not supported\n");
		return;
	}

	if (!secureboot_is_compatible(node, NULL, NULL)) {
		/**
		 * @fwts-label TrustedBootNotCompatible
		 * @fwts-advice Compatible trustedboot driver not found. Probably,
		 * hostboot/mambo/skiboot has updated the
		 * /ibm,secureboot/compatible without adding a driver that
		 * supports it.
		 */
		prlog(PR_ERR, "trustedboot init FAILED, '%s' node not "
		      "compatible.\n", node->name);
		return;
	}

	if (nvram_query_eq("force-trusted-mode", "true")) {
		trusted_mode = true;
		prlog(PR_NOTICE, "trusted mode on (FORCED by nvram)\n");
	} else {
		trusted_mode = dt_has_node_property(node, "trusted-enabled", NULL);
		prlog(PR_INFO, "trusted mode %s\n",
		      trusted_mode ? "on" : "off");
	}

	if (!trusted_mode)
		return;

	cvc_init();
	tpm_init();

	trusted_init = true;
}

int trustedboot_exit_boot_services(void)
{
	uint32_t pcr;
	int rc = 0;
	bool failed = false;

	boot_services_exited = true;

	if (!trusted_mode)
		goto out_free;

#ifdef STB_DEBUG
	prlog(PR_NOTICE, "ev_separator.event: %s\n", ev_separator.event);
	prlog(PR_NOTICE, "ev_separator.sha1:\n");
	stb_print_data((uint8_t*) ev_separator.sha1, TPM_ALG_SHA1_SIZE);
	prlog(PR_NOTICE, "ev_separator.sha256:\n");
	stb_print_data((uint8_t*) ev_separator.sha256, TPM_ALG_SHA256_SIZE);
#endif
	/*
	 * As defined in the TCG Platform Firmware PWe are done. Extending the digest of 0xFFFFFFFF
	 * in PCR[0-7], and recording an EV_SEPARATOR event in
	 * event log as defined in the TCG Platform Firmware Profile
	 * specification, Revision 00.21
	 */
	for (pcr = 0; pcr < 8; pcr++) {
		rc = tpm_extendl(pcr, TPM_ALG_SHA256,
				(uint8_t*) ev_separator.sha256,
				TPM_ALG_SHA256_SIZE, TPM_ALG_SHA1,
				(uint8_t*) ev_separator.sha1,
				TPM_ALG_SHA1_SIZE, EV_SEPARATOR,
				ev_separator.event);
		if (rc)
			failed = true;
	}
	tpm_add_status_property();

out_free:
	tpm_cleanup();

	return (failed) ? -1 : 0;
}

int trustedboot_measure(enum resource_id id, void *buf, size_t len)
{
	uint8_t digest[SHA512_DIGEST_LENGTH];
	void *buf_aux;
	size_t len_aux;
	const char *name;
	TPM_Pcr pcr;
	int rc = -1;

	if (!trusted_mode)
		return 1;

	name = flash_map_resource_name(id);
	if (!name) {
		/**
		 * @fwts-label ResourceNotMeasuredUnknown
		 * @fwts-advice This is a bug in the trustedboot_measure()
		 * caller, which is passing an unknown resource_id.
		 */
		prlog(PR_ERR, "resource NOT MEASURED, resource_id=%d unknown\n", id);
		return -1;
	}

        if (!trusted_init) {
                prlog(PR_ERR, "resource NOT MEASURED, resource_id=%d "
                      "trustedboot not yet initialized\n", id);
                return -1;
        }

	if (boot_services_exited) {
		prlog(PR_ERR, "%s NOT MEASURED. Already exited from boot "
		      "services\n", name);
		return -1;
	}
	pcr = map_pcr(id);
	if (pcr == -1) {
		/**
		 * @fwts-label ResourceNotMappedToPCR
		 * @fwts-advice This is a bug. The resource cannot be measured
		 * because it is not mapped to a PCR in the resources[] array.
		 */
		prlog(PR_ERR, "%s NOT MEASURED, it's not mapped to a PCR\n", name);
		return -1;
	}
	if (!buf) {
		/**
		 * @fwts-label ResourceNotMeasuredNull
		 * @fwts-advice This is a bug. The trustedboot_measure() caller
		 * provided a NULL container.
		 */
		prlog(PR_ERR, "%s NOT MEASURED, it's null\n", name);
		return -1;
	}
	if (stb_is_container(buf, len)) {
		buf_aux = buf + SECURE_BOOT_HEADERS_SIZE;
		len_aux = len - SECURE_BOOT_HEADERS_SIZE;
	} else {
		buf_aux = buf;
		len_aux = len;
	}

	rc = call_cvc_sha512(buf_aux, len_aux, digest, SHA512_DIGEST_LENGTH);

	if (rc == OPAL_SUCCESS) {
		prlog(PR_NOTICE, "%s hash calculated\n", name);
	} else if (rc == OPAL_PARAMETER) {
		prlog(PR_ERR, "%s NOT MEASURED, invalid param. buf=%p, "
		      "len=%zd, digest=%p\n", name, buf_aux,
		      len_aux, digest);
		return -1;
	} else if (rc == OPAL_UNSUPPORTED) {
		prlog(PR_ERR, "%s NOT MEASURED, CVC-sha512 service not "
		      "supported\n", name);
		return -1;
	} else {
		prlog(PR_ERR, "%s NOT MEASURED, unknown CVC-sha512 error. "
		      "rc=%d\n", name, rc);
		return -1;
	}

#ifdef STB_DEBUG
	stb_print_data(digest, TPM_ALG_SHA256_SIZE);
#endif
	/*
	 * Extend the given PCR number in both sha256 and sha1 banks with the
	 * sha512 hash calculated. The hash is truncated accordingly to fit the
	 * PCR.
	 */
	return tpm_extendl(pcr,
			   TPM_ALG_SHA256, digest, TPM_ALG_SHA256_SIZE,
			   TPM_ALG_SHA1,   digest, TPM_ALG_SHA1_SIZE,
			   EV_ACTION, name);
}
