/**
 *  copyright holder grants permission for redistribution and use in source
 *  and binary forms, with or without modification, provided that the
 *  following conditions are met:
 *     1. Redistribution of source code must retain the above copyright
 *	notice, this list of conditions, and the following disclaimer
 *	in all source files.
 *     2. Redistribution in binary form must retain the above copyright
 *	notice, this list of conditions, and the following disclaimer
 *	in the documentation and/or other materials provided with the
 *	distribution.
 *
 *  "DISCLAIMER OF LIABILITY
 *
 *  THIS SOFTWARE IS PROVIDED BY DAN HARKINS ``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 INDUSTRIAL LOUNGE 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."
 *
 * This license and distribution terms cannot be changed. In other words,
 * this code cannot simply be copied and put under a different distribution
 * license (including the GNU public license).
 *
 * @copyright (c) Dan Harkins, 2012
 */

RCSID("$Id: 26260527a536af6cb4d337a0d3be65705cfc1cd9 $")
USES_APPLE_DEPRECATED_API	/* OpenSSL API has been deprecated by Apple */

#include "eap_pwd.h"
#include "const_time.h"
#include <freeradius-devel/openssl3.h>

static uint8_t allzero[SHA256_DIGEST_LENGTH] = { 0x00 };

/* The random function H(x) = HMAC-SHA256(0^32, x) */
static void pwd_hmac_final(HMAC_CTX *hmac_ctx, uint8_t *digest)
{
	unsigned int mdlen = SHA256_DIGEST_LENGTH;
	HMAC_Final(hmac_ctx, digest, &mdlen);
//	HMAC_CTX_reset(hmac_ctx);
}

/* a counter-based KDF based on NIST SP800-108 */
static void eap_pwd_kdf(uint8_t *key, int keylen, char const *label,
			int label_len, uint8_t *result, int result_bit_len)
{
	HMAC_CTX	*hmac_ctx;
	uint8_t		digest[SHA256_DIGEST_LENGTH];
	uint16_t	i, ctr, L;
	int		result_byte_len, len = 0;
	unsigned int	mdlen = SHA256_DIGEST_LENGTH;
	uint8_t		mask = 0xff;

	MEM(hmac_ctx = HMAC_CTX_new());
	result_byte_len = (result_bit_len + 7) / 8;

	ctr = 0;
	L = htons(result_bit_len);
	while (len < result_byte_len) {
		ctr++; i = htons(ctr);

		HMAC_Init_ex(hmac_ctx, key, keylen, EVP_sha256(), NULL);
		if (ctr > 1) HMAC_Update(hmac_ctx, digest, mdlen);
		HMAC_Update(hmac_ctx, (uint8_t *) &i, sizeof(uint16_t));
		HMAC_Update(hmac_ctx, (uint8_t const *)label, label_len);
		HMAC_Update(hmac_ctx, (uint8_t *) &L, sizeof(uint16_t));
		HMAC_Final(hmac_ctx, digest, &mdlen);
		if ((len + (int) mdlen) > result_byte_len) {
			memcpy(result + len, digest, result_byte_len - len);
		} else {
			memcpy(result + len, digest, mdlen);
		}
		len += mdlen;
//		HMAC_CTX_reset(hmac_ctx);
	}

	/* since we're expanding to a bit length, mask off the excess */
	if (result_bit_len % 8) {
		mask <<= (8 - (result_bit_len % 8));
		result[result_byte_len - 1] &= mask;
	}

	HMAC_CTX_free(hmac_ctx);
}

static BIGNUM *consttime_BN (void)
{
	BIGNUM *bn;

	bn = BN_new();
	if (bn) BN_set_flags(bn, BN_FLG_CONSTTIME);
	return bn;
}

/*
 * compute the legendre symbol in constant time
 */
static int legendre(BIGNUM *a, BIGNUM *p, BN_CTX *bnctx)
{
	int		symbol;
	unsigned int	mask;
	BIGNUM		*res, *pm1over2;

	pm1over2 = consttime_BN();
	res = consttime_BN();

	if (!BN_sub(pm1over2, p, BN_value_one()) ||
	    !BN_rshift1(pm1over2, pm1over2) ||
	    !BN_mod_exp_mont_consttime(res, a, pm1over2, p, bnctx, NULL)) {
		BN_free(pm1over2);
		BN_free(res);
		return -2;
	}

	symbol = -1;
	mask = const_time_eq(BN_is_word(res, 1), 1);
	symbol = const_time_select_int(mask, 1, symbol);
	mask = const_time_eq(BN_is_zero(res), 1);
	symbol = const_time_select_int(mask, -1, symbol);

	BN_free(pm1over2);
	BN_free(res);

	return symbol;
}

static void do_equation(EC_GROUP *group, BIGNUM *y2, BIGNUM *x, BN_CTX *bnctx)
{
	BIGNUM *p, *a, *b, *tmp1, *pm1;

	tmp1 = BN_new();
	pm1 = BN_new();
	p = BN_new();
	a = BN_new();
	b = BN_new();
	EC_GROUP_get_curve(group, p, a, b, bnctx);

	BN_sub(pm1, p, BN_value_one());

	/*
	 * y2 = x^3 + ax + b
	 */
	BN_mod_sqr(tmp1, x, p, bnctx);
	BN_mod_mul(y2, tmp1, x, p, bnctx);
	BN_mod_mul(tmp1, a, x, p, bnctx);
	BN_mod_add_quick(y2, y2, tmp1, p);
	BN_mod_add_quick(y2, y2, b, p);

	BN_free(tmp1);
	BN_free(pm1);
	BN_free(p);
	BN_free(a);
	BN_free(b);

	return;
}

static int is_quadratic_residue(BIGNUM *val, BIGNUM *p, BIGNUM *qr, BIGNUM *qnr, BN_CTX *bnctx)
{
	int offset, check, ret = 0;
	BIGNUM *r = NULL, *pm1 = NULL, *res = NULL, *qr_or_qnr = NULL;
	unsigned int mask;
	unsigned char *qr_bin = NULL, *qnr_bin = NULL, *qr_or_qnr_bin = NULL;

	if (((r = consttime_BN()) == NULL) ||
	    ((res = consttime_BN()) == NULL) ||
	    ((qr_or_qnr = consttime_BN()) == NULL) ||
	    ((pm1 = consttime_BN()) == NULL)) {
		ret = -2;
		goto fail;
	}

	if (((qr_bin = (unsigned char *)malloc(BN_num_bytes(p))) == NULL) ||
	    ((qnr_bin = (unsigned char *)malloc(BN_num_bytes(p))) == NULL) ||
	    ((qr_or_qnr_bin = (unsigned char *)malloc(BN_num_bytes(p))) == NULL)) {
		ret = -2;
		goto fail;
	}

	/*
	 * we select binary in constant time so make them binary
	 */
	memset(qr_bin, 0, BN_num_bytes(p));
	memset(qnr_bin, 0, BN_num_bytes(p));
	memset(qr_or_qnr_bin, 0, BN_num_bytes(p));

	offset = BN_num_bytes(p) - BN_num_bytes(qr);
	BN_bn2bin(qr, qr_bin + offset);

	offset = BN_num_bytes(p) - BN_num_bytes(qnr);
	BN_bn2bin(qnr, qnr_bin + offset);

	/*
	 * r = (random() mod p-1) + 1
	 */
	BN_sub(pm1, p, BN_value_one());
	BN_rand_range(r, pm1);
	BN_add(r, r, BN_value_one());

	BN_copy(res, val);

	/*
	 * res = val * r * r which ensures res != val but has same quadratic residocity
	 */
	BN_mod_mul(res, res, r, p, bnctx);
	BN_mod_mul(res, res, r, p, bnctx);

	/*
	 * if r is even (mask is -1) then multiply by qnr and our check is qnr
	 * otherwise multiply by qr and our check is qr
	 */
	mask = const_time_is_zero(BN_is_odd(r));
	const_time_select_bin(mask, qnr_bin, qr_bin, BN_num_bytes(p), qr_or_qnr_bin);
	BN_bin2bn(qr_or_qnr_bin, BN_num_bytes(p), qr_or_qnr);
	BN_mod_mul(res, res, qr_or_qnr, p, bnctx);
	check = const_time_select_int(mask, -1, 1);

	if ((ret = legendre(res, p, bnctx)) == -2) {
		ret = -1;	/* just say no it's not */
		goto fail;
	}
	mask = const_time_eq(ret, check);
	ret = const_time_select_int(mask, 1, 0);

fail:
	if (qr_bin != NULL) free(qr_bin);
	if (qnr_bin != NULL) free(qnr_bin);
	if (qr_or_qnr_bin != NULL) free(qr_or_qnr_bin);
	BN_free(r);
	BN_free(res);
	BN_free(qr_or_qnr);
	BN_free(pm1);

	return ret;
}

int compute_password_element (REQUEST *request, pwd_session_t *session, uint16_t grp_num,
			      char const *password, int password_len,
			      char const *id_server, int id_server_len,
			      char const *id_peer, int id_peer_len,
			      uint32_t *token)
{
	BIGNUM		*x_candidate = NULL, *rnd = NULL, *y_sqrd = NULL, *qr = NULL, *qnr = NULL, *y1 = NULL, *y2 = NULL, *y = NULL, *exp = NULL;
	EVP_MD_CTX	*hmac_ctx;
	EVP_PKEY	*hmac_pkey;
	uint8_t		pwe_digest[SHA256_DIGEST_LENGTH], *prfbuf = NULL, *xbuf = NULL, *pm1buf = NULL, *y1buf = NULL, *y2buf = NULL, *ybuf = NULL, ctr;
	int		nid, is_odd, primebitlen, primebytelen, ret = 0, found = 0, mask;
	int		save, i, rbits, qr_or_qnr, save_is_odd = 0, cmp;
	unsigned int	skip;

	MEM(hmac_ctx = EVP_MD_CTX_new());
	MEM(hmac_pkey = EVP_PKEY_new_mac_key(EVP_PKEY_HMAC, NULL, allzero, sizeof(allzero)));

	switch (grp_num) { /* from IANA registry for IKE D-H groups */
	case 19:
		nid = NID_X9_62_prime256v1;
		break;

	case 20:
		nid = NID_secp384r1;
		break;

	case 21:
		nid = NID_secp521r1;
		break;

	case 25:
		nid = NID_X9_62_prime192v1;
		break;

	case 26:
		nid = NID_secp224r1;
		break;

	default:
		DEBUG("unknown group %d", grp_num);
		goto fail;
	}

	session->pwe = NULL;
	session->order = NULL;
	session->prime = NULL;

	if ((session->group = EC_GROUP_new_by_curve_name(nid)) == NULL) {
		DEBUG("unable to create EC_GROUP");
		goto fail;
	}

	if (((rnd = consttime_BN()) == NULL) ||
	    ((session->pwe = EC_POINT_new(session->group)) == NULL) ||
	    ((session->order = consttime_BN()) == NULL) ||
	    ((session->prime = consttime_BN()) == NULL) ||
	    ((qr = consttime_BN()) == NULL) ||
	    ((qnr = consttime_BN()) == NULL) ||
	    ((x_candidate = consttime_BN()) == NULL) ||
	    ((y_sqrd = consttime_BN()) == NULL) ||
	    ((y1 = consttime_BN()) == NULL) ||
	    ((y2 = consttime_BN()) == NULL) ||
	    ((y = consttime_BN()) == NULL) ||
        ((exp = consttime_BN()) == NULL)) {
		DEBUG("unable to create bignums");
		goto fail;
	}

	if (!EC_GROUP_get_curve(session->group, session->prime, NULL, NULL, NULL)) {
		DEBUG("unable to get prime for GFp curve");
		goto fail;
	}

	if (!EC_GROUP_get_order(session->group, session->order, NULL)) {
		DEBUG("unable to get order for curve");
		goto fail;
	}

	primebitlen = BN_num_bits(session->prime);
	primebytelen = BN_num_bytes(session->prime);
	if ((prfbuf = talloc_zero_array(session, uint8_t, primebytelen)) == NULL) {
		DEBUG("unable to alloc space for prf buffer");
		goto fail;
	}
	if ((xbuf = talloc_zero_array(request, uint8_t, primebytelen)) == NULL) {
		DEBUG("unable to alloc space for x buffer");
		goto fail;
	}
	if ((pm1buf = talloc_zero_array(request, uint8_t, primebytelen)) == NULL) {
		DEBUG("unable to alloc space for pm1 buffer");
		goto fail;
	}
	if ((y1buf = talloc_zero_array(request, uint8_t, primebytelen)) == NULL) {
		DEBUG("unable to alloc space for y1 buffer");
		goto fail;
	}
	if ((y2buf = talloc_zero_array(request, uint8_t, primebytelen)) == NULL) {
		DEBUG("unable to alloc space for y2 buffer");
		goto fail;
	}
	if ((ybuf = talloc_zero_array(request, uint8_t, primebytelen)) == NULL) {
		DEBUG("unable to alloc space for y buffer");
		goto fail;
	}


	/*
	* derive random quadradic residue and quadratic non-residue
	*/
	do {
		BN_rand_range(qr, session->prime);
	} while (legendre(qr, session->prime, session->bnctx) != 1);

	do {
		BN_rand_range(qnr, session->prime);
	} while (legendre(qnr, session->prime, session->bnctx) != -1);

	if (!BN_sub(rnd, session->prime, BN_value_one())) {
		goto fail;
	}
	BN_bn2bin(rnd, pm1buf);

	save_is_odd = 0;
	found = 0;
	memset(xbuf, 0, primebytelen);
	ctr = 0;
	while (ctr < 40) {
		ctr++;

		/*
		 * compute counter-mode password value and stretch to prime
		 *	pwd-seed = H(token | peer-id | server-id | password |
		 *		     counter)
		 */
		EVP_DigestSignInit(hmac_ctx, NULL, EVP_sha256(), NULL, hmac_pkey);
		EVP_DigestSignUpdate(hmac_ctx, (uint8_t *)token, sizeof(*token));
		EVP_DigestSignUpdate(hmac_ctx, (uint8_t const *)id_peer, id_peer_len);
		EVP_DigestSignUpdate(hmac_ctx, (uint8_t const *)id_server, id_server_len);
		EVP_DigestSignUpdate(hmac_ctx, (uint8_t const *)password, password_len);
		EVP_DigestSignUpdate(hmac_ctx, (uint8_t *)&ctr, sizeof(ctr));

		{
			size_t mdlen = SHA256_DIGEST_LENGTH;

			EVP_DigestSignFinal(hmac_ctx, pwe_digest, &mdlen);
			EVP_MD_CTX_reset(hmac_ctx);
		}

		BN_bin2bn(pwe_digest, SHA256_DIGEST_LENGTH, rnd);
		eap_pwd_kdf(pwe_digest, SHA256_DIGEST_LENGTH, "EAP-pwd Hunting And Pecking",
			    strlen("EAP-pwd Hunting And Pecking"), prfbuf, primebitlen);

		/*
		 * eap_pwd_kdf() returns a string of bits 0..primebitlen but
		 * BN_bin2bn will treat that string of bits as a big endian
		 * number. If the primebitlen is not an even multiple of 8
		 * then excessive bits-- those _after_ primebitlen-- so now
		 * we have to shift right the amount we masked off.
		 */
		if (primebitlen % 8) {
			rbits = 8 - (primebitlen % 8);
			for (i = primebytelen - 1; i > 0; i--) {
				prfbuf[i] = (prfbuf[i - 1] << (8 - rbits)) | (prfbuf[i] >> rbits);
			}
			prfbuf[0] >>= rbits;
		}
		BN_bin2bn(prfbuf, primebytelen, x_candidate);

		/*
		* it would've been better if the spec reduced the candidate
		* modulo the prime but it didn't. So if the candidate >= prime
		* we need to skip it but still run through the operations below
		*/
		cmp = const_time_memcmp(pm1buf, prfbuf, primebytelen);
		skip = const_time_fill_msb((unsigned int)cmp);

		/*
		* need to unambiguously identify the solution, if there is
		* one..
		*/
		is_odd = BN_is_odd(rnd);

		/*
		* check whether x^3 + a*x + b is a quadratic residue
		*
		* save the first quadratic residue we find in the loop but do
		* it in constant time.
		*/
		do_equation(session->group, y_sqrd, x_candidate, session->bnctx);
		qr_or_qnr = is_quadratic_residue(y_sqrd, session->prime, qr, qnr, session->bnctx);

		/*
		* if the candidate >= prime then we want to skip it
		*/
		qr_or_qnr = const_time_select(skip, 0, qr_or_qnr);

		/*
		* if we haven't found PWE yet (found = 0) then mask will be true,
		* if we have found PWE then mask will be false
		*/
		mask = const_time_select(found, 0, -1);

		/*
		* save will be 1 if we want to save this value-- i.e. we haven't
		* found PWE yet and this is a quadratic residue-- and 0 otherwise
		*/
		save = const_time_select(mask, qr_or_qnr, 0);

		/*
		* mask will be true (-1) if we want to save this and false (0)
		* otherwise
		*/
		mask = const_time_eq(save, 1);

		const_time_select_bin(mask, prfbuf, xbuf, primebytelen, xbuf);
		save_is_odd = const_time_select(mask, is_odd, save_is_odd);
		found = const_time_select(mask, -1, found);
	}

	/*
	* now we can savely construct PWE
	*/
	BN_bin2bn(xbuf, primebytelen, x_candidate);
	do_equation(session->group, y_sqrd, x_candidate, session->bnctx);
	if ( !BN_add(exp, session->prime, BN_value_one()) ||
		 !BN_rshift(exp, exp, 2) ||
		 !BN_mod_exp_mont_consttime(y1, y_sqrd, exp, session->prime, session->bnctx, NULL) ||
		 !BN_sub(y2, session->prime, y1) ||
		 !BN_bn2bin(y1, y1buf) ||
		 !BN_bn2bin(y2, y2buf)) {
		DEBUG("unable to compute y");
		goto fail;
	}
	mask = const_time_eq(save_is_odd, BN_is_odd(y1));
	const_time_select_bin(mask, y1buf, y2buf, primebytelen, ybuf);
	if (BN_bin2bn(ybuf, primebytelen, y) == NULL ||
		!EC_POINT_set_affine_coordinates(session->group, session->pwe, x_candidate, y, session->bnctx)) {
		DEBUG("unable to set point coordinate");
		goto fail;
	}

	session->group_num = grp_num;
	if (0) {
		fail:		/* DON'T free session, it's in handler->opaque */
		ret = -1;
	}

	/* cleanliness and order.... */
	BN_clear_free(x_candidate);
	BN_clear_free(y_sqrd);
	BN_clear_free(qr);
	BN_clear_free(qnr);
	BN_clear_free(rnd);
	BN_clear_free(y1);
	BN_clear_free(y2);
	BN_clear_free(y);
	BN_clear_free(exp);

	if (prfbuf) talloc_free(prfbuf);
	if (xbuf) talloc_free(xbuf);
	if (pm1buf) talloc_free(pm1buf);
	if (y1buf) talloc_free(y1buf);
	if (y2buf) talloc_free(y2buf);
	if (ybuf) talloc_free(ybuf);

	EVP_MD_CTX_free(hmac_ctx);
	EVP_PKEY_free(hmac_pkey);

	return ret;
}

int compute_scalar_element(REQUEST *request, pwd_session_t *session, BN_CTX *bn_ctx)
{
	BIGNUM *mask = NULL;
	int ret = -1;

	MEM(session->private_value = BN_new());
	MEM(session->my_element = EC_POINT_new(session->group));
	MEM(session->my_scalar = BN_new());

	MEM(mask = BN_new());

	if (BN_rand_range(session->private_value, session->order) != 1) {
		REDEBUG("Unable to get randomness for private_value");
		goto error;
	}
	if (BN_rand_range(mask, session->order) != 1) {
		REDEBUG("Unable to get randomness for mask");
		goto error;
	}
	BN_add(session->my_scalar, session->private_value, mask);
	BN_mod(session->my_scalar, session->my_scalar, session->order, bn_ctx);

	if (!EC_POINT_mul(session->group, session->my_element, NULL, session->pwe, mask, bn_ctx)) {
		REDEBUG("Server element allocation failed");
		goto error;
	}

	if (!EC_POINT_invert(session->group, session->my_element, bn_ctx)) {
		REDEBUG("Server element inversion failed");
		goto error;
	}

	ret = 0;

error:
	BN_clear_free(mask);

	return ret;
}

int process_peer_commit(REQUEST *request, pwd_session_t *session, uint8_t *in, size_t in_len, BN_CTX *bn_ctx)
{
	uint8_t		*ptr;
	size_t		data_len;
	BIGNUM		*x = NULL, *y = NULL, *cofactor = NULL;
	EC_POINT	*K = NULL, *point = NULL;
	int		ret = 1;

	MEM(session->peer_scalar = BN_new());
	MEM(session->k = BN_new());
	MEM(session->peer_element = EC_POINT_new(session->group));
	MEM(point = EC_POINT_new(session->group));
	MEM(K = EC_POINT_new(session->group));

	MEM(cofactor = BN_new());
	MEM(x = BN_new());
	MEM(y = BN_new());

	if (!EC_GROUP_get_cofactor(session->group, cofactor, NULL)) {
		REDEBUG("Unable to get group co-factor");
		goto finish;
	}

	/* element, x then y, followed by scalar */
	ptr = (uint8_t *)in;
	data_len = BN_num_bytes(session->prime);

	/*
	 *	Did the peer send enough data?
	 */
	if (in_len < (2 * data_len + BN_num_bytes(session->order))) {
		REDEBUG("Invalid commit packet");
		goto finish;
	}

	BN_bin2bn(ptr, data_len, x);
	ptr += data_len;
	BN_bin2bn(ptr, data_len, y);
	ptr += data_len;

	data_len = BN_num_bytes(session->order);
	BN_bin2bn(ptr, data_len, session->peer_scalar);

	/* validate received scalar */
	if (BN_is_zero(session->peer_scalar) ||
	    BN_is_one(session->peer_scalar) ||
	    BN_cmp(session->peer_scalar, session->order) >= 0) {
		REDEBUG("Peer's scalar is not within the allowed range");
		goto finish;
	}

	if (!EC_POINT_set_affine_coordinates(session->group, session->peer_element, x, y, bn_ctx)) {
		REDEBUG("Unable to get coordinates of peer's element");
		goto finish;
	}

	/* validate received element */
	if (!EC_POINT_is_on_curve(session->group, session->peer_element, bn_ctx) ||
	    EC_POINT_is_at_infinity(session->group, session->peer_element)) {
		REDEBUG("Peer's element is not a point on the elliptic curve");
		goto finish;
	}

	/* check to ensure peer's element is not in a small sub-group */
	if (BN_cmp(cofactor, BN_value_one())) {
		if (!EC_POINT_mul(session->group, point, NULL, session->peer_element, cofactor, NULL)) {
			REDEBUG("Unable to multiply element by co-factor");
			goto finish;
		}

		if (EC_POINT_is_at_infinity(session->group, point)) {
			REDEBUG("Peer's element is in small sub-group");
			goto finish;
		}
	}

	/* detect reflection attacks */
	if (BN_cmp(session->peer_scalar, session->my_scalar) == 0 ||
	    EC_POINT_cmp(session->group, session->peer_element, session->my_element, bn_ctx) == 0) {
		REDEBUG("Reflection attack detected");
		goto finish;
	}

	/* compute the shared key, k */
	if ((!EC_POINT_mul(session->group, K, NULL, session->pwe, session->peer_scalar, bn_ctx)) ||
	    (!EC_POINT_add(session->group, K, K, session->peer_element, bn_ctx)) ||
	    (!EC_POINT_mul(session->group, K, NULL, K, session->private_value, bn_ctx))) {
		REDEBUG("Unable to compute shared key, k");
		goto finish;
	}

	/* ensure that the shared key isn't in a small sub-group */
	if (BN_cmp(cofactor, BN_value_one())) {
		if (!EC_POINT_mul(session->group, K, NULL, K, cofactor, NULL)) {
			REDEBUG("Unable to multiply k by co-factor");
			goto finish;
		}
	}

	/*
	 * This check is strictly speaking just for the case above where
	 * co-factor > 1 but it was suggested that even though this is probably
	 * never going to happen it is a simple and safe check "just to be
	 * sure" so let's be safe.
	 */
	if (EC_POINT_is_at_infinity(session->group, K)) {
		REDEBUG("K is point-at-infinity");
		goto finish;
	}

	if (!EC_POINT_get_affine_coordinates(session->group, K, session->k, NULL, bn_ctx)) {
		REDEBUG("Unable to get shared secret from K");
		goto finish;
	}
	ret = 0;

finish:
	EC_POINT_clear_free(K);
	EC_POINT_clear_free(point);
	BN_clear_free(cofactor);
	BN_clear_free(x);
	BN_clear_free(y);

	return ret;
}

int compute_server_confirm(REQUEST *request, pwd_session_t *session, uint8_t *out, BN_CTX *bn_ctx)
{
	BIGNUM		*x = NULL, *y = NULL;
	HMAC_CTX	*hmac_ctx = NULL;
	uint8_t		*cruft = NULL;
	int		offset, req = -1;

	/*
	 * Each component of the cruft will be at most as big as the prime
	 */
	MEM(cruft = talloc_zero_array(session, uint8_t, BN_num_bytes(session->prime)));
	MEM(x = BN_new());
	MEM(y = BN_new());

	/*
	 * commit is H(k | server_element | server_scalar | peer_element |
	 *	       peer_scalar | ciphersuite)
	 */
	MEM(hmac_ctx = HMAC_CTX_new());
	HMAC_Init_ex(hmac_ctx, allzero, SHA256_DIGEST_LENGTH, EVP_sha256(), NULL);

	/*
	 * Zero the memory each time because this is mod prime math and some
	 * value may start with a few zeros and the previous one did not.
	 *
	 * First is k
	 */
	offset = BN_num_bytes(session->prime) - BN_num_bytes(session->k);
	BN_bn2bin(session->k, cruft + offset);
	HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime));

	/*
	 * next is server element: x, y
	 */
	if (!EC_POINT_get_affine_coordinates(session->group, session->my_element, x, y, bn_ctx)) {
		REDEBUG("Unable to get coordinates of server element");
		goto finish;
	}
	memset(cruft, 0, BN_num_bytes(session->prime));
	offset = BN_num_bytes(session->prime) - BN_num_bytes(x);
	BN_bn2bin(x, cruft + offset);
	HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime));

	memset(cruft, 0, BN_num_bytes(session->prime));
	offset = BN_num_bytes(session->prime) - BN_num_bytes(y);
	BN_bn2bin(y, cruft + offset);
	HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime));

	/*
	 * and server scalar
	 */
	memset(cruft, 0, BN_num_bytes(session->prime));
	offset = BN_num_bytes(session->order) - BN_num_bytes(session->my_scalar);
	BN_bn2bin(session->my_scalar, cruft + offset);
	HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->order));

	/*
	 * next is peer element: x, y
	 */
	if (!EC_POINT_get_affine_coordinates(session->group, session->peer_element, x, y, bn_ctx)) {
		REDEBUG("Unable to get coordinates of peer's element");
		goto finish;
	}

	memset(cruft, 0, BN_num_bytes(session->prime));
	offset = BN_num_bytes(session->prime) - BN_num_bytes(x);
	BN_bn2bin(x, cruft + offset);
	HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime));

	memset(cruft, 0, BN_num_bytes(session->prime));
	offset = BN_num_bytes(session->prime) - BN_num_bytes(y);
	BN_bn2bin(y, cruft + offset);
	HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime));

	/*
	 * and peer scalar
	 */
	memset(cruft, 0, BN_num_bytes(session->prime));
	offset = BN_num_bytes(session->order) - BN_num_bytes(session->peer_scalar);
	BN_bn2bin(session->peer_scalar, cruft + offset);
	HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->order));

	/*
	 * finally, ciphersuite
	 */
	HMAC_Update(hmac_ctx, (uint8_t *)&session->ciphersuite, sizeof(session->ciphersuite));

	pwd_hmac_final(hmac_ctx, out);

	req = 0;

finish:
	HMAC_CTX_free(hmac_ctx);
	talloc_free(cruft);
	BN_free(x);
	BN_free(y);

	return req;
}

int compute_peer_confirm(REQUEST *request, pwd_session_t *session, uint8_t *out, BN_CTX *bn_ctx)
{
	BIGNUM		*x = NULL, *y = NULL;
	HMAC_CTX	*hmac_ctx = NULL;
	uint8_t		*cruft = NULL;
	int		offset, req = -1;

	/*
	 * Each component of the cruft will be at most as big as the prime
	 */
	MEM(cruft = talloc_zero_array(session, uint8_t, BN_num_bytes(session->prime)));
	MEM(x = BN_new());
	MEM(y = BN_new());

	/*
	 * commit is H(k | server_element | server_scalar | peer_element |
	 *	       peer_scalar | ciphersuite)
	 */
	MEM(hmac_ctx = HMAC_CTX_new());
	HMAC_Init_ex(hmac_ctx, allzero, SHA256_DIGEST_LENGTH, EVP_sha256(), NULL);

	/*
	 * Zero the memory each time because this is mod prime math and some
	 * value may start with a few zeros and the previous one did not.
	 *
	 * First is k
	 */
	offset = BN_num_bytes(session->prime) - BN_num_bytes(session->k);
	BN_bn2bin(session->k, cruft + offset);
	HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime));

	/*
	* then peer element: x, y
	*/
	if (!EC_POINT_get_affine_coordinates(session->group, session->peer_element, x, y, bn_ctx)) {
		REDEBUG("Unable to get coordinates of peer's element");
		goto finish;
	}

	memset(cruft, 0, BN_num_bytes(session->prime));
	offset = BN_num_bytes(session->prime) - BN_num_bytes(x);
	BN_bn2bin(x, cruft + offset);
	HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime));

	memset(cruft, 0, BN_num_bytes(session->prime));
	offset = BN_num_bytes(session->prime) - BN_num_bytes(y);
	BN_bn2bin(y, cruft + offset);
	HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime));

	/*
	 * and peer scalar
	 */
	memset(cruft, 0, BN_num_bytes(session->prime));
	offset = BN_num_bytes(session->order) - BN_num_bytes(session->peer_scalar);
	BN_bn2bin(session->peer_scalar, cruft + offset);
	HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->order));

	/*
	 * then server element: x, y
	 */
	if (!EC_POINT_get_affine_coordinates(session->group, session->my_element, x, y, bn_ctx)) {
		REDEBUG("Unable to get coordinates of server element");
		goto finish;
	}
	memset(cruft, 0, BN_num_bytes(session->prime));
	offset = BN_num_bytes(session->prime) - BN_num_bytes(x);
	BN_bn2bin(x, cruft + offset);
	HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime));

	memset(cruft, 0, BN_num_bytes(session->prime));
	offset = BN_num_bytes(session->prime) - BN_num_bytes(y);
	BN_bn2bin(y, cruft + offset);
	HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime));

	/*
	 * and server scalar
	 */
	memset(cruft, 0, BN_num_bytes(session->prime));
	offset = BN_num_bytes(session->order) - BN_num_bytes(session->my_scalar);
	BN_bn2bin(session->my_scalar, cruft + offset);
	HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->order));

	/*
	 * finally, ciphersuite
	 */
	HMAC_Update(hmac_ctx, (uint8_t *)&session->ciphersuite, sizeof(session->ciphersuite));

	pwd_hmac_final(hmac_ctx, out);

	req = 0;
finish:
	HMAC_CTX_free(hmac_ctx);
	talloc_free(cruft);
	BN_free(x);
	BN_free(y);

	return req;
}

int compute_keys(UNUSED REQUEST *request, pwd_session_t *session, uint8_t *peer_confirm, uint8_t *msk, uint8_t *emsk)
{
	HMAC_CTX	*hmac_ctx;
	uint8_t		mk[SHA256_DIGEST_LENGTH], *cruft;
	uint8_t		session_id[SHA256_DIGEST_LENGTH + 1];
	uint8_t		msk_emsk[128];		/* 64 each */
	int	 	offset;

	MEM(cruft = talloc_array(session, uint8_t, BN_num_bytes(session->prime)));
	MEM(hmac_ctx = HMAC_CTX_new());

	/*
	 * first compute the session-id = TypeCode | H(ciphersuite | scal_p |
	 *	scal_s)
	 */
	session_id[0] = PW_EAP_PWD;
	HMAC_Init_ex(hmac_ctx, allzero, SHA256_DIGEST_LENGTH, EVP_sha256(), NULL);
	HMAC_Update(hmac_ctx, (uint8_t *)&session->ciphersuite, sizeof(session->ciphersuite));
	offset = BN_num_bytes(session->order) - BN_num_bytes(session->peer_scalar);
	memset(cruft, 0, BN_num_bytes(session->prime));
	BN_bn2bin(session->peer_scalar, cruft + offset);
	HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->order));
	offset = BN_num_bytes(session->order) - BN_num_bytes(session->my_scalar);
	memset(cruft, 0, BN_num_bytes(session->prime));
	BN_bn2bin(session->my_scalar, cruft + offset);
	HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->order));
	pwd_hmac_final(hmac_ctx, (uint8_t *)&session_id[1]);

	/* then compute MK = H(k | commit-peer | commit-server) */
	HMAC_Init_ex(hmac_ctx, allzero, SHA256_DIGEST_LENGTH, EVP_sha256(), NULL);

	memset(cruft, 0, BN_num_bytes(session->prime));
	offset = BN_num_bytes(session->prime) - BN_num_bytes(session->k);
	BN_bn2bin(session->k, cruft + offset);
	HMAC_Update(hmac_ctx, cruft, BN_num_bytes(session->prime));

	HMAC_Update(hmac_ctx, peer_confirm, SHA256_DIGEST_LENGTH);

	HMAC_Update(hmac_ctx, session->my_confirm, SHA256_DIGEST_LENGTH);

	pwd_hmac_final(hmac_ctx, mk);

	/* stretch the mk with the session-id to get MSK | EMSK */
	eap_pwd_kdf(mk, SHA256_DIGEST_LENGTH, (char const *)session_id,
		    SHA256_DIGEST_LENGTH + 1, msk_emsk, 1024);  /* it's bits, ((64 + 64) * 8) */

	memcpy(msk, msk_emsk, 64);
	memcpy(emsk, msk_emsk + 64, 64);

	HMAC_CTX_free(hmac_ctx);
	talloc_free(cruft);
	return 0;
}
