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

#include <Common.h>

/**************************************************************************
* Перечисление типов TLV-структур                                         *
**************************************************************************/
typedef enum {
	tlvTypeJournal = 0x80,                      // Журнал операции
	tlvTypeHash = 0xAA,                         // Хэш-код подписываемых данных
	tlvTypeSignature = 0xB6,                    // Цифровая подпись хэш-кода
	tlvTypeOperationInfo = 0x85,                // Информация о выполненной операции
	tlvTypeId = 0x83,                           // Идентификатор токена
	tlvTypeTableInfo = 0x86                     // Информация об использовавшейся загружаемой таблицы тегов
} TlvType;

/**************************************************************************
* Перечисление типов криптографических операций                           *
**************************************************************************/
typedef enum {
	operationTypeSignature = 0x01,              // Операция формирования цифровой подписи
} OperationType;

/**************************************************************************
* Перечисление типов RSF-файлов                                           *
**************************************************************************/
typedef enum {
	rsfSubTypeGchv = 0x01,                      // Глобальный PIN-код
	rsfSubTypeLchv = 0x21,                      // Локальный PIN-код
	rsfSubTypeSymGost = 0x02,                   // Секретный ключ ГОСТ 28147-89
	rsfSubTypeAes = 0x22,                       // Секретный ключ AES
	rsfSubType3Des = 0x42,                      // Секретный ключ Triple DES
	rsfSubTypeAsymGostPr = 0x03,                // Закрытый ключ ГОСТ Р 34.10–2001
	rsfSubTypeRsaPr = 0x23,                     // Закрытый ключ RSA
	rsfSubTypeAsymGostPr_512 = 0x43,            // Закрытый ключ ГОСТ Р 34.10–2012
	rsfSubTypeAsymGostPu = 0x13,                // Открытый ключ ГОСТ Р 34.10–2001
	rsfSubTypeRsaPu = 0x33,                     // Открытый ключ RSA
	rsfSubTypeAsymGostPu_512 = 0x53,            // Открытый ключ ГОСТ Р 34.10–2012
	rsfSubTypeX509Cer = 0x14,                   // Сертификат X.509
	rsfSubTypeDefGost = 0x05,                   // Секретный ключ ГОСТ 28147-89 по умолчанию
	rsfSubTypeSe = 0x1F                         // Файл Security Environment
} RsfSubType;

/**************************************************************************
* Перечисление свойств ключа                                              *
**************************************************************************/
typedef enum {
	rsfFlagAllowKeyExch = 0x01,                 // Ключ, с помощью которого разрешена выработка ключа согласования
	rsfFlagKeyForSm = 0x02,                     // Ключ Secure Messaging
	rsfFlagKeySignsJournal = 0x04,              // Ключ цифровой подписи журнала
	rsfFlagKeyImported = 0x08,                  // Импортированный ключ
	rsfFlagKeyPinEnter = 0x10,                  // Ключ, операции с которым требуют ввода PIN2 на дисплее PINPad’а
	rsfFlagKeyConfirmOp = 0x20                  // Ключ, операции с которым требуют подтверждения операции на дисплее PINPad’а
} RsfFlags;

/**************************************************************************
* Перечисление со флагами операции                                        *
**************************************************************************/
typedef enum {
	opInfoDeviceHash = 0x01,                    // Хэш-код подписываемых данных был вычислен токеном
	opInfoKeyPinEnter = 0x02,                   // Операция цифровой подписи зависит от PIN2, который можно ввести только на дисплее PINPad’а
	opInfoPin2Entered = 0x10,                   // PIN-код действительно был введен на дисплее PINPad’а
	opInfoOpConfirmed = 0x20                    // Подписываемый документ действительно был показан на дисплее PINPad’а
} OperationInfoFlags;

/**************************************************************************
* Структура с информацией об операции                                     *
**************************************************************************/
#pragma pack(push, 1)
typedef struct OperationInfo_ {
	uint8_t operationType;                      // Тип операции
	uint8_t rsfType;                            // Тип RSF-файла
	uint8_t rsfFlags;                           // Флаги RSF-файла
	uint8_t oiFlags;                            // Флаги выполнения операции
	uint8_t pppFlags;                           // Политики PINPad'а
	uint8_t reserved;                           // Резерв
	uint8_t rsfIdHigh;                          // Идентификатор RSF (старший байт)
	uint8_t rsfIdLow;                           // Идентификатор RSF (младший байт)
	uint32_t signatureCount;                    // Значение общего счетчика удачных операций цифровой подписи
} OperationInfo;
#pragma pack(pop)

/**************************************************************************
* Структура для печати TLV-структур                                       *
**************************************************************************/
typedef struct TlvPrinter_ {
	TlvType type;                               // Тип TLV-структуры
	void (* print)(const CK_BYTE*);             // Функция вывода
} TlvPrinter;

/**************************************************************************
* Функция получения длины Value из TLV-структуры                          *
**************************************************************************/
CK_ULONG getTlvValueLen(const CK_BYTE* tlvElement) {
	CK_ULONG shift = 0;
	unsigned int i = 0;
	if (tlvElement[1] & 0x80) {
		for (i = 0; i < (unsigned int)tlvElement[1] - 0x80; ++i) {
			shift = (shift << 8) + tlvElement[2 + i];
		}
	} else {
		shift = tlvElement[1];
	}
	return shift;
}

/**************************************************************************
* Функция поиска тега в TLV-структуре                                     *
**************************************************************************/
const CK_BYTE* findTlvElement(CK_BYTE tag, const CK_BYTE* firstTlvElement, CK_ULONG tlvLength) {
	const CK_BYTE* lastValueByte = firstTlvElement + tlvLength;
	const CK_BYTE* tempTag = firstTlvElement;
	CK_ULONG headerLength = 0;

	while (tempTag < lastValueByte) {
		if (*tempTag == tag) {
			return tempTag;
		}
		if (tempTag[1] & 0x80) {
			headerLength = tempTag[1] - 0x80 + 2;
		} else {
			headerLength = 2;
		}

		tempTag += getTlvValueLen(tempTag) + headerLength;
	}
	return 0; // Тег не найден
}

/**************************************************************************
* Функция получения Value из TLV-структуры                                *
**************************************************************************/
const CK_BYTE* getTlvValue(const CK_BYTE* tlvElement) {
	if (tlvElement[1] & 0x80) {
		return tlvElement + 2 + tlvElement[1] - 0x80;
	} else {
		return tlvElement + 2;
	}
}

/**************************************************************************
* Функция вывода буфера в шестнадцатеричном виде                          *
**************************************************************************/
void printHexBuffer(const CK_BYTE* buffer, const CK_ULONG length, const CK_ULONG offset) {
	unsigned int i = 0, j = 0;
	for (i = 0; i < length; ++i) {
		if (i % 8 == 0) {
			for (j = 0; j < offset; ++j) {
				printf(" ");
			}
		}

		printf("%02X ", buffer[i]);

		if ((i + 1) % 8 == 0 || (i + 1) == length) {
			printf("\n");
		}
	}
}

/**************************************************************************
* Функция вывода типа криптографической операции                          *
**************************************************************************/
void printOperationType(OperationType operationType)
{
	printf("  Operation Type: ");
	switch (operationType) {
	case operationTypeSignature: printf("Signature"); break;
	default: printf("unknown (%02X)", operationType);
	}
	printf("\n");
}

/**************************************************************************
* Функция вывода типа RSF-файла, на котором была выполнена операция       *
**************************************************************************/
void printRsfType(RsfSubType rsfType) {
	printf("  RSF Type: ");
	switch (rsfType) {
	case rsfSubTypeGchv: printf("GCHV (Global PIN)"); break;
	case rsfSubTypeLchv: printf("LCHV (Local PIN)"); break;
	case rsfSubTypeSymGost: printf("SGOST (GOST 28147-89 key)"); break;
	case rsfSubTypeAes: printf("AES"); break;
	case rsfSubType3Des: printf("3DES"); break;
	case rsfSubTypeAsymGostPr: printf("AGOST_PR (GOST 34.10-2001 private key)"); break;
	case rsfSubTypeRsaPr: printf("RSA_PR (RSA private key)"); break;
	case rsfSubTypeAsymGostPr_512: printf("AGOST2012_PR (GOST 34.10-2012 private key)"); break;
	case rsfSubTypeAsymGostPu: printf("AGOST_PU (GOST 34.10-2001 public key)"); break;
	case rsfSubTypeRsaPu: printf("RSA_PU (RSA public key)"); break;
	case rsfSubTypeAsymGostPu_512: printf("AGOST2012_PU (GOST 34.10-2012 public key)"); break;
	case rsfSubTypeX509Cer: printf("X509 (X509 certificate)"); break;
	case rsfSubTypeDefGost: printf("ACGOST (GOST 28147-89 key)"); break;
	case rsfSubTypeSe: printf("SE (SE environment object)"); break;
	default: printf("unknown (%02X)", rsfType);
	}
	printf("\n");
}

/**************************************************************************
* Функция вывода флагов RSF-файла, на котором была выполнена операция     *
**************************************************************************/
void printRsfFlags(RsfFlags rsfFlags) {
	printf("  RSF Flags: \n");
	if (rsfFlags & rsfFlagAllowKeyExch) {
		printf("   Key Exchange Allowed\n");
	}
	if (rsfFlags & rsfFlagKeyForSm) {
		printf("   Key for SM\n");
	}
	if (rsfFlags & rsfFlagKeySignsJournal) {
		printf("   Key for journal signing\n");
	}
	if (rsfFlags & rsfFlagKeyImported) {
		printf("   Key was imported\n");
	}
	if (rsfFlags & rsfFlagKeyPinEnter) {
		printf("   Key usage requires PIN2 enter\n");
	}
	if (rsfFlags & rsfFlagKeyConfirmOp) {
		printf("   Key usage requires operation confirmation\n");
	}
	if (!rsfFlags) {
		printf("   None\n");
	}
}

/**************************************************************************
* Функция вывода флагов выполнения операции                               *
**************************************************************************/
void printOperationInfoFlags(OperationInfoFlags oiFlags) {
	printf("  Operation Info Flags: \n");
	if (oiFlags & opInfoDeviceHash) {
		printf("   Hash was performed on device\n");
	}
	if (oiFlags & opInfoKeyPinEnter) {
		printf("   Key used requires PIN2 enter\n");
	}
	if (oiFlags & opInfoPin2Entered) {
		printf("   PIN2 was entered\n");
	}
	if (oiFlags & opInfoOpConfirmed) {
		printf("   Operation was confirmed\n");
	}
	if (!oiFlags) {
		printf("   None\n");
	}
}

/**************************************************************************
* Функция вывода идентификатора RSF-файла, на котором была выполнена      *
* операция                                                                *
**************************************************************************/
void printRsfId(CK_BYTE rsfIdHigh, CK_BYTE rsfIdLow) {
	unsigned int rsfId = ((unsigned int)rsfIdHigh << 8) | rsfIdLow;
	printf("  RSF Id: %04X\n", rsfId);
}

/**************************************************************************
* Функция вывода количества удачных цифровых подписей                     *
**************************************************************************/
void printSignatureCount(uint32_t signatureCount) {
	uint32_t signatureCountLe = 0;
	unsigned int i = 0;
	for (i = 0; i < sizeof(signatureCount); ++i) {
		signatureCountLe = (signatureCountLe << 8) | (signatureCount & 0xFF);
		signatureCount >>= 8;
	}
	printf("  Successful signature count: %d\n", signatureCountLe);
}

/**************************************************************************
* Функция вывода информации об операции                                   *
**************************************************************************/
void printOperationInfo(const CK_BYTE* tlvElement) {
	const OperationInfo* operationInfo = (const OperationInfo*)getTlvValue(tlvElement);
	printf(" Operation Info:\n");
	printOperationType(operationInfo->operationType);
	printRsfType(operationInfo->rsfType);
	printRsfFlags(operationInfo->rsfFlags);
	printOperationInfoFlags(operationInfo->oiFlags);
	printRsfId(operationInfo->rsfIdHigh, operationInfo->rsfIdLow);
	printSignatureCount(operationInfo->signatureCount);
}

/**************************************************************************
* Функция вывода хэш-кода                                                 *
**************************************************************************/
void printHash(const CK_BYTE* tlvElement) {
	printf(" Hash:\n");
	printHexBuffer(getTlvValue(tlvElement), getTlvValueLen(tlvElement), 2);
}

/**************************************************************************
* Функция вывода электронной цифровой подписи                             *
**************************************************************************/
void printSignature(const CK_BYTE* tlvElement) {
	printf(" Signature:\n");
	printHexBuffer(getTlvValue(tlvElement), getTlvValueLen(tlvElement), 2);
}

/**************************************************************************
* Функция вывода идентификатора токена                                    *
**************************************************************************/
void printId(const CK_BYTE* tlvElement) {
	unsigned int i = 0;
	const CK_BYTE* id = getTlvValue(tlvElement);
	printf(" Token Id: ");
	for (i = 0; i < getTlvValueLen(tlvElement); ++i) {
		printf("%02X", id[i]);
	}
	printf("\n");
}

/**************************************************************************
* Функция вывода журнала                                                  *
**************************************************************************/
void printJournal(const CK_BYTE* buffer, CK_ULONG length)
{
	const CK_BYTE* journal = NULL_PTR;
	const CK_BYTE* journalBody = NULL_PTR;
	const CK_BYTE* tlvElement = NULL_PTR;
	CK_ULONG journalSize = 0;
	TlvPrinter journalPrinters[] = {
		{tlvTypeHash, printHash},
		{tlvTypeSignature, printSignature},
		{tlvTypeOperationInfo, printOperationInfo},
		{tlvTypeId, printId},
	};
	unsigned int i = 0;

	printf("Journal: ");
	journal = findTlvElement(tlvTypeJournal, buffer, length);
	if (!journal) {
		printf("not found\n");
		return;
	} else {
		printf("\n");
	}

	journalBody = getTlvValue(journal);
	journalSize = getTlvValueLen(journal);

	for (i = 0; i < arraysize(journalPrinters); ++i) {
		tlvElement = findTlvElement(journalPrinters[i].type, journalBody, journalSize);
		if (tlvElement) {
			journalPrinters[i].print(tlvElement);
		}
	}
}

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_BYTE_PTR journal;                               // Указатель на буфер, содержащий журнал
	CK_ULONG journalSize;                              // Размер журнала

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

	/*************************************************************************
	* Открыть 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("\nGetting 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, "Token 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);

	/*************************************************************************
	* Распечатать журнал                                                     *
	*************************************************************************/
	printJournal(journal, journalSize);

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

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

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

	/*************************************************************************
	* Очистить память, выделенную под журнал                                 *
	*************************************************************************/
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_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;
}
