text: /*
   * Copyright (C) 1998-2002  D. Hugh Redelmeier.
   * Copyright (C) 1999, 2000, 2001  Henry Spencer.
   * Copyright (C) 2010 Tobias Brunner
   * Copyright (C) 2005-2008 Martin Willi
   * Copyright (C) 2005 Jan Hutter
   * Hochschule fuer Technik Rapperswil
   *
   * 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 <gmp.h>
  
  #include "gmp_diffie_hellman.h"
  
  #include <utils/debug.h>
  
  #ifdef HAVE_MPZ_POWM_SEC
  # undef mpz_powm
  # define mpz_powm mpz_powm_sec
  #endif
  
  typedef struct private_gmp_diffie_hellman_t private_gmp_diffie_hellman_t;
  
  /**
   * Private data of an gmp_diffie_hellman_t object.
   */
  struct private_gmp_diffie_hellman_t {
  	/**
  	 * Public gmp_diffie_hellman_t interface.
  	 */
  	gmp_diffie_hellman_t public;
  
  	/**
  	 * Diffie Hellman group number.
  	 */
  	diffie_hellman_group_t group;
  
  	/*
  	 * Generator value.
  	 */
  	mpz_t g;
  
  	/**
  	 * My private value.
  	 */
  	mpz_t xa;
  
  	/**
  	 * My public value.
  	 */
  	mpz_t ya;
  
  	/**
  	 * Other public value.
  	 */
  	mpz_t yb;
  
  	/**
  	 * Shared secret.
  	 */
  	mpz_t zz;
  
  	/**
  	 * Modulus.
  	 */
  	mpz_t p;
  
  	/**
  	 * Modulus length.
  	 */
  	size_t p_len;
  
  	/**
  	 * True if shared secret is computed and stored in my_public_value.
  	 */
  	bool computed;
  };
  
  METHOD(diffie_hellman_t, set_other_public_value, bool,
  	private_gmp_diffie_hellman_t *this, chunk_t value)
  {
  	mpz_t p_min_1;
  
  	if (!diffie_hellman_verify_value(this->group, value))
  	{
  		return FALSE;
  	}
  
  	mpz_init(p_min_1);
  	mpz_sub_ui(p_min_1, this->p, 1);
  
  	mpz_import(this->yb, value.len, 1, 1, 1, 0, value.ptr);
  
  	/* check public value:
  	 * 1. 0 or 1 is invalid as 0^a = 0 and 1^a = 1
  	 * 2. a public value larger or equal the modulus is invalid */
  	if (mpz_cmp_ui(this->yb, 1) > 0 &&
  		mpz_cmp(this->yb, p_min_1) < 0)
  	{
  #ifdef EXTENDED_DH_TEST
  		/* 3. test if y ^ q mod p = 1, where q = (p - 1)/2. */
  		mpz_t q, one;
  		diffie_hellman_params_t *params;
  
  		mpz_init(q);
  		mpz_init(one);
  
  		params = diffie_hellman_get_params(this->group);
  		if (!params->subgroup.len)
  		{
  			mpz_fdiv_q_2exp(q, p_min_1, 1);
  		}
  		else
  		{
  			mpz_import(q, params->subgroup.len, 1, 1, 1, 0, params->subgroup.ptr);
  		}
  		mpz_powm(one, this->yb, q, this->p);
  		mpz_clear(q);
  		if (mpz_cmp_ui(one, 1) == 0)
  		{
  			mpz_powm(this->zz, this->yb, this->xa, this->p);
  			this->computed = TRUE;
  		}
  		else
  		{
  			DBG1(DBG_LIB, "public DH value verification failed:"
  				 " y ^ q mod p != 1");
  		}
  		mpz_clear(one);
  #else
  		mpz_powm(this->zz, this->yb, this->xa, this->p);
  		this->computed = TRUE;
  #endif
  	}
  	else
  	{
  		DBG1(DBG_LIB, "public DH value verification failed:"
  			 " y < 2 || y > p - 1 ");
  	}
  	mpz_clear(p_min_1);
  	return this->computed;
  }
  
  METHOD(diffie_hellman_t, get_my_public_value, bool,
  	private_gmp_diffie_hellman_t *this,chunk_t *value)
  {
  	value->len = this->p_len;
  	value->ptr = mpz_export(NULL, NULL, 1, value->len, 1, 0, this->ya);
  	if (value->ptr == NULL)
  	{
  		value->len = 0;
  	}
  	return TRUE;
  }
  
  METHOD(diffie_hellman_t, set_private_value, bool,
  	private_gmp_diffie_hellman_t *this, chunk_t value)
  {
  	mpz_import(this->xa, value.len, 1, 1, 1, 0, value.ptr);
  	mpz_powm(this->ya, this->g, this->xa, this->p);
  	this->computed = FALSE;
  	return TRUE;
  }
  
  METHOD(diffie_hellman_t, get_shared_secret, bool,
  	private_gmp_diffie_hellman_t *this, chunk_t *secret)
  {
  	if (!this->computed)
  	{
  		return FALSE;
  	}
  	secret->len = this->p_len;
  	secret->ptr = mpz_export(NULL, NULL, 1, secret->len, 1, 0, this->zz);
  	if (secret->ptr == NULL)
  	{
  		return FALSE;
  	}
  	return TRUE;
  }
  
  METHOD(diffie_hellman_t, get_dh_group, diffie_hellman_group_t,
  	private_gmp_diffie_hellman_t *this)
  {
  	return this->group;
  }
  
  METHOD(diffie_hellman_t, destroy, void,
  	private_gmp_diffie_hellman_t *this)
  {
  	mpz_clear(this->p);
  	mpz_clear(this->xa);
  	mpz_clear(this->ya);
  	mpz_clear(this->yb);
  	mpz_clear(this->zz);
  	mpz_clear(this->g);
  	free(this);
  }
  
  /**
   * Generic internal constructor
   */
  static gmp_diffie_hellman_t *create_generic(diffie_hellman_group_t group,
  											size_t exp_len, chunk_t g, chunk_t p)
  {
  	private_gmp_diffie_hellman_t *this;
  	chunk_t random;
  	rng_t *rng;
  
  	INIT(this,
  		.public = {
  			.dh = {
  				.get_shared_secret = _get_shared_secret,
  				.set_other_public_value = _set_other_public_value,
  				.get_my_public_value = _get_my_public_value,
  				.set_private_value = _set_private_value,
  				.get_dh_group = _get_dh_group,
  				.destroy = _destroy,
  			},
  		},
  		.group = group,
  		.p_len = p.len,
  	);
  
  	mpz_init(this->p);
  	mpz_init(this->yb);
  	mpz_init(this->ya);
  	mpz_init(this->xa);
  	mpz_init(this->zz);
  	mpz_init(this->g);
  	mpz_import(this->g, g.len, 1, 1, 1, 0, g.ptr);
  	mpz_import(this->p, p.len, 1, 1, 1, 0, p.ptr);
  
  	rng = lib->crypto->create_rng(lib->crypto, RNG_STRONG);
  	if (!rng)
  	{
  		DBG1(DBG_LIB, "no RNG found for quality %N", rng_quality_names,
  			 RNG_STRONG);
  		destroy(this);
  		return NULL;
  	}
  	if (!rng->allocate_bytes(rng, exp_len, &random))
  	{
  		DBG1(DBG_LIB, "failed to allocate DH secret");
  		rng->destroy(rng);
  		destroy(this);
  		return NULL;
  	}
  	rng->destroy(rng);
  
  	if (exp_len == this->p_len)
  	{
  		/* achieve bitsof(p)-1 by setting MSB to 0 */
  		*random.ptr &= 0x7F;
  	}
  	mpz_import(this->xa, random.len, 1, 1, 1, 0, random.ptr);
  	chunk_clear(&random);
  	DBG2(DBG_LIB, "size of DH secret exponent: %u bits",
  		 mpz_sizeinbase(this->xa, 2));
  
  	mpz_powm(this->ya, this->g, this->xa, this->p);
  
  	return &this->public;
  }
  
  /*
   * Described in header.
   */
  gmp_diffie_hellman_t *gmp_diffie_hellman_create(diffie_hellman_group_t group)
  {
  	diffie_hellman_params_t *params;
  
  	params = diffie_hellman_get_params(group);
  	if (!params)
  	{
  		return NULL;
  	}
  	return create_generic(group, params->exp_len,
  						  params->generator, params->prime);
  }
  
  
  gmp_diffie_hellman_t *gmp_diffie_hellman_create_custom(
  							diffie_hellman_group_t group, chunk_t g, chunk_t p)
  {
  	if (group == MODP_CUSTOM)
  	{
  		return create_generic(MODP_CUSTOM, p.len, g, p);
  	}
  	return NULL;
  }
  