/*************************************************************
 **         Europäisches Institut für Systemsicherheit        *
 **   Proktikum "Kryptographie und Datensicherheitstechnik"   *
 **                                                           *
 ** Versuch 7: El-gamal-Signatur                              *
 **                                                           *
 **************************************************************
 **
 ** getreport.c: Rahmenprogramm für den Signatur-Versuch
 **/

#include "sign.h"
#include <unistd.h>
#include <getopt.h>
#include <time.h>
#include <gmp.h>
#include <string.h>
#include "pohlighellman.h"

#define DEBUG_STATUS true
#define DEBUG_CHECK true
// #define DEBUG_VERBOSE true

static mpz_t p; //modulus for all operations
static mpz_t w; //generator for all operations

const char *factorlist_hex[] = {
	"5", "7", "9", "B", "D", "11", "13", "17", "1D", "1F", "25", "29",
	"2B", "2F", "35", "3B", "3D", "40", "43", "47", "49", "4F", "53", "59",
	"61", "65", "67", "6B", "6D", "71", "7F", "83", "89", "8B", "95", "97",
	"9D", "A3", "A7", "AD", "B3", "B5", "BF", "C1", "C5", "C7", "D3", "DF",
	"E3", "E5", "E9", "EF", "F1", "FB", "101", "107", "10D", "10F", "115",
	"119", "11B", "125", "133", "137", "139", "13D", "14B",
	"10000000F", "12000050F", 0};

int nfactors;
mpz_t *factorlist; /* Zugriff hierauf wie auf Array. Index 0<=i<nfactors */

/*
 * init_factors() : Füllt die interne factorlist mit Faktoren.
 */
static void init_factors(void)
{
	int i;
	mpz_t tmp;

	for (nfactors = 0; factorlist_hex[nfactors]; nfactors++)
		;
	factorlist = calloc(nfactors, sizeof(mpz_t));
	mpz_init(tmp);
	mpz_set_ui(tmp, 1);
	for (i = 0; i < nfactors; i++)
	{
		mpz_init(factorlist[i]);
		mpz_set_str(factorlist[i], factorlist_hex[i], 16);
		mpz_mul(tmp, tmp, factorlist[i]);
	}
	mpz_add_ui(tmp, tmp, 1);
	if (mpz_cmp(tmp, p))
	{
		printf("FATAL: Faktoren stammen nicht von p-1!\n");
		exit(1);
	}
	mpz_clear(tmp);
}

/*
 * babyStepGiantStep(mpz_t x_i, mpz_t a_i, mpz_t w_i, mpz_t p_i):
 *
 * Berechnet x_i so dass a_i = w_i ^ x_i mod p.
 */
static void babyStepGiantStep(mpz_t x_i, mpz_t a_i, mpz_t w_i, mpz_t p_i)
{
	/*>>>>                                                <<<<*
	 *>>>> AUFGABE: Implementierung von BabyStepGiantStep <<<<*
	 *>>>>                                                <<<<*/
	mpz_t order;
	mpz_init(order);
	mpz_sub_ui(order, p_i, 1);

	baby_step_giant_step(x_i, w_i, a_i, p, order);

	mpz_clear(order);
}

/*
 * dlogP(x, y):
 *
 * Berechnet x, wobei y = w ^ x mod p mithilfe der Faktorisierung von p - 1.
 */
static void dlogP(mpz_t x, mpz_t y)
{
	init_factors();
	/*>>>>                                            <<<<*
	 *>>>> AUFGABE: Berechnen des geheimen Schlüssels <<<<*
	 *>>>>                                            <<<<*/

	mpz_t order;
	mpz_init(order);
	mpz_sub_ui(order, p, 1); //order = p-1 (at most)

	mpz_t *e; //array of exponent for each prime factor in p in the factorization of p-1.
	e = malloc(nfactors * sizeof(mpz_t));
	for (int i = 0; i < nfactors; i++)
	{
		mpz_init_set_ui(e[i], 1);
	}

	pohlig_hellman(x, w, y, p, order, factorlist, e, nfactors);

	mpz_clear(order);
	free(e);

#ifdef DEBUG_STATUS
	gmp_printf("Daemon private key found:\t\t%Zd\n", x);
#endif
}

/*
 * Verify_Sign(mdc,r,s,y) :
 *
 *  überprüft die El-Gamal-Signatur R/S zur MDC. Y ist der öffentliche
 *  Schlüssel des Absenders der Nachricht
 *
 * RETURN-Code: 1, wenn Signatur OK, 0 sonst.
 */
static int Verify_Sign(mpz_t mdc, mpz_t r, mpz_t s, mpz_t y)
{
	/*>>>>                                               <<<<*
	 *>>>> AUFGABE: Verifizieren einer El-Gamal-Signatur <<<<*
	 *>>>>                                               <<<<*/

	int result = 0;

	mpz_t left, right, temp;
	mpz_inits(left, right, temp, NULL);

	mpz_powm(left, y, r, p); //left = y^r mod p
	mpz_powm(temp, r, s, p);
	mpz_mul(left, left, temp);
	mpz_mod(left, left, p); //left += r^s mod p

	mpz_powm(right, w, mdc, p);

	if (mpz_cmp(right, left) == 0)
	{
		result = 1;
	}

	mpz_clears(left, right, temp, NULL);

	return result;
}

/*
 * Generate_Sign(m,r,s,x) : Erzeugt zu der MDC M eine El-Gamal-Signatur 
 *    in R und S. X ist der private Schlüssel
 */
static void Generate_Sign(mpz_t mdc, mpz_t r, mpz_t s, mpz_t x)
{
	/*>>>>                                           <<<<*
	 *>>>> AUFGABE: Erzeugen einer El-Gamal-Signatur <<<<*
	 *>>>>                                           <<<<*/

	mpz_t k, order, gcd;
	mpz_inits(k, order, gcd, NULL);
	mpz_sub_ui(order, p, 1); //order = p-1 (in this algorithm, this is exact)

	gmp_randstate_t randomness;
	gmp_randinit_default(randomness);
	time_t now = time(0);
	gmp_randseed_ui(randomness, now);

#ifdef DEBUG_VERBOSE
	int i = 0;
#endif

	while (mpz_cmp_ui(gcd, 1) != 0)
	{
		mpz_urandomm(k, randomness, order);
		mpz_gcd(gcd, k, order);

#ifdef DEBUG_VERBOSE
		i++;
		gmp_printf("Attempt #%i at generating random number for signature.\nk:\t\t%Zd\n", i, k);
#endif
	}

	gmp_randclear(randomness);
	mpz_clear(gcd);

	mpz_powm(r, w, k, p); //r = w^k mod p

	mpz_t temp;
	mpz_init(temp);

	if (!mpz_invert(s, k, order)) //s = k^-1 mod (p-1)
	{
		gmp_printf("No inverse found for k = %Zd modulu p-1 = %Zd!\n", k, order);
		exit(1);
	}

	mpz_set(temp, mdc);		//temp = mdc
	mpz_submul(temp, r, x); //temp -= r*x (temp = mdc - r*x)
	mpz_mul(s, s, temp);	//s = (mdc - r*x)*k^-1
	mpz_mod(s, s, order);   //s = (mdc - r*x)*k^-1 mod (p-1)

#ifdef DEBUG_CHECK
	mpz_t public_key;
	mpz_init(public_key);
	mpz_powm(public_key, w, x, p);

	if (!Verify_Sign(mdc, r, s, public_key))
	{
		gmp_printf("Error! Self-generated signature failed to verify!\n");
		gmp_printf("mdc:\t\t%Zd\nx:\t\t%Zd\nr:\t\t%Zd\ns:\t\t\n", mdc, x, r, s);
		exit(1);
	}

	mpz_clear(public_key);
#endif

	mpz_clears(k, order, temp, NULL);
}

int main(int argc, char **argv)
{
	Connection con;
	int cnt, ok;
	Message msg;
	mpz_t x, Daemon_y, Daemon_x, mdc, sign_s, sign_r;
	char *OurName;

	mpz_init(x);
	mpz_init(Daemon_y);
	mpz_init(Daemon_x);
	mpz_init(mdc);
	mpz_init(sign_s);
	mpz_init(sign_r);
	mpz_init(p);
	mpz_init(w);

	const char *keyfile = NULL;
	char c;
	while ((c = getopt(argc, argv, "f:")) != -1)
	{
		switch (c)
		{
		case 'f':
			keyfile = optarg;
			break;
		}
	}

	/**************  Laden der öffentlichen und privaten Daten  ***************/
	if (!Get_Private_Key(keyfile, p, w, x) || !Get_Public_Key(DAEMON_NAME, Daemon_y))
		exit(0);

	/********************  Verbindung zum Dämon aufbauen  *********************/
	// OurName = MakeNetName(NULL); /* gibt in Wirklichkeit Unix-Gruppenname zurück! */
	OurName = "rooteins";
	if (!(con = ConnectTo(OurName, DAEMON_NAME)))
	{
		fprintf(stderr, "Kann keine Verbindung zum Daemon aufbauen: %s\n", NET_ErrorText());
		exit(20);
	}

	/***********  Message vom Typ ReportRequest initialisieren  ***************/
	msg.typ = ReportRequest;					  /* Typ setzten */
	strcpy(msg.body.ReportRequest.Name, OurName); /* Gruppennamen eintragen */
	Generate_MDC(&msg, p, mdc);					  /* MDC generieren ... */
	Generate_Sign(mdc, sign_r, sign_s, x);		  /* ... und Nachricht unterschreiben */
	strcpy(msg.sign_r, mpz_get_str(NULL, 16, sign_r));
	strcpy(msg.sign_s, mpz_get_str(NULL, 16, sign_s));

	/*************  Machricht abschicken, Antwort einlesen  *******************/
	Transmit(con, &msg, sizeof(msg));
	ReceiveAll(con, &msg, sizeof(msg));

	/******************  Überprüfen der Dämon-Signatur  ***********************/
	printf("Nachricht vom Dämon:\n");
	for (cnt = 0; cnt < msg.body.ReportResponse.NumLines; cnt++)
	{
		printf("\t%s\n", msg.body.ReportResponse.Report[cnt]);
	}

	Generate_MDC(&msg, p, mdc);
	mpz_set_str(sign_r, msg.sign_r, 16);
	mpz_set_str(sign_s, msg.sign_s, 16);
	ok = Verify_Sign(mdc, sign_r, sign_s, Daemon_y);
	if (ok)
		printf("Dämon-Signatur ist ok!\n");
	else
		printf("Dämon-Signatur ist FEHLERHAFT!\n");

	/*>>>>                                      <<<<*
	 *>>>> AUFGABE: Fälschen der Dämon-Signatur <<<<*
	 *>>>>                                      <<<<*/

	/***********  Hack the shit out of em ***************/
	dlogP(Daemon_x, Daemon_y);

	/***********  Message vom Typ VerifyRequest initialisieren  ***************/
	msg.typ = VerifyRequest;

	strcpy(msg.body.VerifyRequest.Report[0], "  **********************************************");
	strcpy(msg.body.VerifyRequest.Report[1], "  * Auskunft über den Punktestand im Praktikum *");
	strcpy(msg.body.VerifyRequest.Report[2], "  *  Kryptographie und Datensicherheitstechnik *");
	strcpy(msg.body.VerifyRequest.Report[3], "  **********************************************");
	strcpy(msg.body.VerifyRequest.Report[4], " ");
	strcpy(msg.body.VerifyRequest.Report[5], " Stand: die Zukunft");
	strcpy(msg.body.VerifyRequest.Report[6], "Der Teilnehmer root hat in den Versuchen");
	strcpy(msg.body.VerifyRequest.Report[7], "1 bis 6 die erforderliche Punktezahl");
	strcpy(msg.body.VerifyRequest.Report[8], "erreicht. Dieser Schein beweist hiermit");
	strcpy(msg.body.VerifyRequest.Report[9], "seine erfolgreiche Teilnahme am Praktikum,");
	strcpy(msg.body.VerifyRequest.Report[10], "und auch das er der klügste und hübscheste");
	strcpy(msg.body.VerifyRequest.Report[11], "aller Zeiten ist.");
	strcpy(msg.body.VerifyRequest.Report[12], " ");
	strcpy(msg.body.VerifyRequest.Report[13], "Diese Auskunft ist elektronisch unterschrieben und");
	strcpy(msg.body.VerifyRequest.Report[14], "daher völlig Quatch :) --- gez. Sign_Daemon");

	msg.body.VerifyRequest.NumLines = 15;

	Generate_MDC(&msg, p, mdc);
	Generate_Sign(mdc, sign_r, sign_s, Daemon_x);
	strcpy(msg.sign_r, mpz_get_str(NULL, 16, sign_r));
	strcpy(msg.sign_s, mpz_get_str(NULL, 16, sign_s));

	printf("Nachricht an den Dämon:\n");
	for (cnt = 0; cnt < msg.body.VerifyRequest.NumLines; cnt++)
	{
		printf("\t%s\n", msg.body.VerifyRequest.Report[cnt]);
	}

	/*************  Machricht abschicken, Antwort einlesen  *******************/
	Transmit(con, &msg, sizeof(msg));
	ReceiveAll(con, &msg, sizeof(msg));

	/******************  Überprüfen der Dämon-Signatur  ***********************/
	printf("Nachricht vom Dämon:\n");
	printf("\t%s\n", msg.body.VerifyResponse.Res);

	Generate_MDC(&msg, p, mdc);
	mpz_set_str(sign_r, msg.sign_r, 16);
	mpz_set_str(sign_s, msg.sign_s, 16);
	ok = Verify_Sign(mdc, sign_r, sign_s, Daemon_y);
	if (ok)
		printf("Dämon-Signatur ist ok!\n");
	else
		printf("Dämon-Signatur ist FEHLERHAFT!\n");

	/******************  Clean up  ***********************/
	mpz_clears(x, Daemon_y, Daemon_x, mdc, sign_s, sign_r, p, w, NULL);
	return 0;
}
