/*************************************************************************
* Rutoken                                                                *
* Copyright (c) 2003-2017, CJSC Aktiv-Soft. All rights reserved.         *
* Подробная информация:  http://www.rutoken.ru                           *
*------------------------------------------------------------------------*
* Пример работы с Рутокен ЭЦП при помощи библиотеки PKCS#11 на языке C   *
*------------------------------------------------------------------------*
* Использование команды подписи данных ключевой парой ГОСТ 34.10-2001 в  *
* формате PKCS#7:                                                        *
*  - установление соединения с Рутокен ЭЦП в первом доступном слоте;     *
*  - выполнение аутентификации Пользователя;                             *
*  - поиск закрытого ключа ГОСТ 34.10-2001 и сертификата на Рутокен;     *
*  - подпись данных в формате PKCS#7;                                    *
*  - сброс прав доступа Пользователя и закрытие соединения с Рутокен.    *
*------------------------------------------------------------------------*
* Пример использует объекты, созданные в памяти Рутокен примерами        *
* CreateCSR-PKCS10 и ImportCertificate                                   *
*************************************************************************/

#include <Common.h>

/*************************************************************************
* Шаблон для поиска закрытого ключа ГОСТ Р 34.10-2001                    *
*************************************************************************/
CK_ATTRIBUTE privateKeyTemplate[] =
{
	{ CKA_CLASS, &privateKeyObject, sizeof(privateKeyObject) },         // Объект закрытого ключа
	{ CKA_TOKEN, &attributeTrue, sizeof(attributeTrue) },               // Закрытый ключ является объектом токена
	{ CKA_ID, &keyPairIdGost1, sizeof(keyPairIdGost1) - 1 },            // Идентификатор искомой пары
};

/*************************************************************************
* Шаблон для поиска сертификата ключа проверки подписи                   *
*************************************************************************/
CK_ATTRIBUTE certificateTemplate[] =
{
	{ CKA_CLASS, &certificateObject, sizeof(certificateObject) },                      // Объект сертификата
	{ CKA_TOKEN, &attributeTrue, sizeof(attributeTrue) },                              // Сертификат является объектом токена
	{ CKA_ID, &keyPairIdGost1, sizeof(keyPairIdGost1) - 1},                            // Идентификатор ключевой пары, которой соответствует сертификат
	{ CKA_CERTIFICATE_TYPE, &certificateType, sizeof(certificateType) },               // Тип сертификата - X.509
	{ CKA_CERTIFICATE_CATEGORY, &tokenUserCertificate, sizeof(tokenUserCertificate) }, // Категория сертификата - пользовательский
};

/*************************************************************************
* Данные для подписи                                                     *
*************************************************************************/
CK_BYTE data[] = { '0', '1', '2', '3', '4' };

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_OBJECT_HANDLE_PTR privateKeys;                  // Массивы хэндлов объектов, соответствующих критериям поиска ключей
	CK_ULONG keysCount;                                // Количество найденных ключей

	CK_OBJECT_HANDLE_PTR certificates;                 // Указатель на массив хэндлов объектов, соответствующих критериям поиска
	CK_ULONG certificatesCount;                        // Количество найденных сертификатов

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

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

	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", getFunctionList != NULL, unload_pkcs11);

	/*************************************************************************
	* Получить адрес функции запроса структуры с указателями на функции      *
	* расширения стандарта PKCS#11                                           *
	*************************************************************************/
	getFunctionListEx = (CK_C_EX_GetFunctionListExtended)GetProcAddress(module, "C_EX_GetFunctionListExtended");
	CHECK(" GetProcAddress", getFunctionList != NULL, 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);


	/*************************************************************************
	* Открыть 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_slots);

	/*************************************************************************
	* Выполнить аутентификацию Пользователя                                  *
	*************************************************************************/
	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("\nSigning...\n");

	/*************************************************************************
	* Поиск закрытого ключа на токене                                        *
	*************************************************************************/
	printf(" Getting private key...\n");
	r = findObjects(functionList, session, privateKeyTemplate, arraysize(privateKeyTemplate),
	                &privateKeys, &keysCount);
	CHECK(" findObjects", r == 0, logout);

	CHECK_AND_LOG(" Checking number of keys found", keysCount != 0, "No objects found\n", logout);

	/*************************************************************************
	* Поиск сертификата на токене                                            *
	*************************************************************************/
	printf(" Getting certificate...\n");
	r = findObjects(functionList, session, certificateTemplate, arraysize(certificateTemplate),
	                &certificates, &certificatesCount);
	CHECK(" findObjects", r == 0, free_keys);

	CHECK_AND_LOG(" Checking number of keys found", certificatesCount != 0, "No objects found\n", free_keys);


	/*************************************************************************
	* Подпись данных                                                         *
	*************************************************************************/
	rv = functionListEx->C_EX_PKCS7Sign(session, data, arraysize(data), certificates[0],
	                                    &signature, &signatureSize, privateKeys[0], NULL_PTR, 0, 0);
	CHECK_AND_LOG(" C_EX_PKCS7Sign", rv == CKR_OK, rvToStr(rv), free_certificates);


	/*************************************************************************
	* Распечатать буфер, содержащий подпись                                  *
	*************************************************************************/
	printf(" Signature buffer is:\n");
	printHex(signature, signatureSize);

	printf("Data has been signed successfully.\n");

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

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

	/*************************************************************************
	* Освобождение памяти, выделенной в библиотеке                           *
	*************************************************************************/
	rv = functionListEx->C_EX_FreeBuffer(signature);
	CHECK_RELEASE_AND_LOG(" C_EX_FreeBuffer", rv == CKR_OK, rvToStr(rv), errorCode);

	/*************************************************************************
	* Освобождение памяти из-под объектов                                    *
	*************************************************************************/
free_certificates:
	free(certificates);

free_keys:
	free(privateKeys);

	/*************************************************************************
	* Сбросить права доступа                                                 *
	*************************************************************************/
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_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;
}
