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

#include <Common.h>

/*************************************************************************
* Шаблон для генерации открытого ключа RSA                               *
* (ключевая пара для подписи и шифрования)                               *
*************************************************************************/
CK_ATTRIBUTE publicKeyTemplate[] =
{
	{ CKA_CLASS, &publicKeyObject, sizeof(publicKeyObject)},            // Класс - открытый ключ
	{ CKA_LABEL, &publicKeyLabelRsa, sizeof(publicKeyLabelRsa) - 1},    // Метка ключа
	{ CKA_ID, &keyPairIdRsa, sizeof(keyPairIdRsa) - 1},                 // Идентификатор ключевой пары (должен совпадать у открытого и закрытого ключей)
	{ CKA_KEY_TYPE, &keyTypeRsa, sizeof(keyTypeRsa)},                   // Тип ключа - RSA
	{ CKA_TOKEN, &attributeTrue, sizeof(attributeTrue)},                // Ключ является объектом токена
	{ CKA_ENCRYPT, &attributeTrue, sizeof(attributeTrue)},              // Ключ предназначен для зашифрования
	{ CKA_PRIVATE, &attributeFalse, sizeof(attributeFalse)},            // Ключ доступен без аутентификации на токене
	{ CKA_MODULUS_BITS, &rsaModulusBits, sizeof(rsaModulusBits)}        // Длина модуля ключа
};

/*************************************************************************
* Шаблон для генерации закрытого ключа RSA                               *
* (Ключевая пара для подписи и шифрования)                               *
*************************************************************************/
CK_ATTRIBUTE privateKeyTemplate[] =
{
	{ CKA_CLASS, &privateKeyObject, sizeof(privateKeyObject)},           // Класс - закрытый ключ
	{ CKA_LABEL, &privateKeyLabelRsa, sizeof(privateKeyLabelRsa) - 1},   // Метка ключа
	{ CKA_ID, &keyPairIdRsa, sizeof(keyPairIdRsa) - 1},                  // Идентификатор ключевой пары (должен совпадать у открытого и закрытого ключей)
	{ CKA_KEY_TYPE, &keyTypeRsa, sizeof(keyTypeRsa)},                    // Тип ключа - RSA
	{ CKA_DECRYPT, &attributeTrue, sizeof(attributeTrue)},               // Ключ предназначен для расшифрования
	{ CKA_TOKEN, &attributeTrue, sizeof(attributeTrue)},                 // Ключ является объектом токена
	{ CKA_PRIVATE, &attributeTrue, sizeof(attributeTrue)}                // Ключ доступен только после аутентификации на токене
};

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_SLOT_ID_PTR slots;                             // Массив идентификаторов слотов
	CK_ULONG slotCount;                               // Количество идентификаторов слотов в массиве

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

	CK_OBJECT_HANDLE publicKey;                       // Хэндл открытого ключа RSA (ключевая пара для подписи и шифрования)
	CK_OBJECT_HANDLE privateKey;                      // Хэндл закрытого ключа RSA (ключевая пара для подписи и шифрования)

	CK_RV rv;                                         // Код возврата. Могут быть возвращены только ошибки, определенные в PKCS#11

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

	int isRsaSupported = 0;                           // Флаг для проверки поддержки токеном CKM_RSA_PKCS_KEY_PAIR_GEN

	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);

	/*************************************************************************
	* Получить структуру с указателями на функции                            *
	*************************************************************************/
	rv = getFunctionList(&functionList);
	CHECK_AND_LOG(" Get function list", 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, 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) {
		if (mechanisms[i] == CKM_RSA_PKCS_KEY_PAIR_GEN) {
			isRsaSupported = 1;
			break;
		}
	}

	CHECK_AND_LOG(" Checking CKM_RSA_PKCS_KEY_PAIR_GEN support", isRsaSupported,
	              "CKM_RSA_PKCS_KEY_PAIR_GEN 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("\nGenerating RSA key pair...\n");

	rv = functionList->C_GenerateKeyPair(session, &rsaKeyPairGenMech,
	                                     publicKeyTemplate, arraysize(publicKeyTemplate),
	                                     privateKeyTemplate, arraysize(privateKeyTemplate),
	                                     &publicKey, &privateKey);
	CHECK_AND_LOG(" C_GenerateKeyPair", rv == CKR_OK, rvToStr(rv), logout);

	/*************************************************************************
	* Выставить признак успешного завершения программы                       *
	*************************************************************************/
	errorCode = 0;
	printf("Generating has been completed successfully.\n");

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

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

