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

#include "Common.h"

/**********************************************************************
* Подпись сообщения ключевой парой, хранящейся на токене              *
* Сертификат берётся c токена                                         *
**********************************************************************/
BOOL SignPKCS7Token(LPCWSTR szContNameW,             // Имя контейнера
                    DWORD dwKeySpec,                 // Тип ключа
                    BOOL bSilent,                    // Флаг тихого режима: включен - TRUE
                    BOOL bDetached,                  // Флаг формата подписи: отсоединенная - TRUE
                    BYTE* pbMsgToSign,               // Указатель на сообщение для подписи
                    DWORD cbMsgToSign,               // Длина сообщения
                    BYTE** pbSigned,                 // Указатель на буфер с подписью
                    DWORD* cbSigned)                 // Указатель на длину буфера
{
	LPCSTR szProvNameA = CRYPTOPRO_PROV_A;           // Или CRYPTOPRO_FKN_PROV_A для ФКН
	LPWSTR szProvNameW = CRYPTOPRO_PROV_W;           // Или CRYPTOPRO_FKN_PROV_W для ФКН

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

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

	BYTE* pbUnicContName = NULL;                     // Указатель на буфер для FQCN имени контейнера
	DWORD cbUnicContName = 0;                        // Размер буфера для FQCN имени контейнера

	BYTE* pbCert = NULL;                             // Указатель на буфер для значения сертификата
	DWORD cbCert = 0;                                // Размер буфера для значения сертификата
	PCCERT_CONTEXT pCert = NULL;                     // Указатель на контекст сертификата

	CRYPT_KEY_PROV_INFO KeyProvInfo = { 0 };         // Структура с информацией о контейнере
	CRYPT_SIGN_MESSAGE_PARA SignatureParams = { 0 }; // Структура с параметрами подписи формата PKCS#7

	DWORD i = 0;                                     // Вспомогательная переменная-счетчик
	DWORD dwFlags = bSilent ? CRYPT_SILENT : 0;      // Флаг тихого режима

	*pbSigned = NULL;

	for (;; ) {
		/**********************************************************************
		* Шаг 1. Инициализация криптопровайдера для подписи сообщения         *
		**********************************************************************/

		/**********************************************************************
		* Шаг 1.1 Инициализация криптопровайдера для получения уникального    *
		*         имени контейнера                                            *
		**********************************************************************/
		wprintf(L"Initializing cryptoprovider");
		if (!CryptAcquireContextW(
		                          &hProv,         // Дескриптор криптопровайдера
		                          szContNameW,    // Имя ключевого контейнера
		                          szProvNameW,    // Имя криптопровайдера
		                          dwProvType,     // Тип криптопровайдера
		                          dwFlags)) {     // Флаг тихого режима
			if (GetLastError() == NTE_BAD_KEYSET_PARAM || GetLastError() == NTE_BAD_KEYSET) {
				wprintf(L" -> FAILED, container does not exist (0x%0.8x)\n\n", GetLastError());
			} else {
				wprintf(L" -> FAILED, error number: 0x%0.8x\n\n", GetLastError());
			}
			break;
		}
		wprintf(L" -> OK\n");

		/**********************************************************************
		* Шаг 1.2 Получение уникального имени контейнера                      *
		**********************************************************************/
		wprintf(L"Getting unique container name");

		/**********************************************************************
		* Получение размера буфера для имени контейнера                       *
		**********************************************************************/
		if (!CryptGetProvParam(
		                       hProv,               // Дескриптор криптопровайдера
		                       PP_UNIQUE_CONTAINER, // Флаг получения уникального имени контейнера
		                       NULL,                // Указатель на буфер для получения имени контейнера
		                       &cbUnicContName,     // Указатель на длину буфера
		                       0)) {
			wprintf(L" -> FAILED, error number: 0x%0.8x\n\n", GetLastError());
			break;
		}

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

		/**********************************************************************
		* Получение указателя на буфер с именем контейнера                    *
		**********************************************************************/
		if (!CryptGetProvParam(
		                       hProv,               // Дескриптор криптопровайдера
		                       PP_UNIQUE_CONTAINER, // Флаг получения уникального имени контейнера
		                       pbUnicContName,      // Указатель на буфер для получения имени контейнера
		                       &cbUnicContName,     // Указатель на длину буфера
		                       0)) {
			wprintf(L" -> FAILED, error number: 0x%0.8x\n\n", GetLastError());
			break;
		}
		wprintf(L" -> OK\n");

		/**********************************************************************
		* Освобождение контекста криптопровайдера                             *
		**********************************************************************/
		CryptReleaseContext(hProv, 0);
		hProv = 0;

		/**********************************************************************
		* Шаг 1.3 Инициализация криптопровайдера                              *
		**********************************************************************/
		wprintf(L"Initializing cryptoprovider for silent mode");
		/**********************************************************************
		* Используем CryptAcquireContextA, поскольку pbUnicContName является  *
		* ANSI строкой                                                        *
		**********************************************************************/
		if (!CryptAcquireContextA(
		                          &hProv,                // Дескриптор криптопровайдера
		                          (LPSTR)pbUnicContName, // Уникальное имя ключевого контейнера
		                          szProvNameA,           // Имя криптопровайдера
		                          dwProvType,            // Тип криптопровайдера
		                          dwFlags)) {            // Флаг тихого режима
			wprintf(L" -> FAILED, error number: 0x%0.8x\n\n", GetLastError());
			break;
		}
		wprintf(L" -> OK\n");

		/**********************************************************************
		* Шаг 1.4 Передача PIN-кода Рутокен в параметры криптопровайдера      *
		**********************************************************************/
		wprintf(L"Setting Rutoken PIN-code");
		if (!CryptSetProvParam(
		                       hProv,              // Дескриптор криптопровайдера
		                       PP_KEYEXCHANGE_PIN, // Флаг передачи PIN-кода
		                       USER_PIN,           // Значение PIN-кода
		                       0)) {
			wprintf(L" -> FAILED, error number: 0x%0.8x\n\n", GetLastError());
			break;
		}
		wprintf(L" -> OK\n");

		/**********************************************************************
		* Шаг 2. Получение сертификата открытого ключа, включаемого в подпись *
		**********************************************************************/
		/**********************************************************************
		* Шаг 2.1 Получение дескриптора закрытого ключа                       *
		**********************************************************************/
		wprintf(L"Checking signature key existence");
		if (!CryptGetUserKey(
		                     hProv,     // Дескриптор криптопровайдера
		                     dwKeySpec, // Тип ключа
		                     &hKey)) {  // Дескриптор ключа
			wprintf(L" -> FAILED, error number: 0x%0.8x\n", GetLastError());
			break;
		}
		wprintf(L" -> OK\n");

		/**********************************************************************
		* Шаг 2.2 Получение сертификата открытого ключа                       *
		**********************************************************************/
		wprintf(L"Getting certificate");

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

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

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

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

		/**********************************************************************
		* Шаг 4. Ассоциирование закрытого ключа с сертификатом                *
		**********************************************************************/

		/**********************************************************************
		* Шаг 4.1 Заполнение полей структуры с информацией о контейнере       *
		**********************************************************************/
		wprintf(L"Creating structure with information about provider");
		KeyProvInfo.pwszProvName = szProvNameW;                                         // Имя криптопровайдера
		KeyProvInfo.dwProvType = dwProvType;                                            // Тип криптопровайдера
		KeyProvInfo.dwFlags = 0;                                                        // Флаги отсутствуют
		KeyProvInfo.cProvParam = 0;                                                     // Количество дополнительных параметров равно нулю
		KeyProvInfo.rgProvParam = 0;                                                    // Дополнительные параметры отсутствуют
		KeyProvInfo.dwKeySpec = AT_SIGNATURE;                                           // Тип ключа
		KeyProvInfo.pwszContainerName = (LPWSTR)malloc(cbUnicContName * sizeof(WCHAR)); // Уникальное имя контейнера

		if (!KeyProvInfo.pwszContainerName) {
			wprintf(L" -> FAILED, out of memory\n");
			break;
		} else {
			wprintf(L" -> OK\n");
		}

		wprintf(L"Mapping a container name to an UTF-16 string");
		if (!MultiByteToWideChar(CP_ACP,
		                         0,
		                         (LPSTR)pbUnicContName,
		                         -1,
		                         KeyProvInfo.pwszContainerName,
		                         cbUnicContName)) {                  // Преобразование ANSI строки названия контейнера в UTF-16
			wprintf(L" -> FAILED\n");
		} else {
			wprintf(L" -> OK\n");
		}

		/**********************************************************************
		* Шаг 4.2 Передача информации о контейнере в контекст криптопровайдера*
		**********************************************************************/
		wprintf(L"Setting certificate context");
		if (!CertSetCertificateContextProperty(
		                                       pCert,                      // Указатель на контекст сертификата
		                                       CERT_KEY_PROV_INFO_PROP_ID, // Флаг передачи информации о контейнере
		                                       0,
		                                       &KeyProvInfo)) {            // Указатель на передаваемую структуру
			wprintf(L" -> FAILED, error number: 0x%0.8x\n", GetLastError());
			break;
		}
		wprintf(L" -> OK\n");

		/**********************************************************************
		* Шаг 5. Инициализация параметров подписи формата PKCS#7              *
		**********************************************************************/

		SignatureParams.cbSize = sizeof(SignatureParams);               // Размер структуры в байтах
		SignatureParams.dwMsgEncodingType = ENC_TYPE,                   // Тип кодирования
		SignatureParams.pSigningCert = pCert;                           // Указатель на сертификат для подписи
		SignatureParams.HashAlgorithm.pszObjId = szOID_CP_GOST_R3411;   // Алгоритм хэширования
		SignatureParams.HashAlgorithm.Parameters.cbData = 0;
		SignatureParams.rgpMsgCert = &pCert;                            // Указатель на сертификат, включаемый в подпись
		SignatureParams.cMsgCert = 1;                                   // Количество включаемых в подпись сертификатов
		                                                                // Остальные параметры равны нулю

		/**********************************************************************
		* Шаг 6. Подпись сообщения в формате PKCS#7                           *
		**********************************************************************/
		printf("\nMessage to sign is: \n\"%s\"\n\n", pbMsgToSign);
		wprintf(L"Signing message");

		/**********************************************************************
		* Получение размера буфера для подписи                                *
		**********************************************************************/
		if (!CryptSignMessage(
		                      &SignatureParams, // Указатель на структуру с параметрами подписи
		                      bDetached,        // Тип подписи, TRUE - отсоединенная
		                      1,                // Количество сообщений для подписи
		                      &pbMsgToSign,     // Указатель на буфер с подписываемым сообщением
		                      &cbMsgToSign,     // Указатель на буфер с длинами подписываемых сообщений
		                      NULL,             // Указатель на буфер для подписи
		                      cbSigned          // Указатель на длину буфера
		                      )) {
			wprintf(L" -> FAILED, error number: 0x%0.8x\n", GetLastError());
			break;
		}

		/**********************************************************************
		* Выделение памяти для получения буфера с подписью                    *
		**********************************************************************/
		*pbSigned = (BYTE*)malloc(*cbSigned * sizeof(BYTE));
		if (!*pbSigned) {
			wprintf(L" -> FAILED, out of memory\n\n");
			break;
		}

		/**********************************************************************
		* Получение указателя на буфер для подписи                            *
		**********************************************************************/
		if (!CryptSignMessage(
		                      &SignatureParams, // Указатель на структуру с параметрами подписи
		                      bDetached,        // Тип подписи, TRUE - отсоединенная
		                      1,                // Количество сообщений для подписи
		                      &pbMsgToSign,     // Указатель на буфер с подписываемым сообщением
		                      &cbMsgToSign,     // Указатель на длину буфера
		                      *pbSigned,        // Указатель на буфер для подписи
		                      cbSigned          // Указатель на длину буфера
		                      )) {
			wprintf(L" -> FAILED, error number: 0x%0.8x\n", GetLastError());
			free(*pbSigned);
			*pbSigned = NULL;
			break;
		}
		wprintf(L" -> OK");

		/**********************************************************************
		* Шаг 7. Печать буфера с подписью                                     *
		**********************************************************************/
		wprintf(L"\nSignature value is: \n");
		for (i = 0; i < *cbSigned; ++i) {
			wprintf(L"%0.2X ", (*pbSigned)[i]);
			if ((i + 1) % 16 == 0) {
				wprintf(L"\n");
			}
		}
		wprintf(L"\nData has been signed successfully.\n");
		break;
	}

	/**********************************************************************
	* Шаг 8. Освобождение дескрипторов, контекстов, памяти                *
	**********************************************************************/
	if (hKey) {
		CryptDestroyKey(hKey);
	}
	if (pCert) {
		CertFreeCertificateContext(pCert);
	}
	if (hProv) {
		CryptReleaseContext(hProv, 0);
	}
	if (hStore) {
		CertCloseStore(hStore, CERT_CLOSE_STORE_FORCE_FLAG);
	}

	free(pbCert);

	free(pbUnicContName);

	free(KeyProvInfo.pwszContainerName);

	if (!GetLastError()) {
		return TRUE;
	} else {
		return FALSE;
	}
}

/**********************************************************************
* Подпись сообщения ключевой парой, хранящейся на токене              *
* Сертификат берётся из хранилища сертификатов                        *
* Закрытый ключ берётся с токена                                      *
**********************************************************************/
BOOL SignPKCS7Store(DWORD dwKeySpec,                 // Тип ключа
                    BOOL bDetached,                  // Флаг формата подписи: отсоединенная - TRUE
                    BYTE* pbMsgToSign,               // Указатель на сообщение для подписи
                    DWORD cbMsgToSign,               // Длина сообщения
                    BYTE** pbSigned,                 // Указатель на буфер с подписью
                    DWORD* cbSigned)                 // Указатель на длину буфера
{
	HCRYPTPROV hProv = 0;                            // Дескриптор криптопровайдера
	HCERTSTORE hStore = 0;                           // Дескриптор хранилища сертификатов

	PCCERT_CONTEXT pCert = NULL;                     // Указатель на контекст сертификата

	CRYPT_SIGN_MESSAGE_PARA SignatureParams = { 0 }; // Структура с параметрами подписи формата PKCS#7

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

	*pbSigned = NULL;

	for (;; ) {

		/**********************************************************************
		* Шаг 1. Получение сертификата открытого ключа, включаемого в подпись,*
		*	     из хранилища сертификатов                                    *
		**********************************************************************/

		/**********************************************************************
		* Шаг 1.1 Открытие хранилища сертификатов                             *
		**********************************************************************/
		wprintf(L"\nOpen certificate store");
		hStore = CertOpenSystemStoreW(
		                              0,
		                              L"MY");                          // Имя хранилища
		if (hStore) {
			printf(" -> OK\n");
		} else {
			printf(" -> FAILED, error number: 0x%0.8x\n", GetLastError());
			break;
		}

		/**********************************************************************
		* Шаг 1.2 Получение указателя на сертификат для подписи               *
		**********************************************************************/
		wprintf(L"Getting certificate");

		/**********************************************************************
		* Поиск сертификата в хранилище сертификатов осуществляется по имени  *
		* субъекта. Имя субъекта можно изменить в Common.h, переменная        *
		* CERT_SUBJECT_NAME_W                                                 *
		**********************************************************************/
		pCert = CertFindCertificateInStore(
		                                   hStore,                  // Дескриптор хранилища
		                                   ENC_TYPE,                // Тип кодирования
		                                   0,
		                                   CERT_FIND_SUBJECT_STR_W, // Поиск сертификата по имени субъекта
		                                   CERT_SUBJECT_NAME_W,     // Имя субъекта
		                                   NULL);

		if (pCert) {
			printf(" -> OK\n");
		} else {
			printf(" -> FAILED, error number: 0x%0.8x\n", GetLastError());
			break;
		}

		/**********************************************************************
		* Шаг 1.3 Поиск и привязывание закрытого ключа к сертификату          *
		**********************************************************************/
		printf("Acquiring private key for the certificate");
		if (!CryptAcquireCertificatePrivateKey(
		                                       pCert,           // Указатель на контекст сертификата
		                                       0,
		                                       NULL,
		                                       &hProv,          // Указатель на дескриптор криптопровайдера
		                                       &dwKeySpec,      // Тип ключа
		                                       NULL)) {
			printf(" -> FAILED, error number: 0x%0.8x\n", GetLastError());
			break;
		}
		printf(" -> OK\n");

		/**********************************************************************
		* Шаг 2. Инициализация параметров подписи формата PKCS#7              *
		**********************************************************************/

		SignatureParams.cbSize = sizeof(SignatureParams);               // Размер структуры в байтах
		SignatureParams.dwMsgEncodingType = ENC_TYPE,                   // Тип кодирования
		SignatureParams.pSigningCert = pCert;                           // Указатель на сертификат для подписи
		SignatureParams.HashAlgorithm.pszObjId = szOID_CP_GOST_R3411;   // Алгоритм хэширования
		SignatureParams.HashAlgorithm.Parameters.cbData = 0;
		SignatureParams.pvHashAuxInfo = NULL;                           // Не используется
		SignatureParams.rgpMsgCert = &pCert;                            // Указатель на сертификаты, включаемые в подпись
		SignatureParams.cMsgCert = 1;                                   // Количество включаемых в подпись сертификатов
		                                                                // Остальные параметры равны нулю

		/**********************************************************************
		* Шаг 3. Подпись сообщения в формате PKCS#7                           *
		**********************************************************************/
		printf("\nMessage to sign is: \n\"%s\"\n\n", pbMsgToSign);
		wprintf(L"Signing message");

		/**********************************************************************
		* Получение размера буфера для подписи                                *
		**********************************************************************/
		if (!CryptSignMessage(
		                      &SignatureParams, // Указатель на структуру с параметрами подписи
		                      bDetached,        // Тип подписи, TRUE - отсоединенная
		                      1,                // Количество сообщений для подписи
		                      &pbMsgToSign,     // Указатель на буфер с подписываемым сообщением
		                      &cbMsgToSign,     // Указатель на буфер с длинами подписываемых сообщений
		                      NULL,             // Указатель на буфер для подписи
		                      cbSigned          // Указатель на длину буфера
		                      )) {
			wprintf(L" -> FAILED, error number: 0x%0.8x\n", GetLastError());
			break;
		}

		/**********************************************************************
		* Выделение памяти для получения буфера с подписью                    *
		**********************************************************************/
		*pbSigned = (BYTE*)malloc(*cbSigned * sizeof(BYTE));
		if (!*pbSigned) {
			wprintf(L" -> FAILED, out of memory\n\n");
			break;
		}

		/**********************************************************************
		* Получение указателя на буфер для подписи                            *
		**********************************************************************/
		if (!CryptSignMessage(
		                      &SignatureParams, // Указатель на структуру с параметрами подписи
		                      bDetached,        // Тип подписи, TRUE - отсоединенная
		                      1,                // Количество сообщений для подписи
		                      &pbMsgToSign,     // Указатель на буфер с подписываемым сообщением
		                      &cbMsgToSign,     // Указатель на длину буфера
		                      *pbSigned,        // Указатель на буфер для подписи
		                      cbSigned          // Указатель на длину буфера
		                      )) {
			wprintf(L" -> FAILED, error number: 0x%0.8x\n", GetLastError());
			free(*pbSigned);
			*pbSigned = NULL;
			break;
		}
		wprintf(L" -> OK");

		/**********************************************************************
		* Шаг 4. Печать буфера с подписью                                     *
		**********************************************************************/
		wprintf(L"\nSignature value is: \n");
		for (i = 0; i < *cbSigned; ++i) {
			wprintf(L"%0.2X ", (*pbSigned)[i]);
			if ((i + 1) % 16 == 0) {
				wprintf(L"\n");
			}
		}
		wprintf(L"\nData has been signed successfully.\n\n");
		break;
	}

	/**********************************************************************
	* Шаг 5. Освобождение дескрипторов, контекстов                        *
	**********************************************************************/

	if (pCert) {
		CertFreeCertificateContext(pCert);
	}
	if (hProv) {
		CryptReleaseContext(hProv, 0);
	}
	if (hStore) {
		CertCloseStore(hStore, CERT_CLOSE_STORE_FORCE_FLAG);
	}

	return GetLastError() == ERROR_SUCCESS;
}

/**********************************************************************
* Проверка подписи сообщения сертификатом из подписи                  *
**********************************************************************/
BOOL VerifyPKCS7(BOOL bDetached,                    // Флаг формата подписи, TRUE - отсоединенная
                 BYTE* pbMsgToSign,                 // Указатель на буфер с исходным сообщением
                 DWORD cbMsgToSign,                 // Длина буфера
                 BYTE* pbSigned,                    // Указатель на буфер с подписью
                 DWORD cbSigned)                    // Длина буфера
{
	CRYPT_VERIFY_MESSAGE_PARA VerifyParams = { 0 }; // Структура с параметрами проверки подписи формата PKCS#7

	BYTE* pbDecodedMsg = NULL;                      // Указатель на буфер для декодированного сообщения
	DWORD cbDecodedMsg = 0;                         // Длина буфера
	for (;; ) {
		/**********************************************************************
		* Шаг 1. Инициализация параметров проверки подписи формата PKCS#7     *
		**********************************************************************/

		VerifyParams.cbSize = sizeof(VerifyParams);                 // Размер структуры в байтах
		VerifyParams.dwMsgAndCertEncodingType = ENC_TYPE;           // Тип кодирования

		/**********************************************************************
		* Шаг 2. Проверка подписи данных                                      *
		**********************************************************************/
		wprintf(L"Verifying signature value");
		if (bDetached) {
			/**********************************************************************
			* Шаг 2.1 Проверка отсоединенной подписи                              *
			**********************************************************************/
			if (CryptVerifyDetachedMessageSignature(
			                                        &VerifyParams, // Указатель на структуру с параметрами проверки подписи
			                                        0,             // Индекс проверяемой подписи (для проверки подписи с несколькими подписантами)
			                                        pbSigned,      // Указатель на буфер c подписью
			                                        cbSigned,      // Указатель на размер буфера
			                                        1,             // Количество сообщений для подписи
			                                        &pbMsgToSign,  // Указатель на буфер с исходным сообщением
			                                        &cbMsgToSign,  // Указатель на длину буфера
			                                        NULL)) {       // Указатель на сертификат, NULL - сертификат используется из сообщения
				wprintf(L" -> OK");
				wprintf(L"\nSignature is VALID.");
				wprintf(L"\nSignature has been verified successfully");
			} else if (GetLastError() == NTE_BAD_SIGNATURE) {
				wprintf(L" -> FAILED");
				wprintf(L"\nSignature is INVALID.");
			} else {
				wprintf(L" -> FAILED, error number: 0x%0.8x\n", GetLastError());
				break;
			}
		} else {
			/**********************************************************************
			* Шаг 2.2 Проверка присоединенной подписи                             *
			**********************************************************************/
			/**********************************************************************
			* Получение размера буфера для декодированного сообщения              *
			**********************************************************************/
			if (!CryptVerifyMessageSignature(
			                                 &VerifyParams, // Указатель на структуру с параметрами проверки подписи
			                                 0,             // Индекс проверяемой подписи (для проверки подписи с несколькими подписантами)
			                                 pbSigned,      // Указатель на буфер со значением подписи
			                                 cbSigned,      // Указатель на размер буфера
			                                 NULL,          // Указатель на буфер для декодируемого сообщения
			                                 &cbDecodedMsg, // Указатель на размер буфера
			                                 NULL)) {       // Указатель на сертификат, NULL - используется из сообщения
				wprintf(L" -> FAILED, error number: 0x%0.8x\n", GetLastError());
				break;
			}


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

			/**********************************************************************
			* Получение указателя на буфер для декодированного сообщения          *
			**********************************************************************/
			if (CryptVerifyMessageSignature(
			                                &VerifyParams, // Указатель на структуру с параметрами проверки подписи
			                                0,             // Индекс проверяемой подписи (для проверки подписи с несколькими подписантами)
			                                pbSigned,      // Указатель на буфер со значением подписи
			                                cbSigned,      // Указатель на размер буфера
			                                pbDecodedMsg,  // Указатель на буфер для декодируемого сообщения
			                                &cbDecodedMsg, // Указатель на размер буфера
			                                NULL)) {       // Указатель на сертификат, NULL - используется из сообщения
				wprintf(L" -> OK\n");
				printf("\nDecoded message is: \n\"%s\"\n", pbDecodedMsg);
				wprintf(L"\nSignature is VALID.\n");
				wprintf(L"\nSignature has been verified successfully\n");
			} else if (GetLastError() == NTE_BAD_SIGNATURE) {
				wprintf(L" -> FAILED\n");
				wprintf(L"\nSignature is INVALID.\n");
			} else {
				wprintf(L" -> FAILED, error number: 0x%0.8x\n", GetLastError());
				break;
			}
		}
		break;
	}

	free(pbDecodedMsg);

	return GetLastError() == ERROR_SUCCESS;
}

int main(void)
{
	BOOL bDetached = FALSE;         // Флаг формата подписи: TRUE - отсоединенная
	BOOL bSilent = FALSE;           // Флаг тихого режима подписи: TRUE - тихий

	BYTE* pbMsgToSign = NULL;       // Указатель на сообщение для подписи
	DWORD cbMsgToSign = 0;          // Длина сообщения для подписи

	BYTE* pbSignature = NULL;       // Указатель на буфер для подписи
	DWORD cbSignature = 0;          // Длина буфера для подписи

	for (;; ) {
		/**********************************************************************
		* Шаг 1. Формирование буфера с сообщением для подписи                 *
		**********************************************************************/
		// Определяя длину сообщения, учитываем нуль-символ конца строки
		cbMsgToSign = (DWORD)strlen((LPSTR)MSG_TO_SIGN) + 1;
		pbMsgToSign = (BYTE*)MSG_TO_SIGN;

		/**********************************************************************
		* Шаг 2. Генерация сертификата, подпись сообщения и проверка подписи  *
		**********************************************************************/

		/**********************************************************************
		* Шаг 2.1 Подпись сообщения                                           *
		**********************************************************************/
		bDetached = FALSE;  // Форма подписи - присоединенная подпись
		bSilent = TRUE;     // Тихий режим включен
		wprintf(L"Part (1/2) started\n");
		if (!SignPKCS7Token(
		                    CONT_NAME_W,     // Имя контейнера с закрытом ключом и сертификатом
		                    AT_SIGNATURE,    // Тип ключа - для подписи
		                    bSilent,         // Флаг тихого режима
		                    bDetached,       // Флаг отсоединенной подписи
		                    pbMsgToSign,     // Указатель на сообщение для подписи
		                    cbMsgToSign,     // Длина сообщения
		                    &pbSignature,    // Указатель на буфер для значения подписи
		                    &cbSignature)) { // Длина буфера
			wprintf(L"Part (1/2) failed\n");
			break;
		}

		/**********************************************************************
		* Шаг 2.2 Проверка подписи сообщения                                  *
		**********************************************************************/
		if (!VerifyPKCS7(
		                 bDetached,    // Флаг отсоединенной подписи
		                 pbMsgToSign,  // Указатель на буфер с исходным сообщением (только для отсоединенной подписи)
		                 cbMsgToSign,  // Длина буфера (только для отсоединенной подписи)
		                 pbSignature,  // Указатель на буфер с проверяемой подписи
		                 cbSignature   // Длина буфера
		                 )) {
			wprintf(L"Part (1/2) failed\n");
			break;
		}
		wprintf(L"Part (1/2) succeeded\n\n");
		wprintf(L"Part (2/2) started\n");

		free(pbSignature);
		pbSignature = NULL;

		/**********************************************************************
		* Шаг 3. Выбор сертификата из хранилища, подпись сообщения            *
		*        и проверка подписи                                           *
		**********************************************************************/

		/**********************************************************************
		* Шаг 3.1 Подпись сообщения                                           *
		**********************************************************************/
		bDetached = FALSE;  // Форма подписи - присоединенная подпись

		if (!SignPKCS7Store(
		                    AT_SIGNATURE,    // Тип ключа - для подписи
		                    bDetached,       // Флаг отсоединенной подписи
		                    pbMsgToSign,     // Указатель на сообщение для подписи
		                    cbMsgToSign,     // Длина сообщения
		                    &pbSignature,    // Указатель на буфер для значения подписи
		                    &cbSignature)) { // Длина буфера
			wprintf(L"Part (2/2) failed\n");
			break;
		}

		/**********************************************************************
		* Шаг 3.2 Проверка подписи сообщения                                  *
		**********************************************************************/
		if (!VerifyPKCS7(
		                 bDetached,    // Флаг отсоединенной подписи
		                 pbMsgToSign,  // Указатель на буфер с исходным сообщением (только для отсоединенной подписи)
		                 cbMsgToSign,  // Длина буфера (только для отсоединенной подписи)
		                 pbSignature,  // Указатель на буфер с проверяемой подписью
		                 cbSignature   // Длина буфера
		                 )) {

			wprintf(L"Part (2/2) failed\n");
			break;
		}

		wprintf(L"Part (2/2) succeeded\n");
		break;
	}

	free(pbSignature);

	if (!GetLastError()) {
		wprintf(L"\nTest has been completed successfully.");
	} else {
		wprintf(L"\nTest has failed. Error number: 0x%0.8x.", GetLastError());
	}
	return 0;
}
