#define	__MODULE__	"DHEXMPL"
#define	__IDENT__	"X.00-01"

#ifdef	__GNUC__
	#ident			__IDENT__
#endif

#pragma GCC diagnostic ignored  "-Wparentheses"
#pragma GCC diagnostic ignored	"-Wdiscarded-qualifiers"
#pragma GCC diagnostic ignored	"-Wmissing-braces"
#pragma GCC diagnostic ignored	"-Wfdollars-in-identifiers"

/*
**++
**
**  FACILITY:  An example of Anonimous Diffie-Hellman
**
**  ABSTRACT:  An example of building encrypted channel by using keys' interchange base on Anonimpus Diffie-Hellman
**	alghorytm
**
**  DESCRIPTION:
**
**
**  DESIGN ISSUE:
**
** Сервер                                                 Клиент
------------------------------------------------------------------------------------------------
Генерация p, q (не используется), g


PrivKserver - случайное число заданной длины (160 битов, то есть достаточно длинное)
PubKserver = f(PrivKserver, p, g)
	PubKserver = (g^PrivKserver) mod p



				 ----->>>> p, g, PubKserver
------------------------------------------------------------------------------
						  PrivKclient - как-то генерируется
						  PubKclient = f (p, g, PrivKclient)
						  PubKclient = (g^PrivKclient) mod p

						  SesssionKey = f(PubKserver, PrivKclient)
						  SesssionKey = PubKserver ^ PrivKclient

				<<<<----- PubKclient
------------------------------------------------------------------------------------------------
SesssionKey = f(PrivKserver, PubKclient)
SesssionKey = PubKclient ^ PrivKserver
------------------------------------------------------------------------------------------------
				 обмен данными
				     GOST89
**
**  AUTHORS: Ruslan R. Laishev (RRL)
**
**  CREATION DATE: 05-NOV-2019
**
**  USAGE:
**
**  MODIFICATION HISTORY:
**
*/

#include	<stdio.h>
#include	<stdlib.h>
#include	<string.h>
#include	<unistd.h>
#include	<errno.h>


#include	<openssl/dh.h>
#include	<openssl/bn.h>

#ifdef	__x86_64__
	#define	__ARCH__NAME__	"x86_64"
#else
#ifdef	__i386
	#define	__ARCH__NAME__	"i386"
#endif
#endif

#ifdef	_WIN64
	#define	__ARCH__NAME__	"Win64"
#elifdef _WIN32
	#define	__ARCH__NAME__	"Win32"
#endif


/*
* Defines and includes for enable extend trace and logging
*/
#define		__FAC__	"DHEXMPL"
#define		__TFAC__ __FAC__ ": "
#include	"utility_routines.h"

#define $SHOW_PTR(var)			$SHOW_PARM(var, var, "%p")
#define $SHOW_STR(var)			$SHOW_PARM(var, (var ? var : "UNDEF(NULL)"), "'%s'")
#define $SHOW_INT(var)			$SHOW_PARM(var, ((int) var), "%d")
#define $SHOW_UINT(var)			$SHOW_PARM(var, ((unsigned) var), "%u")
#define $SHOW_ULL(var)			$SHOW_PARM(var, ((unsigned long long) var), "%llu")
#define $SHOW_UNSIGNED(var)		$SHOW_PARM(var, var, "0x%08x")
#define $SHOW_BOOL(var)			$SHOW_PARM(var, (var ? "ENABLED(TRUE)" : "DISABLED(FALSE)"), "%s");


typedef struct	__rnd_seed__
{
	struct timespec	ts;
	pid_t		pid;
	struct timespec	ts2;
} RND_SEED;






#define	DH$SZ_PRIME	(20*8)		/* 640 bits	*/

static	int	__dh_server_init	(
			BIGNUM	*a,
			BIGNUM	*p,
			BIGNUM	*g,
			BIGNUM	*A
				)
{
int	status = STS$K_ERROR, rc, count;
BIGNUM	*q, *h, *bn_res, *bn_gexp, *bn_minusone, *bn_one;
BN_CTX	*bn_ctx; /* used internally by the bignum lib */
RND_SEED	rnd_seed;
char	errbuf[512];

	/* Initialize context for random seeding */
	if ( rc = clock_gettime(CLOCK_MONOTONIC, &rnd_seed.ts) )
		return	$LOG(STS$K_ERROR, "clock_gettime()->%d, errno=%d", rc, errno);

	if ( rc = clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &rnd_seed.ts2) )
		return	$LOG(STS$K_ERROR, "clock_gettime()->%d, errno=%d", status, errno);

	rnd_seed.pid = getpid();

	/* Performs random seeding or BN_generate_prime_ex may fail */
	rc = RAND_seed(&rnd_seed, sizeof (rnd_seed) );

	bn_ctx = BN_CTX_new();

	/* Allocate area for bignumbers */
	q = BN_new();
	h = BN_new();
	bn_res = BN_new();
	bn_gexp = BN_new();
	bn_minusone = BN_new();	BN_set_word (bn_minusone, -1);
	bn_one = BN_new();	BN_set_word (bn_one, 1);

	/* Generate p and g ... */
	for (count = 0; ++count; )
		{
		// Calculate "p" = 2q + 1
		if ( !(status = BN_generate_prime_ex (q, DH$SZ_PRIME, 1 /* safe */, NULL, NULL, NULL)) )
			{
			ERR_print_errors_fp(stdout);
			break;
			}


		//$TRACE("q : %s", BN_bn2hex (q));
		BN_lshift1(p, q);	// p = p * 2
		BN_add(p, p, bn_one);	// p = p - 1
		//$TRACE("p : %s", BN_bn2hex (p));

		// 1: gExp = (p - 1) / q
		// 2: h =>   1 < h < (p - 1)
		// 3: g = h^gExp mod p
		BN_sub (bn_res, p, bn_minusone);				// bn_res = p - 1
		BN_div(bn_gexp, NULL, bn_res /*bn_res = p - 1 */, q, bn_ctx);	// 1: gExp = (p - 1) / q
		BN_pseudo_rand_range(h, bn_res /*bn_res = p - 1 */);		// 2: h =>   1 < h < (p - 1)
		BN_mod_exp(g, h, bn_gexp, p, bn_ctx);				// 3: g = h^gExp mod p

		BN_mod_exp(bn_res, g, q, p, bn_ctx);				// g^q mod p = 1, or it should

		if ( (status = (!BN_is_one (bn_res))) )
			break;
		}

	/* Generate private key random prime ... */
	if ( !(status = BN_generate_prime_ex (a, DH$SZ_PRIME, 1 /* safe */, NULL, NULL, NULL)) )
		ERR_print_errors_fp(stdout);

	/* Compute publick key as : A("publick key") = g ^ a mod p */
	BN_mod_exp(A, g, a, p, bn_ctx);


	/* Release has been allocated resource unconditionaly */
	BN_CTX_free(bn_ctx);

	BN_free(h);
	BN_free(bn_res);
	BN_free(bn_gexp);
	BN_free(bn_minusone);
	BN_free(bn_one);

	return	status;
}






static	int	__dh_client_init	(
			BIGNUM	*b,
			BIGNUM	*p,
			BIGNUM	*g,
			BIGNUM	*B
				)
{
int	status = STS$K_ERROR, rc;
BN_CTX	*bn_ctx; /* used internally by the bignum lib */
RND_SEED	rnd_seed;
char	errbuf[512];

	/* Initialize context for random seeding */
	if ( rc = clock_gettime(CLOCK_MONOTONIC, &rnd_seed.ts) )
		return	$LOG(STS$K_ERROR, "clock_gettime()->%d, errno=%d", rc, errno);

	if ( rc = clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &rnd_seed.ts2) )
		return	$LOG(STS$K_ERROR, "clock_gettime()->%d, errno=%d", status, errno);

	rnd_seed.pid = getpid();

	/* Performs random seeding or BN_generate_prime_ex may fail */
	RAND_seed(&rnd_seed, sizeof (rnd_seed) );


	/* Generate private key random prime ... */
	if ( !(status = BN_generate_prime_ex (b, DH$SZ_PRIME, 1 /* safe */, NULL, NULL, NULL)) )
		ERR_print_errors_fp(stdout);


	/* Compute publick key as : A("publick key") = g ^ a mod p */
	bn_ctx = BN_CTX_new();
	BN_mod_exp(B, g, b, p, bn_ctx);
	BN_CTX_free(bn_ctx);

	return	status;
}


static	int	__dh_session_key(
			BIGNUM	*pubk,
			BIGNUM	*privk,
			BIGNUM	*p,
			BIGNUM	*skey
				)
{
int	status = STS$K_ERROR, rc;
BN_CTX	*bn_ctx; /* used internally by the bignum lib */

	/* Compute session key as: Session Key = PublickKey ^ PrivateKey mod p */
	bn_ctx = BN_CTX_new();
	BN_mod_exp(skey, pubk, privk, p, bn_ctx);
	BN_CTX_free(bn_ctx);

}






int	main	(int argc, char **argv)
{
int	status, rc;
char	errbuf[512];
BIGNUM	*cprivk, *cpubk, *sprivk, *spubk, *sskey, *cskey, *p, *g;
BN_CTX	*bn_ctx; /* used internally by the bignum lib */

	$LOG(STS$K_INFO, "Server context initialization ...");

	p = BN_new();
	g = BN_new();
	sprivk = BN_new();
	spubk = BN_new();

	__dh_server_init(sprivk, p, g, spubk);

	$LOG(STS$K_INFO, "Public p             : %s", BN_bn2hex (p));
	$LOG(STS$K_INFO, "Public g             : %s", BN_bn2hex (g));

	$LOG(STS$K_INFO, "Server DH private key: %s", BN_bn2hex (sprivk));
	$LOG(STS$K_INFO, "Server DH public key : %s", BN_bn2hex (spubk));


	/*
	 * Send to client: p, g, Server's publick key
	 *  .....
	 */


	/* Generate client's keys  with has been given from server: A  (erver's public key), p, g */
	$LOG(STS$K_INFO, "Client context initialization, input data (has been gotten from server) :");
	$LOG(STS$K_INFO, "\tPublic p             : %s", BN_bn2hex (p));
	$LOG(STS$K_INFO, "\tPublic g             : %s", BN_bn2hex (g));
	$LOG(STS$K_INFO, "\tServer DH public key : %s", BN_bn2hex (spubk));

	cprivk = BN_new();
	cpubk = BN_new();

	__dh_client_init(cprivk, p, g, cpubk);

	$LOG(STS$K_INFO, "Client DH private key: %s", BN_bn2hex (sprivk));
	$LOG(STS$K_INFO, "Client DH public key : %s", BN_bn2hex (spubk));


	/* Compute Session key at server and client sides */
	$LOG(STS$K_INFO, "Session keys generation ...");

	sskey = BN_new();
	cskey = BN_new();


	__dh_session_key (cpubk, sprivk, p, sskey);
	$LOG(STS$K_INFO, "Server DH session key: %s", BN_bn2hex (sskey));

	__dh_session_key (spubk, cprivk, p, cskey);
	$LOG(STS$K_INFO, "Client DH session key: %s", BN_bn2hex (cskey));
}