/*************************************************************************
* Rutoken                                                                *
* Copyright (c) 2003-2017, CJSC Aktiv-Soft. All rights reserved.         *
* Подробная информация:  http://www.rutoken.ru                           *
*************************************************************************/

#import "Pkcs11.h"
#import "common.h"

#define SLOTS_MAX_COUNT 100

NSString* hexStringToDec(NSString* hex)
{
	unsigned long long int number;
	[[NSScanner scannerWithString:hex] scanHexLongLong:&number];
	return [NSString stringWithFormat:@"%zd", number];
}

@implementation PKCS11
+ (void)setActivationPassword:(NSString *)password forReader:(NSString *)reader
{
	NSMutableArray* cleanupOperations = [[NSMutableArray alloc] init];
	@try {
		LOG("Initialization...\n");

		CK_RV rv = CKR_OK; // Вспомогательная переменная для хранения кода возврата
		__block CK_FUNCTION_LIST_PTR functionList = NULL_PTR; // Указатель на список функций PKCS#11, хранящийся в структуре CK_FUNCTION_LIST
		CK_FUNCTION_LIST_EXTENDED_PTR functionListEx = NULL_PTR; // Указатель на список функций расширения PKCS#11, хранящийся в структуре CK_FUNCTION_LIST_EXTENDED
		
		/*************************************************************************
		 * Получить структуру с указателями на функции                           *
		 *************************************************************************/
		rv = C_GetFunctionList(&functionList);
		CHECK_AND_LOG(" Get function list", rv == CKR_OK, NULL);

		
		/*************************************************************************
		 * Получить структуру с указателями на функции расширения PKCS#11        *
		 *************************************************************************/
		rv = C_EX_GetFunctionListExtended(&functionListEx);
		CHECK_AND_LOG(" Get extended function list", rv == CKR_OK, NULL);

		/*************************************************************************
		 * Инициализировать библиотеку                                           *
		 *************************************************************************/
		rv = functionList->C_Initialize(NULL_PTR);
		CHECK_AND_LOG(" C_Initialize", rv == CKR_OK, NULL);
		
		/**********************************************************************
		 * Деинициализировать библиотеку при выходе из текущего блока         *
		 **********************************************************************/
		[cleanupOperations insertObject: ^{
			CK_RV rv = functionList->C_Finalize(NULL_PTR);
			CHECK_AND_LOG(" C_Finalize", rv == CKR_OK, NULL);
		}
								atIndex: 0];
		
		CK_SLOT_ID slots[SLOTS_MAX_COUNT];
		CK_ULONG ulSlotCount = sizeof(slots)/sizeof(slots[0]);
		/*************************************************************************
		 * Получить количество слотов c подключенными токенами                   *
		 *************************************************************************/
		rv = functionList->C_GetSlotList(CK_TRUE, slots, &ulSlotCount);
		CHECK_AND_LOG(" C_GetSlotList", rv == CKR_OK, NULL);
		
		CK_SLOT_ID slot;
		bool tokenFound = false;
		/*************************************************************************
		 * Найти токен с переданным серийным номером                             *
		 *************************************************************************/
		for (size_t i = 0 ; i < ulSlotCount; ++i) {
			@try {
				/*************************************************************************
				 * Получить информацию о токене                                          *
				 *************************************************************************/
				CK_TOKEN_INFO tokenInfo;
				rv = functionList->C_GetTokenInfo(slots[i], &tokenInfo);
				CHECK_AND_LOG(" C_GetTokenInfo", rv == CKR_OK, NULL);
				
				/*************************************************************************
				 * Сравнить серийный номер с переданным в функцию                        *
				 *************************************************************************/
				NSString* hexSerial = [[[NSString alloc]
										initWithBytes:tokenInfo.serialNumber length:sizeof(tokenInfo.serialNumber)
										encoding:NSUTF8StringEncoding]
									   stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@" "]];

				if([reader rangeOfString:hexStringToDec(hexSerial)].location != NSNotFound) {
					slot = slots[i];
					tokenFound = true;
					break;
				}
			} @catch(NSException* e) {};
		}

		CHECK_AND_LOG("Checking token with serial provided is availible", tokenFound, "Token not found");
		
		/*************************************************************************
		 * Проверить, что требуется инициализация шифрованного канала            *
		 *************************************************************************/
		__block CK_SESSION_HANDLE hSession;
		rv = functionList->C_OpenSession(slot, CKF_SERIAL_SESSION | CKF_RW_SESSION, NULL_PTR, NULL_PTR, &hSession);
		if(rv == CKR_OK) {
			/*************************************************************************
			 * Закрыть сессию при выходе из внешнего блока                           *
			 *************************************************************************/
			[cleanupOperations insertObject: ^{
				CK_RV rv = functionList->C_CloseSession(hSession);
				CHECK_AND_LOG(" C_CloseSession", rv == CKR_OK, NULL);
			}
									atIndex: 0];
		}
		CHECK_AND_LOG("Checking secure messaging activation is required", rv == CKR_FUNCTION_NOT_SUPPORTED,
					  "Secure messaging activation is not required");
		
		/*************************************************************************
		 * Установить пароль активации                                           *
		 *************************************************************************/
		rv = functionListEx->C_EX_SetActivationPassword(slot, (CK_UTF8CHAR_PTR)[password UTF8String]);
		CHECK_AND_LOG(" C_EX_SetActivationPassword", rv == CKR_OK, "Failed to set activation password");
			
		/*************************************************************************
		 * Проверить, что шифрованный канал инициализирован                      *
		 *************************************************************************/
		rv = functionList->C_OpenSession(slot, CKF_SERIAL_SESSION | CKF_RW_SESSION, NULL_PTR, NULL_PTR, &hSession);
		CHECK_AND_LOG(" C_OpenSession", rv == CKR_OK, "Setting activation password seems to have failed");
		/*************************************************************************
		 * Закрыть сессию при выходе из текущего блока                           *
		 *************************************************************************/
		[cleanupOperations insertObject: ^{
			CK_RV rv = functionList->C_CloseSession(hSession);
			CHECK_AND_LOG(" C_CloseSession", rv == CKR_OK, NULL);
		}
								atIndex: 0];
	} @catch(NSException* e) {
	} @finally {
		LOG("Finalizing...\n");
		for(void(^operation)() in cleanupOperations) {
			operation();
		}
	}
}
@end
