// DeCrypt.cpp : Defines the entry point for the console application.
//

#define MORE_ONE_CA          0
#define CRYPT_VERIFYCONTEXT  0xF0000000
#define MAX_BASE_DENCODED_SIZE(base_size)  (((base_size*3)/4)+4)
#include <stdio.h>
#include <time.h>
#include <sys/stat.h>
#ifdef _WIN32
#include <windows.h>
#define CRYPT_VERIFYCONTEXT     0xF0000000
#define strlib "..\\lib\\strlib.dll"
#else
#define GetProcAddress dlsym
#define FreeLibrary dlclose
#include <string>
#include  <unistd.h>
#include  <dlfcn.h>
#define strlib "lib/libstr.1.0.0.sl"
#define _cdecl
#define CRYPT_VERIFYCONTEXT 0xF0000000
#define TRUE 1
#define FALSE 0
#endif
#ifndef _WIN32
typedef unsigned long DWORD;
typedef unsigned char BYTE;
typedef void *HINSTANCE;
typedef char CHAR;
#endif
typedef unsigned long hCERTSTORE;
typedef unsigned long hCERTCONTEXT;
typedef unsigned long hCERTCOLLECTION;
typedef unsigned long hCRYPTMSG;
typedef unsigned long HCRYPTPROV;
#define AT_KEYEXCHANGE          1
#define AT_SIGNATURE            2
typedef void(_cdecl *setldapver)(hCERTSTORE hStore, int ver);
typedef int(_cdecl *signmsgopen)
(HCRYPTPROV hProv,
	int Flags,
	hCRYPTMSG *phCrypt);
typedef int(_cdecl *cspopencontext)
(HCRYPTPROV *phProv,
	CHAR *pszContainer,
	DWORD dwProvType,
	DWORD dwProvParam,
	DWORD dwFlags,
	BYTE * pbSignature,
	DWORD dwSigLen,
	BYTE * pbContainer,
	DWORD * dwContLen,
	HINSTANCE *phLib);
typedef int(_cdecl *crtopenstore)
(DWORD StoreProvider,
	HCRYPTPROV hProv,
	unsigned long dwFlags,
	bool  bFlag,
	const void *pvParam,
	hCERTSTORE *phStore);
typedef int(_cdecl *crtclosestore)                     (hCERTSTORE *phStore);
typedef int(_cdecl *cspclosecontext)                   (HCRYPTPROV *phProv);
typedef char* (_cdecl *errorstring)                     (const int err);
typedef int(_cdecl *basetobin)
(unsigned char *inBuf,
	long inSize,
	unsigned char *ouBuf,
	long *outSize);
typedef int(_cdecl *crtsetstorepropertyext)
(const hCERTSTORE hStore,
	const unsigned long Flags,
	const char *DName,
	const char *host,
	const char *port,
	const int timeOut,
	const char *name,
	const char *pass);
typedef int(_cdecl *getmycertificate)
(hCERTSTORE hStore,
	const char *DName,
	const unsigned long keySpec,
	const bool isCA,
	unsigned char *certBody,
	unsigned long *bodySize);
typedef int(_cdecl *getcertificatecontext)
(const hCERTSTORE hStore,
	const unsigned char *certBody,
	const unsigned long bodySize,
	hCERTCONTEXT *phContext);
typedef int(_cdecl *messagename)
(const unsigned char *cmsData,
	const unsigned long dataSize,
	int *senderCount,
	char **senderName,
	int *recipCount,
	char **recipName);
typedef int(_cdecl *getmessagecertificate)
(const hCERTSTORE hStore,
	const unsigned char *CMSBlob,
	const unsigned long sizeBlob,
	hCERTCOLLECTION *hCollection);
typedef int(_cdecl *decryptmessage)
(const hCRYPTMSG hCrypt,
	const hCERTCOLLECTION hCollection,
	const hCERTCONTEXT MYCert,
	const bool Final,
	const unsigned char *encryptedBlob,
	const unsigned long sizeBlob,
	char *SenderName,
	char *timeToCrypted,
	char *dataDescriptor,
	unsigned char *dataToDecrypted,
	unsigned long *dataSize);
typedef int(_cdecl *messagetype)
(const unsigned char *cmsData,
	const unsigned long dataSize,
	int  *type);
typedef int(_cdecl *freecertificatecollection) (hCERTCOLLECTION *phCollection);
typedef int(_cdecl *signmsgclose) (hCRYPTMSG *phCrypt);
typedef int(_cdecl *crptmsgclose) (hCRYPTMSG *phCrypt);

cspopencontext				CSPOpenContext;
crtopenstore				CrtOpenStore;
crtclosestore				CrtCloseStore;
cspclosecontext				CSPCloseContext;
errorstring					ErrorString;
basetobin					BaseToBin;
crtsetstorepropertyext		CrtSetStorePropertyExt;
getmycertificate			GetMYCertificate;
getcertificatecontext		GetCertificateContext;
messagename					MessageName;
getmessagecertificate		GetMessageCertificate;
decryptmessage				DecryptMessage;
messagetype					MessageType;
freecertificatecollection	FreeCertificateCollection;
signmsgclose				SignMsgClose;
crptmsgclose                    CrptMsgClose;
setldapver                      SetLDAPver;
signmsgopen                     SignMsgOpen;

HINSTANCE load_lib(char *lib)
{
#ifdef _WIN32
	return LoadLibraryA(lib);
#else
	return dlopen(lib, RTLD_LAZY);
#endif
}
void lib_free(HINSTANCE Instance)
{
#ifdef _WIN32
	FreeLibrary(Instance);
#else
	dlclose(Instance);
#endif
}
int get_func(HINSTANCE lib)
{
	CSPOpenContext = (cspopencontext)GetProcAddress(lib, "CSPOpenContext");
	if (!CSPOpenContext)
	{
		printf("error load function - %s", "CSPOpenContext");
		return 1;
	}
	CrtOpenStore = (crtopenstore)GetProcAddress(lib, "CrtOpenStore");
	if (!CrtOpenStore)
	{
		printf("error load function - %s", "CrtOpenStore");
		return 1;
	}
	CSPCloseContext = (cspclosecontext)GetProcAddress(lib, "CSPCloseContext");
	if (!CSPCloseContext)
	{
		printf("error load function - %s", "CSPCloseContext");
		return 1;
	}
	CrtCloseStore = (crtclosestore)GetProcAddress(lib, "CrtCloseStore");
	if (!CrtCloseStore)
	{
		printf("error load function - %s", "CrtCloseStore");
		return 1;
	}
	ErrorString = (errorstring)GetProcAddress(lib, "ErrorString");
	if (!ErrorString)
	{
		printf("error load function - %s", "ErrorString");
		return 1;
	}
	GetMYCertificate = (getmycertificate)GetProcAddress(lib, "GetMYCertificate");
	if (!GetMYCertificate)
	{
		printf("error load function - %s", "GetMYCertificate");
		return 1;
	}
	GetCertificateContext = (getcertificatecontext)GetProcAddress(lib, "GetCertificateContext");
	if (!GetCertificateContext)
	{
		printf("error load function - %s", "GetCertificateContext");
		return 1;
	}
	MessageName = (messagename)GetProcAddress(lib, "MessageName");
	if (!MessageName)
	{
		printf("error load function - %s", "MessageName");
		return 1;
	}
	CrtSetStorePropertyExt = (crtsetstorepropertyext)GetProcAddress(lib, "CrtSetStorePropertyExt");
	if (!CrtSetStorePropertyExt)
	{
		printf("error load function - %s", "CrtSetStorePropertyExt");
		return 1;
	}
	CrptMsgClose = (crptmsgclose)GetProcAddress(lib, "CrptMsgClose");
	if (!CrptMsgClose)
	{
		printf("error load function - %s", "CrptMsgClose");
		return 1;
	}
	BaseToBin = (basetobin)GetProcAddress(lib, "BaseToBin");
	if (!BaseToBin)
	{
		printf("error load function - %s", "BaseToBin");
		return 1;
	}
	GetMessageCertificate = (getmessagecertificate)GetProcAddress(lib, "GetMessageCertificate");
	if (!GetMessageCertificate)
	{
		printf("error load function - %s", "GetMessageCertificate");
		return 1;
	}
	DecryptMessage = (decryptmessage)GetProcAddress(lib, "DecryptMessage");
	if (!DecryptMessage)
	{
		printf("error load function - %s", "DecryptMessage");
		return 1;
	}
	MessageType = (messagetype)GetProcAddress(lib, "MessageType");
	if (!MessageType)
	{
		printf("error load function - %s", "MessageType");
		return 1;
	}
	FreeCertificateCollection = (freecertificatecollection)GetProcAddress(lib, "FreeCertificateCollection");
	if (!FreeCertificateCollection)
	{
		printf("error load function - %s", "FreeCertificateCollection");
		return 1;
	}
	SignMsgClose = (signmsgclose)GetProcAddress(lib, "SignMsgClose");
	if (!SignMsgClose)
	{
		printf("error load function - %s", "SignMsgClose");
		return 1;
	}
	SignMsgOpen = (signmsgopen)GetProcAddress(lib, "SignMsgOpen");
	if (!SignMsgOpen)
	{
		printf("error load function - %s", "SignMsgOpen");
		return 1;
	};
	SetLDAPver = (setldapver)GetProcAddress(lib, "SetLDAPver");
	if (!SetLDAPver)
	{
		printf("error load function - %s", "SetLDAPver");
		return 1;
	}
	return 0;
}
//-----------------------------------------------------------------------
int file_read(char* file, unsigned char **data, long *size);
//-----------------------------------------------------------------------
int file_write(char* file, unsigned char *data, long size);
//-----------------------------------------------------------------------
int main(int argc, char **argv)
{
	HINSTANCE lib = NULL;
	char host[16];
	char port[6];
	char profile[256];
	char Time[16];
	char fileCMS[260];
	char *SenderName = NULL;
	char *timeToCrypted = NULL;
	char *dataDescriptor = NULL;
	char **senderName = NULL;
	char **recipName = NULL;
	int  senderCount = 0;
	int  recipCount = 0;
	long cmcSize = 0;
	int  retCode = 0;
	int type = 0;
	int j = 0;
	unsigned char *dataToDecrypted = NULL;
	unsigned long dataSize = 0;
	unsigned char *cmcBlob = NULL;
	unsigned char *cmsBlob = NULL;
	unsigned long sizeBlob = 0;
	unsigned char *crtCrypt = NULL;
	unsigned long sizeCrt = 0;
	hCERTSTORE      hStore = 0;
	HCRYPTPROV      hProv = 0;
	hCERTCONTEXT    hContext = 0;
	hCRYPTMSG       hCrypt = 0;
	hCERTCOLLECTION hCollection = 0;

	if (argc<2)
	{
		printf("Specify parameters (ex.[]$ ./decrypt /path/CMS-filename profile host port)\n");
		return 1;
	}
	if (argc == 5)
	{
		if (argv[2])
		{
#ifdef _WIN32
			sprintf(profile, "profile://%s\0", argv[2]);
#else
			sprintf(profile, "profile://%s\0", argv[2]);
#endif
		}
		else
			return 1;
		if (argv[3])
		{
			strcpy(host, argv[3]);
		}
		else
			return 1;
		if (argv[4])
		{
			strcpy(port, argv[4]);
		}
		else
			return 1;
	}
	else
	{
		printf("Specify parameters (ex.[]$ ./decrypt /path/CMS-filename profile host port)\n");
		return 1;
	}
	try
	{
		lib = load_lib(strlib);
		if (get_func(lib))
		{
			printf("error load func.\n");
			exit(1);
		}
		// Открыть зашифрованный файл для чтения 
		strcpy(fileCMS, argv[1]);
		printf("Open sign file : %s\n", fileCMS);
		retCode = file_read(fileCMS, &cmsBlob, (long *)&sizeBlob);
		if (retCode != 0)
		{
			printf("Open file %s Error\n", fileCMS);
			throw retCode;
		}
		// Если сообщение в формате BASE64 , то сконвертировать в ASN1
		if (cmsBlob[0] == 'M')
		{
			if (cmcBlob)
			{
				delete[] cmcBlob;
			}
			cmcBlob = new unsigned char[MAX_BASE_DENCODED_SIZE(sizeBlob)];
			BaseToBin(cmsBlob, sizeBlob, cmcBlob, &cmcSize);
			memcpy(cmsBlob, cmcBlob, cmcSize);
			sizeBlob = cmcSize;
		}
		// Открыть контекст криптопровайдера с загрузкой ключей
		printf("Open CSP Context\n");
		retCode = CSPOpenContext(&hProv, (char *)profile, 25, 0, 0, NULL, 0, NULL, NULL, NULL);
		if (retCode != 0)
		{
			printf("%s  [CSPOpenContext - %d]\n", ErrorString(retCode), retCode);
			throw retCode;
		}
		// Открыть хранилище сертификатов
		printf("Open Store L\n");
		retCode = CrtOpenStore(0, hProv, MORE_ONE_CA, false, NULL, &hStore);
		if (retCode != 0)
		{
			printf("%s  [CrtOpenStore - %d]\n", ErrorString(retCode), retCode);
			throw retCode;
		}
		// Установить версию протокола LDAP
		SetLDAPver(hStore, 3);
		// Установить параметры хранилища
		printf("Set Store properties\n");
		retCode = CrtSetStorePropertyExt(hStore, MORE_ONE_CA, "t=L;", host, port, 30, NULL, NULL);
		if (retCode != 0)
		{
			printf("%s  [CrtSetStorePropertyExt - %d]\n", ErrorString(retCode), retCode);
			throw retCode;
		}
		printf("Get own certificate\n");
		// Получить размер для данных собственного сертификата
		// перед вызовом функции обязательно вызывать CrtSetStorePropertyExt, т.к. GetMYCertificate обращается к хранилищю LDAP, но
		// если собственный сертификат будет загружен из файла, то можно сразу вызывать MessageName
		retCode = GetMYCertificate(hStore, "c=KZ", AT_KEYEXCHANGE, false, NULL, &sizeCrt);
		if (retCode != 0)
		{
			printf("%s  [GetMYCertificate - %d]\n", ErrorString(retCode), retCode);
			throw retCode;
		}
		crtCrypt = new unsigned char[sizeCrt];
		// Получить собственный сертификат для ключевого обмена
		retCode = GetMYCertificate(hStore, "c=KZ", AT_KEYEXCHANGE, false, crtCrypt, &sizeCrt);
		if (retCode != 0)
		{
			printf("%s  [GetMYCertificate - %d]\n", ErrorString(retCode), retCode);
			throw retCode;
		}
		// Получить контекст сертификата
		printf("Get Certificate Context\n");
		retCode = GetCertificateContext(hStore, crtCrypt, sizeCrt, &hContext);
		if (retCode)
		{
			printf("%s [GetCertificateContext: error = %d]\n", ErrorString(retCode), retCode);
			throw retCode;
		}
		// Получить информацию о количестве отправителей/получателей из CMS сообщения 
		printf("Get sender/recipient info\n");
		retCode = MessageName(cmsBlob, sizeBlob, &senderCount, NULL, &recipCount, NULL);
		if (retCode)
		{
			printf("%s [MessageName: error = %d]\n", ErrorString(retCode), retCode);
			throw retCode;
		}
		if (senderCount)
		{
			senderName = new char *[senderCount];
			for (j = 0; j<senderCount; j++)
			{
				senderName[j] = new char[260];
			}
		}
		if (recipCount)
		{
			recipName = new char *[recipCount];
			for (j = 0; j<recipCount; j++)
			{
				recipName[j] = new char[260];
			}
		}
		// Получить информацию об отправителях/получателях из CMS
		retCode = MessageName(cmsBlob, sizeBlob, &senderCount, senderName, &recipCount, recipName);
		if (retCode)
		{
			printf("%s [MessageName: error = %d]\n", ErrorString(retCode), retCode);
			throw retCode;
		}
		for (j = 0; j<senderCount; j++)
		{
			printf("Sender Name - %s\n", senderName[j]);
		}
		for (j = 0; j<recipCount; j++)
		{
			printf("Recip Name - %s\n", recipName[j]);
		}
		timeToCrypted = new char[16];
		dataDescriptor = new char[260];
		// Получить хендл на коллекцию сертификатов
		printf("Get cerificate collection\n");
		retCode = GetMessageCertificate(hStore, cmsBlob, sizeBlob, &hCollection);
		if (retCode != 0)
		{
			printf("%s  [GetMessageCertificate - %d]\n", ErrorString(retCode), retCode);
			throw retCode;
		}
		printf("Open Crypt context\n");
		retCode = SignMsgOpen(hProv, 0, &hCrypt);
		if (retCode != 0)
		{
			printf("%s  [GetMessageCertificate - %d]\n", ErrorString(retCode), retCode);
			throw retCode;
		}
		// Получить размер зашифрованных данных
		SenderName = new char[260];
		retCode = DecryptMessage(hCrypt, hCollection, hContext, true, cmsBlob, sizeBlob, SenderName, timeToCrypted, dataDescriptor, NULL, &dataSize);
		if (retCode != 0)
		{
			printf("%s  [DecryptMessage - %d]\n", ErrorString(retCode), retCode);
			throw retCode;
		}
		printf("DecryptMessage : DataDescriptor = %s \n", dataDescriptor);
		dataToDecrypted = new unsigned char[dataSize];
		memset(dataToDecrypted, 0, dataSize);
		printf("Decrypt Message\n");



		// Расшифровывать сообщение
		retCode = DecryptMessage(hCrypt, hCollection, hContext, true, cmsBlob, sizeBlob, SenderName, timeToCrypted, dataDescriptor, dataToDecrypted, &dataSize);
		if (retCode != 0)
		{
			printf("%s  [DecryptMessage - %d]\n", ErrorString(retCode), retCode);
			throw retCode;
		}
		else
		{
			if (dataSize)
			{
				if (dataToDecrypted[0] == 'M')
				{
					if (cmcBlob)
					{
						delete[] cmcBlob;
					}
					cmcBlob = new unsigned char[MAX_BASE_DENCODED_SIZE(dataSize)];
					BaseToBin(dataToDecrypted, dataSize, cmcBlob, &cmcSize);
					if (dataToDecrypted) delete[] dataToDecrypted;
					dataToDecrypted = new unsigned char[cmcSize];
					memcpy(dataToDecrypted, cmcBlob, cmcSize);
					dataSize = cmcSize;
				}
				file_write(fileCMS, dataToDecrypted, dataSize);
			}
		}
		printf("User encrypted[%d] - %s, timeToCrypted[%d] - %s\n", j, SenderName, j, timeToCrypted);
	}
	catch (int err)
	{
		printf("catch ... Error - %d\n", err);
	}
	// Освободить все хенлдлы
	if (hCollection) FreeCertificateCollection(&hCollection);
	if (hCrypt) SignMsgClose(&hCrypt);
	if (hStore) CrtCloseStore(&hStore);
	if (hProv) CSPCloseContext(&hProv);
	//Очистить память
	if (dataDescriptor)
	{
		delete[] dataDescriptor;
	}
	if (timeToCrypted)
	{
		delete[] timeToCrypted;
	}
	if (SenderName)
	{
		delete[] SenderName;
	}
	if (senderName)
	{
		for (j = 0; j<senderCount; j++)
		{
			delete[] senderName[j];
		}
		delete[] senderName;
	}
	if (recipName)
	{
		for (j = 0; j<recipCount; j++)
		{
			delete[] recipName[j];
		}
		delete[] recipName;
	}
	if (dataToDecrypted)
	{
		delete[] dataToDecrypted;
	}
	if (crtCrypt)
	{
		delete[] crtCrypt;
	}
	if (cmsBlob)
	{
		delete[] cmsBlob;
	}
	if (cmcBlob)
	{
		delete[] cmcBlob;
	}
	if (lib) lib_free(lib);
	return 0;
}
//-----------------------------------------------------------------------
int file_read(char* file, unsigned char **data, long *size)
{
	FILE *in = NULL;
	struct stat  st;
	if (stat(file, &st)) return 1; // Файл не найден ... 
	*size = st.st_size;
	if ((in = fopen(file, "rb")) == NULL)
	{
		*size = 0;
		*data = NULL;
		return 2;  // Файл не открыт ... 
	}
	*data = new unsigned char[st.st_size];
	fread(*data, *size, 1, in);
	fclose(in);
	return 0;
}
//-----------------------------------------------------------------------
int file_write(char* file, unsigned char *data, long size)
{
	FILE * in = NULL;
	if ((in = fopen(file, "wb")) != NULL)
	{
		if (fwrite(data, sizeof(unsigned char), size, in))
			return 2; // Данные в файл не были записаны...
	}
	else
		return 1; // Файл не открыт ...
	fclose(in);
	return 0;
}
