Генерация ключевой пары
Для того, чтобы проверить готовность ключевого идентификатора Рутокен к настройке для работы с ЕГАИС, откройте “Панель управления Рутокен” – вкладка “Администрирование” – кнопка “Информация” – и проверьте статус напротив поля “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”:

В этом случае необходимо нажать на ссылку “Активировать”, для того чтобы включить возможность поддержки криптопровайдера “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() данные не совпадут с хранящимися в журнале, подпись выполнена не будет.
Операция подписи записи журнала в самом журнале не фиксируется.
Полезные ссылки

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