/*************************************************************************
* Rutoken                                                                *
* Copyright (c) 2003-2017, CJSC Aktiv-Soft. All rights reserved.         *
* Подробная информация:  http://www.rutoken.ru                           *
*------------------------------------------------------------------------*
* Пример работы Рутокен с криптопровайдером КриптоПро CSP                *
* с использованием интерфейса CryptoAPI на языке C                       *
*------------------------------------------------------------------------*
* Создание ключевого контейнера КриптоПро CSP на носителе Рутокен:       *
*  - проверка наличия криптопровайдера КриптоПро CSP в системе;          *
*  - инициализация криптопровайдера;                                     *
*  - создание ключевого контейнера на Рутокен;                           *
*  - генерация ключевой пары ГОСТ Р 34.10-2001 для подписи в контейнер   *
*    на Рутокен;                                                         *
*  - генерация ключевой пары ГОСТ Р 34.10-2001 для обмена ключами        *
*    в контейнер на Рутокен;                                             *
*  - создание самоподписанного сертификата для ключевой пары для подписи;*
*  - импорт созданного сертификата в контейнер на Рутокен;               *
*  - освобождение памяти, дескрипторов и контекстов.                     *
*------------------------------------------------------------------------*
* Пример является самодостаточным                                        *
*************************************************************************/

#include "Common.h"

/* Шаблон расширений сертификата */
CERT_EXTENSION rgExtension[] =
{
	{szOID_KEY_USAGE, FALSE, {0, NULL} },
	{szOID_ENHANCED_KEY_USAGE, FALSE, {0, NULL} },
};

int main(void)
{
	LPWSTR szProvNameW = CRYPTOPRO_PROV_W;                 // Имя криптопровайдера, заменить на CRYPTOPRO_FKN_PROV_W для ФКН

	DWORD dwProvType = CRYPTOPRO_PROV_TYPE;                // Тип криптопровайдера

	HCRYPTPROV hProv = 0;                                  // Дескриптор криптопровайдера
	HCRYPTKEY hKey = 0;                                    // Дескриптор ключа

	PCCERT_CONTEXT pCertContext = NULL;                    // Указатель на контекст сертификата
	CERT_NAME_BLOB SubjectIssuerBlob = { 0 };              // Структура для информации о субъекте сертификата
	CRYPT_KEY_PROV_INFO KeyProvInfo = { 0 };               // Структура для информации о криптопровайдере
	CRYPT_ALGORITHM_IDENTIFIER SignatureAlgorithm = { 0 }; // Структура для информации об алгоритме подписи сертификата
	CERT_EXTENSIONS Extensions = { 0 };                    // Структура для информации о расширениях сертификата
	CERT_ENHKEY_USAGE ExtUsage = { 0 };                    // Структура для информации о расширенном назначении ключа
	CRYPT_BIT_BLOB KeyUsage = { 0 };                       // Структура для информации о назначении ключа

	DWORD dwSize = 0;                                      // Вспомогательная переменная для хранения длины буфера

	for (;; ) {
		/**********************************************************************
		* Шаг 1. Проверка наличия выбранного криптопровайдера в системе       *
		**********************************************************************/
		wprintf(L"Checking whether %s provider exists", szProvNameW);
		if (!CryptAcquireContextW(
		                          &hProv,                 // Указатель на дескриптор криптопровайдера
		                          NULL,                   // Имя ключевого контейнера
		                          szProvNameW,            // Имя криптопровайдера
		                          dwProvType,             // Тип криптопровайдера
		                          CRYPT_VERIFYCONTEXT)) { // Флаг операции, не требующей работы с контейнером
			if (GetLastError() == NTE_KEYSET_NOT_DEF) {
				wprintf(L" -> FAILED \nProvider has not been installed\n\n");
			} else {
				wprintf(L" -> FAILED, error number: 0x%0.8x\n\n", GetLastError());
			}
			break;
		}
		wprintf(L" -> OK\n");

		/**********************************************************************
		* Шаг 2. Создание ключевого контейнера с заданным именем на Рутокен   *
		**********************************************************************/
		wprintf(L"Creating key container with name \"%s\"", CONT_NAME_W);
		if (!CryptAcquireContextW(
		                          &hProv,             // Указатель на дескриптор криптопровайдера
		                          CONT_NAME_W,        // Имя ключевого контейнера
		                          szProvNameW,        // Имя криптопровайдера
		                          dwProvType,         // Тип криптопровайдера
		                          CRYPT_NEWKEYSET)) { // Флаги создания ключевого контейнера
			if (GetLastError() == NTE_EXISTS) {
				wprintf(L" -> FAILED\nRutoken already has key container with name \"%s\"\n", CONT_NAME_W);

				/**********************************************************************
				* Шаг 3. Инициализация криптопровайдера для работы с выбранным        *
				*        контейнером                                                  *
				**********************************************************************/
				wprintf(L"Choosing key container with name \"%s\"\n", CONT_NAME_W);

				if (!CryptAcquireContextW(
				                          &hProv,      // Дескриптор криптопровайдера
				                          CONT_NAME_W, // Имя ключевого контейнера
				                          szProvNameW, // Имя криптопровайдера
				                          dwProvType,  // Тип криптопровайдера
				                          0)) {
					wprintf(L" -> FAILED, error number: 0x%0.8x\n\n", GetLastError());
					break;
				}
				wprintf(L" -> OK\n");
			} else {
				wprintf(L" -> FAILED, error number: 0x%0.8x\n\n", GetLastError());
				break;
			}
		} else {
			wprintf(L" -> OK\n");
			wprintf(L"Container with name \"%s\" has been created.\n", CONT_NAME_W);
		}

		/**********************************************************************
		* Шаг 4. Проверка наличия ключевых пар в контейнере и их генерация    *
		* в случае отсутствия (КриптоПро CSP не записывает контейнер на токен *
		* без закрытого ключа)                                                *
		**********************************************************************/
		wprintf(L"\nChecking exchange key existence");
		if (!CryptGetUserKey(
		                     hProv,          // Дескриптор криптопровайдера
		                     AT_KEYEXCHANGE, // Тип ключа
		                     &hKey)) {       // Дескриптор ключа
			if (GetLastError() == NTE_NO_KEY) {
				wprintf(L" -> OK, key does not exist\n");
				wprintf(L"   Generating GOST R 34.10-2001 exchange key");
				if (!CryptGenKey(
				                 hProv,          // Дескриптор криптопровайдера
				                 AT_KEYEXCHANGE, // Флаг назначения ключа -- для обмена
				                 0,              // Флаги
				                 &hKey)) {       // Дескриптор ключа
					wprintf(L" -> FAILED, error number: 0x%0.8x\n\n", GetLastError());
					break;
				}
				wprintf(L" -> OK\n");
				wprintf(L"Exchange key pair has been created.\n");
			} else {
				wprintf(L" -> FAILED, error number: 0x%0.8x\n\n", GetLastError());
				break;
			}
		} else {
			wprintf(L" -> OK, key exists\n");
		}

		CryptDestroyKey(hKey);
		hKey = 0;

		wprintf(L"\nChecking signature key existence");
		if (!CryptGetUserKey(
		                     hProv,        // Дескриптор криптопровайдера
		                     AT_SIGNATURE, // Тип ключа
		                     &hKey)) {     // Дескриптор ключа
			if (GetLastError() == NTE_NO_KEY) {
				wprintf(L" -> OK, key does not exist\n");
				wprintf(L"   Generating GOST R 34.10-2001 signature key");
				if (!CryptGenKey(
				                 hProv,        // Дескриптор криптопровайдера
				                 AT_SIGNATURE, // Флаг назначения ключа -- для подписи
				                 0,            // Флаги
				                 &hKey)) {     // Дескриптор ключа
					wprintf(L" -> FAILED, error number: 0x%0.8x\n\n", GetLastError());
					break;
				}
				wprintf(L" -> OK\n");
				wprintf(L"Signature key pair has been created.\n");
			} else {
				wprintf(L" -> FAILED, error number: 0x%0.8x\n\n", GetLastError());
				break;
			}
		} else {
			wprintf(L" -> OK, key exists\n");
		}

		/**********************************************************************
		* Шаг 5. Проверка наличия сертификата в контейнере для ключевой пары, *
		*        предназначенной для подписи                                  *
		**********************************************************************/
		wprintf(L"\nChecking certificate existence for last key pair");
		if (!CryptGetKeyParam(
		                      hKey,           // Дескриптор ключа
		                      KP_CERTIFICATE, // Признак сертификата
		                      NULL,           // Указатель на буфер для получения сертификата
		                      &dwSize,        // Размер буфера
		                      0)) {
			if (GetLastError() == SCARD_E_NO_SUCH_CERTIFICATE) {
				wprintf(L" -> OK, certificate does not exist\n");
			} else {
				wprintf(L" -> FAILED, error number: 0x%0.8x\n\n", GetLastError());
				break;
			}
		} else {
			wprintf(L" -> OK, certificate exists\n");
		}

		/**********************************************************************
		* Шаг 6. Создание самоподписанного сертификата для ключевой пары,     *
		*        предназначенной для подписи                                  *
		**********************************************************************/
		if (GetLastError() == SCARD_E_NO_SUCH_CERTIFICATE) {
			wprintf(L"\nCreating certificate\n");

			/**********************************************************************
			* Шаг 6.1 Конвертирование имени субъекта сертификата                  *
			**********************************************************************/
			wprintf(L"  Converting certificate subject name");

			/**********************************************************************
			* Получение размера буфера с закодированной структурой                *
			**********************************************************************/
			if (!CertStrToNameW(
			                    ENC_TYPE,                  // Тип кодирования сертификата
			                    SUBJECT_NAME_W,            // Указатель на кодируемую строку X.500
			                    CERT_X500_NAME_STR,        // Тип кодируемой строки
			                    NULL,
			                    NULL,                      // Указатель на буфер для закодированной структуры
			                    &SubjectIssuerBlob.cbData, // Указатель на размер буфера
			                    NULL)) {                   // Указатель на расширенную информацию об ошибке
				wprintf(L" -> FAILED, error number: 0x%0.8x\n\n", GetLastError());
				break;
			}

			/**********************************************************************
			* Выделение памяти для буфера                                         *
			**********************************************************************/
			SubjectIssuerBlob.pbData = (BYTE*)malloc(SubjectIssuerBlob.cbData * sizeof(BYTE));
			if (!SubjectIssuerBlob.pbData) {
				wprintf(L" -> FAILED, out of memory\n\n");
				break;
			}

			/**********************************************************************
			* Получение указателя на буфер с закодированной	структурой            *
			**********************************************************************/
			if (!CertStrToNameW(
			                    ENC_TYPE,                  // Тип кодирования сертификата
			                    SUBJECT_NAME_W,            // Указатель на кодируемую строку X.500
			                    CERT_X500_NAME_STR,        // Тип кодируемой строки
			                    NULL,
			                    SubjectIssuerBlob.pbData,  // Указатель на буфер для закодированной структуры
			                    &SubjectIssuerBlob.cbData, // Указатель на размер буфера
			                    NULL)) {                   // Указатель на расширенную информацию об ошибке
				wprintf(L" -> FAILED, error number: 0x%0.8x\n\n", GetLastError());
				break;
			}
			wprintf(L" -> OK\n");

			/**********************************************************************
			* Шаг 6.2 Заполнение структуры с информацией о ключевом контейнере    *
			**********************************************************************/
			KeyProvInfo.pwszContainerName = CONT_NAME_W;        // Имя контейнера
			KeyProvInfo.pwszProvName = szProvNameW;             // Имя криптопровайдера
			KeyProvInfo.dwProvType = dwProvType;                // Тип криптопровайдера
			KeyProvInfo.dwFlags = 0;                            // Флаги
			KeyProvInfo.cProvParam = 0;                         // Количество записей в массиве с расширенной информацией
			KeyProvInfo.rgProvParam = NULL;                     // Указатель на массив с расширенной информацией о контейнере
			KeyProvInfo.dwKeySpec = AT_SIGNATURE;               // Тип ключа -- для подписи

			/**********************************************************************
			* Шаг 6.3 Заполнение структуры с информацией об алгоритме подписи     *
			**********************************************************************/
			SignatureAlgorithm.pszObjId = OID_GOST3410;

			/**********************************************************************
			* Шаг 6.4 Заполнение структуры с информацией о назначении ключа       *
			**********************************************************************/
			wprintf(L"  Encoding data about key usage");

			KeyUsage.cbData = 1;
			KeyUsage.pbData = &KEY_USAGE;
			KeyUsage.cUnusedBits = 0;

			if (!CryptEncodeObject(
			                       ENC_TYPE,                    // Тип кодирования
			                       rgExtension[0].pszObjId,     // Указатель на OID кодируемой структуры
			                       &KeyUsage,                   // Указатель на кодируемую структуру
			                       NULL,                        // Указатель на буфер для закодированной структуры
			                       &rgExtension[0].Value.cbData // Указатель на размер буфера
			                       )) {
				wprintf(L" -> FAILED, error number: 0x%0.8x\n\n", GetLastError());
				break;
			}

			/**********************************************************************
			* Выделение памяти для буфера                                         *
			**********************************************************************/
			rgExtension[0].Value.pbData = (BYTE*)malloc(rgExtension[0].Value.cbData * sizeof(BYTE));
			if (!rgExtension[0].Value.pbData) {
				wprintf(L" -> FAILED, out of memory\n\n");
				break;
			}

			if (!CryptEncodeObject(
			                       ENC_TYPE,                    // Тип кодирования
			                       rgExtension[0].pszObjId,     // Указатель на OID кодируемой структуры
			                       &KeyUsage,                   // Указатель на кодируемую структуру
			                       rgExtension[0].Value.pbData, // Указатель на буфер для закодированной структуры
			                       &rgExtension[0].Value.cbData // Указатель на размер буфера
			                       )) {
				wprintf(L" -> FAILED, error number: 0x%0.8x\n\n", GetLastError());
				break;
			}
			wprintf(L" -> OK\n");

			/**********************************************************************
			* Шаг 6.5 Заполнение структуры с информацией о расширенном            *
			* назначении ключа                                                    *
			**********************************************************************/
			wprintf(L"  Encoding data about extended key usage");

			ExtUsage.cUsageIdentifier = 1;
			ExtUsage.rgpszUsageIdentifier = EXT_KEY_USAGE;

			if (!CryptEncodeObject(
			                       ENC_TYPE,                    // Тип кодирования
			                       rgExtension[1].pszObjId,     // Указатель на OID кодируемой структуры
			                       &ExtUsage,                   // Указатель на кодируемое значение структуры
			                       NULL,                        // Указатель на буфер для закодированной структуры
			                       &rgExtension[1].Value.cbData // Указатель на размер буфера
			                       )) {
				wprintf(L" -> FAILED, error number: 0x%0.8x\n\n", GetLastError());
				break;
			}

			/**********************************************************************
			* Выделение памяти для буфера                                         *
			**********************************************************************/
			rgExtension[1].Value.pbData = (BYTE*)malloc(rgExtension[1].Value.cbData * sizeof(BYTE));
			if (!rgExtension[1].Value.pbData) {
				wprintf(L" -> FAILED, out of memory\n\n");
				break;
			}

			if (!CryptEncodeObject(
			                       ENC_TYPE,                    // Тип кодирования
			                       rgExtension[1].pszObjId,     // Указатель на OID кодируемой структуры
			                       &ExtUsage,                   // Указатель на кодируемую структуру
			                       rgExtension[1].Value.pbData, // Указатель на буфер для закодированной структуры
			                       &rgExtension[1].Value.cbData // Указатель на размер буфера
			                       )) {
				wprintf(L" -> FAILED, error number: 0x%0.8x\n\n", GetLastError());
				break;
			}
			wprintf(L" -> OK\n");

			/**********************************************************************
			* Шаг 6.6 Заполнение структуры с расширениями сертификата             *
			**********************************************************************/
			Extensions.cExtension = GetArraySize(rgExtension);
			Extensions.rgExtension = rgExtension;

			/**********************************************************************
			* Шаг 6.7 Создание самоподписанного сертификата                       *
			**********************************************************************/
			wprintf(L"  Creating self-signed certificate");

			pCertContext = CertCreateSelfSignCertificate(
			                                             hProv,               // Дескриптор криптопровайдера
			                                             &SubjectIssuerBlob,  // Указатель на имя субъекта сертификата
			                                             0,                   // Флаги функции
			                                             &KeyProvInfo,        // Указатель на структуру с информацией о провайдере
			                                             &SignatureAlgorithm, // Указатель на структуру с информацией об алгоритме
			                                             0,                   // Указатель на время издания сертификата (0 -- текущее системное время)
			                                             0,                   // Указатель на время окончания действия сертификата (0 -- через год)
			                                             &Extensions          // Указатель на структуру с информацией о расширениях сертификата
			                                             );
			if (pCertContext) {
				wprintf(L" -> OK\n");
			} else {
				wprintf(L" -> FAILED, error number: 0x%0.8x\n\n", GetLastError());
				break;
			}

			/**********************************************************************
			* Шаг 7. Импорт сертификата на токен                                  *
			**********************************************************************/
			wprintf(L"Importing certificate to container on token");

			if (!CryptSetKeyParam(
			                      hKey,                        // Дескриптор ключа, соответствующий сертификату
			                      KP_CERTIFICATE,              // Признак сертификата
			                      pCertContext->pbCertEncoded, // Указатель на буфер с сертификатом
			                      0)) {                        // Флаги
				wprintf(L" -> FAILED, error number: 0x%0.8x\n\n", GetLastError());
				break;
			}
			wprintf(L" -> OK\n");
			wprintf(L"\nCertificate has been created and imported.\n");
		}
		wprintf(L"\nContainer with name \"%s\" has all required objects.\n\n", CONT_NAME_W);
		break;
	}

	/**********************************************************************
	* Шаг 8. Освобождение памяти                                          *
	**********************************************************************/
	/**********************************************************************
	* Шаг 8.1 Освобождение дескриптора ключа                              *
	**********************************************************************/
	if (hKey) {
		CryptDestroyKey(hKey);
	}

	/**********************************************************************
	* Шаг 8.2 Освобождение контекстов сертификата и криптопровайдера      *
	**********************************************************************/
	if (pCertContext) {
		CertFreeCertificateContext(pCertContext);
	}
	if (hProv) {
		CryptReleaseContext(hProv, 0);
	}

	/**********************************************************************
	* Шаг 8.3 Освобождение памяти                                         *
	**********************************************************************/
	free(rgExtension[0].Value.pbData);
	free(rgExtension[1].Value.pbData);
	free(SubjectIssuerBlob.pbData);

	if (GetLastError() == ERROR_SUCCESS || GetLastError() == NTE_EXISTS) {
		wprintf(L"Test has been completed successfully.");
	} else {
		wprintf(L"Test has failed. Error number: 0x%0.8x.", GetLastError());
	}
	return 0;
}
