/*****************************************************************************
 *
 * Copyright (c) 2018-19, Eduard Gerlits, Paula Herber, Josef Treus
 * Westfälische Wilhelms-Universität Münster, AG Embedded Systems 
 * All rights reserved.
 *
 * This file is part of SysCSec
 *
 * SysCSec 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 3 of the License, or (at your
 * option) any later version.
 *
 * SysCSec 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with SysCSec.  If not, see <http://www.gnu.org/licenses/>.
 *
 *
 *  Please report any problems or bugs to: sysc.sec@uni-muenster.de
 *
 ****************************************************************************/

#include "CA.h"

void CertificateAuthority::ca_main()
{
  //	sc_time t1(1000.0, "SC_MS");
	sc_biguint<960> p_data_server;
	sc_biguint<960> p_data_client;

	sc_biguint<160> q_x_server;
	sc_biguint<160> q_y_server;
	sc_biguint<160> q_x_client;
	sc_biguint<160> q_y_client;
	sc_biguint<160> d0;
	sc_biguint<160> d1;
	sc_biguint<160> d2;
	sc_biguint<160> d3;

	// start CA and generate key parameters
	std::cout << "[" << sc_time_stamp() << "]" << "[CA]" << ": " << "CA is started!" << std::endl;
	std::cout << "[" << sc_time_stamp() << "]" << "[CA]" << ": " << "Generate private and public key with the given ECC parameters ..." << std::endl;
	ecdsa_generate_key_pair(d, q_x, q_y);
	std::cout << "[" << sc_time_stamp() << "]" << "[CA]" << ": " << "The following keys were generated:" << std::endl;
	std::cout << "\t" << "Private Key (d_ca): " << d << std::endl;
	std::cout << "\t" << "Public Key (Q_ca): (" << q_x << "," << q_y << ")" << std::endl;
	std::cout << std::endl;

	// wait for public key from server
	std::cout << "[" << sc_time_stamp() << "]" << "[CA]" << ": " << "Wait for public key of the server ..." << std::endl;
	receive_data_server(p_data_server);
	unpack_data(p_data_server, q_x_server, q_y_server, d0, d1, d2, d3);
	std::cout << "[" << sc_time_stamp() << "]" << "[CA]" << ": " << "Public key received from server:" << std::endl;
	std::cout << "\t" << "Public Key Server (Q_s): (" << q_x_server << "," << q_y_server << ")" << std::endl;
	std::cout << std::endl;


	// generate unique server ID and TTL
	std::cout << "[" << sc_time_stamp() << "]" << "[CA]" << ": " << "Generate unique server ID and TTL..." << std::endl;
	sc_biguint<160> server_id=11111;
	sc_biguint<160> server_id_ttl = 3600;
	std::cout << "[" << sc_time_stamp() << "]" << "[CA]" << ": " << "Server ID and TTL successfully generated!" << std::endl;
	std::cout << "\t" << "Server ID (I_s): " << server_id << std::endl;
	std::cout << "\t" << "TTL (t_s): " << server_id_ttl << " s" << std::endl;
	std::cout << std::endl;


	// Generate Hash from Q_s.x , I_s and t_s
	std::cout << "[" << sc_time_stamp() << "]" << "[CA]" << ": " << "Generate server hash from Q_s.x I_s and t_s..." << std::endl;
	sc_biguint<160> server_info_hash;
	hash_data(q_x_server, server_id, server_id_ttl, server_info_hash);
	std::cout << "[" << sc_time_stamp() << "]" << "[CA]" << ": " << "Server hash successfully generated!" << std::endl;
	std::cout << "\t" << "H(Q_s.x, I_s, t_s): " << server_info_hash << std::endl;
	std::cout << std::endl;


	// Generate digital signature from server hash
	std::cout << "[" << sc_time_stamp() << "]" << "[CA]" << ": " << "Generate digital signature for given server hash..." << std::endl;
	sc_biguint<160> r_server;
	sc_biguint<160> s_server;
	ecdsa_sign(server_info_hash, r_server, s_server);
	std::cout << "[" << sc_time_stamp() << "]" << "[CA]" << ": " << "Digital signature successfully generated!" << std::endl;
	std::cout << "\t" << "Digital Signature (r_s,s_s): (" << r_server << "," << s_server << ")" << std::endl;
	std::cout << std::endl;

	// Send Q_ca, server info and digital signature to server
	std::cout << "[" << sc_time_stamp() << "]" << "[CA]" << ": " << "Send (Q_ca, I_s, (r_s, s_s), t_s) to Server:" << std::endl;
	pack_data(q_x, q_y, server_id, r_server, s_server, server_id_ttl, p_data_server);
	std::cout << "I_s: " << server_id << std::endl;
	send_data_server(p_data_server);
	std::cout << "[" << sc_time_stamp() << "]" << "[CA]" << ": " << "(Q_ca, I_s, (r_s, s_s), t_s) successfully sent!" << std::endl;
	std::cout << std::endl;	


	// Wait for Public Key of the Client
	std::cout << "[" << sc_time_stamp() << "]" << "[CA]" << ": " << "Wait for public key of the client..." << std::endl;
	receive_data_client(p_data_client);
	unpack_data(p_data_client, q_x_client, q_y_client, d0, d1, d2, d3);
	std::cout << "[" << sc_time_stamp() << "]" << "[CA]" << ": " << "Public key received from client:" << std::endl;
	std::cout << "\t" << "Public Key Client (Q_u): (" << q_x_client << "," << q_y_client << ")" << std::endl;
	std::cout << std::endl;


	// generate unique client ID and TTL
	std::cout << "[" << sc_time_stamp() << "]" << "[CA]" << ": " << "Generating unique client ID and TTL ... " << std::endl;
	sc_biguint<160> client_id=22222;
	sc_biguint<160> client_id_ttl = 3600;
	std::cout << "[" << sc_time_stamp() << "]" << "[CA]" << ": " << "Client ID and TTL successfully generated!" << std::endl;
	std::cout << "\t" << "Client-ID (I_s): " << client_id << std::endl;
	std::cout << "\t" << "TTL (t_s): " << client_id_ttl << " s" << std::endl;
	std::cout << std::endl;

	// Generate Hash from Q_u.x , I_u and t_u
	std::cout << "[" << sc_time_stamp() << "]" << "[CA]" << ": " << "Generate client hash from Q_u.x I_u and t_u..." << std::endl;
	sc_biguint<160> client_info_hash;
	hash_data(q_x_client, client_id, client_id_ttl, client_info_hash);
	std::cout << "[" << sc_time_stamp() << "]" << "[CA]" << ": " << "Client hash successfully generated!" << std::endl;
	std::cout << "\t" << "H(Q_u.x, I_u, t_u): " << client_info_hash << std::endl;
	std::cout << std::endl;	


	// generate digital signature of the client hash
	std::cout << "[" << sc_time_stamp() << "]" << "[CA]" << ": " << "Generate digitale signature for the client hash ..." << std::endl;
	sc_biguint<160> r_client;
	sc_biguint<160> s_client;
	ecdsa_sign(client_info_hash, r_client, s_client);
	std::cout << "[" << sc_time_stamp() << "]" << "[CA]" << ": " << "Digitale signature successfully generated!" << std::endl;
	std::cout << "\t" << "Digital Signature (r_u,s_u): (" << r_client << "," << s_client << ")" << std::endl;
	std::cout << std::endl;

	// sende Q_ca, client info and digital signature to client
	std::cout << "[" << sc_time_stamp() << "]" << "[CA]" << ": " << "Send (Q_ca, I_u, (r_u, s_u), t_u) to client:" << std::endl;
	pack_data(q_x, q_y, client_id, r_client, s_client, client_id_ttl, p_data_client);
	//std::cout << "I_u: " << client_id << std::endl;
	send_data_client(p_data_client);
	std::cout << "[" << sc_time_stamp() << "]" << "[CA]" << ": " << "(Q_ca, I_u, (r_u, s_u), t_u) successfully sent!" << std::endl;
	std::cout << std::endl;	
}

void CertificateAuthority::initialise_ecc_parameter(sc_biguint<160> ec_param_a_, 
								sc_biguint<160> ec_param_b_, 
								sc_biguint<160> field_param_m_, 
								sc_biguint<160> ec_pt_x_,
								sc_biguint<160> ec_pt_y_,
								sc_biguint<160> ec_pt_order_n_)
{
	ec_param_a = ec_param_a_;
	ec_param_b = ec_param_b_;
	field_param_m = field_param_m_;
	ec_pt_x = ec_pt_x_;
	ec_pt_y = ec_pt_y_;
	ec_pt_order_n = ec_pt_order_n_;
}

void CertificateAuthority::ecdsa_generate_key_pair(sc_biguint<160> &d, sc_biguint<160> &q_x, sc_biguint<160> &q_y){
	ecc_control_fifo.write(_FW_ECDSA_KEY_GENERATION_);
	data_in0_fifo.write(ec_param_a);
	data_in1_fifo.write(ec_param_b);
	data_in2_fifo.write(ec_pt_x);
	data_in3_fifo.write(ec_pt_y);
	data_in4_fifo.write(ec_pt_order_n);
	data_in5_fifo.write(field_param_m);

	sc_uint<2> ecc_status_in=1;
	sc_biguint<160> p_data_out0;
	sc_biguint<160> p_data_out1;
	sc_biguint<160> p_data_out2;
	sc_biguint<160> p_data_out3;
	sc_biguint<160> p_data_out4;
	sc_biguint<160> p_data_out5;

	while(ecc_status_in!=_FW_FINISHED_){
		ecc_status_in = ecc_status_fifo.read();
	}

	data_out0_fifo.nb_read(d);
	data_out1_fifo.nb_read(q_x);
	data_out2_fifo.nb_read(q_y);
}

void CertificateAuthority::send_data_server(sc_biguint<960> p_data)
{
	out_server->write(p_data);
}

void CertificateAuthority::receive_data_server(sc_biguint<960> &p_data)
{
	p_data = in_server->read();
}

void CertificateAuthority::send_data_client(sc_biguint<960> p_data)
{
	out_client->write(p_data);
}

void CertificateAuthority::receive_data_client(sc_biguint<960> &p_data)
{
	p_data = in_client->read();
}

void CertificateAuthority::pack_data(sc_biguint<160> data0, 
					sc_biguint<160> data1, 
					sc_biguint<160> data2, 
					sc_biguint<160> data3, 
					sc_biguint<160> data4, 
					sc_biguint<160> data5,
					sc_biguint<960> &p_data)
{
	sc_uint<3> io_status=_IO_PROCESSING_;
	sc_biguint<160> data;

	io_control_in_fifo.write(_IO_PACK_DATA_);
	io_data_in_fifo.write(data5);
	p_data_in_fifo.write(0);

	while(io_status!=_IO_FINISHED_){
		io_status=io_status_fifo.read();
	}
	data = io_data_out_fifo.read();
	p_data = p_data_out_fifo.read();	



	io_control_in_fifo.write(_IO_PACK_DATA_);
	io_data_in_fifo.write(data4);
	p_data_in_fifo.write(0);
	io_status=_IO_PROCESSING_;

	while(io_status!=_IO_FINISHED_){
		io_status=io_status_fifo.read();
	}	
	data = io_data_out_fifo.read();
	p_data = p_data_out_fifo.read();



	io_control_in_fifo.write(_IO_PACK_DATA_);
	io_data_in_fifo.write(data3);
	p_data_in_fifo.write(0);
	io_status=_IO_PROCESSING_;

	while(io_status!=_IO_FINISHED_){
		io_status=io_status_fifo.read();
	}	
	data = io_data_out_fifo.read();
	p_data = p_data_out_fifo.read();



	io_control_in_fifo.write(_IO_PACK_DATA_);
	io_data_in_fifo.write(data2);
	p_data_in_fifo.write(0);
	io_status=_IO_PROCESSING_;

	while(io_status!=_IO_FINISHED_){
		io_status=io_status_fifo.read();
	}	
	data = io_data_out_fifo.read();
	p_data = p_data_out_fifo.read();



	io_control_in_fifo.write(_IO_PACK_DATA_);
	io_data_in_fifo.write(data1);
	p_data_in_fifo.write(0);
	io_status=_IO_PROCESSING_;

	while(io_status!=_IO_FINISHED_){
		io_status=io_status_fifo.read();
	}	
	data = io_data_out_fifo.read();
	p_data = p_data_out_fifo.read();



	io_control_in_fifo.write(_IO_PACK_DATA_);
	io_data_in_fifo.write(data0);
	p_data_in_fifo.write(0);
	io_status=_IO_PROCESSING_;

	while(io_status!=_IO_FINISHED_){
		io_status=io_status_fifo.read();
	}	
	data = io_data_out_fifo.read();
	p_data = p_data_out_fifo.read();



	io_control_in_fifo.write(_IO_GET_DATA_);
	io_data_in_fifo.write(0);
	p_data_in_fifo.write(0);
	io_status=_IO_PROCESSING_;

	while(io_status!=_IO_FINISHED_){
		io_status=io_status_fifo.read();
	}
	data = io_data_out_fifo.read();
	p_data = p_data_out_fifo.read();
}

	
void CertificateAuthority::unpack_data(sc_biguint<960> p_data, 
						sc_biguint<160> &data0, 
						sc_biguint<160> &data1,
						sc_biguint<160> &data2, 
						sc_biguint<160> &data3,
						sc_biguint<160> &data4, 
						sc_biguint<160> &data5)
{
	sc_uint<3> io_status=_IO_PROCESSING_;

	//std::cout << "p_data: " << p_data << std::endl;

	io_control_in_fifo.write(_IO_SET_DATA_);
	io_data_in_fifo.write(0);
	p_data_in_fifo.write(p_data);

	while(io_status!=_IO_FINISHED_){
		io_status=io_status_fifo.read();
	}

	io_data_out_fifo.read();
	p_data_out_fifo.read();



	io_control_in_fifo.write(_IO_UNPACK_DATA_);
	io_data_in_fifo.write(0);
	p_data_in_fifo.write(0);
	io_status=_IO_PROCESSING_;

	while(io_status!=_IO_FINISHED_){
		io_status=io_status_fifo.read();
	}

	data0 = io_data_out_fifo.read();
	p_data_out_fifo.read();



	io_control_in_fifo.write(_IO_UNPACK_DATA_);
	io_data_in_fifo.write(0);
	p_data_in_fifo.write(0);
	io_status=_IO_PROCESSING_;

	while(io_status!=_IO_FINISHED_){
		io_status=io_status_fifo.read();
	}
	data1 = io_data_out_fifo.read();
	p_data_out_fifo.read();



	io_control_in_fifo.write(_IO_UNPACK_DATA_);
	io_data_in_fifo.write(0);
	p_data_in_fifo.write(0);
	io_status=_IO_PROCESSING_;

	while(io_status!=_IO_FINISHED_){
		io_status=io_status_fifo.read();
	}
	data2 = io_data_out_fifo.read();
	p_data_out_fifo.read();	



	io_control_in_fifo.write(_IO_UNPACK_DATA_);
	io_data_in_fifo.write(0);
	p_data_in_fifo.write(0);
	io_status=_IO_PROCESSING_;

	while(io_status!=_IO_FINISHED_){
		io_status=io_status_fifo.read();
	}
	data3 = io_data_out_fifo.read();
	p_data_out_fifo.read();



	io_control_in_fifo.write(_IO_UNPACK_DATA_);
	io_data_in_fifo.write(0);
	p_data_in_fifo.write(0);
	io_status=_IO_PROCESSING_;

	while(io_status!=_IO_FINISHED_){
		io_status=io_status_fifo.read();
	}
	data4 = io_data_out_fifo.read();
	p_data_out_fifo.read();



	io_control_in_fifo.write(_IO_UNPACK_DATA_);
	io_data_in_fifo.write(0);
	p_data_in_fifo.write(0);
	io_status=_IO_PROCESSING_;

	while(io_status!=_IO_FINISHED_){
		io_status=io_status_fifo.read();
	}
	data5 = io_data_out_fifo.read();
	p_data_out_fifo.read();		
}



void CertificateAuthority::hash_data(sc_biguint<160> first, sc_biguint<160> second, sc_biguint<160> third, sc_biguint<160> &hash){
	std::string str = first.to_string() + ";" + second.to_string() + ";" + third.to_string();
	sha1_in_fifo.write(str);
	hash = sha1_out_fifo.read();
	//hash = (first + second + third) % field_param_m; 
}

void CertificateAuthority::ecdsa_sign(sc_biguint<160> hash, sc_biguint<160> &r, sc_biguint<160> &s){
	ecc_control_fifo.write(_FW_ECDSA_SIGN_);
	data_in0_fifo.write(ec_param_a);
	data_in1_fifo.write(ec_pt_x);
	data_in2_fifo.write(ec_pt_y);
	data_in3_fifo.write(d);
	data_in4_fifo.write(ec_pt_order_n);
	data_in5_fifo.write(field_param_m);
	data_in6_fifo.write(hash);

	sc_uint<3> ecc_status=_FW_PROCESSING_;

	while(ecc_status!=_FW_FINISHED_){
		ecc_status = ecc_status_fifo.read();
	}

	data_out0_fifo.read(r);
	data_out1_fifo.read(s);
}

bool CertificateAuthority::ecdsa_verify(sc_biguint<160> hash, sc_biguint<160> r, sc_biguint<160> s){
	ecc_control_fifo.write(_FW_ECDSA_VERIFY_);

	data_in0_fifo.write(ec_param_a);

	data_in1_fifo.write(ec_pt_x);

	data_in2_fifo.write(ec_pt_y);

	data_in3_fifo.write(q_x);

	data_in4_fifo.write(q_y);
	
	data_in5_fifo.write(r);

	data_in6_fifo.write(s);

	data_in7_fifo.write(ec_pt_order_n);

	data_in8_fifo.write(field_param_m);

	data_in9_fifo.write(hash);


	sc_uint<3> ecc_status=_FW_PROCESSING_;

	while(ecc_status!=_FW_FINISHED_){
		ecc_status = ecc_status_fifo.read();
	}

	sc_biguint<160> result = data_out0_fifo.read();

	if(result==_FW_ECDSA_VERIFICATION_SUCCESS_){
		return true;
	}
	return false;
}
