/* Copyright 2015 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.
 */
/*
 * TODO(ngm): only the NIST-P256 curve is currently supported.
 */

#include "CryptoEngine.h"
#include "TPMB.h"

#include "trng.h"
#include "util.h"
#include "dcrypto.h"

#include "cryptoc/p256.h"
#include "cryptoc/p256_ecdsa.h"
#include "cryptoc/util.h"

static void reverse_tpm2b(TPM2B *b)
{
	reverse(b->buffer, b->size);
}

TPM2B_BYTE_VALUE(4);
TPM2B_BYTE_VALUE(32);

static int check_p256_param(const TPM2B_ECC_PARAMETER *a)
{
	return a->b.size == sizeof(p256_int);
}

static int check_p256_point(const TPMS_ECC_POINT *a)
{
	return check_p256_param(&a->x) &&
		check_p256_param(&a->y);
}

BOOL _cpri__EccIsPointOnCurve(TPM_ECC_CURVE curve_id, TPMS_ECC_POINT *q)
{
	int result;

	switch (curve_id) {
	case TPM_ECC_NIST_P256:
		if (!check_p256_point(q))
			return FALSE;

		reverse_tpm2b(&q->x.b);
		reverse_tpm2b(&q->y.b);

		result = dcrypto_p256_is_valid_point((p256_int *) q->x.b.buffer,
					(p256_int *) q->y.b.buffer);

		reverse_tpm2b(&q->x.b);
		reverse_tpm2b(&q->y.b);

		if (result)
			return TRUE;
		else
			return FALSE;
	default:
		return FALSE;
	}
}

/* out = n1*G, or out = n2*in */
CRYPT_RESULT _cpri__EccPointMultiply(
	TPMS_ECC_POINT *out, TPM_ECC_CURVE curve_id,
	TPM2B_ECC_PARAMETER *n1, TPMS_ECC_POINT *in, TPM2B_ECC_PARAMETER *n2)
{
	int result;

	switch (curve_id) {
	case TPM_ECC_NIST_P256:
		if ((n1 != NULL && n2 != NULL) ||
			(n1 == NULL && n2 == NULL))
			/* Only one of n1 or n2 must be specified. */
			return CRYPT_PARAMETER;
		if ((n2 != NULL && in == NULL) ||
			(n2 == NULL && in != NULL))
			return CRYPT_PARAMETER;
		if (n1 != NULL && !check_p256_param(n1))
			return CRYPT_PARAMETER;
		if (in != NULL &&
			(!check_p256_point(in) ||
				!_cpri__EccIsPointOnCurve(curve_id, in)))
			return CRYPT_POINT;
		if (n2 != NULL && !check_p256_param(n2))
			return CRYPT_PARAMETER;

		if (n1 != NULL) {
			reverse_tpm2b(&n1->b);

			result = DCRYPTO_p256_base_point_mul(
				(p256_int *) out->x.b.buffer,
				(p256_int *) out->y.b.buffer,
				(p256_int *) n1->b.buffer);

			reverse_tpm2b(&n1->b);
		} else {
			reverse_tpm2b(&n2->b);
			reverse_tpm2b(&in->x.b);
			reverse_tpm2b(&in->y.b);

			result = DCRYPTO_p256_point_mul(
				(p256_int *) out->x.b.buffer,
				(p256_int *) out->y.b.buffer,
				(p256_int *) n2->b.buffer,
				(p256_int *) in->x.b.buffer,
				(p256_int *) in->y.b.buffer);

			reverse_tpm2b(&n2->b);
			reverse_tpm2b(&in->x.b);
			reverse_tpm2b(&in->y.b);
		}

		if (result) {
			out->x.b.size = sizeof(p256_int);
			out->y.b.size = sizeof(p256_int);
			reverse_tpm2b(&out->x.b);
			reverse_tpm2b(&out->y.b);

			return CRYPT_SUCCESS;
		} else {
			return CRYPT_NO_RESULT;
		}
	default:
		return CRYPT_PARAMETER;
	}
}

static const TPM2B_32_BYTE_VALUE ECC_TEMPLATE_EK_EXTRA = {
	.t = {32, {
			0xC2, 0xE0, 0x31, 0x93, 0x40, 0xFB, 0x48, 0xF1,
			0x02, 0x53, 0x9E, 0xA9, 0x83, 0x63, 0xF8, 0x1E,
			0x2D, 0x30, 0x6E, 0x91, 0x8D, 0xD7, 0x78, 0xAB,
			0xF0, 0x54, 0x73, 0xA2, 0xA6, 0x0D, 0xAE, 0x09,
		}
	}
};

/* Key generation based on FIPS-186.4 section B.1.2 (Key Generation by
 * Testing Candidates) */
CRYPT_RESULT _cpri__GenerateKeyEcc(
	TPMS_ECC_POINT *q, TPM2B_ECC_PARAMETER *d,
	TPM_ECC_CURVE curve_id,	TPM_ALG_ID hash_alg,
	TPM2B *seed, const char *label,	TPM2B *extra, UINT32 *counter)
{
	TPM2B_4_BYTE_VALUE marshaled_counter = { .t = {4} };
	TPM2B_32_BYTE_VALUE local_seed = { .t = {32} };
	TPM2B_4_BYTE_VALUE truncated_extra = { .t = {4} };
	TPM2B *local_extra;
	uint32_t count = 0;
	uint8_t key_bytes[P256_NBYTES];
	LITE_HMAC_CTX hmac;

	if (curve_id != TPM_ECC_NIST_P256)
		return CRYPT_PARAMETER;

	/* extra may be empty, but seed must be specified. */
	if (seed == NULL || seed->size < PRIMARY_SEED_SIZE)
		return CRYPT_PARAMETER;

	if (counter != NULL)
		count = *counter;
	if (count == 0)
		count++;

	/* Hash down the primary seed for ECC key generation, so that
	 * the derivation tree is distinct from RSA key derivation. */
	DCRYPTO_HMAC_SHA256_init(&hmac, seed->buffer, seed->size);
	HASH_update(&hmac.hash, "ECC", 4);
	memcpy(local_seed.t.buffer, DCRYPTO_HMAC_final(&hmac),
	       local_seed.t.size);
	always_memset(&hmac, 0, sizeof(hmac));
	/* TODO(ngm): CRBUG/P/55260: the personalize code uses only
	 * the first 4 bytes of extra.
	 */
	if (extra && extra->size == ECC_TEMPLATE_EK_EXTRA.b.size &&
		memcmp(extra->buffer,
		       ECC_TEMPLATE_EK_EXTRA.b.buffer,
		       ECC_TEMPLATE_EK_EXTRA.b.size) == 0) {
		memcpy(truncated_extra.b.buffer, extra->buffer, 4);
		local_extra = &truncated_extra.b;
	} else {
		local_extra = extra;
	}

	for (; count != 0; count++) {
		memcpy(marshaled_counter.t.buffer, &count, sizeof(count));
		_cpri__KDFa(hash_alg, &local_seed.b, label, local_extra,
			&marshaled_counter.b, sizeof(key_bytes) * 8, key_bytes,
			NULL, FALSE);
		if (DCRYPTO_p256_key_from_bytes(
				(p256_int *) q->x.b.buffer,
				(p256_int *) q->y.b.buffer,
				(p256_int *) d->b.buffer, key_bytes)) {
			q->x.b.size = sizeof(p256_int);
			q->y.b.size = sizeof(p256_int);
			reverse_tpm2b(&q->x.b);
			reverse_tpm2b(&q->y.b);

			d->b.size = sizeof(p256_int);
			reverse_tpm2b(&d->b);

			break;
		}
	}

	always_memset(local_seed.t.buffer, 0, local_seed.t.size);
	always_memset(key_bytes, 0, sizeof(key_bytes));

	if (count == 0)
		FAIL(FATAL_ERROR_INTERNAL);
	if (counter != NULL)
		*counter = count;

	return CRYPT_SUCCESS;
}

CRYPT_RESULT _cpri__SignEcc(
	TPM2B_ECC_PARAMETER *r, TPM2B_ECC_PARAMETER *s,
	TPM_ALG_ID scheme, TPM_ALG_ID hash_alg, TPM_ECC_CURVE curve_id,
	TPM2B_ECC_PARAMETER *d, TPM2B *digest, TPM2B_ECC_PARAMETER *k)
{
	uint8_t digest_local[sizeof(p256_int)];
	const size_t digest_len = MIN(digest->size, sizeof(digest_local));
	p256_int p256_digest;
	int result;
	struct drbg_ctx drbg;

	if (curve_id != TPM_ECC_NIST_P256)
		return CRYPT_PARAMETER;

	switch (scheme) {
	case TPM_ALG_ECDSA:
		if (!check_p256_param(d))
			return CRYPT_PARAMETER;
		/* Trucate / zero-pad the digest as appropriate. */
		memset(digest_local, 0, sizeof(digest_local));
		memcpy(digest_local + sizeof(digest_local) - digest_len,
			digest->buffer, digest_len);
		p256_from_bin(digest_local, &p256_digest);

		reverse_tpm2b(&d->b);

		drbg_rand_init(&drbg);
		result = dcrypto_p256_ecdsa_sign(&drbg,
				(p256_int *) d->b.buffer,
				&p256_digest,
				(p256_int *) r->b.buffer,
				(p256_int *) s->b.buffer);
		reverse_tpm2b(&d->b);

		r->b.size = sizeof(p256_int);
		s->b.size = sizeof(p256_int);
		reverse_tpm2b(&r->b);
		reverse_tpm2b(&s->b);

		if (result)
			return CRYPT_SUCCESS;
		else
			return CRYPT_FAIL;
	default:
		return CRYPT_PARAMETER;
	}
}

CRYPT_RESULT _cpri__ValidateSignatureEcc(
	TPM2B_ECC_PARAMETER *r,	TPM2B_ECC_PARAMETER *s,
	TPM_ALG_ID scheme, TPM_ALG_ID hash_alg,
	TPM_ECC_CURVE curve_id,	TPMS_ECC_POINT *q, TPM2B *digest)
{
	uint8_t digest_local[sizeof(p256_int)];
	const size_t digest_len = MIN(digest->size, sizeof(digest_local));
	p256_int p256_digest;
	int result;

	if (curve_id != TPM_ECC_NIST_P256)
		return CRYPT_PARAMETER;

	switch (scheme) {
	case TPM_ALG_ECDSA:
		/* Trucate / zero-pad the digest as appropriate. */
		memset(digest_local, 0, sizeof(digest_local));
		memcpy(digest_local + sizeof(digest_local) - digest_len,
			digest->buffer, digest_len);
		p256_from_bin(digest_local, &p256_digest);

		reverse_tpm2b(&q->x.b);
		reverse_tpm2b(&q->y.b);

		reverse_tpm2b(&r->b);
		reverse_tpm2b(&s->b);

		result = dcrypto_p256_ecdsa_verify(
			(p256_int *) q->x.b.buffer,
			(p256_int *) q->y.b.buffer,
			&p256_digest,
			(p256_int *) r->b.buffer,
			(p256_int *) s->b.buffer);

		reverse_tpm2b(&q->x.b);
		reverse_tpm2b(&q->y.b);

		reverse_tpm2b(&r->b);
		reverse_tpm2b(&s->b);

		if (result)
			return CRYPT_SUCCESS;
		else
			return CRYPT_FAIL;
	default:
		return CRYPT_PARAMETER;
	}
}

CRYPT_RESULT _cpri__GetEphemeralEcc(TPMS_ECC_POINT *q, TPM2B_ECC_PARAMETER *d,
				TPM_ECC_CURVE curve_id)
{
	int result;
	uint8_t key_bytes[P256_NBYTES] __aligned(4);

	if (curve_id != TPM_ECC_NIST_P256)
		return CRYPT_PARAMETER;

	rand_bytes(key_bytes, sizeof(key_bytes));

	result = DCRYPTO_p256_key_from_bytes((p256_int *) q->x.b.buffer,
					(p256_int *) q->y.b.buffer,
					(p256_int *) d->b.buffer,
					key_bytes);
	always_memset(key_bytes, 0, sizeof(key_bytes));

	if (result) {
		q->x.b.size = sizeof(p256_int);
		q->y.b.size = sizeof(p256_int);
		reverse_tpm2b(&q->x.b);
		reverse_tpm2b(&q->y.b);

		d->b.size = sizeof(p256_int);
		reverse_tpm2b(&d->b);

		return CRYPT_SUCCESS;
	} else {
		return CRYPT_FAIL;
	}
}

#ifdef CRYPTO_TEST_SETUP

#include "extension.h"

enum {
	TEST_SIGN = 0,
	TEST_VERIFY = 1,
	TEST_KEYGEN = 2,
	TEST_KEYDERIVE = 3
};

struct TPM2B_ECC_PARAMETER_aligned {
	uint16_t pad;
	TPM2B_ECC_PARAMETER d;
} __packed __aligned(4);

struct TPM2B_MAX_BUFFER_aligned {
	uint16_t pad;
	TPM2B_MAX_BUFFER d;
} __packed __aligned(4);

static const struct TPM2B_ECC_PARAMETER_aligned NIST_P256_d = {
	.d = {
		.t = {32, {
				0xfc, 0x44, 0x1e, 0x07, 0x74, 0x4e, 0x48, 0xf1,
				0x09, 0xb7, 0xe6, 0x6b, 0x29, 0x48, 0x2f, 0x7b,
				0x7e, 0x3e, 0xc9, 0x1f, 0xa2, 0x7f, 0xd4, 0x87,
				0x09, 0x91, 0xb2, 0x89, 0xfe, 0xa0, 0xd2, 0x0a
			}
		}
	}
};

static const struct TPM2B_ECC_PARAMETER_aligned NIST_P256_qx = {
	.d = {
		.t = {32, {
				0x12, 0xc3, 0xd6, 0xa2, 0x67, 0x9c, 0xa8, 0xee,
				0x3c, 0x4d, 0x92, 0x7f, 0x20, 0x4e, 0xd5, 0xbc,
				0xb4, 0x57, 0x7a, 0x04, 0xb0, 0xac, 0x02, 0xb2,
				0xa3, 0x6a, 0xb3, 0xe9, 0xe1, 0x07, 0x81, 0xde
			}
		}
	}
};

static const struct TPM2B_ECC_PARAMETER_aligned NIST_P256_qy = {
	.d = {
		.t = {32, {
				0x5c, 0x85, 0xad, 0x74, 0x13, 0x97, 0x11, 0x72,
				0xfc, 0xa5, 0x73, 0x8f, 0xee, 0x9d, 0x0e, 0x7b,
				0xc5, 0x9f, 0xfd, 0x8a, 0x62, 0x6d, 0x68, 0x9b,
				0xc6, 0xcc, 0xa4, 0xb5, 0x86, 0x65, 0x52, 0x1d
			}
		}
	}
};

#define MAX_MSG_BYTES MAX_DIGEST_BUFFER

static int point_equals(const TPMS_ECC_POINT *a, const TPMS_ECC_POINT *b)
{
	int diff = 0;

	diff = a->x.b.size != b->x.b.size;
	diff |= a->y.b.size != b->y.b.size;
	if (!diff) {
		diff |= !DCRYPTO_equals(
			a->x.b.buffer, b->x.b.buffer, a->x.b.size);
		diff |= !DCRYPTO_equals(
			a->y.b.buffer, b->y.b.buffer, a->y.b.size);
	}

	return !diff;
}

static void ecc_command_handler(void *cmd_body, size_t cmd_size,
			size_t *response_size_out)
{
	uint8_t *cmd;
	uint8_t op;
	uint8_t curve_id;
	uint8_t sign_mode;
	uint8_t hashing;
	uint16_t in_len;
	uint8_t in[MAX_MSG_BYTES];
	uint16_t digest_len;
	struct TPM2B_MAX_BUFFER_aligned digest;
	uint8_t *out = (uint8_t *) cmd_body;
	uint32_t *response_size = (uint32_t *) response_size_out;

	TPMS_ECC_POINT q;
	TPM2B_ECC_PARAMETER d;
	struct TPM2B_ECC_PARAMETER_aligned r;
	struct TPM2B_ECC_PARAMETER_aligned s;

	/* Command format.
	 *
	 *   OFFSET       FIELD
	 *   0            OP
	 *   1            CURVE_ID
	 *   2            SIGN_MODE
	 *   3            HASHING
	 *   4            MSB IN LEN
	 *   5            LSB IN LEN
	 *   6            IN
	 *   6 + IN_LEN   MSB DIGEST LEN
	 *   7 + IN_LEN   LSB DIGEST LEN
	 *   8 + IN_LEN   DIGEST
	 */

	cmd = (uint8_t *) cmd_body;
	op = *cmd++;
	curve_id = *cmd++;
	sign_mode = *cmd++;
	hashing = *cmd++;
	in_len = ((uint16_t) (cmd[0] << 8)) | cmd[1];
	cmd += 2;
	if (in_len > sizeof(in)) {
		*response_size = 0;
		return;
	}
	memcpy(in, cmd, in_len);
	cmd += in_len;

	digest_len = ((uint16_t) (cmd[0] << 8)) | cmd[1];
	cmd += 2;
	if (digest_len > sizeof(digest.d.t.buffer)) {
		*response_size = 0;
		return;
	}
	digest.d.t.size = digest_len;
	memcpy(digest.d.t.buffer, cmd, digest_len);
	cmd += digest_len;

	/* Make copies of d, and q, as const data is immutable. */
	switch (curve_id) {
	case TPM_ECC_NIST_P256:
		d = NIST_P256_d.d;
		q.x = NIST_P256_qx.d;
		q.y = NIST_P256_qy.d;
		break;
	default:
		*response_size = 0;
		return;
	}

	switch (op) {
	case TEST_SIGN:
		if (_cpri__SignEcc(&r.d, &s.d, sign_mode, hashing,
					curve_id, &d, &digest.d.b, NULL)
			!= CRYPT_SUCCESS) {
			*response_size = 0;
			return;
		}
		memcpy(out, r.d.b.buffer, r.d.b.size);
		out += r.d.b.size;
		memcpy(out, s.d.b.buffer, s.d.b.size);
		*response_size = r.d.b.size + s.d.b.size;
		break;
	case TEST_VERIFY:
		r.d.b.size = in_len / 2;
		memcpy(r.d.b.buffer, in, r.d.b.size);
		s.d.b.size = in_len / 2;
		memcpy(s.d.b.buffer, in + r.d.b.size, s.d.b.size);
		if (_cpri__ValidateSignatureEcc(
				&r.d, &s.d, sign_mode, hashing, curve_id,
				&q, &digest.d.b) != CRYPT_SUCCESS) {
			*response_size = 0;
		} else {
			*out = 1;
			*response_size = 1;
		}
		return;
	case TEST_KEYGEN:
	{
		struct TPM2B_ECC_PARAMETER_aligned d_local;
		TPMS_ECC_POINT q_local;

		if (_cpri__GetEphemeralEcc(&q, &d_local.d, curve_id)
			!= CRYPT_SUCCESS) {
			*response_size = 0;
			return;
		}

		if (_cpri__EccIsPointOnCurve(curve_id, &q) != TRUE) {
			*response_size = 0;
			return;
		}

		/* Verify correspondence of secret with the public point. */
		if (_cpri__EccPointMultiply(
				&q_local, curve_id, &d_local.d,
				NULL, NULL) != CRYPT_SUCCESS) {
			*response_size = 0;
			return;
		}
		if (!point_equals(&q, &q_local)) {
			*response_size = 0;
			return;
		}
		*out = 1;
		*response_size = 1;
		return;
	}
	case TEST_KEYDERIVE:
	{
		/* Random seed. */
		TPM2B_SEED seed;
		struct TPM2B_ECC_PARAMETER_aligned d_local;
		TPMS_ECC_POINT q_local;
		const char *label = "ecc_test";


		if (in_len > PRIMARY_SEED_SIZE) {
			*response_size = 0;
			return;
		}
		seed.t.size = in_len;
		memcpy(seed.t.buffer, in, in_len);

		if (_cpri__GenerateKeyEcc(
				&q, &d_local.d, curve_id, hashing,
				&seed.b, label, NULL, NULL) != CRYPT_SUCCESS) {
			*response_size = 0;
			return;
		}

		if (_cpri__EccIsPointOnCurve(curve_id, &q) != TRUE) {
			*response_size = 0;
			return;
		}

		/* Verify correspondence of secret with the public point. */
		if (_cpri__EccPointMultiply(
				&q_local, curve_id, &d_local.d,
				NULL, NULL) != CRYPT_SUCCESS) {
			*response_size = 0;
			return;
		}
		if (!point_equals(&q, &q_local)) {
			*response_size = 0;
			return;
		}

		*out = 1;
		*response_size = 1;
		return;
	}
	default:
		*response_size = 0;
		return;
	}
}

DECLARE_EXTENSION_COMMAND(EXTENSION_ECC, ecc_command_handler);

#endif   /* CRYPTO_TEST_SETUP */
