/*
 * Copyright(C) 2000-2010 Проект ИОК
 *
 * Этот файл содержит информацию, являющуюся
 * собственностью компании Крипто Про.
 *
 * Любая часть этого файла не может быть скопирована,
 * исправлена, переведена на другие языки,
 * локализована или модифицирована любым способом,
 * откомпилирована, передана по сети с или на
 * любую компьютерную систему без предварительного
 * заключения соглашения с компанией Крипто Про.
 *
 * Программный код, содержащийся в этом файле, предназначен
 * исключительно для целей обучения и не может быть использован
 * для защиты информации.
 *
 * Компания Крипто-Про не несет никакой
 * ответственности за функционирование этого кода.
 */

#include <vector>
#include <stdio.h>
#ifdef _WIN32
  #include <windows.h>
  #include <wincrypt.h>
  #include <wincryptex.h>
  #include <tchar.h>
#else
  #include <string.h>
  #include <stdlib.h>
  #include <WinCryptEx.h>
  #include "reader/tchar.h"
#endif	// _WIN32

#define TYPE_DER (X509_ASN_ENCODING | PKCS_7_ASN_ENCODING)

//--------------------------------------------------------------------
// Пример кода для подписи и проверки подписи данных 
// сообщения при помощи функции Crypt.

// Для функционирования данного кода необходим контейнер с ключом 
// AT_KEYEXCHANGE с именем Test(Unix) или контейнер по умолчанию(WIN),
// Ключ можно создать используя пример CryptAcquireContext.

//--------------------------------------------------------------------
// Компилятор должен сам понять, что функция noreturn
static void HandleError(const char *s);

//--------------------------------------------------------------------
// В этой программе используется функция GetRecipientCert, объявленная 
// здесь и описанная после main.
static PCCERT_CONTEXT GetRecipientCert(HCERTSTORE hCertStore);

//--------------------------------------------------------------------
static void do_low_sign(void);
static void do_low_verify(void);

// используются для передачи значений между do_low_verify() и do_low_sign()
static DWORD cbEncodedBlob;
static BYTE  *pbEncodedBlob = NULL;

int main(void)
{
    do_low_sign();
    ::printf("Do_low_sign DONE \n\n");
    do_low_verify();
    ::printf("Do_low_verify DONE \n\n");
    return 0;
} // end of main

// Возвращает oid хэш-алгоритма, подходящего для подписи данным ключом,
// либо NULL в случае ошибки.
static LPCSTR GetHashOidForSign(HCRYPTPROV hProv, DWORD dwKeySpec) 
{   
    LPCSTR hash_oid = NULL; // не все компиляторы понимают, что hash_oid всегда инициализирован
    ALG_ID key_algid;
    DWORD dwAlgidLen = sizeof(ALG_ID);
    HCRYPTKEY hKey;

    if (!::CryptGetUserKey(hProv, dwKeySpec, &hKey)) 
	HandleError("GetUserKey()");
    if (!::CryptGetKeyParam(hKey, KP_ALGID, (BYTE*)&key_algid, &dwAlgidLen, 0)) 
	HandleError("GetKeyParam()");

    switch(key_algid) 
    {
	case CALG_DH_EL_SF:
	case CALG_GR3410EL:
	    hash_oid = szOID_CP_GOST_R3411;
	    break;
	case CALG_DH_GR3410_12_256_SF:
	case CALG_GR3410_12_256:
	    hash_oid = szOID_CP_GOST_R3411_12_256;
	    break;
	case CALG_DH_GR3410_12_512_SF:
	case CALG_GR3410_12_512:
	    hash_oid = szOID_CP_GOST_R3411_12_512;
	    break;
	default:
	    HandleError("Bad key algid"); // noreturn
    }

    if (!::CryptDestroyKey(hKey)) 
	HandleError("DestroyKey()");

    return hash_oid;
}

void do_low_sign(void)
{
//--------------------------------------------------------------------
//  Начало обработки данных.
    static const BYTE pbContent[] = "Security is our business."; // сообщение
    static const DWORD cbContent = (DWORD) sizeof(pbContent); // длина сообщения

    ::printf("About to begin with the message %s.\n",pbContent);
    ::printf("The message length is %d bytes. \n", cbContent);

//--------------------------------------------------------------------
// Открытие системного хранилища сертификатов.

    HCERTSTORE hStoreHandle = ::CertOpenSystemStore(0, _TEXT("My"));
    if(!hStoreHandle)
        HandleError("Error getting store handle.");
    ::printf("The MY store is open. \n");

//--------------------------------------------------------------------
// Получение указателя на сертификат издателя с помощью
// функции GetRecipientCert. 

    PCCERT_CONTEXT pRecipientCert = GetRecipientCert(hStoreHandle);
    if(!pRecipientCert)
    {
        ::printf("No certificate with a CERT_KEY_CONTEXT_PROP_ID \n");
        ::printf("property and an AT_KEYEXCHANGE private key available. \n");
        ::printf("While the message could be sign, in this case, \n");
        ::printf("it could not be veryfy in this program. \n");
        ::printf("For more information, see the documentation  \n");
        HandleError( "No Certificate with AT_KEYEXCHANGE key in store.");
    }
    ::printf("A recipient's certificate has been acquired. \n");

//--------------------------------------------------------------------
// Получение закрытого ключа
    HCRYPTPROV hCryptProv;
    DWORD keytype = AT_KEYEXCHANGE; // Тип ключа (возвращается)
    BOOL bReleaseContext;
    if (!::CryptAcquireCertificatePrivateKey(
	    pRecipientCert,
            CRYPT_ACQUIRE_SILENT_FLAG,
            NULL,
            &hCryptProv,
            &keytype,
            &bReleaseContext)) 
    {
        HandleError( "Cannot acquire the certificate private key");
    }

//--------------------------------------------------------------------
// Инициализация структуры идентификатора алгоритма.
    CRYPT_ALGORITHM_IDENTIFIER  HashAlgorithm;
    ::memset(&HashAlgorithm, 0, sizeof(HashAlgorithm));

//--------------------------------------------------------------------
// Установка необходимого элемента.
    HashAlgorithm.pszObjId = (LPSTR) GetHashOidForSign(hCryptProv, keytype);

//--------------------------------------------------------------------
// Инициализируем структуру CMSG_SIGNER_ENCODE_INFO
    CMSG_SIGNER_ENCODE_INFO SignerEncodeInfo; // Структура, описывающая отправителя
    ::memset(&SignerEncodeInfo, 0, sizeof(CMSG_SIGNER_ENCODE_INFO));
    SignerEncodeInfo.cbSize = sizeof(CMSG_SIGNER_ENCODE_INFO);
    SignerEncodeInfo.pCertInfo = pRecipientCert->pCertInfo;
    SignerEncodeInfo.hCryptProv = hCryptProv;
    SignerEncodeInfo.dwKeySpec = keytype;
    SignerEncodeInfo.HashAlgorithm = HashAlgorithm;
    SignerEncodeInfo.pvHashAuxInfo = NULL;

//--------------------------------------------------------------------
// Массив отправителей из одного отправителя
    CMSG_SIGNER_ENCODE_INFO SignerEncodeInfoArray[1];
    SignerEncodeInfoArray[0] = SignerEncodeInfo;

    CMSG_SIGNED_ENCODE_INFO SignedMsgEncodeInfo; // Структура, описывающая подписанное сообщение
    ::memset(&SignedMsgEncodeInfo, 0, sizeof(CMSG_SIGNED_ENCODE_INFO));
    SignedMsgEncodeInfo.cbSize = sizeof(CMSG_SIGNED_ENCODE_INFO);
    SignedMsgEncodeInfo.cSigners = 1;
    SignedMsgEncodeInfo.rgSigners = SignerEncodeInfoArray;
    SignedMsgEncodeInfo.cCertEncoded = 0;
    SignedMsgEncodeInfo.rgCertEncoded = NULL;
    SignedMsgEncodeInfo.rgCrlEncoded = NULL;

//--------------------------------------------------------------------
// Создадим дескриптор сообщения
    HCRYPTMSG hMsg =
	::CryptMsgOpenToEncode(
	    TYPE_DER,               // Encoding type
	    0,			    // Flags
	    CMSG_SIGNED,            // Message type*
	    &SignedMsgEncodeInfo,   // Pointer to structure
	    NULL,                   // Inner content object ID
	    NULL);                  // Stream information (not used)
    if(!hMsg)
	HandleError("OpenToEncode failed");
    ::printf("The message to be encoded has been opened. \n");

//--------------------------------------------------------------------
// Поместим в сообщение подписываемые данные
    if(!::CryptMsgUpdate(
	   hMsg,		// Handle to the message
	   pbContent,		// Pointer to the content
	   cbContent,		// Size of the content
	   TRUE)) 		// Last call
	HandleError("MsgUpdate failed");

    ::printf("Content has been added to the encoded message. \n");

//--------------------------------------------------------------------
// Определим длину подписанного сообщения
    if(!::CryptMsgGetParam(
	   hMsg,		// Handle to the message
	   CMSG_CONTENT_PARAM,	// Parameter type
	   0,			// Index
	   NULL,		// Pointer to the blob
	   &cbEncodedBlob))	// Size of the blob
	HandleError("MsgGetParam failed");

    ::printf("The length of the data has been calculated.\n");

//--------------------------------------------------------------------
// Резервируем память, требуемой длины
    pbEncodedBlob = (BYTE *)::malloc(cbEncodedBlob);
    if(!pbEncodedBlob)
	HandleError("Memory allocation failed");

//--------------------------------------------------------------------
// Вернем подписанное сообщение
    if(!::CryptMsgGetParam(
	   hMsg,		    // Handle to the message
	   CMSG_CONTENT_PARAM,	    // Parameter type
	   0,			    // Index
	   pbEncodedBlob,	    // Pointer to the blob
	   &cbEncodedBlob))	    // Size of the blob
	HandleError("MsgGetParam failed");
    ::printf("Message encoded successfully. \n");

// освобождение ресурсов
    ::CryptMsgClose(hMsg);
    if(bReleaseContext)
	::CryptReleaseContext(hCryptProv, 0);
    ::CertFreeCertificateContext(pRecipientCert);

    if (::CertCloseStore(hStoreHandle, CERT_CLOSE_STORE_CHECK_FLAG))
        ::printf("The MY store was closed without incident. \n");
    else
        ::printf("Store closed after encryption -- \n"
            "but not all certificates or CRLs were freed\n");

} // end of do_low_sign

void do_low_verify(void)
{
    HCRYPTPROV hCryptProv;
    if(!::CryptAcquireContext(
	   &hCryptProv,
	   NULL,
	   NULL,	         // Используется провайдер по умолчанию
	   PROV_GOST_2012_256,   // Необходимо для зашифрования и подписи
	   CRYPT_VERIFYCONTEXT))  // Никакие флаги не нужны
	HandleError("Cryptographic context could not be acquired.");
    ::printf("A CSP has been acquired. \n");

//--------------------------------------------------------------------
// Открытие системного хранилища сертификатов.
    HCERTSTORE hStoreHandle = ::CertOpenSystemStore(0, _TEXT("MY"));
    if(!hStoreHandle)
        HandleError( "Error getting store handle.");
    ::printf("The MY store is open. \n");
       
//--------------------------------------------------------------------
// Получение указателя на сертификат издателя с помощью
// функции GetRecipientCert.
    PCCERT_CONTEXT pRecipientCert = GetRecipientCert(hStoreHandle);
    if(!pRecipientCert)
    {
        ::printf("No certificate with a CERT_KEY_CONTEXT_PROP_ID \n");
        ::printf("property and an AT_KEYEXCHANGE private key available. \n");
        ::printf("While the message could be sign, in this case, \n");
        ::printf("it could not be veryfy in this program. \n");
        ::printf("For more information, see the documentation \n");
        HandleError( "No Certificate with AT_KEYEXCHANGE key in store.");
    }
    ::printf("A recipient's certificate has been acquired. \n");

//--------------------------------------------------------------------
// Откроем сообщение для декодирования
    HCRYPTMSG hMsg = ::CryptMsgOpenToDecode(
	TYPE_DER,		// Encoding type
	CMSG_CRYPT_RELEASE_CONTEXT_FLAG, // Flags
	0,			// Use the default message type
	hCryptProv,		// Cryptographic provider
	NULL,			// Recipient information
	NULL);			// Stream information
    if(!hMsg)
        HandleError("OpenToDecode failed");
    ::printf("The message to decode is open. \n");

//--------------------------------------------------------------------
// Поместим в сообщение проверяемые данные
    BOOL bResult = ::CryptMsgUpdate(
        hMsg,			// Handle to the message
        pbEncodedBlob,		// Pointer to the encoded blob
        cbEncodedBlob,		// Size of the encoded blob
        TRUE);			// Last call
    if(!bResult)
	HandleError("Decode MsgUpdate failed");
    ::printf("The encoded blob has been added to the message. \n");
    ::free(pbEncodedBlob);

//--------------------------------------------------------------------
// Определим длину подписанных данных
    DWORD cbDecoded;
    bResult = ::CryptMsgGetParam(
	hMsg,			// Handle to the message
	CMSG_CONTENT_PARAM,	// Parameter type
	0,			// Signed Index
	NULL,			// Address for returned info
	&cbDecoded);		// Size of the returned info
    if(!bResult)
	HandleError("Decode CMSG_CONTENT_PARAM failed");
    ::printf("The message parameter (CMSG_CONTENT_PARAM) has been acquired. Message size: %u\n", cbDecoded);

//--------------------------------------------------------------------
// Вернем подписанные данные
    std::vector<BYTE> pbDecoded(cbDecoded);
    bResult = ::CryptMsgGetParam(
	hMsg,			// Handle to the message
	CMSG_CONTENT_PARAM,	// Parameter type
	0,			// Signer Index
	&pbDecoded[0],		// Address for returned info
	&cbDecoded);		// Size of the returned info
    if(!bResult)
	HandleError("Decode CMSG_CONTENT_PARAM #2 failed");
    ::printf("The message param (CMSG_CONTENT_PARAM) returned. Length is %lu.\n", (unsigned long)cbDecoded);

//--------------------------------------------------------------------
// Проверка ЭЦП
// Сначала определим информация CERT_INFO об отправителе

//--------------------------------------------------------------------
// Если сертификат задан
// создадим справочник в памяти с этим сертификатом
// Это сделано только для того, чтобы затем вернуть сертификат функцией
// CertGetSubjectCertificateFromStore, которая также используется, если
// сертификат отправителя находится в самом сообщении
    HCERTSTORE hStoreHandleM =
	::CertOpenStore(CERT_STORE_PROV_MEMORY, TYPE_DER, 0, CERT_STORE_CREATE_NEW_FLAG, NULL);
    if (!hStoreHandleM)
	HandleError("Cannot create temporary store in memory.");

    /* Добавим сертификат в справочник*/
    bResult = CertAddCertificateContextToStore(hStoreHandleM, pRecipientCert, CERT_STORE_ADD_ALWAYS, NULL);
    if (!bResult)
	HandleError("Cannot add user certificate to store.");

    PCERT_INFO pSignerCertInfo = pRecipientCert->pCertInfo;

//--------------------------------------------------------------------
// Найдем сертификат отправителя в справочнике
    PCCERT_CONTEXT pSignerCertContext =
	::CertGetSubjectCertificateFromStore(
	    hStoreHandleM,	// Handle to store
	    TYPE_DER,           // Encoding type
	    pSignerCertInfo);
    if(!pSignerCertContext)
	HandleError("Verify GetSubjectCert failed");
    ::printf("A signer certificate has been retrieved.");
 
//--------------------------------------------------------------------
// Используя структуру CERT_INFO проверяем ЭЦП сообщения
    PCERT_INFO pSignerCertificateInfo = pSignerCertContext->pCertInfo;
    bResult = ::CryptMsgControl(
	hMsg,			    // Handle to the message
	0,			    // Flags
	CMSG_CTRL_VERIFY_SIGNATURE, // Control type
	pSignerCertificateInfo);    // Pointer to the CERT_INFO
    if(bResult)
	::printf("\nSignature was VERIFIED.\n");
    else
	::printf("\nThe signature was NOT VERIFIED.\n");

// освобождение памяти
    ::CertFreeCertificateContext(pSignerCertContext);
    ::CertFreeCertificateContext(pRecipientCert);

    if(::CertCloseStore(hStoreHandleM, CERT_CLOSE_STORE_CHECK_FLAG))
	::printf("Memory store was closed without incident.\n");
    else
	::printf("Memory store closed -- but not all certificates or CRLs were freed.\n");

    // раньше было нельзя, т.к. использовался pRecipientCert
    // с флагом CERT_CLOSE_STORE_CHECK_FLAG получили бы сообщение о неосвобождённых ресурсах
    if(::CertCloseStore(hStoreHandle, CERT_CLOSE_STORE_CHECK_FLAG))
	::printf("The MY store was closed without incident.\n");
    else
	::printf("The MY store closed  -- but not all certificates or CRLs were freed.\n");

    ::CryptMsgClose(hMsg);
} //end of do_low_verify

//  В этом примере используется функция HandleError, функция обработки
//  простых ошибок, для печати сообщения об ошибке в стандартный файл 
//  ошибок (stderr) и выхода из программы. 
//  В большинстве приложений эта функция заменяется другой функцией, 
//  которая выводит более полное сообщение об ошибке.

static void HandleError(const char *s)
{
    const DWORD err = ::GetLastError();
    ::printf("Error number     : 0x%x\n", err);
    ::printf("Error description: %s\n", s);
    if(!err)
	::exit(1);
    ::exit(err);
} // Конец определения HandleError

// Проверка типа провайдера 
static BOOL isGostType(DWORD dwProvType)
{
    return IS_GOST_PROV(dwProvType);
}

//--------------------------------------------------------------------
// GetRecipientCert перечисляет сертификаты в хранилище и находит
// первый сертификат, обладающий ключем AT_EXCHANGE. Если сертификат
// сертификат найден, то возвращается указатель на него.  
static PCCERT_CONTEXT GetRecipientCert(HCERTSTORE hCertStore) 
//-------------------------------------------------------------------- 
// Устанавливаемый параметр: 
// hCertStore, дескриптор хранилища, в котором будет осуществлен поиск. 
{ 
//-------------------------------------------------------------------- 
// Объявление и инициализация локальных переменных. 
    PCCERT_CONTEXT pCertContext = NULL; 
    bool fMore = true;

//-------------------------------------------------------------------- 
// Поиск сертификатов в хранилище до тех пор, пока не будет достигнут 
// конец хранилища, или сертификат с ключем AT_KEYEXCHANGE не будет найден. 

    do { 
//------------------------------------------------------------- 
// Для простоты в этом коде реализован только поиск первого 
// вхождения ключа AT_KEYEXCHANGE. Во многих случаях, помимо 
// поиска типа ключа, осуществляется также поиск определенного 
// имени субъекта. 
	DWORD PropId = CERT_KEY_PROV_INFO_PROP_ID; 
	pCertContext = ::CertFindCertificateInStore( 
	    hCertStore, // Дескриптор хранилища, в котором будет осуществлен поиск. 
	    TYPE_DER,   // Тип зашифрования. В этом поиске не используется. 
	    0,          // dwFindFlags. Специальный критерий поиска. 
	                // в этом поиске не используется. 
	    CERT_FIND_PROPERTY, 
                        // Тип поиска. Задает вид поиска, который будет 
                        // осуществлен. В этом случае, поиск сертификатов,  
                        // имеющих определенные расширенные свойства. 
	    &PropId,    // pvFindPara. Выдает определенное значение
                        // поиска, в данном случае идентификатор  
                        // расширенного свойства. 
	    pCertContext); 
                        // pCertContext равен NULL для первого вызова  
                        // функции. Если функция вызывается в цикле,
                        // после первого ее вызова pCertContext становится
                        // указателем, возвращенным предыдущим вызовом.

	if (!pCertContext) 
	    break;

//------------------------------------------------------------- 
// Однократный вызов функции CertGetCertificateContextProperty  
// для получения возврашенного размера структуры. 
	DWORD dwSize;
        if(!::CertGetCertificateContextProperty( 
             pCertContext, 
             CERT_KEY_PROV_INFO_PROP_ID, 
             NULL, &dwSize))
	    HandleError("Error getting key property."); 

//-------------------------------------------------------------- 
// распределение памяти под возвращенную структуру. 
	std::vector<unsigned char> vKI(dwSize);
	CRYPT_KEY_PROV_INFO* pKeyInfo = reinterpret_cast<CRYPT_KEY_PROV_INFO*> (&vKI[0]);

//-------------------------------------------------------------- 
// Получение структуры информации о ключе. 

        if(!::CertGetCertificateContextProperty(
	       pCertContext,
	       CERT_KEY_PROV_INFO_PROP_ID,
	       pKeyInfo,
	       &dwSize))
            HandleError("The second call to the function failed.");

//------------------------------------------- 
// Проверка члена dwKeySpec на расширенный ключ и типа провайдера.
        if(pKeyInfo->dwKeySpec == AT_KEYEXCHANGE && isGostType(pKeyInfo->dwProvType)) 
            fMore = false;
    } while(fMore && pCertContext);

    if (pCertContext)
    {
        TCHAR sName[256];
        CertGetNameString(pCertContext, CERT_NAME_FRIENDLY_DISPLAY_TYPE, 0, NULL, sName, 256);
        _tprintf(_TEXT("GetRecipientCert: %s\n"), sName);
    }
    return pCertContext;
} // Конец определения GetRecipientCert 

// 2016-sep-01 на UNIX не освобождается hPRov CPCSP-6767
// END-OF-FILE
