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

#include <Common.h>

/*************************************************************************
* Шаблон для создания открытого ключа ГОСТ Р 34.10-2001                  *
*************************************************************************/
CK_ATTRIBUTE publicKeyTemplate[] =
{
	{ CKA_CLASS, &publicKeyObject, sizeof(publicKeyObject)},                    // Объект открытого ключа ГОСТ Р 34.10-2001
	{ CKA_LABEL, &publicKeyLabelGost1, sizeof(publicKeyLabelGost1) - 1},        // Метка ключа
	{ CKA_ID, &keyPairIdGost1, sizeof(keyPairIdGost1) - 1},                     // Идентификатор ключевой пары #1 (должен совпадать у открытого и закрытого ключей)
	{ CKA_KEY_TYPE, &keyTypeGostR3410, sizeof(keyTypeGostR3410)},               // Тип ключа
	{ CKA_TOKEN, &attributeTrue, sizeof(attributeTrue)},                        // Ключ является объектом токена
	{ CKA_PRIVATE, &attributeFalse, sizeof(attributeFalse)},                    // Ключ доступен без аутентификации
	{ CKA_GOSTR3410_PARAMS, &parametersGostR3410, sizeof(parametersGostR3410)}, // Параметры алгоритма ГОСТ Р 34.10-2001
};

/*************************************************************************
* Шаблон для создания закрытого ключа ГОСТ Р 34.10-2001                  *
*************************************************************************/
CK_ATTRIBUTE privateKeyTemplate[] =
{
	{ CKA_CLASS, &privateKeyObject, sizeof(privateKeyObject)},                 // Объект закрытого ключа ГОСТ Р 34.10-2001
	{ CKA_LABEL, &privateKeyLabelGost1, sizeof(privateKeyLabelGost1) - 1},     // Метка ключа
	{ CKA_ID, &keyPairIdGost1, sizeof(keyPairIdGost1) - 1},                    // Идентификатор ключевой пары #1 (должен совпадать у открытого и закрытого ключей)
	{ CKA_KEY_TYPE, &keyTypeGostR3410, sizeof(keyTypeGostR3410)},              // Тип ключа
	{ CKA_TOKEN, &attributeTrue, sizeof(attributeTrue)},                       // Ключ является объектом токена
	{ CKA_PRIVATE, &attributeTrue, sizeof(attributeTrue)},                     // Ключ доступен только после аутентификации
	{ CKA_DERIVE, &attributeTrue, sizeof(attributeTrue)},                      // Ключ поддерживает выработку общих ключей (VKO)
	{ CKA_GOSTR3410_PARAMS, parametersGostR3410, sizeof(parametersGostR3410)}, // Параметры алгоритма ГОСТ Р 34.10-2001
};

/*************************************************************************
* Запрос на получение сертификата                                        *
*************************************************************************/
/*************************************************************************
* Список полей DN (Distinguished Name)                                   *
*************************************************************************/
CK_CHAR_PTR dn[] = { (CK_CHAR_PTR)"CN",                       // Тип поля CN (Common Name)
	                 (CK_CHAR_PTR)"UTF8String:Иванов",        // Значение
	                 (CK_CHAR_PTR)"C",                        // Тип поля C (Country)
	                 (CK_CHAR_PTR)"RU",
	                 (CK_CHAR_PTR)"2.5.4.5",                  // Тип поля SN (Serial Number)
	                 (CK_CHAR_PTR)"12312312312",
	                 (CK_CHAR_PTR)"1.2.840.113549.1.9.1",     // Тип поля E (E-mail)
	                 (CK_CHAR_PTR)"ivanov@mail.ru",
	                 (CK_CHAR_PTR)"ST",                       // Тип поля ST (State or province)
	                 (CK_CHAR_PTR)"UTF8String:Москва", };

/*************************************************************************
* Список дополнительных полей                                            *
*************************************************************************/
CK_CHAR_PTR exts[] = {(CK_CHAR_PTR)"keyUsage",
	                  (CK_CHAR_PTR)"digitalSignature,nonRepudiation,keyEncipherment,dataEncipherment",
	                  (CK_CHAR_PTR)"extendedKeyUsage",
	                  (CK_CHAR_PTR)"1.2.643.2.2.34.6,1.3.6.1.5.5.7.3.2,1.3.6.1.5.5.7.3.4",
	                  (CK_CHAR_PTR)"2.5.29.14",
	                  (CK_CHAR_PTR)"ASN1:FORMAT:HEX,OCTETSTRING:FE117B93CEC6B5065E1613E155D3A9CA597C0F81",
	                  (CK_CHAR_PTR)"1.2.643.100.111",
	                  (CK_CHAR_PTR)"ASN1:UTF8String:СКЗИ \\\"Рутокен ЭЦП 2.0\\\"", };

/*************************************************************************
* Функция конвертирования 6-битного кода в печатный символ Base64        *
*************************************************************************/
char ConvertCodeToSymBase64(CK_BYTE code    // 6-битный код
                            )
{
	const char* alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
	if (code < 0x40) {
		return alphabet[(int)code];
	} else {
		return '?';
	}
}

/*************************************************************************
* Функция выборки 6 бит из массива байт                                  *
*************************************************************************/
CK_BYTE GetNext6Bit(CK_BYTE_PTR csr,          // Указатель на начало массива
                    CK_ULONG start,           // Номер бита в массиве, с которого начинается группа из 6 бит
                    CK_ULONG end              // Номер последнего бита массива
                    )
{
	CK_BYTE diff = start % 8;
	csr += start / 8;
	if (end - start > 8) {
		return 0x3F & (*csr << diff | *(csr + 1) >> (8 - diff)) >> 2;
	} else {
		return 0x3F & (*csr << diff >> 2);
	}
}

/*************************************************************************
* Функция конвертирования массива байт в строку Base64                   *
*************************************************************************/
void ConvertToBase64String(CK_BYTE_PTR data,         // Исходные данные
                           CK_ULONG size,            // Длина исходного массива
                           char** result             // Результирующие данные (нуль-терминированная строка)
                           )
{
	CK_ULONG i = 0;
	char* pt;
	*result = (char*)calloc(((size_t)size + 2) / 3 * 4 + 1, sizeof(char));
	if (*result != NULL) {
		memset(*result, '=', ((size_t)size + 2) / 3 * 4);
		for (pt = *result; i < size * 8; i += 6, ++pt) {
			*pt = ConvertCodeToSymBase64(GetNext6Bit(data, i, size * 8));
		}
	}
}

/*************************************************************************
* Функция получения CSR, закодированного Base64                          *
*************************************************************************/
void GetBase64CSR(CK_BYTE_PTR source,                  // Исходные данные
                  CK_ULONG size,                       // Длина исходного массива
                  char** result                        // Результирующий запрос
                  )
{
	const char* begin = "-----BEGIN NEW CERTIFICATE REQUEST-----\n"; // Начало запроса
	const char* end = "-----END NEW CERTIFICATE REQUEST-----\n";     // Конец запроса
	size_t length;
	size_t width = 0x40;
	char* buffer;
	size_t i;

	ConvertToBase64String(source, size, &buffer);
	if (buffer == NULL) {
		*result = NULL;
		return;
	}
	length = strlen(buffer);
	*result = (char*)calloc(strlen(begin)                    // Место под начало запроса
	                        + length                         // Место под base64 строку
	                        + strlen(end)                    // Место под конец запроса
	                        + (length - 1) / width + 1       // Место под переносы строки
	                        + 1,                             // Нуль-байт
	                        sizeof(char));
	if (*result == NULL) {
		free(buffer);
		return;
	}
	//компоновка запроса
	strcat(*result, begin);
	for (i = 0; i < length; i += width) {
		strncat(*result, buffer + i, width);
		strcat(*result, "\n");
	}
	strcat(*result, end);

	free(buffer);
}

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 publicKey, privateKey;            // Хэндлы ключей

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

	char* csrBase64;                                   // Строка с CSR в формате Base64

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

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

	int isGostR3410Supported = 0;                      // Флаг для проверки поддержки токеном CKM_GOSTR3410_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 (C_GetFunctionList)", getFunctionList != NULL, unload_pkcs11);

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

	/*************************************************************************
	* Получить список поддерживаемых токеном механизмов                      *
	*************************************************************************/
	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_GOSTR3410_KEY_PAIR_GEN) {
			isGostR3410Supported = 1;
			break;
		}
	}

	CHECK_AND_LOG(" Checking CKM_GOSTR3410_KEY_PAIR_GEN support", isGostR3410Supported,
	              "CKM_GOSTR3410_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("\nCreating CSR...\n");

	/*************************************************************************
	* Генерация ключевой пары на токене                                      *
	*************************************************************************/
	rv = functionList->C_GenerateKeyPair(session, &gostR3410KeyPairGenMech,
	                                     publicKeyTemplate, arraysize(publicKeyTemplate),
	                                     privateKeyTemplate, arraysize(privateKeyTemplate),
	                                     &publicKey, &privateKey);
	CHECK_AND_LOG(" C_GenerateKeyPair", rv == CKR_OK, rvToStr(rv), logout);

	/*************************************************************************
	* Создание запроса на сертификат                                         *
	*************************************************************************/
	rv = functionListEx->C_EX_CreateCSR(session, publicKey, dn, arraysize(dn), &csr, &csrSize,
	                                    privateKey, NULL_PTR, 0, exts, arraysize(exts));
	CHECK_AND_LOG(" C_EX_CreateCSR", rv == CKR_OK, rvToStr(rv), logout);

	/*************************************************************************
	* Сконвертировать и распечатать буфер в кодировке Base64                 *
	*************************************************************************/
	GetBase64CSR(csr, csrSize, &csrBase64);
	CHECK(" Get CSR in Base64 format", csrBase64 != NULL, free_csr);

	printf("\nCertificate request is:\n");
	printf("%s\n", csrBase64);

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

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

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

	/*************************************************************************
	* Освободить буферы, содержащие запросы на сертификат                    *
	*************************************************************************/
	free(csrBase64);
free_csr:
	rv = functionListEx->C_EX_FreeBuffer(csr);
	CHECK_RELEASE_AND_LOG(" C_EX_FreeBuffer", rv == CKR_OK, rvToStr(rv), errorCode);

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