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

#include "Common.h"

/* Структура для имен объектных идентификаторов */
typedef struct {
	LPCSTR Name;
	LPCSTR Value;
}OID;

/**********************************************************************
* Печать блоба с серийным номером                                     *
**********************************************************************/
VOID PrintSNBlob(CRYPT_INTEGER_BLOB IntBlob) // Блоб для печати
{
	DWORD i = 0;                             // Вспомогательная переменная-счетчик

	for (i = 0; i < IntBlob.cbData; ++i) {
		wprintf(L"%0.2x ", IntBlob.pbData[IntBlob.cbData - i - 1]);
	}
}

/**********************************************************************
* Печать блоба типа CRYPT_INTEGER_BLOB                                *
**********************************************************************/
VOID PrintIntBlob(BYTE* pIntBlob) // Указатель на блоб для печати
{
	DWORD i = 0;                  // Вспомогательная переменная-счетчик

	for (i = 0; i < ((CRYPT_INTEGER_BLOB*)pIntBlob)->cbData; ++i) {
		wprintf(L"%0.2x ", ((CRYPT_INTEGER_BLOB*)pIntBlob)->pbData[i]);
	}
}

/**********************************************************************
* Печать блоба типа CRYPT_BIT_BLOB                                    *
**********************************************************************/
VOID PrintBitBlob(CRYPT_BIT_BLOB BitBlob) // Блоб для печати
{
	DWORD i = 0;                          // Вспомогательная переменная-счетчик

	for (i = 0; i < BitBlob.cbData - BitBlob.cUnusedBits; ++i) {
		wprintf(L"%0.2x ", BitBlob.pbData[i]);
	}
	wprintf(L"\n");
}

/**********************************************************************
* Печать структуры с именем субъекта или издателя сертификата         *
**********************************************************************/
BOOL PrintName(CERT_NAME_BLOB pbName)
{
	LPWSTR lpszData = NULL; // Указатель на буфер для получения конвертированной строки
	DWORD cbData = 0;       // Размер буфера

	for (;; ) {
		/**********************************************************************
		* Шаг 1. Конвертация строки с закодированным именем                   *
		**********************************************************************/
		/**********************************************************************
		* Получение длины конвертированной строки                             *
		**********************************************************************/
		cbData = CertNameToStrW(
		                        ENC_TYPE,           // Тип кодирования
		                        &pbName,            // Указатель на строку для конвертирования
		                        CERT_X500_NAME_STR, // Тип форматирования строки
		                        NULL,               // Указатель на буфер для конвертированной строки
		                        cbData              // Длина буфера для конвертированной строки
		                        );
		if (cbData) {
			/**********************************************************************
			* Выделение памяти для буфера                                         *
			**********************************************************************/
			lpszData = (LPWSTR)malloc(cbData * sizeof(WCHAR));
			if (!lpszData) {
				break;
			}

			/**********************************************************************
			* Получение указателя на буфер с конвертированной строкой             *
			**********************************************************************/
			if (CertNameToStrW(
			                   ENC_TYPE,           // Тип кодирования
			                   &pbName,            // Указатель на строку для конвертирования
			                   CERT_X500_NAME_STR, // Тип форматирования строки
			                   lpszData,           // Указатель на буфер для конвертированной строки
			                   cbData)) {          // Длина буфера для конвертированной строки
				wprintf(L"%s\n", lpszData);
			}
		}
		break;
	}

	free(lpszData);

	return GetLastError() == ERROR_SUCCESS;
}

/**********************************************************************
* Печать срока действия сертификата                                   *
**********************************************************************/
BOOL PrintTime(FILETIME FileTime)
{
	SYSTEMTIME SysTime;

	/**********************************************************************
	* Шаг 1. Конвертация времени файла в формат системного времени (UTC)  *
	**********************************************************************/
	if (FileTimeToSystemTime(
	                         &FileTime,   // Указатель на структуру с временем файла
	                         &SysTime)) { // Указатель на структуру с системным временем
		wprintf(L"%.2d.%.2d.%.4d ", SysTime.wDay, SysTime.wMonth, SysTime.wYear);
		wprintf(L"%.2d:%.2d:%.2d (UTC)", SysTime.wHour, SysTime.wMinute, SysTime.wSecond);
		wprintf(L"\n");
	}

	return GetLastError() == ERROR_SUCCESS;
}

/**********************************************************************
* Печать расширения использования ключа                               *
**********************************************************************/
VOID PrintKeyUsage(BYTE* pbCertExtInfo)
{
	/* Структура для флагов использования ключа */
	typedef struct {
		LPCSTR Name;
		DWORD Value;
	}keyUsage;

	/* Флаги использования ключа */
	keyUsage KeyUsageRestriction[] = {
		{ "Digital Signature", CERT_DIGITAL_SIGNATURE_KEY_USAGE },  // Цифровая подпись
		{ "Non Repudiation", CERT_NON_REPUDIATION_KEY_USAGE },      // Неотрекаемость
		{ "Key Encipherment", CERT_KEY_ENCIPHERMENT_KEY_USAGE },    // Шифрование ключей
		{ "Data Encipherment", CERT_DATA_ENCIPHERMENT_KEY_USAGE },  // Шифрование данных
		{ "Key Agreement", CERT_KEY_AGREEMENT_KEY_USAGE },          // Согласование ключа
		{ "Key Cert Sign", CERT_KEY_CERT_SIGN_KEY_USAGE },          // Подпись сертификата
		{ "CRL Sign", CERT_CRL_SIGN_KEY_USAGE },                    // Подпись CRL
		{ "Encipher Only", CERT_ENCIPHER_ONLY_KEY_USAGE },          // Только шифрование
		{ "Decipher Only", (CERT_DECIPHER_ONLY_KEY_USAGE << 8) },   // Только расшифрование
	};

	DWORD i = 0;                // Вспомогательная переменная-счетчик

	for (i = 0; i < GetArraySize(KeyUsageRestriction); ++i) {
		if (((CERT_KEY_ATTRIBUTES_INFO*)pbCertExtInfo)->IntendedKeyUsage.cbData & KeyUsageRestriction[i].Value) {
			printf("\n        %s", KeyUsageRestriction[i].Name);
		}
	}
}

/**********************************************************************
* Печать расширенного использования ключа                             *
**********************************************************************/
VOID PrintEnhKeyUsage(BYTE* pbCertExtInfo)
{
	OID ExtKeyUsage[] = {
		{ "Extended key purpose", szOID_PKIX_KP },
		{ "Server Authentication", szOID_PKIX_KP_SERVER_AUTH },
		{ "Client Authentication", szOID_PKIX_KP_CLIENT_AUTH },
		{ "Code Signing", szOID_PKIX_KP_CODE_SIGNING },
		{ "Email Protection", szOID_PKIX_KP_EMAIL_PROTECTION },
		{ "Timestamping ", szOID_PKIX_KP_TIMESTAMP_SIGNING },
		{ "OCSP Signing", szOID_PKIX_KP_OCSP_SIGNING },
	};

	DWORD i, j = 0;             // Вспомогательные переменные-счетчики

	for (i = 0; i < ((CERT_ENHKEY_USAGE*)pbCertExtInfo)->cUsageIdentifier; ++i) {
		wprintf(L"\n        ");
		for (j = 0; j < GetArraySize(ExtKeyUsage); ++j) {
			if (!strcmp(ExtKeyUsage[j].Value,
			            ((CERT_ENHKEY_USAGE*)pbCertExtInfo)->rgpszUsageIdentifier[i])) {
				printf("%s ", ExtKeyUsage[j].Name);
			}
		}
		printf("%s",
		       ((CERT_ENHKEY_USAGE*)pbCertExtInfo)->rgpszUsageIdentifier[i]);
	}
}

/**********************************************************************
* Печать метода доступа                                               *
**********************************************************************/
VOID PrintAccessMethod(BYTE* pbCertExtInfo)
{
	OID AccessMethod[] = {
		{ "Certificate Authority Issuers", szOID_PKIX_CA_ISSUERS },
		{ "Certificate Authority Repository", szOID_PKIX_CA_REPOSITORY },
		{ "On-line Certificate Status Protocol", szOID_PKIX_OCSP },
		{ "Timestamping", szOID_PKIX_TIME_STAMPING },
	};

	DWORD i, j = 0;             // Вспомогательные переменные-счетчик

	for (i = 0; i < ((CERT_AUTHORITY_INFO_ACCESS*)pbCertExtInfo)->cAccDescr; ++i) {
		for (j = 0; j < GetArraySize(AccessMethod); ++j) {
			if (!strcmp(AccessMethod[j].Value,
			            ((CERT_AUTHORITY_INFO_ACCESS*)pbCertExtInfo)->rgAccDescr[i].pszAccessMethod)) {
				printf("\n        Access Method [%i]: %s %s",
				       i + 1,
				       AccessMethod[j].Name,
				       ((CERT_AUTHORITY_INFO_ACCESS*)pbCertExtInfo)->rgAccDescr[i].pszAccessMethod);
			}
		}
		wprintf(L"\n        URL: %s",
		        ((CERT_AUTHORITY_INFO_ACCESS*)pbCertExtInfo)->rgAccDescr[i].AccessLocation.pwszURL);
	}
}

/**********************************************************************
* Печать точек распространения списка отозванных сертифкатов          *
**********************************************************************/
VOID PrintDistPoints(BYTE* pbCertExtInfo)
{
	DWORD i = 0;                // Вспомогательная переменная-счетчик

	for (i = 0; i < ((CRL_DIST_POINTS_INFO*)pbCertExtInfo)->cDistPoint; ++i) {
		wprintf(L"\n        URL [%i]: %s ",
		        i + 1,
		        ((CRL_DIST_POINTS_INFO*)pbCertExtInfo)->rgDistPoint[i].DistPointName.FullName.rgAltEntry->pwszURL);
	}
}

/**********************************************************************
* Печать расширений сертификата                                       *
**********************************************************************/
BOOL PrintExtensions(PCERT_INFO pCertInfo)
{
	/* Объектные идентификаторы расширений сертификата */
	OID extOID[] = {
		{ "Authority Key Identifier", szOID_AUTHORITY_KEY_IDENTIFIER },
		{ "Subject Key Identifier", szOID_KEY_ATTRIBUTES },
		{ "Certificate Policies", szOID_CERT_POLICIES_95 },
		{ "Key Usage Restriction", szOID_KEY_USAGE_RESTRICTION },
		{ "Subject Alternative Name", szOID_SUBJECT_ALT_NAME },
		{ "Issuer Alternative Name", szOID_ISSUER_ALT_NAME },
		{ "Basic Constraints", szOID_BASIC_CONSTRAINTS },
		{ "Key Usage", szOID_KEY_USAGE },
		{ "szOID_PRIVATEKEY_USAGE_PERIOD", szOID_PRIVATEKEY_USAGE_PERIOD },
		{ "szOID_BASIC_CONSTRAINTS2", szOID_BASIC_CONSTRAINTS2 },
		{ "Certificate Policies", szOID_CERT_POLICIES },
		{ "szOID_ANY_CERT_POLICY", szOID_ANY_CERT_POLICY },
		{ "szOID_INHIBIT_ANY_POLICY", szOID_INHIBIT_ANY_POLICY },
		{ "Authority Key Identifier", szOID_AUTHORITY_KEY_IDENTIFIER2 },
		{ "Subject Key Identifier", szOID_SUBJECT_KEY_IDENTIFIER },
		{ "szOID_SUBJECT_ALT_NAME2", szOID_SUBJECT_ALT_NAME2 },
		{ "szOID_ISSUER_ALT_NAME2", szOID_ISSUER_ALT_NAME2 },
		{ "szOID_CRL_REASON_CODE", szOID_CRL_REASON_CODE },
		{ "szOID_REASON_CODE_HOLD", szOID_REASON_CODE_HOLD },
		{ "CRL Distribution Points", szOID_CRL_DIST_POINTS },
		{ "Extended Key Usage", szOID_ENHANCED_KEY_USAGE },
		{ "szOID_ANY_ENHANCED_KEY_USAGE", szOID_ANY_ENHANCED_KEY_USAGE },
		{ "szOID_CRL_NUMBER", szOID_CRL_NUMBER },
		// Internet Public Key Infrastructure (PKIX)
		{ "szOID_PKIX", szOID_PKIX },
		{ "szOID_PKIX_PE", szOID_PKIX_PE },
		{ "Authority Info Access", szOID_AUTHORITY_INFO_ACCESS },
		{ "szOID_SUBJECT_INFO_ACCESS", szOID_SUBJECT_INFO_ACCESS },
		{ "szOID_BIOMETRIC_EXT", szOID_BIOMETRIC_EXT },
		{ "szOID_QC_STATEMENTS_EXT", szOID_QC_STATEMENTS_EXT },
		{ "szOID_LOGOTYPE_EXT", szOID_LOGOTYPE_EXT },
	};

	PCERT_EXTENSION pCertExt = NULL;    // Структура с информацией о расширении
	BYTE* pbCertExtInfo = NULL;         // Указатель на буфер для декодированной структуры
	DWORD cbCertExtInfo = 0;            // Размер буфера с декодированной структурой
	DWORD i = 0;                        // Вспомогательная переменная-счетчик

	for (i = 0; i < GetArraySize(extOID); ++i) {
		/**********************************************************************
		* Шаг 1. Поиск структуры с указанными OID расширения сертификата      *
		**********************************************************************/
		pCertExt = CertFindExtension(
		                             extOID[i].Value,       // Указатель на объектный идентификатор
		                             pCertInfo->cExtension, // Количество структур в массиве
		                             pCertInfo->rgExtension // Массив структур с расширениями
		                             );

		if (!pCertExt) {
			continue;
		}

		/**********************************************************************
		* Шаг 2. Конвертирование структуры с информацией о расширении         *
		**********************************************************************/
		/**********************************************************************
		* Получение размера буфера для конвертированной структуры             *
		**********************************************************************/
		if (!CryptDecodeObject(
		                       ENC_TYPE,               // Тип кодирования
		                       extOID[i].Value,        // Указатель на объектный идентификатор
		                       pCertExt->Value.pbData, // Указатель на буфер со структурой для декодирования
		                       pCertExt->Value.cbData, // Размер буфера
		                       0,                      // Флаги
		                       NULL,                   // Указатель на буфер для конвертированной структуры
		                       &cbCertExtInfo)) {      // Размер буфера
			wprintf(L" -> FAILED, error number: 0x%0.8x\n\n", GetLastError());
			break;
		}

		/**********************************************************************
		* Выделение памяти для буфера                                         *
		**********************************************************************/
		pbCertExtInfo = (BYTE*)malloc(cbCertExtInfo * sizeof(BYTE));
		if (!pbCertExtInfo) {
			wprintf(L" -> FAILED, error number: 0x%0.8x\n\n", GetLastError());
			break;
		}

		/**********************************************************************
		* Получение указателя на буфер с конвертированной структурой          *
		**********************************************************************/
		if (!CryptDecodeObject(
		                       ENC_TYPE,               // Тип кодирования
		                       extOID[i].Value,        // Указатель на объектный идентификатор
		                       pCertExt->Value.pbData, // Указатель на буфер со структурой для декодирования
		                       pCertExt->Value.cbData, // Размер буфера
		                       0,                      // Флаги
		                       pbCertExtInfo,          // Указатель на буфер для конвертированной структуры
		                       &cbCertExtInfo)) {      // Размер буфера
			wprintf(L" -> FAILED, error number: 0x%0.8x\n\n", GetLastError());
			free(pbCertExtInfo);
			break;
		}

		/**********************************************************************
		* Шаг 3. Печать наименования расширения                               *
		**********************************************************************/
		printf("\n     %s: ", extOID[i].Name);

		/**********************************************************************
		* Шаг 4. Печать использования ключа                                   *
		**********************************************************************/
		if (!strcmp(extOID[i].Value, szOID_KEY_USAGE)) {
			PrintKeyUsage(pbCertExtInfo);
		}

		/**********************************************************************
		* Шаг 5. Печать идентификаторов ключей субъекта и издателя            *
		**********************************************************************/
		if (!strcmp(extOID[i].Value, szOID_AUTHORITY_KEY_IDENTIFIER2) ||
		    !strcmp(extOID[i].Value, szOID_SUBJECT_KEY_IDENTIFIER)) {
			PrintIntBlob(pbCertExtInfo);
		}

		/**********************************************************************
		* Шаг 6. Печать расширенного использования ключа                      *
		**********************************************************************/
		if (!strcmp(extOID[i].Value, szOID_ENHANCED_KEY_USAGE)) {
			PrintEnhKeyUsage(pbCertExtInfo);
		}

		/**********************************************************************
		* Шаг 7. Печать точек распространения списка отозванных сертификатов  *
		**********************************************************************/
		if (!strcmp(extOID[i].Value, szOID_CRL_DIST_POINTS)) {
			PrintDistPoints(pbCertExtInfo);
		}

		/**********************************************************************
		* Шаг 8. Печать информации о доступе к сведениям ЦС                   *
		**********************************************************************/
		if (!strcmp(extOID[i].Value, szOID_AUTHORITY_INFO_ACCESS)) {
			PrintAccessMethod(pbCertExtInfo);
		}

		free(pbCertExtInfo);
		cbCertExtInfo = 0;
	}

	return GetLastError() == ERROR_SUCCESS;
}

/**********************************************************************
* Печать отпечатка сертификата                                        *
**********************************************************************/
BOOL PrintFingerprint(PCCERT_CONTEXT pCertContext)
{
	BYTE* pbStamp = NULL;   // Указатель на буфер с отпечатком
	DWORD cbStamp = 0;      // Размер буфера
	DWORD i = 0;            // Вспомогательная переменная-счетчик

	for (;; ) {

		/**********************************************************************
		* Получение размера буфера для получения отпечатка сертификата        *
		**********************************************************************/
		if (!CertGetCertificateContextProperty(
		                                       pCertContext,      // Указатель на контекст сертификата
		                                       CERT_HASH_PROP_ID, // Флаг получения SHA1 хэша сертификата
		                                       NULL,              // Указатель на буфер для получения отпечатка
		                                       &cbStamp)) {       // Размер буфера
			wprintf(L" -> FAILED, error number: 0x%0.8x\n\n", GetLastError());
			break;
		}

		/**********************************************************************
		* Выделение памяти для буфера                                         *
		**********************************************************************/
		pbStamp = (BYTE*)malloc(cbStamp * sizeof(BYTE));
		if (!pbStamp) {
			wprintf(L" -> FAILED, error number: 0x%0.8x\n\n", GetLastError());
			break;
		}

		/**********************************************************************
		* Получение указателя на буфер с отпечатком сертификата               *
		**********************************************************************/
		if (!CertGetCertificateContextProperty(
		                                       pCertContext,      // Указатель на контекст сертификата
		                                       CERT_HASH_PROP_ID, // Флаг получения SHA1 хэша сертификата
		                                       pbStamp,           // Указатель на буфер для получения отпечатка
		                                       &cbStamp)) {       // Размер буфера
			wprintf(L" -> FAILED, error number: 0x%0.8x\n\n", GetLastError());
			free(pbStamp);
			break;
		}

		/**********************************************************************
		* Печать значения отпечатка сертификата                               *
		**********************************************************************/
		wprintf(L":\n    Algorithm: SHA-1 \n    Value: ");
		for (i = 0; i < cbStamp; ++i) {
			wprintf(L"%0.2x ", pbStamp[i]);
		}
		wprintf(L"\n");
		free(pbStamp);
		break;
	}

	return GetLastError() == ERROR_SUCCESS;
}

/**********************************************************************
* Парсинг и печать информации о сертификате из буфера                 *
**********************************************************************/
BOOL PrintCertificateInfo(BYTE* pbCert,     // Указатель на буфер со значением сертификата
                          DWORD cbCert)     // Длина буфера со значением сертификата
{
	PCCERT_CONTEXT pCertContext = NULL;     // Указатель на контекст сертификата

	for (;; ) {
		/**********************************************************************
		* Шаг 1. Создание контекста сертификата                               *
		**********************************************************************/
		wprintf(L" Create certificate context");
		pCertContext = CertCreateCertificateContext(
		                                            ENC_TYPE, // Тип кодирования сертификата
		                                            pbCert,   // Указатель на буфер с сертификатом
		                                            cbCert    // Длина буфера
		                                            );
		if (pCertContext) {
			wprintf(L" -> OK\n");
		} else {
			wprintf(L" -> FAILED, error number: 0x%0.8x\n\n", GetLastError());
			break;
		}

		wprintf(L"\n Certificate info:\n");

		/**********************************************************************
		* Шаг 2. Печать общей информации о сертификате                        *
		**********************************************************************/
		wprintf(L"  Version: %i.0\n",
		        (pCertContext->pCertInfo->dwVersion) + 1);

		wprintf(L"  Serial Number: ");
		PrintSNBlob(pCertContext->pCertInfo->SerialNumber);
		wprintf(L"\n");

		printf("  Signature Algorithm Identifier: %s\n",
		       pCertContext->pCertInfo->SignatureAlgorithm.pszObjId);

		/**********************************************************************
		* Шаг 3. Печать информации о субъекте сертификата                     *
		**********************************************************************/
		wprintf(L"  Subject Name: ");
		if (!PrintName(pCertContext->pCertInfo->Subject)) {
			wprintf(L" -> FAILED, error number: 0x%0.8x\n\n", GetLastError());
		}

		printf("  Subject Public Key Algorithm Identifier: %s\n",
		       pCertContext->pCertInfo->SubjectPublicKeyInfo.Algorithm.pszObjId);

		wprintf(L"  Subject Public Key Value: ");
		PrintBitBlob(pCertContext->pCertInfo->SubjectPublicKeyInfo.PublicKey);

		/**********************************************************************
		* Шаг 4. Печать информации об издателе                                *
		**********************************************************************/
		wprintf(L"  Issuer Name: ");
		if (!PrintName(pCertContext->pCertInfo->Issuer)) {
			wprintf(L" -> FAILED, error number: 0x%0.8x\n\n", GetLastError());
		}

		/**********************************************************************
		* Шаг 5. Печать срока действия сертификата                            *
		**********************************************************************/
		wprintf(L"  Not Before: ");
		if (!PrintTime(pCertContext->pCertInfo->NotBefore)) {
			wprintf(L" -> FAILED, error number: 0x%0.8x\n\n", GetLastError());
		}

		wprintf(L"  Not After: ");
		if (!PrintTime(pCertContext->pCertInfo->NotAfter)) {
			wprintf(L" -> FAILED, error number: 0x%0.8x\n\n", GetLastError());
		}

		/**********************************************************************
		* Шаг 6. Печать расширений сертификата                                *
		**********************************************************************/
		wprintf(L"  Extensions:");
		if (!PrintExtensions(pCertContext->pCertInfo)) {
			wprintf(L" -> FAILED, error number: 0x%0.8x\n\n", GetLastError());
		}

		/**********************************************************************
		* Шаг 7. Печать отпечатка сертификата                                 *
		**********************************************************************/
		wprintf(L"\n  Fingerprint");
		if (!PrintFingerprint(pCertContext)) {
			wprintf(L" -> FAILED, error number: 0x%0.8x\n\n", GetLastError());
		}

		/**********************************************************************
		* Шаг 8. Освобождение контекста сертификата                           *
		**********************************************************************/
		wprintf(L"\n Free certificate context");
		if (CertFreeCertificateContext(pCertContext)) {
			wprintf(L" -> OK\n");
		} else {
			wprintf(L" -> FAILED, error number: 0x%0.8x\n\n", GetLastError());
		}
		break;
	}

	return GetLastError() == ERROR_SUCCESS;
}

int main(void)
{
	LPCWSTR szProvNameW = CRYPTOPRO_PROV_W; // Имя криптопровайдера, заменить на CRYPTOPRO_FKN_PROV_W для ФКН
	LPCSTR szProvNameA = CRYPTOPRO_PROV_A;  // Имя криптопровайдера, заменить на CRYPTOPRO_FKN_PROV_A для ФКН
	DWORD dwProvType = CRYPTOPRO_PROV_TYPE; // Тип криптопровайдера

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

	BYTE* aContName[MAX_CONT] = { NULL };   // Массив для имен контейнеров
	DWORD dwNameSize = 0;                   // Длина буфера с именем контейнера
	DWORD dwContCount = 0;                  // Количество контейнеров

	DWORD dwFlag = CRYPT_FIRST;             // Переменная для хранения флага операции
	DWORD i = 0;                            // Вспомогательная переменная-счетчик

	BYTE* pbCert = NULL;                    // Указатель на буфер со значением сертификата
	DWORD cbCert = 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"\nSearching for key containers");

		/**********************************************************************
		* Получение максимальной длины имени контейнера                       *
		**********************************************************************/
		if (!CryptGetProvParam(
		                       hProv,             // Дескриптор криптопровайдера
		                       PP_ENUMCONTAINERS, // Флаг перечисления имен ключевых контейнеров
		                       NULL,              // Буфер, в который возвращается имя ключевого контейнера
		                       &dwNameSize,       // Длина буфера с максимальной длиной имени контейнера
		                       dwFlag)) {
			if (GetLastError() == ERROR_NO_MORE_ITEMS) {
				wprintf(L" -> Rutoken does not have any key container\n");
			} else {
				wprintf(L" -> FAILED, error number: 0x%0.8x\n\n", GetLastError());
			}
			break;
		}

		for (i = 0; i < MAX_CONT; ++i) {
			/**********************************************************************
			* Выделение памяти для буфера                                         *
			**********************************************************************/
			aContName[i] = (BYTE*)malloc(dwNameSize * sizeof(BYTE));
			if (!aContName[i]) {
				wprintf(L" -> FAILED, out of memory\n\n");
				break;
			}

			/**********************************************************************
			* Получение указателя на буфер с именем контейнера                    *
			**********************************************************************/
			if (!CryptGetProvParam(
			                       hProv,             // Дескриптор криптопровайдера
			                       PP_ENUMCONTAINERS, // Флаг перечисления имен ключевых контейнеров
			                       aContName[i],      // Буфер, в который возвращается имя ключевого контейнера
			                       &dwNameSize,       // Длина буфера
			                       dwFlag)) {         // Флаги операции
				if (GetLastError() == ERROR_NO_MORE_ITEMS) {
					dwContCount = i;
					wprintf(L" -> OK\n");
					break;
				}
			}
			dwFlag = CRYPT_NEXT;
		}

		CryptReleaseContext(hProv, 0);
		hProv = 0;

		/**********************************************************************
		* Шаг 3. Поиск и перечисление содержимого контейнеров (наличие ключей *
		* подписи, ключей обмена, сертификата)                                *
		**********************************************************************/
		for (i = 0; i < dwContCount; ++i) {
			/**********************************************************************
			* Шаг 3.1 Инициализация провайдера для работы с контейнером           *
			**********************************************************************/
			printf("\nContainer name No %d: %s\n", i + 1, aContName[i]);
			/**********************************************************************
			* Используем CryptAcquireContextA, поскольку aContName[i] является    *
			* ANSI строкой                                                        *
			**********************************************************************/
			if (!CryptAcquireContextA(
			                          &hProv,                // Дескриптор криптопровайдера
			                          (LPSTR)aContName[i],   // Имя ключевого контейнера
			                          szProvNameA,           // Имя криптопровайдера
			                          dwProvType,            // Тип криптопровайдера
			                          0)) {                  // Флаги
				wprintf(L" -> FAILED, error number: 0x%0.8x\n\n", GetLastError());
				break;
			}

			dwFlag = AT_SIGNATURE;

			for (;; ) {
				if (dwFlag == AT_SIGNATURE) {
					wprintf(L" Getting signature public key");
				} else if (dwFlag == AT_KEYEXCHANGE) {
					wprintf(L" Getting exchange public key");
				}

				/**********************************************************************
				* Шаг 3.2 Получение дескриптора ключа                                 *
				**********************************************************************/
				if (CryptGetUserKey(
				                    hProv,    // Дескриптор криптопровайдера
				                    dwFlag,   // Спецификация ключа
				                    &hKey)) { // Дескриптор ключа
					wprintf(L" -> OK, key exists\n");

					/**********************************************************************
					* Шаг 3.3 Получение буфера с закодированным значением сертификата     *
					**********************************************************************/
					wprintf(L" Getting certificate");
					/**********************************************************************
					* Получение размера буфера со значением сертификата                   *
					**********************************************************************/
					if (CryptGetKeyParam(
					                     hKey,           // Дескриптор ключа
					                     KP_CERTIFICATE, // Флаг получения значения сертификата
					                     NULL,           // Указатель на буфер для сертификата
					                     &cbCert,        // Длина буфера
					                     0)) {           // Флаги
						/**********************************************************************
						* Выделение памяти для буфера                                         *
						**********************************************************************/
						pbCert = (BYTE*)malloc(cbCert * sizeof(BYTE));
						if (!pbCert) {
							wprintf(L" -> FAILED, out of memory\n\n");
							break;
						}

						/**********************************************************************
						* Получение указателя на буфер со значением сертификата               *
						**********************************************************************/
						if (!CryptGetKeyParam(
						                      hKey,           // Дескриптор ключа
						                      KP_CERTIFICATE, // Флаг получения значения сертификата
						                      pbCert,         // Указатель на буфер для сертификата
						                      &cbCert,        // Длина буфера
						                      0)) {           // Флаги
							wprintf(L" -> FAILED, error number: 0x%0.8x\n\n", GetLastError());
							break;
						}
						wprintf(L" -> OK, certificate exists\n");

						/**********************************************************************
						* Шаг 3.4 Печать информации о сертификате                             *
						**********************************************************************/
						if (!PrintCertificateInfo(
						                          pbCert,   // Указатель на буфер с сертификатом
						                          cbCert    // Длина буфера
						                          )) {
							break;
						}
					} else 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());
					}

				} else if (GetLastError() == NTE_NO_KEY) {
					wprintf(L" -> OK, key does not exist\n");
				} else {
					wprintf(L" -> FAILED, error number: 0x%0.8x\n\n", GetLastError());
				}

				free(pbCert);
				pbCert = NULL;

				if (hKey) {
					CryptDestroyKey(hKey);
					hKey = 0;
				}

				if (dwFlag == AT_KEYEXCHANGE) {
					break;
				} else {
					dwFlag = AT_KEYEXCHANGE;
				}
			}

			free(pbCert);

			CryptReleaseContext(hProv, 0);
			hProv = 0;

			if (hKey) {
				CryptDestroyKey(hKey);
				hKey = 0;
			}

			printf("\nReading container name No %d: %s has been completed\n", i + 1, aContName[i]);
		}

		break;
	}

	/**********************************************************************
	* Шаг 4. Освобождение дескрипторов, контекстов и памяти               *
	**********************************************************************/
	/**********************************************************************
	* Шаг 4.1 Освобождение дескриптора провайдера                         *
	**********************************************************************/

	if (hProv) {
		CryptReleaseContext(hProv, 0);
		hProv = 0;
	}

	/**********************************************************************
	* Шаг 4.2 Освобождение памяти                                         *
	**********************************************************************/
	for (i = 0; i < dwContCount; ++i) {
		free(aContName[i]);
	}

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