/*************************************************************************
* Rutoken                                                                *
* Copyright (c) 2003-2017, CJSC Aktiv-Soft. All rights reserved.         *
* Подробная информация:  http://www.rutoken.ru                           *
*------------------------------------------------------------------------*
* Пример работы с Рутокен при помощи Rutoken Cryptographic Service       *
* Provider на языке C                                                    *
*----------------------------------------------------------------------- *
* Пример передачи шифрованного симметричного ключа между абонентами      *
*----------------------------------------------------------------------- *
* На компьютере А создается RSA-пара (открытый и закрытый ключи),        *
* после чего открытый ключ передается на компьютер B, который создает    *
* сессионный симметричный ключ для обмена закрытой информацией с А       *
* и шифрует этот ключ на открытом ключе.                                 *
*                                                                        *
* В данном примере все несколько упрощено: все необходимые для данной    *
* схемы действия c CryptoAPI выполняются в одной программе.              *
*************************************************************************/

// Если не компилируется из-за основных типов CAPI, раскомментируйте:
// #define _WIN32_WINNT 0x0400

#include <stdio.h>
#include <conio.h>
#include <windows.h>
#include <wincrypt.h>

#define MYPROV L"Aktiv ruToken CSP v1.0"
#define NULL_KEY (HCRYPTKEY)0
#define NULL_PROV (HCRYPTPROV)0

/************************************************************************
* Вывести сообщение об ошибке и завершить программу                     *
************************************************************************/

DWORD Error(IN WCHAR* s  /* Указатель на строку с комментарием */
            )
{
	wprintf(L"Error occurred: %s\n", s);
	wprintf(L"Error number: 0x%x\n", GetLastError());
	return GetLastError();
}


int main(void)
{

	/* Имя используемого контейнера */
	LPWSTR pszContainerName = L"TestRtContainer";
	/* Хэндл CSP */
	HCRYPTPROV hCryptProv = NULL_PROV;
	/* Хэндл RSA-пары */
	HCRYPTKEY hRSAKey = NULL_KEY;
	/* Хэндл того же открытого ключа */
	HCRYPTKEY hPubKeyOnBComp = NULL_KEY;
	/* Хэндл сессионного симметричного ключа */
	HCRYPTKEY hSymKey = NULL_KEY;
	/* Хэндл импортированного симметричного ключа */
	HCRYPTKEY hSymKeyOnAComp = NULL_KEY;
	/* Буфер с блобом симметричного ключа */
	BYTE* pbSymKeyBlob = NULL;
	/* Буфер с блобом открытого ключа */
	BYTE* pbPubKeyBlob = NULL;
	/* Размер буфера с блобом симметричного ключа */
	DWORD dwSymKeyBlobLen = 0;
	/* Размер буфера с блобом открытого ключа */
	DWORD dwPubKeyBlobLen = 0;
	/* Ошибка */
	DWORD dwError = 0;

	for (;; ) {
		/*---------------------------------------------------------------------*/
		/* Шаг 1 : создается новый контейнер с именем TestRtContainer,         */
		/*         если он не существует.                                      */
		/*---------------------------------------------------------------------*/
		if (CryptAcquireContextW(&hCryptProv,        /* ([out]) хэндл CSP */
		                         pszContainerName,   /* имя контейнера */
		                         MYPROV,             /* имя провайдера */
		                         PROV_RSA_FULL,      /* тип провайдера */
		                         CRYPT_NEWKEYSET)) { /* создание нового контейнера */
			wprintf(L"Container TestRtContainer has been created successfully.\n");
		} else {
			/* если контейнер с таким именем уже существует:               */
			if (GetLastError() == NTE_EXISTS) {
				/* то он просто запрашивается:                             */
				if (CryptAcquireContextW(&hCryptProv,
				                         pszContainerName,
				                         MYPROV,
				                         PROV_RSA_FULL,
				                         0)) { /* 0 - запрос контейнера */
					wprintf(L"Container TestRtContainer has been acquired successfully.\n");
				} else {
					dwError = Error(L"Acquiring of TestRtContainer failed.\n");
					break;
				}
			} else {
				dwError = Error(L"Creation of TestRtContainer failed.\n");
				break;
			}
		}

		/* Был получен хэндл hCryptProv, который соответствует контейнеру      */
		/* TestRtContainer                                                     */
		/*---------------------------------------------------------------------*/
		/* Шаг 2 : создается RSA-пара на Рутокен в контейнере TestRtContainer. */
		/*---------------------------------------------------------------------*/

		/* Если в контейнере уже есть такая пара (KEYEXCHANGE в данном случае),*/
		/* то она уничтожается, и на ее месте появляется новая                 */

		if (CryptGenKey(hCryptProv,  /* в контейнере TestRtContainer */
		                AT_KEYEXCHANGE,
		                0,
		                &hRSAKey)) {
			wprintf(L"RSA key pair has been generated successfully.\n");
		} else {
			dwError = Error(L"RSA key pair generation failed.\n");
			break;
		}

		/*---------------------------------------------------------------------*/
		/* Шаг 3 : извлекается из контейнера блоб открытого ключа.             */
		/*---------------------------------------------------------------------*/

		/*  запрашивается длина блоба:                                         */
		if (CryptExportKey(hRSAKey,
		                   0,
		                   PUBLICKEYBLOB, /* блоб открытого ключа */
		                   0,
		                   NULL,
		                   &dwPubKeyBlobLen)) {
			wprintf(L"Length of public key BLOB: %d bit.\n", dwPubKeyBlobLen);
		} else {
			dwError = Error(L"Cannot obtain length of public key BLOB.\n");
			break;
		}

		/*  выделяется для него память                                         */
		pbPubKeyBlob = (BYTE*)malloc(dwPubKeyBlobLen);
		if (!pbPubKeyBlob) {
			wprintf(L"FAILED, out of memory.\n");
			dwError = (DWORD)-1;
			break;
		}
		/*  копируется блоб в выделенную память                                */
		if (CryptExportKey(hRSAKey,
		                   0,
		                   PUBLICKEYBLOB, /* блоб открытого ключа */
		                   0,
		                   pbPubKeyBlob,
		                   &dwPubKeyBlobLen)) {
			wprintf(L"Public key has been exported successfully.\n");
		} else {
			dwError = Error(L"Export of public key failed.\n");
			break;
		}

		/* Теперь предположим, что каким-либо образом этот открытый ключ был   */
		/* передан, т.е. содержимое массива pbPubKeyBlob с данного компьютера А*/
		/* было передано на компьютер B. Для простоты представим, что следующий*/
		/* код выполняется на компьютере B:                                    */

		/*---------------------------------------------------------------------*/
		/* Шаг 4 : импортируется открытый ключ RSA в CSP.                      */
		/*---------------------------------------------------------------------*/

		/* (при импорте открытого ключа записи в контейнер не происходит)      */
		if (CryptImportKey(hCryptProv,
		                   pbPubKeyBlob,
		                   dwPubKeyBlobLen,
		                   0,
		                   0,
		                   &hPubKeyOnBComp)) {
			wprintf(L"Public key has been imported successfully.\n");
		} else {
			dwError = Error(L"Import of public key failed.\n");
			break;
		}

		/*---------------------------------------------------------------------*/
		/* Шаг 5 : генерация симметричного ключ алгоритма RC2.                 */
		/*---------------------------------------------------------------------*/
		if (CryptGenKey(hCryptProv,
		                CALG_RC2,
		                CRYPT_EXPORTABLE, /* ключ можно извлечь из CSP */
		                &hSymKey)) {
			wprintf(L"RC2 key has been generated successfully.\n");
		} else {
			dwError = Error(L"RC2 key generation failed.\n");
			break;
		}

		/*---------------------------------------------------------------------*/
		/* Шаг 6 : определение длины блоба ключа RC2, зашифрованного на        */
		/*         открытом RSA-ключе.                                         */
		/*---------------------------------------------------------------------*/

		if (CryptExportKey(hSymKey,
		                   hPubKeyOnBComp,
		                   SIMPLEBLOB, /* блоб симметричного ключа */
		                   0,
		                   NULL,
		                   &dwSymKeyBlobLen)) {
			wprintf(L"Length of RC2 key BLOB has been obtained successfully.\n");
		} else {
			dwError = Error(L"Cannot obtain length of session key BLOB.\n");
			break;
		}

		/*   выделение памяти для блоба зашифрованного ключа RC2               */
		pbSymKeyBlob = (BYTE*)malloc(dwSymKeyBlobLen);
		if (!pbSymKeyBlob) {
			wprintf(L"FAILED, out of memory.\n");
			dwError = (DWORD)-1;
			break;
		}

		/*---------------------------------------------------------------------*/
		/* Шаг 7 : с помощью CryptExportKey на открытом ключе шифруется        */
		/*         симметричный ключ, соответствующий hSymKey, и результат     */
		/*         копируется в выделенную память.                             */
		/*---------------------------------------------------------------------*/

		if (CryptExportKey(hSymKey,
		                   hPubKeyOnBComp,
		                   SIMPLEBLOB,
		                   0,
		                   pbSymKeyBlob,
		                   &dwSymKeyBlobLen)) {
			wprintf(L"RC2 key BLOB has been obtained successfully.\n");
		} else {
			dwError = Error(L"Export of RC2 key BLOB failed.\n");
			break;
		}

		/* Таким образом, на компьютере B был создан сессионный ключ и         */
		/* экспортирован в блоб, в котором он зашифрован на открытом ключе А.  */
		/* Если послать этот блоб А, то он сможет расшифровать                 */
		/* его своим закрытым ключом. Поэтому теперь B может каждый раз        */
		/* создавать новый сессионный симметричный ключ и посылать A           */
		/* зашифрованные на нем сообщения + зашифрованный на открытом ключе    */
		/* сессионный ключ.                                                    */
		/* Сам процесс шифрования сообщений в данном примере не                */
		/* рассматривается.                                                    */

		/* Предположим, что следующий код выполняется снова на компьютере А.   */
		/* А получил блоб сессионного ключа pbSymKeyBlob.                      */

		/*---------------------------------------------------------------------*/
		/* Шаг 8 : Импорт RC2 ключа с одновременной расшифровкой.              */
		/*---------------------------------------------------------------------*/

		/*  Так как у А есть закрытый ключ, то он может расшифровать           */
		/*  зашифрованный сессионный ключ.                                     */

		/*  Импорт RC2-ключа, получение его хэндла                             */
		if (CryptImportKey(hCryptProv,
		                   pbSymKeyBlob,
		                   dwSymKeyBlobLen,
		                   hRSAKey, /* на этом ключе расшифровывается RC2-ключ */
		                   0,
		                   &hSymKeyOnAComp)) {
			wprintf(L"RC2 key BLOB has been imported successfully.\n");
		} else {
			dwError = Error(L"Import of RC2 key BLOB failed.\n");
			break;
		}
		break;
	}

	/* При помощи расшифрованного сессионного ключа А сможет расшифровывать сообщение B */

	free(pbSymKeyBlob);
	free(pbPubKeyBlob);

	if (hSymKey) {
		CryptDestroyKey(hSymKey);
	}
	if (hRSAKey) {
		CryptDestroyKey(hRSAKey);
	}
	if (hSymKeyOnAComp) {
		CryptDestroyKey(hSymKeyOnAComp);
	}
	if (hPubKeyOnBComp) {
		CryptDestroyKey(hPubKeyOnBComp);
	}

	if (hCryptProv) {
		/* Освобождение контекста                              */
		if (!CryptReleaseContext(hCryptProv,
		                         0)) {
			dwError = Error(L"Cannot release context.\n");
		} else {
			wprintf(L"Context has been released successfully.\n");
		}

		/* Удаление контейнера TestRtContainer                 */
		if (!CryptAcquireContextW(&hCryptProv,
		                          pszContainerName,
		                          MYPROV,
		                          PROV_RSA_FULL,
		                          CRYPT_DELETEKEYSET)) {
			dwError = Error(L"Cannot delete container TestRtContainer.\n");
		} else {
			wprintf(L"Container has been deleted successfully.\n");
		}
	}
	return dwError;
}
