#include <stdio.h>
#ifdef _WIN32
#   include <windows.h>
#   include <wincrypt.h>
#   include <tchar.h>
#else
#   include <string.h>
#   include <stdlib.h>
#   include <CSP_WinDef.h>
#   include <CSP_WinCrypt.h>
#   include "reader/tchar.h"
#endif
#include <WinCryptEx.h>

// Начало примера (не следует удалять данный комментарий, он используется 
// для автоматической сборки документации)
//--------------------------------------------------------------------
// Данный пример демонстрирует импорт закрытого ключа из блоба.
// Блоб импортируемого закрытого Ключа хранится в файле EncryptedKeyBlob.enc.
// Для корректной работы этого примера передаваемые ему входные данные
// должны являться результатом работы примера EncryptKey.
// Для корректной расшифровки импортируемого блоба закрытого ключа
// необходимы вектор инициализации и соль, которые хранятся
// в файлах "vector.bin" и "salt.bin".
// Следует сделить за тем, чтобы контейнеры "Sender" и "Responder", 
// используемые в программах ExportInFile, EncryptKey и DecryptKey на момент
// выполнения этих программ существовали.
// Замечание: под win32 рекомендуется использовать _s аналоги CRT функций.
//--------------------------------------------------------------------

#ifdef _WIN32
#   define CONTAINER_COPY _TEXT("\\\\.\\registry\\SenderCopy")
#else
#   define CONTAINER_COPY _TEXT("\\\\.\\hdimage\\SenderCopy")
#endif
#define CONTAINER_COPY_PIN "111"

#pragma region vars

#define BLOCK_LENGTH 4096
#define SALT_LENGTH 16

static void HandleError(const char *s);
static void CleanUp(void);


static HCRYPTPROV hProv = 0;		// Дескриптор CSP для получения переданного ключа 
static HCRYPTKEY hKey = 0;		// Дескриптор переданного закрытого Ключа отправителя AT_EXCHANGE
static HCRYPTKEY hDerivedKey = 0;	// Дескриптор сессионного ключа в hProv
static HCRYPTHASH hHash = 0;		// Дескриптор хэша пароля

static FILE *vectorf=NULL;		// Файл для хранения вектора инициализации
static FILE *saltf = NULL;		// Файл для хранения соли
static FILE *EncryptedKeyBlob = NULL;   // Файл блоба зашифрованного закрытого Ключа

static CRYPT_DATA_BLOB salt = {0, NULL}; // Структура CRYPT_DATA_BLOB для хранения соли


//#define PROVIDER_NAME NULL
//#define PROVIDER_NAME "CP2001KC1"

#pragma endregion


int main(void)
{
    BYTE *pbKeyBlobExport = NULL;    // Указатель на  BLOB переданного закрытого Ключа

    DWORD dwKeyBlobExportLen;   // Длина блоба переданного закрытого Ключа
    
    BYTE pbIV[100];		// Вектор инициализации сессионного ключа
    DWORD dwIV = 0;		// Длина вектора инициализации

    DWORD szf;
    BOOL bContExists = FALSE;

    ALG_ID ke_alg = CALG_PRO12_EXPORT; // Алгоритм сессионного ключа

    char password[] = "password";   // Пароль
    CRYPT_DATA_BLOB password_blob;  // Структура CRYPT_DATA_BLOB для хранения пароля
    password_blob.pbData = (BYTE*)password;
    password_blob.cbData = (DWORD)strlen(password);
    
    // Выделение памяти под соль.
    salt.cbData = SALT_LENGTH;
    salt.pbData = (BYTE*)malloc(salt.cbData);
    if (!salt.pbData)
	HandleError("Out of memory.");

    // Открытие файла, в котором хранится соль.
    if(!(saltf = fopen("salt.bin", "r+b")))
	HandleError( "Problem opening file 'salt.bin'\n" );
    printf( "File 'salt.bin' was opened\n" );

    // Чтение соли из файла.
    salt.cbData = (DWORD)fread(salt.pbData, 1, salt.cbData, saltf);
    if(!salt.cbData)
	HandleError( "Failed to read salt from 'salt.bin'\n" );
    printf( "Salt was read from 'salt.bin'\n" );

    // Открытие файла, в котором хранится вектор инициализации.
    if(!(vectorf = fopen("vector.bin", "r+b")))
	HandleError( "Problem opening the file 'vector.bin'\n" );
    printf( "The file 'vector.bin' was opened\n" );

    // Чтение вектора инициализации из файла.
    dwIV = (DWORD)fread(pbIV, 1, 100, vectorf);
    if(!dwIV)
	HandleError( "The IV can not be reading from the 'vector.bin'\n" );
    printf( "The IV was read from the 'vector.bin'\n" );
    
    //Открытие Файла блоба зашифрованного закрытого Ключа
    if(!(EncryptedKeyBlob = fopen("EncryptedKeyBlob.enc", "r+b")))
	HandleError( "Problem opening the file 'EncryptedKeyBlob.enc'\n" );
    printf( "The file 'EncryptedKeyBlob.enc' was opened\n" );

    //------------------------------------------------------------------
    // получение зашифрованного закрытого Ключа
    //------------------------------------------------------------------
    // находим размер файла EncryptedKeyBlob.enc, записываем его в szfile
    fseek(EncryptedKeyBlob, 0, SEEK_END);
    szf = ftell(EncryptedKeyBlob);
    fseek(EncryptedKeyBlob, 0, SEEK_SET);
    
    // выделение памяти
    pbKeyBlobExport = (BYTE*)malloc(szf);
    if (!pbKeyBlobExport)
	HandleError("Out of memory.");

    // чтение зашифрованного закрытого Ключа из файла EncryptedKeyBlob.enc 
    dwKeyBlobExportLen = (DWORD)fread(pbKeyBlobExport, 1, szf, EncryptedKeyBlob);
    if(!dwKeyBlobExportLen)
	HandleError( "The private Key can not be reading from the 'EncryptedKeyBlob.enc'\n" );
    printf( "The private Key was read from the 'EncryptedKeyBlob.enc'\n" );


    //------------------------------------------------------------------
    //------------------------------------------------------------------
    // Получение дескриптора контейнера с именем "SenderCopy", 
    // для хранения переданного закрытого Ключа. 
    bContExists = CryptAcquireContext(
	&hProv,
	CONTAINER_COPY,
	NULL,
	PROV_GOST_2012_256,
	CRYPT_SILENT);
    if (bContExists) {
	if (!CryptSetProvParam(
	    hProv,
	    PP_DELETE_KEYSET,
	    NULL,
	    0)) {
	    HandleError("Error during CryptSetProvParam CRYPT_DELETEKEYSET");
	}
	if (!CryptReleaseContext(hProv, 0)) {
	    HandleError("Error during CryptReleaseContext");
	}
    }
    if (!CryptAcquireContext(
	&hProv,
	CONTAINER_COPY,
	NULL,
	PROV_GOST_2012_256,
	CRYPT_NEWKEYSET | CRYPT_SILENT)) {
	HandleError("Error during CryptAcquireContext CRYPT_NEWKEYSET");
    }

    if (!CryptSetProvParam(
	hProv,
	PP_KEYEXCHANGE_PIN,
	(BYTE*)CONTAINER_COPY_PIN,
	0)) {
	HandleError("Failed to set PIN");
    }

    printf("The key container \"SenderCopy\" has been acquired. \n");


    //-----------------------------------------------------------------
    // Хэширование пароля
    //-----------------------------------------------------------------

    // Создание пустого объекта хэширования.
    if (!CryptCreateHash(
	hProv,
	CALG_PBKDF2_2012_512,
	0,
	0,
	&hHash))
    {
	HandleError("CryptCreateHash failed.");
    }

    // Задание соли.
    if (!CryptSetHashParam(
	hHash,
	HP_PBKDF2_SALT,
	(BYTE*)&salt,
	0))
    {
	HandleError("CryptSetHashParam HP_PBKDF2_SALT failed.");
    }

    // Задание строки пароля.
    if (!CryptSetHashParam(
	hHash,
	HP_PBKDF2_PASSWORD,
	(BYTE*)&password_blob,
	0))
    {
	HandleError("CryptSetHashParam HP_PBKDF2_PASSWORD failed.");
    }

    //--------------------------------------------------------------------
    // Создание сессионного ключа, основанного на хэше, полученном из пароля.

    if(CryptDeriveKey(
	hProv,
	CALG_G28147,
	hHash, 
	CRYPT_EXPORTABLE,
	&hDerivedKey)) 
    {
	printf("The key has been derived. \n");
    }
    else
    {
	HandleError("Error during CryptDeriveKey!");
    }

    // Установление PRO12_EXPORT алгоритма сессионного ключа
    if(CryptSetKeyParam(
        hDerivedKey,
        KP_ALGID,
        (LPBYTE)&ke_alg,
        0))
    {
	printf("PRO12_EXPORT agree key algorithm has been set. \n");
    }
    else
    {
	HandleError("Error during CryptSetKeyParam agree key.");
    }

    //-----------------------------------------------------------------
    // Расшифрование закрытого переданного Ключа
    //-----------------------------------------------------------------
    // Установка вектора инициализации - без него первые 8 байт 
    // расшифруются неправильно.
    if(!CryptSetKeyParam(
	hDerivedKey,
	KP_SV,
	pbIV,
	0))
    {
	HandleError("Error during CryptSetKeyParam.");
    }
    printf( "CryptSetKeyParam succeeded. \n");

    // Получение закрытого Ключа импортом зашифрованного закрытого Ключа 
    // на ключе Derived в контейнер
    if(CryptImportKey(
	hProv, 
	pbKeyBlobExport, 
	dwKeyBlobExportLen, 
	hDerivedKey, 
	0, 
	&hKey))
    {
	printf("The private Key has been imported. \n");
    }
    else
    {
	HandleError("Error during CryptImportKey private Key.");
    }


    CleanUp();

    printf("The program ran to completion without error. \n");    

    free(pbKeyBlobExport);


    return 0;

}// Конец main


void CleanUp(void)
{
    if (EncryptedKeyBlob)
	fclose (EncryptedKeyBlob);
    if(vectorf)    
        fclose (vectorf);
    if(saltf)
	fclose(saltf);

    // Уничтожение дескриптора переданного закрытого ключа.
    if(hKey)
	CryptDestroyKey(hKey);

    // Уничтожение дескриптора сессионного ключа.
    if(hDerivedKey)
	CryptDestroyKey(hDerivedKey);

    // Уничтожение дескриптора хэша.
    if(hHash)
	CryptDestroyHash(hHash);

    if (hProv) {
	// Удаление контейнера SenderCopy.
	CryptSetProvParam(
	    hProv,
	    PP_DELETE_KEYSET,
	    NULL,
	    0);

	// Освобождение дескриптора провайдера.
	CryptReleaseContext(hProv, 0);
    }

    free(salt.pbData);
}

// Конец примера 
// (не следует удалять данный комментарий, он используется 
//  для автоматической сборки документации)

//--------------------------------------------------------------------
//  В этом примере используется функция HandleError, функция обработки
//  простых ошибок, для печати сообщения и выхода из программы. 
//  В большинстве приложений эта функция заменяется другой функцией, 
//  которая выводит более полное сообщение об ошибке.

void HandleError(const char *s)
{
    DWORD err = GetLastError();
    printf("Error number     : 0x%x\n", err);
    printf("Error description: %s\n", s);
    CleanUp();
    if(!err) err = 1;
    exit(err);
}
