Как работать с Rutoken и Rutoken ЭЦП

Как работать с Rutoken и Rutoken ЭЦП Электронная цифровая подпись

Генерация ключевой пары

Для того, чтобы проверить готовность ключевого идентификатора Рутокен к настройке для работы с ЕГАИС, откройте “Панель управления Рутокен” – вкладка “Администрирование” – кнопка “Информация” – и проверьте статус напротив поля “Microsoft Base Smart Card Crypto Provider”:

  • Поддерживается

Данный статус означает, что электронный идентификатор Рутокен уже готов к настройке криптопровайдера по умолчанию. Переходите ко второму пункту данной инструкции – “Смена криптопровайдера по умолчанию”

https://www.youtube.com/watch?v=https:accounts.google.comServiceLogin

Если статус Поддерживается, переходим к пункту 3

Если напротив поля “Microsoft Base Smart Card Crypto Provider” стоит статус  Активировать или Не поддерживается, переходим к пункту 2.

Для проверки готовности ключевого идентификатора Рутокен к настройке для работы с ЕГАИС, откройте “Панель управления Рутокен” – закладка “Администрирование” – кнопка “Информация” – и проверьте статус напротив поля “Microsoft Base Smart Card Crypto Provider”:

  • Активировать

Как работать с Rutoken и Rutoken ЭЦП

В этом случае необходимо нажать на ссылку “Активировать”, для того чтобы включить возможность поддержки криптопровайдера “Microsoft Base Smart Card Crypto Provider”.

Если для Пользователя или Администратора установлен PIN-код не по умолчанию, его необходимо будет ввести во время активации.

Обратите внимание на то, что если оба PIN-кода не соответствуют значениям по умолчанию, для активации необходимо будет ввести последовательно PIN-код Администратора, затем Пользователя.

Если один или оба PIN-кода неизвестны, необходимо обратиться в компанию, предоставившую вам ключевой идентификатор, для получения PIN-кодов.

В случае, если узнать текущие значения PIN-кодов не представляется возможным, остается только вариант форматирования идентификатора Рутокен для установки новых значений PIN-кодов. Обращаем ваше внимание на то, что во время форматирования ключевого идентификатора все содержимое удаляется безвозвратно.

После процедуры активации статус в поле “Microsoft Base Smart Card Crypto Provider” должен измениться на “Поддерживается”

чтобы продолжить настройку ключевого идентификатора Рутокен переходим к Пункту 2.

  • Не поддерживается

Статус “Не поддерживается” отображается если производится попытка настройки модели Рутокена, не предназначенной для работы с ЕГАИС, например Рутокен Lite или Рутокен S. Для работы с ЕГАИС подходит только модель Рутокен ЭЦП 2.0

https://www.youtube.com/watch?v=ytpress

Откройте “Пуск” – (“Настройки”) – “Панель управления” – “Панель управления Рутокен” – закладка “Настройки” – в пункте “Настройки криптопровайдера” нажмите кнопку “Настройка…”

Начало работы

Рутокен PINPad предоставляет возможность подтверждения того, что операция подписи была выполнена конкретным устройством Рутокен PINPad. Для этого в памяти устройства после каждой успешной операции подписи сохраняется подробная информация о выполненной операции (журнал операции), которую затем можно извлечь и подписать специальной ключевой парой. Журнал и ключевая пара хранятся в защищенном разделе памяти и остаются доступными после выполнения любых операций конечным пользователем, в том числе и форматирования устройства. 

Атрибуты ключевой пары журнала

Для того, чтобы ключевая пара была записана в защищенном от форматирования разделе памяти, шаблоны закрытого и открытого ключа ГОСТ 34.10-2011, используемые для генерации ключевой пары журнала, должны содержать атрибут CKA_VENDOR_KEY_JOURNAL со значением СК_TRUE.

Перед генерацией ключевой пары должна быть выполнена авторизация на Рутокен PINPad с правами Пользователя.

Удаление такой ключевой пары средствами PKCS#11 невозможно. 

CK_OBJECT_CLASS GostPubKey = CKO_PUBLIC_KEY;
CK_OBJECT_CLASS GostPrivKey = CKO_PRIVATE_KEY;
CK_KEY_TYPE GostKeyType = CKK_GOSTR3410;
CK_BBOOL bTrue = CK_TRUE;
CK_BBOOL bFalse = CK_FALSE;
 
CK_ATTRIBUTE	PubKeyJournal [] =
{	{ CKA_CLASS, {amp}amp;GostPubKey, sizeof(CK_OBJECT_CLASS) },	{ CKA_KEY_TYPE, {amp}amp;GostKeyType, sizeof(CK_KEY_TYPE) },	{ CKA_TOKEN, {amp}amp;bTrue, sizeof(CK_BBOOL) },	{ CKA_PRIVATE, {amp}amp;bFalse, sizeof(CK_BBOOL) },	{ CKA_VENDOR_KEY_JOURNAL, {amp}amp;bTrue, sizeof(CK_BBOOL) },
};
CK_ATTRIBUTE	PriKeyJournal [] =
{	{ CKA_CLASS, {amp}amp;GostPrivKey, sizeof(CK_OBJECT_CLASS) },	{ CKA_KEY_TYPE, {amp}amp;GostKeyType, sizeof(CK_KEY_TYPE) },	{ CKA_TOKEN, {amp}amp;bTrue, sizeof(CK_BBOOL) },	{ CKA_PRIVATE, {amp}amp;bTrue, sizeof(CK_BBOOL) },	{ CKA_VENDOR_KEY_JOURNAL, {amp}amp;bTrue, sizeof(CK_BBOOL) },
};

Получение записи журнала

Журнал позволяет хранить только одну запись с информацией о последней выполненной операции подписи. Неудачные попытки подписи в журнале не фиксируются. Описание формата записи журнала доступно по ссылке Формат записи журнала.

Для получения записи журнала необходимо вызвать функцию C_EX_GetJournal() с переданными в нее значением слота, к которому подключен Рутокен PINPad, и данными буфера, в который будет возвращена запись журнала. Вызов функции C_EX_GetJournal() с указателем на NULL вместо буфера вернет длину записи журнала.

Перед запросом информации о журнале необходимо выполнить авторизацию на Рутокен PINPad с правами владельца ключевой пары журнала (Пользователя).

CK_BYTE_PTR pJournal = NULL_PTR;	// Указатель на значение журнала
CK_ULONG ulJournalSize = 0;	// Размер журнала
 
while(TRUE)
{
... /* Получить размер журнала */ printf("Getting journal size"); rv = pFunctionListEx-{amp}gt;C_EX_GetJournal(aSlots[0],  // Хэндл слота с подключенным токеном NULL_PTR, // Указатель на журнал {amp}amp;ulJournalSize);// Размер журнала if (rv != CKR_OK) { printf(" -{amp}gt; Failedn"); break; } printf(" -{amp}gt; OKn"); pJournal = (CK_BYTE*)malloc(ulSlotCount * sizeof(CK_BYTE)); if (pJournal == NULL) { printf("Memory allocation for pJournal failed! n"); break; } memset(pJournal, 0, (ulJournalSize * sizeof(CK_BYTE)));
  /* Получить журнал */ printf("Getting journal"); rv = pFunctionListEx-{amp}gt;C_EX_GetJournal(aSlots[0], // Хэндл слота с подключенным токеном pJournal, // Указатель на журнал {amp}amp;ulJournalSize);// Размер журнала if (rv != CKR_OK) { printf(" -{amp}gt; Failed %Xn", (int)rv); break; } printf(" -{amp}gt; OKn"); ... break;
}

Пример ниже разбирает полученную запись согласно описанному формату записи журнала. 

typedef enum {	TLVTJournal = 0x80,	TLVTHash = 0xAA,	TLVTSignature = 0xB6,	TLVTOperationInfo = 0x85,	TLVTPinPadId = 0x83,	TLVTTagTableInfo = 0x86
} TLVType;
typedef enum {	OTSignature = 0x01,
} OperationType;
typedef enum
{	RSFSTypeGCHV = 0x01,	RSFSTypeLCHV = 0x21,	RSFSTypeSGOST = 0x02,	RSFSTypeAES = 0x22,	RSFSType3DES = 0x42,	RSFSTypeAGOSTPR = 0x03,	RSFSTypeRSAPR = 0x23,	RSFSTypeAGOST_512PR = 0x43,	RSFSTypeAGOSTPU = 0x13,	RSFSTypeRSAPU = 0x33,	RSFSTypeAGOST_512PU = 0x53,	RSFSTypeX509CER = 0x14,	RSFSTypeACGOST = 0x05,	RSFSTypeSE = 0x1F
} RSFSubType;
typedef enum
{	RSFFAllowKeyExch = 0x01,	RSFFKeyForSM = 0x02,	RSFFKeySignsJournal = 0x04,	RSFFKeyImported = 0x08,	RSFFKeyPINEnter = 0x10,	RSFFKeyConfirmOp = 0x20
} RSFFlags;
typedef enum
{	OIDeviceHash = 0x01,	OIKeyPINEnter = 0x02,	OIPIN2Entered = 0x10,	OIOpConfirmed = 0x20
} OperationInfoFlags;
typedef enum
{	PPPPIN2CashDisabled = 0x01,	PPPDefPINNotRequested = 0x02,	PPPChangePINOnScreen = 0x04
} PinPadPolicyFlags;
#pragma pack(push, 1)
typedef struct OperationInfo_ {	uint8_t operationType;	uint8_t rsfType;	uint8_t rsfFlags;	uint8_t oiFlags;	uint8_t pppFlags;	uint8_t reserved;	uint8_t rsfIDHigh;	uint8_t rsfIDLow;	uint32_t ulSignatureCount;
} OperationInfo;
typedef struct TagTableInfo_ {	uint16_t tableNumber;	uint16_t tableEncoding;	uint8_t tableHash[32];
} TagTableInfo;
#pragma pack(pop)
typedef struct TLVPrinter_ {	TLVType type;	void (*print)(const CK_BYTE*);
} TLVPrinter;
uint16_t be2les(uint16_t be) {	uint16_t le;	int i = 0;	for (i = 0; i {amp}lt; sizeof(be); i) {	le = (le {amp}lt;{amp}lt; 8) | (be {amp}amp; 0xFF);	be {amp}gt;{amp}gt;= 8;	}	return le;
}
uint32_t be2lei(uint32_t be) {	uint32_t le;	int i = 0;	for (i = 0; i {amp}lt; sizeof(be); i) {	le = (le {amp}lt;{amp}lt; 8) | (be {amp}amp; 0xFF);	be {amp}gt;{amp}gt;= 8;	}	return le;
}
const CK_BYTE* FindTlVElement(CK_BYTE ubTag, const CK_BYTE* pFirstTLVElt, CK_ULONG ulLen)
{	const CK_BYTE* pLastValueByte = pFirstTLVElt ulLen;	const CK_BYTE* pTmpTag = pFirstTLVElt;	while (pTmpTag {amp}lt; pLastValueByte)	{	if (*pTmpTag == ubTag)	return pTmpTag;	pTmpTag = *(pTmpTag 1) 2;	}	return 0; // tag is not found
}
const CK_BYTE* GetTlVValue(const CK_BYTE* pTLVElt) {	return pTLVElt 2;
}
CK_ULONG GetTlVLen(const CK_BYTE* pTLVElt) {	return *(pTLVElt 1);
}
void printHexBuffer(const CK_BYTE* pBuffer, const CK_ULONG ulLen, const CK_ULONG offset) {	unsigned int i = 0, j=0;	for (i = 0; i {amp}lt; ulLen; i)	{	if(i%8 == 0)	for(j = 0; j {amp}lt; offset; j)	printf("t");	printf("X ", pBuffer[i]);	if ((i 1) % 8 == 0 || (i 1) == ulLen)	printf("n");	}
}
void printOperationType(OperationType operationType)
{	printf("ttOperation Type: ");	switch(operationType) {	case OTSignature: printf("Signature"); break;	default: printf("unknown (X)", operationType);	}	printf("n");
}
void printRSFType(RSFSubType rsfType) {	printf("ttRSF Type: ");	switch(rsfType) {	case RSFSTypeGCHV: printf("GCHV (Global PIN)"); break;	case RSFSTypeLCHV: printf("LCHV (Local PIN)"); break;	case RSFSTypeSGOST: printf("SGOST (GOST 28147-89 key)"); break;	case RSFSTypeAES: printf("AES"); break;	case RSFSType3DES: printf("3DES"); break;	case RSFSTypeAGOSTPR: printf("AGOST_PR (GOST 34.10-2001 private key)"); break;	case RSFSTypeRSAPR: printf("RSA_PR (RSA private key)"); break;	case RSFSTypeAGOST_512PR: printf("AGOST2012_PR (GOST 34.10-2012 private key)"); break;	case RSFSTypeAGOSTPU: printf("AGOST_PU (GOST 34.10-2001 public key)"); break;	case RSFSTypeRSAPU: printf("RSA_PU (RSA public key)"); break;	case RSFSTypeAGOST_512PU: printf("AGOST2012_PU (GOST 34.10-2012 public key)"); break;	case RSFSTypeX509CER: printf("X509 (X509 certificate)"); break;	case RSFSTypeACGOST: printf("ACGOST (GOST 28147-89 key)"); break;	case RSFSTypeSE: printf("SE (SE environment object)"); break;	default: printf("unknown (X)", rsfType);	}	printf("n");
}
void printRSFFlags(RSFFlags rsfFlags) {	printf("ttRSF Flags: n");	if (rsfFlags{amp}amp;RSFFAllowKeyExch) printf("tttKey Exchange Allowedn");	if (rsfFlags{amp}amp;RSFFKeyForSM) printf("tttKey used for SMn");	if (rsfFlags{amp}amp;RSFFKeySignsJournal) printf("tttKey used to sign journaln");	if (rsfFlags{amp}amp;RSFFKeyImported) printf("tttKey was importedn");	if (rsfFlags{amp}amp;RSFFKeyPINEnter) printf("tttKey usage requires PIN2 entern");	if (rsfFlags{amp}amp;RSFFKeyConfirmOp) printf("tttKey usage requires operation confirmationn");	if (!rsfFlags)	printf("tttNonen");
}
void printOperationInfoFlags(OperationInfoFlags oiFlags) {	printf("ttOperation Info Flags: n");	if (oiFlags{amp}amp;OIDeviceHash) printf("tttHash was performed on devicen");	if (oiFlags{amp}amp;OIKeyPINEnter) printf("tttKey used requires PIN2 entern");	if (oiFlags{amp}amp;OIPIN2Entered) printf("tttPIN2 was enteredn");	if (oiFlags{amp}amp;OIOpConfirmed) printf("tttOperation was confirmedn");	if (!oiFlags)	printf("tttNonen");
}
void printPinPadPolicyFlags(PinPadPolicyFlags pppFlags) {	printf("ttPinPad Policy Flags: n");	if (pppFlags{amp}amp;PPPPIN2CashDisabled) printf("tttPIN2 cache disabledn");	if (pppFlags{amp}amp;PPPDefPINNotRequested) printf("tttDefault PIN2 not requested on PIN2 changen");	if (pppFlags{amp}amp;PPPChangePINOnScreen) printf("tttPIN2 can be change on screen onlyn");	if (!pppFlags)	printf("tttNonen");
}
void printRSFID(CK_BYTE rsfIDHigh, CK_BYTE rsfIDLow) {	unsigned int rsfID = ((unsigned int)rsfIDHigh{amp}lt;{amp}lt;8) | rsfIDLow;	printf("ttRSF Id: Xn", rsfID);
}
void printSignatureCount(uint32_t ulSignatureCount) {	uint32_t ulSignatureCountLE = 0;	unsigned int i = 0;	for (i = 0; i {amp}lt; sizeof(ulSignatureCount); i) {	ulSignatureCountLE = (ulSignatureCountLE {amp}lt;{amp}lt; 8) | (ulSignatureCount {amp}amp; 0xFF);	ulSignatureCount {amp}gt;{amp}gt;= 8;	}	printf("ttSuccessfull Signature Count: %dn", ulSignatureCountLE);
}
void printOperationInfo(const CK_BYTE* pTLVElt) {	const OperationInfo* pOperationInfo = (const OperationInfo*)GetTlVValue(pTLVElt);	printf("tOperation Info:n");	printOperationType(pOperationInfo-{amp}gt;operationType);	printRSFType(pOperationInfo-{amp}gt;rsfType);	printRSFFlags(pOperationInfo-{amp}gt;rsfFlags);	printOperationInfoFlags(pOperationInfo-{amp}gt;oiFlags);	printPinPadPolicyFlags(pOperationInfo-{amp}gt;pppFlags);	printRSFID(pOperationInfo-{amp}gt;rsfIDHigh, pOperationInfo-{amp}gt;rsfIDLow);	printSignatureCount(pOperationInfo-{amp}gt;ulSignatureCount);
}
void printTableNumber(uint16_t tableNumber) {	printf("ttTable number: %dn", be2les(tableNumber));
}
void printTableEncoding(uint16_t tableEncoding) {	printf("ttTable encoding: ");	switch(be2les(tableEncoding)) {	case 0: printf("CP-1251"); break;	case 1: printf("UTF-8"); break;	default: printf("unknown");	}	printf("n");
}
void printTableHash(const CK_BYTE* pTableHash, CK_ULONG ulLen) {	printf("ttHash:n");	printHexBuffer(pTableHash, ulLen, 3);
}
void printTagTableInfo(const CK_BYTE* pTLVElt) {	const TagTableInfo* pTagTableInfo = (const TagTableInfo*)GetTlVValue(pTLVElt);	printf("tTag Table Info:n");	printTableNumber(pTagTableInfo-{amp}gt;tableNumber);	printTableEncoding(pTagTableInfo-{amp}gt;tableEncoding);	printTableHash(pTagTableInfo-{amp}gt;tableHash, sizeof(pTagTableInfo-{amp}gt;tableHash));
}
void printHash(const CK_BYTE* pTLVElt) {	printf("tHash:n");	printHexBuffer(GetTlVValue(pTLVElt), GetTlVLen(pTLVElt), 2);
}
void printSignature(const CK_BYTE* pTLVElt) {	printf("tSignature:n");	printHexBuffer(GetTlVValue(pTLVElt), GetTlVLen(pTLVElt), 2);
}
void printPinPadId(const CK_BYTE* pTLVElt) {	unsigned int i = 0;	const CK_BYTE* id = GetTlVValue(pTLVElt);	printf("tPinPad Id: ");	for (i = 0; i {amp}lt; GetTlVLen(pTLVElt); i)	printf("X", id[i]);	printf("n");
}
void printJournal(const CK_BYTE* pBuffer, CK_ULONG ulLength)
{	const CK_BYTE* pJournal = NULL;	const CK_BYTE* pJournalBody = NULL;	CK_ULONG ulJournalLength = 0;	TLVPrinter journalPrinters[] = {	{TLVTHash, printHash},	{TLVTSignature, printSignature},	{TLVTOperationInfo, printOperationInfo},	{TLVTPinPadId, printPinPadId},	{TLVTTagTableInfo, printTagTableInfo}	};	unsigned int i = 0;	printf("PinPad Journal: ");	pJournal = FindTlVElement(TLVTJournal, pBuffer, ulLength);	if (!pJournal) {	printf("not foundn");	return;	} else {	printf("n");	}	pJournalBody = GetTlVValue(pJournal);	ulJournalLength = GetTlVLen(pJournal);	for (i = 0; i {amp}lt; arraysize(journalPrinters); i) {	const CK_BYTE* pTLVElt = FindTlVElement(journalPrinters[i].type, pJournalBody, ulJournalLength);	if (pTLVElt)	journalPrinters[i].print(pTLVElt);	}
}
 
int main(void)
{	...	if (pJournal)	{	printJournal(pJournal, ulJournalSize);	free(pJournal);	pJournal = NULL_PTR;	}	else	printf("Journal is emptyn");	...
}

Подпись журнала

Подписанная ключевой парой журнала запись журнала может служить доказательством того, что подпись была выполнена именно этим устройством Рутокен PINPad. Удостоверяющаяся сторона проверяет полученную подпись записи журнала открытым ключом и делает вывод о подлинности устройства. Кроме того, из самой записи журнала может быть получена дополнительная информация о выполненной операции подписи (хэш и подпись сообщения, используемый для подписи ключ, действующий на момент подписи политики устройства и т.д.).

Подпись журнала осуществляется только функциями C_EX_SignInvisibleInit() и C_EX_SignInvisible(). В  C_EX_SignInvisibleInit() передается идентификатор сессии, механизма и закрытого ключа журнальной пары. В C_EX_SignInvisible() передаются предварительно прохешированные данные, возвращенные функцией C_EX_GetJournal(), для определения размера подписанных данных и вторым вызовом C_EX_SignInvisible() выполняет подпись. В случае, если переданные в C_EX_SignInvisible() данные не совпадут с хранящимися в журнале, подпись выполнена не будет.

Операция подписи записи журнала в самом журнале не фиксируется.

Полезные ссылки

Как работать с Rutoken и Rutoken ЭЦП

Демосистема Рутокен ПлагинWEB-сервис генерации ключей, формирования запросов, управления сертификатами, формирования шаблонов запросов на сертификаты Документация по использованию утилиты openssl с российскими крипталгоритмами

Читайте также:  Электронная подпись на бумажном документе | Такском
Оцените статью
ЭЦП Эксперт