/*
 * Copyright (C) 2010 Martin Willi
 * Copyright (C) 2010 revosec AG
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 */

#include "tls_prf.h"

typedef struct private_tls_prf12_t private_tls_prf12_t;

#include <library.h>

/**
 * Private data of an tls_prf_t object.
 */
struct private_tls_prf12_t {

	/**
	 * Public tls_prf_t interface.
	 */
	tls_prf_t public;

	/**
	 * Underlying primitive PRF
	 */
	prf_t *prf;
};

METHOD(tls_prf_t, set_key12, bool,
	private_tls_prf12_t *this, chunk_t key)
{
	return this->prf->set_key(this->prf, key);
}

/**
 * The P_hash function as in TLS 1.0/1.2
 */
static bool p_hash(prf_t *prf, char *label, chunk_t seed, size_t block_size,
				   size_t bytes, char *out)
{
	char buf[block_size], abuf[block_size];
	chunk_t a;

	/* seed = label + seed */
	seed = chunk_cata("cc", chunk_create(label, strlen(label)), seed);
	/* A(0) = seed */
	a = seed;

	while (TRUE)
	{
		/* A(i) = HMAC_hash(secret, A(i-1)) */
		if (!prf->get_bytes(prf, a, abuf))
		{
			return FALSE;
		}
		a = chunk_from_thing(abuf);
		/* HMAC_hash(secret, A(i) + seed) */
		if (!prf->get_bytes(prf, a, NULL) ||
			!prf->get_bytes(prf, seed, buf))
		{
			return FALSE;
		}

		if (bytes <= block_size)
		{
			memcpy(out, buf, bytes);
			break;
		}
		memcpy(out, buf, block_size);
		out += block_size;
		bytes -= block_size;
	}
	return TRUE;
}

METHOD(tls_prf_t, get_bytes12, bool,
	private_tls_prf12_t *this, char *label, chunk_t seed,
	size_t bytes, char *out)
{
	return p_hash(this->prf, label, seed, this->prf->get_block_size(this->prf),
				  bytes, out);
}

METHOD(tls_prf_t, destroy12, void,
	private_tls_prf12_t *this)
{
	this->prf->destroy(this->prf);
	free(this);
}

/**
 * See header
 */
tls_prf_t *tls_prf_create_12(pseudo_random_function_t prf)
{
	private_tls_prf12_t *this;

	INIT(this,
		.public = {
			.set_key = _set_key12,
			.get_bytes = _get_bytes12,
			.destroy = _destroy12,
		},
		.prf = lib->crypto->create_prf(lib->crypto, prf),
	);
	if (!this->prf)
	{
		free(this);
		return NULL;
	}
	return &this->public;
}


typedef struct private_tls_prf10_t private_tls_prf10_t;

/**
 * Private data of an tls_prf_t object.
 */
struct private_tls_prf10_t {

	/**
	 * Public tls_prf_t interface.
	 */
	tls_prf_t public;

	/**
	 * Underlying MD5 PRF
	 */
	prf_t *md5;

	/**
	 * Underlying SHA1 PRF
	 */
	prf_t *sha1;
};

METHOD(tls_prf_t, set_key10, bool,
	private_tls_prf10_t *this, chunk_t key)
{
	size_t len = key.len / 2 + key.len % 2;

	return this->md5->set_key(this->md5, chunk_create(key.ptr, len)) &&
		   this->sha1->set_key(this->sha1, chunk_create(key.ptr + key.len - len,
														len));
}

METHOD(tls_prf_t, get_bytes10, bool,
	private_tls_prf10_t *this, char *label, chunk_t seed,
	size_t bytes, char *out)
{
	char buf[bytes];

	if (!p_hash(this->md5, label, seed, this->md5->get_block_size(this->md5),
				bytes, out) ||
		!p_hash(this->sha1, label, seed, this->sha1->get_block_size(this->sha1),
				bytes, buf))
	{
		return FALSE;
	}
	memxor(out, buf, bytes);
	return TRUE;
}

METHOD(tls_prf_t, destroy10, void,
	private_tls_prf10_t *this)
{
	DESTROY_IF(this->md5);
	DESTROY_IF(this->sha1);
	free(this);
}

/**
 * See header
 */
tls_prf_t *tls_prf_create_10()
{
	private_tls_prf10_t *this;

	INIT(this,
		.public = {
			.set_key = _set_key10,
			.get_bytes = _get_bytes10,
			.destroy = _destroy10,
		},
		.md5 = lib->crypto->create_prf(lib->crypto, PRF_HMAC_MD5),
		.sha1 = lib->crypto->create_prf(lib->crypto, PRF_HMAC_SHA1),
	);
	if (!this->md5 || !this->sha1)
	{
		destroy10(this);
		return NULL;
	}
	return &this->public;
}
