#import "CryptoPro.h"
#import "CPReader.h"

static const DWORD kGostProvType = PROV_GOST_2001_DH; // Тип криптопровайдера

@implementation CryptoPro

+ (NSArray*)getReaderList
{
	NSMutableArray* readerList = [[NSMutableArray alloc] init];
	
	NSMutableArray* cleanupOperations = [[NSMutableArray alloc] init];
	@try {
		LOG("Initializing CSP context...\n");
		__block HCRYPTPROV  hCryptProv;
		/*************************************************************************
		 * Получить дескриптор криптопровайдера                                  *
		 *************************************************************************/
		CSP_BOOL bResult = CryptAcquireContext(&hCryptProv, NULL, NULL, kGostProvType, CRYPT_VERIFYCONTEXT);
		CHECK_AND_LOG(" CryptAcquireContext", bResult, NULL);
		
		/**********************************************************************
		 * Освободить дескриптор криптопровайдера при выходе из текущего блока*
		 **********************************************************************/
		[cleanupOperations insertObject: ^{
			CSP_BOOL bResult = CryptReleaseContext(hCryptProv, 0);
			CHECK_AND_LOG(" CryptReleaseContext", bResult, NULL);
		} atIndex: 0];
		
		LOG("Crypt media enumeration...\n");
		/**********************************************************************
		 * Перечислить считыватели, доступные криптопровайдеру                *
		 **********************************************************************/
		for (BYTE searchStrategy = CRYPT_MEDIA | CRYPT_FIRST; ; searchStrategy = CRYPT_MEDIA) {
			DWORD dwLen = 0;
			/**********************************************************************
			 * Получить значение размера буфера для хранения имени считывателя    *
			 **********************************************************************/
			CSP_SetLastError(ERROR_SUCCESS);
			bResult = CryptGetProvParam(hCryptProv, PP_ENUMREADERS, NULL, &dwLen, searchStrategy);
			DWORD error = CSP_GetLastError();
			CHECK_AND_LOG(" CryptGetProvParam", bResult || error == ERROR_NO_MORE_ITEMS, NULL);
			
			if(!bResult && error == ERROR_NO_MORE_ITEMS) {
				LOG("  Checking all readers are enumerated -> OK\n"
					"  No more readers to enumerate\n");
				break;
			}
			
			NSMutableData* data = [[NSMutableData alloc] initWithCapacity:dwLen];
			
			/**********************************************************************
			 * Получить значение имени считывателя                                *
			 **********************************************************************/
			bResult = CryptGetProvParam(hCryptProv, PP_ENUMREADERS, (BYTE*)[data bytes], &dwLen, searchStrategy);
			error = CSP_GetLastError();
			CHECK_AND_LOG(" CryptGetProvParam", bResult || error == ERROR_NO_MORE_ITEMS, NULL);
			
			if(!bResult && error == ERROR_NO_MORE_ITEMS) {
				LOG("  Checking all readers are enumerated -> OK\n"
					"  No more readers to enumerate\n");
				break;
			}
			
			CPReader* reader = [[CPReader alloc] initWithData:(uint8_t*)[data bytes]];
			[readerList addObject: reader];
		}
	} @catch(NSException* e) {
	} @finally {
		LOG("Finalizing...\n");
		for(void(^operation)() in cleanupOperations) {
			operation();
		}
	}
	
	return readerList;
}

+ (NSArray*)getContainerList
{
	NSMutableArray* containerList = [[NSMutableArray alloc] init];
	
	NSMutableArray* cleanupOperations = [[NSMutableArray alloc] init];
	@try {
		LOG("Initializing CSP context...\n");
		__block HCRYPTPROV  hCryptProv;
		/*************************************************************************
		 * Получить дескриптор криптопровайдера                                  *
		 *************************************************************************/
		CSP_BOOL bResult = CryptAcquireContext(&hCryptProv, NULL, NULL, kGostProvType, CRYPT_VERIFYCONTEXT);
		CHECK_AND_LOG(" CryptAcquireContext", bResult, NULL);
		
		/**********************************************************************
		 * Освободить дескриптор криптопровайдера при выходе из текущего блока*
		 **********************************************************************/
		[cleanupOperations insertObject: ^{
			CSP_BOOL bResult = CryptReleaseContext(hCryptProv, 0);
			CHECK_AND_LOG("CryptReleaseContext", bResult, NULL);
		} atIndex: 0];
		
		LOG("Enumerate containers...\n");
		for (BYTE searchStrategy = CRYPT_MEDIA | CRYPT_FIRST; ; searchStrategy = CRYPT_MEDIA) {
			DWORD dwLen = 0;
			/**********************************************************************
			 * Получить значение размера буфера для хранения имени контейнера     *
			 **********************************************************************/
			CSP_SetLastError(ERROR_SUCCESS);
			bResult = CryptGetProvParam(hCryptProv, PP_ENUMCONTAINERS, NULL, &dwLen, searchStrategy);
			DWORD error = CSP_GetLastError();
			CHECK_AND_LOG(" CryptGetProvParam", bResult || error == ERROR_NO_MORE_ITEMS, NULL);
			
			if(!bResult && error == ERROR_NO_MORE_ITEMS) {
				LOG("  Checking all containers are enumerated -> OK\n"
					"  No more containers to enumerate\n");
				break;
			}
			
			NSMutableData* data = [[NSMutableData alloc] initWithCapacity:dwLen];
			
			/**********************************************************************
			 * Получить значение имени контейнера                                 *
			 **********************************************************************/
			CSP_SetLastError(ERROR_SUCCESS);
			bResult = CryptGetProvParam(hCryptProv, PP_ENUMCONTAINERS, (BYTE*)[data bytes], &dwLen, searchStrategy);
			error = CSP_GetLastError();
			CHECK_AND_LOG(" CryptGetProvParam", bResult || error == ERROR_NO_MORE_ITEMS, NULL);
			
			if(!bResult && error == ERROR_NO_MORE_ITEMS) {
				LOG("  Checking all containers are enumerated -> OK\n"
					"  No more containers to enumerate\n");
				break;
			}
			
			NSString* container = [NSString stringWithCString:(char*)[data bytes] encoding:NSASCIIStringEncoding];
			[containerList addObject: container];
		}
	} @catch(NSException* e) {
	} @finally {
		LOG("Finalizing...\n");
		for (void(^operation)() in cleanupOperations) {
			operation();
		}
	}
	
	return containerList;
}

+ (void)createNewKeyset:(NSString*)name forReader:(CPReader*) reader {
	NSString* containerName =  [NSString stringWithFormat: @"\\\\.\\%@\\%@", [reader name], name];
	
	NSMutableArray* cleanupOperations = [[NSMutableArray alloc] init];
	@try {
		LOG("Creating container...\n");
		__block HCRYPTPROV hCryptProv = 0;
		/*************************************************************************
		 * Создать ключевой контейнера с заданным именем                         *
		 *************************************************************************/
		CSP_BOOL bResult = CryptAcquireContext(&hCryptProv, [containerName cStringUsingEncoding:NSASCIIStringEncoding],
											   "Crypto-Pro GOST R 34.10-2001 KC1 CSP", kGostProvType, CRYPT_NEWKEYSET);
		CHECK_AND_LOG(" CryptAcquireContext", bResult, NULL);
		
		/**********************************************************************
		 * Освободить дескриптор криптопровайдера при выходе из текущего блока*
		 **********************************************************************/
		[cleanupOperations insertObject: ^{
			CSP_BOOL bResult = CryptReleaseContext(hCryptProv, 0);
			CHECK_AND_LOG(" CryptReleaseContext", bResult, NULL);
		} atIndex: 0];
		
		LOG("Creating signature key...\n");
		/**********************************************************************
		 * Сгенерировать ключ подписи в контейнере                            *
		 **********************************************************************/
		__block HCRYPTKEY hSignatureKey;
		bResult = CryptGenKey(hCryptProv, AT_SIGNATURE, 0, &hSignatureKey);
		CHECK_AND_LOG(" CryptGenKey(AT_SIGNATURE)", bResult, NULL);
		
		/**********************************************************************
		 * Освободить дескриптор ключа при выходе из текущего блока           *
		 **********************************************************************/
		[cleanupOperations insertObject: ^{
			CSP_BOOL bResult = CryptDestroyKey(hSignatureKey);
			CHECK_AND_LOG(" CryptDestroyKey", bResult, NULL);
		} atIndex: 0];
		
		LOG("Creating exchange key...\n");
		/**********************************************************************
		 * Сгенерировать ключ обмена в контейнере                             *
		 **********************************************************************/
		__block HCRYPTKEY hExchangeKey;
		bResult = CryptGenKey(hCryptProv, AT_KEYEXCHANGE, 0, &hExchangeKey);
		CHECK_AND_LOG(" CryptGenKey(AT_KEYEXCHANGE)", bResult, NULL);
		
		/**********************************************************************
		 * Освободить дескриптор ключа при выходе из текущего блока           *
		 **********************************************************************/
		[cleanupOperations insertObject: ^{
			CSP_BOOL bResult = CryptDestroyKey(hExchangeKey);
			CHECK_AND_LOG(" CryptDestroyKey", bResult, NULL);
		} atIndex: 0];
	} @catch (NSException* e) {
	} @finally {
		LOG("Finalizing...\n");
		for (void(^operation)() in cleanupOperations) {
			operation();
		}
	}
}
@end
