/* vim:set sw=4 ts=8 fileencoding=cp1251::Кодировка:WINDOWS-1251[АБЁЪЯабёъя] */
#ifdef _WIN32
    #pragma setlocale("rus")
#endif
/*
 * Copyright(C) 2013 Проект ИОК
 *
 * Этот файл содержит информацию, являющуюся
 * собственностью компании Крипто Про.
 *
 * Любая часть этого файла не может быть скопирована,
 * исправлена, переведена на другие языки,
 * локализована или модифицирована любым способом,
 * откомпилирована, передана по сети с или на
 * любую компьютерную систему без предварительного
 * заключения соглашения с компанией Крипто Про.
 *
 * Программный код, содержащийся в этом файле, предназначен
 * исключительно для целей обучения и не может быть использован
 * для защиты информации.
 *
 * Компания Крипто-Про не несет никакой
 * ответственности за функционирование этого кода.
 */

/*!
 * \version $Revision: 89611 $
 * \date $Date: 2013-05-29 23:43:11 +0400 (СЃСЂ, 29 РјР°Р№ 2013) $
 * \author $Author: lse $
 *
 * \brief Пример взаимодействия с реализацей TLS уровня ядра Linux
 */

//--------------------------------------------------------------------
// В данном примере устанавливается соединение клиента с web сервером
// по защищенному каналу.
//--------------------------------------------------------------------

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <pthread.h>
#include <time.h>

#   include "CSP_WinDef.h"
#   include "CSP_WinCrypt.h"
#   include "CSP_Sspi.h"
#   include "CSP_SChannel.h"
#   include "Crypt.h"
#   include "WCMemSTDC.h"
#   include <sys/types.h>
#   ifdef _AIX
#	include <fcntl.h>
#   else
#	include <sys/fcntl.h>
#   endif
#   include <sys/stat.h>
#   include <sys/socket.h>
#   include <netinet/in.h>
#   include <arpa/inet.h>
#   include <netdb.h>
#   include <errno.h>
#   include <unistd.h>
#   include <signal.h>

#   define INVALID_SOCKET (-1)
#   define IS_SOCKET_ERROR(a) (a<0)
    typedef int SOCKET;
    typedef struct sockaddr_in SOCKADDR_IN;
    typedef struct sockaddr *LPSOCKADDR;
    static int WSAGetLastError()
    {
      return errno;
    }
#   define LocalAlloc(a, b) malloc(b)
#   define LocalFree free
#   define MoveMemory memmove
#   define closesocket close

#include "UTLS.h"
#include "WCKernelPart.h"

#define IO_BUFFER_SIZE  0x8000


static LPSTR   pszProxyServer  = "proxy";
static INT     iProxyPort      = 80;

// Опции пользователя.
static LPSTR   pszServerName   = NULL;
static INT     iPortNumber     = 443;
static LPSTR   pszFileName     = "Default.Htm";
static DWORD   dwProtocol      = 0;
static LPSTR   pszContName     = NULL;
static BOOL    fExportSession  = FALSE;
static BOOL    fImportSession  = FALSE;
static BOOL    fUserCertProvided = FALSE;
static LPSTR   pszExportFileName  = NULL;
static LPSTR   pszImportFileName  = NULL;
static LPSTR   pszUserCertStr = NULL;

static VTABLEPROVSTRUC VTABLE_2001 = { 3, 0, 0, PROV_GOST_2012_256, 0, 0, 0 };

static HCERTSTORE      hMyCertStore = NULL;
static HCRYPTMODULE    hCPC2Crypt = NULL;
static CPC_CONFIG      CPCConfig;
static SCHANNEL_CRED   SchannelCred;

static utls_gost_handle hUTLS=0;
static CredHandle hClientCreds;
static CredHandle hCertClientCreds;
static BOOL fCredsInitialized = FALSE;
static pthread_once_t POTSaveOnce = PTHREAD_ONCE_INIT;
static CtxtHandle * phSaveContext = NULL;
#ifdef KRN_EMUL
static HCRYPTPROV hProvExch=0;
static PDRTKTLS_DRV pKDrv=NULL;
#endif

static
SECURITY_STATUS
CreateCredentials(
    PCredHandle phCreds);

static INT
ConnectToServer(
    LPSTR pszServerName,
    INT   iPortNumber,
    SOCKET *pSocket);

static
SECURITY_STATUS
PerformClientHandshake(
    SOCKET          Socket,
    PCredHandle     phCreds,
    LPSTR           pszServerName,
    CtxtHandle *    phContext,
    SecBuffer *     pExtraData);

static
SECURITY_STATUS
ClientHandshakeLoop(
    SOCKET          Socket,
    PCredHandle     phCreds,
    CtxtHandle *    phContext,
    BOOL            fDoInitialRead,
    SecBuffer *     pExtraData);

static
SECURITY_STATUS
HttpsGetFile(
    SOCKET          Socket,
    PCredHandle     phCreds,
    CtxtHandle *    phContext,
    LPSTR           pszFileName);

static 
void
DisplayCertChain(
    PCCERT_CONTEXT  pServerCert,
    BOOL            fLocal);

static 
DWORD
VerifyServerCertificate(
    PCCERT_CONTEXT  pServerCert,
    PSTR            pszServerName,
    DWORD           dwCertFlags);

static
LONG
DisconnectFromServer(
    SOCKET          Socket, 
    PCredHandle     phCreds,
    CtxtHandle *    phContext);

static void
DisplayConnectionInfo(
    CtxtHandle *phContext);

static
HRESULT
GetUserCreds(
    LPSTR pszUserName,    
    PCredHandle phCreds); 

static BOOL InitializeDriver(PDRTKTLS_DRV *ppKDrv,HCRYPTPROV hProv, BOOL fCreateSA);

//-------------------------------------------------------------
// Функция вывода информации об использовании данного примера.
static void Usage(void)
{
    printf("\n");
    printf("USAGE: webclient -s<server> \n");
    printf("\n");
    printf("    -s<server>      DNS name of server.\n");
}//Usage


//-------------------------------------------------------------
// Функция загрузки библиотеки.
static BOOL
InitUTLS(void)
{
    utls_gost_in gi;
    utls_gost_out go;

    memset(&gi, 0, sizeof(gi));
    memset(&go, 0, sizeof(go));
    cputls_init_gost(NULL, &gi, 0, &go);
    gi.allocatedMem = go.requiredMem;
    hUTLS = (utls_gost_handle)malloc(gi.allocatedMem);
    cputls_init_gost(hUTLS, &gi, 0, &go);

    memset(&CPCConfig, 0, sizeof(CPC_CONFIG));
    CPCConfig.cbSize = sizeof(CPC_CONFIG);
    CPCGetDefaultConfig(&CPCConfig, NULL);
    stdInitMemory(&CPCConfig.pArena, NULL, 0);

    CPC2CryptCreateProvider(&hCPC2Crypt, NULL);
    return TRUE;
}//InitUTLS

//-------------------------------------------------------------
// Функция разгрузки библиотеки.

static void
DoneUTLS(void)
{
    cputls_shutdown_gost(hUTLS,0);

    if(hCPC2Crypt->DestroyProvider)
    {
    	hCPC2Crypt->DestroyProvider(hCPC2Crypt);
	// hCPC2Crypt->DestroyProvider = 0;
	hCPC2Crypt = 0;
    }
    if(CPCConfig.pArena->pDoneMemory)
    {
	CPCConfig.pArena->pDoneMemory(CPCConfig.pArena);
	CPCConfig.pArena->pDoneMemory = 0;
    }
} //DoneUTLS

BOOL CreateSelfExportToken(LPCSTR ContName, utlsdb_token * pTok)
{
    HCRYPTPROV hProv;
    unsigned tlsdb_len=0;
    unsigned char * our_tlsdb=NULL;
    if (!CryptAcquireContext(&hProv,ContName,0,PROV_GOST_2012_256,0))
	return FALSE;
    hUTLS->tlsdb.CreateProvFn(hCPC2Crypt,hProv,0,&pTok->Priv,0,&tlsdb_len);
    our_tlsdb=(unsigned char *)malloc(tlsdb_len);
    hUTLS->tlsdb.CreateProvFn(hCPC2Crypt,hProv,0,&pTok->Priv,our_tlsdb,&tlsdb_len);
    hUTLS->tlsdb.deSerializePubKeyFn(hCPC2Crypt,hProv,our_tlsdb,tlsdb_len,0,&pTok->Pub,&CPCConfig);
    free(our_tlsdb);
    return TRUE;
}
void DestroyExportToken(utlsdb_token *pTok)
{
    hUTLS->tlsdb.DestroyPubKeyFn(0,&pTok->Pub);
    hUTLS->tlsdb.DestroyPrivKeyFn(0,&pTok->Priv);
}

BOOL ExportSessionToFile(CtxtHandle * phContext, utlsdb_token * pTok, LPCSTR pszFileName)
{
    FILE * f=NULL;
    BOOL fRet=FALSE;
    size_t szread=0;
    SecBuffer SB;
    SB.cbBuffer=0;
    SB.pvBuffer=NULL;
    if (hUTLS->pSSPI->ExportSecurityContext(phContext,
				0,
				&SB,(void **)pTok))
	return FALSE;				
    SB.pvBuffer=malloc(SB.cbBuffer);
    if (!SB.pvBuffer)
	return FALSE;
    if (hUTLS->pSSPI->ExportSecurityContext(phContext,
				0,
				&SB,(void **)pTok))
	goto done;			
    f=fopen(pszFileName,"w");
    if (!f)
	goto done;
    while (szread<SB.cbBuffer)
	szread+=fwrite(((LPBYTE)SB.pvBuffer)+szread,1,SB.cbBuffer-szread,f);	
    fRet=TRUE;
done:    
    if (f)
	fclose(f);
    free(SB.pvBuffer);
    return fRet;
}

BOOL ImportSessionFromFile(LPCSTR pszFileName, utlsdb_token * pTok, CtxtHandle * phContext,CredHandle * pCreds)
{
    FILE * f=NULL;
    SecBuffer SB;
    SECURITY_STATUS scRet;
    size_t szlen,bread=0;
    f=fopen(pszFileName,"r");

    if (!f)
	return FALSE;
    fseek(f,0,SEEK_END);	
    szlen=(size_t)ftell(f);
    SB.pvBuffer=malloc(szlen);
    if (SB.pvBuffer == NULL)
    {
	fclose(f);
	return FALSE;
    }
    fseek(f,0,SEEK_SET);
    while (bread<szlen)
	bread+=fread(((LPBYTE)SB.pvBuffer)+bread,1,szlen-bread,f);
    fclose(f);
    SB.BufferType=SECBUFFER_DATA;
    SB.cbBuffer=(DWORD)szlen;
    scRet=hUTLS->pSSPI->ImportSecurityContext(NULL,&SB,pTok,phContext);
    free(SB.pvBuffer);
    if (scRet != STATUS_SUCCESS)
	return FALSE;
    scRet = hUTLS->pSSPI->QueryContextAttributes(phContext,
					     CPUTLS_ATTR_CREDENTIAL_HANDLE,
                                             (PVOID)pCreds);
    if (scRet != STATUS_SUCCESS)
    {
	return FALSE;
    }
    return TRUE;
}

int sig_ign_restart(int signum)
{
    struct sigaction my_sig;
    memset(&my_sig,0,sizeof(my_sig));
    my_sig.sa_handler=SIG_IGN;
    my_sig.sa_flags=SA_RESTART;
    return sigaction(signum,&my_sig,NULL);
}
void SaveSessionOnce(void)
{

    if (fExportSession)
    {
	utlsdb_token tok;
	if (!CreateSelfExportToken(pszContName, &tok))
	    pthread_exit((void *)SEC_E_INTERNAL_ERROR);

	if (!ExportSessionToFile(phSaveContext, &tok,pszExportFileName))
	    pthread_exit((void *)SEC_E_INTERNAL_ERROR);
	DestroyExportToken(&tok);
    }
}


void * ClientThread(void * arg)
{
    /*SECURITY_STATUS*/ void *ret = (void *)SEC_E_INTERNAL_ERROR;
    SOCKET  Socket = INVALID_SOCKET;
    BOOL fContextInitialized;
    SecBuffer  ExtraData;
    CtxtHandle hContext;
    PCCERT_CONTEXT pRemoteCertContext = NULL;
    SECURITY_STATUS Status = SEC_E_INTERNAL_ERROR;

    //
    // Соединение с сервером.
    //

    if(ConnectToServer(pszServerName, iPortNumber, &Socket))
    {
        printf("Error connecting to server\n");
        goto cleanup;
    }


	// 
        // Установление связи
        //
    if(PerformClientHandshake(Socket,
			      &hClientCreds,
			      pszServerName,
			      &hContext,
			      &ExtraData))
    {
	printf("Error performing handshake\n");
	goto cleanup;
    }
    fContextInitialized = TRUE;


    //
    // Аутентификация мандатов сервера.
    //

    // Получение сертификата сервера.
    Status = hUTLS->pSSPI->QueryContextAttributes(&hContext,
                                             SECPKG_ATTR_REMOTE_CERT_CONTEXT,
                                             (PVOID)&pRemoteCertContext);
    if(Status != SEC_E_OK)
    {
        printf("Error 0x%x querying remote certificate\n", Status);
        goto cleanup;
    }

    // Вывод цепочки сертификатов сервера.
    DisplayCertChain(pRemoteCertContext, FALSE);

    // Проверка действительности сертификата сервера.
    Status = VerifyServerCertificate(pRemoteCertContext,
                                     pszServerName,
                                     0);
    if(Status)
    {
        // Сертификат сервера не действителен. Возможно было осуществлено
	// соединение с недопустимым сервером, либо производилась атака
	// "противник посередине".

        // Лучше всего прервать соединение.

        printf("**** Error 0x%x authenticating server credentials!\n", Status);
//        goto cleanup;
    }

    // Освобождение контекста сертификата сервера.
    CertFreeCertificateContext(pRemoteCertContext);
    pRemoteCertContext = NULL;


    //
    // Вывод информации о соединении. 
    //

    DisplayConnectionInfo(&hContext);
    phSaveContext=&hContext;
    pthread_once(&POTSaveOnce,SaveSessionOnce);
    //
    // Чтение файла с сервера.
    //

    if(HttpsGetFile(Socket, 
                    &hClientCreds,
                    &hContext, 
                    pszFileName))
    {
        printf("Error fetching file from server\n");
        goto cleanup;
    }

#if 0 
    // Unimplemented
    //
    // Отправка уведомления серверу и закрытие соединения.
    //

    if(DisconnectFromServer(Socket, &hClientCreds, &hContext))
    {
        printf("Error disconnecting from server\n");
        goto cleanup;
    }
    fContextInitialized = FALSE;
    Socket = INVALID_SOCKET;
#endif

    ret = 0;
cleanup:    
    // Освобождение контекста сертификата сервера.
    if(pRemoteCertContext)
    {
        CertFreeCertificateContext(pRemoteCertContext);
        pRemoteCertContext = NULL;
    }

    // Освобождение дескриптора SSPI контекста.
    if(fContextInitialized)
    {
        hUTLS->pSSPI->DeleteSecurityContext(&hContext);
    }

    // Закрытие сокета.
    if(Socket != INVALID_SOCKET)
    {
        closesocket(Socket);
    }
    return (void *)ret;
}

//-------------------------------------------------------------
// Функция main.

int main(int argc, char *argv[])
{

    CtxtHandle hContext;

    BOOL fContextInitialized = FALSE;
    pthread_t * threads = NULL;

    SECURITY_STATUS Status;
#ifndef KRN_EMUL    
    HCRYPTPROV hProvExch=0;
    PDRTKTLS_DRV pKDrv=NULL;
#endif    


    int ret = 1;
    INT i;
    INT iOption;
    int nThreads=1;
    PCHAR pszOption;

    //
    // Разбор командной строки.
    //

    if(argc <= 1)
    {
        Usage();
        return 1;
    }
    sig_ign_restart(SIGHUP);
    sig_ign_restart(SIGPIPE);

    for(i = 1; i < argc; i++) 
    {
        if(argv[i][0] == '/') argv[i][0] = '-';

        if(argv[i][0] != '-') 
        {
            printf("**** Invalid argument \"%s\"\n", argv[i]);
            Usage();
            return 1;
        }

        iOption = argv[i][1];
        pszOption = &argv[i][2];

        switch(iOption) 
        {
        case 's':
            pszServerName = pszOption;
            break;
        case 'f':
            pszFileName = pszOption;
            break;
        case 'c':
            pszContName = pszOption;
            break;
        case 'e':
	    pszExportFileName = pszOption;
            fExportSession = TRUE;
            break;
        case 'i':
	    pszImportFileName = pszOption;
            fImportSession = TRUE;
            break;
	case 'u':
	    pszUserCertStr = pszOption;
	    fUserCertProvided = TRUE;
	    break;
	case 'p':
	    iPortNumber = atoi(pszOption);
	    break;
	case 'n':
	    nThreads = atoi(pszOption);
	    break;
        
        default:
            printf("**** Invalid option \"%s\"\n", argv[i]);
            Usage();
            return 1;
        }
    }


    if(!InitUTLS())
    {
        printf("Error initializing the security library\n");
        goto cleanup;
    }

    //
    // Создание мандатов.
    //
    if (fImportSession)
    {
	utlsdb_token tok;
	if (!CreateSelfExportToken(pszContName, &tok))
	{
	    printf("Cannot create self-export token\n");
	    goto cleanup;
	}

	if (!ImportSessionFromFile(pszImportFileName, &tok, &hContext,&hClientCreds))
	{
	    printf("Error importing session from %s\n",pszImportFileName);
	    DestroyExportToken(&tok);
	    goto cleanup;
	}
	DestroyExportToken(&tok);
	fContextInitialized = TRUE;
    }
    else
    {
	if(CreateCredentials(&hClientCreds))
	{
	    printf("Error creating credentials\n");
	    goto cleanup;
	}
    }
    fCredsInitialized = TRUE;
    if (fUserCertProvided)
    {
	if (GetUserCreds(pszUserCertStr,&hCertClientCreds))
	{
	    printf("Error getting user creds\n");
	    goto cleanup;
	}
    }

    CryptAcquireContext(&hProvExch,NULL,NULL,PROV_GOST_2012_256,CRYPT_VERIFYCONTEXT);
    InitializeDriver(&pKDrv,hProvExch, TRUE);

    if (nThreads<=0)
    {
	printf("Invalid number of threads\n");
	goto cleanup;
    }
    threads=(pthread_t *)malloc(nThreads*sizeof(pthread_t));
    for (i=0;i<nThreads;i++)
	if (pthread_create(threads+i,NULL,ClientThread,NULL))
	    goto cleanup;

    ret = 0;
    for (i=0;i<nThreads;i++)
    {
	void * rv;
	if (pthread_join(threads[i],&rv))
	    ret|=1;
	if (rv)    
	    ret|=1;
    }
    free(threads);

cleanup:

    // Освобождение дескриптора SSPI мандатов.
    if(fCredsInitialized)
    {
        hUTLS->pSSPI->FreeCredentialsHandle(&hClientCreds);
    }
    if(fImportSession)
    {
        hUTLS->pSSPI->DeleteSecurityContext(&hContext);
    }

    // Закрытие хранилища сертификатов "MY".
    if(hMyCertStore)
    {
        CertCloseStore(hMyCertStore, 0);
    }

    DoneUTLS();
    if (pKDrv)	
    {
	kdone_gost(pKDrv);
	free(pKDrv); 	
    }
    if (hProvExch)
	CryptReleaseContext(hProvExch,0);

    printf("Done\n");
    return ret;
} // main

//-------------------------------------------------------------
// Функция создания мандатов.
static
SECURITY_STATUS
CreateCredentials(
    PCredHandle phCreds)            // out
{
    TimeStamp       tsExpiry;
    SECURITY_STATUS Status;

    DWORD           cSupportedAlgs = 0;
    ALG_ID          rgbSupportedAlgs[16];

    PCCERT_CONTEXT  pCertContext = NULL;

    // Открытие хранилища сертификатов "MY" , в котором Internet Explorer
    // хранит сертификаты клиента.
    hMyCertStore = CertOpenSystemStore(0, "MY");

        if(!hMyCertStore)
        {
            printf("**** Error 0x%x returned by CertOpenSystemStore\n", 
                GetLastError());
            return SEC_E_NO_CREDENTIALS;
        }
   
   
    // Построение структуры Schannel мандатов. 
    // В данном примере определяются используемые протокол и сертификат.

    ZeroMemory(&SchannelCred, sizeof(SchannelCred));

    SchannelCred.dwVersion  = SCHANNEL_CRED_VERSION;
    if(pCertContext)
    {
        SchannelCred.cCreds     = 1;
        SchannelCred.paCred     = &pCertContext;
    }

    SchannelCred.grbitEnabledProtocols = dwProtocol;

    if(cSupportedAlgs)
    {
        SchannelCred.cSupportedAlgs    = cSupportedAlgs;
        SchannelCred.palgSupportedAlgs = rgbSupportedAlgs;
    }

    SchannelCred.dwFlags |= SCH_CRED_NO_DEFAULT_CREDS;

    // Флаг SCH_CRED_MANUAL_CRED_VALIDATION установлен, поскольку
    // поскольку в данном примере сертификат сервера проверяется "вручную". 
    SchannelCred.dwFlags |= SCH_CRED_MANUAL_CRED_VALIDATION;

    //
    // Создание SSPI мандата.
    //

    Status = hUTLS->pSSPI->AcquireCredentialsHandleA(
                        NULL,                   // Name of principal    
                        UNISP_NAME_A,           // Name of package
                        SECPKG_CRED_OUTBOUND,   // Flags indicating use
                        NULL,                   // Pointer to logon ID
                        &SchannelCred,          // Package specific data
                        NULL,                   // Pointer to GetKey() func
                        NULL,                   // Value to pass to GetKey()
                        phCreds,                // (out) Cred Handle
                        &tsExpiry);             // (out) Lifetime (optional)
    if(Status != SEC_E_OK)
    {
        printf("**** Error 0x%x returned by AcquireCredentialsHandle\n", Status);
        goto cleanup;
    }

cleanup:

    //
    // Освобождение контекста сертификата. В Schannel уже создана его копия.
    //

    if(pCertContext)
    {
        CertFreeCertificateContext(pCertContext);
    }


    return Status;
} //CreateCredentials

//-------------------------------------------------------------
// Функция, устанавливающая соединение с сервером.
static INT
ConnectToServer(
    LPSTR    pszServerName, // in
    INT      iPortNumber,   // in
    SOCKET * pSocket)       // out
{
  SOCKET Socket;
  struct sockaddr_in sin;
  struct hostent *hp;

  Socket = socket(PF_INET, SOCK_STREAM, 0);
  if(Socket == INVALID_SOCKET)
    {
      printf("**** Error %d creating socket\n", WSAGetLastError());
      return WSAGetLastError();
    }

  sin.sin_family = AF_INET;
  sin.sin_port = htons((unsigned short)iPortNumber);

  if((hp = gethostbyname(pszServerName)) == NULL)
    {
      printf("**** Error %d returned by gethostbyname\n", WSAGetLastError());
      return WSAGetLastError();
    }
  else
    {
      memcpy(&sin.sin_addr, hp->h_addr_list[0], 4);
    }
    

  if(IS_SOCKET_ERROR(connect(Socket, (struct sockaddr *)&sin, sizeof(sin))))
    {
      printf("**** Error %d connecting to \"%s\" (%s)\n", 
	     WSAGetLastError(),
	     pszServerName, 
	     inet_ntoa(sin.sin_addr));
      closesocket(Socket);
      return WSAGetLastError();
    }

  *pSocket = Socket;

  return SEC_E_OK;
} //ConnectToServer

//-------------------------------------------------------------
// Функция, выполняющая разрыв соединения с сервером.

#if 0 // Unimplemented
static
LONG
DisconnectFromServer(
    SOCKET          Socket, 
    PCredHandle     phCreds,
    CtxtHandle *    phContext)
{
    DWORD           dwType;
    char*           pbMessage;
    DWORD           cbMessage;
    DWORD           cbData;

    SecBufferDesc   OutBuffer;
    SecBuffer       OutBuffers[1];
    DWORD           dwSSPIFlags;
    unsigned long   dwSSPIOutFlags;
    TimeStamp       tsExpiry;
    DWORD           Status;

    //
    // Уведомление schannel о закрытии соединения.
    //

    dwType = SCHANNEL_SHUTDOWN;

    OutBuffers[0].pvBuffer   = &dwType;
    OutBuffers[0].BufferType = SECBUFFER_TOKEN;
    OutBuffers[0].cbBuffer   = sizeof(dwType);

    OutBuffer.cBuffers  = 1;
    OutBuffer.pBuffers  = OutBuffers;
    OutBuffer.ulVersion = SECBUFFER_VERSION;

    Status = hUTLS->pSSPI->ApplyControlToken(phContext, &OutBuffer);

    if(FAILED(Status)) 
    {
        printf("**** Error 0x%x returned by ApplyControlToken\n", Status);
        goto cleanup;
    }

    //
    // Построение SSL сообщения, являющегося уведомлением о закрытии.
    //

    dwSSPIFlags = ISC_REQ_SEQUENCE_DETECT   |
                  ISC_REQ_REPLAY_DETECT     |
                  ISC_REQ_CONFIDENTIALITY   |
                  ISC_RET_EXTENDED_ERROR    |
                  ISC_REQ_ALLOCATE_MEMORY   |
                  ISC_REQ_STREAM;

    OutBuffers[0].pvBuffer   = NULL;
    OutBuffers[0].BufferType = SECBUFFER_TOKEN;
    OutBuffers[0].cbBuffer   = 0;

    OutBuffer.cBuffers  = 1;
    OutBuffer.pBuffers  = OutBuffers;
    OutBuffer.ulVersion = SECBUFFER_VERSION;

    Status = hUTLS->pSSPI->InitializeSecurityContextA(
                    phCreds,
                    phContext,
                    NULL,
                    dwSSPIFlags,
                    0,
                    SECURITY_NATIVE_DREP,
                    NULL,
                    0,
                    phContext,
                    &OutBuffer,
                    &dwSSPIOutFlags,
                    &tsExpiry);

    if(FAILED(Status)) 
    {
        printf("**** Error 0x%x returned by InitializeSecurityContext\n", Status);
        goto cleanup;
    }

    pbMessage = OutBuffers[0].pvBuffer;
    cbMessage = OutBuffers[0].cbBuffer;


    //
    // Посылка этого сообщения серверу.
    //

    if(pbMessage != NULL && cbMessage != 0)
    {
        cbData = send(Socket, pbMessage, cbMessage, 0);
        if(IS_SOCKET_ERROR(cbData) || cbData == 0)
        {
            Status = WSAGetLastError();
            printf("**** Error %d sending close notify\n", Status);
            goto cleanup;
        }

        printf("Sending Close Notify\n");
        printf("%d bytes of handshake data sent\n", cbData);

        
        // Освобождение выходного буфера.
        hUTLS->pSSPI->FreeContextBuffer(pbMessage);
    }
    

cleanup:

    // Освобождение закрытого контекста.
    hUTLS->pSSPI->DeleteSecurityContext(phContext);

    // Закрытие сокета.
    closesocket(Socket);

    return Status;
} // DisconnectFromServer
#endif

//-------------------------------------------------------------
// Функция установления связи клиента с сервером.
static
SECURITY_STATUS
PerformClientHandshake(
    SOCKET          Socket,         // in
    PCredHandle     phCreds,        // in
    LPSTR           pszServerName,  // in
    CtxtHandle *    phContext,      // out
    SecBuffer *     pExtraData)     // out
{
    SecBufferDesc   OutBuffer;
    SecBuffer       OutBuffers[1];
    DWORD           dwSSPIFlags;
    unsigned long   dwSSPIOutFlags;
    TimeStamp       tsExpiry;
    SECURITY_STATUS scRet;
    DWORD           cbData;

    dwSSPIFlags = ISC_REQ_SEQUENCE_DETECT   |
                  ISC_REQ_REPLAY_DETECT     |
                  ISC_REQ_CONFIDENTIALITY   |
                  ISC_RET_EXTENDED_ERROR    |
                  ISC_REQ_ALLOCATE_MEMORY   |
                  ISC_REQ_STREAM;

    //
    //  Инициализация сообщения ClientHello и генерация токена.
    //

    OutBuffers[0].pvBuffer   = NULL;
    OutBuffers[0].BufferType = SECBUFFER_TOKEN;
    OutBuffers[0].cbBuffer   = 0;

    OutBuffer.cBuffers = 1;
    OutBuffer.pBuffers = OutBuffers;
    OutBuffer.ulVersion = SECBUFFER_VERSION;

    scRet = hUTLS->pSSPI->InitializeSecurityContextA(
                    phCreds,
                    NULL,
                    pszServerName,
                    dwSSPIFlags,
                    0,
                    SECURITY_NATIVE_DREP,
                    NULL,
                    0,
                    phContext,
                    &OutBuffer,
                    &dwSSPIOutFlags,
                    &tsExpiry);

    if(scRet != SEC_I_CONTINUE_NEEDED)
    {
        printf("**** Error %d returned by InitializeSecurityContext (1)\n", scRet);
        return scRet;
    }

    // Посылка отвера серверу.
    if(OutBuffers[0].cbBuffer != 0 && OutBuffers[0].pvBuffer != NULL)
    {
        cbData = send(Socket,
                      OutBuffers[0].pvBuffer,
                      OutBuffers[0].cbBuffer,
                      0);
        if(IS_SOCKET_ERROR(cbData) || cbData == 0)
        {
            printf("**** Error %d sending data to server (1)\n", WSAGetLastError());
            hUTLS->pSSPI->FreeContextBuffer(OutBuffers[0].pvBuffer);
            hUTLS->pSSPI->DeleteSecurityContext(phContext);
            return SEC_E_INTERNAL_ERROR;
        }

        printf("%d bytes of handshake data sent\n", cbData);

        
        // Освобождение выходного буфера.
        hUTLS->pSSPI->FreeContextBuffer(OutBuffers[0].pvBuffer);
        OutBuffers[0].pvBuffer = NULL;
    }


    return ClientHandshakeLoop(Socket, phCreds, phContext, TRUE, pExtraData);
} // PerformClientHandshake

//-------------------------------------------------------------
// Функция обмена сообщениями между клиентом и сервером.
static
SECURITY_STATUS
ClientHandshakeLoop(
    SOCKET          Socket,         // in
    PCredHandle     phCreds,        // in
    CtxtHandle *    phContext,      // in, out
    BOOL            fDoInitialRead, // in
    SecBuffer *     pExtraData)     // out
{
    SecBufferDesc   InBuffer;
    SecBuffer       InBuffers[2];
    SecBufferDesc   OutBuffer;
    SecBuffer       OutBuffers[1];
    DWORD           dwSSPIFlags;
    unsigned long   dwSSPIOutFlags;
    TimeStamp       tsExpiry;
    SECURITY_STATUS scRet;
    DWORD           cbData;

    char*          IoBuffer;
    DWORD           cbIoBuffer;
    BOOL            fDoRead;


    dwSSPIFlags = ISC_REQ_SEQUENCE_DETECT   |
                  ISC_REQ_REPLAY_DETECT     |
                  ISC_REQ_CONFIDENTIALITY   |
                  ISC_RET_EXTENDED_ERROR    |
                  ISC_REQ_ALLOCATE_MEMORY   |
                  ISC_REQ_STREAM;

    //
    // Размещение буфера данных.
    //

    IoBuffer = LocalAlloc(LMEM_FIXED, IO_BUFFER_SIZE);
    if(IoBuffer == NULL)
    {
        printf("**** Out of memory (1)\n");
        return SEC_E_INTERNAL_ERROR;
    }
    cbIoBuffer = 0;

    fDoRead = fDoInitialRead;


    // 
    // Цикл до тех пока, пока не закончится обмен сообщениями, 
    // либо не произойдет ошибка.
    //

    scRet = SEC_I_CONTINUE_NEEDED;

    while(scRet == SEC_I_CONTINUE_NEEDED        ||
          scRet == SEC_E_INCOMPLETE_MESSAGE     ||
          scRet == SEC_I_INCOMPLETE_CREDENTIALS) 
   {

        //
        // Чтение данных из сервера.
        //

        if(0 == cbIoBuffer || scRet == SEC_E_INCOMPLETE_MESSAGE)
        {
            if(fDoRead)
            {
                cbData = recv(Socket, 
                              IoBuffer + cbIoBuffer, 
                              IO_BUFFER_SIZE - cbIoBuffer, 
                              0);
                if(IS_SOCKET_ERROR(cbData))
                {
                    printf("**** Error %d reading data from server\n", WSAGetLastError());
                    scRet = SEC_E_INTERNAL_ERROR;
                    break;
                }
                else if(cbData == 0)
                {
                    printf("**** Server unexpectedly disconnected\n");
                    scRet = SEC_E_INTERNAL_ERROR;
                    break;
                }

                printf("%d bytes of handshake data received\n", cbData);

                
                cbIoBuffer += cbData;
            }
            else
            {
                fDoRead = TRUE;
            }
        }


        //
        // Установка входных буферов. Buffer 0 содержит данные, получаемые
        // от сервера. Schannel поглощает некоторые или все из них.
        // Оставшиеся данные (в любом случае) располагаются в  buffer 1 и
        // получают тип буфера SECBUFFER_EXTRA.
        //

        InBuffers[0].pvBuffer   = IoBuffer;
        InBuffers[0].cbBuffer   = cbIoBuffer;
        InBuffers[0].BufferType = SECBUFFER_TOKEN;

        InBuffers[1].pvBuffer   = NULL;
        InBuffers[1].cbBuffer   = 0;
        InBuffers[1].BufferType = SECBUFFER_EMPTY;

        InBuffer.cBuffers       = 2;
        InBuffer.pBuffers       = InBuffers;
        InBuffer.ulVersion      = SECBUFFER_VERSION;

        //
        // Установка выходных буферов. Инициализация производится таким образом, 
	// чтобы pvBuffer содержал NULL. 
	// Это сделано для того, чтобы в случае неудачи не было необходимости выполнять
	// освобождение памяти.
        //

        OutBuffers[0].pvBuffer  = NULL;
        OutBuffers[0].BufferType= SECBUFFER_TOKEN;
        OutBuffers[0].cbBuffer  = 0;

        OutBuffer.cBuffers      = 1;
        OutBuffer.pBuffers      = OutBuffers;
        OutBuffer.ulVersion     = SECBUFFER_VERSION;

        //
        // Вызов InitializeSecurityContext.
        //

        scRet = hUTLS->pSSPI->InitializeSecurityContextA(phCreds,
                                          phContext,
                                          NULL,
                                          dwSSPIFlags,
                                          0,
                                          SECURITY_NATIVE_DREP,
                                          &InBuffer,
                                          0,
                                          NULL,
                                          &OutBuffer,
                                          &dwSSPIOutFlags,
                                          &tsExpiry);

        //
        // Если InitializeSecurityContext успешно выполнена (или если произошла 
        // одна из распространенных ошибок), то выполняется посылка выходного
        // буфера серверу.
        //

        if(scRet == SEC_E_OK                ||
           scRet == SEC_I_CONTINUE_NEEDED   ||
           FAILED(scRet) && (dwSSPIOutFlags & ISC_RET_EXTENDED_ERROR))
        {
            if(OutBuffers[0].cbBuffer != 0 && OutBuffers[0].pvBuffer != NULL)
            {
                cbData = send(Socket,
                              OutBuffers[0].pvBuffer,
                              OutBuffers[0].cbBuffer,
                              0);
                if(IS_SOCKET_ERROR(cbData) || cbData == 0)
                {
                    printf("**** Error %d sending data to server (2)\n", 
                        WSAGetLastError());
                    hUTLS->pSSPI->FreeContextBuffer(OutBuffers[0].pvBuffer);
                    hUTLS->pSSPI->DeleteSecurityContext(phContext);
                    return SEC_E_INTERNAL_ERROR;
                }

                printf("%d bytes of handshake data sent\n", cbData);

               
                // Освобождение выходного буфера.
                hUTLS->pSSPI->FreeContextBuffer(OutBuffers[0].pvBuffer);
                OutBuffers[0].pvBuffer = NULL;
            }
        }


        //
        // Если InitializeSecurityContext вернула ошибку SEC_E_INCOMPLETE_MESSAGE,
        // тогда необходимо прочитать большее количество данных от сервера и повторить
	// попытку снова.
        //

        if(scRet == SEC_E_INCOMPLETE_MESSAGE)
        {
            continue;
        }


        //
        // Если InitializeSecurityContext возвратила SEC_E_OK, тогда 
        // обмен данными завершился успешно.
        //

        if(scRet == SEC_E_OK)
        {
            //
            // Если "extra" буфер содержит данные, то они являются данными
            // протокола зашифрования. Их необходимо сохранить. 
	    // Приложение позже расшифрует их при помощи DecryptMessage.
            //

            printf("Handshake was successful\n");

            if(InBuffers[1].BufferType == SECBUFFER_EXTRA)
            {
                pExtraData->pvBuffer = LocalAlloc(LMEM_FIXED, 
                                                  InBuffers[1].cbBuffer);
                if(pExtraData->pvBuffer == NULL)
                {
                    printf("**** Out of memory (2)\n");
                    return SEC_E_INTERNAL_ERROR;
                }

                MoveMemory(pExtraData->pvBuffer,
                           IoBuffer + (cbIoBuffer - InBuffers[1].cbBuffer),
                           InBuffers[1].cbBuffer);

                pExtraData->cbBuffer   = InBuffers[1].cbBuffer;
                pExtraData->BufferType = SECBUFFER_TOKEN;

                printf("%d bytes of app data was bundled with handshake data\n",
                    pExtraData->cbBuffer);
            }
            else
            {
                pExtraData->pvBuffer   = NULL;
                pExtraData->cbBuffer   = 0;
                pExtraData->BufferType = SECBUFFER_EMPTY;
            }

            //
            // Выход
            //
	    {
		// Sample for usage QueryContextAttributes() & native TimeStamp
		SecPkgContext_Lifespan ls;
		double hi, lo;
		time_t clock_1970;

		scRet = hUTLS->pSSPI->QueryContextAttributes(phContext,
					SECPKG_ATTR_LIFESPAN, &ls);
		hi = ls.tsExpiry.HighPart;
		lo = ls.tsExpiry.LowPart;
		    // Convert 100-ns interval since January 1, 1601 (UTC) 
		    // to 1-sec interval science January 1, 1970, UTC
		clock_1970 = (time_t)(
			((ldexp(hi, 32) + lo)*100.e-9)
			- 11644473600. //SystemTimeToFileTime({.wYear = 1970, .wMonth = 1, .wDay = 1}... 
		    );
		    // Convert UTC time_t to local time string
		printf("Security Context of this session valid till %s (local time)\n", ctime(&clock_1970));
	    }
	    {
		// Sample for usage QueryContextAttributes() & FileTimeToSystemTime()
		FILETIME   ft;
		SYSTEMTIME st;
		unsigned hi;
		unsigned lo;
		SecPkgContext_Lifespan ls;

		scRet = hUTLS->pSSPI->QueryContextAttributes(phContext,
					SECPKG_ATTR_LIFESPAN, &ls);
		hi = ls.tsStart.HighPart;
		lo = ls.tsStart.LowPart;
		memcpy(&ft, &ls.tsStart, sizeof(ft));
		FileTimeToSystemTime(&ft,&st);
		printf("Connection start {%x, %x}: %d/%d/%d %d:%d:%d.%03d UTC\n", 
			    hi, lo,
			    st.wYear, st.wMonth, st.wDay,
			    st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);

		hi = ls.tsExpiry.HighPart;
		lo = ls.tsExpiry.LowPart;
		memcpy(&ft, &ls.tsExpiry, sizeof(ft));
		FileTimeToSystemTime(&ft,&st);
		printf("Connection expiry {%x, %x}: %d/%d/%d %d:%d:%d.%03d UTC\n", 
			    hi, lo,
			    st.wYear, st.wMonth, st.wDay,
			    st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);
	    }
            break;
        }


        //
        // Проверка на ошибки.
        //

        if(FAILED(scRet))
        {
            printf("**** Error 0x%x returned by InitializeSecurityContext (2)\n", scRet);
            break;
        }


        //
        // Если InitializeSecurityContext возвратила SEC_I_INCOMPLETE_CREDENTIALS,
        // то сервер только что запросил аутентификацию клиента. 
        //

        if(scRet == SEC_I_INCOMPLETE_CREDENTIALS)
        {
            //
            // Ошибка. Сервер запросил аутентификацию клиента, но переданный
            // мандат не содержит сертификата клиента.
            //

            // 
            // Эта функция читает список доверенных сертификатов авторов
            // ("issuers"), полученный от сервера и пытается найти
            // подходящий сертификат клиента. Если эта функция выполнена успешно,
            // то происходит соединение при помощи нового сертификата.
            // В противном случае осуществляется попытка произвести
	    // соединение анонимно (используя текущий мандат).
            //
            
	    if (!fUserCertProvided)
		return scRet;

	    phCreds = &hCertClientCreds;

            // Повторная попытка.
            fDoRead = FALSE;
            scRet = SEC_I_CONTINUE_NEEDED;

	    // Исправляем ошибку Platform SDK!
	    // Считаем, что за этим сообщением не может следовать другое
	    cbIoBuffer = 0;

            continue;
        }


        //
        // Копирование всех данных из "extra" буфера и повторная попытка.
        //

        if ( InBuffers[1].BufferType == SECBUFFER_EXTRA )
        {
            MoveMemory(IoBuffer,
                       IoBuffer + (cbIoBuffer - InBuffers[1].cbBuffer),
                       InBuffers[1].cbBuffer);

            cbIoBuffer = InBuffers[1].cbBuffer;
        }
        else
        {
            cbIoBuffer = 0;
        }
    }

    // Уничтожение закрытого контекста в случае непоправимой ошибки.
    if(FAILED(scRet))
    {
        hUTLS->pSSPI->DeleteSecurityContext(phContext);
    }

    LocalFree(IoBuffer);

    return scRet;
} // ClientHandshakeLoop


static BOOL InitializeDriver(PDRTKTLS_DRV *ppKDrv,HCRYPTPROV hProv, BOOL fCreateSA)
{
    PDRTKTLS_DRV pKDrv=NULL;
    BYTE * pbRandomBlob = NULL;
    DWORD dwLen=0;
    unsigned kdrv_len=0;
    BOOL ret=FALSE;

    if (kinit_gost(NULL,&kdrv_len)!=STATUS_SUCCESS)
	return FALSE;
    pKDrv=(PDRTKTLS_DRV)malloc(kdrv_len);
    if (!pKDrv)
	return FALSE;
    if (kinit_gost(pKDrv,&kdrv_len)!=STATUS_SUCCESS)
    {
	free(pKDrv);
	return FALSE;
    }

    if (fCreateSA)
    {
	if (!CryptGetProvParam(hProv,PP_RANDOM, NULL,&dwLen,0))
	    goto done;
	pbRandomBlob=(BYTE *)malloc(dwLen);	
	if (!pbRandomBlob)
	    goto done;
	if (!CryptGetProvParam(hProv,PP_RANDOM, pbRandomBlob,&dwLen,0))
	    goto done;
	if (kset_rng(pKDrv,pbRandomBlob,dwLen))
	    goto done;
	if (kcreate_SA(pKDrv))
	    goto done;
    }
    ret=TRUE;	
done:    
    free(pbRandomBlob);
    if (!ret)	
    {
	kdone_gost(pKDrv);	
	free(pKDrv);
    }
    else
	*ppKDrv=pKDrv;
	
    return ret;
}

SECURITY_STATUS CreateDriverContext(PDRTKTLS_DRV pDrv,PDRTKTLS_CTX *ppKCTX)
{
    unsigned kctx_len=0;
    SECURITY_STATUS scRet=STATUS_SUCCESS;
    PDRTKTLS_CTX pKCtx = NULL;
    if ((scRet = kinit_ctx(pDrv,NULL,&kctx_len)) != STATUS_SUCCESS)
	return scRet;
    if ((pKCtx=(PDRTKTLS_CTX)malloc(kctx_len)) == NULL)
	return STATUS_NO_MEMORY;
    if ((scRet = kinit_ctx(pDrv,pKCtx,&kctx_len)) != STATUS_SUCCESS)
    {
	free(pKCtx);
	return scRet;
    }
    *ppKCTX=pKCtx;
    return STATUS_SUCCESS;
}

static void * CPCAlloc(LPCPC_CONFIG CSPConfig, size_t dwSize, DWORD dwMemPoolId)
{
    unsigned long dwThreadId;
    void * res=NULL;
    if (CSPConfig == NULL)
	return NULL;
    if (CSPConfig->pArena == NULL)
	return NULL;
    if (CSPConfig->logConfig.get_thread_id == NULL)
	dwThreadId=0;
    else
	dwThreadId=CSPConfig->logConfig.get_thread_id();
    if (CSPConfig->pArena->pAllocMemory(
			CSPConfig->pArena,
			dwSize,
			dwMemPoolId,
			dwThreadId,
			&res)!=S_OK)
	return NULL;
    memset(res,0,dwSize);
    return res;
}

/* Освободить память с помощью менеджера памяти, заданного пользователем */
static void CPCFree(LPCPC_CONFIG CSPConfig, void * ptr, DWORD dwMemPoolId)
{
    if(ptr)
    {
	CSPConfig->pArena->pFreeMemory(CSPConfig->pArena,ptr,dwMemPoolId);
    }
}

#define PRINTF_SIZE 200	      
static void CPC_printf(LPCPC_CONFIG pConfig, const char * fmt, ...)
{
    char * buffer=NULL;
    va_list ap;
    va_start(ap,fmt);
    buffer=(char *)CPCAlloc(pConfig,PRINTF_SIZE,MP_WORK);
    vsnprintf(buffer,PRINTF_SIZE-1,fmt,ap);
    va_end(ap);
    pConfig->logConfig.cprint_str(pConfig->logConfig.lpArg,buffer);
    CPCFree(pConfig, buffer, MP_WORK);
}

static char hexchar(int x)
{
    if (x<0 || x>15)
	return '*';
    if (x<10)
	return '0'+x;
    return x-10+'A';	
}

static void CPC_hexdump(LPCPC_CONFIG pConfig, const char * msg, void * src, size_t size)
{
    char * buffer=NULL,*ptr=NULL;
    size_t len=strlen(msg);
    size_t sz=len+3*size+size%26+2;
    size_t i;
    unsigned char * s=(unsigned char *)src;
    buffer=(char *)CPCAlloc(pConfig,sz,MP_WORK);
    memcpy(buffer,msg,len);
    ptr=buffer+len;
    for (i=0;i<size;i++)
    {
	*ptr++=hexchar(s[i]>>4);
	*ptr++=hexchar(s[i]&0xF);
	*ptr++=(i%26==25)?'\n':' ';
    }
    *ptr=0;
    pConfig->logConfig.cprint_str(pConfig->logConfig.lpArg,buffer);
    CPCFree(pConfig, buffer, MP_WORK);
}

SECURITY_STATUS CreateAndExchangeSA(PDRTKTLS_DRV pKDrv,PDRTKTLS_CTX pKCtx,HCRYPTPROV hProv,utlsdb_token * pTok)
{
    unsigned tlsdb_len=0;
    unsigned char * our_tlsdb=NULL;
    unsigned char * their_tlsdb=NULL;
    unsigned their_tlsdb_len=0;
    SECURITY_STATUS scRet = STATUS_INTERNAL_ERROR;
    if (hUTLS->tlsdb.CreateEphemFn(hCPC2Crypt,hProv,0,&pTok->Priv,0,&tlsdb_len)!= CAPI_NOERROR)
	goto ret;
    if ((our_tlsdb=(unsigned char *)malloc(tlsdb_len)) == NULL)
    {
	scRet = STATUS_NO_MEMORY;
	goto ret;
    }
    if (hUTLS->tlsdb.CreateEphemFn(hCPC2Crypt,hProv,0,&pTok->Priv,our_tlsdb,&tlsdb_len)!= CAPI_NOERROR)
	goto ret;
    if ((scRet = kexport_SA(pKDrv,NULL,&their_tlsdb_len))!= STATUS_SUCCESS)
	goto ret;
    
    if ((their_tlsdb=(unsigned char *)malloc(their_tlsdb_len)) == NULL )
    {
	scRet = STATUS_NO_MEMORY;
	goto ret;
    }
    if ((scRet = kexport_SA(pKDrv,their_tlsdb,&their_tlsdb_len)) != STATUS_SUCCESS)
	goto ret;
    if (hUTLS->tlsdb.deSerializePubKeyFn(hCPC2Crypt,hProv,their_tlsdb,their_tlsdb_len,0,&pTok->Pub,&CPCConfig) != CAPI_NOERROR) {
        scRet = STATUS_INTERNAL_ERROR;
	goto ret;
    }
//    CPC_hexdump(&CPCConfig,"TheirPub:",pTok->Pub.sa,sizeof(pTok->Pub.sa));
//    CPC_hexdump(&CPCConfig,"our_tlsdb:",our_tlsdb,tlsdb_len);
    scRet = kimport_SA(pKCtx,our_tlsdb,tlsdb_len);
ret:    
    free(their_tlsdb);
    free(our_tlsdb);
    return scRet;	
}
//-------------------------------------------------------------
// Функция получения файла при помощи Https.
static
SECURITY_STATUS
HttpsGetFile(
    SOCKET          Socket,         // in
    PCredHandle     phCreds,        // in
    CtxtHandle *    phContext,      // in
    LPSTR           pszFileName)    // in
{
    SecPkgContext_StreamSizes Sizes;
    SECURITY_STATUS scRet = STATUS_INTERNAL_ERROR;
    SecBufferDesc   Message;
    SecBuffer       Buffers[4];
    SecBuffer *     pDataBuffer = NULL;
    SecBuffer *     pExtraBuffer = NULL;
    SecBuffer       ExtraBuffer;

    char* pbIoBuffer = NULL;
    DWORD cbIoBuffer = 0;
    DWORD cbIoBufferLength = 0;
    char* pbMessage = NULL;
    DWORD cbMessage = 0;
#ifndef KRN_EMUL    
    HCRYPTPROV hProvExch=0;
    PDRTKTLS_DRV pKDrv=NULL;
#endif    

    DWORD cbData = 0;
    INT   i;
    unsigned char * pbKernelKeys = NULL;
    unsigned cbKernelKeys = 0;
    utlsdb_token tok;
    BOOL tokInited = FALSE;
    PDRTKTLS_CTX pKCTX=NULL;
#ifndef KRN_EMUL    
    if (!CryptAcquireContext(&hProvExch,NULL,NULL,PROV_GOST_2012_256,CRYPT_VERIFYCONTEXT))
	return STATUS_INTERNAL_ERROR;
    if (!InitializeDriver(&pKDrv,hProvExch,FALSE))
    {
	CryptReleaseContext(hProvExch,0);
	return STATUS_INTERNAL_ERROR;
    }
#endif    
    if ((scRet = CreateDriverContext(pKDrv,&pKCTX)) != STATUS_SUCCESS)
	goto ret;
    if ((scRet = CreateAndExchangeSA(pKDrv,pKCTX,hProvExch,&tok)) !=STATUS_SUCCESS)
	goto ret;
    tokInited = TRUE;
    //
    // Чтение свойств поточного зашифрования.
    //

    scRet = hUTLS->pSSPI->QueryContextAttributes(phContext,
                                   SECPKG_ATTR_STREAM_SIZES,
                                   &Sizes);
    if(scRet != SEC_E_OK)
    {
        printf("**** Error 0x%x reading SECPKG_ATTR_STREAM_SIZES\n", scRet);
        return scRet;
    }

    printf("\nHeader: %lu, Trailer: %lu, MaxMessage: %lu\n",
        Sizes.cbHeader,
        Sizes.cbTrailer,
        Sizes.cbMaximumMessage);

    //
    // Выделение рабочего буфера. Открытый текст, передаваемый EncryptMessage,
    // должен ен провосходить по размерам 'Sizes.cbMaximumMessage', поэтому 
    // размер буфера равен этой величене, сложеной с размерами заголовка и 
    // заключительной части.
    // 

    cbIoBufferLength = Sizes.cbHeader + 
                       Sizes.cbMaximumMessage +
                       Sizes.cbTrailer;// Могут быть такие реализации 
    //сервера и клиента что длина буфера будет cbIoBufferLength
    // вычисленная таким образом + 2048 байт см. RFC 2246

    pbIoBuffer = LocalAlloc(LMEM_FIXED, cbIoBufferLength);
    if(pbIoBuffer == NULL)
    {
        printf("**** Out of memory (2)\n");
        scRet = STATUS_NO_MEMORY;
	goto ret;
    }


    //
    // Построение HTTP запроса серверу.
    //

    // Удаление завершающих '/' из имени файла, должен быть только один.
    if(pszFileName && 
       strlen(pszFileName) > 1 && 
       pszFileName[strlen(pszFileName) - 1] == '/')
    {
        pszFileName[strlen(pszFileName)-1] = 0;
    }

    // Построение HTTP запроса, сдвинутого на "header size" байт в буфере.
    // Это позволяет Schannel выполнять операцию зашифрования.
    pbMessage = pbIoBuffer + Sizes.cbHeader;

    // Построение HTTP запроса. Он меньше, чем максимальный размер сообщения.
    // Если это не так, что происходит break.
    sprintf(pbMessage, 
            "GET /%s HTTP/1.0\r\nUser-Agent: Webclient\r\nAccept:*/*\r\n\r\n", 
            pszFileName);
    printf("\nHTTP request: %s\n", pbMessage);

    cbMessage = (DWORD)strlen(pbMessage);

    printf("Sending plaintext: %d bytes\n", cbMessage);

  
    //
    // Зашифрование HTTP запроса.
    //

    Buffers[0].pvBuffer     = pbIoBuffer;
    Buffers[0].cbBuffer     = Sizes.cbHeader;
    Buffers[0].BufferType   = SECBUFFER_STREAM_HEADER;

    Buffers[1].pvBuffer     = pbMessage;
    Buffers[1].cbBuffer     = cbMessage;
    Buffers[1].BufferType   = SECBUFFER_DATA;

    Buffers[2].pvBuffer     = pbMessage + cbMessage;
    Buffers[2].cbBuffer     = Sizes.cbTrailer;
    Buffers[2].BufferType   = SECBUFFER_STREAM_TRAILER;

    Buffers[3].BufferType   = SECBUFFER_EMPTY;

    Message.ulVersion       = SECBUFFER_VERSION;
    Message.cBuffers        = 4;
    Message.pBuffers        = Buffers;

    scRet = hUTLS->pSSPI->EncryptMessage(phContext, 0, &Message, 0);

    if(FAILED(scRet))
    {
        printf("**** Error 0x%x returned by EncryptMessage\n", scRet);
	goto ret;
    }


    // 
    // Посылка зашифрованных данных серверу.
    //
    {
	DWORD dwTotal=Buffers[0].cbBuffer + Buffers[1].cbBuffer + Buffers[2].cbBuffer;

	while (dwTotal)
	{
	    cbData = send(Socket,
		      pbIoBuffer,
		      Buffers[0].cbBuffer + Buffers[1].cbBuffer + Buffers[2].cbBuffer,
		      0);
	    printf("%d bytes of application data sent\n", cbData);
	    if(IS_SOCKET_ERROR(cbData) || cbData == 0)
	    {
		printf("**** Error %d sending data to server (3)\n", 
		    WSAGetLastError());
		hUTLS->pSSPI->DeleteSecurityContext(phContext);
		scRet =  STATUS_INTERNAL_ERROR;
		goto ret;
	    }
	    dwTotal-=cbData;
        }

    }

    do 
    {
	SecBuffer SB;
	SECURITY_STATUS scRet=0;
	unsigned cbBuffer=cbIoBufferLength;
	SB.pvBuffer=NULL;
	scRet=hUTLS->pSSPI->ExportSecurityContext(phContext,
				CPUTLS_CONTEXT_EXPORT_TO_KERNEL,
				&SB,(void **)&tok);
	if (scRet)
	    goto ret;
	if ((SB.pvBuffer=malloc(SB.cbBuffer)) == NULL)
	{
	    scRet = STATUS_NO_MEMORY;
	    goto ret;
	}
	scRet=hUTLS->pSSPI->ExportSecurityContext(phContext,
				CPUTLS_CONTEXT_EXPORT_TO_KERNEL,
				&SB,(void **)&tok);
	if (scRet)			
	    goto ret;

	if ((scRet = kimport_keys(pKCTX,SB.pvBuffer,SB.cbBuffer)) != STATUS_SUCCESS)
	{
	    free(SB.pvBuffer);
	    goto ret;
	}

	free(SB.pvBuffer);

	scRet = kreadall(pKCTX,Socket,pbIoBuffer,&cbBuffer);
	{
	    SecBuffer SB2;
	    SECURITY_STATUS scRet2=0;
	    scRet2=kexport_keys(pKCTX,NULL,&cbKernelKeys);
	    if (scRet2)
	    {
		scRet = scRet2;
		goto ret;
	    }
	    pbKernelKeys=(char *)malloc(cbKernelKeys);
	    if (!pbKernelKeys)
	    {
		scRet = STATUS_NO_MEMORY;
		goto ret;
	    }
	    scRet2=kexport_keys(pKCTX,pbKernelKeys,&cbKernelKeys);
	    if (scRet2)
	    {
		scRet = scRet2;
		goto ret;
	    }
	    SB2.pvBuffer=pbKernelKeys;
	    SB2.cbBuffer=cbKernelKeys;
	    SB2.BufferType=SECBUFFER_DATA;
//	    CPC_hexdump(&CPCConfig,"TheirPub2:",&tok.Pub.sa,sizeof(tok.Pub.sa));
	    scRet2 = hUTLS->pSSPI->ImportSecurityContext(NULL,&SB2,&tok,phContext);
	    free(pbKernelKeys);
	    if (scRet2)
	    {
		scRet = scRet2;
		goto ret;
	    }
	}
        if(scRet == SEC_I_RENEGOTIATE)
        {
            // Сервер потребовал другой обмен сообщениями с клиентом.
	    ExtraBuffer.pvBuffer=pbIoBuffer;
	    ExtraBuffer.cbBuffer=cbBuffer;
	    ExtraBuffer.BufferType=SECBUFFER_EXTRA;

            printf("Server requested renegotiate!\n");


            scRet = ClientHandshakeLoop(Socket, 
                                        phCreds, 
                                        phContext, 
                                        FALSE, 
                                        &ExtraBuffer);
            if(scRet != SEC_E_OK)
            {
		goto ret;
            }

            // Перенос всех "extra" данных во входной буфер.
            if(ExtraBuffer.pvBuffer)
            {
                MoveMemory(pbIoBuffer, ExtraBuffer.pvBuffer, ExtraBuffer.cbBuffer);
                cbIoBuffer = ExtraBuffer.cbBuffer;
            }
        }    
	else
	    break;
   } while (1);	
ret:
    if (tokInited)
	DestroyExportToken(&tok);
    free(pbIoBuffer);
    if (pKCTX)	
    {
	kdone_ctx(pKCTX);
	free(pKCTX);
    }
    return scRet;
} // HttpsGetFile
    
//-------------------------------------------------------------
// Функция вывода цепочки сертификатов.
static 
void
DisplayCertChain(
    PCCERT_CONTEXT  pServerCert,
    BOOL            fLocal)
{
    CHAR szName[1000];
    PCCERT_CONTEXT pCurrentCert;
    PCCERT_CONTEXT pIssuerCert;
    DWORD dwVerificationFlags;

    printf("\n");

    // Вывод листа цеточки
    CertNameToStr(pServerCert->dwCertEncodingType,
                  &pServerCert->pCertInfo->Subject,
                  CERT_X500_NAME_STR | CERT_NAME_STR_NO_PLUS_FLAG,
                  szName, sizeof(szName));
    if(fLocal)
    {
        printf("Client subject: %s\n", szName);
    }
    else
    {
        printf("Server subject: %s\n", szName);
    }
    CertNameToStr(pServerCert->dwCertEncodingType,
                  &pServerCert->pCertInfo->Issuer,
                  CERT_X500_NAME_STR | CERT_NAME_STR_NO_PLUS_FLAG,
                  szName, sizeof(szName));
    if(fLocal)
    {
        printf("Client issuer: %s\n", szName);
    }
    else
    {
        printf("Server issuer: %s\n\n", szName);
    }


    // Вывод цепочки сертификатов
    pCurrentCert = pServerCert;
    while(pCurrentCert != NULL)
    {
        dwVerificationFlags = 0;
        pIssuerCert = CertGetIssuerCertificateFromStore(pServerCert->hCertStore,
                                                        pCurrentCert,
                                                        NULL,
                                                        &dwVerificationFlags);
        if(pIssuerCert == NULL)
        {
            if(pCurrentCert != pServerCert)
            {
                CertFreeCertificateContext(pCurrentCert);
            }
            break;
        }

        CertNameToStr(pIssuerCert->dwCertEncodingType,
                      &pIssuerCert->pCertInfo->Subject,
                      CERT_X500_NAME_STR | CERT_NAME_STR_NO_PLUS_FLAG,
                      szName, sizeof(szName));
        printf("CA subject: %s\n", szName);
        CertNameToStr(pIssuerCert->dwCertEncodingType,
                      &pIssuerCert->pCertInfo->Issuer,
                      CERT_X500_NAME_STR | CERT_NAME_STR_NO_PLUS_FLAG,
                      szName, sizeof(szName));
        printf("CA issuer: %s\n\n", szName);

        if(pCurrentCert != pServerCert)
        {
            CertFreeCertificateContext(pCurrentCert);
        }
        pCurrentCert = pIssuerCert;
        pIssuerCert = NULL;
    }
} // DisplayCertChain


//-------------------------------------------------------------
// Функция вывода ошибки полученной при проверке цепочки сертификатов.

static
void
DisplayWinVerifyTrustError(DWORD Status)
{
    LPSTR pszName = NULL;

    switch(Status)
    {
    case CERT_E_EXPIRED:                pszName = "CERT_E_EXPIRED";                 break;
    case CERT_E_VALIDITYPERIODNESTING:  pszName = "CERT_E_VALIDITYPERIODNESTING";   break;
    case CERT_E_ROLE:                   pszName = "CERT_E_ROLE";                    break;
    case CERT_E_PATHLENCONST:           pszName = "CERT_E_PATHLENCONST";            break;
    case CERT_E_CRITICAL:               pszName = "CERT_E_CRITICAL";                break;
    case CERT_E_PURPOSE:                pszName = "CERT_E_PURPOSE";                 break;
    case CERT_E_ISSUERCHAINING:         pszName = "CERT_E_ISSUERCHAINING";          break;
    case CERT_E_MALFORMED:              pszName = "CERT_E_MALFORMED";               break;
    case CERT_E_UNTRUSTEDROOT:          pszName = "CERT_E_UNTRUSTEDROOT";           break;
    case CERT_E_CHAINING:               pszName = "CERT_E_CHAINING";                break;
    case TRUST_E_FAIL:                  pszName = "TRUST_E_FAIL";                   break;
    case CERT_E_REVOKED:                pszName = "CERT_E_REVOKED";                 break;
    case CERT_E_UNTRUSTEDTESTROOT:      pszName = "CERT_E_UNTRUSTEDTESTROOT";       break;
    case CERT_E_REVOCATION_FAILURE:     pszName = "CERT_E_REVOCATION_FAILURE";      break;
    case CERT_E_CN_NO_MATCH:            pszName = "CERT_E_CN_NO_MATCH";             break;
    case CERT_E_WRONG_USAGE:            pszName = "CERT_E_WRONG_USAGE";             break;
    default:                            pszName = "(unknown)";                      break;
    }

    printf("Error 0x%x (%s) returned by CertVerifyCertificateChainPolicy!\n", 
        Status, pszName);
} //DisplayWinVerifyTrustError

//-------------------------------------------------------------
// Функция проверки сертификата сервера.
static 
DWORD
VerifyServerCertificate(
    PCCERT_CONTEXT  pServerCert,
    PSTR            pszServerName,
    DWORD           dwCertFlags)
{
    HTTPSPolicyCallbackData  polHttps;
    CERT_CHAIN_POLICY_PARA   PolicyPara;
    CERT_CHAIN_POLICY_STATUS PolicyStatus;
    CERT_CHAIN_PARA          ChainPara;
    PCCERT_CHAIN_CONTEXT     pChainContext = NULL;

    LPSTR rgszUsages[] = {  szOID_PKIX_KP_SERVER_AUTH,
                            szOID_SERVER_GATED_CRYPTO,
                            szOID_SGC_NETSCAPE };
    DWORD cUsages = sizeof(rgszUsages) / sizeof(LPSTR);

    PWSTR   pwszServerName = NULL;
    DWORD   cchServerName;
    HRESULT   Status;

    if(pServerCert == NULL)
    {
        Status = SEC_E_WRONG_PRINCIPAL;
        goto cleanup;
    }


    //
    // Преобразование имени сервера в unicode.
    //

    if(pszServerName == NULL || strlen(pszServerName) == 0)
    {
        Status = SEC_E_WRONG_PRINCIPAL;
        goto cleanup;
    }

    cchServerName = MultiByteToWideChar(CP_ACP, 0, pszServerName, -1, NULL, 0);
    pwszServerName = LocalAlloc(LMEM_FIXED, cchServerName * sizeof(WCHAR));
    if(pwszServerName == NULL)
    {
        Status = SEC_E_INSUFFICIENT_MEMORY;
        goto cleanup;
    }
    cchServerName = MultiByteToWideChar(CP_ACP, 0, pszServerName, -1, pwszServerName, cchServerName);
    if(cchServerName == 0)
    {
        Status = SEC_E_WRONG_PRINCIPAL;
        goto cleanup;
    }


    //
    // Построение цепочки сертификатов.
    //

    ZeroMemory(&ChainPara, sizeof(ChainPara));
    ChainPara.cbSize = sizeof(ChainPara);
    ChainPara.RequestedUsage.dwType = USAGE_MATCH_TYPE_OR;
    ChainPara.RequestedUsage.Usage.cUsageIdentifier     = cUsages;
    ChainPara.RequestedUsage.Usage.rgpszUsageIdentifier = rgszUsages;

    if(!CertGetCertificateChain(
                            NULL,
                            pServerCert,
                            NULL,
                            pServerCert->hCertStore,
                            &ChainPara,
                            0,
                            NULL,
                            &pChainContext))
    {
        Status = GetLastError();
        printf("Error 0x%x returned by CertGetCertificateChain!\n", Status);
        goto cleanup;
    }


    //
    // Проверка цепочки сертификатов.
    // 

    ZeroMemory(&polHttps, sizeof(HTTPSPolicyCallbackData));
    polHttps.cbStruct           = sizeof(HTTPSPolicyCallbackData);
    polHttps.dwAuthType         = AUTHTYPE_SERVER;
    polHttps.fdwChecks          = dwCertFlags;
    polHttps.pwszServerName     = pwszServerName;

    memset(&PolicyPara, 0, sizeof(PolicyPara));
    PolicyPara.cbSize            = sizeof(PolicyPara);
    PolicyPara.pvExtraPolicyPara = &polHttps;

    memset(&PolicyStatus, 0, sizeof(PolicyStatus));
    PolicyStatus.cbSize = sizeof(PolicyStatus);

    if(!CertVerifyCertificateChainPolicy(
                            CERT_CHAIN_POLICY_SSL,
                            pChainContext,
                            &PolicyPara,
                            &PolicyStatus))
    {
        Status = GetLastError();
        printf("Error 0x%x returned by CertVerifyCertificateChainPolicy!\n", Status);
        goto cleanup;
    }

    if(PolicyStatus.dwError)
    {
        Status = PolicyStatus.dwError;
        DisplayWinVerifyTrustError(Status); 
        goto cleanup;
    }


    Status = SEC_E_OK;

cleanup:

    if(pChainContext)
    {
        CertFreeCertificateChain(pChainContext);
    }

    if(pwszServerName)
    {
        LocalFree(pwszServerName);
    }

    return Status;
} // VerifyServerCertificate


//-------------------------------------------------------------
// Функция вывода информации о соединении.

static
void
DisplayConnectionInfo(
    CtxtHandle *phContext)
{
    SECURITY_STATUS Status;
    SecPkgContext_ConnectionInfo ConnectionInfo;

    Status = hUTLS->pSSPI->QueryContextAttributes(phContext,
                                    SECPKG_ATTR_CONNECTION_INFO,
                                    (PVOID)&ConnectionInfo);
    if(Status != SEC_E_OK)
    {
        printf("Error 0x%x querying connection info\n", Status);
        return;
    }

    printf("\n");

    switch(ConnectionInfo.dwProtocol)
    {
        case SP_PROT_TLS1_CLIENT:
            printf("Protocol: TLS 1.0\n");
            break;
        case SP_PROT_TLS1_1_CLIENT:
            printf("Protocol: TLS 1.1\n");
            break;
        case SP_PROT_TLS1_2_CLIENT:
            printf("Protocol: TLS 1.2\n");
            break;
        default:
            printf("Protocol: 0x%x\n", ConnectionInfo.dwProtocol);
    }

    switch(ConnectionInfo.aiCipher)
    {
        case CALG_G28147: 
            printf("Cipher: Gost 28147-89\n");
            break;
        case CALG_GR3412_2015_K: 
            printf("Cipher: Gost R 34.12-2015 K\n");
            break;
        case CALG_GR3412_2015_M: 
            printf("Cipher: Gost R 34.12-2015 M\n");
            break;
        case CALG_AES_256:
            printf("Cipher: AES 256\n");
            break;
        case CALG_AES_192:
            printf("Cipher: AES 192\n");
            break;
        case CALG_AES_128:
            printf("Cipher: AES 128\n");
            break;
        default: 
            printf("Cipher: 0x%x\n", ConnectionInfo.aiCipher);
    }


    printf("Cipher strength: %d\n", ConnectionInfo.dwCipherStrength);

    switch(ConnectionInfo.aiHash)
    {
        case CALG_GR3411: 
            printf("Hash: Gost R 34.11-94\n");
            break;

        default: 
            printf("Hash: 0x%x\n", ConnectionInfo.aiHash);
    }

    printf("Hash strength: %d\n", ConnectionInfo.dwHashStrength);

    switch(ConnectionInfo.aiExch)
    {
        case CALG_DH_EPHEM:
            printf("Key exchange: DH Ephemeral\n");
            break;

        default: 
            printf("Key exchange: 0x%x\n", ConnectionInfo.aiExch);
    }

    printf("Key exchange strength: %d\n", ConnectionInfo.dwExchStrength);
} //DisplayConnectionInfo

//-------------------------------------------------------------
// Функция создания мандатов.
static
HRESULT
GetUserCreds(
    LPSTR pszUserName,              // in
    PCredHandle phCreds)            // out
{
    SCHANNEL_CRED   SchannelCred;
    TimeStamp       tsExpiry;
    SECURITY_STATUS Status;
    PCCERT_CONTEXT  pCertContext = NULL;

    if(pszUserName == NULL || strlen(pszUserName) == 0)
    {
        printf("**** No user name specified!\n");
        return SEC_E_NO_CREDENTIALS;
    }

    // Открытие хранилища сертификатов "MY".
        hMyCertStore = CertOpenSystemStore(0, "MY");
        
        if(!hMyCertStore)
        {
            printf("**** Error 0x%x returned by CertOpenSystemStore\n", 
                GetLastError());
            return SEC_E_NO_CREDENTIALS;
        }
    
    // Поиск сертификата. В данном примере осуществляется поиск сертификата 
    // по subject name, содержащем заданное в командной строке имя пользователя.
    pCertContext = CertFindCertificateInStore(hMyCertStore, 
                                              X509_ASN_ENCODING, 
                                              0,
                                              CERT_FIND_SUBJECT_STR_A,
                                              pszUserName,
                                              NULL);
    if(pCertContext == NULL)
    {
        printf("**** Error 0x%x returned by CertFindCertificateInStore\n",
            GetLastError());
        return SEC_E_NO_CREDENTIALS;
    }


    // Построение структуры Schannel мандатов. 
    // В данном примере определяются используемые протокол и сертификат.
    
    ZeroMemory(&SchannelCred, sizeof(SchannelCred));

    SchannelCred.dwVersion = SCHANNEL_CRED_VERSION;

    SchannelCred.cCreds = 1;
    SchannelCred.paCred = &pCertContext;

    SchannelCred.grbitEnabledProtocols = dwProtocol;


    // Создание SSPI мандата.
    Status = hUTLS->pSSPI->AcquireCredentialsHandle(
                        NULL,                   // Имя администратора
                        UNISP_NAME_A,           // Имя пакета
                        SECPKG_CRED_INBOUND,    // Флаг, определяющий использование
                        NULL,                   // Указатель на идентификатор пароля
                        &SchannelCred,          // Данные пакета
                        NULL,                   // Указатель на функицю GetKey()
                        NULL,                   // Значения, передаваемые функции GetKey()
                        phCreds,                // (out) Даскриптор мандата
                        &tsExpiry);             // (out) Период актуальности (необязательно)
    if(Status != SEC_E_OK)
    {
        printf("**** Error 0x%x returned by AcquireCredentialsHandle\n", Status);
        return Status;
    }

    // Освобождение контекста сертификата. В Schannel уже создана его копия.
    if(pCertContext)
    {
        CertFreeCertificateContext(pCertContext);
    }


    return SEC_E_OK;
}


