Генерация ключевой пары
Для того, чтобы проверить готовность ключевого идентификатора Рутокен к настройке для работы с ЕГАИС, откройте “Панель управления Рутокен” – вкладка “Администрирование” – кнопка “Информация” – и проверьте статус напротив поля “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_SignInvisibleIn
it()
передается идентификатор сессии, механизма и закрытого ключа журнальной пары. В C_EX_SignInvisible()
передаются предварительно прохешированные данные, возвращенные функцией C_EX_GetJournal()
, для определения размера подписанных данных и вторым вызовом C_EX_SignInvisible()
выполняет подпись. В случае, если переданные в C_EX_SignInvisible()
данные не совпадут с хранящимися в журнале, подпись выполнена не будет.
Операция подписи записи журнала в самом журнале не фиксируется.
Полезные ссылки
Демосистема Рутокен ПлагинWEB-сервис генерации ключей, формирования запросов, управления сертификатами, формирования шаблонов запросов на сертификаты Документация по использованию утилиты openssl с российскими крипталгоритмами