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

#include <Common.h>

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

/*************************************************************************
* Функция конвертирования печатного символа в 6-битный код               *
*************************************************************************/
CK_BYTE ConvertSymBase64ToCode(char sym)    // символ
{
	char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
	char* newSym = strchr(alphabet, sym);
	if (newSym != NULL) {
		return (CK_BYTE)(newSym - alphabet);
	} else {
		return 0;
	}
}

/*************************************************************************
* Функция добавления 6 бит в массив байт                                 *
*************************************************************************/
void SetNext6Bit(CK_BYTE_PTR result,       // Указатель на начало массива
                 CK_ULONG begin,           // Номер бита в массиве, начиная с которого будем вставлять 6 бит
                 CK_BYTE code              // Вставляемые 6 бит
                 )
{
	CK_BYTE diff = begin % 8;
	result += begin / 8;
	*result |= code << 2 >> diff;
	*(result + 1) |= code << 2 << (8 - diff);
}

/*************************************************************************
* Функция конвертирования строки base64 в массив байт                    *
*************************************************************************/
void ConvertToByteArray(const char* source,          // Исходная base64 строка
                        CK_BYTE_PTR* result,         // Результирующий массив байт
                        CK_ULONG_PTR size            // Размер массива
                        )
{
	CK_ULONG i = 0;
	*size = (CK_ULONG)strlen(source) / 4 * 3;
	*result = (CK_BYTE_PTR)calloc(*size, sizeof(CK_CHAR));
	if (*result == NULL_PTR) {
		return;
	}
	for (; *source; i += 6, ++source) {
		SetNext6Bit(*result, i, ConvertSymBase64ToCode(*source));
	}
}

/*************************************************************************
* Функция получения сертификата в DER формате из Base64                  *
*************************************************************************/
void GetDERCert(const char* source,                    // Исходные данные
                CK_BYTE_PTR* result,                   // Результирующий запрос
                CK_ULONG_PTR size                      // Длина результирующего массива
                )
{
	const char* begin = "-----BEGIN CERTIFICATE-----";   // Начало сертификата
	const char* end = "-----END CERTIFICATE-----";       // Конец сертификата
	size_t length;
	char* buffer;

	if (*source != '-') {
		ConvertToByteArray(source, result, size);
	} else {
		length = (int)(strlen(source) - strlen(begin) - strlen(end));
		length = length > 0 ? length : 0;             // Проверка на случай неправильных данных
		buffer = (char*)calloc(length + 1, sizeof(char));
		if (buffer == NULL) {
			return;
		}
		strncpy(buffer, source + strlen(begin), length);
		ConvertToByteArray(buffer, result, size);
		free(buffer);
	}
}

/*************************************************************************
* Чтение base64 строки сертификата из входного потока.                   *
* При вводе без результирующего переноса строки, или если последняя      *
* строка имеет длину 64 символа, придётся нажать ещё раз enter           *
*************************************************************************/

void GetStr(char** str)
{
	char buf[65];                               // Буфер для построчного считывания
	int n;                                      // Результат функции scanf
	size_t length = 0;                          // Длина результирующей строки
	int count = 3;                              // Счётчик строк, длина которых меньше 64 символов (нужен для определения конца сертификата)
	char* temp;                                 // Переменная для временного хранения указателя

	*str = (char*)calloc(1, sizeof(char));
	if (*str == NULL) {
		return;
	}
	do {
		n = scanf("%65[^\n]", buf);
		if (n > 0) {
			length += strlen(buf);
			temp = (char*)realloc(*str, length + 1);
			if (temp == NULL) {
				free(*str);
				*str = NULL;
				return;
			}
			*str = temp;
			strcat(*str, buf);
			n = scanf("%*c");
		} else {
			n = scanf("%*c");
		}
		if (strlen(buf) < 64) {
			--count;
		}
	} while ((**str == '-' && count != 0) || (**str != '-' && count != 2));
}

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

	char* certBase64;                                 // Строка с сертификатом в base64 формате
	CK_BYTE_PTR certDer;                              // Массив с сертификатом в DER формате
	CK_ULONG certSize;                                // Размер массива сертификата

	CK_OBJECT_HANDLE certificate;                     // Хэндл сертификата

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

	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_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("\nImport certificate... \n");

	/*************************************************************************
	* Ввод сертификата в формате base64 и перекодирование в DER              *
	*************************************************************************/
	printf(" Enter certificate in base64 format:\n");
	GetStr(&certBase64);
	if (certBase64 == NULL) {
		printf("Reading failed");
		goto logout;
	}

	GetDERCert(certBase64, &certDer, &certSize);
	free(certBase64);
	CHECK(" GetDerCert", certDer != NULL_PTR, logout);

	certificateTemplate[0].pValue = certDer;
	certificateTemplate[0].ulValueLen = certSize;

	/*************************************************************************
	* Создание сертификата на токене                                         *
	*************************************************************************/
	rv = functionList->C_CreateObject(session, certificateTemplate, arraysize(certificateTemplate),
	                                  &certificate);
	CHECK_AND_LOG(" C_CreateObject", rv == CKR_OK, rvToStr(rv), free_certificate);

	printf("Certificate has been created successfully\n");

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

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

	/*************************************************************************
	* Очистить память из-под строки с сертификатом                           *
	*************************************************************************/
free_certificate:
	free(certDer);

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