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

#include <Common.h>

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_TOKEN_INFO_EXTENDED tokenInfoEx;                // Структура данных типа CK_TOKEN_INFO_EXTENDED с информацией о токене

	CK_CHAR_PTR tokenName;                             // Указатель на метку токена
	CK_ULONG tokenNameLength;                          // Размер метки токена

	CK_RUTOKEN_INIT_PARAM initParam;                   // Структура данных типа CK_RUTOKEN_INIT_PARAM, содержащая параметры для работы функции C_EX_InitToken

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

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

	int isRutokenS;                                    // Вспомогательная переменная для хранения признака типа токена

	int errorCode = 1;                                 // Флаг ошибки

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

	/*************************************************************************
	* Загрузить библиотеку                                                   *
	*************************************************************************/
	module = LoadLibrary(PKCS11_LIBRARY_NAME);
	CHECK(" LoadLibrary", module != NULL, exit);

	/*************************************************************************
	* Получить адрес функции запроса структуры с указателями на функции      *
	*************************************************************************/
	getFunctionList = (CK_C_GetFunctionList)GetProcAddress(module, "C_GetFunctionList");
	CHECK(" GetProcAddress", getFunctionList != NULL, unload_pkcs11);

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

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

	/*************************************************************************
	* Получить расширенную информацию о подключенном токене                  *
	*************************************************************************/
	printf("\nGetting extended token information...\n");

	tokenInfoEx.ulSizeofThisStructure = sizeof(tokenInfoEx);
	rv = functionListEx->C_EX_GetTokenInfoExtended(slots[0], &tokenInfoEx);
	CHECK_AND_LOG(" C_EX_GetTokenInfoExtended", rv == CKR_OK, rvToStr(rv), free_slots);

	/*************************************************************************
	* Определить класс токена                                                *
	*************************************************************************/
	if (tokenInfoEx.ulTokenClass == TOKEN_CLASS_S) {
		isRutokenS = 1;
	} else {
		isRutokenS = 0;
	}

	printf("Extended token information has been got successfully.\n");

	/*************************************************************************
	* Заполнить поля структуры CK_RUTOKEN_INIT_PARAM                         *
	*************************************************************************/
	printf("\nExtended initializing token...\n");

	initParam.ulSizeofThisStructure = sizeof(CK_RUTOKEN_INIT_PARAM);
	initParam.UseRepairMode = 0;
	initParam.pNewAdminPin = SO_PIN;
	initParam.ulNewAdminPinLen = SO_PIN_LEN;
	initParam.pNewUserPin = NEW_USER_PIN;
	initParam.ulNewUserPinLen = NEW_USER_PIN_LEN;
	initParam.ulMinAdminPinLen = isRutokenS ? 1 : 6;
	initParam.ulMinUserPinLen = isRutokenS ? 1 : 6;
	initParam.ChangeUserPINPolicy = (TOKEN_FLAGS_ADMIN_CHANGE_USER_PIN | TOKEN_FLAGS_USER_CHANGE_USER_PIN);
	initParam.ulMaxAdminRetryCount = MAX_ADMIN_RETRY_COUNT;
	initParam.ulMaxUserRetryCount = MAX_USER_RETRY_COUNT;
	initParam.pTokenLabel = tokenStdLabel;
	initParam.ulLabelLen = sizeof(tokenStdLabel);
	initParam.ulSmMode = 0;

	/*************************************************************************
	* Инициализировать токен                                                 *
	*************************************************************************/
	rv = functionListEx->C_EX_InitToken(slots[0], SO_PIN, SO_PIN_LEN, &initParam);
	CHECK_AND_LOG(" C_EX_InitToken", rv == CKR_OK, rvToStr(rv), free_slots);

	printf("Token has been initialized successfully.\n");

	/*************************************************************************
	* Начать работу с PIN-кодами                                             *
	*************************************************************************/
	printf("\nExtended PIN function test...\n");

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

	/*************************************************************************
	* Пробуем заблокировать PIN-код Пользователя                             *
	*************************************************************************/
	printf(" Locking user PIN...\n");
	for (i = 1; i < (MAX_USER_RETRY_COUNT + 1); ++i) {
		/*************************************************************************
		* Ввод неправильного PIN-кода Пользователя до блокировки PIN-кода        *
		*************************************************************************/
		printf("  %i. C_Login with wrong user PIN:", (int)i);
		rv = functionList->C_Login(session, CKU_USER, WRONG_USER_PIN, WRONG_USER_PIN_LEN);
		if (rv == CKR_PIN_INCORRECT) {
			printf(" -> Wrong PIN\n");
		} else if (rv == CKR_PIN_LOCKED) {
			printf(" -> PIN Locked\n");
		} else {
			CHECK_AND_LOG(" C_Login (CKU_USER)", rv == CKR_OK, rvToStr(rv), close_session);
		}
	}

	/*************************************************************************
	* Пробуем разблокировать PIN-код Пользователя                            *
	*************************************************************************/
	printf(" Unlocking user PIN...\n");

	/*************************************************************************
	* Выполнить аутентификацию администратора                                *
	*************************************************************************/
	rv = functionList->C_Login(session, CKU_SO, SO_PIN, SO_PIN_LEN);
	CHECK_AND_LOG(" C_Login (CKU_SO)", rv == CKR_OK, rvToStr(rv), close_session);

	/*************************************************************************
	* Разблокировать PIN-код Пользователя                                    *
	*************************************************************************/
	rv = functionListEx->C_EX_UnblockUserPIN(session);
	CHECK_AND_LOG(" C_EX_UnblockUserPIN", rv == CKR_OK, rvToStr(rv), logout);

	/*************************************************************************
	* Сбросить права доступа                                                 *
	*************************************************************************/
	rv = functionList->C_Logout(session);
	CHECK_AND_LOG(" C_Logout", rv == CKR_OK, rvToStr(rv), close_session);

	printf("Extended PIN function test has been completed successfully.\n");

	/*************************************************************************
	* Демонстрация работы с меткой токена                                    *
	*************************************************************************/
	printf("\nWork with token name...\n");

	/*************************************************************************
	* Выполнить аутентификацию Пользователя                                  *
	*************************************************************************/
	rv = functionList->C_Login(session, CKU_USER, NEW_USER_PIN, NEW_USER_PIN_LEN);
	CHECK_AND_LOG(" C_Login (CKU_USER)", rv == CKR_OK, rvToStr(rv), close_session);

	/*************************************************************************
	* Изменить метку токена на "длинную"                                     *
	*************************************************************************/
	rv = functionListEx->C_EX_SetTokenName(session, tokenLongLabel, arraysize(tokenLongLabel) - 1);
	CHECK_AND_LOG(" C_EX_SetTokenName", rv == CKR_OK, rvToStr(rv), logout);
	printf(" Token name: %s\n", tokenLongLabel);

	/*************************************************************************
	* Получить метку токена                                                  *
	*************************************************************************/
	rv = functionListEx->C_EX_GetTokenName(session, NULL_PTR, &tokenNameLength);
	CHECK_AND_LOG(" C_EX_GetTokenName", rv == CKR_OK, rvToStr(rv), logout);

	tokenName = (CK_CHAR_PTR)malloc((tokenNameLength + 1) * sizeof(CK_CHAR));
	CHECK(" Memory allocation for token name", tokenName != NULL_PTR, logout);

	rv = functionListEx->C_EX_GetTokenName(session, tokenName, &tokenNameLength);
	CHECK_AND_LOG(" C_EX_GetTokenName", rv == CKR_OK, rvToStr(rv), free_name);

	tokenName[tokenNameLength] = '\0';
	printf(" Token name: %s\n", tokenName);

	printf("Work with token name has been completed successfully.\n");

	/*************************************************************************
	* Вывести расширенную информацию о токене                                *
	*************************************************************************/
	printf("\nExtended information:\n");
	printf(" Token type:                  0x%8.8x ", (int)tokenInfoEx.ulTokenType);
	if (tokenInfoEx.ulTokenType == TOKEN_TYPE_RUTOKEN_ECP) {
		printf("(Rutoken ECP) \n");
	} else if (tokenInfoEx.ulTokenType == TOKEN_TYPE_RUTOKEN_LITE) {
		printf("(Rutoken Lite) \n");
	} else if (tokenInfoEx.ulTokenType == TOKEN_TYPE_RUTOKEN) {
		printf("(Rutoken S) \n");
	}

	printf(" Protocol number:             0x%8.8x \n", (int)tokenInfoEx.ulProtocolNumber);
	printf(" Microcode number:            0x%8.8x \n", (int)tokenInfoEx.ulMicrocodeNumber);
	printf(" Order number:                0x%8.8x \n", (int)tokenInfoEx.ulOrderNumber);
	printf(" Flags:                       0x%8.8x \n", (int)tokenInfoEx.flags);
	printf(" Max admin PIN length:        0x%8.8x \n", (int)tokenInfoEx.ulMaxAdminPinLen);
	printf(" Min admin PIN length:        0x%8.8x \n", (int)tokenInfoEx.ulMinAdminPinLen);
	printf(" Max user PIN length:         0x%8.8x \n", (int)tokenInfoEx.ulMaxUserPinLen);
	printf(" Min user PIN length:         0x%8.8x \n", (int)tokenInfoEx.ulMinUserPinLen);
	printf(" Max admin retry counter:     0x%8.8x \n", (int)tokenInfoEx.ulMaxAdminRetryCount);
	printf(" Admin retry counter:         0x%8.8x \n", (int)tokenInfoEx.ulAdminRetryCountLeft);
	printf(" Max user retry counter:      0x%8.8x \n", (int)tokenInfoEx.ulMaxUserRetryCount);
	printf(" User retry counter:          0x%8.8x \n", (int)tokenInfoEx.ulUserRetryCountLeft);
	printf(" Serial number:               ");
	for (i = 0; i < arraysize(tokenInfoEx.serialNumber); ++i) {
		printf("%02X ", tokenInfoEx.serialNumber[i]);
	}
	printf("\n Total memory:                0x%8.8x \n", (int)tokenInfoEx.ulTotalMemory);
	printf(" Free memory:                 0x%8.8x \n", (int)tokenInfoEx.ulFreeMemory);
	printf(" ATR:                         ");
	for (i = 0; i < tokenInfoEx.ulATRLen; ++i) {
		printf("%02X ", (int)tokenInfoEx.ATR[i]);
	}
	printf("\n Token class:                 0x%8.8x \n", (int)tokenInfoEx.ulTokenClass);
	printf(" Battery voltage (Bluetooth): 0x%8.8x \n", (int)tokenInfoEx.ulBatteryVoltage);
	printf(" BodyColor (Bluetooth):       0x%8.8x \n", (int)tokenInfoEx.ulBodyColor);
	printf(" Firmware checksum:           0x%8.8x \n", (int)tokenInfoEx.ulFirmwareChecksum);

	printf("\nExtended info test has been completed successfully.\n");

	/*************************************************************************
	* Установить PIN-код Пользователя по умолчанию                           *
	*************************************************************************/
	printf("\nChanging user PIN to default...\n");

	rv = functionList->C_SetPIN(session, NEW_USER_PIN, NEW_USER_PIN_LEN, USER_PIN, USER_PIN_LEN);
	CHECK_AND_LOG(" C_SetPIN", rv == CKR_OK, rvToStr(rv), free_name);

	printf("User PIN has been changed to default successfully.\n");

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

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

	/*************************************************************************
	* Очистить память, выделенную под имя токена                             *
	*************************************************************************/
free_name:
	free(tokenName);

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