/*
 * Copyright(C) 2000-2010 Проект ИОК
 *
 * Этот файл содержит информацию, являющуюся
 * собственностью компании Крипто Про.
 *
 * Любая часть этого файла не может быть скопирована,
 * исправлена, переведена на другие языки,
 * локализована или модифицирована любым способом,
 * откомпилирована, передана по сети с или на
 * любую компьютерную систему без предварительного
 * заключения соглашения с компанией Крипто Про.
 *
 * Программный код, содержащийся в этом файле, предназначен
 * исключительно для целей обучения и не может быть использован
 * для защиты информации.
 *
 * Компания Крипто-Про не несет никакой
 * ответственности за функционирование этого кода.
 */

//--------------------------------------------------------------------
// Пример кода для отправки запроса на сертификат на удостоверяющий 
// центр КриптоПро 1.5

#include <cstdio>
#include <cstring>

#include "UnixRequest.h"
#include "UnixEnroll.h"
#include "WinCryptEx.h"
#include "CPCA15UserInfo.h"
#include <time.h> 
#include"SecureBuffer.h"
#ifdef _WIN32
#  ifdef _DEBUG
#    pragma comment(lib, "comsuppwd.lib")
#  else
#    pragma comment(lib, "comsuppw.lib")
#  endif
#  define CONTAINER L"\\\\.\\REGISTRY\\CPCA15test"
#else
#  define CONTAINER L"\\\\.\\HDIMAGE\\CPCA15test"
#endif
#include "compiler_attributes.h"

//Имя файла для сохранения
#define CERFILE "CPCA15test.cer"
#define _UN _empty_union_.

class Callbacks: public UnixEnroll::UserCallbacks
{
public:
	bool askPermissionToAddToRootStore( const BYTE* pbCert, DWORD cbCert, bool force = false) const;
	UserCallbacks* clone() const;
	virtual ~Callbacks() {}
};

//--------------------------------------------------------------------
// Функция askPermissionToAddToRootStore всегда будет разрешать 
// записывать сертификат в корневое хранилище

bool Callbacks::askPermissionToAddToRootStore( const BYTE* pbCert, DWORD cbCert, bool) const
{
  	int res;
	DWORD size = 0;
	PBYTE pHashValue = NULL;
	res = CryptHashCertificate(0,0,0,pbCert,cbCert,NULL,&size);
	if(!res) {
	    return false;
	}
	
	pHashValue =(BYTE *)malloc(size);
	if(!pHashValue) {
	    return false;
	}
	
	res = CryptHashCertificate(0,0,0,pbCert,cbCert,pHashValue,&size);
	if(!res) {
	    free(pHashValue);
	    return false;
	}
	
	return true;
}

UnixEnroll::UserCallbacks* Callbacks::clone() const
{
	return new Callbacks();
}

static void ATTR_NORETURN HandleCode(HRESULT err, const char *s)
{
    ::printf("Error number     : 0x%x\n", err);
    ::printf("Error description: %s\n", s);
    if(!err)
	err = 1;
    ::exit(err);
}

static void ATTR_NORETURN HandleError(const char *s)
{
    const HRESULT err = (HRESULT) GetLastError();
    HandleCode(err, s);
}

static long get_templates(CSecurePin* sbPassword,std::string tokenID, std::string UIURL,
				   std::vector<std::string>& templates)
{
	//URFactory - конструктор объекта взаимодействия с УЦ
	UnixRequest * pRequest = UnixRequest::URFactory("CPCA15");
    	
	if (pRequest == 0)
	    return NTE_FAIL;
    	
	HRESULT res = -1;
	BSTR bstrTemplates = 0;
    	
	BSTR UrlOfCA = ConvertStringToBSTR(UIURL.c_str());
	    /* UnixRequest */
	CSecurePin* sp;
	BSTR bstrTokenID = 0;
	BSTR bstrTP = 0;
	BSTR * tmpTemplates = &bstrTemplates;
	VARIANT * variantTmpTemplates = (VARIANT*)tmpTemplates;
	char * chTemplates = 0;
        
	sp = sbPassword;
	bstrTokenID = ConvertStringToBSTR(tokenID.c_str());
        
	// Метод проверки аутентичности TLS соединения при первоначальном контакте с УЦ
	X509EnrollmentCheckChainFlags chainFlags = X509CC_TLS;
        
	// SetCredential устанавливает расширенные параметры метода аутентификации при взаимодействии с УЦ.
	res = pRequest->SetCredential(NULL,X509AuthUsername, chainFlags, bstrTokenID,sp);
        
	if(res != S_OK){
	    SysFreeString(bstrTokenID);
	    SysFreeString(UrlOfCA);
	    return res;
	}

	// Возвращает свойства УЦ. Этот метод функционально идентичен методу 
	// ICertAdmin2::GetCAProperty Microsoft CryptoAPI

	res = pRequest->GetCAProperty(UrlOfCA, CR_PROP_TEMPLATES, 0, PROPTYPE_STRING, 0 , variantTmpTemplates);

	if(res != S_OK){
	    SysFreeString(bstrTokenID);
	    SysFreeString(UrlOfCA);
	    return res;
	}
        
        
	    chTemplates = ConvertBSTRToString(bstrTemplates) ;
	    std::string strTemplates(chTemplates );
	    printf("Templates:\n%s\n",strTemplates.c_str());

	    std::string separator = "\n";
	    size_t counter = 1;
	    size_t found = strTemplates.find_first_of(separator);
	    while(found != std::string::npos){
	        if(found > 0 && counter%2 ){
		    templates.push_back(strTemplates.substr(0,found));
	        }
	        strTemplates = strTemplates.substr(found+1);
	        found = strTemplates.find_first_of(separator);
	        ++counter;
	    }
	    if(strTemplates.length() > 0 && counter%2){
	        templates.push_back(strTemplates);
	    }    
	if(bstrTemplates) SysFreeString(bstrTemplates);
	if(UrlOfCA) SysFreeString(UrlOfCA);
	if(bstrTokenID) SysFreeString(bstrTokenID);
	if(bstrTP) SysFreeString(bstrTP);
	delete [] chTemplates;
	delete pRequest;
	return S_OK;
}

static CPCA15UserInfo *get_reg_info(std::string UIURL)
{
	UnixRequest * pRequest = UnixRequest::URFactory("CPCA15");
	X509EnrollmentCheckChainFlags chainFlags = X509CC_TLS;
       
	long res = pRequest->SetCredential(NULL, X509AuthAnonymous, chainFlags, NULL, NULL);
	if (res != S_OK)
	    return NULL;
	BSTR UrlOfCA = ConvertStringToBSTR(UIURL.c_str());
	CPCA15UserInfo *ui = new CPCA15UserInfo;
	// GetUserRegisterInfo получает данные формы регистрации пользователя.
	res = pRequest->GetUserRegisterInfo(UrlOfCA, ui);
	SysFreeString(UrlOfCA);
	delete pRequest;
	return ui;
}

static long register_user(std::string& UIURL, CPCA15UserInfo& ui)
{
	UnixRequest * pRequest = UnixRequest::URFactory("CPCA15");
	X509EnrollmentCheckChainFlags chainFlags = X509CC_TLS;
	long res = pRequest->SetCredential(NULL, X509AuthAnonymous, chainFlags, NULL, NULL);
	if (res != S_OK)
	    return res;

	const BSTR UrlOfCA = ConvertStringToBSTR(&UIURL[0]);
	printf("%d\n", (int)ui.fields.size());
	// регистрация пользователя
	res = pRequest->RegisterUser(UrlOfCA, &ui);
	delete pRequest;
	SysFreeString(UrlOfCA);
	return res;
}

static void create_UPN_extension(BSTR &value, BSTR &oid)
{
    CERT_NAME_VALUE  OtherNameInfo;
    CERT_ALT_NAME_ENTRY AltNames = { 0 };
    CERT_ALT_NAME_INFO  AltNameInfo = { 0 };
    CERT_OTHER_NAME otherName;
    LPCWSTR pwnzUPN = L"test@cryptopro.ru";
    DWORD size = 0;

    //Заносим строчку в CERT_NAME_VALUE и кодируем для последующей вставки в CERT_OTHER_NAME
    OtherNameInfo.dwValueType = CERT_RDN_BMP_STRING;
    OtherNameInfo.Value.cbData = (DWORD)wcslen(pwnzUPN)*sizeof(wchar_t);
    OtherNameInfo.Value.pbData = (LPBYTE)pwnzUPN;
    BOOL bRet = CryptEncodeObject(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
	X509_ANY_STRING, &OtherNameInfo, 0, &size);
    if (!bRet) {
	HandleError("Error in extension (CryptEncodeObject OtherNameInfo).");
    }
    std::vector<BYTE> encodedUPNSeq(size);
    bRet = CryptEncodeObject(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
	X509_ANY_STRING, &OtherNameInfo, &encodedUPNSeq[0], &size);
    if (!bRet) {
	HandleError("Error in extension (CryptEncodeObject OtherNameInfo).");
    }
    //Готовим структуру CERT_ALT_NAME_INFO для вставки в CERT_EXTENSION
    AltNames.dwAltNameChoice = CERT_ALT_NAME_OTHER_NAME;
    otherName.Value.cbData = encodedUPNSeq.size();
    otherName.Value.pbData = &encodedUPNSeq[0];
    otherName.pszObjId = (LPSTR)szOID_NT_PRINCIPAL_NAME;
    AltNames._UN pOtherName = &otherName;

    AltNameInfo.cAltEntry = 1;
    AltNameInfo.rgAltEntry = &AltNames;

    bRet = CryptEncodeObject(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
	X509_ALTERNATE_NAME, &AltNameInfo, 0, &size);
    if (!bRet) {
	HandleError("Error in extension (CryptEncodeObject AltNameInfo).");
    }
    std::vector<BYTE> encodedNameSeq(size);
    bRet = CryptEncodeObject(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
	X509_ALTERNATE_NAME, &AltNameInfo, &encodedNameSeq[0], &size);
    if (!bRet) {
	HandleError("Error in extension (CryptEncodeObject AltNameInfo).");
    }
    //Пишем всё в CERT_EXTENSION, кодируем в base 64
    CERT_EXTENSION AltnameExtension;
    AltnameExtension.fCritical = 0;
    AltnameExtension.pszObjId = const_cast<LPSTR>(szOID_SUBJECT_ALT_NAME2);
    AltnameExtension.Value.pbData = &encodedNameSeq[0];
    AltnameExtension.Value.cbData = encodedNameSeq.size();
    
    if (!CryptBinaryToStringA(AltnameExtension.Value.pbData, AltnameExtension.Value.cbData, 
    CRYPT_STRING_BASE64, 0, &size))
        HandleError("Error in extension (CryptBinaryToStringA).");
    std::vector<char> b64(size + 1);
    if (!CryptBinaryToStringA(AltnameExtension.Value.pbData, AltnameExtension.Value.cbData, 
			      CRYPT_STRING_BASE64, &b64[0], &size))
        HandleError("Error in extension (CryptBinaryToStringA).");
    
    b64[size] = '\0';

    //Готовим value и oid для функции addExtensionToRequest
    value = ConvertStringToBSTR(&b64[0]);
    oid = ConvertStringToBSTR(AltnameExtension.pszObjId);
}

static char* create_request(BSTR bstrRDN, BSTR bstrEKUsage, BSTR contName)
{
	HRESULT res = -1;
	Callbacks tb;
    	
	DWORD dwKeySpec = AT_KEYEXCHANGE;
	UnixEnroll * pEnroll = new UnixEnroll(tb, false);

	HRESULT hr = 0;
	BSTR value, oid;
	create_UPN_extension(value, oid);
	// put_ContainerName создает имя контейнера
	pEnroll->put_ContainerName(contName);
        //задаём дефолтный пин "1" на контейнер 
	std::string PIN = "1";
	CSecurePin pin(PIN.length() + 1);
	memcpy(pin.ptr_rw(), PIN.c_str(), PIN.length() + 1);
	hr = pEnroll->put_PIN(pin);
	if (hr) {
	    HandleCode(hr, "Error in put_Pin.");
	}
	pin.clean();	
	// put_LimitExchangeKeyToEncipherment задает ограничения ключа AT_KEYEXCHANGE, false
	// и шифрование, и подпись, true - только шифрование
	pEnroll->put_LimitExchangeKeyToEncipherment(TRUE);
	// put_KeySpec задает тип закрытого ключа
	pEnroll->put_KeySpec(dwKeySpec);
	// put_ProviderType задает тип CSP для создания ключа.
	pEnroll->put_ProviderType(PROV_GOST_2001_DH);
	// put_RequestStoreFlags задаеь хранилище запроса и флаги его открытия.
	pEnroll->put_RequestStoreFlags(CERT_SYSTEM_STORE_CURRENT_USER);
	hr = pEnroll->addExtensionToRequest(0, oid, value);
	if (hr) {
	    HandleCode(hr, "Error in addExtensionToRequest.");
	}
	SysFreeString(oid);
	SysFreeString(value);

	// происходит создание запроса функцией createRequest
	BSTR strRequest;
	res = pEnroll->createRequest(XECR_PKCS10_V2_0, bstrRDN, bstrEKUsage, &strRequest);
	if(res != S_OK){
	    HandleCode(res, "Error createRequest.");
	} 
	char *chTmp = ConvertBSTRToString(strRequest);
	printf("charRequest = %s\n", chTmp);

	SysFreeString(strRequest);
	delete pEnroll;
	return chTmp;
}

int main(int argc, char* argv[])
{
	X509EnrollmentCheckChainFlags chainFlags = X509CC_TLS;
	std::vector<std::string> ::iterator it;
	std::vector<std::string> templates;
	std::vector<UnixRequest::AttrTriple> vTrip;
	std::string UIURL;
	std :: string s;
	
	HRESULT res;
	DWORD dwKeySpec;
	LONG Flags = CR_IN_PKCS10;
	LONG pDisposition;
	UnixRequest * pRequest = 0;
	pRequest = UnixRequest::URFactory("CPCA15");
	if (pRequest == 0)
	    return NTE_FAIL;
	
	BSTR UrlOfCA;
	BSTR bstrRDN = 0;
	BSTR bstrEKUsage = 0;
	BSTR strRequest = 0;
	BSTR bstrAttributes = 0;
	BSTR strConfig;
        //если дали ключ -serv с именем сервера, выбираем этот сервер, иначе
        //выбираем дефолнтый cryptopro.ru
	if (argc == 3 && !strcmp(argv[1], "-serv"))
	{
	    //WideCharToMultiByte(CP_UTF8, 0, (src), (int)(-1 /*len*/), (dest), (int)(len), NULL, NULL)
	    const WCHAR *pwcsName;
	    // required size
	    int nChars = MultiByteToWideChar(CP_UTF8, 0, argv[2], -1, NULL, 0);
	    // allocate it
	    pwcsName = new WCHAR[nChars];
	    MultiByteToWideChar(CP_UTF8, 0, argv[2], -1, (LPWSTR)pwcsName, nChars);
	    // use it....
	    strConfig = SysAllocString(pwcsName);
	    UIURL = argv[2];
	    // delete it
	    delete[] pwcsName;
	} else {
	    strConfig = SysAllocString(L"https://cryptopro.ru:5555/ui");
	    UIURL = "https://cryptopro.ru:5555/ui";
	}
	BSTR bstrTokenID = 0;
	BSTR contName = SysAllocString(CONTAINER);
	BSTR bstrTmplName = 0;
	FILE * fp = NULL;

	UrlOfCA = ConvertStringToBSTR(UIURL.c_str());
	
	// получили Id и password пользователя
	CPCA15UserInfo* pui = get_reg_info(UIURL);
	if(pui == NULL)
	    HandleError("Error getting information.");
	printf("successful getting information. \n");
	
//-----------------------------------------------------------------------------------------//
// заполняем обязательные поля случайным именем
	{
	    HCRYPTPROV hProv = 0;
	    if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_GOST_2012_256, CRYPT_VERIFYCONTEXT)) {
		HandleError("Error during CryptAcquireContext.");
	    }
	    unsigned seed;
	    BOOL bRet = CryptGenRandom(hProv, sizeof(seed), (BYTE *)&seed);
	    if (hProv) {
		CryptReleaseContext(hProv, 0);
	    }
	    if (!bRet) {
		HandleError("Random bytes were not correctly generated.");
	    }
	    srand(seed);
	}
	for(CPCA15UserFields::iterator it = pui->fields.begin(); it != pui->fields.end(); it++) {
	    if(it->mandatory == true) {
		int flag;
		for (int i = 1; i <= 9; i++) {
		    flag = rand()%2;
		    if (flag)
			it->value += ('a'+rand()%26);
		    else
			it->value += ('0'+rand()%10);
		}
		s = it->value;
	    }
	}
	printf("name = %s\n",s.c_str());

//-----------------------------------------------------------------------------------------//
// регистарация пользователя и создание шаблонов
	res = register_user(UIURL, *pui);
	if (res != S_OK) {
	    HandleCode(res, "Error register.");
	}
	printf("successful register. \n");

	res = get_templates(pui->sbPassword, pui->TokenID, UIURL, templates);
	if (res != S_OK) {
	    HandleCode(res, "Error getting tamplates.");
	}
	printf("successful getting tamplates. \n");

	it = templates.begin();
	bstrTmplName = ConvertStringToBSTR((*it).c_str());
	printf("template = %s \n", (*it).c_str());

//-----------------------------------------------------------------------------------------//
// установка параметров метода аутентификации при взаимодействии с УЦ и создание запроса

	bstrTokenID = ConvertStringToBSTR(pui->TokenID.c_str());
	res = pRequest->SetCredential(NULL, X509AuthUsername, chainFlags, bstrTokenID, pui->sbPassword);
	delete pui;
	if (res != S_OK) {
	    HandleCode(res, "Error SetCredential.");
	}
	printf("successful SetCredential. \n");

	res = pRequest->GetRequestParams(UrlOfCA,bstrTmplName,&bstrRDN, &bstrEKUsage,&dwKeySpec,&vTrip);
	if (res != S_OK) {
	    HandleCode(res, "Error GetRequestParams.");
	}
	printf("successful GetRequestParams. \n");

	char* charRequest = create_request(bstrRDN, bstrEKUsage, contName);
	if(charRequest == NULL)
	    HandleError("Error create_request.");
	printf("create_request. \n");

//-----------------------------------------------------------------------------------------//
// отправка и обработка результата запроса
	strRequest = ConvertStringToBSTR(charRequest);
	printf("charRequest = %s\n",charRequest);
	delete [] charRequest;

	res = pRequest->Submit(Flags, strRequest, bstrAttributes, strConfig, &pDisposition);
	if (res != S_OK) {
	    HandleCode(res, "Error submit.");
	}

	switch (pDisposition) {
	case CR_DISP_DENIED:
	    printf("request was denied. \n");
	    break;

	case CR_DISP_ERROR:
	    printf("error request. \n");
	    break;

	case CR_DISP_INCOMPLETE:
	    printf("request was incompleted\n");
	    break;

	case CR_DISP_ISSUED:
	{
	    printf("certificate was issued. \n");
	    BSTR pMyCert1=NULL;
	    res=pRequest->GetCertificate(CR_OUT_BASE64HEADER,&pMyCert1);
	    if (res!= S_OK)
		return 2;
	    //пишем сертификат в дефолтный файл
	    fp = fopen(CERFILE, "wb");
	    if (fp == NULL)
	    {
		HandleError("Error while openning file.");
	    }
	    char *pCertStr1 = ConvertBSTRToString(pMyCert1);
	    fputs(pCertStr1, fp);
	    fclose(fp);

	    //и заодно выводим его на экран
	    printf("Cert:\n%s",pCertStr1);

	    delete [] pCertStr1;
	    SysFreeString(pMyCert1);
	}
	break;

	case CR_DISP_ISSUED_OUT_OF_BAND:
	//Сертификат выдан (будет выдан) непосредственно на УЦ, а не по сети
	    printf("certificate was issued out of band. \n");
	    break;

	case CR_DISP_UNDER_SUBMISSION:
	    printf("certificate is under submission. \n");
	    break;	
	default:
	    printf("default. \n");
	    break;
	}

	printf("Successful submitting. \n");

	SysFreeString(contName);
	SysFreeString(strConfig);
	SysFreeString(UrlOfCA);
	SysFreeString(strRequest);
	SysFreeString(bstrEKUsage);
	SysFreeString(bstrRDN);
	SysFreeString(bstrAttributes);
	SysFreeString(bstrTokenID);
	SysFreeString(bstrTmplName);
	delete pRequest;
	if (res == S_OK)
	    return 0;
	else
	    return 1;
}
