/*************************************************************************
* Rutoken                                                                *
* Copyright (c) 2003-2017, CJSC Aktiv-Soft. All rights reserved.         *
* Подробная информация:  http://www.rutoken.ru                           *
*------------------------------------------------------------------------*
* Пример работы Рутокен с криптопровайдером КриптоПро CSP                *
* с использованием интерфейса CryptoAPI на языке C                       *
*------------------------------------------------------------------------*
* Электронная подпись данных и проверка подписи ключевой парой,          *
* хранящейся на Рутокен, в тихом режиме:                                 *
*  - проверка наличия криптопровайдера КриптоПро CSP в системе;          *
*  - инициализация криптопровайдера для работы в тихом режиме;           *
*  - получение дружественного FQCN имени контейнера;                     *
*  - установка PIN-кода Рутокен в контекст криптопровайдера;             *
*  - хэширование данных по алгоритму ГОСТ Р 34.11-94 с параметрами       *
*    по умолчанию;                                                       *
*  - подпись хэшированных данных по алгоритму ГОСТ Р 34.10-2001;         *
*  - проверка подписи;                                                   *
*  - освобождение памяти, дескрипторов и контекстов.                     *
*------------------------------------------------------------------------*
* Пример использует объекты, созданные в памяти Рутокен примером         *
* CreateContainer.                                                       *
*************************************************************************/

#include "Common.h"

static BYTE pbData[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
	                     0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
	                     0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
	                     0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07};

int main(void)
{
	LPCWSTR szProvNameW = CRYPTOPRO_PROV_W; // Имя криптопровайдера, заменить на CRYPTOPRO_FKN_PROV_W для ФКН
	LPCSTR szProvNameA = CRYPTOPRO_PROV_A;  // Имя криптопровайдера, заменить на CRYPTOPRO_FKN_PROV_A для ФКН
	DWORD dwProvType = CRYPTOPRO_PROV_TYPE; // Тип криптопровайдера

	HCRYPTPROV hProv = 0;                   // Дескриптор криптопровайдера
	HCRYPTKEY hKey = 0;                     // Дескриптор ключа
	HCRYPTHASH hHash = 0;                   // Дескриптор хэша

	BYTE* pbHash = NULL;                    // Указатель на буфер для хэша
	DWORD dwHashSize = 0;                   // Размер хэша
	BYTE* pbSignature = NULL;               // Указатель на буфер для подписи
	DWORD dwSignatureSize = 0;              // Размер подписи

	BYTE* pbUnicContName = NULL;            // Буфер для FQCN имени контейнера
	DWORD dwUnicNameSize = 0;               // Длина FQCN имени контейнера

	DWORD i = 0;                            // Вспомогательная переменная-счетчик

	for (;; ) {
		/**********************************************************************
		* Шаг 1. Проверка наличия выбранного криптопровайдера в системе       *
		**********************************************************************/

		wprintf(L"Checking whether %s provider exists", szProvNameW);
		if (!CryptAcquireContextW(
		                          &hProv,                 // Указатель на дескриптор криптопровайдера
		                          NULL,                   // Имя ключевого контейнера
		                          szProvNameW,            // Имя криптопровайдера
		                          dwProvType,             // Тип криптопровайдера
		                          CRYPT_VERIFYCONTEXT)) { // Флаг операции, не требующей работы с контейнером
			if (GetLastError() == NTE_KEYSET_NOT_DEF) {
				wprintf(L" -> FAILED \nProvider has not been installed\n");
			} else {
				wprintf(L" -> FAILED, error number: 0x%0.8x\n", GetLastError());
			}
			break;
		}
		wprintf(L" -> OK\n");

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

		/**********************************************************************
		* Шаг 2. Настройка криптопровайдера	для работы в тихом режиме         *
		**********************************************************************/

		/**********************************************************************
		* Шаг 2.1 Инициализация контекста криптопровайдера                    *
		**********************************************************************/
		wprintf(L"Initializing cryptoprovider");
		if (!CryptAcquireContextW(
		                          &hProv,          // Дескриптор криптопровайдера
		                          CONT_NAME_W,     // Имя ключевого контейнера
		                          szProvNameW,     // Имя криптопровайдера
		                          dwProvType,      // Тип криптопровайдера
		                          CRYPT_SILENT)) { // Флаг тихого режима
			if (GetLastError() == NTE_BAD_KEYSET_PARAM || GetLastError() == NTE_BAD_KEYSET) {
				wprintf(L" -> FAILED, container does not exist (0x%0.8x)\n\n", GetLastError());
			} else {
				wprintf(L" -> FAILED, error number: 0x%0.8x\n\n", GetLastError());
			}
			break;
		}
		wprintf(L" -> OK\n");

		/**********************************************************************
		* Шаг 2.2 Получение дружественного FQCN имени контейнера              *
		**********************************************************************/
		wprintf(L"Getting unique container name");

		/**********************************************************************
		* Получение размера буфера для имени контейнера                       *
		**********************************************************************/
		if (!CryptGetProvParam(
		                       hProv,           // Дескриптор криптопровайдера
		                       PP_FQCN,         // Флаг получения FQCN имени контейнера
		                       NULL,            // Указатель на буфер для получения имени контейнера
		                       &dwUnicNameSize, // Указатель на длину буфера
		                       0)) {
			wprintf(L" -> FAILED, error number: 0x%0.8x\n\n", GetLastError());
			break;
		}

		/**********************************************************************
		* Выделение памяти для буфера                                         *
		**********************************************************************/
		pbUnicContName = (BYTE*)malloc(dwUnicNameSize * sizeof(BYTE));
		if (!pbUnicContName) {
			wprintf(L" -> FAILED, out of memory\n\n");
			break;
		}

		/**********************************************************************
		* Получение указателя на буфер с именем контейнера                    *
		**********************************************************************/
		if (!CryptGetProvParam(
		                       hProv,           // Дескриптор криптопровайдера
		                       PP_FQCN,         // Флаг получения FQCN имени контейнера
		                       pbUnicContName,  // Указатель на буфер для получения имени контейнера
		                       &dwUnicNameSize, // Указатель на длину буфера
		                       0)) {
			wprintf(L" -> FAILED, error number: 0x%0.8x\n\n", GetLastError());
			break;
		}
		wprintf(L" -> OK\n");

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

		/**********************************************************************
		* Шаг 2.3 Инициализация криптопровайдера для работы в тихом режиме    *
		**********************************************************************/
		wprintf(L"Initializing cryptoprovider for silent mode");
		/**********************************************************************
		* Используем CryptAcquireContextA, поскольку pbUnicContName является  *
		* ANSI строкой                                                        *
		**********************************************************************/
		if (!CryptAcquireContextA(
		                          &hProv,                // Дескриптор криптопровайдера
		                          (LPSTR)pbUnicContName, // FQCN имя ключевого контейнера
		                          szProvNameA,           // Имя криптопровайдера
		                          dwProvType,            // Тип криптопровайдера
		                          CRYPT_SILENT)) {       // Флаг тихого режима
			wprintf(L" -> FAILED, error number: 0x%0.8x\n\n", GetLastError());
			break;
		}
		wprintf(L" -> OK\n");

		/**********************************************************************
		* Шаг 2.4 Передача PIN-кода Рутокен в параметры криптопровайдера      *
		**********************************************************************/
		wprintf(L"Setting Rutoken PIN-code");
		if (!CryptSetProvParam(
		                       hProv,              // Дескриптор криптопровайдера
		                       PP_KEYEXCHANGE_PIN, // Флаг передачи PIN-кода
		                       USER_PIN,           // Значение PIN-кода
		                       0)) {
			wprintf(L" -> FAILED, error number: 0x%0.8x\n\n", GetLastError());
			break;
		}
		wprintf(L" -> OK\n");

		/**********************************************************************
		* Шаг 3. Вычисление хэша данных                                       *
		**********************************************************************/
		wprintf(L"\nGetting data hash...\n");

		/**********************************************************************
		* Шаг 3.1 Создание объекта хэша                                       *
		**********************************************************************/
		wprintf(L" Creating hash object");
		if (!CryptCreateHash(
		                     hProv,        // Дескриптор криптопровайдера
		                     ALG_GOST3411, // Идентификатор алгоритма хэширования
		                     0,
		                     0,
		                     &hHash)) {    // Указатель на дескриптор хэша
			wprintf(L" -> FAILED, error number: 0x%0.8x\n\n", GetLastError());
			break;
		}
		wprintf(L" -> OK\n");

		/**********************************************************************
		* Шаг 3.2 Задание параметров функции хэширования                      *
		**********************************************************************/
		wprintf(L" Setting algorithm parameters");
		if (!CryptSetHashParam(
		                       hHash,                  // Дескриптор хэша
		                       HP_OID,                 // Признак параметров алгоритма
		                       (BYTE*)GOST3411_PARAMS, // Значение параметров алгоритма
		                       0)) {
			wprintf(L" -> FAILED, error number: 0x%0.8x\n\n", GetLastError());
			break;
		}
		wprintf(L" -> OK\n");

		/**********************************************************************
		* Шаг 3.3 Вычисление значения хэша                                    *
		**********************************************************************/
		wprintf(L" Data hashing");
		if (!CryptHashData(
		                   hHash,                // Дескриптор хэша
		                   pbData,               // Указатель на буфер с данными для хэширования
		                   GetArraySize(pbData), // Длина буфера
		                   0)) {
			wprintf(L" -> FAILED, error number: 0x%0.8x\n\n", GetLastError());
			break;
		}
		wprintf(L" -> OK\n");

		/**********************************************************************
		* Шаг 3.4 Получение значения хэша                                     *
		**********************************************************************/
		wprintf(L" Retrieving hash value");

		/**********************************************************************
		* Получение размера буфера для хэша                                   *
		**********************************************************************/
		if (!CryptGetHashParam(
		                       hHash,       // Дескриптор хэша
		                       HP_HASHVAL,  // Признак значения хэша
		                       NULL,        // Указатель на буфер для значения хэша
		                       &dwHashSize, // Указатель на длину буфера
		                       0)) {
			wprintf(L" -> FAILED, error number: 0x%0.8x\n\n", GetLastError());
			break;
		}

		/**********************************************************************
		* Выделение памяти для буфера                                         *
		**********************************************************************/
		pbHash = (BYTE*)malloc(dwHashSize * sizeof(BYTE));
		if (!pbHash) {
			wprintf(L" -> FAILED, out of memory\n\n");
			break;
		}

		/**********************************************************************
		* Получение указателя на буфер с хэшем                                *
		**********************************************************************/
		if (!CryptGetHashParam(
		                       hHash,       // Дескриптор хэша
		                       HP_HASHVAL,  // Признак значения хэша
		                       pbHash,      // Указатель на буфер для значения хэша
		                       &dwHashSize, // Указатель на длину буфера
		                       0)) {
			wprintf(L" -> FAILED, error number: 0x%0.8x\n\n", GetLastError());
			break;
		}
		wprintf(L" -> OK\n");

		/**********************************************************************
		* Шаг 3.5 Печать значения хэша                                        *
		**********************************************************************/
		wprintf(L"Hash value is: \n");
		for (i = 0; i < dwHashSize; ++i) {
			wprintf(L"%0.2X ", pbHash[i]);
			if ((i + 1) % 8 == 0) {
				wprintf(L"\n");
			}
		}
		wprintf(L"\nHash has been calculated successfully.\n\n");

		/**********************************************************************
		* Шаг 4. Подпись хэшированных данных                                  *
		**********************************************************************/
		wprintf(L"Signing data");

		/**********************************************************************
		* Получение размера буфера для подписи                                *
		**********************************************************************/
		if (!CryptSignHashW(
		                    hHash,               // Дескриптор хэша
		                    AT_SIGNATURE,        // Тип ключа -- для подписи
		                    NULL,
		                    0,
		                    NULL,                // Указатель на буфер для подписи
		                    &dwSignatureSize)) { // Указатель на размер буфера
			wprintf(L" -> FAILED, error number: 0x%0.8x\n\n", GetLastError());
			break;
		}

		/**********************************************************************
		* Выделение памяти для буфера                                         *
		**********************************************************************/
		pbSignature = (BYTE*)malloc(dwSignatureSize * sizeof(BYTE));
		if (!pbSignature) {
			wprintf(L" -> FAILED, out of memory\n\n");
			break;
		}

		/**********************************************************************
		* Получение указателя на буфер с подписью                             *
		**********************************************************************/
		if (!CryptSignHashW(
		                    hHash,               // Дескриптор хэша
		                    AT_SIGNATURE,        // Тип ключа -- для подписи
		                    NULL,
		                    0,
		                    pbSignature,         // Указатель на буфер для подписи
		                    &dwSignatureSize)) { // Указатель на размер буфера
			wprintf(L" -> FAILED, error number: 0x%0.8x\n\n", GetLastError());
			break;
		}
		wprintf(L" -> OK\n");

		/**********************************************************************
		* Шаг 5. Печать значения подписи                                      *
		**********************************************************************/
		wprintf(L"\nSignature value is: \n");
		for (i = 0; i < dwSignatureSize; ++i) {
			wprintf(L"%0.2X ", pbSignature[i]);
			if ((i + 1) % 8 == 0) {
				wprintf(L"\n");
			}
		}
		wprintf(L"\nData has been signed successfully.\n\n");

		/**********************************************************************
		* Шаг 6. Освобождение дескриптора хэша и памяти                       *
		**********************************************************************/
		if (hHash) {
			CryptDestroyHash(hHash);
			hHash = 0;
		}

		free(pbHash);
		pbHash = NULL;

		/**********************************************************************
		* Шаг 7. Проверка подписи данных                                      *
		**********************************************************************/
		wprintf(L"Verifying signature...\n");

		/**********************************************************************
		* Шаг 7.1 Создание объекта хэша                                       *
		**********************************************************************/
		wprintf(L" Creating hash object");
		if (!CryptCreateHash(
		                     hProv,        // Дескриптор криптопровайдера
		                     ALG_GOST3411, // Идентификатор алгоритма хэширования
		                     0,
		                     0,
		                     &hHash)) {    // Указатель на дескриптор хэша
			wprintf(L" -> FAILED, error number: 0x%0.8x\n\n", GetLastError());
			break;
		}
		wprintf(L" -> OK\n");

		/**********************************************************************
		* Шаг 7.2 Задание параметров функции хэширования                      *
		**********************************************************************/
		wprintf(L" Setting algorithm parameters");
		if (!CryptSetHashParam(
		                       hHash,                  // Дескриптор хэша
		                       HP_OID,                 // Признак параметров алгоритма
		                       (BYTE*)GOST3411_PARAMS, // Значение параметров алгоритма
		                       0)) {
			wprintf(L" -> FAILED, error number: 0x%0.8x\n\n", GetLastError());
			break;
		}
		wprintf(L" -> OK\n");

		/**********************************************************************
		* Шаг 7.3 Вычисление значения хэша                                    *
		**********************************************************************/
		wprintf(L" Data hashing");
		if (!CryptHashData(
		                   hHash,                // Дескриптор хэша
		                   pbData,               // Указатель на буфер с данными для хэширования
		                   GetArraySize(pbData), // Длина буфера
		                   0)) {
			wprintf(L" -> FAILED, error number: 0x%0.8x\n\n", GetLastError());
			break;
		}
		wprintf(L" -> OK\n");

		/**********************************************************************
		* Шаг 7.4 Получение значения хэша                                     *
		**********************************************************************/
		wprintf(L" Retrieving hash value");

		/**********************************************************************
		* Получение размера буфера для хэша                                   *
		**********************************************************************/
		if (!CryptGetHashParam(
		                       hHash,       // Дескриптор хэша
		                       HP_HASHVAL,  // Признак значения хэша
		                       NULL,        // Указатель на буфер для значения хэша
		                       &dwHashSize, // Указатель на длину буфера
		                       0)) {
			wprintf(L" -> FAILED, error number: 0x%0.8x\n\n", GetLastError());
			break;
		}

		/**********************************************************************
		* Выделение памяти для буфера                                         *
		**********************************************************************/
		pbHash = (BYTE*)malloc(dwHashSize * sizeof(BYTE));
		if (!pbHash) {
			wprintf(L" -> FAILED, out of memory\n\n");
			break;
		}

		/**********************************************************************
		* Получение указателя на буфер с хэшем                                *
		**********************************************************************/
		if (!CryptGetHashParam(
		                       hHash,       // Дескриптор хэша
		                       HP_HASHVAL,  // Признак значения хэша
		                       pbHash,      // Указатель на буфер для значения хэша
		                       &dwHashSize, // Указатель на длину буфера
		                       0)) {
			wprintf(L" -> FAILED, error number: 0x%0.8x\n\n", GetLastError());
			break;
		}
		wprintf(L" -> OK\n");

		/**********************************************************************
		* Шаг 7.5 Печать значения хэша                                        *
		**********************************************************************/
		wprintf(L"Hash value is: \n");
		for (i = 0; i < dwHashSize; ++i) {
			wprintf(L"%0.2X ", pbHash[i]);
			if ((i + 1) % 8 == 0) {
				wprintf(L"\n");
			}
		}
		wprintf(L"\nHash has been calculated successfully.\n\n");

		/**********************************************************************
		* Шаг 7.6 Получение дескриптора открытого ключа                       *
		**********************************************************************/
		wprintf(L"Getting public key");
		if (!CryptGetUserKey(
		                     hProv,        // Дескриптор криптопровайдера
		                     AT_SIGNATURE, // Назначение ключа -- для подписи
		                     &hKey)) {     // Дескриптор ключа
			if (GetLastError() == NTE_NO_KEY) {
				wprintf(L" -> FAILED, key does not exist (0x%0.8x)\n\n", GetLastError());
			} else {
				wprintf(L" -> FAILED, error number: 0x%0.8x\n\n", GetLastError());
			}
			break;
		}
		wprintf(L" -> OK\n");

		/**********************************************************************
		* Шаг 7.7 Проверка подписи                                            *
		**********************************************************************/
		wprintf(L"Verifying signature value");
		if (CryptVerifySignatureW(
		                          hHash,           // Дескриптор хэша
		                          pbSignature,     // Указатель на буфер с подписью
		                          dwSignatureSize, // Длина буфера
		                          hKey,            // Дескриптор ключа
		                          NULL,
		                          0)) {
			wprintf(L" -> OK\n");
			wprintf(L"\nSignature has been verified successfully.\n\n");
		} else if (GetLastError() == NTE_BAD_SIGNATURE) {
			wprintf(L" -> FAILED\n");
			wprintf(L"Signature verification has failed!\n\n");
		} else {
			wprintf(L" -> FAILED, error number: 0x%0.8x\n\n", GetLastError());
		}

		break;
	}

	/**********************************************************************
	* Шаг 8. Освобождение памяти                                          *
	**********************************************************************/
	/**********************************************************************
	* Шаг 8.1 Освобождение дескрипторов ключа и хэша                      *
	**********************************************************************/
	if (hKey) {
		CryptDestroyKey(hKey);
	}
	if (hHash) {
		CryptDestroyHash(hHash);
	}

	/**********************************************************************
	* Шаг 8.2 Освобождение контекста криптопровайдера                     *
	**********************************************************************/
	if (hProv) {
		CryptReleaseContext(hProv, 0);
	}

	/**********************************************************************
	* Шаг 8.3 Освобождение памяти                                         *
	**********************************************************************/
	free(pbHash);

	free(pbSignature);

	free(pbUnicContName);


	if (GetLastError() == ERROR_SUCCESS || GetLastError() == NTE_EXISTS) {
		wprintf(L"Test has been completed successfully. Press Enter to exit");
	} else {
		wprintf(L"Test has failed. Error number: 0x%0.8x. Press Enter to exit", GetLastError());
	}
	return 0;
}
