/*
 *  Copyright (C) 2007-2008 CMM Sigma Andrzej Chmielowiec <cmmsigma@cmmsigma.eu>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 3
 *  as published by the Free Software Foundation.
 *
 *  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 <stdio.h>
#include <string.h>

#include <openssl/bio.h>
#include <openssl/evp.h>
#include <openssl/err.h>
#include <openssl/x509.h>
#include <openssl/objects.h>
#include <openssl/pem.h>
#include <openssl/engine.h>

#include "ossl_engine.h"

#include "common.h"
#include "CRD_UT10XX_threads.h"

#include <QApplication>
#include <QFile>

////////////////////////////////////////////////////////////////////////////////////////////////////
CRD_UT10XX_thread::CRD_UT10XX_thread(CRD_UT10XX_t *ut)
{
	mUt = ut;
}

int
CRD_UT10XX_thread::execWithCursor(Qt::CursorShape shape)
{
	start();
	
	QApplication::setOverrideCursor(QCursor(shape));
	
	while (isRunning())
		QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
	
	QApplication::restoreOverrideCursor();
	
	return mStatus;
}

int
CRD_UT10XX_thread::status()
{
	return mStatus;
}

QString
CRD_UT10XX_thread::errorString()
{
	return mErrorString;
}

void
CRD_UT10XX_thread::setStatus(int status)
{
	if ((mStatus = status) < 0)
	{
		if (mUt)
			mErrorString = QString("%1").arg(CRD_UT10XX_lib_detail_error_desc(mUt));
		else
			mErrorString = QString("%1").arg(CRD_UT10XX_lib_error_desc(mStatus));
	}
}


////////////////////////////////////////////////////////////////////////////////////////////////////
CRD_UT10XX_thread_lib_refresh::CRD_UT10XX_thread_lib_refresh()
{
}

void
CRD_UT10XX_thread_lib_refresh::run()
{
	setStatus(CRD_UT10XX_lib_find_devices());
	
	if (status() < 0)
		return;
	
	setStatus(CRD_UT10XX_lib_actualize_devices());
}


////////////////////////////////////////////////////////////////////////////////////////////////////
CRD_UT10XX_thread_cmd_dev_init::CRD_UT10XX_thread_cmd_dev_init(CRD_UT10XX_t *ut):
	CRD_UT10XX_thread(ut)
{
}

void
CRD_UT10XX_thread_cmd_dev_init::run()
{
	if (mUt)
		setStatus(CRD_UT10XX_cmd_dev_init(mUt));
}


////////////////////////////////////////////////////////////////////////////////////////////////////
CRD_UT10XX_thread_cmd_dev_conf::CRD_UT10XX_thread_cmd_dev_conf(CRD_UT10XX_t *ut):
	CRD_UT10XX_thread(ut)
{
}

void
CRD_UT10XX_thread_cmd_dev_conf::run()
{
	if (mUt)
		setStatus(CRD_UT10XX_cmd_dev_conf(mUt));
}


////////////////////////////////////////////////////////////////////////////////////////////////////
CRD_UT10XX_thread_cmd_read_dev_info::CRD_UT10XX_thread_cmd_read_dev_info(CRD_UT10XX_t *ut):
	CRD_UT10XX_thread(ut)
{
}

void
CRD_UT10XX_thread_cmd_read_dev_info::run()
{
	if (mUt)
		setStatus(CRD_UT10XX_cmd_read_dev_info(mUt));
}


////////////////////////////////////////////////////////////////////////////////////////////////////
CRD_UT10XX_thread_cmd_read_key_info::CRD_UT10XX_thread_cmd_read_key_info(CRD_UT10XX_t *ut, 
	int keyNum): CRD_UT10XX_thread(ut)
{
	mKeyNum = keyNum;
}

void
CRD_UT10XX_thread_cmd_read_key_info::run()
{
	if (mUt)
		setStatus(CRD_UT10XX_cmd_read_key_info(mUt, mKeyNum));
}


////////////////////////////////////////////////////////////////////////////////////////////////////
CRD_UT10XX_thread_cmd_read_public_obj::CRD_UT10XX_thread_cmd_read_public_obj(CRD_UT10XX_t *ut, 
	int objNum): CRD_UT10XX_thread(ut)
{
	mObjNum = objNum;
	mObj = QByteArray(64, '\0');
}

QByteArray
CRD_UT10XX_thread_cmd_read_public_obj::obj()
{
	return mObj;
}

void
CRD_UT10XX_thread_cmd_read_public_obj::run()
{
	if (mUt)
		setStatus(CRD_UT10XX_cmd_read_public_obj(mUt, mObjNum, mObj.data()));
}


////////////////////////////////////////////////////////////////////////////////////////////////////
CRD_UT10XX_thread_cmd_rbg_generate_bits::CRD_UT10XX_thread_cmd_rbg_generate_bits(CRD_UT10XX_t *ut, 
	int octets): CRD_UT10XX_thread(ut)
{
	mOctets = octets;
	mBuffer = QByteArray(mOctets, '\0');
}

QByteArray
CRD_UT10XX_thread_cmd_rbg_generate_bits::buffer()
{
	return mBuffer;
}

void
CRD_UT10XX_thread_cmd_rbg_generate_bits::copyBuffer(char *buffer)
{
	for (int i = 0; i < mOctets; i++)
		buffer[i] = mBuffer[i];
}

void
CRD_UT10XX_thread_cmd_rbg_generate_bits::run()
{
	if (mUt)
		setStatus(CRD_UT10XX_cmd_rbg_generate_bits(mUt, mBuffer.data(), mOctets));
}


////////////////////////////////////////////////////////////////////////////////////////////////////
CRD_UT10XX_thread_cmd_dev_unlock::CRD_UT10XX_thread_cmd_dev_unlock(CRD_UT10XX_t *ut, 
	const char *pin): CRD_UT10XX_thread(ut)
{
	mPin = pin;
}

void
CRD_UT10XX_thread_cmd_dev_unlock::run()
{
	if (mUt)
		setStatus(CRD_UT10XX_cmd_dev_unlock(mUt, mPin));
}


////////////////////////////////////////////////////////////////////////////////////////////////////
CRD_UT10XX_thread_cmd_dev_erase::CRD_UT10XX_thread_cmd_dev_erase(CRD_UT10XX_t *ut, 
	const char *pin): CRD_UT10XX_thread(ut)
{
	mPin = pin;
}

void
CRD_UT10XX_thread_cmd_dev_erase::run()
{
	if (mUt)
		setStatus(CRD_UT10XX_cmd_dev_erase(mUt, mPin));
}


////////////////////////////////////////////////////////////////////////////////////////////////////
CRD_UT10XX_thread_cmd_change_so_pin::CRD_UT10XX_thread_cmd_change_so_pin(CRD_UT10XX_t *ut, 
	const char *pin, const char *newPin): CRD_UT10XX_thread(ut)
{
	mPin = pin;
	mNewPin = newPin;
}

void
CRD_UT10XX_thread_cmd_change_so_pin::run()
{
	if (mUt)
		setStatus(CRD_UT10XX_cmd_change_so_pin(mUt, mPin, mNewPin));
}


////////////////////////////////////////////////////////////////////////////////////////////////////
CRD_UT10XX_thread_cmd_ecdsa_sign::CRD_UT10XX_thread_cmd_ecdsa_sign(CRD_UT10XX_t *ut, 
	const char *pin, int keyNum, const char *digest, int digestLen): CRD_UT10XX_thread(ut)
{
	mKeyNum = keyNum;
	mDigestLen = digestLen;
	mPin = pin;
	mDigest = digest;
	mSignature = QByteArray(1024, '\0');
}

QByteArray
CRD_UT10XX_thread_cmd_ecdsa_sign::signature()
{
	return mSignature;
}

void
CRD_UT10XX_thread_cmd_ecdsa_sign::run()
{
	if (mUt)
	{
		setStatus(CRD_UT10XX_cmd_ecdsa_sign(mUt, mPin, mKeyNum, mSignature.data(), 
			mSignature.size(), mDigest, mDigestLen));
		
		if (0 < status())
			mSignature.resize(status());
	}
}


////////////////////////////////////////////////////////////////////////////////////////////////////
CRD_UT10XX_thread_cmd_change_user_pin::CRD_UT10XX_thread_cmd_change_user_pin(CRD_UT10XX_t *ut, 
	const char *pin, int pinId, const char *newPin): CRD_UT10XX_thread(ut)
{
	mPinId = pinId;
	mPin = pin;
	mNewPin = newPin;
}

void
CRD_UT10XX_thread_cmd_change_user_pin::run()
{
	if (mUt)
		setStatus(CRD_UT10XX_cmd_change_user_pin(mUt, mPin, mPinId, mNewPin));
}


////////////////////////////////////////////////////////////////////////////////////////////////////
CRD_UT10XX_thread_cmd_generate_key::CRD_UT10XX_thread_cmd_generate_key(CRD_UT10XX_t *ut, 
	const char *pin, int pinId, const char *keyName, int keyNum): CRD_UT10XX_thread(ut)
{
	mPinId = pinId;
	mKeyNum = keyNum;
	mPin = pin;
	mKeyName = keyName;
}

void
CRD_UT10XX_thread_cmd_generate_key::run()
{
	if (mUt)
		setStatus(CRD_UT10XX_cmd_generate_key(mUt, mPin, mPinId, mKeyName, mKeyNum));
}


////////////////////////////////////////////////////////////////////////////////////////////////////
CRD_UT10XX_thread_cmd_delete_key::CRD_UT10XX_thread_cmd_delete_key(CRD_UT10XX_t *ut, 
	const char *pin, int pinId, int keyNum): CRD_UT10XX_thread(ut)
{
	mPinId = pinId;
	mKeyNum = keyNum;
	mPin = pin;
}

void
CRD_UT10XX_thread_cmd_delete_key::run()
{
	if (mUt)
		setStatus(CRD_UT10XX_cmd_delete_key(mUt, mPin, mPinId, mKeyNum));
}


////////////////////////////////////////////////////////////////////////////////////////////////////
CRD_UT10XX_thread_cmd_write_public_obj::CRD_UT10XX_thread_cmd_write_public_obj(CRD_UT10XX_t *ut, 
	const char *pin, int pinId, int objNum, const char *obj): CRD_UT10XX_thread(ut)
{
	mPinId = pinId;
	mObjNum = objNum;
	mPin = pin;
	mObj = obj;
}

void
CRD_UT10XX_thread_cmd_write_public_obj::run()
{
	if (mUt)
		setStatus(CRD_UT10XX_cmd_write_public_obj(mUt, mPin, mPinId, mObjNum, mObj));
}


////////////////////////////////////////////////////////////////////////////////////////////////////
CRD_UT10XX_thread_cmd_try_pin::CRD_UT10XX_thread_cmd_try_pin(CRD_UT10XX_t *ut, 
	const char *pin, int pinId): CRD_UT10XX_thread(ut)
{
	mPinId = pinId;
	mPin = pin;
}

void
CRD_UT10XX_thread_cmd_try_pin::run()
{
	if (mUt)
		setStatus(CRD_UT10XX_cmd_try_pin(mUt, mPin, mPinId));
}


////////////////////////////////////////////////////////////////////////////////////////////////////
CRD_UT10XX_thread_fs_get_file_list::CRD_UT10XX_thread_fs_get_file_list(CRD_UT10XX_t *ut): 
	CRD_UT10XX_thread(ut)
{
}

void
CRD_UT10XX_thread_fs_get_file_list::run()
{
	if (mUt)
		setStatus(CRD_UT10XX_fs_get_file_list(mUt));
}


////////////////////////////////////////////////////////////////////////////////////////////////////
CRD_UT10XX_thread_file_open::CRD_UT10XX_thread_file_open(CRD_UT10XX_t *ut, const char *fileName):
	CRD_UT10XX_thread(ut)
{
	mFileName = fileName;
	mFileHandle = 0;
}

CRD_UT10XX_file_t*
CRD_UT10XX_thread_file_open::fileHandle()
{
	return mFileHandle;
}

void
CRD_UT10XX_thread_file_open::run()
{
	if (mUt)
	{
		if ((mFileHandle = CRD_UT10XX_file_open(mUt, mFileName)) == 0)
			setStatus(-1);
		
		else
			setStatus(0);
	}
}


////////////////////////////////////////////////////////////////////////////////////////////////////
CRD_UT10XX_thread_file_flush::CRD_UT10XX_thread_file_flush(CRD_UT10XX_t *ut, CRD_UT10XX_file_t *fd, 
	const char *pin, int pinId): CRD_UT10XX_thread(ut)
{
	mPinId = pinId;
	mPin = pin;
	mFileHandle = fd;
}

void
CRD_UT10XX_thread_file_flush::run()
{
	if (mUt && mFileHandle)
		setStatus(CRD_UT10XX_file_flush(mFileHandle, mPin, mPinId));
}


////////////////////////////////////////////////////////////////////////////////////////////////////
CRD_UT10XX_thread_file_delete::CRD_UT10XX_thread_file_delete(CRD_UT10XX_t *ut, CRD_UT10XX_file_t *fd, 
	const char *pin, int pinId): CRD_UT10XX_thread(ut)
{
	mPinId = pinId;
	mPin = pin;
	mFileHandle = fd;
}

void
CRD_UT10XX_thread_file_delete::run()
{
	if (mUt && mFileHandle)
		setStatus(CRD_UT10XX_file_delete(mFileHandle, mPin, mPinId));
}

////////////////////////////////////////////////////////////////////////////////////////////////////
CRD_UT10XX_thread_generate_cert_request::CRD_UT10XX_thread_generate_cert_request(CRD_UT10XX_t *ut, 
	const char *pin, int keyId, QString fileName, QStringList dn, QString mail): CRD_UT10XX_thread(ut)
{
	mKeyNum = keyId;
	mPin = pin;
	mFileName = fileName;
	mSubject = dn;
	mMail = mail;
}

static void
apps_startup()
{
	OpenSSL_add_all_algorithms();
	ERR_load_crypto_strings();
	ENGINE_load_builtin_engines();
}

static void
apps_shutdown()
{
	OBJ_cleanup();
	EVP_cleanup();
	ENGINE_cleanup();
	CRYPTO_cleanup_all_ex_data();
	ERR_remove_state(0);
	ERR_free_strings();
}

static X509_NAME*
createSubjectName(QStringList &dn)
{
	X509_NAME *name = X509_NAME_new();
	
	for (int i = 0; i < dn.size(); i++)
	{
		QStringList sl = dn.value(i).split('=');
		QByteArray type = sl.value(0).toAscii();
		QByteArray value = sl.value(1).toUtf8();
		
		X509_NAME_add_entry_by_txt(name, type.data(), MBSTRING_UTF8, 
			(const unsigned char *)value.data(), -1, -1, 0);
	}
	
	return name;
}

void
CRD_UT10XX_thread_generate_cert_request::run()
{
	QFile file(mFileName);
	
	char *reqRep;
	int reqRepLen;
	
	BIO *out = NULL;
	ENGINE *engine = NULL;
	X509_REQ *req = NULL;
	X509_NAME *name = NULL;
	EVP_PKEY *pkey = NULL;
	const EVP_MD *digest = NULL;
	
	apps_startup();
	
	ENGINE_load_stoken();
	engine = ENGINE_by_id("stoken");
	ENGINE_set_default(engine, ENGINE_METHOD_ALL);
	
	digest = EVP_get_digestbyname("ecdsa-with-sha256");
	
	//ENGINE_ctrl_cmd_string(engine, "log_file", (char *)"crd_ut10xx_log", 0);
	ENGINE_ctrl_cmd_string(engine, "token_name", mUt->info.name, 0);
	ENGINE_ctrl_cmd_string(engine, "key_name", (mUt->keys + mKeyNum)->name, 0);
	ENGINE_ctrl_cmd_string(engine, "user_pin", (char *)mPin, 0);
	
	pkey = ENGINE_load_private_key(engine, 0, 0, 0);
	
	req = X509_REQ_new();
	
	name = createSubjectName(mSubject);
	
	X509_REQ_set_version(req, 0);
	X509_REQ_set_subject_name(req, name);
	X509_REQ_set_pubkey(req, pkey);
	
	X509_REQ_sign(req, pkey, digest);
	
	out = BIO_new(BIO_s_mem());
	//i2d_X509_REQ_bio(out, req);
	//X509_REQ_print_ex(out, req, 0, 0);
	PEM_write_bio_X509_REQ(out, req);
	
	reqRepLen = BIO_get_mem_data(out, &reqRep);
	
	file.open(QIODevice::ReadWrite | QIODevice::Truncate);
	file.write(reqRep, reqRepLen);
	file.close();
	
	EVP_PKEY_free(pkey);
	X509_NAME_free(name);
	X509_REQ_free(req);
	ENGINE_free(engine);
	BIO_free(out);
	
	apps_shutdown();
	
	setStatus(0);
	
	QApplication::restoreOverrideCursor();
	emit genFinished(mFileName, mMail);
}
