/*************************************************************************
* Rutoken                                                                *
* Copyright (c) 2003-2017, CJSC Aktiv-Soft. All rights reserved.         *
* Подробная информация:  http://www.rutoken.ru                           *
*------------------------------------------------------------------------*
* Пример работы с Рутокен PINPad при помощи библиотеки PKCS#11 на языке C*
*------------------------------------------------------------------------*
* Использование команд работы с журналом операций                        *
*  - установление соединения с Рутокен PINPad в первом доступном слоте;  *
*  - выполнение аутентификации Пользователя;                             *
*  - получение журнала операций Рутокен PINPad;                          *
*  - генерация пары для цифровой подписи журнала на Рутокен PINPad;      *
*  - цифровая подпись журнала Рутокен PINPad журнальной парой;           *
*  - проверка цифровой подписи журнала Рутокен PINPad журнальной парой;  *
*  - сброс прав доступа Пользователя на Рутокен PINPad и закрытие        *
*    соединения с Рутокен PINPad.                                        *
*------------------------------------------------------------------------*
* Для примера необходима хотя бы одна операция формирования ЭЦП,         *
* например, выполнение примера SignGOST34.10-2001-PINPad.                *
*************************************************************************/

#include <Common.h>

/**************************************************************************
* Шаблон для генерации открытого ключа ГОСТ Р 34.10-2001 для проверки     *
* цифровой подписи журнала                                                *
**************************************************************************/
CK_ATTRIBUTE publicKeyGenTemplate[] =
{
	{ CKA_CLASS, &publicKeyObject, sizeof(publicKeyObject) },                   // Объект открытого ключа
	{ CKA_KEY_TYPE, &keyTypeGostR3410, sizeof(keyTypeGostR3410) },              // Тип ключа ГОСТ Р 34.10-2001
	{ CKA_TOKEN, &attributeTrue, sizeof(attributeTrue) },                       // Ключ является объектом токена
	{ CKA_PRIVATE, &attributeFalse, sizeof(attributeFalse) },                   // Ключ доступен без авторизации на токене
	{ CKA_VENDOR_KEY_JOURNAL, &attributeTrue, sizeof(attributeTrue) },          // Ключ предназначен для проверки цифровой подписи журнала
};

/**************************************************************************
* Шаблон для генерации закрытого ключа ГОСТ Р 34.10-2001 для цифровой     *
* подписи журнала                                                         *
**************************************************************************/
CK_ATTRIBUTE privateKeyGenTemplate[] =
{
	{ CKA_CLASS, &privateKeyObject, sizeof(privateKeyObject) },                 // Объект закрытого ключа
	{ CKA_KEY_TYPE, &keyTypeGostR3410, sizeof(keyTypeGostR3410) },              // Тип ключа ГОСТ Р 34.10-2001
	{ CKA_TOKEN, &attributeTrue, sizeof(attributeTrue) },                       // Ключ является объектом токена
	{ CKA_PRIVATE, &attributeTrue, sizeof(attributeTrue) },                     // Ключ доступен только после авторизации на токене
	{ CKA_VENDOR_KEY_JOURNAL, &attributeTrue, sizeof(attributeTrue) },          // Ключ предназначен для цифровой подписи журнала
	{ CKA_VENDOR_KEY_CONFIRM_OP, &attributeFalse, sizeof(attributeFalse) },     // Операции с ключом не требуют подтверждения на экране Рутокен PINPad
};

/**************************************************************************
* Шаблон для поиска открытого ключа ГОСТ Р 34.10-2001 для проверки        *
* цифровой подписи журнала                                                *
**************************************************************************/
CK_ATTRIBUTE publicKeyFindTemplate[] =
{
	{ CKA_VENDOR_KEY_JOURNAL, &attributeTrue, sizeof(attributeTrue) },         // Ключ предназначен для проверки цифровой подписи журнала
	{ CKA_CLASS, &publicKeyObject, sizeof(publicKeyObject) },                  // Объект открытого ключа
	{ CKA_KEY_TYPE, &keyTypeGostR3410, sizeof(keyTypeGostR3410) },             // Тип ключа ГОСТ Р 34.10-2001
};

/**************************************************************************
* Шаблон для поиска закрытого ключа ГОСТ Р 34.10-2001 для цифровой        *
* подписи журнала                                                         *
**************************************************************************/
CK_ATTRIBUTE privateKeyFindTemplate[] =
{
	{ CKA_VENDOR_KEY_JOURNAL, &attributeTrue, sizeof(attributeTrue) },         // Ключ предназначен для цифровой подписи журнала
	{ CKA_CLASS, &privateKeyObject, sizeof(privateKeyObject) },                // Объект закрытого ключа
	{ CKA_KEY_TYPE, &keyTypeGostR3410, sizeof(keyTypeGostR3410) },             // Тип ключа ГОСТ Р 34.10-2001
};

int main(void)
{
	HMODULE module;                                     // Хэндл загруженной библиотеки PKCS#11
	CK_SESSION_HANDLE session;                          // Хэндл открытой сессии

	CK_FUNCTION_LIST_PTR functionList;                  // Указатель на список функций PKCS#11, хранящийся в структуре CK_FUNCTION_LIST
	CK_C_GetFunctionList getFunctionList;               // Указатель на функцию C_GetFunctionList

	CK_FUNCTION_LIST_EXTENDED_PTR functionListEx;       // Указатель на список функций расширения PKCS#11, хранящийся в структуре CK_FUNCTION_LIST_EXTENDED
	CK_C_EX_GetFunctionListExtended getFunctionListEx;  // Указатель на функцию C_EX_GetFunctionListExtended

	CK_SLOT_ID_PTR slots;                               // Указатель на массив идентификаторов всех доступных слотов
	CK_ULONG slotCount;                                 // Количество идентификаторов всех доступных слотов в массиве

	CK_MECHANISM_TYPE_PTR mechanisms;                   // Указатель на массив поддерживаемых механизмов
	CK_ULONG mechanismCount;                            // Количество поддерживаемых механизмов

	CK_OBJECT_HANDLE publicKeyJournal;                  // Хэндл открытого ключа журнальной пары
	CK_OBJECT_HANDLE privateKeyJournal;                 // Хэндл закрытого ключа журнальной пары

	CK_OBJECT_HANDLE_PTR objects1;                      // Массив хэндлов объектов, соответствующих критериям поиска
	CK_ULONG objectCount1;                              // Количество хэндлов объектов в массиве

	CK_OBJECT_HANDLE_PTR objects2;                      // Массив хэндлов объектов, соответствующих критериям поиска
	CK_ULONG objectCount2;                              // Количество хэндлов объектов в массиве

	CK_BYTE_PTR journal;                                // Указатель на буфер, содержащий журнал
	CK_ULONG journalSize;                               // Размер журнала

	CK_BYTE_PTR signature;                              // Указатель на буфер, содержащий цифровую подпись журнала
	CK_ULONG signatureSize;                             // Размер буфера, содержащего цифровую подпись журнала, в байтах

	CK_BYTE_PTR hash1;                                  // Указатель на временный буфер хэш-кода журнала
	CK_BYTE_PTR hash2;                                  // Указатель на временный буфер хэш-кода журнала

	CK_ULONG hashSize1;                                 // Размер временного буфера в байтах
	CK_ULONG hashSize2;                                 // Размер временного буфера в байтах

	CK_RV rv;                                           // Код возврата. Могут быть возвращены только ошибки, определенные в PKCS#11
	int r;                                              // Код возврата для функций, возвращающих int

	CK_ULONG i;                                         // Вспомогательная переменная-счетчик в циклах

	int isGostR3410GenSupported = 0;                    // Флаг для проверки поддержки токеном CKM_GOSTR3410_KEY_PAIR_GEN
	int isGostR3410Supported = 0;                       // Флаг для проверки поддержки токеном CKM_GOSTR3410
	int isGostR3411Supported = 0;                       // Флаг для проверки поддержки токеном CKM_GOSTR3411

	int errorCode = 1;                                  // Флаг ошибки

	/*************************************************************************
	* Выполнить действия для начала работы с библиотекой PKCS#11             *
	*************************************************************************/
	printf("Initialization...\n");

	/*************************************************************************
	* Загрузить библиотеку                                                   *
	*************************************************************************/
	module = LoadLibrary(PKCS11ECP_LIBRARY_NAME);
	CHECK(" LoadLibrary", module != NULL, exit);

	/*************************************************************************
	* Получить адрес функции запроса структуры с указателями на функции      *
	*************************************************************************/
	getFunctionList = (CK_C_GetFunctionList)GetProcAddress(module, "C_GetFunctionList");
	CHECK(" GetProcAddress (C_GetFunctionList)", getFunctionList != NULL_PTR, unload_pkcs11);

	/*************************************************************************
	* Получить адрес функции запроса структуры с указателями на              *
	* функции расширения                                                     *
	*************************************************************************/
	getFunctionListEx = (CK_C_EX_GetFunctionListExtended)GetProcAddress(module, "C_EX_GetFunctionListExtended");
	CHECK(" GetProcAddress (C_EX_GetFunctionListExtended)", getFunctionListEx != NULL_PTR, unload_pkcs11);

	/*************************************************************************
	* Получить структуру с указателями на функции                            *
	*************************************************************************/
	rv = getFunctionList(&functionList);
	CHECK_AND_LOG(" Get function list", rv == CKR_OK, rvToStr(rv), unload_pkcs11);

	/*************************************************************************
	* Получить структуру с указателями на функции расширения                 *
	*************************************************************************/
	rv = getFunctionListEx(&functionListEx);
	CHECK_AND_LOG(" Get function list extended", rv == CKR_OK, rvToStr(rv), unload_pkcs11);

	/*************************************************************************
	* Инициализировать библиотеку                                            *
	*************************************************************************/
	rv = functionList->C_Initialize(NULL_PTR);
	CHECK_AND_LOG(" C_Initialize", rv == CKR_OK, rvToStr(rv), unload_pkcs11);

	/*************************************************************************
	* Получить количество слотов c подключенными токенами                    *
	*************************************************************************/
	rv = functionList->C_GetSlotList(CK_TRUE, NULL_PTR, &slotCount);
	CHECK_AND_LOG(" C_GetSlotList (number of slots)", rv == CKR_OK, rvToStr(rv), finalize_pkcs11);

	CHECK_AND_LOG(" Checking available tokens", slotCount > 0, " No tokens available", finalize_pkcs11);

	/*************************************************************************
	* Получить список слотов c подключенными токенами                        *
	*************************************************************************/
	slots = (CK_SLOT_ID_PTR)malloc(slotCount * sizeof(CK_SLOT_ID));
	CHECK(" Memory allocation for slots", slots != NULL_PTR, finalize_pkcs11);

	rv = functionList->C_GetSlotList(CK_TRUE, slots, &slotCount);
	CHECK_AND_LOG(" C_GetSlotList", rv == CKR_OK, rvToStr(rv), free_slots);
	printf(" Slots available: %d\n", (int)slotCount);

	/*************************************************************************
	* Получить список поддерживаемых токеном механизмов                      *
	*************************************************************************/
	rv = functionList->C_GetMechanismList(slots[0], NULL_PTR, &mechanismCount);
	CHECK_AND_LOG(" C_GetMechanismList (number of mechanisms)", rv == CKR_OK, rvToStr(rv), free_slots);

	CHECK_AND_LOG(" Checking mechanisms available", mechanismCount > 0, " No mechanisms available", free_slots);

	mechanisms = (CK_MECHANISM_TYPE_PTR)malloc(mechanismCount * sizeof(CK_MECHANISM_TYPE));
	CHECK(" Memory allocation for mechanisms", mechanisms != NULL_PTR, free_slots);

	rv = functionList->C_GetMechanismList(slots[0], mechanisms, &mechanismCount);
	CHECK_AND_LOG(" C_GetMechanismList", rv == CKR_OK, rvToStr(rv), free_mechanisms);

	/*************************************************************************
	* Определение поддерживаемых токеном механизмов                          *
	*************************************************************************/
	for (i = 0; i < mechanismCount; ++i) {
		switch (mechanisms[i]) {
		case CKM_GOSTR3410_KEY_PAIR_GEN:
			isGostR3410GenSupported = 1;
			break;
		case CKM_GOSTR3410:
			isGostR3410Supported = 1;
			break;
		case CKM_GOSTR3411:
			isGostR3411Supported = 1;
			break;
		}
	}

	CHECK_AND_LOG(" Checking CKM_GOSTR3410_KEY_PAIR_GEN support", isGostR3410GenSupported, "CKM_GOSTR3410_KEY_PAIR_GEN isn`t supported!\n", free_mechanisms);
	CHECK_AND_LOG(" Checking CKM_GOSTR3410 support", isGostR3410Supported, "CKM_GOSTR3410 isn`t supported!\n", free_mechanisms);
	CHECK_AND_LOG(" Checking CKM_GOSTR3411 support", isGostR3411Supported, "CKM_GOSTR3411 isn`t supported!\n", free_mechanisms);

	/*************************************************************************
	* Открыть RW сессию в первом доступном слоте                             *
	*************************************************************************/
	rv = functionList->C_OpenSession(slots[0], CKF_SERIAL_SESSION | CKF_RW_SESSION, NULL_PTR, NULL_PTR, &session);
	CHECK_AND_LOG(" C_OpenSession", rv == CKR_OK, rvToStr(rv), free_mechanisms);

	/*************************************************************************
	* Выполнить аутентификацию Пользователя                                  *
	*************************************************************************/
	rv = functionList->C_Login(session, CKU_USER, USER_PIN, USER_PIN_LEN);
	CHECK_AND_LOG(" C_Login", rv == CKR_OK, rvToStr(rv), close_session);

	printf("Initialization has been completed successfully.\n");

	/*************************************************************************
	* Получить журнал                                                        *
	*************************************************************************/
	printf("\nAcquiring journal...\n");

	/*************************************************************************
	* Получить размер журнала                                                *
	*************************************************************************/
	rv = functionListEx->C_EX_GetJournal(slots[0], NULL_PTR, &journalSize);
	if (rv == CKR_FUNCTION_NOT_SUPPORTED) {
		CHECK_AND_LOG(" C_EX_GetJournal (get size)", rv == CKR_OK, "PINPad doesn't support journal!", logout);
	}
	CHECK_AND_LOG(" C_EX_GetJournal (get size)", rv == CKR_OK, rvToStr(rv), logout);

	CHECK_AND_LOG(" Verifying journal length", journalSize != 0, "Journal is empty!", logout);

	/*************************************************************************
	* Получить журнал                                                        *
	*************************************************************************/
	journal = (CK_BYTE_PTR)malloc(journalSize * sizeof(CK_BYTE));
	CHECK(" Memory allocation for journal", journal != NULL_PTR, logout);

	rv = functionListEx->C_EX_GetJournal(slots[0], journal, &journalSize);
	CHECK_AND_LOG(" C_EX_GetJournal", rv == CKR_OK, rvToStr(rv), free_journal);

	/*************************************************************************
	* Распечатать журнал                                                     *
	*************************************************************************/
	printf(" Journal buffer is: \n");
	printHex(journal, journalSize);

	printf("Journal has been acquired successfully.\n");

	/*************************************************************************
	* Получить хэндл закрытого ключа журнальной пары                         *
	*************************************************************************/
	printf("\nGetting journal private key...\n");
	r = findObjects(functionList, session, privateKeyFindTemplate, arraysize(privateKeyFindTemplate),
	                &objects1, &objectCount1);
	CHECK("findObjects (journal private key)", r == 0, free_journal);

	if (objectCount1 == 0) {
		printf("No journal keys found! Journal key pair will be generated.\n");

		/*************************************************************************
		* Сгенерировать журнальную ключевую пару                                 *
		*************************************************************************/
		rv = functionList->C_GenerateKeyPair(session, &gostR3410KeyPairGenMech,
		                                     publicKeyGenTemplate, arraysize(publicKeyGenTemplate),
		                                     privateKeyGenTemplate, arraysize(privateKeyGenTemplate),
		                                     &publicKeyJournal, &privateKeyJournal);
		CHECK_AND_LOG("C_GenerateKeyPair", rv == CKR_OK, rvToStr(rv), free_journal);
	} else {
		privateKeyJournal = objects1[0];
	}

	/*************************************************************************
	* Сформировать хэш-код журнала для цифровой подписи                      *
	*************************************************************************/
	printf("\nHashing journal...\n");

	/*************************************************************************
	* Инициализировать хэш-функцию                                           *
	*************************************************************************/
	rv = functionList->C_DigestInit(session, &gostR3411HashMech);
	CHECK_AND_LOG(" C_DigestInit", rv == CKR_OK, rvToStr(rv), free_objects1);

	/*************************************************************************
	* Определить размер хэш-кода                                             *
	*************************************************************************/
	rv = functionList->C_Digest(session, journal, journalSize, NULL_PTR, &hashSize1);
	CHECK_AND_LOG(" C_Digest (get size)", rv == CKR_OK, rvToStr(rv), free_objects1);

	/*************************************************************************
	* Сформировать хэш-код журнала                                           *
	*************************************************************************/
	hash1 = (CK_BYTE_PTR)malloc(hashSize1 * sizeof(CK_BYTE));
	CHECK(" Memory allocation for hash", hash1 != NULL_PTR, free_objects1);

	rv = functionList->C_Digest(session, journal, journalSize, hash1, &hashSize1);
	CHECK_AND_LOG(" C_Digest", rv == CKR_OK, rvToStr(rv), free_hash1);

	/*************************************************************************
	* Распечатать буфер, содержащий хэш-код                                  *
	*************************************************************************/
	printf(" Hashed buffer is: \n");
	printHex(hash1, hashSize1);
	printf("Hashing has been completed.\n");

	/*************************************************************************
	* Сформировать цифровую подпись журнала по алгоритму ГОСТ Р 34.10-2001   *
	*************************************************************************/
	printf("\nSigning journal...\n");

	/*************************************************************************
	* Инициализировать операцию цифровой подписи журнала                     *
	*************************************************************************/
	rv = functionListEx->C_EX_SignInvisibleInit(session, &gostR3410SigVerMech, privateKeyJournal);
	CHECK_AND_LOG(" C_SignInit", rv == CKR_OK, rvToStr(rv), free_hash1);

	/*************************************************************************
	* Определить размер подписанных данных                                   *
	*************************************************************************/
	rv = functionListEx->C_EX_SignInvisible(session, hash1, hashSize1, NULL_PTR, &signatureSize);
	CHECK_AND_LOG(" C_Sign (get size)", rv == CKR_OK, rvToStr(rv), free_hash1);

	/*************************************************************************
	* Подписать журнал                                                       *
	*************************************************************************/
	signature = (CK_BYTE_PTR)malloc(signatureSize * sizeof(CK_BYTE));
	CHECK(" Memory allocation for signature", signature != NULL_PTR, free_hash1);

	rv = functionListEx->C_EX_SignInvisible(session, hash1, hashSize1, signature, &signatureSize);
	CHECK_AND_LOG(" C_Sign", rv == CKR_OK, rvToStr(rv), free_signature);

	/*************************************************************************
	* Распечатать буфер, содержащий цифровую подпись журнала                 *
	*************************************************************************/
	printf(" Sign buffer is: \n");
	printHex(signature, signatureSize);
	printf("Signing has been completed.\n");

	/*************************************************************************
	* Сформировать хэш-код журнала для проверки цифровой подписи             *
	*************************************************************************/
	printf("\nHashing journal...\n");

	/*************************************************************************
	* Инициализировать хэш-функцию                                           *
	*************************************************************************/
	rv = functionList->C_DigestInit(session, &gostR3411HashMech);
	CHECK_AND_LOG(" C_DigestInit", rv == CKR_OK, rvToStr(rv), free_signature);

	/*************************************************************************
	* Определить размер хэш-кода                                             *
	*************************************************************************/
	rv = functionList->C_Digest(session, journal, journalSize, NULL_PTR, &hashSize2);
	CHECK_AND_LOG(" C_Digest (get size)", rv == CKR_OK, rvToStr(rv), free_signature);

	/*************************************************************************
	* Сформировать хэш-код журнала                                           *
	*************************************************************************/
	hash2 = (CK_BYTE_PTR)malloc(hashSize2 * sizeof(CK_BYTE));
	CHECK(" Memory allocation for hash", hash2 != NULL_PTR, free_signature);

	rv = functionList->C_Digest(session, journal, journalSize, hash2, &hashSize2);
	CHECK_AND_LOG(" C_Digest (get hash)", rv == CKR_OK, rvToStr(rv), free_hash2);

	/*************************************************************************
	* Распечатать буфер, содержащий хэш-код                                  *
	*************************************************************************/
	printf(" Hashed buffer is: \n");
	printHex(hash2, hashSize2);
	printf("Hashing has been completed.\n");

	/*************************************************************************
	* Выполнить проверку цифровой подписи по алгоритму ГОСТ Р 34.10-2001     *
	*************************************************************************/
	printf("\nVerifying signature...\n");

	/*************************************************************************
	* Получить массив хэндлов открытых ключей                                *
	*************************************************************************/
	printf(" Getting key to verify...\n");
	r = findObjects(functionList, session, publicKeyFindTemplate, arraysize(publicKeyFindTemplate),
	                &objects2, &objectCount2);
	CHECK(" findObjects (public keys)", r == 0, free_hash2);

	CHECK_AND_LOG(" Checking number of keys found", objectCount2 != 0, "No objects found\n", free_hash2);
	publicKeyJournal = objects2[0];

	/*************************************************************************
	* Инициализировать операцию проверки цифровой подписи журнала            *
	*************************************************************************/
	rv = functionList->C_VerifyInit(session, &gostR3410SigVerMech, publicKeyJournal);
	CHECK_AND_LOG(" C_VerifyInit", rv == CKR_OK, rvToStr(rv), free_objects2);

	/*************************************************************************
	* Проверить цифровую подпись журнала                                     *
	*************************************************************************/
	rv = functionList->C_Verify(session, hash2, hashSize2, signature, signatureSize);
	CHECK_AND_LOG(" C_Verify", rv == CKR_OK, rvToStr(rv), free_objects2);

	printf("Verifying has been completed successfully.\n");

	/*************************************************************************
	* Выставить признак успешного завершения программы                       *
	*************************************************************************/
	errorCode = 0;

	/*************************************************************************
	* Выполнить действия для завершения работы с библиотекой PKCS#11         *
	*************************************************************************/
	printf("\nFinalizing... \n");

	/*************************************************************************
	* Очистить память, выделенную под подпись, объекты ключей, хэш-коды и    *
	* журнал                                                                 *
	*************************************************************************/
free_objects2:
	free(objects2);

free_hash2:
	free(hash2);

free_signature:
	free(signature);

free_hash1:
	free(hash1);

free_objects1:
	free(objects1);

free_journal:
	free(journal);

	/*************************************************************************
	* Сбросить права доступа                                                 *
	*************************************************************************/
logout:
	rv = functionList->C_Logout(session);
	CHECK_RELEASE_AND_LOG(" C_Logout", rv == CKR_OK, rvToStr(rv), errorCode);

	/*************************************************************************
	* Закрыть открытую сессию в слоте                                        *
	*************************************************************************/
close_session:
	rv = functionList->C_CloseSession(session);
	CHECK_RELEASE_AND_LOG(" C_CloseSession", rv == CKR_OK, rvToStr(rv), errorCode);

	/*************************************************************************
	* Очистить память, выделенную под механизмы и слоты                      *
	*************************************************************************/
free_mechanisms:
	free(mechanisms);

free_slots:
	free(slots);

	/*************************************************************************
	* Деинициализировать библиотеку                                          *
	*************************************************************************/
finalize_pkcs11:
	rv = functionList->C_Finalize(NULL_PTR);
	CHECK_RELEASE_AND_LOG(" C_Finalize", rv == CKR_OK, rvToStr(rv), errorCode);

	/*************************************************************************
	* Выгрузить библиотеку из памяти                                         *
	*************************************************************************/
unload_pkcs11:
	CHECK_RELEASE(" FreeLibrary", FreeLibrary(module), errorCode);

exit:
	if (errorCode) {
		printf("\n\nSome error occurred. Sample failed.\n");
	} else {
		printf("\n\nSample has been completed successfully.\n");
	}

	return errorCode;
}
