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

#include <Common.h>

/* Формат сообщения, распознаваемого PINPad:
   <!PINPADFILE RU>              // обязательный заголовок строки в кодировке CP-1251, которая будет отображаться на экране устройства
   <!PINPADFILE UTF8>            // обязательный заголовок строки в кодировке UTF-8, которая будет отображаться на экране устройства
   <!PINPADFILE INVISIBLE RU>    // обязательный заголовок строки в кодировке CP-1251, которая будет подписана PINPad без отображения на экране устройства
   <!PINPADFILE INVISIBLE UTF8>  // обязательный заголовок строки в кодировке UTF-8, которая будет подписана PINPad без отображения на экране устройства
   <N>some text                  // наименование поля - текст будет отображен в левой части строки на экране PINPad
   <V>some text                  // значение поля - текст будет отображен в правой части строки на экране PINPad
   <T>some text                  // информационное поле - текст будет отображен на всей строке на экране PINPad
 */

/*************************************************************************
* Данные в формате PINPADFILE для подписи c отображением в кодировке     *
* UTF-8                                                                  *
*************************************************************************/
/*
   char data[] = "<!PINPADFILE UTF8><N>ФИО:<V>Петров Петр Петрович Москва, Пионерская ул, д. 3, кв. 72"
                 "<N>Перевод со счета:<V>42301810001000075212<N>Сумма:<V>150000<N>Валюта:<V>RUR"
                 "<N>Наименование получателя:<V>Иванова Елена Ивановна<N>Номер счета получателя:<V>40817810338295201618"
                 "<N>БИК банка получателя:<V>044525225<N>Наименование банка получателя:<V>ОАО 'СБЕРБАНК РОССИИ' Г. МОСКВА"
                 "<N>Номер счета банка получателя:<V>30101810400000000225<N>Назначение платежа:<V>перевод личных средств";
 */

/*************************************************************************
* Данные в формате PINPADFILE для подписи с отображением в кодировке     *
* Windows-1251                                                           *
*************************************************************************/
/*
   char data[] = "<!PINPADFILE RU><N>ФИО:<V>Петров Петр Петрович Москва, Пионерская ул, д. 3, кв. 72"
                 "<N>Перевод со счета:<V>42301810001000075212<N>Сумма:<V>150000<N>Валюта:<V>RUR"
                 "<N>Наименование получателя:<V>Иванова Елена Ивановна<N>Номер счета получателя:<V>40817810338295201618"
                 "<N>БИК банка получателя:<V>044525225<N>Наименование банка получателя:<V>ОАО 'СБЕРБАНК РОССИИ' Г. МОСКВА"
                 "<N>Номер счета банка получателя:<V>30101810400000000225<N>Назначение платежа:<V>перевод личных средств";
 */

/*************************************************************************
* Данные в формате PINPADFILE для подписи без отображения в кодировке    *
* UTF-8                                                                  *
*************************************************************************/
char data[] = "<!PINPADFILE INVISIBLE UTF8><N>ФИО:<V>Петров Петр Петрович Москва, Пионерская ул, д. 3, кв. 72"
              "<N>Перевод со счета:<V>42301810001000075212<N>Сумма:<V>150000<N>Валюта:<V>RUR"
              "<N>Наименование получателя:<V>Иванова Елена Ивановна<N>Номер счета получателя:<V>40817810338295201618"
              "<N>БИК банка получателя:<V>044525225<N>Наименование банка получателя:<V>ОАО 'СБЕРБАНК РОССИИ' Г. МОСКВА"
              "<N>Номер счета банка получателя:<V>30101810400000000225<N>Назначение платежа:<V>перевод личных средств";

/*************************************************************************
* Данные в формате PINPADFILE для подписи без отображения в кодировке    *
* Windows-1251                                                           *
*************************************************************************/
/*
   char data[] = "<!PINPADFILE INVISIBLE RU><N>ФИО:<V>Петров Петр Петрович Москва, Пионерская ул, д. 3, кв. 72"
                 "<N>Перевод со счета:<V>42301810001000075212<N>Сумма:<V>150000<N>Валюта:<V>RUR"
                 "<N>Наименование получателя:<V>Иванова Елена Ивановна<N>Номер счета получателя:<V>40817810338295201618"
                 "<N>БИК банка получателя:<V>044525225<N>Наименование банка получателя:<V>ОАО 'СБЕРБАНК РОССИИ' Г. МОСКВА"
                 "<N>Номер счета банка получателя:<V>30101810400000000225<N>Назначение платежа:<V>перевод личных средств";
 */

/*************************************************************************
* Данные в формате XML для подписи c отображением в кодировке UTF-8      *
*************************************************************************/
/*
   char data[] = "<?xml version=\"1.0\" encoding=\"utf-8\"?><Файл><ИмяФайла>Name-00001.XML</ИмяФайла><ЗаголовокФайла>"
                 "<ВерсияФормата>01.00</ВерсияФормата><ТипФайла>ВНЕШНИЙ</ТипФайла></ЗаголовокФайла><ФИО><Фамилия>Петров</Фамилия>"
                 "<Имя>Сергей</Имя><Отчество>Александрович</Отчество></ФИО><Пол>М</Пол><ДатаРождения>01.02.1990</ДатаРождения>"
                 "<ДатаЗаполнения>01.02.2015</ДатаЗаполнения><ext><МестоРожд>г. Москва</МестоРожд><Адрес><Индекс>123456</Индекс>"
                 "<Область>Московская обл.</Область><Район>Первомайский р-н</Район>Город>г. Москва</Город><Улица>ул. Первомайская </Улица>"
                 "<Дом>3</Дом><Корпус>1</Корпус><Квартира>123</Квартира></Адрес></ext></Файл>";
 */

/*************************************************************************
* Данные в формате XML для подписи c отображением в кодировке            *
* Windows-1251                                                           *
*************************************************************************/
/*
   char data[] = "<?xml version=\"1.0\" encoding=\"windows-1251\"?><Файл><ИмяФайла>Name-00001.XML</ИмяФайла><ЗаголовокФайла>"
                 "<ВерсияФормата>01.00</ВерсияФормата><ТипФайла>ВНЕШНИЙ</ТипФайла></ЗаголовокФайла><ФИО><Фамилия>Петров</Фамилия>"
                 "<Имя>Сергей</Имя><Отчество>Александрович</Отчество></ФИО><Пол>М</Пол><ДатаРождения>01.02.1990</ДатаРождения>"
                 "<ДатаЗаполнения>01.02.2015</ДатаЗаполнения><ext><МестоРожд>г. Москва</МестоРожд><Адрес><Индекс>123456</Индекс>"
                 "<Область>Московская обл.</Область><Район>Первомайский р-н</Район>Город>г. Москва</Город><Улица>ул. Первомайская </Улица>"
                 "<Дом>3</Дом><Корпус>1</Корпус><Квартира>123</Квартира></Адрес></ext></Файл>";
 */

/*************************************************************************
* Данные для подписи с отображением в виде двоичной строки               *
* (PINPADFILE, Windows-1251)                                             *
*************************************************************************/
/*
   CK_BYTE binData[] = {  0x3C, 0x21, 0x50, 0x49, 0x4E, 0x50, 0x41, 0x44, 0x46, 0x49, 0x4C, 0x45, 0x20, 0x52, 0x55, 0x3E,
                          0x3C, 0x4E, 0x3E, 0x0A, 0xD4, 0xC8, 0xCE, 0x3A, 0x3C, 0x56, 0x3E, 0xCF, 0xE5, 0xF2, 0xF0, 0xEE,
                          0xE2, 0x20, 0xCF, 0xE5, 0xF2, 0xF0, 0x20, 0xCF, 0xE5, 0xF2, 0xF0, 0xEE, 0xE2, 0xE8, 0xF7, 0x20,
                          0xCC, 0xEE, 0xF1, 0xEA, 0xE2, 0xE0, 0x2C, 0x20, 0xCF, 0xE8, 0xEE, 0xED, 0xE5, 0xF0, 0xF1, 0xEA,
                          0xE0, 0xFF, 0x20, 0xF3, 0xEB, 0x2C, 0x20, 0xE4, 0x2E, 0x20, 0x33, 0x2C, 0x20, 0xEA, 0xE2, 0x2E,
                          0x20, 0x37, 0x32, 0x2C, 0x20, 0xE2, 0xF2, 0xEE, 0xF0, 0xEE, 0xE9, 0x20, 0xF7, 0xE5, 0xF0, 0xE4,
                          0xE0, 0xEA, 0x0A, 0x3C, 0x4E, 0x3E, 0xCF, 0xE5, 0xF0, 0xE5, 0xE2, 0xEE, 0xE4, 0x20, 0xF1, 0xEE,
                          0x20, 0xF1, 0xF7, 0xE5, 0xF2, 0xE0, 0x3A, 0x3C, 0x56, 0x3E, 0x34, 0x32, 0x33, 0x30, 0x31, 0x38,
                          0x31, 0x30, 0x30, 0x30, 0x31, 0x30, 0x30, 0x30, 0x30, 0x37, 0x35, 0x32, 0x31, 0x32, 0x0A, 0x3C,
                          0x4E, 0x3E, 0xD1, 0xF3, 0xEC, 0xEC, 0xE0, 0x3A, 0x3C, 0x56, 0x3E, 0x31, 0x35, 0x30, 0x30, 0x30,
                          0x30, 0x0A, 0x3C, 0x4E, 0x3E, 0xC2, 0xE0, 0xEB, 0xFE, 0xF2, 0xE0, 0x3A, 0x3C, 0x56, 0x3E, 0x52,
                          0x55, 0x52, 0x0A, 0x3C, 0x4E, 0x3E, 0xCD, 0xE0, 0xE8, 0xEC, 0xE5, 0xED, 0xEE, 0xE2, 0xE0, 0xED,
                          0xE8, 0xE5, 0x20, 0xEF, 0xEE, 0xEB, 0xF3, 0xF7, 0xE0, 0xF2, 0xE5, 0xEB, 0xFF, 0x3A, 0x3C, 0x56,
                          0x3E, 0xC8, 0xE2, 0xE0, 0xED, 0xEE, 0xE2, 0xE0, 0x20, 0xC5, 0xEB, 0xE5, 0xED, 0xE0, 0x20, 0xC8,
                          0xE2, 0xE0, 0xED, 0xEE, 0xE2, 0xED, 0xE0, 0x0A, 0x3C, 0x4E, 0x3E, 0xCD, 0xEE, 0xEC, 0xE5, 0xF0,
                          0x20, 0xF1, 0xF7, 0xE5, 0xF2, 0xE0, 0x20, 0xEF, 0xEE, 0xEB, 0xF3, 0xF7, 0xE0, 0xF2, 0xE5, 0xEB,
                          0xFF, 0x3A, 0x3C, 0x56, 0x3E, 0x34, 0x30, 0x38, 0x31, 0x37, 0x38, 0x31, 0x30, 0x33, 0x33, 0x38,
                          0x32, 0x39, 0x35, 0x32, 0x30, 0x31, 0x36, 0x31, 0x38, 0x0A, 0x3C, 0x4E, 0x3E, 0xC1, 0xC8, 0xCA,
                          0x20, 0xE1, 0xE0, 0xED, 0xEA, 0xE0, 0x20, 0xEF, 0xEE, 0xEB, 0xF3, 0xF7, 0xE0, 0xF2, 0xE5, 0xEB,
                          0xFF, 0x3A, 0x3C, 0x56, 0x3E, 0x30, 0x34, 0x34, 0x35, 0x32, 0x35, 0x32, 0x32, 0x35, 0x0A, 0x3C,
                          0x4E, 0x3E, 0xCD, 0xE0, 0xE8, 0xEC, 0xE5, 0xED, 0xEE, 0xE2, 0xE0, 0xED, 0xE8, 0xE5, 0x20, 0xE1,
                          0xE0, 0xED, 0xEA, 0xE0, 0x20, 0xEF, 0xEE, 0xEB, 0xF3, 0xF7, 0xE0, 0xF2, 0xE5, 0xEB, 0xFF, 0x3A,
                          0x3C, 0x56, 0x3E, 0xCE, 0xC0, 0xCE, 0x20, 0x27, 0xD1, 0xC1, 0xC5, 0xD0, 0xC1, 0xC0, 0xCD, 0xCA,
                          0x20, 0xD0, 0xCE, 0xD1, 0xD1, 0xC8, 0xC8, 0x27, 0x20, 0xC3, 0x2E, 0x20, 0xCC, 0xCE, 0xD1, 0xCA,
                          0xC2, 0xC0, 0x0A, 0x3C, 0x4E, 0x3E, 0xCD, 0xEE, 0xEC, 0xE5, 0xF0, 0x20, 0xF1, 0xF7, 0xE5, 0xF2,
                          0xE0, 0x20, 0xE1, 0xE0, 0xED, 0xEA, 0xE0, 0x20, 0xEF, 0xEE, 0xEB, 0xF3, 0xF7, 0xE0, 0xF2, 0xE5,
                          0xEB, 0xFF, 0x3A, 0x3C, 0x56, 0x3E, 0x33, 0x30, 0x31, 0x30, 0x31, 0x38, 0x31, 0x30, 0x34, 0x30,
                          0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x32, 0x32, 0x35, 0x0A, 0x3C, 0x4E, 0x3E, 0xCD, 0xE0,
                          0xE7, 0xED, 0xE0, 0xF7, 0xE5, 0xED, 0xE8, 0xE5, 0x20, 0xEF, 0xEB, 0xE0, 0xF2, 0xE5, 0xE6, 0xE0,
                          0x3A, 0x3C, 0x56, 0x3E, 0xEF, 0xE5, 0xF0, 0xE5, 0xE2, 0xEE, 0xE4, 0x20, 0xEB, 0xE8, 0xF7, 0xED,
                          0xFB, 0xF5, 0x20, 0xF1, 0xF0, 0xE5, 0xE4, 0xF1, 0xF2, 0xE2 };
 */

/*************************************************************************
* Запрос на сертификат для подписи в виде двоичной строки                *
*************************************************************************/
CK_BYTE certReq[] = {0x30, 0x82, 0x02, 0x69, 0x02, 0x01, 0x00, 0x30, 0x82, 0x01, 0x83, 0x31, 0x0b, 0x30, 0x09, 0x06,
	                 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x52, 0x55, 0x31, 0x0f, 0x30, 0x0d, 0x06, 0x03, 0x55, 0x04,
	                 0x08, 0x13, 0x06, 0x4d, 0x6f, 0x73, 0x63, 0x6f, 0x77, 0x31, 0x0c, 0x30, 0x0a, 0x06, 0x03, 0x55,
	                 0x04, 0x07, 0x13, 0x03, 0x6d, 0x73, 0x6b, 0x31, 0x0f, 0x30, 0x0d, 0x06, 0x03, 0x55, 0x04, 0x09,
	                 0x13, 0x06, 0x73, 0x74, 0x72, 0x65, 0x65, 0x74, 0x31, 0x0e, 0x30, 0x0c, 0x06, 0x03, 0x55, 0x04,
	                 0x0a, 0x13, 0x05, 0x41, 0x6b, 0x74, 0x69, 0x76, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04,
	                 0x0b, 0x13, 0x02, 0x49, 0x54, 0x31, 0x17, 0x30, 0x15, 0x06, 0x03, 0x55, 0x04, 0x10, 0x13, 0x0e,
	                 0x70, 0x6f, 0x73, 0x74, 0x61, 0x6c, 0x20, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x31, 0x1b,
	                 0x30, 0x19, 0x06, 0x03, 0x55, 0x04, 0x0c, 0x1e, 0x12, 0x04, 0x34, 0x04, 0x3e, 0x04, 0x3b, 0x04,
	                 0x36, 0x04, 0x3d, 0x04, 0x3e, 0x04, 0x41, 0x04, 0x42, 0x04, 0x4c, 0x31, 0x19, 0x30, 0x17, 0x06,
	                 0x08, 0x2a, 0x85, 0x03, 0x03, 0x81, 0x03, 0x01, 0x01, 0x12, 0x0b, 0x31, 0x32, 0x33, 0x34, 0x35,
	                 0x36, 0x37, 0x38, 0x39, 0x38, 0x37, 0x31, 0x16, 0x30, 0x14, 0x06, 0x05, 0x2a, 0x85, 0x03, 0x64,
	                 0x03, 0x12, 0x0b, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x38, 0x37, 0x31, 0x16,
	                 0x30, 0x14, 0x06, 0x05, 0x2a, 0x85, 0x03, 0x64, 0x01, 0x12, 0x0b, 0x31, 0x32, 0x33, 0x34, 0x35,
	                 0x36, 0x37, 0x38, 0x39, 0x38, 0x37, 0x31, 0x16, 0x30, 0x14, 0x06, 0x05, 0x2a, 0x85, 0x03, 0x64,
	                 0x05, 0x12, 0x0b, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x38, 0x37, 0x31, 0x2f,
	                 0x30, 0x2d, 0x06, 0x03, 0x55, 0x04, 0x03, 0x1e, 0x26, 0x04, 0x24, 0x04, 0x30, 0x04, 0x3c, 0x04,
	                 0x38, 0x04, 0x3b, 0x04, 0x38, 0x04, 0x4f, 0x00, 0x20, 0x04, 0x18, 0x04, 0x3c, 0x04, 0x4f, 0x00,
	                 0x20, 0x04, 0x1e, 0x04, 0x47, 0x04, 0x35, 0x04, 0x41, 0x04, 0x42, 0x04, 0x32, 0x04, 0x3e, 0x31,
	                 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x41, 0x13, 0x09, 0x70, 0x73, 0x65, 0x75, 0x64, 0x6f,
	                 0x6e, 0x79, 0x6d, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x04, 0x13, 0x07, 0x73, 0x75,
	                 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x2a, 0x13, 0x0a,
	                 0x67, 0x69, 0x76, 0x65, 0x6e, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x31, 0x22, 0x30, 0x20, 0x06, 0x09,
	                 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01, 0x16, 0x13, 0x65, 0x78, 0x61, 0x6d, 0x70,
	                 0x6c, 0x65, 0x40, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x30, 0x63,
	                 0x30, 0x1c, 0x06, 0x06, 0x2a, 0x85, 0x03, 0x02, 0x02, 0x13, 0x30, 0x12, 0x06, 0x07, 0x2a, 0x85,
	                 0x03, 0x02, 0x02, 0x23, 0x01, 0x06, 0x07, 0x2a, 0x85, 0x03, 0x02, 0x02, 0x1e, 0x01, 0x03, 0x43,
	                 0x00, 0x04, 0x40, 0x26, 0x68, 0x22, 0x87, 0x6b, 0x3e, 0x60, 0xde, 0x6e, 0xcf, 0x7d, 0x9b, 0xc5,
	                 0x99, 0x49, 0x88, 0xe3, 0xce, 0x8d, 0x05, 0xb2, 0x0a, 0x3c, 0x3d, 0x2c, 0xb3, 0x7c, 0xc6, 0x9e,
	                 0x7e, 0x5a, 0xc6, 0x95, 0xde, 0x97, 0x86, 0x9a, 0x56, 0xe3, 0xc5, 0xf5, 0xc5, 0xca, 0x9a, 0x4a,
	                 0xd9, 0x11, 0xa0, 0x40, 0x08, 0xca, 0x70, 0x29, 0x13, 0x64, 0x7f, 0xa1, 0x6c, 0x5b, 0x5b, 0x25,
	                 0xc9, 0xa6, 0x0c, 0xa0, 0x78, 0x30, 0x76, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01,
	                 0x09, 0x0e, 0x31, 0x69, 0x30, 0x67, 0x30, 0x0b, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x04, 0x04, 0x03,
	                 0x02, 0x06, 0xc0, 0x30, 0x16, 0x06, 0x03, 0x55, 0x1d, 0x25, 0x01, 0x01, 0xff, 0x04, 0x0c, 0x30,
	                 0x0a, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x04, 0x30, 0x13, 0x06, 0x03, 0x55,
	                 0x1d, 0x20, 0x04, 0x0c, 0x30, 0x0a, 0x30, 0x08, 0x06, 0x06, 0x2a, 0x85, 0x03, 0x64, 0x71, 0x01,
	                 0x30, 0x2b, 0x06, 0x05, 0x2a, 0x85, 0x03, 0x64, 0x6f, 0x04, 0x22, 0x0c, 0x20, 0xd0, 0xa1, 0xd0,
	                 0x9a, 0xd0, 0x97, 0xd0, 0x98, 0x20, 0x22, 0xd0, 0xa0, 0xd0, 0xa3, 0xd0, 0xa2, 0xd0, 0x9e, 0xd0,
	                 0x9a, 0xd0, 0x95, 0xd0, 0x9d, 0x20, 0xd0, 0xad, 0xd0, 0xa6, 0xd0, 0x9f, 0x22};

const int publicKeyValueOffset = 435; //Смещение значения открытого ключа в запросе

/*************************************************************************
* Шаблон для поиска открытого ключа ГОСТ Р 34.10-2001                    *
*************************************************************************/
CK_ATTRIBUTE publicKeyTemplate[] =
{
	{ CKA_ID, &keyPairIdGost1, sizeof(keyPairIdGost1) - 1},         // ID пары
	{ CKA_KEY_TYPE, &keyTypeGostR3410, sizeof(keyTypeGostR3410) },  // Ключ ГОСТ Р 34.10-2001
	{ CKA_CLASS, &publicKeyObject, sizeof(publicKeyObject)}         // Класс - открытый ключ
};

/*************************************************************************
* Шаблон для поиска закрытого ключа ГОСТ Р 34.10-2001                    *
*************************************************************************/
CK_ATTRIBUTE privateKeyTemplate[] =
{
	{ CKA_ID, &keyPairIdGost1, sizeof(keyPairIdGost1) - 1},         // ID пары
	{ CKA_KEY_TYPE, &keyTypeGostR3410, sizeof(keyTypeGostR3410) },  // Ключ ГОСТ Р 34.10-2001
	{ CKA_CLASS, &privateKeyObject, sizeof(privateKeyObject)}       // Класс - закрытый ключ
};

int main(void)
{
	HMODULE module;                                                     // Хэндл загруженной библиотеки PKCS#11
	CK_SESSION_HANDLE session;                                          // Хэндл открытой сессии

	CK_FUNCTION_LIST_PTR functionList;                                  // Указатель на список функций PKCS#11, хранящийся в структуре CK_FUNCTION_LIST
	CK_C_GetFunctionList getFunctionList;                               // Указатель на функцию C_GetFunctionList

	CK_C_EX_GetFunctionListExtended getFunctionListEx;                  // Указатель на функцию C_EX_GetFunctionListExtended
	CK_FUNCTION_LIST_EXTENDED_PTR functionListEx;                       // Указатель на список функций расширения PKCS#11, хранящийся в структуре CK_FUNCTION_LIST_EXTENDED

	CK_TOKEN_INFO_EXTENDED tokenInfoEx;                                 // Структура данных типа CK_TOKEN_INFO_EXTENDED с информацией о токене

	CK_SLOT_ID_PTR slots;                                               // Указатель на массив идентификаторов слотов
	CK_ULONG slotCount;                                                 // Количество идентификаторов слотов в массиве

	CK_MECHANISM_TYPE_PTR mechanisms;                                   // Указатель на массив поддерживаемых механизмов
	CK_ULONG mechanismCount;                                            // Количество поддерживаемых механизмов

	CK_OBJECT_HANDLE_PTR privateKeys;                                   // Массив хэндлов объектов, соответствующих критериям поиска
	CK_ULONG privateKeysCount;                                          // Количество хэндлов объектов в массиве

	CK_OBJECT_HANDLE_PTR publicKeys;                                    // Массив хэндлов объектов, соответствующих критериям поиска
	CK_ULONG publicKeysCount;                                           // Количество хэндлов объектов в массиве

	CK_BYTE_PTR signature1;                                             // Указатель на буфер, содержащий подпись для исходных данных
	CK_ULONG signature1Size;                                            // Размер буфера, содержащего подпись для исходных данных, в байтах

	CK_BYTE_PTR signature2;                                             // Указатель на буфер, содержащий подпись для исходных данных
	CK_ULONG signature2Size;                                            // Размер буфера, содержащего подпись для исходных данных, в байтах

	CK_BYTE_PTR hash1;                                                  // Указатель на временный буфер для хэш-кода от данных
	CK_ULONG hash1Size;                                                 // Размер временного буфера в байтах

	CK_BYTE_PTR hash2;                                                  // Указатель на временный буфер для хэш-кода от данных
	CK_ULONG hash2Size;                                                 // Размер временного буфера в байтах

	CK_BYTE_PTR hash3;                                                  // Указатель на временный буфер для хэш-кода от данных
	CK_ULONG hash3Size;                                                 // Размер временного буфера в байтах

	CK_BYTE_PTR hash4;                                                  // Указатель на временный буфер для хэш-кода от данных
	CK_ULONG hash4Size;                                                 // Размер временного буфера в байтах

	CK_BBOOL confirmOp = CK_FALSE;                                      // Вспомогательная переменная для хранения значения атрибута CKA_VENDOR_KEY_CONFIRM_OP
	CK_ATTRIBUTE confirmOpAttr;                                         // Шаблон для получения значения атрибута CKA_VENDOR_KEY_CONFIRM_OP
	int isOperationInvisible;                                           // Вспомогательная переменная для хранения признака необходимости использовать функции расширения для подписи

	CK_BYTE publicKeyValue[GOST_3410_KEY_SIZE];                         // Буфер для хранения открытого ключа

	CK_ATTRIBUTE valueAttr;                                             // Шаблон для получения значения открытого ключа

	CK_RV rv;                                                           // Код возврата. Могут быть возвращены только ошибки, определенные в PKCS#11
	int r;                                                              // Код возврата для функций, возвращающих int

	CK_ULONG i;                                                         // Вспомогательная переменная-счетчик в циклах

	int isGostR3410Supported = 0;                                       // Флаги для проверки поддержки токеном CKM_GOSTR3410
	int isGostR3411Supported = 0;                                       // Флаги для проверки поддержки токеном CKM_GOSTR3411

	int errorCode = 1;                                                  // Флаг ошибки

	/*************************************************************************
	* Выполнить действия для начала работы с библиотекой PKCS#11             *
	*************************************************************************/
	printf("Initialization...\n");

	/*************************************************************************
	* Загрузить библиотеку                                                   *
	*************************************************************************/
	module = LoadLibrary(PKCS11ECP_LIBRARY_NAME);
	CHECK(" LoadLibrary", module != NULL, exit);

	/*************************************************************************
	* Получить адрес функции запроса структуры с указателями на функции      *
	*************************************************************************/
	getFunctionList = (CK_C_GetFunctionList)GetProcAddress(module, "C_GetFunctionList");
	CHECK(" GetProcAddress (C_GetFunctionList)", getFunctionList != NULL, unload_pkcs11);

	/*************************************************************************
	* Получить структуру с указателями на функции                            *
	*************************************************************************/
	rv = getFunctionList(&functionList);
	CHECK_AND_LOG(" Get function list", rv == CKR_OK, rvToStr(rv), unload_pkcs11);

	/*************************************************************************
	* Получить адрес функции запроса структуры с указателями на функции      *
	* расширения                                                             *
	*************************************************************************/
	getFunctionListEx = (CK_C_EX_GetFunctionListExtended)GetProcAddress(module, "C_EX_GetFunctionListExtended");
	CHECK(" GetProcAddress (C_EX_GetFunctionListExtended)", getFunctionListEx != NULL, unload_pkcs11);

	/*************************************************************************
	* Получить структуру с указателями на функции расширения                 *
	*************************************************************************/
	rv = getFunctionListEx(&functionListEx);
	CHECK_AND_LOG(" Get extended function list", rv == CKR_OK, rvToStr(rv), unload_pkcs11);

	/*************************************************************************
	* Инициализировать библиотеку                                            *
	*************************************************************************/
	rv = functionList->C_Initialize(NULL_PTR);
	CHECK_AND_LOG(" C_Initialize", rv == CKR_OK, rvToStr(rv), unload_pkcs11);

	/*************************************************************************
	* Получить количество слотов c подключенными токенами                    *
	*************************************************************************/
	rv = functionList->C_GetSlotList(CK_TRUE, NULL_PTR, &slotCount);
	CHECK_AND_LOG(" C_GetSlotList (number of slots)", rv == CKR_OK, rvToStr(rv), finalize_pkcs11);

	CHECK_AND_LOG(" Checking available tokens", slotCount > 0, " No tokens available", finalize_pkcs11);

	/*************************************************************************
	* Получить список слотов c подключенными токенами                        *
	*************************************************************************/
	slots = (CK_SLOT_ID_PTR)malloc(slotCount * sizeof(CK_SLOT_ID));
	CHECK(" Memory allocation for slots", slots != NULL_PTR, finalize_pkcs11);

	rv = functionList->C_GetSlotList(CK_TRUE, slots, &slotCount);
	CHECK_AND_LOG(" C_GetSlotList", rv == CKR_OK, rvToStr(rv), free_slots);
	printf(" Slots available: %d\n", (int)slotCount);

	/*************************************************************************
	* Определить класс токена                                                *
	*************************************************************************/
	printf(" Determining token type\n");

	/*************************************************************************
	* Получить расширенную информацию о подключенном токене                  *
	*************************************************************************/
	tokenInfoEx.ulSizeofThisStructure = sizeof(tokenInfoEx);
	rv = functionListEx->C_EX_GetTokenInfoExtended(slots[0], &tokenInfoEx);
	CHECK_AND_LOG(" C_EX_GetTokenInfoExtended", rv == CKR_OK, rvToStr(rv), free_slots);

	/*************************************************************************
	* Проверить наличие PINPad в нулевом слоте                               *
	*************************************************************************/
	CHECK_AND_LOG(" Checking token type", tokenInfoEx.ulTokenType == TOKEN_TYPE_RUTOKEN_PINPAD_FAMILY,
	              " Device in slot 0 is not Rutoken PINPad", free_slots);

	/*************************************************************************
	* Получить список поддерживаемых токеном механизмов                      *
	*************************************************************************/
	rv = functionList->C_GetMechanismList(slots[0], NULL_PTR, &mechanismCount);
	CHECK_AND_LOG(" C_GetMechanismList (number of mechanisms)", rv == CKR_OK, rvToStr(rv), free_slots);

	CHECK_AND_LOG(" Checking mechanisms available", mechanismCount > 0, " No mechanisms available", free_slots);

	mechanisms = (CK_MECHANISM_TYPE_PTR)malloc(mechanismCount * sizeof(CK_MECHANISM_TYPE));
	CHECK(" Memory allocation for mechanisms", mechanisms != NULL_PTR, free_slots);

	rv = functionList->C_GetMechanismList(slots[0], mechanisms, &mechanismCount);
	CHECK_AND_LOG(" C_GetMechanismList", rv == CKR_OK, rvToStr(rv), free_mechanisms);

	/*************************************************************************
	* Определение поддерживаемых токеном механизмов                          *
	*************************************************************************/
	for (i = 0; i < mechanismCount; ++i) {
		if (mechanisms[i] == CKM_GOSTR3410) {
			isGostR3410Supported = 1;
			break;
		}
	}

	for (i = 0; i < mechanismCount; ++i) {
		if (mechanisms[i] == CKM_GOSTR3411) {
			isGostR3411Supported = 1;
			break;
		}
	}

	CHECK_AND_LOG(" Checking CKM_GOSTR3410 support", isGostR3410Supported, "CKM_GOSTR3410 isn`t supported!\n", free_mechanisms);
	CHECK_AND_LOG(" Checking CKM_GOSTR3411 support", isGostR3411Supported, "CKM_GOSTR3411 isn`t supported!\n", free_mechanisms);

	/*************************************************************************
	* Открыть RW сессию в первом доступном слоте                             *
	*************************************************************************/
	rv = functionList->C_OpenSession(slots[0], CKF_SERIAL_SESSION | CKF_RW_SESSION, NULL_PTR, NULL_PTR, &session);
	CHECK_AND_LOG(" C_OpenSession", rv == CKR_OK, rvToStr(rv), free_mechanisms);

	/*************************************************************************
	* Выполнить аутентификацию Пользователя                                  *
	*************************************************************************/
	rv = functionList->C_Login(session, CKU_USER, USER_PIN, USER_PIN_LEN);
	CHECK_AND_LOG(" C_Login", rv == CKR_OK, rvToStr(rv), close_session);
	printf("Initialization has been completed successfully.\n");

	/*************************************************************************
	* Выполнить подпись платежной информации по алгоритму ГОСТ Р 34.10-2001  *
	*************************************************************************/
	printf("\nSigning data...\n");

	/*************************************************************************
	* Получить массив хэндлов закрытых ключей                                *
	*************************************************************************/
	printf(" Getting private key...\n");
	r = findObjects(functionList, session, privateKeyTemplate, arraysize(privateKeyTemplate),
	                &privateKeys, &privateKeysCount);
	CHECK(" findObjects", r == 0, logout);

	CHECK_AND_LOG(" Checking number of keys found", privateKeysCount > 0, " No objects found", logout);

	/*************************************************************************
	* Вычислить хэш-код данных                                               *
	*************************************************************************/
	printf(" Hashing data...\n");

	/*************************************************************************
	* Инициализировать хэш-функцию                                           *
	*************************************************************************/
	rv = functionList->C_DigestInit(session, &gostR3411HashMech);
	CHECK_AND_LOG("  C_DigestInit", rv == CKR_OK, rvToStr(rv), free_privateKeys);

	/*************************************************************************
	* Определить размер хэш-кода                                             *
	*************************************************************************/
	rv = functionList->C_Digest(session, (CK_BYTE_PTR)data, arraysize(data), NULL_PTR, &hash1Size);
	CHECK_AND_LOG("  C_Digest(get size)", rv == CKR_OK, rvToStr(rv), free_privateKeys);

	/*************************************************************************
	* Вычислить хэш-код данных                                               *
	*************************************************************************/
	hash1 = (CK_BYTE_PTR)malloc(hash1Size * sizeof(CK_BYTE));
	CHECK("  Memory allocation for hash", hash1 != NULL_PTR, free_privateKeys);

	rv = functionList->C_Digest(session, (CK_BYTE_PTR)data, arraysize(data), hash1, &hash1Size);
	CHECK_AND_LOG("  C_Digest (get hash)", rv == CKR_OK, rvToStr(rv), free_hash1);

	/*************************************************************************
	* Распечатать буфер, содержащий хэш-код                                  *
	*************************************************************************/
	printf("  Hashed buffer is: \n");
	printHex(hash1, hash1Size);
	printf(" Hashing has been completed.\n");

	/*************************************************************************
	* Получить значение флага подтверждения операции подписи                 *
	*************************************************************************/
	printf(" Checking whether signature confirmation is required...\n");

	confirmOpAttr.type = CKA_VENDOR_KEY_CONFIRM_OP;
	confirmOpAttr.pValue = &confirmOp;
	confirmOpAttr.ulValueLen = sizeof(confirmOp);
	rv = functionList->C_GetAttributeValue(session, privateKeys[0], &confirmOpAttr, 1);
	CHECK_AND_LOG("  C_GetAttributeValue", rv == CKR_OK, rvToStr(rv), free_hash1);

	/*************************************************************************
	* Инициализировать операцию подписи данных                               *
	*************************************************************************/
	/*************************************************************************
	* При подписи сообщения с заголовком <!PINPADFILE INVISIBLE RU> или      *
	* <!PINPADFILE INVISIBLE UTF8> на ключе, имеющем атрибут                 *
	* CKA_VENDOR_KEY_CONFIRM_OP равным CK_TRUE, а так же для подписи на      *
	* ключе, имеющем атрибут CKA_VENDOR_KEY_CONFIRM_OP равным CK_FALSE, для  *
	* инициализации подписи должна использоваться функция                    *
	* C_EX_SignInvisibleInit, для подписи - C_EX_SignInvisible.              *
	*                                                                        *
	* При подписи сообщения с заголовком <!PINPADFILE RU> или                *
	* <!PINPADFILE UTF8> на ключе, имеющем атрибут CKA_VENDOR_KEY_CONFIRM_OP *
	* равным CK_TRUE, для инициализации подписи должна использоваться        *
	* функция C_SignInit, для подписи - C_Sign.                              *
	*************************************************************************/

	isOperationInvisible = ((strncmp(data, "<!PINPADFILE INVISIBLE RU>", 26) == 0 || strncmp(data, "<!PINPADFILE INVISIBLE UTF8>", 28) == 0)
	                        && confirmOp == CK_TRUE) || confirmOp == CK_FALSE;

	/*************************************************************************
	* Инициализировать подпись и подписать данные                            *
	*************************************************************************/
	if (isOperationInvisible) {
		rv = functionListEx->C_EX_SignInvisibleInit(session, &gostR3410SigVerMech, privateKeys[0]);
		CHECK_AND_LOG(" C_EX_SignInvisibleInit", rv == CKR_OK, rvToStr(rv), free_hash1);

		rv = functionListEx->C_EX_SignInvisible(session, hash1, hash1Size, NULL_PTR, &signature1Size);
		CHECK_AND_LOG(" C_EX_SignInvisible", rv == CKR_OK, rvToStr(rv), free_hash1);

		signature1 = (CK_BYTE_PTR)malloc(signature1Size * sizeof(CK_BYTE));
		CHECK("  Memory allocation for signature", signature1 != NULL_PTR, free_hash1);

		rv = functionListEx->C_EX_SignInvisible(session, hash1, hash1Size, signature1, &signature1Size);
		CHECK_AND_LOG(" C_EX_SignInvisible", rv == CKR_OK, rvToStr(rv), free_signature1);
	} else {
		rv = functionList->C_SignInit(session, &gostR3410SigVerMech, privateKeys[0]);
		CHECK_AND_LOG(" C_SignInit", rv == CKR_OK, rvToStr(rv), free_hash1);

		rv = functionList->C_Sign(session, hash1, hash1Size, NULL_PTR, &signature1Size);
		CHECK_AND_LOG(" C_Sign", rv == CKR_OK, rvToStr(rv), free_hash1);

		signature1 = (CK_BYTE_PTR)malloc(signature1Size * sizeof(CK_BYTE));
		CHECK("  Memory allocation for signature", signature1 != NULL_PTR, free_hash1);

		rv = functionList->C_Sign(session, hash1, hash1Size, signature1, &signature1Size);
		CHECK_AND_LOG(" C_Sign", rv == CKR_OK, rvToStr(rv), free_signature1);
	}

	/*************************************************************************
	* Распечатать буфер, содержащий подпись                                  *
	*************************************************************************/
	printf("  Signature buffer is: \n");
	printHex(signature1, signature1Size);
	printf("Data has been signed successfully.\n");

	/*************************************************************************
	* Выполнить проверку подписи данных по алгоритму ГОСТ Р 34.10-2001       *
	*************************************************************************/
	printf("\nVerifying signature...\n");

	/*************************************************************************
	* Получить массив хэндлов открытых ключей                                *
	*************************************************************************/
	printf(" Getting public key...\n");
	rv = findObjects(functionList, session, publicKeyTemplate,
	                 arraysize(publicKeyTemplate), &publicKeys, &publicKeysCount);

	CHECK(" findObjects", r == 0, free_signature1);
	CHECK_AND_LOG(" Checking number of keys found", publicKeysCount > 0, " No objects found", free_signature1);

	/*************************************************************************
	* Инициализировать хэш-функцию                                           *
	*************************************************************************/
	rv = functionList->C_DigestInit(session, &gostR3411HashMech);
	CHECK_AND_LOG("  C_DigestInit", rv == CKR_OK, rvToStr(rv), free_publicKeys);

	/*************************************************************************
	* Определить размер хэш-кода                                             *
	*************************************************************************/
	rv = functionList->C_Digest(session, (CK_BYTE_PTR)data, arraysize(data), NULL_PTR, &hash2Size);
	CHECK_AND_LOG("  C_Digest(get size)", rv == CKR_OK, rvToStr(rv), free_publicKeys);

	/*************************************************************************
	* Вычислить хэш-код данных                                               *
	*************************************************************************/
	hash2 = (CK_BYTE_PTR)malloc(hash2Size * sizeof(CK_BYTE));
	CHECK("  Memory allocation for hash", hash2 != NULL_PTR, free_publicKeys);

	rv = functionList->C_Digest(session, (CK_BYTE_PTR)data, arraysize(data), hash2, &hash2Size);
	CHECK_AND_LOG("  C_Digest (get hash)", rv == CKR_OK, rvToStr(rv), free_hash2);

	/*************************************************************************
	* Распечатать буфер, содержащий хэш-код                                  *
	*************************************************************************/
	printf("  Hashed buffer is: \n");
	printHex(hash2, hash2Size);
	printf(" Hashing has been completed.\n");

	/*************************************************************************
	* Проверка подписи                                                       *
	*************************************************************************/
	printf(" Verifying data...\n");
	/*************************************************************************
	* Инициализировать операцию проверки подписи                             *
	*************************************************************************/
	rv = functionList->C_VerifyInit(session, &gostR3410SigVerMech, publicKeys[0]);
	CHECK_AND_LOG("  C_VerifyInit", rv == CKR_OK, rvToStr(rv), free_hash2);

	/*************************************************************************
	* Проверить подпись для данных                                           *
	*************************************************************************/
	rv = functionList->C_Verify(session, hash2, hash2Size, signature1, signature1Size);
	CHECK_AND_LOG("  C_Verify", rv == CKR_OK, rvToStr(rv), free_hash2);

	printf("Verifying has been completed successfully.\n");

	/*************************************************************************
	* Выполнить подпись запроса на сертификат по алгоритму ГОСТ Р 34.10-2001 *
	*************************************************************************/
	printf("\nSigning certificate request...\n");

	/*************************************************************************
	* Получить значение открытого ключа                                      *
	*************************************************************************/
	printf(" Getting public key value");
	valueAttr.type = CKA_VALUE;
	valueAttr.pValue = publicKeyValue;
	valueAttr.ulValueLen = sizeof(publicKeyValue);
	rv = functionList->C_GetAttributeValue(session, publicKeys[0], &valueAttr, 1);
	CHECK_AND_LOG("  C_GetAttributeValue", rv == CKR_OK, rvToStr(rv), free_hash2);

	/*************************************************************************
	* Распечатать буфер, содержащий открытый ключ                            *
	*************************************************************************/
	printf(" Public key:\n");
	printHex(publicKeyValue, sizeof(publicKeyValue));

	/*************************************************************************
	* Внести значение открытого ключа в запрос на сертификат                 *
	*************************************************************************/
	for (i = 0; i < sizeof(publicKeyValue); ++i) {
		certReq[i + publicKeyValueOffset] = publicKeyValue[i];
	}

	/*************************************************************************
	* Сформировать хэш-код от исходных данных                                *
	*************************************************************************/
	printf(" Hashing data...\n");

	/*************************************************************************
	* Инициализировать хэш-функцию                                           *
	*************************************************************************/
	rv = functionList->C_DigestInit(session, &gostR3411HashMech);
	CHECK_AND_LOG("  C_DigestInit", rv == CKR_OK, rvToStr(rv), free_hash2);

	/*************************************************************************
	* Определить размер хэш-кода                                             *
	*************************************************************************/
	rv = functionList->C_Digest(session, certReq, arraysize(certReq), NULL_PTR, &hash3Size);
	CHECK_AND_LOG("  C_Digest(get size)", rv == CKR_OK, rvToStr(rv), free_hash2);

	/*************************************************************************
	* Вычислить хэш-код данных                                               *
	*************************************************************************/
	hash3 = (CK_BYTE_PTR)malloc(hash3Size * sizeof(CK_BYTE));
	CHECK("  Memory allocation for hash", hash3 != NULL_PTR, free_hash2);

	rv = functionList->C_Digest(session, certReq, arraysize(certReq), hash3, &hash3Size);
	CHECK_AND_LOG("  C_Digest (get hash)", rv == CKR_OK, rvToStr(rv), free_hash3);

	/*************************************************************************
	* Распечатать буфер, содержащий хэш-код                                  *
	*************************************************************************/
	printf("  Hashed buffer is: \n");
	printHex(hash3, hash3Size);
	printf(" Hashing has been completed.\n");

	/*************************************************************************
	 * Инициализировать операцию подписи данных                              *
	 ************************************************************************/
	/*************************************************************************
	* Для подписи запроса на сертификат в зависимости от атрибута ключа      *
	* CKA_VENDOR_KEY_CONFIRM_OP используются функции C_EX_SignInvisibleInit  *
	* и C_EX_SignInvisible (для значения CK_FALSE), либо C_SignInit и C_Sign *
	* (для значения CK_TRUE)                                                 *
	*************************************************************************/
	if (confirmOp == CK_TRUE) {
		rv = functionList->C_SignInit(session, &gostR3410SigVerMech, privateKeys[0]);
		CHECK_AND_LOG(" C_SignInit", rv == CKR_OK, rvToStr(rv), free_hash3);

		rv = functionList->C_Sign(session, hash3, hash3Size, NULL_PTR, &signature2Size);
		CHECK_AND_LOG(" C_Sign", rv == CKR_OK, rvToStr(rv), free_hash3);

		signature2 = (CK_BYTE_PTR)malloc(signature2Size * sizeof(CK_BYTE));
		CHECK("  Memory allocation for signature", signature2 != NULL_PTR, free_hash3);

		rv = functionList->C_Sign(session, hash3, hash3Size, signature2, &signature2Size);
		CHECK_AND_LOG(" C_Sign", rv == CKR_OK, rvToStr(rv), free_signature2);

	} else {
		rv = functionListEx->C_EX_SignInvisibleInit(session, &gostR3410SigVerMech, privateKeys[0]);
		CHECK_AND_LOG(" C_EX_SignInvisibleInit", rv == CKR_OK, rvToStr(rv), free_hash3);

		rv = functionListEx->C_EX_SignInvisible(session, hash3, hash3Size, NULL_PTR, &signature2Size);
		CHECK_AND_LOG(" C_EX_SignInvisible", rv == CKR_OK, rvToStr(rv), free_hash3);

		signature2 = (CK_BYTE_PTR)malloc(signature2Size * sizeof(CK_BYTE));
		CHECK("  Memory allocation for signature", signature2 != NULL_PTR, free_hash3);

		rv = functionListEx->C_EX_SignInvisible(session, hash3, hash3Size, signature2, &signature2Size);
		CHECK_AND_LOG(" C_EX_SignInvisible", rv == CKR_OK, rvToStr(rv), free_signature2);
	}

	/*************************************************************************
	* Распечатать буфер, содержащий подпись                                  *
	*************************************************************************/
	printf("  Signature buffer is: \n");
	printHex(signature2, signature2Size);
	printf("Data has been signed successfully.\n");

	/*************************************************************************
	* Выполнить проверку подписи данных по алгоритму ГОСТ Р 34.10-2001       *
	*************************************************************************/
	printf("\nVerifying signature...\n");

	/*************************************************************************
	* Инициализировать хэш-функцию                                           *
	*************************************************************************/
	rv = functionList->C_DigestInit(session, &gostR3411HashMech);
	CHECK_AND_LOG("  C_DigestInit", rv == CKR_OK, rvToStr(rv), free_signature2);

	/*************************************************************************
	* Определить размер хэш-кода                                             *
	*************************************************************************/
	rv = functionList->C_Digest(session, certReq, arraysize(certReq), NULL_PTR, &hash4Size);
	CHECK_AND_LOG("  C_Digest(get size)", rv == CKR_OK, rvToStr(rv), free_signature2);

	/*************************************************************************
	* Вычислить хэш-код данных                                               *
	*************************************************************************/
	hash4 = (CK_BYTE_PTR)malloc(hash4Size * sizeof(CK_BYTE));
	CHECK("  Memory allocation for hash", hash4 != NULL_PTR, free_signature2);

	rv = functionList->C_Digest(session, certReq, arraysize(certReq), hash4, &hash4Size);
	CHECK_AND_LOG("  C_Digest (get hash)", rv == CKR_OK, rvToStr(rv), free_hash4);

	/*************************************************************************
	* Распечатать буфер, содержащий хэш-код                                  *
	*************************************************************************/
	printf("  Hashed buffer is: \n");
	printHex(hash4, hash4Size);
	printf(" Hashing has been completed.\n");

	/*************************************************************************
	* Проверка подписи                                                       *
	*************************************************************************/
	printf(" Verifying data...\n");
	/*************************************************************************
	* Инициализировать операцию проверки подписи                             *
	*************************************************************************/
	rv = functionList->C_VerifyInit(session, &gostR3410SigVerMech, publicKeys[0]);
	CHECK_AND_LOG("  C_VerifyInit", rv == CKR_OK, rvToStr(rv), free_hash4);

	/*************************************************************************
	* Проверить подпись для данных                                           *
	*************************************************************************/
	rv = functionList->C_Verify(session, hash4, hash4Size, signature2, signature2Size);
	CHECK_AND_LOG("  C_Verify", rv == CKR_OK, rvToStr(rv), free_hash4);

	printf("Verifying has been completed successfully.\n");

	/*************************************************************************
	* Выставить признак успешного завершения программы                       *
	*************************************************************************/
	errorCode = 0;

	/*************************************************************************
	* Выполнить действия для завершения работы с библиотекой PKCS#11         *
	*************************************************************************/
	printf("\nFinalizing... \n");

	/*************************************************************************
	* Очистить память, выделенную под объекты и хэш-коды                     *
	*************************************************************************/
free_hash4:
	free(hash4);
free_signature2:
	free(signature2);
free_hash3:
	free(hash3);
free_hash2:
	free(hash2);
free_publicKeys:
	free(publicKeys);
free_signature1:
	free(signature1);
free_hash1:
	free(hash1);
free_privateKeys:
	free(privateKeys);

	/*************************************************************************
	* Сбросить права доступа                                                 *
	*************************************************************************/
logout:
	rv = functionList->C_Logout(session);
	CHECK_RELEASE_AND_LOG(" C_Logout", rv == CKR_OK, rvToStr(rv), errorCode);

	/*************************************************************************
	* Закрыть открытую сессию в слоте                                        *
	*************************************************************************/
close_session:
	rv = functionList->C_CloseSession(session);
	CHECK_RELEASE_AND_LOG(" C_CloseSession", rv == CKR_OK, rvToStr(rv), errorCode);

	/*************************************************************************
	* Очистить память, выделенную  под механизмы и слоты                     *
	*************************************************************************/
free_mechanisms:
	free(mechanisms);

free_slots:
	free(slots);

	/*************************************************************************
	* Деинициализировать библиотеку                                          *
	*************************************************************************/
finalize_pkcs11:
	rv = functionList->C_Finalize(NULL_PTR);
	CHECK_RELEASE_AND_LOG(" C_Finalize", rv == CKR_OK, rvToStr(rv), errorCode);

	/*************************************************************************
	* Выгрузить библиотеку из памяти                                         *
	*************************************************************************/
unload_pkcs11:
	CHECK_RELEASE(" FreeLibrary", FreeLibrary(module), errorCode);

exit:
	if (errorCode) {
		printf("\n\nSome error occurred. Sample failed.\n");
	} else {
		printf("\n\nSample has been completed successfully.\n");
	}

	return errorCode;
}
