Простой ASN1-кодек на базе sprintf
Транспортный синтаксис ASN.1 определяет однозначный способ преобразования значений переменных допустимых типов в последовательность байт для передачи по сети. В ASN.1 он называется базовыми правилами кодирования (Basic Encoding Rules, BER). Правила являются рекурсивными, так что кодирование составных объектов представляет собой составление в цепочку закодированных последовательностей составляющих объектов. Протокол ASN.1 описывает структуру данных простым и понятным языком.
Каждое передаваемое значение — как базового, так и производного типа — состоит из трех полей:
- идентификатор;
- длина поля данных (в байтах);
- поле данных.
Если всегда указывать длину поля данных (я считаю это правилом хорошего тона), то флаг конца поля данных не используется.
Есть много разных компиляторов для ASN.1, как платных, так и бесплатных, для разных языков программирования, но нам хотелось бы иметь под рукой что-то очень простое.
Подавляющее большинство разработчиков программ считают стандарт ASN.1 сложным. Я тоже так думал до недавнего времени. Работая в сфере ИОК/PKI/криптографии практически каждый день имеешь дело с ASN1-структурами в виде сертификатов X509, запросов на сертификаты, списки отозванных сертификатов. И этот список можно продолжать. И вот, работая над утилитой по созданию запроса на сертификат в формате PKCS#10 с генерацией ключевой пары на токене/смарткартк PKCS#11, мне, естественно, пришлось формировать, в частности, asn1-структуру публичного ключа для его записи в запрос на сертификат:
C-Sequence
C-Sequence (<длина>)
Object Identifier (<длина>)
C-Sequence (<длина>)
Object Identifier (<длина>)
Object Identifier (<длина>)
Bit String (<длина>)
<значение публичного ключа>
Поскольку в качестве СКЗИ мы задействовали токен PKCS#11 с поддержкой российской криптографии, то исходный материал для данной структуры был получен с токена в соответствии со следующим шаблоном:
CK_BYTE gostr3410par[12];
CK_BYTE gostr3411par[12];
CK_ULONG gostr3410par_len;
CK_ULONG gostr3411par_len;
CK_BYTE pubkey[128];
CK_ULONG pubkeu_len;
CK_KEY_TYPE key_type;
CK_ATTRIBUTE templ_pk[] = {
. . .
{CKA_GOSTR3410PARAMS, gostr3410par, sizeof(gostr3410par)},
{CKA_GOSTR3411PARAMS, gostr3411par, sizeof(gostr3410par)},
{CKA_VALUE, pubkey, sizeof(pubkey)},
{CKA_KEY_TYPE, &key_type, sizeof(key_type)}
}
Напрямую из этой структуры для заполнения asn1-publickeyinfo будут задействованы значения атрибута CKA_VALUE, содержащее значение открытого ключа, и значения атрибутов CKA_GOSTR3410PARAMS и CKA_GOSTR3411PARAMS, которые содержат oid-ы параметра подписи и параметра хэша.
Атрибут CKA_KEY_TYPE, который может принимать значения CKK_GOSTR3410 и CKK_GOSTR3410_512 (в условиях когда алгоритм подписи ГОСТ Р 34.10–2001 продолжает действовать) неоднозначно определяет алгоритм ключевой пары. Если значение атрибута CKA_KEY_TYPE равно CKK_GOSTR3410_512, то, конечно, он одназначно указывает на алгоритм ГОСТ Р 34.10–2012 с длиной ключа в 512 бит (oid = 1.2.643.7.1.1.1.2). А вот если он равен просто CKK_GOSTR3410, то возникает двусмысленность, к какому типу ключа относится данный ключ: ГОСТ Р 34.10–2001 или все же это ГОСТ Р 34.10–2012 с длиной ключа 256 бит. Эту двусмысленность помогает разрешить атрибут CKA_GOSTR3411PARAMS.
Сразу отметим, что параметры CKA_GOSTR3410PARAMS и CKA_GOSTR3411PARAMS на токене в соответствии с рекомендациями ТК-26 хранятся в виде объектного идентификатора, закодированного oid-а, например:
\x06\x06\x2a\x85\x03\x02\x02\x13, где нулевой байт определяет тип последовательности (0×06 — объектный идентификатор, см. таблицу ниже), во втором байте указана длина (в общем случае длина может занимать несколько байт, но об этом ниже) поля данных, в котором хранится oid в бинарном виде.
Если этот параметр содержит oid алгоритма хэша ГОСТ Р 34.10–2012 с длиной 256 бита (oid=1.2.643.7.1.1.2.2, в бинарном виде »\x2a\x 85\x 03\x 07\x 01\x 01\x 02\x02»), то тип ключа должен быть установлен как ГОСТ Р 34.10–2012 с длиной ключа 256 бит. В противном случае это ключ ГОСТ Р 34.10–2001. Алгоритм определения типа ключа может выглядеть так:
. . .
for (curr_attr_idx = 0; curr_attr_idx < (sizeof(templ_pk)/sizeof(templ_pk[0])); curr_attr_idx++){
curr_attr = &templ_pk[curr_attr_idx];
if (!curr_attr->pValue) {
continue;
}
swith (curr_attr->type) {
. . .
case CKA_VALUE:
/*Длина публичного ключа*/
pubkey_len = curr_attr->ulValueLen;
break;
case CKA_GOSTR3410PARAMS:
/*Длина объектного идентификатора алгоритма подписи*/
gostr3410par_len = curr_attr->ulValueLen;
break;
case CKA_GOSTR3410PARAMS:
/*Длина объектного идентификатора хэш*/
gostr3411par_len = curr_attr->ulValueLen;
break;
case CKA_KEY_TYPE:
ulattr = curr_attr->pValue;
if (*ulattr == CKK_GOSTR3410) {
if (!memmem(gostr3411par), gostr3411par_len,"\x06\x08\x2a\x85\x03\x07", 6)) {
/*Тип ключа ГОСТ Р 34.10-2001*/
strcpy(oid_key_type, "1.2.643.2.2.19");
memcpy(oid_key_type_asn1("\x06\x06\x2a\x85\x03\x02\x02\x13", 8);
} else {
/*Тип ключа ГОСТ Р 34.10-2012-256*/
strcpy(oid_key_type, ("1 2 643 7 1 1 1 1");
memcpy(oid_key_type_asn1 ("\x06\x08\x2a\x85\x03\x07\x01\x01\x01\x01", 10);
}
} else if (*ulattr == CKK_GOSTR3410_512) {
/*Тип ключа ГОСТ Р 34.10-2012-512*/
strcpy(oid_key_type, ("1 2 643 7 1 1 1 2");
memcpy(oid_key_type_asn1 ("\x06\x08\x2a\x85\x03\x07\x01\x01\x01\x02", 10);
} else {
fprintf(stderr, "tclpkcs11_perform_pki_keypair CKK_GOSTR ERROR\n");
return (-1)
}
break;
. . .
}
}
. . .
Теперь у нас есть все исходные данные для создания asn1- структуры публичного ключа.
Напомним, что каждый элемент asn1-структуры состоит из трех полей:
- идентификатор;
- длина поля данных (в байтах);
- поле данных.
Приведем таблицу кодирования некоторых типов идентификаторов, используемых в ИОК/PKI:
Наименование типа | Краткое описание | Представление типа в DER-кодировке |
---|---|---|
SEQUENCE | Используется для описания структуры данных, состоящей из различных типов. | 30 |
INTEGER | Целое число. | 02 |
OBJECT IDENTIFIER | Последовательность целых чисел. | 06 |
UTCTime | Временной тип, содержит 2 цифры для определения года | 17 |
GeneralizedTime | Расширенный временной тип, содержит 4 цифры для обозначения года. | 18 |
SET | Описывает структуру данных разных типов. | 31 |
UTF8String | Описывает строковые данные. | 0C |
NULL | Собственно NULL | 05 |
BIT STRING | Тип для хранения последовательности бит. | 03 |
OCTET STRING | Тип для хранения последовательности байт | 04 |
При работе с asn1-структурами наибольший шок для непосвященных вызывает метод кодирования длины поля данных, особенно при его формировании, да если еще учитывать архитектуру компьютера (littleendien, bigendien). Это целая наука. И вот в процессе рассуждения об алгоритме формирования этого поля, на ум пришла мысль использовать функцию sprintf, которая сама будет учитывать архитектуру, а как определяется количество байт для хранения длины видно по коду функции, которая готовит буфер с идентификатором типа данных и длиной данных:
unsigned char *wrap_id_with_length(unsigned char type, //тип данных
unsigned long length, //Длина данных
unsigned long *lenasn) //Возврат длины asn1-структуры
{
// unsigned long length;
int buflen = 0;
unsigned char *buf;
char *format;
char *buf_for_len[100];
const char *s;
/*Формат вывода заголовка в зависимости от длины данных*/
char f0[] = "%02x%02x";
char f1[] = "%02x81%02x";
char f2[] = "%02x82%04x";
char f3[] = "%02x83%06x";
char f4[] = "%02x84%08x";
/*Определяем длину буфера для типа и длины данных */
buflen = ( length < 0x80 ? 1:
length <= 0xff ? 2:
length <= 0xffff ? 3:
length <= 0xffffff ? 4: 5);
/*Выделяем буфер для asn-структуры*/
buf = malloc(length + buflen);
// buf = malloc(buflen);
/*В зависимости от длины данных выбираем формат для sprintf*/
switch (buflen - 1) {
case 0:
format = f0;
break;
case 1:
format = f1;
break;
case 2:
format = f2;
break;
case 3:
format = f3;
break;
case 4:
format = f4;
break;
}
//Через sprintf мы решаем проблемы little и bigendian и упаковываем тип поля и длину
sprintf((char*)buf_for_len, (const char *)format, type, length);
length = 0;
/*Печатаем asn1-заголовок*/
fprintf(stderr, "ASN1 - заголовок:%s\n", buf_for_len);
/*Из шестнадцатеричного вида в бинарный вид*/
for (s=(const char *)buf_for_len; *s; s +=2 )
{
if (!hexdigitp (s) || (!hexdigitp (s+1) && hexdigitp (s+1) != 0) ){
fprintf (stderr, "invalid hex digits in \"%s\"\n", buf_for_len);
*lenasn = 0;
return NULL;
}
((unsigned char*)buf)[length++] = xtoi_2 (s);
}
*lenasn = length;
return (buf);
}
Функция возвращает указатель на буфер с asn1-структурой, выделенный с учетом длины данных. Осталось эти данные скопировать в полученный буфер со смещением на длину заголовка. Длина заголовка возвращается через параметр lenasn.
Для того, чтобы проверить как работает эта функция, напишем простую утилиту:
#include
#include
#define digitp(p) (*(p) >= '0' && *(p) <= '9')
#define hexdigitp(a) (digitp (a) \
|| (*(a) >= 'A' && *(a) <= 'F') \
|| (*(a) >= 'a' && *(a) <= 'f'))
#define xtoi_1(p) (*(p) <= '9'? (*(p)- '0'): \
*(p) <= 'F'? (*(p)-'A'+10):(*(p)-'a'+10))
#define xtoi_2(p) ((xtoi_1(p) * 16) + xtoi_1((p)+1))
int main (int argc, char *argv[]) {
unsigned char *hdrasn;
unsigned char type;
unsigned long length;
unsigned long lenasn;
if (argc != 3) {
fprintf (stderr, "Usage: wrap_id_with_length \n");
exit(-1);
}
type = atoi(argv[1]);
length = atol(argv[2]);
fprintf (stderr, " \n", type, length);
if (length == 0) {
fprintf (stderr, "Bad length=%s\nUsage: wrap_id_with_length \n", argv[2]);
exit(-1);
}
hdrasn = wrap_id_with_length(type, length, &lenasn);
fprintf (stderr, "Length asn1-buffer=%lu, LEN_HEADER=%lu, LEN_DATA=%lu\n", lenasn, lenasn - length, length);
}
Сохраним ее вместе с функцией wrap_id_with_length в файле wrap_id_with_length.c.
Оттранслируем:
$cc –o wrap_id_with_length wrap_id_with_length.c
$
Полученную программу запустим с различными исходными данными. Тип данных задается десятичным числом.
Полученную программу запустим с различными исходными данными. Тип данных задается десятичным числом:
bash-4.3$ ./wrap_id_with_length 06 8
ASN1 - заголовок:0608
Length asn1-buffer=10, LEN_HEADER=2, LEN_DATA=8
bash-4.3$ ./wrap_id_with_length 06 127
ASN1 - заголовок:067f
Length asn1-buffer=129, LEN_HEADER=2, LEN_DATA=127
bash-4.3$ ./wrap_id_with_length 48 128
ASN1 - заголовок:308180
Length asn1-buffer=131, LEN_HEADER=3, LEN_DATA=128
bash-4.3$ ./wrap_id_with_length 48 4097
ASN1 - заголовок:30821001
Length asn1-buffer=4101, LEN_HEADER=4, LEN_DATA=4097
bash-4.3$
Проверить правильность формирования заголовка можно с помощью любого калькулятора:
Все мы готовы формировать любую ASN1-структуру. Но прежде внесем небольшие изменения в функцию wrap_id_with_length и назовем ее
unsigned char *wrap_for_asn1(unsigned char type, unsigned char *prefix, unsigned long prefix_len, unsigned char *wrap, unsigned long wrap_len, unsigned long *lenasn){
unsigned long length;
int buflen = 0;
unsigned char *buf;
char *format;
const char buf_for_len[100];
const char *s;
char f0[] = "%02x%02x";
char f1[] = "%02x81%02x";
char f2[] = "%02x82%04x";
char f3[] = "%02x83%06x";
char f4[] = "%02x84%08x";
length = prefix_len + wrap_len;
buflen += ( length <= 0x80 ? 1:
length <= 0xff ? 2:
length <= 0xffff ? 3:
length <= 0xffffff ? 4: 5);
buf = malloc(length + buflen);
switch (buflen - 1) {
case 0:
format = f0;
break;
case 1:
format = f1;
break;
case 2:
format = f2;
break;
case 3:
format = f3;
break;
case 4:
format = f4;
break;
}
//Через sprintf мы решаем проблемы little и bigendian и вычисляем длину
sprintf((char*)buf_for_len, (const char *)format, type, length);
length = 0;
for (s=buf_for_len; *s; s +=2 )
{
if (!hexdigitp (s) || (!hexdigitp (s+1) && hexdigitp (s+1) != 0) ){
fprintf (stderr, "invalid hex digits in \"%s\"\n", buf_for_len);
}
((unsigned char*)buf)[length++] = xtoi_2 (s);
}
if (prefix_len > 0) {
memcpy(buf + length, prefix, prefix_len);
}
memcpy(buf + length + prefix_len, wrap, wrap_len);
*lenasn = (unsigned long)(length + prefix_len + wrap_len);
return (buf);
}
Как видно, изменения минимальные. В качестве входных параметров добавлены сами данные, которые внутри функции пакуются в asn1-структуру. Причем, на вход можно подавать сразу два буфера. Это, как нам кажется, удобно.
Прежде чем представить контрольный пример дадим коды еще трех функций. Первая функция oid2buffer преобразует oid-ы из точечно-десятичной формы в DER-кодировку. Нам эта функция потребуется для преобразования, в частности, oid-ов ключевой пары (см.выше).
char *curstr;
char *curstr1;
char *nextstr;
unsigned int firstval;
unsigned int secondval;
unsigned int val;
unsigned char buf[5];
int count;
unsigned char oid_hex[100];
char *res;
int i;
if (oid_str == NULL) {
*len = 0;
return NULL;
}
*len = 0;
curstr = strdup ((const char*)oid_str);
curstr1 = curstr;
nextstr = strchr (curstr, '.');
if (nextstr == NULL) {
*len = 0;
return NULL;
}
*nextstr = '\0';
firstval = atoi (curstr);
curstr = nextstr + 1;
nextstr = strchr (curstr, '.');
if (nextstr) {
*nextstr = '\0';
}
secondval = atoi (curstr);
if (firstval > 2) {
*len = 0;
return NULL;
}
if (secondval > 39) {
*len = 0;
return NULL;
}
oid_hex[0] = (unsigned char)((firstval * 40) + secondval);
i = 1;
while (nextstr) {
curstr = nextstr + 1;
nextstr = strchr (curstr, '.');
if (nextstr) {
*nextstr = '\0';
}
memset (buf, 0, sizeof (buf));
val = atoi (curstr);
count = 0;
if (curstr[0] != '0')
while (val) {
buf[count] = (val & 0×7f);
val = val >> 7;
count++;
}
else{
buf[count] = (val & 0×7f);
val = val >> 7;
count++;
}
while (count--) {
if (count) {
oid_hex[i] = buf[count] | 0×80;
} else {
oid_hex[i] = buf[count];
}
i++;
}
}
res = (char*) malloc (i);
if (res){
memcpy (res, oid_hex, i);
*len = i;
}
free (curstr1);
return res;
}
Две остальные функции позволяют бинарный буфер преобразовать в шестнадцатеричный кол (buffer2hex) и обратно (hex2buffer).
buffer2hex (const unsigned char *src, size_t len)
{
int i;
char *dest;
char *res;
dest = (char *)malloc (len * 2 + 1);
res = dest;
if (dest)
{
for (i=0; i
sprintf (dest,»%02X», src[i]);
}
return res;
}
static void *
hex2buffer (const char *string, size_t *r_length)
{
const char *s;
unsigned char *buffer;
size_t length;
buffer = malloc (strlen (string)/2+1);
length = 0;
for (s=string; *s; s +=2)
{
if (! hexdigitp (s) || ! hexdigitp (s+1)){
fprintf (stderr, «invalid hex digits in \»%s\»\n», string);
}
((unsigned char*)buffer)[length++] = xtoi_2 (s);
}
*r_length = length;
return buffer;
}
Эти функции очень удобны при отладке и наверняка у многих они есть.
И вот теперь возвращаемся к решению поставленной задачи, получению asn1-структуры публичного ключа. Напишем утилиту, которая сформирует и сохранит в файле ASN1_PIBINFO.der asn1-структуру публичного ключа.
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define digitp(p) (*(p) >= '0' && *(p) <= '9')
#define hexdigitp(a) (digitp (a) \
|| (*(a) >= 'A' && *(a) <= 'F') \
|| (*(a) >= 'a' && *(a) <= 'f'))
#define xtoi_1(p) (*(p) <= '9'? (*(p)- '0'): \
*(p) <= 'F'? (*(p)-'A'+10):(*(p)-'a'+10))
#define xtoi_2(p) ((xtoi_1(p) * 16) + xtoi_1((p)+1))
/*Вставьте код функции oid2buffer*/
/*Вставьте код функций buffer2hex и hex2buffer*/
/*Вставьте код функции wrap_for_asn1*/
int main() {
int fd;
unsigned char *asn, *asn1, *asn2, *asn3, *pubkeyalgo;
unsigned char* pubkey_bin;
//Исходные данные
char gost3410par[] = "\x06\x7\x2a\x85\x03\x02\x02\x23\x01";
unsigned long gost3410par_len = sizeof(gost3410par) - 1;
char gost3411par[] = "\x06\x8\x2a\x85\x03\x07\x01\x01\x02\x02";
unsigned long gost3411par_len = sizeof(gost3411par) - 1;
unsigned char pubkey_hex[] = "9af03570ed0c54cd4953f11ab19e551022cd48603326c1b9b630b1cff74e5a160ba1718166cc22bf70f82bdc957d924c501b9332491cb3a36ce45770f05487b5";
char pubkey_oid_2001[] = "1.2.643.2.2.19";
char pubkey_oid_2012_256[] = "1.2.643.7.1.1.1.1";
char pubkey_oid_2012_512[] = "1.2.643.7.1.1.1.2";
unsigned long pubkey_len, pubkey_len_full, len10, len11, len12, lenalgo;
unsigned char *pkalgo;
unsigned long pkalgo_len;
uint16_t x = 1; /* 0x0001 */
printf("%s\n", *((uint8_t *) &x) == 0 ? "big-endian" : "little-endian");
////pubkeyinfo
//Определяем тип ключа по алгоритмы хэш
if (!memmem(gost3411par, 8, "\x2a\x85\x03\x07", 4)) {
//хэш ГОСТ Р 34.11-94, тип ключа ГОСТ Р 34.10-2001 - 1.2.643.2.2.19
pubkeyalgo = (unsigned char *)oid2buffer(pubkey_oid_2001, &lenalgo);
} else if (!memcmp(gost3411par, "\x2a\x85\x03\x07\x01\x01\x02\x02", 8)){
//хэш ГОСТ Р 34.11-2012-256, тип ключа ГОСТ Р 34.10-2012-256 - 1.2.643.7.1.1.1.1
pubkeyalgo = (unsigned char *)oid2buffer(pubkey_oid_2012_256, &lenalgo);
} else {
//хэш ГОСТ Р 34.11-2012-512, тип ключа ГОСТ Р 34.10-2012-512 - 1.2.643.7.1.1.1.2
pubkeyalgo = (unsigned char *)oid2buffer(pubkey_oid_2012_512, &lenalgo);
}
pubkey_bin =(unsigned char*)hex2buffer((const char *)pubkey_hex, &pubkey_len);
//Упаковываем значение публичного ключа
asn1 = wrap_for_asn1_bin('\x04', (unsigned char *)"", 0, pubkey_bin, pubkey_len, &pubkey_len);
asn = wrap_for_asn1_bin('\x03', (unsigned char *)"\x00", 1, asn1, pubkey_len, &pubkey_len_full);
fprintf(stderr, "PUBLIC_VALUE=%s\n", buffer2hex(asn, pubkey_len_full));
free(asn1);
//Упаковываем параметры
asn3 = wrap_for_asn1_bin('\x30', (unsigned char*)gost3410par, gost3410par_len, (unsigned char *)gost3411par, gost3411par_len, &len12);
fprintf(stderr, "\nPARAMS len12=%lu, FULL=%s\n", len12, buffer2hex(asn3, len12));
//Упаковываем тип ключа
pkalgo = wrap_for_asn1_bin('\x06', (unsigned char *)"", 0, pubkeyalgo, lenalgo, &pkalgo_len);
//Упаковываем тип ключа с параметрами
asn2 = wrap_for_asn1_bin('\x30', pkalgo, pkalgo_len, asn3, len12, &len11);
fprintf(stderr, "PubKEY=%s\n", buffer2hex(asn3, len11));
asn1 = wrap_for_asn1_bin('\x30', asn2, len11, asn, pubkey_len_full, &len10);
free(asn2); free(asn3);
fprintf(stderr, "\n%s\n", buffer2hex(asn1, len10));
fd = open ("ASN1_PUBINFO.der", O_TRUNC|O_RDWR|O_CREAT,S_IRWXO);
write(fd, asn1, len10);
close(fd);
free(asn1);
chmod("ASN1_PUBINFO.der", 0666);
}
Для проверки результата воспользуемся утилитами derdump и pp из состава пакета NSS.
Первая утилита нам покажет asn1-структуру публичного ключа:
$ derdump -i ASN1_PUBINFO.der
C-Sequence (102)
C-Sequence (31)
Object Identifier (8)
1 2 643 7 1 1 1 2 (GOST R 34.10-2012 Key 512)
C-Sequence (19)
Object Identifier (7)
1 2 643 2 2 35 1
Object Identifier (8)
1 2 643 7 1 1 2 2 (GOST R 34.11-2012 256)
Bit String (67)
00 04 40 9a f0 35 70 ed 0c 54 cd 49 53 f1 1a b1 9e 55 10 22 cd 48
60 33 26 c1 b9 b6 30 b1 cf f7 4e 5a 16 0b a1 71 81 66 cc 22 bf 70
f8 2b dc 95 7d 92 4c 50 1b 93 32 49 1c b3 a3 6c e4 57 70 f0 54 87
b5
$
Вторая покажет содержание ключа:
$ pp -t pk -i ASN1_PUBINFO.der
Public Key:
Subject Public Key Info:
Public Key Algorithm: GOST R 34.10-2012 512 Public Key:
PublicValue:
9a:f0:35:70:ed:0c:54:cd:49:53:f1:1a:b1:9e:55:10:
22:cd:48:60:33:26:c1:b9:b6:30:b1:cf:f7:4e:5a:16:
0b:a1:71:81:66:cc:22:bf:70:f8:2b:dc:95:7d:92:4c:
50:1b:93:32:49:1c:b3:a3:6c:e4:57:70:f0:54:87:b5
GOSTR3410Params: OID.1.2.643.2.2.35.1
GOSTR3411Params: GOST R 34.11-2012 256
$
Желающие могут перепроверить, например утилитой openssl желательно с подключенным ГОСТ-овым engine:
$ /usr/local/lirssl_csp_64/bin/lirssl_static asn1parse -inform DER -in ASN1_PUBINFO.der
0:d=0 hl=2 l= 102 cons: SEQUENCE
2:d=1 hl=2 l= 31 cons: SEQUENCE
4:d=2 hl=2 l= 8 prim: OBJECT :GOST R 34.10-2012 with 512 bit modulus
14:d=2 hl=2 l= 19 cons: SEQUENCE
16:d=3 hl=2 l= 7 prim: OBJECT :id-GostR3410-2001-CryptoPro-A-ParamSet
25:d=3 hl=2 l= 8 prim: OBJECT :GOST R 34.11-2012 with 256 bit hash
35:d=1 hl=2 l= 67 prim: BIT STRING
$
Как видим, полученная ASN1структура везде успешно проходит проверку.
Предложенный алгоритм и утилита формирование asn1-структур не требует использования никаких ASN1-компиляторов и доролнительных библиотек (той же openssl) и оказались очень удобными в использовании. Мы их еще вспомним в следующей статье, когда исполнится пожелание Pas и будет представлена графическая утилита, делающая не только «парсинг сертификатов» и проверку их валидности, но и генерирующая ключевую пару на токенах PKCS#11, формирующая и подписывающая запрос на квалифицированным сертификат. С этим запросом можно смело отправляться на УЦ за сертификатом. Опережая вопросы, сразу отмечу, что в последнем случае токен должен быть сертифицирован как СКЗИ в системе сертификации ФСБ России.